From b3d37f815a2b874cca6e5458c5a36d891dfa3c16 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 1 Feb 2026 10:29:21 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BD=BF=E7=94=A8=E7=8B=AC=E7=AB=8B?= =?UTF-8?q?=E7=9A=84=20appendedMessages=20=E7=8A=B6=E6=80=81=E9=81=BF?= =?UTF-8?q?=E5=85=8D=E4=B8=8E=20useChat=20=E5=86=B2=E7=AA=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 参考 lobe-chat-pro 的架构,将 data-appendMessage 消息存储在 独立的状态中,通过 mergedMessages 合并后传递给组件, 避免直接修改 useChat 的内部状态导致消息丢失 Co-authored-by: Cursor --- components/chat.tsx | 92 +++++++++++++++++++++++++++++------------ components/messages.tsx | 27 +++++++----- 2 files changed, 81 insertions(+), 38 deletions(-) diff --git a/components/chat.tsx b/components/chat.tsx index b8bbf09..a31d839 100644 --- a/components/chat.tsx +++ b/components/chat.tsx @@ -3,7 +3,7 @@ import { useChat } from "@ai-sdk/react"; import { DefaultChatTransport } from "ai"; import { useRouter, useSearchParams } from "next/navigation"; -import { useEffect, useRef, useState } from "react"; +import { useEffect, useMemo, useRef, useState } from "react"; import useSWR, { useSWRConfig } from "swr"; import { unstable_serialize } from "swr/infinite"; import { ChatHeader } from "@/components/chat-header"; @@ -73,6 +73,9 @@ export function Chat({ // 🆕 跟踪是否正在生成图片(用于占位图显示) const [isGeneratingImageFlag, setIsGeneratingImageFlag] = useState(false); + // 🆕 存储通过 data-appendMessage 添加的消息,避免与 useChat 内部状态冲突 + const [appendedMessages, setAppendedMessages] = useState([]); + // 🆕 当检测到恢复的 pendingGeneration 时,刷新页面获取最新消息 useEffect(() => { if (pendingGeneration && !hasTriedRefreshRef.current) { @@ -320,28 +323,25 @@ export function Chat({ setIsGeneratingImageFlag(false); } - // 🆕 立即添加消息到状态,使用 requestAnimationFrame 避免与 useChat 冲突 - // 这比延迟到 onFinish 更可靠,因为用户需要立即看到对话消息 - requestAnimationFrame(() => { - setMessages((prevMessages) => { - // 检查消息是否已存在 - if (prevMessages.some(m => m.id === messageData.id)) { - console.log("[Chat] Message already exists, skipping:", messageData.id); - return prevMessages; - } - - const newMessage: any = { - id: messageData.id, - role: messageData.role, - content: messageData.parts?.[0]?.text || "", - createdAt: new Date(), - parts: messageData.parts || [], - attachments: [], - }; - - console.log("[Chat] Adding message via requestAnimationFrame:", messageData.id); - return [...prevMessages, newMessage]; - }); + // 🆕 将消息添加到 appendedMessages 状态,而不是直接修改 useChat 的 messages + // 这样可以避免与 useChat 内部状态冲突导致消息丢失 + const newMessage: ChatMessage = { + id: messageData.id, + role: messageData.role, + content: messageData.parts?.[0]?.text || "", + createdAt: new Date(), + parts: messageData.parts || [], + attachments: [], + } as ChatMessage; + + setAppendedMessages((prev) => { + // 检查消息是否已存在 + if (prev.some(m => m.id === messageData.id)) { + console.log("[Chat] Appended message already exists, skipping:", messageData.id); + return prev; + } + console.log("[Chat] Adding to appendedMessages:", messageData.id); + return [...prev, newMessage]; }); // 🆕 收到消息,清除持久化状态 @@ -356,7 +356,28 @@ export function Chat({ // 🆕 生成完成,清除持久化状态 completeGeneration(); console.log("[Chat] onFinish called"); - // 🆕 移除 router.refresh(),因为它会导致 React 错误和消息丢失 + + // 🆕 在流式传输完成后,将 appendedMessages 合并到 messages + // 延迟执行以确保 useChat 内部状态已稳定 + setTimeout(() => { + setAppendedMessages((prevAppended) => { + if (prevAppended.length > 0) { + console.log("[Chat] Merging appendedMessages to messages:", prevAppended.length); + setMessages((prevMessages) => { + // 过滤掉已存在的消息 + const newMessages = prevAppended.filter( + (appended) => !prevMessages.some((m) => m.id === appended.id) + ); + if (newMessages.length > 0) { + console.log("[Chat] Actually adding new messages:", newMessages.length); + return [...prevMessages, ...newMessages]; + } + return prevMessages; + }); + } + return []; // 清空 appendedMessages + }); + }, 100); }, onError: (error) => { if (error instanceof ChatSDKError) { @@ -1060,6 +1081,23 @@ export function Chat({ setMessages, }); + // 🆕 合并 useChat 的 messages 和 appendedMessages + // appendedMessages 包含通过 data-appendMessage 添加的消息 + const mergedMessages = useMemo(() => { + if (appendedMessages.length === 0) { + return messages; + } + // 过滤掉已存在的消息,然后合并 + const newMessages = appendedMessages.filter( + (appended) => !messages.some((m) => m.id === appended.id) + ); + if (newMessages.length > 0) { + console.log("[Chat] Rendering with merged messages:", messages.length, "+", newMessages.length); + return [...messages, ...newMessages]; + } + return messages; + }, [messages, appendedMessages]); + return ( <>
@@ -1073,7 +1111,7 @@ export function Chat({ chatId={id} isArtifactVisible={isArtifactVisible} isReadonly={isReadonly} - messages={messages} + messages={mergedMessages} regenerate={regenerate} sendMessage={sendMessage} selectedModelId={currentModelId} @@ -1101,7 +1139,7 @@ export function Chat({ attachments={attachments} chatId={id} input={input} - messages={messages} + messages={mergedMessages} onModelChange={setCurrentModelId} selectedModelId={currentModelId} selectedVisibilityType={visibilityType} @@ -1134,7 +1172,7 @@ export function Chat({ chatId={id} input={input} isReadonly={isReadonly} - messages={messages} + messages={mergedMessages} regenerate={regenerate} selectedModelId={currentModelId} selectedVisibilityType={visibilityType} diff --git a/components/messages.tsx b/components/messages.tsx index c6631c5..220971d 100644 --- a/components/messages.tsx +++ b/components/messages.tsx @@ -220,7 +220,7 @@ function PureMessages({ // 根据模型类型设置延迟时间 const delayTime = (isQianwenVideoModel || isVideoModel) ? 60000 : (isImageGenModel || isAutoModeWithImage) ? 4000 : 6000; - // 🆕 智能自动模式的占位图逻辑(简化版) + // 🆕 智能自动模式的占位图逻辑 if (isAutoModeWithImage) { console.log("[Messages useEffect] 智能模式检查:", { hasImageContent, @@ -229,22 +229,27 @@ function PureMessages({ status, }); - // 🆕 简化的占位图逻辑 - // 优先级:hasImageContent > isGeneratingImageFlag > isGeneratingImage > status + // 🆕 正确的占位图逻辑 + // 优先级:isGeneratingImageFlag > (hasImageContent && status=ready) > isGeneratingImage > status + // 关键:isGeneratingImageFlag 有最高优先级,只有它变 false 且有图片时才隐藏 - if (hasImageContent) { - // 图片已生成,延迟后隐藏占位图(无论其他标记如何) - console.log("[Messages useEffect] 图片已生成,延迟后隐藏"); + if (isGeneratingImageFlag) { + // 后端正在生成图片,始终显示占位图 + console.log("[Messages useEffect] isGeneratingImageFlag=true,强制显示占位图"); + setShowThinkingWithDelay(true); + } else if (hasImageContent && status === "ready") { + // 生成完成(flag=false)且有图片内容,延迟后隐藏占位图 + console.log("[Messages useEffect] 图片已生成且status=ready,延迟后隐藏"); const timer = setTimeout(() => { setShowThinkingWithDelay(false); }, delayTime); return () => clearTimeout(timer); - } else if (isGeneratingImageFlag) { - // 后端正在生成图片 - console.log("[Messages useEffect] isGeneratingImageFlag=true,显示占位图"); - setShowThinkingWithDelay(true); + } else if (hasImageContent && !isGeneratingImage) { + // 有图片内容且没有生成标记(可能是历史消息),直接隐藏 + console.log("[Messages useEffect] 有图片内容无生成标记,隐藏占位图"); + setShowThinkingWithDelay(false); } else if (isGeneratingImage) { - // 消息中检测到 GENERATING_IMAGE 标记(但没有图片内容) + // 消息中检测到 GENERATING_IMAGE 标记 console.log("[Messages useEffect] 检测到消息中的生成标记,显示占位图"); setShowThinkingWithDelay(true); } else if (status === "streaming" || status === "submitted") {