From 8da1a3d46df364a261d0146102b17d53a976307d Mon Sep 17 00:00:00 2001 From: JOJO <1498581755@qq.com> Date: Sat, 4 Apr 2026 19:47:46 +0800 Subject: [PATCH] chore(socket): add legacy socket backup snapshot --- .../src/composables/useLegacySocket.ts.backup | 1668 +++++++++++++++++ 1 file changed, 1668 insertions(+) create mode 100644 static/src/composables/useLegacySocket.ts.backup diff --git a/static/src/composables/useLegacySocket.ts.backup b/static/src/composables/useLegacySocket.ts.backup new file mode 100644 index 0000000..e59d764 --- /dev/null +++ b/static/src/composables/useLegacySocket.ts.backup @@ -0,0 +1,1668 @@ +// @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 = true; + 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_ENABLED = false; + 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 = () => + STREAMING_ENABLED && + ( !!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(); + if (!STREAMING_ENABLED) { + // 关闭逐字模式:整块入缓冲并立即刷新,保持与 chunk 顺序一致 + streamingState.pendingCompleteContent = ''; + streamingState.buffer.length = 0; + streamingState.apiCompleted = false; + applyTextChunk(text); + return; + } + 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, options: { force?: boolean } = {}) => { + const { force = false } = options; + if (!ctx || typeof ctx.fetchAndDisplayHistory !== 'function') { + return; + } + setTimeout(() => { + const targetConversationId = ctx.currentConversationId; + if (!targetConversationId || targetConversationId.startsWith('temp_')) { + return; + } + try { + if (!force) { + const loadingSame = + !!ctx.historyLoading && ctx.historyLoadingFor === targetConversationId; + const historyReady = + ctx.lastHistoryLoadedConversationId === targetConversationId && + Array.isArray(ctx.messages) && + ctx.messages.length > 0; + if (loadingSame || historyReady) { + socketLog('跳过重复历史加载(scheduleHistoryReload)'); + return; + } + } + ctx.fetchAndDisplayHistory({ force }); + 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(); + }); + } + + let hasConnectedOnce = false; + + // 连接事件 + ctx.socket.on('connect', () => { + const isReconnect = hasConnectedOnce; + const historyLoadingSame = + !!ctx.historyLoading && ctx.historyLoadingFor === ctx.currentConversationId; + const historyReady = + ctx.lastHistoryLoadedConversationId === ctx.currentConversationId && + Array.isArray(ctx.messages) && + ctx.messages.length > 0; + + ctx.isConnected = true; + socketLog('WebSocket已连接'); + + // 首次连接且历史已在加载/已就绪时,避免重复清空与重复动画 + if (!isReconnect && (historyLoadingSame || historyReady)) { + socketLog('初次连接已存在历史,跳过重复重置与加载'); + hasConnectedOnce = true; + return; + } + + hasConnectedOnce = true; + ctx.resetAllStates(isReconnect ? 'socket:reconnect' : 'socket:connect'); + scheduleHistoryReload(200, { force: isReconnect }); + }); + + 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('context_warning', (data) => { + const title = data?.title || '上下文过长'; + const message = + data?.message || + '当前对话上下文接近上限,建议使用压缩功能。'; + if (typeof ctx.uiPushToast === 'function') { + ctx.uiPushToast({ + title, + message, + type: data?.type || 'warning', + duration: data?.duration || 6000 + }); + } + }); + + ctx.socket.on('todo_updated', (data) => { + socketLog('收到todo更新事件:', data); + // 初始化期间不修改 currentConversationId,避免与 bootstrapRoute 冲突 + if (ctx.initialRouteResolved && 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); + + // 初始化期间不修改 currentConversationId,避免与 bootstrapRoute 冲突 + if (ctx.initialRouteResolved) { + 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); + + // 初始化期间不修改 currentConversationId,避免与 bootstrapRoute 冲突 + if (ctx.initialRouteResolved) { + 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; + } + const targetConversationId = (data && data.conversation_id) || ctx.currentConversationId; + const historyLoadingSame = + !!targetConversationId && + !!ctx.historyLoading && + ctx.historyLoadingFor === targetConversationId; + const historyReady = + !!targetConversationId && + ctx.lastHistoryLoadedConversationId === targetConversationId && + Array.isArray(ctx.messages) && + ctx.messages.length > 0; + const forceReload = !!(data && data.force_reload); + + if (!forceReload && (historyLoadingSame || historyReady)) { + socketLog('conversation_loaded: 历史已加载/加载中,跳过重复刷新'); + } else { + if (data.clear_ui) { + // 清理当前UI状态,准备显示历史内容 + ctx.resetAllStates('socket:conversation_loaded'); + } + // 延迟获取并显示历史对话内容 + setTimeout(() => { + ctx.fetchAndDisplayHistory({ force: forceReload }); + }, 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); + // 只有在初始化完成后,才允许 status_update 修改 currentConversationId + // 避免与 bootstrapRoute 冲突 + if (status.conversation && status.conversation.current_id) { + if (ctx.initialRouteResolved && !ctx.currentConversationId) { + // 初始化完成且当前没有对话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'; + } + }); + + // 用户消息(后台子智能体完成后自动触发) + ctx.socket.on('user_message', (data) => { + if (ctx.usePollingMode && !ctx.waitingForSubAgent) { + return; + } + if (!data) { + return; + } + if (data?.conversation_id && data.conversation_id !== ctx.currentConversationId) { + socketLog('跳过user_message(对话不匹配)', data.conversation_id); + return; + } + const message = (data.message || data.content || '').trim(); + if (!message) { + return; + } + ctx.chatAddUserMessage(message, data.images || [], data.videos || []); + ctx.taskInProgress = true; + ctx.streamingMessage = false; + ctx.stopRequested = false; + if (data?.sub_agent_notice && data?.task_id && ctx.usePollingMode) { + if (typeof data?.has_running_sub_agents === 'boolean') { + ctx.waitingForSubAgent = data.has_running_sub_agents; + } else if (typeof data?.remaining_count === 'number') { + ctx.waitingForSubAgent = data.remaining_count > 0; + } + (async () => { + try { + const { useTaskStore } = await import('../stores/task'); + const taskStore = useTaskStore(); + taskStore.resumeTask(data.task_id); + } catch (error) { + console.warn('恢复任务轮询失败', error); + } + })(); + } + ctx.$forceUpdate(); + ctx.conditionalScrollToBottom(); + }); + + + // AI消息开始(已废弃,改用 REST API 轮询) + ctx.socket.on('ai_message_start', () => { + // 只处理子智能体模式 + if (ctx.waitingForSubAgent) { + ctx.waitingForSubAgent = false; + ctx.taskInProgress = false; + } + }); + + // 思考流开始 + ctx.socket.on('thinking_start', () => { + // 轮询模式下跳过 WebSocket 事件 + if (ctx.usePollingMode && !ctx.waitingForSubAgent) { + socketLog('思考开始 (轮询模式,跳过)'); + return; + } + 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.scrollToBottom(); + ctx.chatSetThinkingLock(blockId, true); + ctx.$forceUpdate(); + } + }); + + // 思考内容块 + ctx.socket.on('thinking_chunk', (data) => { + // 轮询模式下跳过 WebSocket 事件 + if (ctx.usePollingMode && !ctx.waitingForSubAgent) { + return; + } + 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) => { + // 轮询模式下跳过 WebSocket 事件 + if (ctx.usePollingMode && !ctx.waitingForSubAgent) { + socketLog('思考结束 (轮询模式,跳过)'); + return; + } + 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', () => { + // 轮询模式下跳过 WebSocket 事件 + if (ctx.usePollingMode && !ctx.waitingForSubAgent) { + socketLog('文本开始 (轮询模式,跳过)'); + return; + } + console.log('[DEBUG] 收到 text_start 事件:', { + currentConversationId: ctx.currentConversationId, + messagesCount: ctx.messages.length, + currentMessageIndex: ctx.currentMessageIndex + }); + 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(); + console.log('[DEBUG] text_start 处理完成'); + }); + + // 文本内容块 + ctx.socket.on('text_chunk', (data) => { + // 轮询模式下跳过 WebSocket 事件 + if (ctx.usePollingMode && !ctx.waitingForSubAgent) { + return; + } + console.log('[DEBUG] 收到 text_chunk 事件:', { + conversation_id: data?.conversation_id, + currentConversationId: ctx.currentConversationId, + chunkLength: (data?.content || '').length, + index: data?.index + }); + 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) { + if (STREAMING_ENABLED) { + enqueueStreamingContent(data.content); + } else { + // 关闭逐字模式时,直接追加当前chunk + applyTextChunk(data.content); + } + const speech = sanitizeBubbleText(data.content); + if (speech) { + ctx.monitorShowSpeech(speech); + } + } + }); + + // 文本结束 + ctx.socket.on('text_end', (data) => { + // 轮询模式下跳过 WebSocket 事件 + if (ctx.usePollingMode && !ctx.waitingForSubAgent) { + socketLog('文本结束 (轮询模式,跳过)'); + return; + } + socketLog('文本结束'); + logStreamingDebug('socket:text_end', { + finalLength: (data?.full_content || '').length, + snapshot: snapshotStreamingState() + }); + if (!STREAMING_ENABLED) { + // 已逐块追加,这里保证动作收尾 + const full = data?.full_content || ''; + if (full) { + ctx.chatCompleteTextAction(full); + } else { + ctx.chatCompleteTextAction(''); + } + streamingState.buffer.length = 0; + streamingState.pendingCompleteContent = ''; + streamingState.renderedText = ''; + streamingState.apiCompleted = false; + ctx.monitorEndModelOutput(); + flushPendingToolEvents(); + return; + } + 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) => { + // 轮询模式下跳过 WebSocket 事件 + if (ctx.usePollingMode && !ctx.waitingForSubAgent) { + return; + } + if (ctx.dropToolEvents) { + return; + } + console.log('[DEBUG] 收到 tool_preparing 事件:', { + name: data.name, + id: data.id, + conversation_id: data?.conversation_id, + currentConversationId: ctx.currentConversationId, + match: data?.conversation_id === ctx.currentConversationId + }); + 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) { + console.log('[DEBUG] 跳过 tool_preparing (对话不匹配)'); + socketLog('跳过tool_preparing(对话不匹配)', data.conversation_id); + return; + } + const msg = ctx.chatEnsureAssistantMessage(); + if (!msg) { + console.log('[DEBUG] tool_preparing: 无法获取assistant消息'); + return; + } + if (msg.awaitingFirstContent) { + msg.awaitingFirstContent = false; + msg.generatingLabel = ''; + } + 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); + } + console.log('[DEBUG] tool_preparing 处理完成,action已添加'); + ctx.$forceUpdate(); + ctx.conditionalScrollToBottom(); + if (ctx.monitorPreviewTool) { + ctx.monitorPreviewTool(data); + } + }); + + // 工具意图(流式增量)事件 + ctx.socket.on('tool_intent', (data) => { + // 轮询模式下跳过 WebSocket 事件 + if (ctx.usePollingMode && !ctx.waitingForSubAgent) { + return; + } + 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) => { + // 轮询模式下跳过 WebSocket 事件 + if (ctx.usePollingMode && !ctx.waitingForSubAgent) { + return; + } + 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) => { + // 轮询模式下跳过 WebSocket 事件 + if (ctx.usePollingMode && !ctx.waitingForSubAgent) { + return; + } + 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); + + if (data.name === 'view_video' && typeof ctx.uiPushToast === 'function') { + ctx.uiPushToast({ + title: '视频读取中', + message: '读取视频需要较长时间,请耐心等待', + type: 'info', + duration: 5000 + }); + } + }; + + handler(); + }); + + // 更新action(工具完成) + ctx.socket.on('update_action', (data) => { + // 轮询模式下跳过 WebSocket 事件 + if (ctx.usePollingMode && !ctx.waitingForSubAgent) { + return; + } + 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) => { + console.log('[DEBUG] 收到 task_complete 事件:', data); + console.log('[DEBUG] 当前状态 (before task_complete):', { + taskInProgress: ctx.taskInProgress, + streamingMessage: ctx.streamingMessage, + has_running_sub_agents: data.has_running_sub_agents + }); + socketLog('任务完成', data); + + // 如果有运行中的子智能体,不重置任务状态 + if (!data.has_running_sub_agents) { + console.log('[DEBUG] 没有运行中的子智能体,重置任务状态'); + if (ctx.waitingForSubAgent) { + ctx.waitingForSubAgent = false; + } + ctx.taskInProgress = false; + ctx.scheduleResetAfterTask('socket:task_complete', { preserveMonitorWindows: true }); + } else { + console.log('[DEBUG] 有运行中的子智能体,保持任务状态'); + } + + console.log('[DEBUG] 当前状态 (after task_complete):', { + taskInProgress: ctx.taskInProgress, + streamingMessage: ctx.streamingMessage + }); + + resetPendingToolEvents(); + + // 任务完成后立即更新Token统计(关键修复) + if (ctx.currentConversationId) { + ctx.updateCurrentContextTokens(); + ctx.fetchConversationTokenStatistics(); + } + }); + + // 子智能体等待状态 + ctx.socket.on('sub_agent_waiting', (data) => { + console.log('[DEBUG] 收到 sub_agent_waiting 事件:', data); + console.log('[DEBUG] 当前状态 (before sub_agent_waiting):', { + taskInProgress: ctx.taskInProgress, + streamingMessage: ctx.streamingMessage, + stopRequested: ctx.stopRequested + }); + socketLog('等待子智能体完成:', data); + + // 设置标志:有子智能体在运行,阻止状态重置 + ctx.waitingForSubAgent = true; + + // 保持任务进行中状态 + ctx.taskInProgress = true; + ctx.streamingMessage = false; + ctx.stopRequested = false; + + console.log('[DEBUG] 当前状态 (after sub_agent_waiting):', { + taskInProgress: ctx.taskInProgress, + streamingMessage: ctx.streamingMessage, + stopRequested: ctx.stopRequested, + waitingForSubAgent: ctx.waitingForSubAgent + }); + + // 显示等待提示 + if (typeof ctx.appendSystemAction === 'function') { + const taskList = data.tasks.map((t: any) => `子智能体${t.agent_id} (${t.summary || '无描述'})`).join('、'); + ctx.appendSystemAction(`⏳ 等待 ${data.count} 个后台子智能体完成:${taskList}`); + } + + ctx.$forceUpdate(); + console.log('[DEBUG] sub_agent_waiting 处理完成'); + }); + + // 聚焦文件更新 + 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) => { + const msg = data?.message || '发生未知错误'; + const code = data?.status_code; + const errType = data?.error_type; + const errCode = data?.error_code; + const dumpPath = data?.request_dump; + const baseUrl = data?.base_url; + const modelId = data?.model_id; + const conversationId = data?.conversation_id; + const taskId = data?.task_id; + const shouldRetry = Boolean(data?.retry); + const retryIn = Number(data?.retry_in) || 5; + const retryAttempt = Number(data?.attempt) || 1; + const retryMax = Number(data?.max_attempts) || retryAttempt; + const detailParts = [ + dumpPath ? `请求记录: ${dumpPath}` : '', + baseUrl ? `接口: ${baseUrl}` : '', + modelId ? `模型: ${modelId}` : '', + conversationId ? `对话ID: ${conversationId}` : '', + taskId ? `任务ID: ${taskId}` : '' + ].filter(Boolean); + const detailText = detailParts.length ? `\n${detailParts.join('\n')}` : ''; + if (typeof ctx.uiPushToast === 'function') { + ctx.uiPushToast({ + title: code ? `API错误 ${code}` : 'API错误', + message: `${errType ? `${errType}${errCode ? `(${errCode})` : ''}: ${msg}` : msg}${detailText}`, + type: 'error', + duration: 6000 + }); + if (shouldRetry) { + ctx.uiPushToast({ + title: '即将重试', + message: `将在 ${retryIn} 秒后重试(第 ${retryAttempt}/${retryMax} 次)`, + type: 'info', + duration: Math.max(retryIn, 1) * 1000 + }); + } + } + if (shouldRetry) { + // 错误后保持停止按钮态,用户可手动停止或等待自动重试 + ctx.stopRequested = false; + ctx.taskInProgress = true; + ctx.streamingMessage = true; + return; + } + + if (typeof ctx.appendSystemAction === 'function') { + ctx.appendSystemAction( + `${code ? `[API ${code}] ` : '[API] '}${errType ? `${errType}${errCode ? `(${errCode})` : ''}: ` : ''}${msg}${detailText}` + ); + } + + // 最后一次报错:恢复输入状态并清理提示动画 + const msgIndex = typeof ctx.currentMessageIndex === 'number' ? ctx.currentMessageIndex : -1; + if (msgIndex >= 0 && Array.isArray(ctx.messages)) { + const currentMessage = ctx.messages[msgIndex]; + if (currentMessage && currentMessage.role === 'assistant') { + currentMessage.awaitingFirstContent = false; + currentMessage.generatingLabel = ''; + } + } + if (typeof ctx.chatClearThinkingLocks === 'function') { + ctx.chatClearThinkingLocks(); + } + 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); + } +}