diff --git a/components/canvas/CanvasChatPanel.tsx b/components/canvas/CanvasChatPanel.tsx index deefd87b2..09feca94c 100644 --- a/components/canvas/CanvasChatPanel.tsx +++ b/components/canvas/CanvasChatPanel.tsx @@ -352,6 +352,13 @@ function isClawdTableFile(file: File) { ); } +function normalizeClawdComposerText(value: string) { + return String(value || "") + .replace(/\u200b/g, " ") + .replace(/\s+/g, " ") + .trim(); +} + function shouldShowClawdProcessingHint( message: string, hasTableFiles: boolean, @@ -361,10 +368,7 @@ function shouldShowClawdProcessingHint( return true; } - const normalizedMessage = String(message || "") - .replace(/\u200b/g, " ") - .replace(/\s+/g, " ") - .trim(); + const normalizedMessage = normalizeClawdComposerText(message); if (!normalizedMessage) { return false; @@ -1758,6 +1762,11 @@ export function CanvasChatPanel({ const historyScrollTimerRef = useRef | null>(null); const forceHistoryHydrationRef = useRef(null); const copiedUserMessageRef = useRef(null); + const pendingClawdSubmissionRef = useRef<{ + chatId: string; + submittedTexts: string[]; + startedAt: number; + } | null>(null); const replaceChatSessionQuery = useCallback( (sessionId: string) => { @@ -2095,6 +2104,63 @@ export function CanvasChatPanel({ setSelectedSkillTagId(null); }, [activeChatId]); + const clearComposerEditor = useCallback(() => { + setInput(""); + const editableDiv = textareaRef.current as HTMLDivElement | null; + if (editableDiv) { + editableDiv.innerHTML = ""; + } + }, []); + + const clearRestoredClawdComposerText = useCallback(() => { + const pendingSubmission = pendingClawdSubmissionRef.current; + const editableDiv = textareaRef.current as HTMLDivElement | null; + if (!pendingSubmission || !editableDiv) return; + if (pendingSubmission.chatId !== activeChatIdRef.current) return; + + const editorText = normalizeClawdComposerText( + getComposerPlainTextFromElement(editableDiv) + ); + if (!editorText) { + if (editableDiv.childNodes.length > 0) { + clearComposerEditor(); + } + return; + } + + const submittedTextSet = new Set( + pendingSubmission.submittedTexts + .map((text) => normalizeClawdComposerText(text)) + .filter(Boolean) + ); + if (!submittedTextSet.has(editorText)) return; + + clearComposerEditor(); + }, [clearComposerEditor]); + + useEffect(() => { + if (typeof window === "undefined") return; + + const clearAfterRestore = () => { + window.setTimeout(clearRestoredClawdComposerText, 0); + }; + const handleVisibilityChange = () => { + if (document.visibilityState === "visible") { + clearAfterRestore(); + } + }; + + document.addEventListener("visibilitychange", handleVisibilityChange); + window.addEventListener("focus", clearAfterRestore); + window.addEventListener("pageshow", clearAfterRestore); + + return () => { + document.removeEventListener("visibilitychange", handleVisibilityChange); + window.removeEventListener("focus", clearAfterRestore); + window.removeEventListener("pageshow", clearAfterRestore); + }; + }, [clearRestoredClawdComposerText]); + useEffect(() => { const now = Date.now(); let rootChatId = chatId; @@ -9313,10 +9379,7 @@ export function CanvasChatPanel({ } if (isClawdMode) { - const textContent = input - .replace(/\u200b/g, " ") - .replace(/\s+/g, " ") - .trim(); + const textContent = normalizeClawdComposerText(input); const tableFilesSnapshot = [...clawdTableFiles]; const hasTableFiles = tableFilesSnapshot.length > 0; const attachmentsSnapshot = [...attachments]; @@ -9389,6 +9452,16 @@ export function CanvasChatPanel({ signature: sendSignature, timestamp: Date.now(), }); + pendingClawdSubmissionRef.current = { + chatId: activeChatId, + submittedTexts: [ + textContent, + baseRequestMessage, + requestMessage, + optimisticMessageText, + ], + startedAt: Date.now(), + }; setClawdProcessingHint(nextProcessingHint); setIsClawdSubmitting(true); setMessages((previousMessages) => @@ -9397,16 +9470,11 @@ export function CanvasChatPanel({ optimisticUserMessage, ]) ); - setInput(""); + clearComposerEditor(); setAttachments([]); setClawdTableFiles([]); clearSelection(); - const editableDiv = textareaRef.current as HTMLDivElement | null; - if (editableDiv) { - editableDiv.innerHTML = ""; - } - try { const formData = new FormData(); formData.append("id", rawChatId); @@ -9474,6 +9542,7 @@ export function CanvasChatPanel({ toast.error(error instanceof Error ? error.message : `${CLAW_MODE_LABEL} 请求失败`); setClawdTableFiles(tableFilesSnapshot); setAttachments(attachmentsSnapshot); + const editableDiv = textareaRef.current as HTMLDivElement | null; const currentEditorText = (editableDiv?.textContent || "") .replace(/\u200b/g, " ") .replace(/\s+/g, " ") @@ -9495,6 +9564,7 @@ export function CanvasChatPanel({ } } } finally { + pendingClawdSubmissionRef.current = null; setIsClawdSubmitting(false); setClawdProcessingHint(null); releaseSendGuard(activeChatId); @@ -9828,6 +9898,7 @@ export function CanvasChatPanel({ releaseSendGuard, setSendGuard, setMessages, + clearComposerEditor, shouldBlockDuplicateSend, status, mutate,