diff --git a/src/modules/opencode-api.ts b/src/modules/opencode-api.ts index 852b649..33b69bf 100644 --- a/src/modules/opencode-api.ts +++ b/src/modules/opencode-api.ts @@ -11,6 +11,7 @@ const api = { "create": { "path": "opencode", "key": "create", + "id": "b662fba3c0a8a593", "description": "创建 OpenCode 客户端", "metadata": { "tags": [ @@ -40,6 +41,7 @@ const api = { "close": { "path": "opencode", "key": "close", + "id": "49672adea9daa837", "description": "关闭 OpenCode 客户端", "metadata": { "tags": [ @@ -69,6 +71,7 @@ const api = { "restart": { "path": "opencode", "key": "restart", + "id": "e0b1564a796ea88b", "description": "重启 OpenCode 客户端", "metadata": { "tags": [ @@ -98,6 +101,7 @@ const api = { "getUrl": { "path": "opencode", "key": "getUrl", + "id": "c611acf038e41279", "description": "获取 OpenCode 服务 URL", "metadata": { "tags": [ @@ -121,6 +125,7 @@ const api = { "ls-projects": { "path": "opencode", "key": "ls-projects", + "id": "ee72cd09da63d13d", "metadata": { "url": "/root/v1/cnb-dev", "source": "query-proxy-api" @@ -135,6 +140,7 @@ const api = { "runProject": { "path": "opencode", "key": "runProject", + "id": "112127fa82fe1d9d", "metadata": { "tags": [ "opencode" @@ -163,13 +169,17 @@ const api = { * @param data.question - {string} 问题 * @param data.baseUrl - {string} OpenCode 服务地址,默认为 http://localhost:4096 * @param data.directory - {string} 运行目录,默认为根目录 - * @param data.messageID - {string} 消息 ID,选填 + * @param data.messageId - {string} 消息 ID,选填 * @param data.sessionId - {string} 会话 ID,选填 + * @param data.providerId - {string} 指定使用的提供商 ID,默认为空,表示使用默认提供商 + * @param data.modelId - {string} 指定使用的模型 ID,默认为空,表示使用默认模型 * @param data.parts - {array} 消息内容的分块,优先于 question 参数 + * @param data.awaitAnswer - {boolean} 是否等待回答完成,默认为 false,开启后会在回答完成后返回完整回答内容 */ "question": { "path": "opencode-cnb", "key": "question", + "id": "193c9c63bc50cbbc", "description": "创建 OpenCode 客户端", "metadata": { "args": { @@ -190,7 +200,7 @@ const api = { "type": "string", "optional": true }, - "messageID": { + "messageId": { "$schema": "https://json-schema.org/draft/2020-12/schema", "description": "消息 ID,选填", "type": "string", @@ -202,17 +212,430 @@ const api = { "type": "string", "optional": true }, + "providerId": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "指定使用的提供商 ID,默认为空,表示使用默认提供商", + "type": "string", + "optional": true + }, + "modelId": { + "$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 + }, + "awaitAnswer": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "是否等待回答完成,默认为 false,开启后会在回答完成后返回完整回答内容", + "type": "boolean", + "optional": true } }, "url": "/root/v1/cnb-dev", "source": "query-proxy-api" } + }, + /** + * 获取 OpenCode 可用模型列表,返回 providerID 和 modelID + * + * @param data - Request parameters + * @param data.baseUrl - {string} OpenCode 服务地址,默认为 http://localhost:4096 + */ + "models": { + "path": "opencode-cnb", + "key": "models", + "id": "a66f19f8427e7085", + "description": "获取 OpenCode 可用模型列表,返回 providerID 和 modelID", + "metadata": { + "args": { + "baseUrl": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "OpenCode 服务地址,默认为 http://localhost:4096", + "type": "string", + "optional": true + } + }, + "url": "/root/v1/cnb-dev", + "source": "query-proxy-api" + } + } + }, + "opencode-session": { + /** + * 在指定目录创建一个新的 OpenCode 会话 + * + * @param data - Request parameters + * @param data.directory - {string} 工作目录,默认为 /workspace + * @param data.port - {number} OpenCode 服务端口,默认为 4096 + */ + "create": { + "path": "opencode-session", + "key": "create", + "id": "f07990a69e2a1eaf", + "description": "创建 OpenCode Session", + "metadata": { + "tags": [ + "session" + ], + "args": { + "directory": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "工作目录,默认为 /workspace", + "type": "string", + "optional": true + }, + "port": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "OpenCode 服务端口,默认为 4096", + "type": "number", + "optional": true + } + }, + "skill": "create-opencode-session", + "title": "创建 Session", + "summary": "在指定目录创建一个新的 OpenCode 会话", + "url": "/root/v1/cnb-dev", + "source": "query-proxy-api" + } + }, + /** + * 更新指定 OpenCode 会话的属性,如标题 + * + * @param data - Request parameters + * @param data.sessionId - {string} Session ID + * @param data.title - {string} 新的会话标题 + * @param data.port - {number} OpenCode 服务端口,默认为 4096 + */ + "update": { + "path": "opencode-session", + "key": "update", + "id": "0a6f7cd78fa5ff20", + "description": "更新 OpenCode Session", + "metadata": { + "tags": [ + "session" + ], + "args": { + "sessionId": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "string", + "description": "Session ID" + }, + "title": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "新的会话标题", + "type": "string", + "optional": true + }, + "port": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "OpenCode 服务端口,默认为 4096", + "type": "number", + "optional": true + } + }, + "skill": "update-opencode-session", + "title": "更新 Session", + "summary": "更新指定 OpenCode 会话的属性,如标题", + "url": "/root/v1/cnb-dev", + "source": "query-proxy-api" + } + }, + /** + * 根据 ID 删除指定的 OpenCode 会话及其所有数据 + * + * @param data - Request parameters + * @param data.sessionId - {string} Session ID + * @param data.port - {number} OpenCode 服务端口,默认为 4096 + */ + "delete": { + "path": "opencode-session", + "key": "delete", + "id": "c7bd762b2eccdfc2", + "description": "删除 OpenCode Session", + "metadata": { + "tags": [ + "session" + ], + "args": { + "sessionId": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "string", + "description": "Session ID" + }, + "port": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "OpenCode 服务端口,默认为 4096", + "type": "number", + "optional": true + } + }, + "skill": "delete-opencode-session", + "title": "删除 Session", + "summary": "根据 ID 删除指定的 OpenCode 会话及其所有数据", + "url": "/root/v1/cnb-dev", + "source": "query-proxy-api" + } + }, + /** + * 中止正在运行的 OpenCode 会话 + * + * @param data - Request parameters + * @param data.sessionId - {string} Session ID + * @param data.port - {number} OpenCode 服务端口,默认为 4096 + */ + "abort": { + "path": "opencode-session", + "key": "abort", + "id": "0b89922558164ffd", + "description": "中止 OpenCode Session", + "metadata": { + "tags": [ + "session" + ], + "args": { + "sessionId": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "string", + "description": "Session ID" + }, + "port": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "OpenCode 服务端口,默认为 4096", + "type": "number", + "optional": true + } + }, + "skill": "abort-opencode-session", + "title": "中止 Session", + "summary": "中止正在运行的 OpenCode 会话", + "url": "/root/v1/cnb-dev", + "source": "query-proxy-api" + } + }, + /** + * 对指定的 OpenCode 会话进行内容总结 + * + * @param data - Request parameters + * @param data.sessionId - {string} Session ID + * @param data.port - {number} OpenCode 服务端口,默认为 4096 + */ + "summarize": { + "path": "opencode-session", + "key": "summarize", + "id": "c51ae8a43b269383", + "description": "总结 OpenCode Session", + "metadata": { + "tags": [ + "session" + ], + "args": { + "sessionId": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "string", + "description": "Session ID" + }, + "port": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "OpenCode 服务端口,默认为 4096", + "type": "number", + "optional": true + } + }, + "skill": "summarize-opencode-session", + "title": "总结 Session", + "summary": "对指定的 OpenCode 会话进行内容总结", + "url": "/root/v1/cnb-dev", + "source": "query-proxy-api" + } + }, + /** + * 获取当前 OpenCode 会话的运行状态,可按目录过滤 + * + * @param data - Request parameters + * @param data.directory - {string} 工作目录 + * @param data.port - {number} OpenCode 服务端口,默认为 4096 + */ + "status": { + "path": "opencode-session", + "key": "status", + "id": "a2507055e8e1ed42", + "description": "获取 OpenCode Session 状态", + "metadata": { + "tags": [ + "session" + ], + "args": { + "directory": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "工作目录", + "type": "string", + "optional": true + }, + "port": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "OpenCode 服务端口,默认为 4096", + "type": "number", + "optional": true + } + }, + "skill": "get-opencode-session-status", + "title": "获取 Session 状态", + "summary": "获取当前 OpenCode 会话的运行状态,可按目录过滤", + "url": "/root/v1/cnb-dev", + "source": "query-proxy-api" + } + }, + /** + * 列出指定 OpenCode 会话的所有消息记录 + * + * @param data - Request parameters + * @param data.sessionId - {string} Session ID + * @param data.port - {number} OpenCode 服务端口,默认为 4096 + */ + "messages": { + "path": "opencode-session", + "key": "messages", + "id": "04e78517e6e9144e", + "description": "列出 OpenCode Session 消息", + "metadata": { + "tags": [ + "session" + ], + "args": { + "sessionId": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "string", + "description": "Session ID" + }, + "port": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "OpenCode 服务端口,默认为 4096", + "type": "number", + "optional": true + } + }, + "skill": "list-opencode-session-messages", + "title": "列出 Session 消息", + "summary": "列出指定 OpenCode 会话的所有消息记录", + "url": "/root/v1/cnb-dev", + "source": "query-proxy-api" + } + }, + /** + * 列出 OpenCode 中的所有会话,可按目录过滤 + * + * @param data - Request parameters + * @param data.port - {number} OpenCode 服务端口,默认为 4096 + */ + "list": { + "path": "opencode-session", + "key": "list", + "id": "a44e79adfcb199dd", + "description": "列出所有 OpenCode Session", + "metadata": { + "tags": [ + "session" + ], + "args": { + "port": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "OpenCode 服务端口,默认为 4096", + "type": "number", + "optional": true + } + }, + "skill": "list-opencode-sessions", + "title": "列出所有 Session", + "summary": "列出 OpenCode 中的所有会话,可按目录过滤", + "url": "/root/v1/cnb-dev", + "source": "query-proxy-api" + } + }, + /** + * 根据 ID 获取指定的 OpenCode 会话信息 + * + * @param data - Request parameters + * @param data.id - {string} Session ID + * @param data.port - {number} OpenCode 服务端口,默认为 4096 + */ + "get": { + "path": "opencode-session", + "key": "get", + "id": "7acea53865affb10", + "description": "获取指定 OpenCode Session", + "metadata": { + "tags": [ + "session" + ], + "args": { + "id": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "string", + "description": "Session ID" + }, + "port": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "OpenCode 服务端口,默认为 4096", + "type": "number", + "optional": true + } + }, + "skill": "get-opencode-session", + "title": "获取 Session", + "summary": "根据 ID 获取指定的 OpenCode 会话信息", + "url": "/root/v1/cnb-dev", + "source": "query-proxy-api" + } + }, + /** + * 从指定消息处 Fork 一个 OpenCode 会话 + * + * @param data - Request parameters + * @param data.sessionId - {string} Session ID + * @param data.messageId - {string} 从该消息处开始 Fork + * @param data.port - {number} OpenCode 服务端口,默认为 4096 + */ + "fork": { + "path": "opencode-session", + "key": "fork", + "id": "d43a8e2282412078", + "description": "Fork OpenCode Session", + "metadata": { + "tags": [ + "session" + ], + "args": { + "sessionId": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "string", + "description": "Session ID" + }, + "messageId": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "string", + "description": "从该消息处开始 Fork" + }, + "port": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "OpenCode 服务端口,默认为 4096", + "type": "number", + "optional": true + } + }, + "skill": "fork-opencode-session", + "title": "Fork Session", + "summary": "从指定消息处 Fork 一个 OpenCode 会话", + "url": "/root/v1/cnb-dev", + "source": "query-proxy-api" + } } } } as const; diff --git a/src/pages/chat-dev/components/OpencodeChat.tsx b/src/pages/chat-dev/components/OpencodeChat.tsx new file mode 100644 index 0000000..d9b971d --- /dev/null +++ b/src/pages/chat-dev/components/OpencodeChat.tsx @@ -0,0 +1,126 @@ +import { useEffect } from 'react'; +import { useShallow } from 'zustand/react/shallow'; +import { FileIcon, FolderIcon, RefreshCwIcon, TrashIcon } from 'lucide-react'; +import { useChatDevStore } from '../store'; +import { useCodeGraphStore } from '@/pages/code-graph/store'; + +export const OpencodeChat = () => { + const { + question, projectInfo, + sessionId, isLoading, + saveSessionInfo, loadSessionInfo, fetchSession, fetchMessages, clearSession, + setData, + } = useChatDevStore( + useShallow((s) => ({ + question: s.question, + projectInfo: s.projectInfo, + setData: s.setData, + sessionId: s.sessionId, + isLoading: s.isLoading, + saveSessionInfo: s.saveSessionInfo, + loadSessionInfo: s.loadSessionInfo, + fetchSession: s.fetchSession, + fetchMessages: s.fetchMessages, + clearSession: s.clearSession, + })), + ); + const codeGraphStore = useCodeGraphStore(useShallow((s) => ({ + createQuestion: s.createQuestion, + url: s.url, + }))); + + // 初始化后尝试从 sessionStorage 恢复 opencode session 并加载历史消息 + useEffect(() => { + if (!codeGraphStore.url) return; + const info = loadSessionInfo(); + if (info) { + fetchSession(info.sessionId); + fetchMessages(info.sessionId); + } + }, [codeGraphStore.url]); + + const relativePath = projectInfo + ? (projectInfo.filepath || '').replace((projectInfo.projectPath || '') + '/', '') || '/' + : null; + + const onSend = async () => { + const res = await codeGraphStore.createQuestion({ + question, + projectPath: projectInfo?.projectPath, + engine: 'opencode', + sessionId: sessionId || undefined, + }); + console.log(res); + if (res?.code === 200 && res?.data) { + const { sessionId: newSessionId, messageId: newMessageId } = res.data as any; + if (newSessionId) { + saveSessionInfo(newSessionId, newMessageId || ''); + fetchMessages(newSessionId); + } + } + }; + + return ( +
+ {/* Session 信息栏 */} + {sessionId && ( +
+ + + Session: {sessionId.slice(0, 8)}... + + + + +
+ )} + + {/* 内容区 */} +
+ {/* 节点信息 */} + {relativePath && ( +
+ + + {relativePath} + +
+ )} + {projectInfo?.projectPath && !relativePath && ( +
+ + + {projectInfo.projectPath} + +
+ )} + + {/* 问题输入区 */} +