diff --git a/static/src/composables/useLegacySocket.ts b/static/src/composables/useLegacySocket.ts index 72efe07..d0ac893 100644 --- a/static/src/composables/useLegacySocket.ts +++ b/static/src/composables/useLegacySocket.ts @@ -37,6 +37,32 @@ export async function initializeLegacySocket(ctx: any) { ignoreThinking: false }; + const pendingToolEvents: Array<{ event: string; data: any; handler: () => void }> = []; + + const hasPendingStreamingText = () => + !!streamingState.buffer.length || + !!streamingState.pendingCompleteContent || + streamingState.timer !== null || + streamingState.completionTimer !== null; + + const resetPendingToolEvents = () => { + pendingToolEvents.length = 0; + }; + + const flushPendingToolEvents = () => { + if (!pendingToolEvents.length) { + return; + } + const queue = pendingToolEvents.splice(0); + queue.forEach((item) => { + try { + item.handler(); + } catch (error) { + console.warn('延后工具事件处理失败:', item.event, error); + } + }); + }; + const snapshotStreamingState = () => ({ bufferLength: streamingState.buffer.length, timerActive: streamingState.timer !== null, @@ -297,6 +323,7 @@ export async function initializeLegacySocket(ctx: any) { streamingState.activeMessageIndex = null; streamingState.activeTextAction = null; markStreamingIdleIfPossible('finalizeStreamingText'); + flushPendingToolEvents(); return true; }; @@ -326,6 +353,7 @@ export async function initializeLegacySocket(ctx: any) { streamingState.renderedText = ''; streamingState.activeMessageIndex = null; streamingState.activeTextAction = null; + resetPendingToolEvents(); logStreamingDebug('resetStreamingBuffer', snapshotStreamingState()); }; @@ -658,6 +686,7 @@ export async function initializeLegacySocket(ctx: any) { socketLog('AI消息开始'); logStreamingDebug('socket:ai_message_start'); finalizeStreamingText({ force: true }); + flushPendingToolEvents(); resetStreamingBuffer(); ctx.monitorResetSpeech(); ctx.cleanupStaleToolActions(); @@ -807,34 +836,41 @@ export async function initializeLegacySocket(ctx: any) { socketLog('跳过tool_preparing(对话不匹配)', data.conversation_id); return; } - const msg = ctx.chatEnsureAssistantMessage(); - if (!msg) { - return; - } - const action = { - id: data.id, - type: 'tool', - tool: { + const handler = () => { + const msg = ctx.chatEnsureAssistantMessage(); + if (!msg) { + return; + } + const action = { id: data.id, - name: data.name, - arguments: {}, - argumentSnapshot: null, - argumentLabel: '', - status: 'preparing', - result: null, - message: data.message || `准备调用 ${data.name}...` - }, - timestamp: Date.now() + type: 'tool', + tool: { + id: data.id, + name: data.name, + arguments: {}, + argumentSnapshot: null, + argumentLabel: '', + status: 'preparing', + result: null, + message: data.message || `准备调用 ${data.name}...` + }, + timestamp: Date.now() + }; + msg.actions.push(action); + ctx.preparingTools.set(data.id, action); + ctx.toolRegisterAction(action, data.id); + ctx.toolTrackAction(data.name, action); + ctx.$forceUpdate(); + ctx.conditionalScrollToBottom(); + if (ctx.monitorPreviewTool) { + ctx.monitorPreviewTool(data); + } }; - msg.actions.push(action); - ctx.preparingTools.set(data.id, action); - ctx.toolRegisterAction(action, data.id); - ctx.toolTrackAction(data.name, action); - ctx.$forceUpdate(); - ctx.conditionalScrollToBottom(); - // 虚拟显示器:在模型检测到工具时立即展示“正在XX”预览 - if (ctx.monitorPreviewTool) { - ctx.monitorPreviewTool(data); + + if (hasPendingStreamingText()) { + pendingToolEvents.push({ event: 'tool_preparing', data, handler }); + } else { + handler(); } }); @@ -868,46 +904,54 @@ export async function initializeLegacySocket(ctx: any) { socketLog('跳过tool_start(对话不匹配)', data.conversation_id); return; } - let action = null; - if (data.preparing_id && ctx.preparingTools.has(data.preparing_id)) { - action = ctx.preparingTools.get(data.preparing_id); - ctx.preparingTools.delete(data.preparing_id); - } else { - action = ctx.toolFindAction(data.id, data.preparing_id, data.execution_id); - } - if (!action) { - const msg = ctx.chatEnsureAssistantMessage(); - if (!msg) { - return; + const handler = () => { + let action = null; + if (data.preparing_id && ctx.preparingTools.has(data.preparing_id)) { + action = ctx.preparingTools.get(data.preparing_id); + ctx.preparingTools.delete(data.preparing_id); + } else { + action = ctx.toolFindAction(data.id, data.preparing_id, data.execution_id); } - action = { - id: data.id, - type: 'tool', - tool: { - id: data.id, - name: data.name, - arguments: {}, - argumentSnapshot: null, - argumentLabel: '', - status: 'running', - result: null - }, - timestamp: Date.now() - }; - msg.actions.push(action); + if (!action) { + const msg = ctx.chatEnsureAssistantMessage(); + if (!msg) { + return; + } + action = { + id: data.id, + type: 'tool', + tool: { + id: data.id, + name: data.name, + arguments: {}, + argumentSnapshot: null, + argumentLabel: '', + status: 'running', + result: null + }, + timestamp: Date.now() + }; + msg.actions.push(action); + } + action.tool.status = 'running'; + action.tool.arguments = data.arguments; + action.tool.argumentSnapshot = ctx.cloneToolArguments(data.arguments); + action.tool.argumentLabel = ctx.buildToolLabel(action.tool.argumentSnapshot); + action.tool.message = null; + action.tool.id = data.id; + action.tool.executionId = data.id; + ctx.toolRegisterAction(action, data.id); + ctx.toolTrackAction(data.name, action); + ctx.$forceUpdate(); + ctx.conditionalScrollToBottom(); + ctx.monitorQueueTool(data); + }; + + if (hasPendingStreamingText()) { + pendingToolEvents.push({ event: 'tool_start', data, handler }); + } else { + handler(); } - action.tool.status = 'running'; - action.tool.arguments = data.arguments; - action.tool.argumentSnapshot = ctx.cloneToolArguments(data.arguments); - action.tool.argumentLabel = ctx.buildToolLabel(action.tool.argumentSnapshot); - action.tool.message = null; - action.tool.id = data.id; - action.tool.executionId = data.id; - ctx.toolRegisterAction(action, data.id); - ctx.toolTrackAction(data.name, action); - ctx.$forceUpdate(); - ctx.conditionalScrollToBottom(); - ctx.monitorQueueTool(data); }); // 更新action(工具完成) @@ -917,82 +961,89 @@ export async function initializeLegacySocket(ctx: any) { socketLog('跳过update_action(对话不匹配)', data.conversation_id); return; } - let targetAction = ctx.toolFindAction(data.id, data.preparing_id, data.execution_id); - if (!targetAction && data.preparing_id && ctx.preparingTools.has(data.preparing_id)) { - targetAction = ctx.preparingTools.get(data.preparing_id); - } - if (!targetAction) { - outer: for (const message of ctx.messages) { - if (!message.actions) continue; - for (const action of message.actions) { - if (action.type !== 'tool') continue; - const matchByExecution = action.tool.executionId && action.tool.executionId === data.id; - const matchByToolId = action.tool.id === data.id; - const matchByPreparingId = action.id === data.preparing_id; - if (matchByExecution || matchByToolId || matchByPreparingId) { - targetAction = action; - break outer; + const handler = () => { + let targetAction = ctx.toolFindAction(data.id, data.preparing_id, data.execution_id); + if (!targetAction && data.preparing_id && ctx.preparingTools.has(data.preparing_id)) { + targetAction = ctx.preparingTools.get(data.preparing_id); + } + if (!targetAction) { + outer: for (const message of ctx.messages) { + if (!message.actions) continue; + for (const action of message.actions) { + if (action.type !== 'tool') continue; + const matchByExecution = action.tool.executionId && action.tool.executionId === data.id; + const matchByToolId = action.tool.id === data.id; + const matchByPreparingId = action.id === data.preparing_id; + if (matchByExecution || matchByToolId || matchByPreparingId) { + targetAction = action; + break outer; + } } } } - } - if (targetAction) { - if (data.status) { - targetAction.tool.status = data.status; - } - if (data.result !== undefined) { - targetAction.tool.result = data.result; - } - if (targetAction.tool && targetAction.tool.name === 'trigger_easter_egg' && data.result !== undefined) { - const eggPromise = ctx.handleEasterEggPayload(data.result); - if (eggPromise && typeof eggPromise.catch === 'function') { - eggPromise.catch((error) => { - console.warn('彩蛋处理异常:', error); - }); + if (targetAction) { + if (data.status) { + targetAction.tool.status = data.status; } - } - if (data.message !== undefined) { - targetAction.tool.message = data.message; - } - if (data.awaiting_content) { - targetAction.tool.awaiting_content = true; - } else if (data.status === 'completed') { - targetAction.tool.awaiting_content = false; - } - if (!targetAction.tool.executionId && (data.execution_id || data.id)) { - targetAction.tool.executionId = data.execution_id || data.id; - } - if (data.arguments) { - targetAction.tool.arguments = data.arguments; - targetAction.tool.argumentSnapshot = ctx.cloneToolArguments(data.arguments); - targetAction.tool.argumentLabel = ctx.buildToolLabel(targetAction.tool.argumentSnapshot); - } - ctx.toolRegisterAction(targetAction, data.execution_id || data.id); - if (data.status && ["completed", "failed", "error"].includes(data.status) && !data.awaiting_content) { - ctx.toolUnregisterAction(targetAction); - if (data.id) { - ctx.preparingTools.delete(data.id); + if (data.result !== undefined) { + targetAction.tool.result = data.result; } - if (data.preparing_id) { - ctx.preparingTools.delete(data.preparing_id); + if (targetAction.tool && targetAction.tool.name === 'trigger_easter_egg' && data.result !== undefined) { + const eggPromise = ctx.handleEasterEggPayload(data.result); + if (eggPromise && typeof eggPromise.catch === 'function') { + eggPromise.catch((error) => { + console.warn('彩蛋处理异常:', error); + }); + } } + if (data.message !== undefined) { + targetAction.tool.message = data.message; + } + if (data.awaiting_content) { + targetAction.tool.awaiting_content = true; + } else if (data.status === 'completed') { + targetAction.tool.awaiting_content = false; + } + if (!targetAction.tool.executionId && (data.execution_id || data.id)) { + targetAction.tool.executionId = data.execution_id || data.id; + } + if (data.arguments) { + targetAction.tool.arguments = data.arguments; + targetAction.tool.argumentSnapshot = ctx.cloneToolArguments(data.arguments); + targetAction.tool.argumentLabel = ctx.buildToolLabel(targetAction.tool.argumentSnapshot); + } + ctx.toolRegisterAction(targetAction, data.execution_id || data.id); + if (data.status && ["completed", "failed", "error"].includes(data.status) && !data.awaiting_content) { + ctx.toolUnregisterAction(targetAction); + if (data.id) { + ctx.preparingTools.delete(data.id); + } + if (data.preparing_id) { + ctx.preparingTools.delete(data.preparing_id); + } + } + ctx.$forceUpdate(); + ctx.conditionalScrollToBottom(); } - ctx.$forceUpdate(); - ctx.conditionalScrollToBottom(); - } - // 关键修复:每个工具完成后都更新当前上下文Token - if (data.status === 'completed') { - setTimeout(() => { - ctx.updateCurrentContextTokens(); - }, 500); - try { - ctx.fileFetchTree(); - } catch (error) { - console.warn('刷新文件树失败', error); + if (data.status === 'completed') { + setTimeout(() => { + ctx.updateCurrentContextTokens(); + }, 500); + try { + ctx.fileFetchTree(); + } catch (error) { + console.warn('刷新文件树失败', error); + } } + ctx.monitorResolveTool(data); + }; + + if (hasPendingStreamingText()) { + pendingToolEvents.push({ event: 'update_action', data, handler }); + } else { + handler(); } - ctx.monitorResolveTool(data); }); ctx.socket.on('append_payload', (data) => { @@ -1046,6 +1097,8 @@ export async function initializeLegacySocket(ctx: any) { socketLog('任务完成', data); ctx.scheduleResetAfterTask('socket:task_complete', { preserveMonitorWindows: true }); + resetPendingToolEvents(); + // 任务完成后立即更新Token统计(关键修复) if (ctx.currentConversationId) { ctx.updateCurrentContextTokens();