fix: 修复智能自动模式占位图提前消失问题

问题:
- appendMessage 放入队列延迟处理导致 GENERATING_IMAGE 标记无法被及时检测
- status 变成 ready 时占位图消失,但图片还在生成中

解决方案:
- 添加 isGeneratingImageFlag 状态来跟踪图片生成进度
- 在收到包含 GENERATING_IMAGE 标记的消息时立即设置标志
- 在收到包含图片的消息时清除标志
- 将标志传递给 Messages 组件用于占位图显示判断

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Your Name 2026-02-01 02:49:52 +08:00
parent ed4c2fe8cd
commit c6180e8610
2 changed files with 72 additions and 37 deletions

View File

@ -69,6 +69,12 @@ export function Chat({
// 🆕 标记是否已经尝试刷新过(避免无限刷新)
const hasTriedRefreshRef = useRef(false);
// 🆕 存储待处理的 appendMessage(避免与 useChat 内部状态冲突)
const pendingAppendMessagesRef = useRef<any[]>([]);
// 🆕 跟踪是否正在生成图片(用于占位图显示)
const [isGeneratingImageFlag, setIsGeneratingImageFlag] = useState(false);
// 🆕 当检测到恢复的 pendingGeneration 时,刷新页面获取最新消息
useEffect(() => {
if (pendingGeneration && !hasTriedRefreshRef.current) {
@ -280,48 +286,44 @@ export function Chat({
setUsage(dataPart.data);
}
// 处理后端返回的自定义消息(用于图片/视频生成的非流式响应)
// 🆕 处理后端返回的自定义消息(用于图片/视频生成的非流式响应)
// 注意:不要在这里调用 setMessages,因为会与 useChat 内部状态冲突
// 改为存储到待处理队列,在 onFinish 时统一处理
if (dataPart.type === "data-appendMessage") {
try {
// dataPart.data 是 JSON 字符串
const messageData = typeof dataPart.data === 'string'
? JSON.parse(dataPart.data)
: dataPart.data;
console.log("[Chat] Received appendMessage:", messageData);
console.log("[Chat] Received appendMessage (queued):", messageData);
// 🆕 检测是否包含 GENERATING_IMAGE 标记
const hasGeneratingMarker = messageData.parts?.some(
(part: any) => part.type === "text" && part.text?.includes("<!-- GENERATING_IMAGE -->")
);
if (hasGeneratingMarker) {
console.log("[Chat] 🖼️ Detected GENERATING_IMAGE marker, showing placeholder");
setIsGeneratingImageFlag(true);
}
// 🆕 检测是否包含图片(生成完成)
const hasImage = messageData.parts?.some(
(part: any) => part.type === "text" && part.text && (
part.text.includes("![thumb|") ||
part.text.includes("![thumb|||") ||
part.text.includes("![Generated Image")
)
);
if (hasImage) {
console.log("[Chat] 🎉 Image generated, hiding placeholder");
setIsGeneratingImageFlag(false);
}
// 存储到待处理队列
pendingAppendMessagesRef.current.push(messageData);
// 🆕 收到消息,清除持久化状态
completeGeneration();
setMessages((prevMessages) => {
console.log("[Chat] 📋 setMessages 调用前:", prevMessages.map(m => ({
id: m.id?.substring(0, 8),
role: m.role
})));
if (prevMessages.some(m => m.id === messageData.id)) {
console.log("[Chat] ⚠️ 消息已存在,跳过");
return prevMessages;
}
// 构造 ChatMessage 对象
// 注意:这里需要确保符合 ChatMessage 类型定义
const newMessage: any = {
id: messageData.id,
role: messageData.role,
content: messageData.parts?.[0]?.text || "",
createdAt: new Date(),
parts: messageData.parts || [],
attachments: [],
};
const newMessages = [...prevMessages, newMessage];
console.log("[Chat] 📋 setMessages 调用后:", newMessages.map(m => ({
id: m.id?.substring(0, 8),
role: m.role
})));
return newMessages;
});
} catch (e) {
console.error("[Chat] Failed to parse data-appendMessage:", e);
}
@ -331,6 +333,35 @@ export function Chat({
mutate(unstable_serialize(getChatHistoryPaginationKey));
// 🆕 生成完成,清除持久化状态
completeGeneration();
// 🆕 处理待处理的 appendMessage(在流结束后安全地添加)
if (pendingAppendMessagesRef.current.length > 0) {
console.log("[Chat] Processing pending appendMessages:", pendingAppendMessagesRef.current.length);
const pendingMessages = [...pendingAppendMessagesRef.current];
pendingAppendMessagesRef.current = [];
// 使用 setTimeout 延迟处理,确保 useChat 内部状态已稳定
setTimeout(() => {
setMessages((prevMessages) => {
let newMessages = [...prevMessages];
for (const messageData of pendingMessages) {
if (!newMessages.some(m => m.id === messageData.id)) {
const newMessage: any = {
id: messageData.id,
role: messageData.role,
content: messageData.parts?.[0]?.text || "",
createdAt: new Date(),
parts: messageData.parts || [],
attachments: [],
};
newMessages = [...newMessages, newMessage];
}
}
console.log("[Chat] After processing pending messages:", newMessages.length);
return newMessages;
});
}, 100);
}
},
onError: (error) => {
if (error instanceof ChatSDKError) {
@ -1066,6 +1097,7 @@ export function Chat({
isProcessingAngleEdit={isProcessingAngleEdit}
pendingGeneration={pendingGeneration}
imageCount={imageParams?.imageCount || 1}
isGeneratingImageFlag={isGeneratingImageFlag}
/>
<div className="sticky bottom-0 z-1 mx-auto w-full max-w-4xl bg-background px-2 pb-3 md:px-4 md:pb-4">

View File

@ -48,6 +48,7 @@ type MessagesProps = {
isProcessingAngleEdit?: boolean;
pendingGeneration?: PendingGeneration | null; // 🆕 持久化的生成状态
imageCount?: number; // 🆕 用户选择的图片数量
isGeneratingImageFlag?: boolean; // 🆕 正在生成图片的标志(来自 Chat 组件)
};
function PureMessages({
@ -72,6 +73,7 @@ function PureMessages({
isProcessingAngleEdit = false,
pendingGeneration,
imageCount = 1,
isGeneratingImageFlag = false,
}: MessagesProps) {
const {
containerRef: messagesContainerRef,
@ -226,8 +228,9 @@ function PureMessages({
setShowThinkingWithDelay(false);
}, delayTime);
return () => clearTimeout(timer);
} else if (isGeneratingImage) {
} else if (isGeneratingImage || isGeneratingImageFlag) {
// 收到 GENERATING_IMAGE 标记(对话消息已发送),继续显示占位图等待图片
// 使用 isGeneratingImageFlag 来解决消息延迟添加时标记检测不到的问题
setShowThinkingWithDelay(true);
} else if (status === "streaming" || status === "submitted") {
// 正在处理中(分析意图或流式传输),显示占位图
@ -251,7 +254,7 @@ function PureMessages({
setShowThinkingWithDelay(false);
}
}
}, [status, messages, isGeminiModel, isQianwenVideoModel, isVolcanoVisionModel, isVideoModel, isAlibabaImageModel, selectedModelId]);
}, [status, messages, isGeminiModel, isQianwenVideoModel, isVolcanoVisionModel, isVideoModel, isAlibabaImageModel, selectedModelId, isGeneratingImageFlag]);
// 🆕 监听新消息添加,当用户发送带图片的消息或添加加载消息时,强制滚动到底部
const prevMessagesLengthRef = useRef(messages.length);
@ -411,8 +414,8 @@ function PureMessages({
// 🆕 智能自动模式占位图
if (isAutoMode && userMessageHasImage) {
// 如果已经收到了 GENERATING_IMAGE 标记,显示生图占位图
if (hasGeneratingMarker) {
// 如果已经收到了 GENERATING_IMAGE 标记 或 isGeneratingImageFlag 为 true,显示生图占位图
if (hasGeneratingMarker || isGeneratingImageFlag) {
return <ImageGenerationLoading imageCount={imageCount} maxImages={6} providerType="alibaba-image" modelName="智能处理" />;
}
// 否则显示思考动画