agent-Specialization/static/src/composables/useLegacySocket.ts

1331 lines
53 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// @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<any> = [];
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);
}
}