diff --git a/components/canvas/CanvasChatPanel.tsx b/components/canvas/CanvasChatPanel.tsx index d02e0c9f9..79e5bfc1b 100644 --- a/components/canvas/CanvasChatPanel.tsx +++ b/components/canvas/CanvasChatPanel.tsx @@ -898,10 +898,30 @@ function CanvasTypewriterText({ return
{displayText}
; } +function CanvasAssistantTypingPlaceholder() { + return ( +
+
+ {[0, 1, 2].map((index) => ( + + ))} +
+
+ ); +} + function normalizeCanvasAgentText(value: string): string { return String(value || "") .replace(/\r\n/g, "\n") - .replace(/\n{3,}/g, "\n\n") + .replace(/[ \t\u00a0]+\n/g, "\n") + .replace(/\n[ \t\u00a0]*\n(?:[ \t\u00a0]*\n)+/g, "\n\n") .trim(); } @@ -914,6 +934,7 @@ function hasCanvasRenderableMarkdown(value: string): boolean { /\*\*[^*]+\*\*/.test(text) || /(^|\n)\s*[-*+]\s+/.test(text) || /(^|\n)\s*\d+\.\s+/.test(text) || + /(^|\n)\s*\|.+\|\s*\n\s*\|[-:\s|]+\|/.test(text) || /\[[^\]]+\]\([^)]+\)/.test(text) ); } @@ -5849,9 +5870,9 @@ export function CanvasChatPanel({ // 滚动到底部 useEffect(() => { - if (messages.length === 0) return; + if (messages.length === 0 && !isClawdSubmitting) return; messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); - }, [messages]); + }, [isClawdSubmitting, messages]); // 标记是否已经为当前画布设置过标题(避免重复调用) const hasSetCanvasTitle = useRef(false); @@ -10785,7 +10806,7 @@ export function CanvasChatPanel({ text={analysisSection.analysis} typingKey={analysisTypingKey} enabled={shouldAnimateAnalysis} - className="whitespace-pre-wrap break-words [overflow-wrap:anywhere]" + className="break-words [overflow-wrap:anywhere]" renderMarkdown onActiveChange={handleTypewriterActiveChange} onComplete={handleTypewriterComplete} @@ -10967,6 +10988,10 @@ export function CanvasChatPanel({ /> )} + {isClawdMode && isClawdSubmitting ? ( + + ) : null} + {isLoading && !shouldRenderImageLoading && (
diff --git a/components/elements/response.tsx b/components/elements/response.tsx index 7f2f783bb..1eebc685b 100644 --- a/components/elements/response.tsx +++ b/components/elements/response.tsx @@ -80,6 +80,14 @@ function downloadTextFile(filename: string, content: string, type: string) { URL.revokeObjectURL(url); } +function normalizeMarkdownContent(value: string) { + return String(value || "") + .replace(/\r\n/g, "\n") + .replace(/[ \t\u00a0]+\n/g, "\n") + .replace(/\n[ \t\u00a0]*\n(?:[ \t\u00a0]*\n)+/g, "\n\n") + .trim(); +} + function splitMarkdownImages(content: string) { const segments: Array< | { type: "text"; value: string } @@ -275,7 +283,7 @@ function DownloadableMarkdownTable({ }; return ( -
+