fix(canvas): dedupe vote row and show model badge
This commit is contained in:
parent
7cc489a510
commit
1d293f180e
@ -158,6 +158,46 @@ function stripMediaMarkup(text: string): string {
|
|||||||
.trim();
|
.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 {
|
function getMessageTimestamp(message: ChatMessage): number | null {
|
||||||
const rawValue =
|
const rawValue =
|
||||||
(message as any).createdAt ??
|
(message as any).createdAt ??
|
||||||
@ -4840,19 +4880,32 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
|
|||||||
const assistantTextParts = message.role === "assistant"
|
const assistantTextParts = message.role === "assistant"
|
||||||
? (message.parts || []).filter((part: any) => part.type === "text" && part.text)
|
? (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) =>
|
const hasAssistantImages = assistantTextParts.some((part: any) =>
|
||||||
extractImageUrls(part.text || "").length > 0
|
extractImageUrls(part.text || "").length > 0
|
||||||
);
|
);
|
||||||
const hasAssistantVideos = assistantTextParts.some((part: any) =>
|
const hasAssistantVideos = assistantTextParts.some((part: any) =>
|
||||||
extractVideoUrls(part.text || "").length > 0
|
extractVideoUrls(part.text || "").length > 0
|
||||||
);
|
);
|
||||||
const hasAssistantText = assistantTextParts.some((part: any) => {
|
const hasAssistantMedia = hasAssistantImages || hasAssistantVideos;
|
||||||
return stripMediaMarkup(part.text).length > 0;
|
const hasAssistantText = assistantTextParts.some((part: any) =>
|
||||||
});
|
stripCanvasModelRoutingLines(stripMediaMarkup(part.text || "")).length > 0
|
||||||
|
);
|
||||||
const isImageOnlyAssistant =
|
const isImageOnlyAssistant =
|
||||||
message.role === "assistant" &&
|
message.role === "assistant" &&
|
||||||
(hasAssistantImages || hasAssistantVideos) &&
|
hasAssistantMedia &&
|
||||||
!hasAssistantText;
|
!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 messageTimestamp = getMessageTimestamp(message);
|
||||||
const prevMessageTimestamp =
|
const prevMessageTimestamp =
|
||||||
@ -4984,7 +5037,9 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
|
|||||||
const rawText = part.text || '';
|
const rawText = part.text || '';
|
||||||
const imageUrls = extractImageUrls(rawText);
|
const imageUrls = extractImageUrls(rawText);
|
||||||
const videoUrls = extractVideoUrls(rawText);
|
const videoUrls = extractVideoUrls(rawText);
|
||||||
const textWithoutImages = stripMediaMarkup(rawText);
|
const textWithoutImages = stripCanvasModelRoutingLines(
|
||||||
|
stripMediaMarkup(rawText)
|
||||||
|
);
|
||||||
const analysisSection = extractCanvasImageAnalysisSection(textWithoutImages);
|
const analysisSection = extractCanvasImageAnalysisSection(textWithoutImages);
|
||||||
const plainText = normalizeCanvasAgentText(textWithoutImages);
|
const plainText = normalizeCanvasAgentText(textWithoutImages);
|
||||||
const analysisToggleKey = `${message.id}-${i}-analysis`;
|
const analysisToggleKey = `${message.id}-${i}-analysis`;
|
||||||
@ -5109,31 +5164,40 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-1 flex items-center gap-1.5 text-[#8f96a3]">
|
<div className="mt-1 flex items-center gap-2 text-[#8f96a3]">
|
||||||
<button
|
{showModelMeta ? (
|
||||||
type="button"
|
<span className="text-[12px] font-medium tracking-tight text-[#8d94a2]">
|
||||||
className={cn(
|
◈ {assistantModelName}
|
||||||
"inline-flex h-7 w-7 items-center justify-center rounded-md transition-colors hover:bg-black/5",
|
</span>
|
||||||
currentVote?.isUpvoted && "text-[#22a06b] bg-emerald-50/80"
|
) : null}
|
||||||
)}
|
{showVoteActions ? (
|
||||||
title="点赞"
|
<>
|
||||||
disabled={isVotingCurrentMessage}
|
<button
|
||||||
onClick={() => void handleAssistantVote(message.id, "up")}
|
type="button"
|
||||||
>
|
className={cn(
|
||||||
<ThumbsUp className="h-4 w-4" />
|
"inline-flex h-7 w-7 items-center justify-center rounded-md transition-colors hover:bg-black/5",
|
||||||
</button>
|
currentVote?.isUpvoted && "text-[#22a06b] bg-emerald-50/80"
|
||||||
<button
|
)}
|
||||||
type="button"
|
title="点赞"
|
||||||
className={cn(
|
disabled={isVotingCurrentMessage}
|
||||||
"inline-flex h-7 w-7 items-center justify-center rounded-md transition-colors hover:bg-black/5",
|
onClick={() => void handleAssistantVote(message.id, "up")}
|
||||||
currentVote && !currentVote.isUpvoted && "text-[#d94848] bg-rose-50/80"
|
>
|
||||||
)}
|
<ThumbsUp className="h-4 w-4" />
|
||||||
title="点踩"
|
</button>
|
||||||
disabled={isVotingCurrentMessage}
|
<button
|
||||||
onClick={() => void handleAssistantVote(message.id, "down")}
|
type="button"
|
||||||
>
|
className={cn(
|
||||||
<ThumbsDown className="h-4 w-4" />
|
"inline-flex h-7 w-7 items-center justify-center rounded-md transition-colors hover:bg-black/5",
|
||||||
</button>
|
currentVote && !currentVote.isUpvoted && "text-[#d94848] bg-rose-50/80"
|
||||||
|
)}
|
||||||
|
title="点踩"
|
||||||
|
disabled={isVotingCurrentMessage}
|
||||||
|
onClick={() => void handleAssistantVote(message.id, "down")}
|
||||||
|
>
|
||||||
|
<ThumbsDown className="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@ -5156,7 +5220,7 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
|
|||||||
<div className="flex w-full justify-start">
|
<div className="flex w-full justify-start">
|
||||||
<div className="flex items-center gap-2 text-[#8e97a5]">
|
<div className="flex items-center gap-2 text-[#8e97a5]">
|
||||||
<Loader2 className="h-4 w-4 animate-spin" />
|
<Loader2 className="h-4 w-4 animate-spin" />
|
||||||
<span className="text-[16px]">思考中</span>
|
<span className="text-[16px]">分析中</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user