From 469d23b0b93b9dc8f9dc027152cba151532cd3b4 Mon Sep 17 00:00:00 2001 From: xiongxiao Date: Thu, 19 Mar 2026 01:46:16 +0800 Subject: [PATCH] Auto commit: 2026-03-19 01:46 --- src/modules/mark-api.ts | 284 ++++++++++++ src/pages/repos/modules/CreateRepoDialog.tsx | 10 +- src/pages/repos/modules/SyncRepoDialog.tsx | 115 ----- src/pages/repos/page.tsx | 2 - src/pages/repos/store/build.ts | 45 +- src/pages/repos/store/index.ts | 25 +- src/pages/workspaces/page.tsx | 427 ++++++++++++++++++- src/pages/workspaces/store/index.ts | 134 +++++- 8 files changed, 850 insertions(+), 192 deletions(-) create mode 100644 src/modules/mark-api.ts delete mode 100644 src/pages/repos/modules/SyncRepoDialog.tsx diff --git a/src/modules/mark-api.ts b/src/modules/mark-api.ts new file mode 100644 index 0000000..5d1924f --- /dev/null +++ b/src/modules/mark-api.ts @@ -0,0 +1,284 @@ +import { createQueryApi } from '@kevisual/query/api'; +const api = { + "mark": { + /** + * mark list. + * + * @param data - Request parameters + * @param data.page - {number} 页码 + * @param data.pageSize - {number} 每页数量 + * @param data.search - {string} 搜索关键词 + * @param data.markType - {string} mark类型,simple,wallnote,md,draw等 + * @param data.sort - {"DESC" | "ASC"} 排序字段 + */ + "list": { + "path": "mark", + "key": "list", + "description": "mark list.", + "metadata": { + "args": { + "page": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "页码", + "type": "number", + "optional": true + }, + "pageSize": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "每页数量", + "type": "number", + "optional": true + }, + "search": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "搜索关键词", + "type": "string", + "optional": true + }, + "markType": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "mark类型,simple,wallnote,md,draw等", + "type": "string", + "optional": true + }, + "sort": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "default": "DESC", + "description": "排序字段", + "type": "string", + "enum": [ + "DESC", + "ASC" + ], + "optional": true + } + }, + "url": "/api/router", + "source": "query-proxy-api" + } + }, + /** + * @param data - Request parameters + * @param data.id - {string} mark id + */ + "getVersion": { + "path": "mark", + "key": "getVersion", + "metadata": { + "args": { + "id": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "string", + "description": "mark id" + } + }, + "url": "/api/router", + "source": "query-proxy-api" + } + }, + /** + * @param data - Request parameters + * @param data.id - {string} mark id + */ + "get": { + "path": "mark", + "key": "get", + "metadata": { + "args": { + "id": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "string", + "description": "mark id" + } + }, + "url": "/api/router", + "source": "query-proxy-api" + } + }, + /** + * @param data - Request parameters + * @param data.id - {string} mark id + */ + "update": { + "path": "mark", + "key": "update", + "metadata": { + "args": { + "id": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "string", + "description": "mark id" + } + }, + "url": "/api/router", + "source": "query-proxy-api" + } + }, + /** + * @param data - Request parameters + * @param data.id - {string} mark id + * @param data.operate - {"update" | "delete"} 节点操作类型,update或delete + * @param data.data - {object} 要更新的节点数据 + */ + "updateNode": { + "path": "mark", + "key": "updateNode", + "metadata": { + "args": { + "id": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "string", + "description": "mark id" + }, + "operate": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "default": "update", + "description": "节点操作类型,update或delete", + "type": "string", + "enum": [ + "update", + "delete" + ], + "optional": true + }, + "data": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "节点id" + }, + "node": { + "description": "要更新的节点数据" + } + }, + "required": [ + "id", + "node" + ], + "additionalProperties": false, + "description": "要更新的节点数据" + } + }, + "url": "/api/router", + "source": "query-proxy-api" + } + }, + /** + * @param data - Request parameters + * @param data.id - {string} mark id + * @param data.nodeOperateList - {array} 要更新的节点列表 + */ + "updateNodes": { + "path": "mark", + "key": "updateNodes", + "metadata": { + "args": { + "id": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "string", + "description": "mark id" + }, + "nodeOperateList": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "array", + "items": { + "type": "object", + "properties": { + "operate": { + "default": "update", + "description": "节点操作类型,update或delete", + "type": "string", + "enum": [ + "update", + "delete" + ] + }, + "node": { + "description": "要更新的节点数据" + } + }, + "required": [ + "operate", + "node" + ], + "additionalProperties": false + }, + "description": "要更新的节点列表" + } + }, + "url": "/api/router", + "source": "query-proxy-api" + } + }, + /** + * @param data - Request parameters + * @param data.id - {string} mark id + */ + "delete": { + "path": "mark", + "key": "delete", + "metadata": { + "args": { + "id": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "string", + "description": "mark id" + } + }, + "url": "/api/router", + "source": "query-proxy-api" + } + }, + /** + * 创建mark + * + * @param data - Request parameters + * @param data.name - {string} mark名称 + * @param data.markType - {string} mark类型,simple,wallnote,md,draw等 + * @param data.data - {object} mark数据 + */ + "create": { + "path": "mark", + "key": "create", + "description": "创建mark", + "metadata": { + "args": { + "name": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "mark名称", + "type": "string" + }, + "markType": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "mark类型", + "type": "string" + }, + "data": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "mark数据", + "type": "object", + "optional": true + } + }, + "url": "/api/router", + "source": "query-proxy-api" + } + }, + /** + * 获取菜单 + */ + "getMenu": { + "path": "mark", + "key": "getMenu", + "description": "获取菜单", + "metadata": { + "url": "/api/router", + "source": "query-proxy-api" + } + } + } +} as const; +const queryApi = createQueryApi({ api }); +export { queryApi }; diff --git a/src/pages/repos/modules/CreateRepoDialog.tsx b/src/pages/repos/modules/CreateRepoDialog.tsx index 46b3683..b7fe350 100644 --- a/src/pages/repos/modules/CreateRepoDialog.tsx +++ b/src/pages/repos/modules/CreateRepoDialog.tsx @@ -49,7 +49,7 @@ export function CreateRepoDialog({ open, onOpenChange }: CreateRepoDialogProps) path: '', license: '', description: '', - visibility: 'Public' + visibility: 'public' }) } }, [open, reset]) @@ -104,16 +104,16 @@ export function CreateRepoDialog({ open, onOpenChange }: CreateRepoDialogProps) ( )} diff --git a/src/pages/repos/modules/SyncRepoDialog.tsx b/src/pages/repos/modules/SyncRepoDialog.tsx deleted file mode 100644 index 93f2005..0000000 --- a/src/pages/repos/modules/SyncRepoDialog.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog' -import { Button } from '@/components/ui/button' -import { Input } from '@/components/ui/input' -import { Label } from '@/components/ui/label' -import { useRepoStore } from '../store' -import { useState, useEffect } from 'react' -import { useShallow } from 'zustand/shallow' -import { get, set } from 'idb-keyval' -import { gitea } from '@/agents/app'; -import { toast } from 'sonner' - -const SYNC_REPO_STORAGE_KEY = 'sync-repo-mapping' - -export function SyncRepoDialog() { - const { syncDialogOpen, setSyncDialogOpen, selectedSyncRepo, buildSync } = useRepoStore(useShallow((state) => ({ - syncDialogOpen: state.syncDialogOpen, - setSyncDialogOpen: state.setSyncDialogOpen, - selectedSyncRepo: state.selectedSyncRepo, - buildSync: state.buildSync, - }))) - const [toRepo, setToRepo] = useState('') - - useEffect(() => { - const loadSavedMapping = async () => { - if (syncDialogOpen && selectedSyncRepo) { - const currentPath = selectedSyncRepo.path || '' - // 从 idb-keyval 获取存储的映射 - const mapping = await get>(SYNC_REPO_STORAGE_KEY) - // 如果有存储的值,使用存储的值,否则使用当前仓库路径 - setToRepo(mapping?.[currentPath] || currentPath) - } - } - loadSavedMapping() - }, [syncDialogOpen, selectedSyncRepo]) - - const handleSync = async () => { - if (!selectedSyncRepo || !toRepo.trim()) { - return - } - - // 保存映射到 idb-keyval - const currentPath = selectedSyncRepo.path || '' - const mapping = await get>(SYNC_REPO_STORAGE_KEY) || {} - mapping[currentPath] = toRepo - await set(SYNC_REPO_STORAGE_KEY, mapping) - - await buildSync(selectedSyncRepo, { toRepo }) - setSyncDialogOpen(false) - } - const onCreateRepo = async () => { - if (!toRepo.trim()) { - return - } - try { - const res = await gitea.repo.createRepo({ name: toRepo }) - if (res.code !== 200 && res.code !== 409) { - // 409 表示仓库已存在,可以继续同步 - throw new Error(`${res.message}`) - } - if (res.code === 200) { - toast.success('仓库创建成功,正在同步...') - } else { - toast.warning('仓库已存在,正在同步...') - } - handleSync() - } catch (error) { - console.error('创建仓库失败:', error) - } - } - return ( - - - - 同步仓库到 Gitea - - 将仓库 {selectedSyncRepo?.path} 同步到目标仓库 - - - -
-
- - setToRepo(e.target.value)} - /> -

