mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-05 12:29:44 +08:00
fix: thinking time on stop (#11900)
* fix: preserve thinking time when stopping reply Fixes #11886 Signed-off-by: Calvin <calvinvwei@gmail.com> * fix: also preserve thinking time when stopping during thinking This extends the previous fix to also handle the case when the user stops the reply while thinking is still in progress (not just after thinking is complete). Signed-off-by: Calvin <calvinvwei@gmail.com> * fix: auto-complete thinking when text output starts This fixes the issue where the thinking timer continues running after thinking is complete and text output begins. Some AI providers don't send a reasoning-end event explicitly, so we now auto-complete thinking when a text-start event is received with accumulated reasoning content. Fixes #11796 Signed-off-by: Calvin <calvinvwei@gmail.com> * refactor: extract emitThinkingCompleteIfNeeded to reduce duplication Extract the shared logic for emitting THINKING_COMPLETE chunk into a reusable method. This removes code duplication between text-start and reasoning-end event handlers as suggested in code review. Signed-off-by: Calvin <calvinvwei@gmail.com> --------- Signed-off-by: Calvin <calvinvwei@gmail.com>
This commit is contained in:
parent
d27d750bc5
commit
b4aeced1f9
@ -120,6 +120,21 @@ export class AiSdkToChunkAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果有累积的思考内容,发送 THINKING_COMPLETE chunk 并清空
|
||||
* @param final 包含 reasoningContent 的状态对象
|
||||
* @returns 是否发送了 THINKING_COMPLETE chunk
|
||||
*/
|
||||
private emitThinkingCompleteIfNeeded(final: { reasoningContent: string; [key: string]: any }) {
|
||||
if (final.reasoningContent) {
|
||||
this.onChunk({
|
||||
type: ChunkType.THINKING_COMPLETE,
|
||||
text: final.reasoningContent
|
||||
})
|
||||
final.reasoningContent = ''
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换 AI SDK chunk 为 Cherry Studio chunk 并调用回调
|
||||
* @param chunk AI SDK 的 chunk 数据
|
||||
@ -145,6 +160,9 @@ export class AiSdkToChunkAdapter {
|
||||
}
|
||||
// === 文本相关事件 ===
|
||||
case 'text-start':
|
||||
// 如果有未完成的思考内容,先生成 THINKING_COMPLETE
|
||||
// 这处理了某些提供商不发送 reasoning-end 事件的情况
|
||||
this.emitThinkingCompleteIfNeeded(final)
|
||||
this.onChunk({
|
||||
type: ChunkType.TEXT_START
|
||||
})
|
||||
@ -215,11 +233,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 事件,如果没有被中间件处理) ===
|
||||
|
||||
@ -8,7 +8,7 @@ import { updateOneBlock } from '@renderer/store/messageBlock'
|
||||
import { selectMessagesForTopic } from '@renderer/store/newMessage'
|
||||
import { newMessagesActions } from '@renderer/store/newMessage'
|
||||
import type { Assistant } from '@renderer/types'
|
||||
import type { PlaceholderMessageBlock, Response } from '@renderer/types/newMessage'
|
||||
import type { PlaceholderMessageBlock, Response, ThinkingMessageBlock } from '@renderer/types/newMessage'
|
||||
import { AssistantMessageStatus, MessageBlockStatus, MessageBlockType } from '@renderer/types/newMessage'
|
||||
import { uuid } from '@renderer/utils'
|
||||
import { isAbortError, serializeError } from '@renderer/utils/error'
|
||||
@ -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: Partial<ThinkingMessageBlock> = {
|
||||
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: Partial<ThinkingMessageBlock> = {
|
||||
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
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
@ -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({
|
||||
|
||||
@ -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<MessageBlock> = {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user