From 06ab2822be98258d2df6dbf6e2d37c179505024c Mon Sep 17 00:00:00 2001 From: MyPrototypeWhat Date: Sun, 28 Sep 2025 19:38:44 +0800 Subject: [PATCH] Refactor/reasoning time (#10393) --- .../src/aiCore/chunk/AiSdkToChunkAdapter.ts | 5 +- .../src/aiCore/plugins/PluginBuilder.ts | 7 ++- .../home/Messages/Blocks/ThinkingBlock.tsx | 51 +++++++++++-------- .../Blocks/__tests__/ThinkingBlock.test.tsx | 3 +- .../callbacks/thinkingCallbacks.ts | 19 +++---- .../streamCallback.integration.test.ts | 5 +- 6 files changed, 49 insertions(+), 41 deletions(-) diff --git a/src/renderer/src/aiCore/chunk/AiSdkToChunkAdapter.ts b/src/renderer/src/aiCore/chunk/AiSdkToChunkAdapter.ts index 2e8ce32969..fb68cedb23 100644 --- a/src/renderer/src/aiCore/chunk/AiSdkToChunkAdapter.ts +++ b/src/renderer/src/aiCore/chunk/AiSdkToChunkAdapter.ts @@ -163,14 +163,13 @@ export class AiSdkToChunkAdapter { final.reasoningContent += chunk.text || '' this.onChunk({ type: ChunkType.THINKING_DELTA, - text: final.reasoningContent || '', - thinking_millsec: (chunk.providerMetadata?.metadata?.thinking_millsec as number) || 0 + text: final.reasoningContent || '' }) break case 'reasoning-end': this.onChunk({ type: ChunkType.THINKING_COMPLETE, - text: (chunk.providerMetadata?.metadata?.thinking_content as string) || final.reasoningContent + text: final.reasoningContent || '' }) final.reasoningContent = '' break diff --git a/src/renderer/src/aiCore/plugins/PluginBuilder.ts b/src/renderer/src/aiCore/plugins/PluginBuilder.ts index 7c5478eb77..7767564bd9 100644 --- a/src/renderer/src/aiCore/plugins/PluginBuilder.ts +++ b/src/renderer/src/aiCore/plugins/PluginBuilder.ts @@ -5,7 +5,6 @@ import { getEnableDeveloperMode } from '@renderer/hooks/useSettings' import { Assistant } from '@renderer/types' import { AiSdkMiddlewareConfig } from '../middleware/AiSdkMiddlewareBuilder' -import reasoningTimePlugin from './reasoningTimePlugin' import { searchOrchestrationPlugin } from './searchOrchestrationPlugin' import { createTelemetryPlugin } from './telemetryPlugin' @@ -39,9 +38,9 @@ export function buildPlugins( } // 3. 推理模型时添加推理插件 - if (middlewareConfig.enableReasoning) { - plugins.push(reasoningTimePlugin) - } + // if (middlewareConfig.enableReasoning) { + // plugins.push(reasoningTimePlugin) + // } // 4. 启用Prompt工具调用时添加工具插件 if (middlewareConfig.isPromptToolUse) { diff --git a/src/renderer/src/pages/home/Messages/Blocks/ThinkingBlock.tsx b/src/renderer/src/pages/home/Messages/Blocks/ThinkingBlock.tsx index 98cad2a8ca..109562f7d5 100644 --- a/src/renderer/src/pages/home/Messages/Blocks/ThinkingBlock.tsx +++ b/src/renderer/src/pages/home/Messages/Blocks/ThinkingBlock.tsx @@ -5,7 +5,7 @@ import { useSettings } from '@renderer/hooks/useSettings' import { useTemporaryValue } from '@renderer/hooks/useTemporaryValue' import { MessageBlockStatus, type ThinkingMessageBlock } from '@renderer/types/newMessage' import { Collapse, message as antdMessage, Tooltip } from 'antd' -import { memo, useCallback, useEffect, useMemo, useState } from 'react' +import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' @@ -105,30 +105,37 @@ const ThinkingBlock: React.FC = ({ block }) => { const ThinkingTimeSeconds = memo( ({ blockThinkingTime, isThinking }: { blockThinkingTime: number; isThinking: boolean }) => { const { t } = useTranslation() - // const [thinkingTime, setThinkingTime] = useState(blockThinkingTime || 0) + const [displayTime, setDisplayTime] = useState(blockThinkingTime) - // FIXME: 这里统计的和请求处统计的有一定误差 - // useEffect(() => { - // let timer: NodeJS.Timeout | null = null - // if (isThinking) { - // timer = setInterval(() => { - // setThinkingTime((prev) => prev + 100) - // }, 100) - // } else if (timer) { - // // 立即清除计时器 - // clearInterval(timer) - // timer = null - // } + const timer = useRef(null) - // return () => { - // if (timer) { - // clearInterval(timer) - // timer = null - // } - // } - // }, [isThinking]) + useEffect(() => { + if (isThinking) { + if (!timer.current) { + timer.current = setInterval(() => { + setDisplayTime((prev) => prev + 100) + }, 100) + } + } else { + if (timer.current) { + clearInterval(timer.current) + timer.current = null + } + setDisplayTime(blockThinkingTime) + } - const thinkingTimeSeconds = useMemo(() => (blockThinkingTime / 1000).toFixed(1), [blockThinkingTime]) + return () => { + if (timer.current) { + clearInterval(timer.current) + timer.current = null + } + } + }, [isThinking, blockThinkingTime]) + + const thinkingTimeSeconds = useMemo( + () => ((displayTime < 1000 ? 100 : displayTime) / 1000).toFixed(1), + [displayTime] + ) return isThinking ? t('chat.thinking', { diff --git a/src/renderer/src/pages/home/Messages/Blocks/__tests__/ThinkingBlock.test.tsx b/src/renderer/src/pages/home/Messages/Blocks/__tests__/ThinkingBlock.test.tsx index 8db122d948..d573408225 100644 --- a/src/renderer/src/pages/home/Messages/Blocks/__tests__/ThinkingBlock.test.tsx +++ b/src/renderer/src/pages/home/Messages/Blocks/__tests__/ThinkingBlock.test.tsx @@ -235,13 +235,12 @@ describe('ThinkingBlock', () => { renderThinkingBlock(thinkingBlock) const activeTimeText = getThinkingTimeText() - expect(activeTimeText).toHaveTextContent('1.0s') expect(activeTimeText).toHaveTextContent('Thinking...') }) it('should handle extreme thinking times correctly', () => { const testCases = [ - { thinking_millsec: 0, expectedTime: '0.0s' }, + { thinking_millsec: 0, expectedTime: '0.1s' }, // New logic: values < 1000ms display as 0.1s { thinking_millsec: 86400000, expectedTime: '86400.0s' }, // 1 day { thinking_millsec: 259200000, expectedTime: '259200.0s' } // 3 days ] diff --git a/src/renderer/src/services/messageStreaming/callbacks/thinkingCallbacks.ts b/src/renderer/src/services/messageStreaming/callbacks/thinkingCallbacks.ts index 4d717c6c64..605259b646 100644 --- a/src/renderer/src/services/messageStreaming/callbacks/thinkingCallbacks.ts +++ b/src/renderer/src/services/messageStreaming/callbacks/thinkingCallbacks.ts @@ -15,7 +15,7 @@ export const createThinkingCallbacks = (deps: ThinkingCallbacksDependencies) => // 内部维护的状态 let thinkingBlockId: string | null = null - let _thinking_millsec = 0 + let thinking_millsec_now: number = 0 return { onThinkingStart: async () => { @@ -24,27 +24,27 @@ export const createThinkingCallbacks = (deps: ThinkingCallbacksDependencies) => type: MessageBlockType.THINKING, content: '', status: MessageBlockStatus.STREAMING, - thinking_millsec: _thinking_millsec + thinking_millsec: 0 } thinkingBlockId = blockManager.initialPlaceholderBlockId! blockManager.smartBlockUpdate(thinkingBlockId, changes, MessageBlockType.THINKING, true) } else if (!thinkingBlockId) { const newBlock = createThinkingBlock(assistantMsgId, '', { status: MessageBlockStatus.STREAMING, - thinking_millsec: _thinking_millsec + thinking_millsec: 0 }) thinkingBlockId = newBlock.id await blockManager.handleBlockTransition(newBlock, MessageBlockType.THINKING) } + thinking_millsec_now = performance.now() }, - onThinkingChunk: async (text: string, thinking_millsec?: number) => { - _thinking_millsec = thinking_millsec || 0 + onThinkingChunk: async (text: string) => { if (thinkingBlockId) { const blockChanges: Partial = { content: text, - status: MessageBlockStatus.STREAMING, - thinking_millsec: _thinking_millsec + status: MessageBlockStatus.STREAMING + // thinking_millsec: performance.now() - thinking_millsec_now } blockManager.smartBlockUpdate(thinkingBlockId, blockChanges, MessageBlockType.THINKING) } @@ -52,14 +52,15 @@ export const createThinkingCallbacks = (deps: ThinkingCallbacksDependencies) => onThinkingComplete: (finalText: string) => { if (thinkingBlockId) { + const now = performance.now() const changes: Partial = { content: finalText, status: MessageBlockStatus.SUCCESS, - thinking_millsec: _thinking_millsec + thinking_millsec: now - thinking_millsec_now } blockManager.smartBlockUpdate(thinkingBlockId, changes, MessageBlockType.THINKING, true) thinkingBlockId = null - _thinking_millsec = 0 + thinking_millsec_now = 0 } else { logger.warn( `[onThinkingComplete] Received thinking.complete but last block was not THINKING (was ${blockManager.lastBlockType}) or lastBlockId is null.` diff --git a/src/renderer/src/store/thunk/__tests__/streamCallback.integration.test.ts b/src/renderer/src/store/thunk/__tests__/streamCallback.integration.test.ts index e8c113d62b..49c71aea56 100644 --- a/src/renderer/src/store/thunk/__tests__/streamCallback.integration.test.ts +++ b/src/renderer/src/store/thunk/__tests__/streamCallback.integration.test.ts @@ -425,7 +425,10 @@ describe('streamCallback Integration Tests', () => { expect(thinkingBlock).toBeDefined() expect(thinkingBlock?.content).toBe('Final thoughts') expect(thinkingBlock?.status).toBe(MessageBlockStatus.SUCCESS) - expect((thinkingBlock as any)?.thinking_millsec).toBe(3000) + // thinking_millsec 现在是本地计算的,只验证它存在且是一个合理的数字 + expect((thinkingBlock as any)?.thinking_millsec).toBeDefined() + expect(typeof (thinkingBlock as any)?.thinking_millsec).toBe('number') + expect((thinkingBlock as any)?.thinking_millsec).toBeGreaterThanOrEqual(0) }) it('should handle tool call flow', async () => {