fix detail workspace layout gap by filtering non-renderable chat messages
This commit is contained in:
parent
8aba32d35d
commit
9d2b21e1ef
@ -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<ChatMessage[]>((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*<!-- GENERATING_IMAGE -->\n*/g, "")
|
||||
.replace(/\n*<!-- IMAGE_GENERATION_START -->\n*/g, "")
|
||||
.replace(/\n*<!-- IMAGE_GENERATION_END -->\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 = () => (
|
||||
<SelectContent className="w-[238px] max-h-[340px] overflow-hidden rounded-[18px] border border-[#cfd4dc] bg-[#f3f4f6] p-1.5 shadow-[0_8px_20px_rgba(15,23,42,0.13)]">
|
||||
@ -3170,19 +3232,19 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
|
||||
|
||||
{/* 消息列表 */}
|
||||
{isDetailEmptyState ? (
|
||||
<div className="h-16 shrink-0" />
|
||||
<div className="h-3 shrink-0" />
|
||||
) : (
|
||||
<div
|
||||
key={`${activeChatId}-${messageViewportKey}`}
|
||||
className={cn(
|
||||
"flex-1 min-h-0 overflow-y-auto overflow-x-hidden p-4",
|
||||
isDetailGenerationMode && visibleMessages.length === 0
|
||||
isDetailGenerationMode && renderableVisibleMessages.length === 0
|
||||
? "pt-10"
|
||||
: "pt-4"
|
||||
)}
|
||||
>
|
||||
<div className="space-y-6 overflow-x-hidden pb-24 pr-1">
|
||||
{!isDetailGenerationMode && visibleMessages.length === 0 && (
|
||||
{!isDetailGenerationMode && renderableVisibleMessages.length === 0 && (
|
||||
<div className={cn("flex flex-col h-full", isDetailGenerationMode ? "pt-0" : "pt-4")}>
|
||||
{/* Greeting */}
|
||||
<div className={cn(isDetailGenerationMode ? "mb-4" : "mb-8")}>
|
||||
@ -3219,66 +3281,9 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
|
||||
</div>
|
||||
)}
|
||||
|
||||
{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*<!-- GENERATING_IMAGE -->\n*/g, "")
|
||||
.replace(/\n*<!-- IMAGE_GENERATION_START -->\n*/g, "")
|
||||
.replace(/\n*<!-- IMAGE_GENERATION_END -->\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(
|
||||
<span key="fallback-text" className="text-sm text-foreground">
|
||||
@ -3420,7 +3425,7 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
|
||||
>
|
||||
{/* 消息内容 */}
|
||||
<div className="w-full min-w-0 whitespace-pre-wrap break-words [overflow-wrap:anywhere] leading-relaxed">
|
||||
{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]"
|
||||
)}
|
||||
>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user