diff --git a/src/renderer/components/chat-area.ts b/src/renderer/components/chat-area.ts index 3ae74ef..351e4fa 100644 --- a/src/renderer/components/chat-area.ts +++ b/src/renderer/components/chat-area.ts @@ -159,8 +159,8 @@ export function appendMessageDOM(msg: ChatMessage, index: number): void { const stats: string[] = []; if (msg.timestamp) stats.push(formatTime(msg.timestamp)); - if (msg.eval_count) stats.push(`${msg.eval_count} tokens`); - if (msg.total_duration) stats.push(`${(msg.total_duration / 1e9).toFixed(1)}s`); + if (msg.eval_count != null && msg.eval_count > 0) stats.push(`${msg.eval_count} tokens`); + if (msg.total_duration != null && msg.total_duration > 0) stats.push(`${(msg.total_duration / 1e9).toFixed(1)}s`); if (stats.length > 0) { contentHtml += `
${stats.map(s => `${s}`).join(' · ')}
`; } @@ -337,8 +337,8 @@ export function updateLastAssistantMessage(content: string, think: string | null lastMsg.querySelector('.msg-body')!.appendChild(statsDiv); } const parts: string[] = []; - if (stats.eval_count) parts.push(`${stats.eval_count} tokens`); - if (stats.total_duration) parts.push(`${(stats.total_duration / 1e9).toFixed(1)}s`); + if (stats.eval_count != null && stats.eval_count > 0) parts.push(`${stats.eval_count} tokens`); + if (stats.total_duration != null && stats.total_duration > 0) parts.push(`${(stats.total_duration / 1e9).toFixed(1)}s`); if (parts.length) statsDiv.innerHTML = parts.join(' · '); } diff --git a/src/renderer/components/input-area.ts b/src/renderer/components/input-area.ts index 0cc303f..81b1bac 100644 --- a/src/renderer/components/input-area.ts +++ b/src/renderer/components/input-area.ts @@ -463,9 +463,20 @@ export async function sendMessage(): Promise { if (think && typeof think === 'string') thinkContent += think; } if (chunk.model) modelName = chunk.model; + // 捕获任何包含统计的 chunk(不限于 done) + if (chunk.eval_count || chunk.total_duration) { + finalStats = { + eval_count: chunk.eval_count ?? finalStats?.eval_count, + total_duration: chunk.total_duration ?? finalStats?.total_duration, + }; + } updateLastAssistantMessage(assistantContent, thinkContent || null, null, modelName || undefined); if (chunk.done) { - finalStats = { eval_count: chunk.eval_count, total_duration: chunk.total_duration }; + // done chunk 优先使用其统计,保留之前的兜底 + finalStats = { + eval_count: chunk.eval_count ?? finalStats?.eval_count, + total_duration: chunk.total_duration ?? finalStats?.total_duration, + }; } }, abortController); @@ -706,7 +717,7 @@ async function sendMessageWithAgentLoop(text: string, currentSession: ChatSessio onConfirmTool: async (call) => { return showToolConfirm(call); }, - onDone: async (finalContent, toolRecords) => { + onDone: async (finalContent, toolRecords, loopStats) => { assistantContent = finalContent; const assistantMsg: ChatMessage = { @@ -714,7 +725,9 @@ async function sendMessageWithAgentLoop(text: string, currentSession: ChatSessio content: assistantContent || '', timestamp: Date.now(), ...(thinkContent && { think: thinkContent }), - ...(toolRecords?.length && { toolCalls: toolRecords }) + ...(toolRecords?.length && { toolCalls: toolRecords }), + ...(loopStats?.eval_count && { eval_count: loopStats.eval_count }), + ...(loopStats?.total_duration && { total_duration: loopStats.total_duration }), }; state.update(KEYS.CURRENT_SESSION, (session: any) => ({ ...session, @@ -722,7 +735,7 @@ async function sendMessageWithAgentLoop(text: string, currentSession: ChatSessio updatedAt: Date.now() })); - updateLastAssistantMessage(assistantContent, thinkContent || null, null); + updateLastAssistantMessage(assistantContent, thinkContent || null, loopStats || null); renderMessages(); await saveCurrentSession(); } diff --git a/src/renderer/services/agent-engine.ts b/src/renderer/services/agent-engine.ts index aecb1c6..d0955b0 100644 --- a/src/renderer/services/agent-engine.ts +++ b/src/renderer/services/agent-engine.ts @@ -32,7 +32,7 @@ export interface AgentCallbacks { onToolCallStart: (call: ToolCall) => void; onToolCallResult: (name: string, result: ToolResult, call: ToolCall) => void; onToolCallError: (name: string, error: string, call: ToolCall) => void; - onDone: (finalContent: string, toolRecords?: ToolCallRecord[]) => void; + onDone: (finalContent: string, toolRecords?: ToolCallRecord[], stats?: { eval_count?: number; total_duration?: number }) => void; onConfirmTool: (call: ToolCall) => Promise; } @@ -101,6 +101,12 @@ export async function runAgentLoop( const allToolRecords: ToolCallRecord[] = []; const loopStartTime = Date.now(); let content = ''; + let totalEvalCount = 0; + + const makeStats = () => { + const totalDuration = (Date.now() - loopStartTime) * 1e6; // 转为纳秒 + return { eval_count: totalEvalCount || undefined, total_duration: totalDuration }; + }; while (loopCount < MAX_LOOPS) { loopCount++; @@ -108,14 +114,14 @@ export async function runAgentLoop( // 全局超时检查 if (Date.now() - loopStartTime > MAX_LOOP_TIME) { logWarn('Agent Loop 达到最大运行时间(5分钟),自动停止'); - callbacks.onDone(content || '(达到最大运行时间限制)', allToolRecords); + callbacks.onDone(content || '(达到最大运行时间限制)', allToolRecords, makeStats()); return; } // 检查是否已中止 if (state.get(KEYS.ABORT_CONTROLLER)?.signal.aborted) { logInfo('Agent Loop 已中止'); - callbacks.onDone(content, allToolRecords.length > 0 ? allToolRecords : undefined); + callbacks.onDone(content, allToolRecords.length > 0 ? allToolRecords : undefined, makeStats()); return; } @@ -156,6 +162,7 @@ export async function runAgentLoop( content += chunk.message.content; callbacks.onContent(content); } + if (chunk.eval_count) totalEvalCount = chunk.eval_count; if (chunk.message?.tool_calls?.length) { for (const tc of chunk.message.tool_calls) { if (tc.function?.name) { @@ -193,7 +200,7 @@ export async function runAgentLoop( ...(thinking && { thinking }) }); } - callbacks.onDone(content, allToolRecords.length > 0 ? allToolRecords : undefined); + callbacks.onDone(content, allToolRecords.length > 0 ? allToolRecords : undefined, makeStats()); return; } // 超时错误 — 保留已生成的内容 @@ -206,7 +213,7 @@ export async function runAgentLoop( ...(thinking && { thinking }) }); } - callbacks.onDone(content || '(模型响应超时,已自动中断)', allToolRecords.length > 0 ? allToolRecords : undefined); + callbacks.onDone(content || '(模型响应超时,已自动中断)', allToolRecords.length > 0 ? allToolRecords : undefined, makeStats()); return; } throw err; @@ -228,14 +235,14 @@ export async function runAgentLoop( if (toolCalls.length === 0) { logInfo('无工具调用,Agent Loop 结束'); - callbacks.onDone(content, allToolRecords.length > 0 ? allToolRecords : undefined); + callbacks.onDone(content, allToolRecords.length > 0 ? allToolRecords : undefined, makeStats()); return; } for (const call of toolCalls) { // 检查是否已中止 if (abortController.signal.aborted) { - callbacks.onDone(content, allToolRecords.length > 0 ? allToolRecords : undefined); + callbacks.onDone(content, allToolRecords.length > 0 ? allToolRecords : undefined, makeStats()); return; } @@ -246,7 +253,7 @@ export async function runAgentLoop( const confirmed = await callbacks.onConfirmTool(call); // 确认后再次检查是否已中止 if (abortController.signal.aborted) { - callbacks.onDone(content, allToolRecords.length > 0 ? allToolRecords : undefined); + callbacks.onDone(content, allToolRecords.length > 0 ? allToolRecords : undefined, makeStats()); return; } if (!confirmed) { @@ -320,5 +327,5 @@ export async function runAgentLoop( } logWarn('Agent Loop 达到最大工具调用次数限制'); - callbacks.onDone(content || '(达到最大工具调用次数限制)', allToolRecords); + callbacks.onDone(content || '(达到最大工具调用次数限制)', allToolRecords, makeStats()); }