1331 lines
53 KiB
TypeScript
1331 lines
53 KiB
TypeScript
// @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);
|
||
}
|
||
}
|