- 格式: owner/repo-name -

-
-
- -
- - - -
-
-
- ) -} diff --git a/src/pages/repos/page.tsx b/src/pages/repos/page.tsx index b7039ad..cc42f94 100644 --- a/src/pages/repos/page.tsx +++ b/src/pages/repos/page.tsx @@ -5,7 +5,6 @@ import { RepoCard } from './components/RepoCard' import { EditRepoDialog } from './modules/EditRepoDialog' import { CreateRepoDialog } from './modules/CreateRepoDialog' import { WorkspaceDetailDialog } from './modules/WorkspaceDetailDialog' -import { SyncRepoDialog } from './modules/SyncRepoDialog' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { ExternalLinkIcon, Plus, RefreshCw, Search, Settings } from 'lucide-react' @@ -176,7 +175,6 @@ export const CommonRepoDialog = () => { onOpenChange={setShowCreateDialog} /> - ) } diff --git a/src/pages/repos/store/build.ts b/src/pages/repos/store/build.ts index 43fd2bf..e755685 100644 --- a/src/pages/repos/store/build.ts +++ b/src/pages/repos/store/build.ts @@ -57,28 +57,11 @@ export const createDevConfig = (params: { repo?: string, event?: string, branch? return `##### 配置开始,保留注释 ##### .common_env: &common_env env: - # 使用环境变量管理密钥,推荐使用密钥仓库管理密钥, 详情见 readme.md - # 使用仓库密钥时,注释 - ## 可选 API-Key 配置(按需取消注释) - # MINIMAX_API_KEY: '' # Minimax 模型 - # ZHIPU_API_KEY: '' # 智谱 AI - # BAILIAN_CODE_API_KEY: '' # 阿里云百炼 - # VOLCENGINE_API_KEY: '' # 火山引擎 - # CNB_API_KEY: '' # CNB API - # CNB_COOKIE: '' # 可选配置,用cnb.cool的cookie - - # 可选应用配置 - # FEISHU_APP_ID: '' # 飞书应用 ID - # FEISHU_APP_SECRET: '' # 飞书应用密钥 - USERNAME: root - ASSISTANT_CONFIG_DIR: /workspace/kevisual # ASSISTANT_CONFIG_DIR 环境变量指定了配置文件所在的目录 - # CNB_KEVISUAL_ORG: kevisual # 私密仓库使用环境配置(默认即可,默认为当前用户组CNB_GROUP_SLUG) - # CNB_KEVISUAL_APP: assistant-app # 可选配置(默认即可) - # CNB_OPENCLAW: openclaw # 仓库名(默认即可) - # CNB_OPENWEBUI: open-webui # 仓库名(默认即可) imports: - - https://cnb.cool/kevisual/env/-/blob/main/.env.development + - https://cnb.cool/\${CNB_GROUP_SLUG}/env/-/blob/main/.env + # - https://cnb.cool/\${CNB_GROUP_SLUG}/env/-/blob/main/ssh.yml + # - https://cnb.cool/\${CNB_GROUP_SLUG}/env/-/blob/main/ssh-config.yml ##### 配置结束 ##### @@ -89,19 +72,29 @@ ${branch}: services: - vscode - docker - runner: - cpus: 16 - #tags: cnb:arch:amd64:gpu imports: !reference [.common_env, imports] env: !reference [.common_env, env] + runner: + cpus: $RUN_CPU + #tags: cnb:arch:amd64:gpu stages: + - name: 安装dev-cnb的仓库代码模块 + script: | + cd /workspace && find . -mindepth 1 -delete + git init + git remote add origin https://cnb.cool/kevisual/dev-cnb + git fetch origin main + git reset --hard origin/main - name: 启动nginx script: nginx + - name: 启动搜索服务 + script: zsh -i -c 'bun src/cli.ts init start-meilisearch' - name: 初始化开发机 - script: zsh /workspace/scripts/init.sh + script: zsh -i -c 'bun run start' + - name: 启动当前工作区 + script: zsh -i -c 'cloud cnb keep-alive-current-workspace' # endStages: # - name: 结束阶段 - # script: bun /workspace/scripts/end.ts - + # script: zsh -i -c 'bun run end' ` } \ No newline at end of file diff --git a/src/pages/repos/store/index.ts b/src/pages/repos/store/index.ts index e9190c7..3b38145 100644 --- a/src/pages/repos/store/index.ts +++ b/src/pages/repos/store/index.ts @@ -3,7 +3,7 @@ import { query } from '@/modules/query'; import { toast } from 'sonner'; import { queryApi as cnbApi } from '@/modules/cnb-api' import { WorkspaceInfo } from '@kevisual/cnb' -import { createBuildConfig, createCommitBlankConfig, createDevConfig } from './build'; +import { createCommitBlankConfig, createDevConfig } from './build'; import { useLayoutStore } from '@/pages/auth/store'; import { Query } from '@kevisual/query'; interface DisplayModule { @@ -94,7 +94,6 @@ type State = { setSyncDialogOpen: (open: boolean) => void; selectedSyncRepo: Data | null; setSelectedSyncRepo: (repo: Data | null) => void; - buildSync: (data: Partial, params: { toRepo?: string, fromRepo?: string }) => Promise; buildUpdate: (data: Partial, params?: any) => Promise; getItem: (repo: string) => Promise; buildConfig: BuildConfig | null; @@ -525,28 +524,6 @@ export const useRepoStore = create((set, get) => { } }, workspaceLink: {}, - buildSync: async (data, params) => { - const repo = data.path!; - const toRepo = params.toRepo; - const fromRepo = params.fromRepo; - if (!toRepo && !fromRepo) { - toast.error('请选择同步的目标仓库或来源仓库') - return; - } - let event = toRepo ? 'api_trigger_sync_to_gitea' : 'api_trigger_sync_from_gitea'; - const res = await cnbApi.cnb['cloud-build']({ - repo: toRepo! || fromRepo!, - branch: 'main', - env: {} as any, - event: event, - config: createBuildConfig({ repo: toRepo! || fromRepo! }), - }) - if (res.code === 200) { - toast.success('同步提交成功') - } else { - toast.error(res.message || '同步提交失败') - } - }, buildUpdate: async (data) => { const res = await cnbApi.cnb['cloud-build']({ repo: data.path!, diff --git a/src/pages/workspaces/page.tsx b/src/pages/workspaces/page.tsx index 764464b..118989b 100644 --- a/src/pages/workspaces/page.tsx +++ b/src/pages/workspaces/page.tsx @@ -1,30 +1,421 @@ +import { useWorkspaceStore, type WorkspaceState } from "./store"; import { useShallow } from "zustand/shallow"; -import { useMarkStore, useWorkspaceStore } from "./store"; -import { useEffect } from "react"; +import { useEffect, useState } from "react"; + export const App = () => { - const markStore = useMarkStore(useShallow(state => { + const workspaceStore = useWorkspaceStore(useShallow((state: WorkspaceState) => { return { - init: state.init, list: state.list, + loading: state.loading, + getList: state.getList, + createItem: state.createItem, + updateItem: state.updateItem, + deleteItem: state.deleteItem, + showCreateDialog: state.showCreateDialog, + setShowCreateDialog: state.setShowCreateDialog, + showEditDialog: state.showEditDialog, + setShowEditDialog: state.setShowEditDialog, + editingItem: state.editingItem, + setEditingItem: state.setEditingItem, } })); - const workspaceStore = useWorkspaceStore(useShallow(state => { - return { - edit: state.edit, - setEdit: state.setEdit, - } - })); + const [search, setSearch] = useState(''); + useEffect(() => { - // @ts-ignore - markStore.init('cnb'); - }, []); - console.log('markStore.list', markStore.list); + workspaceStore.getList({ search }); + }, [search]); + + const handleDelete = async (id: string) => { + if (confirm('确定要删除这个workspace吗?')) { + await workspaceStore.deleteItem(id); + } + }; + + const handleRefresh = () => { + workspaceStore.getList({ search }); + }; + + const handleEdit = (item: any) => { + workspaceStore.setEditingItem(item); + workspaceStore.setShowEditDialog(true); + }; + + const handleCreate = () => { + workspaceStore.setShowCreateDialog(true); + }; + return ( -
-

Workspaces

-

This is the workspaces page.

+
+
+

Workspaces

+
+ setSearch(e.target.value)} + style={{ padding: '8px 12px', border: '1px solid #ddd', borderRadius: '4px', width: '200px' }} + /> + + +
+
+ + {workspaceStore.loading ? ( +
加载中...
+ ) : workspaceStore.list.length === 0 ? ( +
暂无workspace数据
+ ) : ( +
+ {workspaceStore.list.map((item) => ( +
+

{item.name || '未命名'}

+

ID: {item.id}

+

+ 创建时间: {item.created_at ? new Date(item.created_at).toLocaleString() : '-'} +

+

+ 更新时间: {item.updated_at ? new Date(item.updated_at).toLocaleString() : '-'} +

+
+ + +
+
+ ))} +
+ )} + + {/* 创建弹窗 */} + workspaceStore.setShowCreateDialog(false)} + onSubmit={workspaceStore.createItem} + /> + + {/* 编辑弹窗 */} + { + workspaceStore.setShowEditDialog(false); + workspaceStore.setEditingItem(null); + }} + onSubmit={workspaceStore.updateItem} + />
); }; -export default App; \ No newline at end of file +// 创建弹窗组件 +function CreateDialog({ open, onClose, onSubmit }: { + open: boolean; + onClose: () => void; + onSubmit: (data: { name: string, data?: any }) => Promise; +}) { + const [name, setName] = useState(''); + const [data, setData] = useState(''); + const [submitting, setSubmitting] = useState(false); + + useEffect(() => { + if (open) { + setName(''); + setData(''); + } + }, [open]); + + const handleSubmit = async () => { + if (!name.trim()) { + alert('请输入名称'); + return; + } + setSubmitting(true); + try { + let parsedData = {}; + if (data.trim()) { + try { + parsedData = JSON.parse(data); + } catch { + alert('JSON格式不正确'); + setSubmitting(false); + return; + } + } + await onSubmit({ name: name.trim(), data: parsedData }); + } finally { + setSubmitting(false); + } + }; + + if (!open) return null; + + return ( +
+
e.stopPropagation()}> +

创建Workspace

+
+ + setName(e.target.value)} + placeholder="请输入名称" + style={{ + width: '100%', + padding: '8px 12px', + border: '1px solid #ddd', + borderRadius: '4px', + boxSizing: 'border-box' + }} + /> +
+
+ +