fix: 使用独立的 appendedMessages 状态避免与 useChat 冲突
参考 lobe-chat-pro 的架构,将 data-appendMessage 消息存储在 独立的状态中,通过 mergedMessages 合并后传递给组件, 避免直接修改 useChat 的内部状态导致消息丢失 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
3737b1ce99
commit
b3d37f815a
@ -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<ChatMessage[]>([]);
|
||||
|
||||
// 🆕 当检测到恢复的 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 (
|
||||
<>
|
||||
<div className="overscroll-behavior-contain flex h-dvh min-w-0 touch-pan-y flex-col bg-background">
|
||||
@ -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}
|
||||
|
||||
@ -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") {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user