From 9d2b21e1efb16be4757f0816512e87b35b8e4181 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 19 Feb 2026 11:51:23 +0800 Subject: [PATCH] fix detail workspace layout gap by filtering non-renderable chat messages --- components/canvas/CanvasChatPanel.tsx | 141 +++++++++++++------------- 1 file changed, 73 insertions(+), 68 deletions(-) diff --git a/components/canvas/CanvasChatPanel.tsx b/components/canvas/CanvasChatPanel.tsx index 3b0b11935..cd51119d2 100644 --- a/components/canvas/CanvasChatPanel.tsx +++ b/components/canvas/CanvasChatPanel.tsx @@ -3010,7 +3010,69 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF ); const shouldShowImageLoading = hasGenerationStart && !hasGenerationEnd && !!currentModel; const loadingImageCount = imageParams.imageCount || 1; - const isDetailEmptyState = isDetailGenerationMode && visibleMessages.length === 0; + const renderableVisibleMessages = useMemo(() => { + return visibleMessages.reduce((acc, message) => { + const rawParts = (message.parts || []) as any[]; + const cleanedParts = rawParts.map((part: any) => { + if (part.type === "text" && part.text) { + return { + ...part, + text: String(part.text) + .replace(/\n*\n*/g, "") + .replace(/\n*\n*/g, "") + .replace(/\n*\n*/g, "") + .trim(), + }; + } + return part; + }); + + const fallbackContent = + typeof (message as any).content === "string" + ? String((message as any).content).trim() + : ""; + + if (cleanedParts.length === 0 && fallbackContent) { + cleanedParts.push({ type: "text", text: fallbackContent } as any); + } + + let hasContent = false; + for (const part of cleanedParts) { + if (part?.type === "text" && part?.text && String(part.text).trim().length > 0) { + hasContent = true; + break; + } + const fileUrl = + typeof part?.url === "string" && part.url.trim() + ? part.url + : typeof part?.data === "string" && part.data.trim() + ? part.data + : ""; + if ((part?.type === "file" || part?.type === "image") && fileUrl) { + hasContent = true; + break; + } + } + + const attachmentsList = (message as any).attachments || []; + if (!hasContent && attachmentsList.length > 0) { + hasContent = true; + } + + if (!hasContent) { + return acc; + } + + acc.push({ ...message, parts: cleanedParts } as ChatMessage); + return acc; + }, []); + }, [visibleMessages]); + + const isDetailEmptyState = + isDetailGenerationMode && + renderableVisibleMessages.length === 0 && + !isLoading && + !shouldShowImageLoading; const renderModelSelectContent = () => ( @@ -3170,19 +3232,19 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF {/* 消息列表 */} {isDetailEmptyState ? ( -
+
) : (
- {!isDetailGenerationMode && visibleMessages.length === 0 && ( + {!isDetailGenerationMode && renderableVisibleMessages.length === 0 && (
{/* Greeting */}
@@ -3219,66 +3281,9 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
)} - {visibleMessages.map((message) => { - // 🆕 清理消息中的内部标记并检查是否有实际内容 - const rawParts = message.parts || []; - const cleanedParts = rawParts.map((part: any) => { - if (part.type === 'text' && part.text) { - return { - ...part, - text: part.text - .replace(/\n*\n*/g, "") - .replace(/\n*\n*/g, "") - .replace(/\n*\n*/g, "") - .trim() - }; - } - return part; - }); - - const fallbackContent = - typeof (message as any).content === "string" - ? (message as any).content.trim() - : ""; - if (cleanedParts.length === 0 && fallbackContent) { - cleanedParts.push({ type: "text", text: fallbackContent } as any); - } - - // 检查清理后是否有实际内容(更严格的检查) - let hasContent = false; - for (const part of cleanedParts) { - if (part.type === 'text' && part.text && part.text.trim().length > 0) { - hasContent = true; - break; - } - const fileUrl = - typeof part?.url === "string" && part.url.trim() - ? part.url - : typeof part?.data === "string" && part.data.trim() - ? part.data - : ""; - if ((part.type === 'file' || part.type === 'image') && fileUrl) { - hasContent = true; - break; - } - } - - // 同时检查附件 - const attachmentsList = (message as any).attachments || []; - if (attachmentsList.length > 0) { - hasContent = true; - } - - // 没有内容则不渲染(包括空 parts 的情况) - if (!hasContent) { - console.log('[CanvasChat] 跳过空消息:', message.id); - return null; - } - - // 使用清理后的消息 - const cleanedMessage = { ...message, parts: cleanedParts }; + {renderableVisibleMessages.map((message) => { const assistantTextParts = message.role === "assistant" - ? cleanedParts.filter((part: any) => part.type === "text" && part.text) + ? (message.parts || []).filter((part: any) => part.type === "text" && part.text) : []; const hasAssistantImages = assistantTextParts.some((part: any) => extractImageUrls(part.text || "").length > 0 @@ -3335,7 +3340,7 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF {(() => { const renderedUserParts: React.ReactNode[] = []; - cleanedMessage.parts?.forEach((part: any, idx: number) => { + message.parts?.forEach((part: any, idx: number) => { const fileUrl = typeof part?.url === "string" && part.url.trim() ? part.url @@ -3372,7 +3377,7 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF }); if ( - !cleanedMessage.parts?.some((p: any) => p.type === 'file') && + !message.parts?.some((p: any) => p.type === 'file') && (message as any).attachments?.length > 0 ) { (message as any).attachments.forEach((att: any, idx: number) => { @@ -3395,7 +3400,7 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF if (renderedUserParts.length === 0) { const fallbackText = - extractUserTextPrompt(cleanedMessage as any) || + extractUserTextPrompt(message as any) || (isVideoModel ? "已发送视频生成请求" : "已发送请求"); renderedUserParts.push( @@ -3420,7 +3425,7 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF > {/* 消息内容 */}
- {cleanedMessage.parts?.map((part: any, i: number) => { + {message.parts?.map((part: any, i: number) => { if (part.type === 'text') { // 提取并显示图片/视频 const rawText = part.text || ''; @@ -3568,7 +3573,7 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF className={cn( "mx-3 overflow-y-auto rounded-2xl border border-black/10 bg-white/95 p-3 pr-2", "mt-3", - visibleMessages.length === 0 ? "max-h-[62vh]" : "max-h-[54vh]" + renderableVisibleMessages.length === 0 ? "max-h-[62vh]" : "max-h-[54vh]" )} >