diff --git a/src/renderer/src/aiCore/chunk/AiSdkToChunkAdapter.ts b/src/renderer/src/aiCore/chunk/AiSdkToChunkAdapter.ts index 5de2ac345..a53ac3c81 100644 --- a/src/renderer/src/aiCore/chunk/AiSdkToChunkAdapter.ts +++ b/src/renderer/src/aiCore/chunk/AiSdkToChunkAdapter.ts @@ -120,6 +120,23 @@ export class AiSdkToChunkAdapter { } } + /** + * 如果有累积的思考内容,发送 THINKING_COMPLETE chunk 并清空 + * @param final 包含 reasoningContent 的状态对象 + * @returns 是否发送了 THINKING_COMPLETE chunk + */ + private emitThinkingCompleteIfNeeded(final: { reasoningContent: string; [key: string]: any }): boolean { + if (final.reasoningContent) { + this.onChunk({ + type: ChunkType.THINKING_COMPLETE, + text: final.reasoningContent + }) + final.reasoningContent = '' + return true + } + return false + } + /** * 转换 AI SDK chunk 为 Cherry Studio chunk 并调用回调 * @param chunk AI SDK 的 chunk 数据 @@ -145,6 +162,9 @@ export class AiSdkToChunkAdapter { } // === 文本相关事件 === case 'text-start': + // 如果有未完成的思考内容,先生成 THINKING_COMPLETE + // 这处理了某些提供商不发送 reasoning-end 事件的情况 + this.emitThinkingCompleteIfNeeded(final) this.onChunk({ type: ChunkType.TEXT_START }) @@ -215,11 +235,7 @@ export class AiSdkToChunkAdapter { }) break case 'reasoning-end': - this.onChunk({ - type: ChunkType.THINKING_COMPLETE, - text: final.reasoningContent || '' - }) - final.reasoningContent = '' + this.emitThinkingCompleteIfNeeded(final) break // === 工具调用相关事件(原始 AI SDK 事件,如果没有被中间件处理) === diff --git a/src/renderer/src/services/messageStreaming/callbacks/baseCallbacks.ts b/src/renderer/src/services/messageStreaming/callbacks/baseCallbacks.ts index ed9bdd584..7000ad3d5 100644 --- a/src/renderer/src/services/messageStreaming/callbacks/baseCallbacks.ts +++ b/src/renderer/src/services/messageStreaming/callbacks/baseCallbacks.ts @@ -29,10 +29,20 @@ interface BaseCallbacksDependencies { assistantMsgId: string saveUpdatesToDB: any assistant: Assistant + getCurrentThinkingInfo?: () => { blockId: string | null; millsec: number } } export const createBaseCallbacks = (deps: BaseCallbacksDependencies) => { - const { blockManager, dispatch, getState, topicId, assistantMsgId, saveUpdatesToDB, assistant } = deps + const { + blockManager, + dispatch, + getState, + topicId, + assistantMsgId, + saveUpdatesToDB, + assistant, + getCurrentThinkingInfo + } = deps const startTime = Date.now() const notificationService = NotificationService.getInstance() @@ -98,10 +108,17 @@ export const createBaseCallbacks = (deps: BaseCallbacksDependencies) => { const possibleBlockId = findBlockIdForCompletion() if (possibleBlockId) { - // 更改上一个block的状态为ERROR - const changes = { + // 更改上一个block的状态为ERROR/PAUSED + const changes: Record = { status: isErrorTypeAbort ? MessageBlockStatus.PAUSED : MessageBlockStatus.ERROR } + // 如果是 thinking block,保留实际思考时间 + if (blockManager.lastBlockType === MessageBlockType.THINKING) { + const thinkingInfo = getCurrentThinkingInfo?.() + if (thinkingInfo?.blockId === possibleBlockId && thinkingInfo?.millsec && thinkingInfo.millsec > 0) { + changes.thinking_millsec = thinkingInfo.millsec + } + } blockManager.smartBlockUpdate(possibleBlockId, changes, blockManager.lastBlockType!, true) } @@ -111,13 +128,28 @@ export const createBaseCallbacks = (deps: BaseCallbacksDependencies) => { if (currentMessage) { const allBlockRefs = findAllBlocks(currentMessage) const blockState = getState().messageBlocks + // 获取当前思考信息(如果有),用于保留实际思考时间 + const thinkingInfo = getCurrentThinkingInfo?.() for (const blockRef of allBlockRefs) { const block = blockState.entities[blockRef.id] if (block && block.status === MessageBlockStatus.STREAMING && block.id !== possibleBlockId) { + // 构建更新对象 + const changes: Record = { + status: isErrorTypeAbort ? MessageBlockStatus.PAUSED : MessageBlockStatus.ERROR + } + // 如果是 thinking block 且有思考时间信息,保留实际思考时间 + if ( + block.type === MessageBlockType.THINKING && + thinkingInfo?.blockId === block.id && + thinkingInfo?.millsec && + thinkingInfo.millsec > 0 + ) { + changes.thinking_millsec = thinkingInfo.millsec + } dispatch( updateOneBlock({ id: block.id, - changes: { status: isErrorTypeAbort ? MessageBlockStatus.PAUSED : MessageBlockStatus.ERROR } + changes }) ) } diff --git a/src/renderer/src/services/messageStreaming/callbacks/index.ts b/src/renderer/src/services/messageStreaming/callbacks/index.ts index 2bb1d158b..1587cb936 100644 --- a/src/renderer/src/services/messageStreaming/callbacks/index.ts +++ b/src/renderer/src/services/messageStreaming/callbacks/index.ts @@ -23,6 +23,12 @@ interface CallbacksDependencies { export const createCallbacks = (deps: CallbacksDependencies) => { const { blockManager, dispatch, getState, topicId, assistantMsgId, saveUpdatesToDB, assistant } = deps + // 首先创建 thinkingCallbacks ,以便传递 getCurrentThinkingInfo 给 baseCallbacks + const thinkingCallbacks = createThinkingCallbacks({ + blockManager, + assistantMsgId + }) + // 创建基础回调 const baseCallbacks = createBaseCallbacks({ blockManager, @@ -31,13 +37,8 @@ export const createCallbacks = (deps: CallbacksDependencies) => { topicId, assistantMsgId, saveUpdatesToDB, - assistant - }) - - // 创建各类回调 - const thinkingCallbacks = createThinkingCallbacks({ - blockManager, - assistantMsgId + assistant, + getCurrentThinkingInfo: thinkingCallbacks.getCurrentThinkingInfo }) const toolCallbacks = createToolCallbacks({ diff --git a/src/renderer/src/services/messageStreaming/callbacks/thinkingCallbacks.ts b/src/renderer/src/services/messageStreaming/callbacks/thinkingCallbacks.ts index aeb160fd0..3c718c4a6 100644 --- a/src/renderer/src/services/messageStreaming/callbacks/thinkingCallbacks.ts +++ b/src/renderer/src/services/messageStreaming/callbacks/thinkingCallbacks.ts @@ -19,6 +19,12 @@ export const createThinkingCallbacks = (deps: ThinkingCallbacksDependencies) => let thinking_millsec_now: number = 0 return { + // 获取当前思考时间(用于停止回复时保留思考时间) + getCurrentThinkingInfo: () => ({ + blockId: thinkingBlockId, + millsec: thinking_millsec_now > 0 ? performance.now() - thinking_millsec_now : 0 + }), + onThinkingStart: async () => { if (blockManager.hasInitialPlaceholder) { const changes: Partial = {