From 6967fd6683921419660518854f57c4374938839e Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 2 Mar 2026 22:36:26 +0800 Subject: [PATCH] fix(chat): delay image placeholder until agent analysis stage is visible --- components/chat.tsx | 45 +++++++++++++++++++++++++++++------------ components/messages.tsx | 1 + 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/components/chat.tsx b/components/chat.tsx index f440c5821..bc5f9ae40 100644 --- a/components/chat.tsx +++ b/components/chat.tsx @@ -115,6 +115,21 @@ function estimateTypewriterDurationMs(text: string): number { return Math.min(9000, Math.max(1200, length * 22)); } +function extractDisplayTextFromParts(parts?: Array): string { + if (!Array.isArray(parts) || parts.length === 0) return ""; + return parts + .filter((part) => part?.type === "text" && typeof part.text === "string") + .map((part) => + String(part.text) + .replace(/\n*\n*/g, "") + .replace(/\n*\n*/g, "") + .replace(/\n*\n*/g, "") + .trim() + ) + .join("\n") + .trim(); +} + function sortMessagesByCreatedAt(messages: ChatMessage[]): ChatMessage[] { return [...messages].sort((a, b) => { const timeA = (a as any).createdAt ? new Date((a as any).createdAt).getTime() : 0; @@ -242,6 +257,7 @@ export function Chat({ const [imageGenerationStartedAt, setImageGenerationStartedAt] = useState(null); const imageGenerationPhaseRef = useRef("idle"); const imagePlaceholderTimerRef = useRef | null>(null); + const latestAssistantDisplayTextRef = useRef(""); const startedGenerationMessageIdRef = useRef(null); // 🆕 存储通过 data-appendMessage 添加的消息,避免与 useChat 内部状态冲突 @@ -486,6 +502,7 @@ export function Chat({ api: "/api/chat", fetch: fetchWithErrorHandlers, prepareSendMessagesRequest(request) { + latestAssistantDisplayTextRef.current = ""; console.log("[Chat] Preparing request, videoParams:", videoParamsRef.current); console.log("[Chat] Preparing request, imageParams:", imageParamsRef.current); console.log("[Chat] Is video model:", isVideoModelRef.current); @@ -536,6 +553,10 @@ export function Chat({ .trim(); return cleanedText.length === 0; }); + const displayTextFromMessage = extractDisplayTextFromParts(messageData.parts); + if (displayTextFromMessage) { + latestAssistantDisplayTextRef.current = displayTextFromMessage; + } if (hasImageGenEndMarker && isMarkerOnlyMessage) { console.log("[Chat] 🖼️ Image generation entered finalizing stage"); @@ -548,28 +569,26 @@ export function Chat({ } if (hasImageGenStartMarker && isMarkerOnlyMessage) { - console.log("[Chat] 🖼️ Image generation started, show placeholder"); + const delayText = latestAssistantDisplayTextRef.current; + const delayMs = delayText + ? estimateTypewriterDurationMs(delayText) + 240 + : 1200; + console.log("[Chat] 🖼️ Image generation start marker received, delay processing placeholder:", delayMs); if (imagePlaceholderTimerRef.current) { clearTimeout(imagePlaceholderTimerRef.current); imagePlaceholderTimerRef.current = null; } - updateImageGenerationPhase("processing"); + updateImageGenerationPhase("queued"); + imagePlaceholderTimerRef.current = setTimeout(() => { + updateImageGenerationPhase("processing"); + imagePlaceholderTimerRef.current = null; + }, delayMs); return; // 不渲染标记消息 } if (hasLegacyGeneratingMarker) { console.log("[Chat] 🖼️ Detected legacy marker, will show placeholder after delay"); - const displayText = messageData.parts - ?.filter((part: any) => part.type === "text" && typeof part.text === "string") - .map((part: any) => - String(part.text) - .replace(/\n*\n*/g, "") - .replace(/\n*\n*/g, "") - .replace(/\n*\n*/g, "") - .trim() - ) - .join("\n") - .trim() || ""; + const displayText = displayTextFromMessage; const hasDisplayText = displayText.length > 0; const placeholderDelayMs = hasDisplayText ? estimateTypewriterDurationMs(displayText) + 240 diff --git a/components/messages.tsx b/components/messages.tsx index e21d34b32..9b00a2370 100644 --- a/components/messages.tsx +++ b/components/messages.tsx @@ -676,6 +676,7 @@ function PureMessages({ if ( autoImagePlaceholderReady && isGeneratingImageFlag && + imageGenerationPhase !== "queued" && hasAssistantTextReplyWithContent && !hasGenerationEnd ) {