diff --git a/src/renderer/src/aiCore/clients/openai/OpenAIResponseAPIClient.ts b/src/renderer/src/aiCore/clients/openai/OpenAIResponseAPIClient.ts index 043ca65872..0705321396 100644 --- a/src/renderer/src/aiCore/clients/openai/OpenAIResponseAPIClient.ts +++ b/src/renderer/src/aiCore/clients/openai/OpenAIResponseAPIClient.ts @@ -367,16 +367,15 @@ export class OpenAIResponseAPIClient extends OpenAIBaseClient< (m) => (m as OpenAI.Responses.EasyInputMessage).role === 'assistant' ) as OpenAI.Responses.EasyInputMessage const finalUserMessage = userMessage.pop() as OpenAI.Responses.EasyInputMessage - if ( - finalAssistantMessage && - Array.isArray(finalAssistantMessage.content) && - finalUserMessage && - Array.isArray(finalUserMessage.content) - ) { - finalAssistantMessage.content = [...finalAssistantMessage.content, ...finalUserMessage.content] + if (finalUserMessage && Array.isArray(finalUserMessage.content)) { + if (finalAssistantMessage && Array.isArray(finalAssistantMessage.content)) { + finalAssistantMessage.content = [...finalAssistantMessage.content, ...finalUserMessage.content] + // 这里是故意将上条助手消息的内容(包含图片和文件)作为用户消息发送 + userMessage = [{ ...finalAssistantMessage, role: 'user' } as OpenAI.Responses.EasyInputMessage] + } else { + userMessage.push(finalUserMessage) + } } - // 这里是故意将上条助手消息的内容(包含图片和文件)作为用户消息发送 - userMessage = [{ ...finalAssistantMessage, role: 'user' } as OpenAI.Responses.EasyInputMessage] } // 4. 最终请求消息 diff --git a/src/renderer/src/aiCore/middleware/feat/ThinkingTagExtractionMiddleware.ts b/src/renderer/src/aiCore/middleware/feat/ThinkingTagExtractionMiddleware.ts index 2fcefc1b34..cf9bd918e1 100644 --- a/src/renderer/src/aiCore/middleware/feat/ThinkingTagExtractionMiddleware.ts +++ b/src/renderer/src/aiCore/middleware/feat/ThinkingTagExtractionMiddleware.ts @@ -1,5 +1,11 @@ import { Model } from '@renderer/types' -import { ChunkType, TextDeltaChunk, ThinkingCompleteChunk, ThinkingDeltaChunk } from '@renderer/types/chunk' +import { + ChunkType, + TextDeltaChunk, + ThinkingCompleteChunk, + ThinkingDeltaChunk, + ThinkingStartChunk +} from '@renderer/types/chunk' import { TagConfig, TagExtractor } from '@renderer/utils/tagExtraction' import Logger from 'electron-log/renderer' @@ -59,6 +65,8 @@ export const ThinkingTagExtractionMiddleware: CompletionsMiddleware = let hasThinkingContent = false let thinkingStartTime = 0 + let isFirstTextChunk = true + const processedStream = resultFromUpstream.pipeThrough( new TransformStream({ transform(chunk: GenericChunk, controller) { @@ -87,6 +95,9 @@ export const ThinkingTagExtractionMiddleware: CompletionsMiddleware = if (!hasThinkingContent) { hasThinkingContent = true thinkingStartTime = Date.now() + controller.enqueue({ + type: ChunkType.THINKING_START + } as ThinkingStartChunk) } if (extractionResult.content?.trim()) { @@ -98,6 +109,12 @@ export const ThinkingTagExtractionMiddleware: CompletionsMiddleware = controller.enqueue(thinkingDeltaChunk) } } else { + if (isFirstTextChunk) { + controller.enqueue({ + type: ChunkType.TEXT_START + }) + isFirstTextChunk = false + } // 发送清理后的文本内容 const cleanTextChunk: TextDeltaChunk = { ...textChunk, @@ -107,7 +124,7 @@ export const ThinkingTagExtractionMiddleware: CompletionsMiddleware = } } } - } else { + } else if (chunk.type !== ChunkType.TEXT_START) { // 其他类型的chunk直接传递(包括 THINKING_DELTA, THINKING_COMPLETE 等) controller.enqueue(chunk) } diff --git a/src/renderer/src/pages/home/Messages/Blocks/ImageBlock.tsx b/src/renderer/src/pages/home/Messages/Blocks/ImageBlock.tsx index eeafa559a9..ceb4bcceb8 100644 --- a/src/renderer/src/pages/home/Messages/Blocks/ImageBlock.tsx +++ b/src/renderer/src/pages/home/Messages/Blocks/ImageBlock.tsx @@ -9,9 +9,8 @@ interface Props { } const ImageBlock: React.FC = ({ block }) => { - if (block.status === MessageBlockStatus.STREAMING || block.status === MessageBlockStatus.PROCESSING) - return - if (block.status === MessageBlockStatus.SUCCESS) { + if (block.status === MessageBlockStatus.PENDING) return + if (block.status === MessageBlockStatus.STREAMING || block.status === MessageBlockStatus.SUCCESS) { const images = block.metadata?.generateImageResponse?.images?.length ? block.metadata?.generateImageResponse?.images : block?.file?.path diff --git a/src/renderer/src/store/thunk/messageThunk.ts b/src/renderer/src/store/thunk/messageThunk.ts index 63ca59fa43..1cd98aa319 100644 --- a/src/renderer/src/store/thunk/messageThunk.ts +++ b/src/renderer/src/store/thunk/messageThunk.ts @@ -344,14 +344,27 @@ const fetchAndProcessAssistantResponseImpl = async ( * 智能更新策略:根据块类型连续性自动判断使用节流还是立即更新 * - 连续同类块:使用节流(减少重渲染) * - 块类型切换:立即更新(确保状态正确) + * @param blockId 块ID + * @param changes 块更新内容 + * @param blockType 块类型 + * @param isComplete 是否完成,如果完成,则需要保存块更新到redux中 */ - const smartBlockUpdate = (blockId: string, changes: Partial, blockType: MessageBlockType) => { + const smartBlockUpdate = ( + blockId: string, + changes: Partial, + blockType: MessageBlockType, + isComplete: boolean = false + ) => { const isBlockTypeChanged = currentActiveBlockType !== null && currentActiveBlockType !== blockType - - if (isBlockTypeChanged) { - if (lastBlockId && lastBlockId !== blockId) { + if (isBlockTypeChanged || isComplete) { + // 如果块类型改变,则取消上一个块的节流更新,并保存块更新到redux中(尽管有可能被上一个块本身的oncomplete事件的取消节流已经取消了) + if (isBlockTypeChanged && lastBlockId) { cancelThrottledBlockUpdate(lastBlockId) } + // 如果当前块完成,则取消当前块的节流更新,并保存块更新到redux中,避免streaming状态覆盖掉完成状态 + if (isComplete) { + cancelThrottledBlockUpdate(blockId) + } dispatch(updateOneBlock({ id: blockId, changes })) saveUpdatedBlockToDB(blockId, assistantMsgId, topicId, getState) } else { @@ -464,7 +477,7 @@ const fetchAndProcessAssistantResponseImpl = async ( content: finalText, status: MessageBlockStatus.SUCCESS } - smartBlockUpdate(mainTextBlockId, changes, MessageBlockType.MAIN_TEXT) + smartBlockUpdate(mainTextBlockId, changes, MessageBlockType.MAIN_TEXT, true) mainTextBlockId = null } else { console.warn( @@ -512,7 +525,7 @@ const fetchAndProcessAssistantResponseImpl = async ( status: MessageBlockStatus.SUCCESS, thinking_millsec: final_thinking_millsec } - smartBlockUpdate(thinkingBlockId, changes, MessageBlockType.THINKING) + smartBlockUpdate(thinkingBlockId, changes, MessageBlockType.THINKING, true) } else { console.warn( `[onThinkingComplete] Received thinking.complete but last block was not THINKING (was ${lastBlockType}) or lastBlockId is null.` @@ -591,7 +604,7 @@ const fetchAndProcessAssistantResponseImpl = async ( if (finalStatus === MessageBlockStatus.ERROR) { changes.error = { message: `Tool execution failed/error`, details: toolResponse.response } } - smartBlockUpdate(existingBlockId, changes, MessageBlockType.TOOL) + smartBlockUpdate(existingBlockId, changes, MessageBlockType.TOOL, true) } else { console.warn( `[onToolCallComplete] Received unhandled tool status: ${toolResponse.status} for ID: ${toolResponse.id}` @@ -612,7 +625,7 @@ const fetchAndProcessAssistantResponseImpl = async ( knowledge: externalToolResult.knowledge, status: MessageBlockStatus.SUCCESS } - smartBlockUpdate(citationBlockId, changes, MessageBlockType.CITATION) + smartBlockUpdate(citationBlockId, changes, MessageBlockType.CITATION, true) } else { console.error('[onExternalToolComplete] citationBlockId is null. Cannot update.') } @@ -652,7 +665,7 @@ const fetchAndProcessAssistantResponseImpl = async ( const mainTextChanges = { citationReferences: [...currentRefs, { blockId, citationBlockSource: llmWebSearchResult.source }] } - smartBlockUpdate(existingMainTextBlock.id, mainTextChanges, MessageBlockType.MAIN_TEXT) + smartBlockUpdate(existingMainTextBlock.id, mainTextChanges, MessageBlockType.MAIN_TEXT, true) } if (initialPlaceholderBlockId) { @@ -678,7 +691,7 @@ const fetchAndProcessAssistantResponseImpl = async ( const mainTextChanges = { citationReferences: [...currentRefs, { citationBlockId, citationBlockSource: llmWebSearchResult.source }] } - smartBlockUpdate(existingMainTextBlock.id, mainTextChanges, MessageBlockType.MAIN_TEXT) + smartBlockUpdate(existingMainTextBlock.id, mainTextChanges, MessageBlockType.MAIN_TEXT, true) } await handleBlockTransition(citationBlock, MessageBlockType.CITATION) } @@ -688,7 +701,7 @@ const fetchAndProcessAssistantResponseImpl = async ( lastBlockType = MessageBlockType.IMAGE const initialChanges: Partial = { type: MessageBlockType.IMAGE, - status: MessageBlockStatus.STREAMING + status: MessageBlockStatus.PENDING } lastBlockType = MessageBlockType.IMAGE imageBlockId = initialPlaceholderBlockId @@ -696,7 +709,7 @@ const fetchAndProcessAssistantResponseImpl = async ( smartBlockUpdate(imageBlockId, initialChanges, MessageBlockType.IMAGE) } else if (!imageBlockId) { const imageBlock = createImageBlock(assistantMsgId, { - status: MessageBlockStatus.STREAMING + status: MessageBlockStatus.PENDING }) imageBlockId = imageBlock.id await handleBlockTransition(imageBlock, MessageBlockType.IMAGE) @@ -710,7 +723,7 @@ const fetchAndProcessAssistantResponseImpl = async ( metadata: { generateImageResponse: imageData }, status: MessageBlockStatus.STREAMING } - smartBlockUpdate(imageBlockId, changes, MessageBlockType.IMAGE) + smartBlockUpdate(imageBlockId, changes, MessageBlockType.IMAGE, true) } }, onImageGenerated: (imageData) => { @@ -727,7 +740,7 @@ const fetchAndProcessAssistantResponseImpl = async ( metadata: { generateImageResponse: imageData }, status: MessageBlockStatus.SUCCESS } - smartBlockUpdate(imageBlockId, changes, MessageBlockType.IMAGE) + smartBlockUpdate(imageBlockId, changes, MessageBlockType.IMAGE, true) } } else { console.error('[onImageGenerated] Last block was not an Image block or ID is missing.') @@ -775,7 +788,7 @@ const fetchAndProcessAssistantResponseImpl = async ( const changes: Partial = { status: isErrorTypeAbort ? MessageBlockStatus.PAUSED : MessageBlockStatus.ERROR } - smartBlockUpdate(possibleBlockId, changes, MessageBlockType.MAIN_TEXT) + smartBlockUpdate(possibleBlockId, changes, lastBlockType!, true) } const errorBlock = createErrorBlock(assistantMsgId, serializableError, { status: MessageBlockStatus.SUCCESS }) @@ -817,7 +830,7 @@ const fetchAndProcessAssistantResponseImpl = async ( const changes: Partial = { status: MessageBlockStatus.SUCCESS } - smartBlockUpdate(possibleBlockId, changes, lastBlockType!) + smartBlockUpdate(possibleBlockId, changes, lastBlockType!, true) } const endTime = Date.now()