// @ts-nocheck import { io as createSocketClient } from 'socket.io-client'; import { renderLatexInRealtime } from './useMarkdownRenderer'; export async function initializeLegacySocket(ctx: any) { try { const SOCKET_DEBUG_LOGS_ENABLED = false; const socketLog = (...args: any[]) => { if (!SOCKET_DEBUG_LOGS_ENABLED) { return; } console.log(...args); }; socketLog('初始化WebSocket连接...'); const socketOptions = { transports: ['websocket', 'polling'], autoConnect: false }; ctx.socket = createSocketClient('/', socketOptions); const STREAMING_CHAR_DELAY = 22; const STREAMING_FINALIZE_DELAY = 1000; const STREAMING_DEBUG = false; const STREAMING_DEBUG_HISTORY_LIMIT = 2000; const streamingState = { buffer: [] as string[], timer: null as number | null, completionTimer: null as number | null, apiCompleted: false, pendingCompleteContent: '' as string, renderedText: '' as string, activeMessageIndex: null as number | null, activeTextAction: null as any, ignoreThinking: false }; const pendingToolEvents: Array<{ event: string; data: any; handler: () => void }> = []; const startIntentTyping = (action: any, intentText: any) => { if (!action || !action.tool) { return; } if (typeof intentText !== 'string') { return; } const text = intentText.trim(); if (!text) { return; } if (action.tool.intent_full === text) { if (console && console.debug) { console.debug('[tool_intent] skip (same text)', { id: action?.id || action?.tool?.id, name: action?.tool?.name, text }); } return; } if (typeof console !== 'undefined' && console.debug) { console.debug('[tool_intent] typing start', { id: action?.id || action?.tool?.id, name: action?.tool?.name, text }); } if (action.tool.intent_full === text && action.tool.intent_rendered === text) { return; } // 清理旧定时器 if (action.tool.intent_typing_timer) { clearInterval(action.tool.intent_typing_timer); action.tool.intent_typing_timer = null; } action.tool.intent_full = text; action.tool.intent_rendered = ''; const duration = 1000; const step = Math.max(10, Math.floor(duration / Math.max(1, text.length))); let index = 0; action.tool.intent_typing_timer = window.setInterval(() => { action.tool.intent_rendered = text.slice(0, index + 1); if (index === 0 && console && console.debug) { console.debug('[tool_intent] typing tick', { id: action?.id || action?.tool?.id, name: action?.tool?.name, next: action.tool.intent_rendered }); } index += 1; if (index >= text.length) { clearInterval(action.tool.intent_typing_timer); action.tool.intent_typing_timer = null; action.tool.intent_rendered = text; if (console && console.debug) { console.debug('[tool_intent] typing done', { id: action?.id || action?.tool?.id, name: action?.tool?.name, full: text }); } } if (typeof ctx?.$forceUpdate === 'function') { ctx.$forceUpdate(); } }, step); }; 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, completionTimerActive: streamingState.completionTimer !== null, apiCompleted: streamingState.apiCompleted, pendingLength: (streamingState.pendingCompleteContent || '').length, renderedLength: streamingState.renderedText.length, currentMessageIndex: typeof ctx?.currentMessageIndex === 'number' ? ctx.currentMessageIndex : null, messagesLength: Array.isArray(ctx?.messages) ? ctx.messages.length : null, streamingMessage: !!ctx?.streamingMessage }); const streamingDebugHistory: Array = []; const sanitizeBubbleText = (text: unknown) => { if (typeof text !== 'string') { return ''; } return text.replace(/\r/g, ''); }; const getActiveMessage = () => { if (streamingState.activeMessageIndex === null) { return null; } const messages = ctx?.messages; if (!Array.isArray(messages)) { return null; } return messages[streamingState.activeMessageIndex] || null; }; const ensureActiveMessageBinding = () => { if ( streamingState.activeMessageIndex === null && typeof ctx?.currentMessageIndex === 'number' && ctx.currentMessageIndex >= 0 ) { streamingState.activeMessageIndex = ctx.currentMessageIndex; } if ( typeof ctx?.currentMessageIndex !== 'number' || ctx.currentMessageIndex < 0 ) { if (streamingState.activeMessageIndex !== null) { ctx.currentMessageIndex = streamingState.activeMessageIndex; } } if (!ctx.streamingMessage) { ctx.streamingMessage = true; } const msg = getActiveMessage(); if (!msg && Array.isArray(ctx?.messages) && ctx.messages.length) { streamingState.activeMessageIndex = ctx.messages.length - 1; } return getActiveMessage(); }; const ensureActiveTextAction = () => { const msg = getActiveMessage(); if (!msg || !Array.isArray(msg.actions) || !msg.actions.length) { return null; } const known = streamingState.activeTextAction; if (known && msg.actions.includes(known)) { return known; } for (let i = msg.actions.length - 1; i >= 0; i--) { const action = msg.actions[i]; if (action && action.type === 'text') { streamingState.activeTextAction = action; return action; } } return null; }; const fallbackAppendToActiveMessage = (text: string) => { const msg = ensureActiveMessageBinding(); if (!msg) { return null; } if (typeof msg.streamingText !== 'string') { msg.streamingText = ''; } msg.streamingText += text; const action = ensureActiveTextAction(); if (!action) { return null; } if (typeof action.content !== 'string') { action.content = ''; } action.content += text; action.streaming = action.streaming !== false; streamingState.activeTextAction = action; ctx.$forceUpdate(); ctx.conditionalScrollToBottom(); renderLatexInRealtime(); return action; }; const completeActiveTextAction = (fullContent: string) => { const msg = ensureActiveMessageBinding(); if (!msg) { return false; } const action = ensureActiveTextAction(); if (action) { action.streaming = false; const fallback = (typeof fullContent === 'string' && fullContent.length ? fullContent : action.content) || ''; action.content = fallback; } msg.streamingText = ''; msg.currentStreamingType = null; return !!action; }; const logStreamingDebug = (event: string, detail?: any) => { if (!STREAMING_DEBUG) { return; } const payload = typeof detail === 'undefined' ? snapshotStreamingState() : detail; const entry = { event, detail: payload, conversationId: ctx.currentConversationId || null, timestamp: Date.now() }; streamingDebugHistory.push(entry); if (streamingDebugHistory.length > STREAMING_DEBUG_HISTORY_LIMIT) { streamingDebugHistory.shift(); } try { window.__streamingDebugLogs = streamingDebugHistory.slice(); } catch (error) { // ignore } console.log('[streaming-debug]', event, payload); try { if (ctx.socket && typeof ctx.socket.emit === 'function') { ctx.socket.emit('client_stream_debug_log', entry); } } catch (error) { console.warn('上报 streaming debug 日志失败:', error); } }; const markStreamingIdleIfPossible = (source: string) => { try { if (!ctx) { return; } if (typeof ctx.maybeResetStreamingState === 'function') { const reset = ctx.maybeResetStreamingState(source); if (reset) { logStreamingDebug('streaming_idle_reset', { source }); } return; } if (!ctx.streamingMessage) { return; } const hasPending = typeof ctx.hasPendingToolActions === 'function' ? ctx.hasPendingToolActions() : false; if (!hasPending) { ctx.streamingMessage = false; ctx.stopRequested = false; logStreamingDebug('streaming_idle_reset:fallback', { source }); } } catch (error) { console.warn('自动结束流式状态失败:', error); } }; const stopStreamingTimer = () => { if (streamingState.timer !== null) { clearTimeout(streamingState.timer); streamingState.timer = null; logStreamingDebug('stopStreamingTimer', snapshotStreamingState()); } }; const stopCompletionTimer = () => { if (streamingState.completionTimer !== null) { clearTimeout(streamingState.completionTimer); streamingState.completionTimer = null; logStreamingDebug('stopCompletionTimer', snapshotStreamingState()); } }; const finalizeStreamingText = (options?: { force?: boolean; allowIncomplete?: boolean }) => { const forceFlush = !!options?.force; const allowIncomplete = !!options?.allowIncomplete; logStreamingDebug('finalizeStreamingText:start', { forceFlush, allowIncomplete, snapshot: snapshotStreamingState() }); if (!forceFlush) { if (streamingState.buffer.length) { logStreamingDebug('finalizeStreamingText:blocked-buffer', snapshotStreamingState()); return false; } if (!allowIncomplete && !streamingState.apiCompleted) { logStreamingDebug('finalizeStreamingText:blocked-api-incomplete', snapshotStreamingState()); return false; } } stopStreamingTimer(); stopCompletionTimer(); if (forceFlush && streamingState.buffer.length) { const remainder = streamingState.buffer.join(''); streamingState.buffer.length = 0; if (remainder) { applyTextChunk(remainder); logStreamingDebug('finalizeStreamingText:force-flush-buffer', { flushedChars: remainder.length }); } } const pendingText = streamingState.pendingCompleteContent || ''; const renderedText = streamingState.renderedText || ''; let finalText = pendingText || renderedText || ''; let remainderToAppend = ''; if (!pendingText) { finalText = renderedText; } else if (!renderedText) { finalText = pendingText; remainderToAppend = pendingText; } else if (pendingText.length < renderedText.length) { // 后端返回的最终内容比已渲染的还短,避免覆盖已展示的字符 finalText = renderedText; } else if (pendingText.startsWith(renderedText)) { finalText = pendingText; remainderToAppend = pendingText.slice(renderedText.length); } else { finalText = pendingText; } logStreamingDebug('finalizeStreamingText:resolved-final-text', { pendingLength: pendingText.length, renderedLength: renderedText.length, remainderToAppendLength: remainderToAppend.length, finalLength: finalText.length }); if (remainderToAppend) { applyTextChunk(remainderToAppend); } streamingState.pendingCompleteContent = ''; streamingState.apiCompleted = false; streamingState.renderedText = ''; ctx.chatCompleteTextAction(finalText || ''); completeActiveTextAction(finalText || ''); ctx.$forceUpdate(); // 注意:不在这里重置 streamingMessage,因为可能还有工具调用在进行 // streamingMessage 只在 task_complete 事件时重置 logStreamingDebug('finalizeStreamingText:complete', snapshotStreamingState()); streamingState.activeMessageIndex = null; streamingState.activeTextAction = null; markStreamingIdleIfPossible('finalizeStreamingText'); flushPendingToolEvents(); return true; }; const scheduleFinalizationAfterDrain = () => { if (streamingState.buffer.length) { logStreamingDebug('scheduleFinalizationAfterDrain:buffer-not-empty', snapshotStreamingState()); return; } if (streamingState.completionTimer !== null) { logStreamingDebug('scheduleFinalizationAfterDrain:already-scheduled', snapshotStreamingState()); return; } logStreamingDebug('scheduleFinalizationAfterDrain:scheduled', snapshotStreamingState()); streamingState.completionTimer = window.setTimeout(() => { streamingState.completionTimer = null; logStreamingDebug('scheduleFinalizationAfterDrain:timer-fired', snapshotStreamingState()); finalizeStreamingText({ allowIncomplete: true }); }, STREAMING_FINALIZE_DELAY); }; const resetStreamingBuffer = () => { stopStreamingTimer(); stopCompletionTimer(); streamingState.buffer.length = 0; streamingState.apiCompleted = false; streamingState.pendingCompleteContent = ''; streamingState.renderedText = ''; streamingState.activeMessageIndex = null; streamingState.activeTextAction = null; resetPendingToolEvents(); logStreamingDebug('resetStreamingBuffer', snapshotStreamingState()); }; const applyTextChunk = (text: string) => { if (!text) { return null; } ensureActiveMessageBinding(); let action = ctx.chatAppendTextChunk(text); if (action) { ctx.$forceUpdate(); ctx.conditionalScrollToBottom(); renderLatexInRealtime(); } else { action = fallbackAppendToActiveMessage(text); } streamingState.renderedText += text; const appended = !!action; logStreamingDebug('applyTextChunk', { chunkLength: text.length, appended, snapshot: snapshotStreamingState() }); if (!appended) { logStreamingDebug('applyTextChunk:missing-target', { chunkLength: text.length, currentMessageIndex: typeof ctx?.currentMessageIndex === 'number' ? ctx.currentMessageIndex : null, messagesLength: Array.isArray(ctx?.messages) ? ctx.messages.length : null }); } return action; }; const drainStreamingBufferImmediately = () => { if (!streamingState.buffer.length) { return; } const chunk = streamingState.buffer.join(''); streamingState.buffer.length = 0; applyTextChunk(chunk); }; const scheduleStreamingFlush = () => { const shouldFlushImmediately = typeof document !== 'undefined' && document.hidden === true; if (shouldFlushImmediately) { stopStreamingTimer(); drainStreamingBufferImmediately(); if (streamingState.apiCompleted) { finalizeStreamingText({ force: true }); } else { scheduleFinalizationAfterDrain(); } return; } if (streamingState.timer !== null) { logStreamingDebug('scheduleStreamingFlush:timer-exists', snapshotStreamingState()); return; } if (!streamingState.buffer.length) { logStreamingDebug('scheduleStreamingFlush:no-buffer', snapshotStreamingState()); return; } logStreamingDebug('scheduleStreamingFlush:start', snapshotStreamingState()); const process = () => { streamingState.timer = null; logStreamingDebug('scheduleStreamingFlush:tick', snapshotStreamingState()); if (!streamingState.buffer.length) { logStreamingDebug('scheduleStreamingFlush:buffer-empty', snapshotStreamingState()); return; } const piece = streamingState.buffer.shift(); if (piece) { applyTextChunk(piece); } if (streamingState.buffer.length) { scheduleStreamingFlush(); } else { logStreamingDebug('scheduleStreamingFlush:buffer-drained', snapshotStreamingState()); scheduleFinalizationAfterDrain(); } }; streamingState.timer = window.setTimeout(process, STREAMING_CHAR_DELAY); }; const enqueueStreamingContent = (text: string) => { if (!text) { return; } stopCompletionTimer(); logStreamingDebug('enqueueStreamingContent', { incomingLength: text.length }); for (const ch of Array.from(text)) { streamingState.buffer.push(ch); } logStreamingDebug('enqueueStreamingContent:buffered', snapshotStreamingState()); scheduleStreamingFlush(); }; const scheduleHistoryReload = (delay = 0) => { if (!ctx || typeof ctx.fetchAndDisplayHistory !== 'function') { return; } if (!ctx.currentConversationId || ctx.currentConversationId.startsWith('temp_')) { return; } setTimeout(() => { try { ctx.fetchAndDisplayHistory(); if (typeof ctx.fetchConversationTokenStatistics === 'function') { ctx.fetchConversationTokenStatistics(); } if (typeof ctx.updateCurrentContextTokens === 'function') { ctx.updateCurrentContextTokens(); } } catch (error) { console.warn('重新加载对话历史失败:', error); } }, delay); }; const assignSocketToken = async () => { if (typeof window.requestSocketToken !== 'function') { console.warn('缺少 requestSocketToken(),无法获取实时连接凭证'); return false; } try { const freshToken = await window.requestSocketToken(); ctx.socket.auth = { socket_token: freshToken }; return true; } catch (error) { console.error('获取 WebSocket token 失败:', error); return false; } }; if (ctx.socket.io && typeof ctx.socket.io.on === 'function') { ctx.socket.io.on('reconnect_attempt', async () => { await assignSocketToken(); }); } // 连接事件 ctx.socket.on('connect', () => { ctx.isConnected = true; socketLog('WebSocket已连接'); // 连接时重置所有状态并刷新当前对话 ctx.resetAllStates('socket:connect'); scheduleHistoryReload(200); }); ctx.socket.on('disconnect', () => { ctx.isConnected = false; socketLog('WebSocket已断开'); // 断线时也重置状态,防止状态混乱 ctx.resetAllStates('socket:disconnect'); }); ctx.socket.on('connect_error', (error) => { console.error('WebSocket连接错误:', error.message); }); ctx.socket.on('quota_update', (data) => { if (data && data.quotas) { ctx.resourceSetUsageQuota({ quotas: data.quotas }); } else { ctx.fetchUsageQuota(); } }); ctx.socket.on('quota_notice', (data) => { ctx.showQuotaToast(data || {}); ctx.fetchUsageQuota(); }); ctx.socket.on('quota_exceeded', (data) => { ctx.showQuotaToast(data || {}); ctx.fetchUsageQuota(); }); ctx.socket.on('reconnect_attempt', async () => { await assignSocketToken(); }); const ready = await assignSocketToken(); if (!ready) { console.error('无法获取实时连接凭证,WebSocket 初始化中止。'); return; } ctx.socket.connect(); // ========================================== // Token统计WebSocket事件处理(修复版) // ========================================== ctx.socket.on('token_update', (data) => { socketLog('收到token更新事件:', data); // 只处理当前对话的token更新 if (data.conversation_id === ctx.currentConversationId) { // 更新累计统计(使用后端提供的准确字段名) ctx.currentConversationTokens.cumulative_input_tokens = data.cumulative_input_tokens || 0; ctx.currentConversationTokens.cumulative_output_tokens = data.cumulative_output_tokens || 0; ctx.currentConversationTokens.cumulative_total_tokens = data.cumulative_total_tokens || 0; socketLog(`累计Token统计更新: 输入=${data.cumulative_input_tokens}, 输出=${data.cumulative_output_tokens}, 总计=${data.cumulative_total_tokens}`); const hasContextTokens = typeof data.current_context_tokens === 'number'; if (hasContextTokens && typeof ctx.resourceSetCurrentContextTokens === 'function') { ctx.resourceSetCurrentContextTokens(data.current_context_tokens); } else { // 同时更新当前上下文Token(关键修复) ctx.updateCurrentContextTokens(); } ctx.$forceUpdate(); } }); ctx.socket.on('todo_updated', (data) => { socketLog('收到todo更新事件:', data); if (data && data.conversation_id) { ctx.currentConversationId = data.conversation_id; } ctx.fileSetTodoList((data && data.todo_list) || null); }); // 系统就绪 ctx.socket.on('system_ready', (data) => { ctx.projectPath = data.project_path || ''; ctx.agentVersion = data.version || ctx.agentVersion; ctx.thinkingMode = !!data.thinking_mode; if (data.run_mode) { ctx.runMode = data.run_mode; } else { ctx.runMode = ctx.thinkingMode ? 'thinking' : 'fast'; } socketLog('系统就绪:', data); // 系统就绪后立即加载对话列表 ctx.loadConversationsList(); }); ctx.socket.on('tool_settings_updated', (data) => { socketLog('收到工具设置更新:', data); if (data && Array.isArray(data.categories)) { ctx.applyToolSettingsSnapshot(data.categories); } }); // ========================================== // 对话管理相关Socket事件 // ========================================== // 监听对话变更事件 ctx.socket.on('conversation_changed', (data) => { socketLog('对话已切换:', data); console.log('[conv-trace] socket:conversation_changed', data); ctx.currentConversationId = data.conversation_id; ctx.currentConversationTitle = data.title || ''; ctx.promoteConversationToTop(data.conversation_id); if (data.cleared) { // 对话被清空 ctx.logMessageState?.('socket:conversation_changed-clearing', { event: data }); ctx.messages = []; ctx.logMessageState?.('socket:conversation_changed-cleared', { event: data }); ctx.currentConversationId = null; ctx.currentConversationTitle = ''; // 重置Token统计 ctx.resetTokenStatistics(); history.replaceState({}, '', '/new'); } // 刷新对话列表 ctx.loadConversationsList(); ctx.fetchTodoList(); ctx.fetchSubAgents(); }); ctx.socket.on('conversation_resolved', (data) => { if (!data || !data.conversation_id) { return; } const convId = data.conversation_id; console.log('[conv-trace] socket:conversation_resolved', data); ctx.currentConversationId = convId; if (data.title) { ctx.currentConversationTitle = data.title; } ctx.promoteConversationToTop(convId); const pathFragment = ctx.stripConversationPrefix(convId); const currentPath = window.location.pathname.replace(/^\/+/, ''); if (data.created) { history.pushState({ conversationId: convId }, '', `/${pathFragment}`); } else if (currentPath !== pathFragment) { history.replaceState({ conversationId: convId }, '', `/${pathFragment}`); } }); // 监听对话加载事件 ctx.socket.on('conversation_loaded', (data) => { socketLog('对话已加载:', data); if (ctx.skipConversationLoadedEvent) { ctx.skipConversationLoadedEvent = false; socketLog('跳过重复的 conversation_loaded 处理'); return; } if (data.clear_ui) { // 清理当前UI状态,准备显示历史内容 ctx.resetAllStates('socket:conversation_loaded'); } // 延迟获取并显示历史对话内容 setTimeout(() => { ctx.fetchAndDisplayHistory(); }, 300); // 延迟获取Token统计(累计+当前上下文) setTimeout(() => { ctx.fetchConversationTokenStatistics(); ctx.updateCurrentContextTokens(); ctx.fetchTodoList(); }, 500); }); // 监听对话列表更新事件 ctx.socket.on('conversation_list_update', (data) => { socketLog('对话列表已更新:', data); // 刷新对话列表 ctx.conversationsOffset = 0; ctx.hasMoreConversations = false; ctx.loadingMoreConversations = false; ctx.loadConversationsList(); }); // 监听状态更新事件 ctx.socket.on('status_update', (status) => { ctx.applyStatusSnapshot(status); if (status.conversation && status.conversation.current_id) { ctx.currentConversationId = status.conversation.current_id; } if (typeof status.run_mode === 'string') { ctx.runMode = status.run_mode; } else if (typeof status.thinking_mode !== 'undefined') { ctx.runMode = status.thinking_mode ? 'thinking' : 'fast'; } }); // AI消息开始 ctx.socket.on('ai_message_start', () => { socketLog('AI消息开始'); logStreamingDebug('socket:ai_message_start'); finalizeStreamingText({ force: true }); flushPendingToolEvents(); resetStreamingBuffer(); ctx.monitorResetSpeech(); ctx.cleanupStaleToolActions(); ctx.taskInProgress = true; ctx.chatStartAssistantMessage(); streamingState.activeMessageIndex = typeof ctx.currentMessageIndex === 'number' ? ctx.currentMessageIndex : null; streamingState.activeTextAction = null; ctx.stopRequested = false; ctx.streamingMessage = true; // 确保设置为流式状态 ctx.chatEnableAutoScroll(); ctx.scrollToBottom(); }); // 思考流开始 ctx.socket.on('thinking_start', () => { socketLog('思考开始'); const ignoreThinking = ctx.runMode === 'fast' || ctx.thinkingMode === false; streamingState.ignoreThinking = ignoreThinking; if (ignoreThinking) { ctx.monitorEndModelOutput(); return; } ctx.monitorShowThinking(); const result = ctx.chatStartThinkingAction(); if (result && result.blockId) { const blockId = result.blockId; ctx.chatExpandBlock(blockId); // 开始思考时自动锁定滚动到底部 ctx.chatEnableAutoScroll(); ctx.scrollToBottom(); ctx.chatSetThinkingLock(blockId, true); ctx.$forceUpdate(); } }); // 思考内容块 ctx.socket.on('thinking_chunk', (data) => { if (streamingState.ignoreThinking) { return; } const thinkingAction = ctx.chatAppendThinkingChunk(data.content); if (thinkingAction) { ctx.$forceUpdate(); ctx.$nextTick(() => { if (thinkingAction && thinkingAction.blockId) { ctx.scrollThinkingToBottom(thinkingAction.blockId); } ctx.conditionalScrollToBottom(); }); } ctx.monitorShowThinking(); }); // 思考结束 ctx.socket.on('thinking_end', (data) => { socketLog('思考结束'); if (streamingState.ignoreThinking) { streamingState.ignoreThinking = false; return; } const blockId = ctx.chatCompleteThinkingAction(data.full_content); if (blockId) { setTimeout(() => { ctx.chatCollapseBlock(blockId); ctx.chatSetThinkingLock(blockId, false); ctx.$forceUpdate(); }, 1000); ctx.$nextTick(() => ctx.scrollThinkingToBottom(blockId)); } ctx.$forceUpdate(); ctx.monitorEndModelOutput(); }); // 文本流开始 ctx.socket.on('text_start', () => { socketLog('文本开始'); logStreamingDebug('socket:text_start'); finalizeStreamingText({ force: true }); resetStreamingBuffer(); const action = ctx.chatStartTextAction(); streamingState.activeMessageIndex = typeof ctx.currentMessageIndex === 'number' ? ctx.currentMessageIndex : null; streamingState.activeTextAction = action || ensureActiveTextAction(); ensureActiveMessageBinding(); ctx.$forceUpdate(); }); // 文本内容块 ctx.socket.on('text_chunk', (data) => { logStreamingDebug('socket:text_chunk', { index: data?.index ?? null, elapsed: data?.elapsed ?? null, chunkLength: (data?.content || '').length, snapshot: snapshotStreamingState() }); try { ctx.socket.emit('client_chunk_log', { conversation_id: ctx.currentConversationId, index: data?.index ?? null, elapsed: data?.elapsed ?? null, length: (data?.content || '').length, ts: Date.now() }); } catch (error) { console.warn('上报chunk日志失败:', error); } if (data && typeof data.content === 'string' && data.content.length) { enqueueStreamingContent(data.content); const speech = sanitizeBubbleText(data.content); if (speech) { ctx.monitorShowSpeech(speech); } } }); // 文本结束 ctx.socket.on('text_end', (data) => { socketLog('文本结束'); logStreamingDebug('socket:text_end', { finalLength: (data?.full_content || '').length, snapshot: snapshotStreamingState() }); streamingState.apiCompleted = true; streamingState.pendingCompleteContent = data?.full_content || ''; const hidden = typeof document !== 'undefined' && document.hidden === true; if (hidden) { stopStreamingTimer(); drainStreamingBufferImmediately(); finalizeStreamingText({ force: true }); } else if (!streamingState.buffer.length) { scheduleFinalizationAfterDrain(); } else { scheduleStreamingFlush(); } ctx.monitorEndModelOutput(); flushPendingToolEvents(); }); // 工具提示事件(可选) ctx.socket.on('tool_hint', (data) => { socketLog('工具提示:', data.name); if (data?.conversation_id && data.conversation_id !== ctx.currentConversationId) { socketLog('跳过tool_hint(对话不匹配)', data.conversation_id); return; } // 可以在这里添加提示UI }); // 工具准备中事件 - 实时显示 ctx.socket.on('tool_preparing', (data) => { if (ctx.dropToolEvents) { return; } socketLog('工具准备中:', data.name); if (typeof console !== 'undefined' && console.debug) { console.debug('[tool_intent] preparing', { id: data?.id, name: data?.name, intent: data?.intent, convo: data?.conversation_id }); } if (data?.conversation_id && data.conversation_id !== ctx.currentConversationId) { socketLog('跳过tool_preparing(对话不匹配)', data.conversation_id); return; } const msg = ctx.chatEnsureAssistantMessage(); if (!msg) { return; } const action = { id: data.id, type: 'tool', tool: { id: data.id, name: data.name, arguments: {}, argumentSnapshot: null, argumentLabel: '', status: 'preparing', result: null, message: data.message || `准备调用 ${data.name}...`, intent_full: data.intent || '', intent_rendered: data.intent || '' }, timestamp: Date.now() }; msg.actions.push(action); ctx.preparingTools.set(data.id, action); ctx.toolRegisterAction(action, data.id); ctx.toolTrackAction(data.name, action); if (data.intent) { startIntentTyping(action, data.intent); } ctx.$forceUpdate(); ctx.conditionalScrollToBottom(); if (ctx.monitorPreviewTool) { ctx.monitorPreviewTool(data); } }); // 工具意图(流式增量)事件 ctx.socket.on('tool_intent', (data) => { if (ctx.dropToolEvents) { return; } if (data?.conversation_id && data.conversation_id !== ctx.currentConversationId) { return; } if (typeof console !== 'undefined' && console.debug) { console.debug('[tool_intent] update', { id: data?.id, name: data?.name, intent: data?.intent, convo: data?.conversation_id }); } const target = ctx.toolFindAction(data.id, data.preparing_id, data.execution_id) || ctx.toolGetLatestAction(data.name); if (target) { startIntentTyping(target, data.intent); } ctx.$forceUpdate(); }); // 工具状态更新事件 - 实时显示详细状态 ctx.socket.on('tool_status', (data) => { if (ctx.dropToolEvents) { return; } socketLog('工具状态:', data); if (data?.conversation_id && data.conversation_id !== ctx.currentConversationId) { socketLog('跳过tool_status(对话不匹配)', data.conversation_id); return; } const target = ctx.toolFindAction(data.id, data.preparing_id, data.execution_id); if (target) { target.tool.statusDetail = data.detail; target.tool.statusType = data.status; if (data.intent) { startIntentTyping(target, data.intent); } ctx.$forceUpdate(); return; } const fallbackAction = ctx.toolGetLatestAction(data.tool); if (fallbackAction) { fallbackAction.tool.statusDetail = data.detail; fallbackAction.tool.statusType = data.status; ctx.toolRegisterAction(fallbackAction, data.execution_id || data.id || data.preparing_id); if (data.intent) { startIntentTyping(fallbackAction, data.intent); } ctx.$forceUpdate(); } }); // 工具开始(从准备转为执行) ctx.socket.on('tool_start', (data) => { if (ctx.dropToolEvents) { return; } socketLog('工具开始执行:', data.name); if (data?.conversation_id && data.conversation_id !== ctx.currentConversationId) { socketLog('跳过tool_start(对话不匹配)', data.conversation_id); 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); } 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; if (data.arguments && data.arguments.intent) { startIntentTyping(action, data.arguments.intent); } ctx.toolRegisterAction(action, data.id); ctx.toolTrackAction(data.name, action); ctx.$forceUpdate(); ctx.conditionalScrollToBottom(); ctx.monitorQueueTool(data); }; handler(); }); // 更新action(工具完成) ctx.socket.on('update_action', (data) => { if (ctx.dropToolEvents) { return; } socketLog('更新action:', data.id, 'status:', data.status); if (data?.conversation_id && data.conversation_id !== ctx.currentConversationId) { socketLog('跳过update_action(对话不匹配)', data.conversation_id); return; } 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 (data.message !== undefined) { targetAction.tool.message = data.message; } if (data.arguments && data.arguments.intent) { startIntentTyping(targetAction, data.arguments.intent); } 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); if (data.arguments.intent) { startIntentTyping(targetAction, data.arguments.intent); } } 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(); } if (data.status === 'completed') { setTimeout(() => { ctx.updateCurrentContextTokens(); }, 500); try { ctx.fileFetchTree(); } catch (error) { console.warn('刷新文件树失败', error); } } ctx.monitorResolveTool(data); }; handler(); }); ctx.socket.on('append_payload', (data) => { if (ctx.dropToolEvents) { return; } socketLog('收到append_payload事件:', data); if (data?.conversation_id && data.conversation_id !== ctx.currentConversationId) { socketLog('跳过append_payload(对话不匹配)', data.conversation_id); return; } ctx.chatAddAppendPayloadAction({ path: data.path || '未知文件', forced: !!data.forced, success: data.success === undefined ? true : !!data.success, lines: data.lines ?? null, bytes: data.bytes ?? null }); ctx.$forceUpdate(); ctx.conditionalScrollToBottom(); }); ctx.socket.on('modify_payload', (data) => { if (ctx.dropToolEvents) { return; } socketLog('收到modify_payload事件:', data); if (data?.conversation_id && data.conversation_id !== ctx.currentConversationId) { socketLog('跳过modify_payload(对话不匹配)', data.conversation_id); return; } ctx.chatAddModifyPayloadAction({ path: data.path || '未知文件', total: data.total ?? null, completed: data.completed || [], failed: data.failed || [], forced: !!data.forced }); ctx.$forceUpdate(); ctx.conditionalScrollToBottom(); }); // 停止请求确认 ctx.socket.on('stop_requested', (data) => { socketLog('停止请求已接收:', data.message); // 可以显示提示信息 try { if (typeof ctx.clearPendingTools === 'function') { ctx.clearPendingTools('socket:stop_requested'); } } catch (error) { console.warn('清理未完成工具失败', error); } }); // 任务停止 ctx.socket.on('task_stopped', (data) => { socketLog('任务已停止:', data.message); ctx.taskInProgress = false; ctx.scheduleResetAfterTask('socket:task_stopped', { preserveMonitorWindows: true }); }); // 任务完成(重点:更新Token统计) ctx.socket.on('task_complete', (data) => { socketLog('任务完成', data); ctx.taskInProgress = false; ctx.scheduleResetAfterTask('socket:task_complete', { preserveMonitorWindows: true }); resetPendingToolEvents(); // 任务完成后立即更新Token统计(关键修复) if (ctx.currentConversationId) { ctx.updateCurrentContextTokens(); ctx.fetchConversationTokenStatistics(); } }); // 聚焦文件更新 ctx.socket.on('focused_files_update', (data) => { ctx.focusSetFiles(data || {}); // 聚焦文件变化时更新当前上下文Token(关键修复) if (ctx.currentConversationId) { setTimeout(() => { ctx.updateCurrentContextTokens(); }, 500); } }); // 文件树更新 ctx.socket.on('file_tree_update', (data) => { ctx.fileSetTreeFromResponse(data); // 文件树变化时也可能影响上下文 if (ctx.currentConversationId) { setTimeout(() => { ctx.updateCurrentContextTokens(); }, 500); } }); // 系统消息 ctx.socket.on('system_message', (data) => { if (!data || !data.content) { return; } ctx.appendSystemAction(data.content); }); // 错误处理 ctx.socket.on('error', (data) => { ctx.addSystemMessage(`错误: ${data.message}`); // 仅标记当前流结束,避免状态错乱 ctx.streamingMessage = false; ctx.stopRequested = false; ctx.taskInProgress = false; }); // 命令结果 ctx.socket.on('command_result', (data) => { if (data.command === 'clear' && data.success) { ctx.logMessageState?.('socket:command_result-clear', { data }); ctx.messages = []; ctx.logMessageState?.('socket:command_result-cleared', { data }); ctx.currentMessageIndex = -1; ctx.chatClearExpandedBlocks(); // 清除对话时重置Token统计 ctx.resetTokenStatistics(); } else if (data.command === 'status' && data.success) { ctx.addSystemMessage(`系统状态:\n${JSON.stringify(data.data, null, 2)}`); } else if (!data.success) { ctx.addSystemMessage(`命令失败: ${data.message}`); } }); } catch (error) { console.error('Socket初始化失败:', error); } }