From 7d66ccb3a112ecdf96d4ce4cb308f51460e099ba Mon Sep 17 00:00:00 2001 From: xiongxiao Date: Sat, 14 Mar 2026 02:27:09 +0800 Subject: [PATCH] feat: enhance code graph with AI assistant and file management improvements --- src/modules/opencode-api.ts | 221 ++++++++++++++++++ src/pages/code-graph/assets/openclaw.svg | 22 ++ src/pages/code-graph/assets/opencode.png | Bin 0 -> 245 bytes .../code-graph/components/BotHelperModal.tsx | 120 ++++++++++ .../code-graph/components/Code3DGraph.tsx | 2 +- src/pages/code-graph/components/CodePod.tsx | 11 +- src/pages/code-graph/components/NodeInfo.tsx | 11 +- src/pages/code-graph/modules/api/get-files.ts | 21 -- src/pages/code-graph/page.tsx | 33 ++- src/pages/code-graph/store/bot-helper.ts | 27 +++ src/pages/code-graph/store/index.ts | 70 +++++- 11 files changed, 490 insertions(+), 48 deletions(-) create mode 100644 src/modules/opencode-api.ts create mode 100644 src/pages/code-graph/assets/openclaw.svg create mode 100644 src/pages/code-graph/assets/opencode.png create mode 100644 src/pages/code-graph/components/BotHelperModal.tsx delete mode 100644 src/pages/code-graph/modules/api/get-files.ts create mode 100644 src/pages/code-graph/store/bot-helper.ts diff --git a/src/modules/opencode-api.ts b/src/modules/opencode-api.ts new file mode 100644 index 0000000..852b649 --- /dev/null +++ b/src/modules/opencode-api.ts @@ -0,0 +1,221 @@ +import { createQueryApi } from '@kevisual/query/api'; +import { query } from '@/modules/query.ts'; +const api = { + "opencode": { + /** + * 创建 OpenCode 客户端,如果存在则复用 + * + * @param data - Request parameters + * @param data.port - {number} OpenCode 服务端口,默认为 4096 + */ + "create": { + "path": "opencode", + "key": "create", + "description": "创建 OpenCode 客户端", + "metadata": { + "tags": [ + "opencode" + ], + "args": { + "port": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "OpenCode 服务端口,默认为 4096", + "type": "number", + "optional": true + } + }, + "skill": "create-opencode-client", + "title": "创建 OpenCode 客户端", + "summary": "创建 OpenCode 客户端,如果存在则复用", + "url": "/root/v1/cnb-dev", + "source": "query-proxy-api" + } + }, + /** + * 关闭 OpenCode 客户端, 未提供端口则关闭默认端口 + * + * @param data - Request parameters + * @param data.port - {number} OpenCode 服务端口,默认为 4096 + */ + "close": { + "path": "opencode", + "key": "close", + "description": "关闭 OpenCode 客户端", + "metadata": { + "tags": [ + "opencode" + ], + "args": { + "port": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "OpenCode 服务端口,默认为 4096", + "type": "number", + "optional": true + } + }, + "skill": "close-opencode-client", + "title": "关闭 OpenCode 客户端", + "summary": "关闭 OpenCode 客户端, 未提供端口则关闭默认端口", + "url": "/root/v1/cnb-dev", + "source": "query-proxy-api" + } + }, + /** + * 重启 OpenCode 客户端 + * + * @param data - Request parameters + * @param data.port - {number} OpenCode 服务端口,默认为 4096 + */ + "restart": { + "path": "opencode", + "key": "restart", + "description": "重启 OpenCode 客户端", + "metadata": { + "tags": [ + "opencode" + ], + "args": { + "port": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "OpenCode 服务端口,默认为 4096", + "type": "number", + "optional": true + } + }, + "skill": "restart-opencode-client", + "title": "重启 OpenCode 客户端", + "summary": "重启 OpenCode 客户端", + "url": "/root/v1/cnb-dev", + "source": "query-proxy-api" + } + }, + /** + * 获取当前 OpenCode 服务的 URL 地址 + * + * @param data - Request parameters + * @param data.port - {number} OpenCode 服务端口,默认为 4096 + */ + "getUrl": { + "path": "opencode", + "key": "getUrl", + "description": "获取 OpenCode 服务 URL", + "metadata": { + "tags": [ + "opencode" + ], + "args": { + "port": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "OpenCode 服务端口,默认为 4096", + "type": "number", + "optional": true + } + }, + "skill": "get-opencode-url", + "title": "获取 OpenCode 服务 URL", + "summary": "获取当前 OpenCode 服务的 URL 地址", + "url": "/root/v1/cnb-dev", + "source": "query-proxy-api" + } + }, + "ls-projects": { + "path": "opencode", + "key": "ls-projects", + "metadata": { + "url": "/root/v1/cnb-dev", + "source": "query-proxy-api" + } + }, + /** + * 运行一个已有的 OpenCode 项目 + * + * @param data - Request parameters + * @param data.projectPath - {string} OpenCode 项目的路径, 默认为 /workspace + */ + "runProject": { + "path": "opencode", + "key": "runProject", + "metadata": { + "tags": [ + "opencode" + ], + "args": { + "projectPath": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "OpenCode 项目的路径, 默认为 /workspace", + "type": "string", + "optional": true + } + }, + "skill": "run-opencode-project", + "title": "运行 OpenCode 项目", + "summary": "运行一个已有的 OpenCode 项目", + "url": "/root/v1/cnb-dev", + "source": "query-proxy-api" + } + } + }, + "opencode-cnb": { + /** + * 创建 OpenCode 客户端 + * + * @param data - Request parameters + * @param data.question - {string} 问题 + * @param data.baseUrl - {string} OpenCode 服务地址,默认为 http://localhost:4096 + * @param data.directory - {string} 运行目录,默认为根目录 + * @param data.messageID - {string} 消息 ID,选填 + * @param data.sessionId - {string} 会话 ID,选填 + * @param data.parts - {array} 消息内容的分块,优先于 question 参数 + */ + "question": { + "path": "opencode-cnb", + "key": "question", + "description": "创建 OpenCode 客户端", + "metadata": { + "args": { + "question": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "string", + "description": "问题" + }, + "baseUrl": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "OpenCode 服务地址,默认为 http://localhost:4096", + "type": "string", + "optional": true + }, + "directory": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "运行目录,默认为根目录", + "type": "string", + "optional": true + }, + "messageID": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "消息 ID,选填", + "type": "string", + "optional": true + }, + "sessionId": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "会话 ID,选填", + "type": "string", + "optional": true + }, + "parts": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "消息内容的分块,优先于 question 参数", + "type": "array", + "items": {}, + "optional": true + } + }, + "url": "/root/v1/cnb-dev", + "source": "query-proxy-api" + } + } + } +} as const; +const queryApi = createQueryApi({ api, query }); + +export { queryApi }; diff --git a/src/pages/code-graph/assets/openclaw.svg b/src/pages/code-graph/assets/openclaw.svg new file mode 100644 index 0000000..bcbc1e1 --- /dev/null +++ b/src/pages/code-graph/assets/openclaw.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/pages/code-graph/assets/opencode.png b/src/pages/code-graph/assets/opencode.png new file mode 100644 index 0000000000000000000000000000000000000000..ef9f5ffe5362e02d6c30dfd3deaaa1ba630397fb GIT binary patch literal 245 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1SD@H`;SU!>8q`%4E%NT*w-7|)@8+7S(_+C32=ILuxQACmahsb(!6x? z=M1OHC-rlMlwL-*oU1*q#BDv_^~Ain?%_^LEV?gV5@rcxuz_67%ph4``Y}dFSRW|D;OXk;vd$@?2>>GoTeJWG literal 0 HcmV?d00001 diff --git a/src/pages/code-graph/components/BotHelperModal.tsx b/src/pages/code-graph/components/BotHelperModal.tsx new file mode 100644 index 0000000..5c11d1a --- /dev/null +++ b/src/pages/code-graph/components/BotHelperModal.tsx @@ -0,0 +1,120 @@ +import { BotIcon, XIcon, FileIcon, FolderIcon, DatabaseIcon } from 'lucide-react'; +import { useBotHelperStore, BOT_KEYS, BotKey } from '../store/bot-helper'; +import { useShallow } from 'zustand/react/shallow'; +import { useCodeGraphStore, NodeInfoData } from '../store'; +import openclawSvg from '../assets/openclaw.svg'; +import opencodePng from '../assets/opencode.png'; + +const BOT_ICONS: Record = { + openclaw: openclawSvg, + opencode: opencodePng, +}; + +function NodeIcon({ kind, color }: { kind: NodeInfoData['kind']; color: string }) { + const cls = 'size-4 shrink-0'; + if (kind === 'root') return ; + if (kind === 'dir') return ; + return ; +} + +export function BotHelperModal() { + const { open, input, setInput, closeModal, activeKey, setActiveKey } = useBotHelperStore( + useShallow((s) => ({ + open: s.open, + input: s.input, + setInput: s.setInput, + closeModal: s.closeModal, + activeKey: s.activeKey, + setActiveKey: s.setActiveKey, + })), + ); + const { nodeInfoData, createQuestion } = useCodeGraphStore(useShallow((s) => ({ + nodeInfoData: s.nodeInfoData, + createQuestion: s.createQuestion, + }))); + + const relativePath = nodeInfoData + ? nodeInfoData.fullPath.replace((nodeInfoData.projectPath || '') + '/', '') || '/' + : null; + + const handleConfirm = async () => { + if (nodeInfoData) { + const res = await createQuestion({ + question: input, + projectPath: nodeInfoData.projectPath, + engine: activeKey, + }); + console.log(res); + } + closeModal(); + }; + + if (!open) return null; + + return ( +
+ {/* Mask:点击不关闭 */} +
+
+ {/* 标题栏 */} +
+
+ + AI 助手 +
+ +
+ {/* 内容区 */} +
+ {/* 节点信息 + 按钮组 */} +
+ {nodeInfoData && relativePath && ( +
+ + + {relativePath} + +
+ )} + {/* Bot 切换按钮组 */} +
+ {BOT_KEYS.map((key) => ( + + ))} +
+
+