diff --git a/components/canvas/CanvasChatPanel.tsx b/components/canvas/CanvasChatPanel.tsx index 8410edcab..0cf3ea2f6 100644 --- a/components/canvas/CanvasChatPanel.tsx +++ b/components/canvas/CanvasChatPanel.tsx @@ -158,6 +158,46 @@ function stripMediaMarkup(text: string): string { .trim(); } +function extractCanvasModelName(text: string): string { + const source = String(text || ""); + if (!source) return ""; + + const badgeMatch = source.match( + /^\s*[◈◇◆🔹⬡✦•·]\s*([A-Za-z][A-Za-z0-9 .:+\-_/()]{2,})\s*$/im + ); + if (badgeMatch?.[1]) { + return badgeMatch[1].trim(); + } + + const routedMatch = source.match( + /^\s*(?:Routed to|Using model)\s*[::]\s*([^\n(]+).*$/im + ); + if (routedMatch?.[1]) { + return routedMatch[1].trim(); + } + + return ""; +} + +function stripCanvasModelRoutingLines(text: string): string { + return String(text || "") + .replace(/^\s*(?:Routed to|Using model)\s*[::].*$/gim, "") + .replace(/^\s*[◈◇◆🔹⬡✦•·]\s*[A-Za-z][A-Za-z0-9 .:+\-_/()]{2,}\s*$/gim, "") + .replace(/\n{3,}/g, "\n\n") + .trim(); +} + +function messageHasAssistantMedia(message: ChatMessage | undefined): boolean { + if (!message || message.role !== "assistant") return false; + const textParts = (message.parts || []).filter( + (part: any) => part.type === "text" && part.text + ); + return textParts.some((part: any) => { + const text = String(part.text || ""); + return extractImageUrls(text).length > 0 || extractVideoUrls(text).length > 0; + }); +} + function getMessageTimestamp(message: ChatMessage): number | null { const rawValue = (message as any).createdAt ?? @@ -4840,19 +4880,32 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF const assistantTextParts = message.role === "assistant" ? (message.parts || []).filter((part: any) => part.type === "text" && part.text) : []; + const assistantModelName = + assistantTextParts + .map((part: any) => extractCanvasModelName(part.text || "")) + .find((name) => Boolean(name)) || ""; const hasAssistantImages = assistantTextParts.some((part: any) => extractImageUrls(part.text || "").length > 0 ); const hasAssistantVideos = assistantTextParts.some((part: any) => extractVideoUrls(part.text || "").length > 0 ); - const hasAssistantText = assistantTextParts.some((part: any) => { - return stripMediaMarkup(part.text).length > 0; - }); + const hasAssistantMedia = hasAssistantImages || hasAssistantVideos; + const hasAssistantText = assistantTextParts.some((part: any) => + stripCanvasModelRoutingLines(stripMediaMarkup(part.text || "")).length > 0 + ); const isImageOnlyAssistant = message.role === "assistant" && - (hasAssistantImages || hasAssistantVideos) && + hasAssistantMedia && !hasAssistantText; + const nextMessage = renderableVisibleMessages[index + 1] as ChatMessage | undefined; + const nextAssistantHasMedia = messageHasAssistantMedia(nextMessage); + const hideVoteActions = + message.role === "assistant" && + !hasAssistantMedia && + nextAssistantHasMedia; + const showVoteActions = message.role === "assistant" && !hideVoteActions; + const showModelMeta = hideVoteActions && Boolean(assistantModelName); const messageTimestamp = getMessageTimestamp(message); const prevMessageTimestamp = @@ -4984,7 +5037,9 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF const rawText = part.text || ''; const imageUrls = extractImageUrls(rawText); const videoUrls = extractVideoUrls(rawText); - const textWithoutImages = stripMediaMarkup(rawText); + const textWithoutImages = stripCanvasModelRoutingLines( + stripMediaMarkup(rawText) + ); const analysisSection = extractCanvasImageAnalysisSection(textWithoutImages); const plainText = normalizeCanvasAgentText(textWithoutImages); const analysisToggleKey = `${message.id}-${i}-analysis`; @@ -5109,31 +5164,40 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF })} -
- - +
+ {showModelMeta ? ( + + ◈ {assistantModelName} + + ) : null} + {showVoteActions ? ( + <> + + + + ) : null}
)} @@ -5156,7 +5220,7 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
- 思考中 + 分析中
)}