fix: improve canvas agent response rendering
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled

This commit is contained in:
Your Name 2026-05-19 10:29:35 +08:00
parent 0b5866bb94
commit 82876977aa
3 changed files with 35 additions and 31 deletions

View File

@ -11976,18 +11976,6 @@ export async function POST(request: Request) {
},
"protocol:request"
);
writeAgentProgressChunk(
dataStream,
{
stage: "planning",
currentStep: "analyze",
title: "正在理解你的需求",
message: "我会先结合当前图、历史结果和画布上下文整理执行方案。",
toolName: "agentConversation",
capability: smartAutoExecution?.preferredCapability || undefined,
},
"chat-default:progress-planning",
);
writeCurrentAgentTargetLockChunk(dataStream, "chat-default:target-lock");
for (const event of pendingProtocolDebugEvents) {
writeProtocolDebugChunk(

View File

@ -91,6 +91,7 @@ import { getCanvasSkillIconComponent } from "@/components/canvas/canvas-skill-ic
import { ImageParamsBar, type ImageGenerationParams } from "@/components/image-params-bar";
import { VideoParamsBar, type VideoGenerationParams } from "@/components/video-params-bar";
import { ImageGenerationLoading } from "@/components/image-generation-loading";
import { Response } from "@/components/elements/response";
import type { VideoToolCapabilityContract } from "@/types/video-tool-capability";
import { DetailUiValidationPanel } from "@/components/canvas/DetailUiValidationPanel";
import { DetailSurfaceInspectorPanel } from "@/components/canvas/DetailSurfaceInspectorPanel";
@ -1019,6 +1020,7 @@ type CanvasTypewriterTextProps = {
typingKey: string;
enabled: boolean;
className?: string;
renderMarkdown?: boolean;
onActiveChange?: (typingKey: string, active: boolean) => void;
onComplete?: (typingKey: string) => void;
};
@ -1028,6 +1030,7 @@ function CanvasTypewriterText({
typingKey,
enabled,
className,
renderMarkdown = false,
onActiveChange,
onComplete,
}: CanvasTypewriterTextProps) {
@ -1074,18 +1077,33 @@ function CanvasTypewriterText({
};
}, [content, enabled, onActiveChange, onComplete, typingKey]);
if (renderMarkdown) {
return <Response className={className}>{displayText}</Response>;
}
return <div className={className}>{displayText}</div>;
}
function normalizeCanvasAgentText(value: string): string {
return String(value || "")
.replace(/\r\n/g, "\n")
.replace(/^#{1,6}\s*/gm, "")
.replace(/^\*\*(.+)\*\*$/gm, "$1")
.replace(/\n{3,}/g, "\n\n")
.trim();
}
function hasCanvasRenderableMarkdown(value: string): boolean {
const text = String(value || "");
if (!text) return false;
return (
/(^|\n)\s*#{1,6}\s+/.test(text) ||
/\*\*[^*]+\*\*/.test(text) ||
/(^|\n)\s*[-*+]\s+/.test(text) ||
/(^|\n)\s*\d+\.\s+/.test(text) ||
/\[[^\]]+\]\([^)]+\)/.test(text)
);
}
function normalizeCanvasSectionTitle(line: string): string {
let normalized = line.trim();
if (!normalized) return "";
@ -9285,8 +9303,7 @@ export function CanvasChatPanel({
!isPresetAgentSelection &&
Boolean(activeAgentProgress) &&
!(activeAgentProgress?.stage === "clarify" && isAgentTargetClarificationActive) &&
(isLoading ||
activeAgentProgress?.stage === "clarify" ||
(activeAgentProgress?.stage === "clarify" ||
activeAgentProgress?.stage === "failed");
const agentTargetLockPreviewItems = useMemo(() => {
if (!activeAgentTargetLock) return [] as AgentTargetLockRefPreview[];
@ -10111,11 +10128,13 @@ export function CanvasChatPanel({
const shouldAnimateAnalysis =
shouldTypeAssistantMessage &&
Boolean(analysisSection?.analysis) &&
!hasCanvasRenderableMarkdown(analysisSection?.analysis || "") &&
!typedAssistantSections[analysisTypingKey];
const shouldAnimatePlainText =
shouldTypeAssistantMessage &&
!analysisSection &&
Boolean(plainText) &&
!hasCanvasRenderableMarkdown(plainText) &&
!typedAssistantSections[plainTextTypingKey];
const shouldShowDoneSummary =
!isDetailGenerationMode &&
@ -10123,12 +10142,12 @@ export function CanvasChatPanel({
!plainText.includes(CANVAS_IMAGE_DONE_SUMMARY) &&
!isLatestAssistantDuringGeneration;
const renderPlainText = (text: string, key: string) => (
<div
<Response
key={key}
className="whitespace-pre-wrap break-words [overflow-wrap:anywhere]"
className="break-words [overflow-wrap:anywhere]"
>
{text}
</div>
</Response>
);
return (
@ -10185,6 +10204,7 @@ export function CanvasChatPanel({
typingKey={analysisTypingKey}
enabled={shouldAnimateAnalysis}
className="whitespace-pre-wrap break-words [overflow-wrap:anywhere]"
renderMarkdown
onActiveChange={handleTypewriterActiveChange}
onComplete={handleTypewriterComplete}
/>
@ -10200,6 +10220,7 @@ export function CanvasChatPanel({
typingKey={plainTextTypingKey}
enabled={shouldAnimatePlainText}
className="break-words [overflow-wrap:anywhere]"
renderMarkdown
onActiveChange={handleTypewriterActiveChange}
onComplete={handleTypewriterComplete}
/>
@ -11348,11 +11369,7 @@ export function CanvasChatPanel({
<span>Auto Agent</span>
</div>
)}
{!isPresetAgentSelection ? (
<div className="inline-flex items-center rounded-full bg-[#f4f5f7] px-3 py-1.5 text-xs text-[#5b6472] dark:bg-white/8 dark:text-zinc-300">
直接对话即可,也可上传图片让我分析、修图或继续生成
</div>
) : selectedAgentPreset?.type === "text" ? (
{isPresetAgentSelection && selectedAgentPreset?.type === "text" ? (
<div className="inline-flex items-center rounded-full bg-[#f4f5f7] px-3 py-1.5 text-xs text-[#5b6472] dark:bg-white/8 dark:text-zinc-300">
可上传图片做分析,也可直接输入文案需求
</div>

View File

@ -776,13 +776,12 @@ export const ThinkingMessage = () => {
</div>
<div className="flex w-full flex-col gap-2">
{/* 简洁明了的文本 + 动态点 */}
<div className="flex items-center gap-1.5">
<span className="text-base font-medium text-foreground">AI 正在分析中</span>
<span className="flex gap-1">
<span className="h-2 w-2 rounded-full bg-foreground animate-bounce" style={{ animationDelay: '0ms' }} />
<span className="h-2 w-2 rounded-full bg-foreground animate-bounce" style={{ animationDelay: '150ms' }} />
<span className="h-2 w-2 rounded-full bg-foreground animate-bounce" style={{ animationDelay: '300ms' }} />
<div className="flex items-center gap-1.5 text-sm text-muted-foreground">
<span className="sr-only">正在处理</span>
<span className="flex gap-1" aria-hidden="true">
<span className="h-2 w-2 rounded-full bg-foreground/65 animate-bounce" style={{ animationDelay: '0ms' }} />
<span className="h-2 w-2 rounded-full bg-foreground/65 animate-bounce" style={{ animationDelay: '150ms' }} />
<span className="h-2 w-2 rounded-full bg-foreground/65 animate-bounce" style={{ animationDelay: '300ms' }} />
</span>
</div>
</div>