diff --git a/CHANGELOG.md b/CHANGELOG.md index 8526dce..c8cd11e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ## [main] 0.1.10 - 修复 issue #48: 修复错误的引导路径。 - 修复非中文语言下,初始化引导界面不跟随主界面的问题。 +- 修复部分模型的 usage 无法正常显示的问题。 +- 实现 openmcp 工具测试的并行实现和暂停功能。 ## [main] 0.1.9 - 增加 mook 功能:可以利用随机种子或者AI生成来自动化填充测试 tool 的表单数据 diff --git a/renderer/src/api/message-bridge.ts b/renderer/src/api/message-bridge.ts index 231973d..36e6f6e 100644 --- a/renderer/src/api/message-bridge.ts +++ b/renderer/src/api/message-bridge.ts @@ -1,6 +1,7 @@ import { pinkLog, redLog } from '@/views/setting/util'; import { acquireVsCodeApi, electronApi, getPlatform } from './platform'; import { isReactive } from 'vue'; +import { v4 as uuidv4 } from 'uuid'; export interface VSCodeMessage { command: string; @@ -9,6 +10,7 @@ export interface VSCodeMessage { } export interface RestFulResponse { + _id?: string code: number; msg: T; } @@ -163,8 +165,8 @@ export class MessageBridge { const command = message.command; const data = message.data; - const handlers = this.handlers.get(command) || []; - handlers.forEach(handler => handler(data)); + const handlers = this.handlers.get(command) || new Set(); + handlers.forEach(handler => handler(data)); } public postMessage(message: VSCodeMessage) { @@ -231,15 +233,26 @@ export class MessageBridge { * @returns */ public commandRequest(command: string, data?: ICommandRequestData): Promise> { + const _id = uuidv4(); + + return new Promise((resolve, reject) => { + const handler = this.addCommandListener(command, (data) => { + if (data._id === undefined) { + console.warn('detect data without id, data: ' + JSON.stringify(data, null, 2)); + } - return new Promise((resolve, reject) => { - this.addCommandListener(command, (data) => { - resolve(data as RestFulResponse); - }, { once: true }); + if (data._id === _id) { + handler(); + resolve(data as RestFulResponse); + } + }, { once: false }); this.postMessage({ - command, - data: this.deserializeReactiveData(data) + command, + data: this.deserializeReactiveData({ + _id, + ...data + }) }); }); } diff --git a/renderer/src/components/main-panel/chat/core/task-loop.ts b/renderer/src/components/main-panel/chat/core/task-loop.ts index 00f58da..b53b1ee 100644 --- a/renderer/src/components/main-panel/chat/core/task-loop.ts +++ b/renderer/src/components/main-panel/chat/core/task-loop.ts @@ -18,6 +18,7 @@ import { getXmlWrapperPrompt, getToolCallFromXmlString, getXmlsFromString, handl export type ChatCompletionChunk = OpenAI.Chat.Completions.ChatCompletionChunk; export interface TaskLoopChatOption { id?: string + sessionId: string; proxyServer?: string enableXmlWrapper?: boolean } @@ -178,17 +179,30 @@ export class TaskLoop { } } - private handleChunkUsage(chunk: ChatCompletionChunk) { + private handleChunkUsage(chunk: ChatCompletionChunk) { const usage = chunk.usage; + if (usage) { this.completionUsage = usage; + } else { + // 有一些模型会把 usage 放在 completion 中 + const choice = chunk.choices[0] as any; + if (choice.usage) { + this.completionUsage = choice.usage; + } } } private doConversation(chatData: ChatCompletionCreateParamsBase, toolcallIndexAdapter: (toolCall: ToolCall) => IToolCallIndex) { + const sessionId = chatData.sessionId; return new Promise((resolve, reject) => { const chunkHandler = this.bridge.addCommandListener('llm/chat/completions/chunk', data => { + + if (data.sessionId !== sessionId) { + return; + } + // data.code 一定为 200,否则不会走这个 route const { chunk } = data.msg as { chunk: ChatCompletionChunk }; @@ -207,30 +221,41 @@ export class TaskLoop { }, { once: false }); const doneHandler = this.bridge.addCommandListener('llm/chat/completions/done', data => { + + if (data.sessionId !== sessionId) { + return; + } + this.consumeDones(); chunkHandler(); errorHandler(); + doneHandler(); resolve({ stop: false }); - }, { once: true }); + }, { once: false }); const errorHandler = this.bridge.addCommandListener('llm/chat/completions/error', data => { + if (data.sessionId !== sessionId) { + return; + } + this.consumeErrors({ state: MessageState.ReceiveChunkError, msg: data.msg || '请求模型服务时发生错误' }); chunkHandler(); + errorHandler(); doneHandler(); resolve({ stop: true }); - }, { once: true }); + }, { once: false }); this.bridge.postMessage({ command: 'llm/chat/completions', @@ -282,10 +307,12 @@ export class TaskLoop { prompt += getXmlWrapperPrompt(tabStorage.settings.enableTools, tabStorage); } - userMessages.push({ - role: 'system', - content: prompt - }); + if (prompt) { + userMessages.push({ + role: 'system', + content: prompt + }); + } // 如果超出了 tabStorage.settings.contextLength, 则删除最早的消息 const loadMessages = tabStorage.messages.slice(- tabStorage.settings.contextLength); @@ -295,7 +322,7 @@ export class TaskLoop { const id = crypto.randomUUID(); const chatData = { - id, + sessionId: id, baseURL, apiKey, model, @@ -566,7 +593,7 @@ export class TaskLoop { break; } - this.currentChatId = chatData.id!; + this.currentChatId = chatData.sessionId; const llm = this.getLlmConfig(); const toolcallIndexAdapter = getToolCallIndexAdapter(llm, chatData); diff --git a/renderer/src/components/main-panel/tool/auto-detector/diagram-item-record.vue b/renderer/src/components/main-panel/tool/auto-detector/diagram-item-record.vue index 85c107c..f870485 100644 --- a/renderer/src/components/main-panel/tool/auto-detector/diagram-item-record.vue +++ b/renderer/src/components/main-panel/tool/auto-detector/diagram-item-record.vue @@ -182,7 +182,7 @@ const toolcallPercent = computed(() => { .item-json { border-radius: 4px; padding: 6px 10px; - font-size: 15px; + font-size: 13px; font-family: var(--code-font-family, monospace); margin: 2px 0 8px 0; white-space: pre-wrap; @@ -193,6 +193,7 @@ const toolcallPercent = computed(() => { } .code-container { + font-size: 13px; margin-top: 10px; border-radius: .3em; padding: 0 10px; diff --git a/renderer/src/components/main-panel/tool/auto-detector/diagram.ts b/renderer/src/components/main-panel/tool/auto-detector/diagram.ts index bc3dcf4..ec76f11 100644 --- a/renderer/src/components/main-panel/tool/auto-detector/diagram.ts +++ b/renderer/src/components/main-panel/tool/auto-detector/diagram.ts @@ -51,6 +51,7 @@ export interface NodeDataView { export interface DiagramContext { preset: (type: string) => void, render: () => void, + resetDataView: () => void, state?: DiagramState, setCaption: (value: string) => void } @@ -161,6 +162,7 @@ export function topoSortParallel(state: DiagramState): string[][] { return result; } + export async function makeNodeTest( dataView: Reactive, enableXmlWrapper: boolean, @@ -177,7 +179,7 @@ export async function makeNodeTest( context.render(); try { - const loop = new TaskLoop({ maxEpochs: 1 }); + const loop = new TaskLoop({ maxEpochs: 1, verbose: 0 }); const usePrompt = (prompt || 'please call the tool {tool} to make some test').replace('{tool}', dataView.tool.name); const chatStorage = { messages: [], @@ -197,8 +199,6 @@ export async function makeNodeTest( } } as ChatStorage; - loop.setMaxEpochs(1); - let aiMockJson: any = undefined; loop.registerOnToolCall(toolCall => { @@ -224,7 +224,7 @@ export async function makeNodeTest( return toolCall; }); - loop.registerOnToolCalled(toolCalled => { + loop.registerOnToolCalled(toolCalled => { dataView.toolcallTimecost = Date.now() - createAt - dataView.llmTimecost!; if (toolCalled.state === MessageState.Success) { diff --git a/renderer/src/components/main-panel/tool/auto-detector/diagram.vue b/renderer/src/components/main-panel/tool/auto-detector/diagram.vue index eaecf05..789db0f 100644 --- a/renderer/src/components/main-panel/tool/auto-detector/diagram.vue +++ b/renderer/src/components/main-panel/tool/auto-detector/diagram.vue @@ -188,7 +188,7 @@ const drawDiagram = async () => { // 如果保存了 edges 信息,则需要进行同步 const reservedEdges = autoDetectDiagram?.edges; - if (reservedEdges && reservedEdges.length > 0) { + if (reservedEdges) { for (const edge of reservedEdges) { if (edge.sources && edge.targets && edge.sources.length > 0 && edge.targets.length > 0) { edges.push({ @@ -662,6 +662,24 @@ function getNodePopupStyle(node: any): any { height: `${popupHeight}px` }; } + +// 重置所有节点的状态为初始值 +function resetDataView() { + state.dataView.forEach((view, key) => { + state.dataView.set(key, { + ...view, + status: 'waiting', + result: null, + createAt: undefined, + finishAt: undefined, + llmTimecost: undefined, + toolcallTimecost: undefined + }); + }); +} + +context.resetDataView = resetDataView; +