fix: persist Claw composer clear guard
This commit is contained in:
parent
754209ee53
commit
4de04234d0
@ -333,6 +333,9 @@ const CLAWD_TABLE_MAX_FILES = 3;
|
||||
const CLAWD_TABLE_MAX_BYTES = 10 * 1024 * 1024;
|
||||
const CLAWD_PROCESSING_HINT =
|
||||
"已收到,这个任务稍微繁重一些,处理时间可能会长一点,请耐心等候。";
|
||||
const CLAWD_SUBMITTED_COMPOSER_RECOVERY_TTL_MS = 30 * 60 * 1000;
|
||||
const CLAWD_SUBMITTED_COMPOSER_STORAGE_KEY =
|
||||
"ai-chatbot:clawd-submitted-composer";
|
||||
const CLAWD_SIMPLE_PROMPT_PATTERNS = [
|
||||
/^(hi|hello|hey|你好|您好|哈喽|嗨|在吗|早上好|下午好|晚上好)[\s!.。!??~~]*$/i,
|
||||
/^(你是谁|你叫什么|你叫什么名字|你能做什么|你可以做什么|有什么功能|介绍一下自己|帮我介绍一下你)[\s!.。!??~~]*$/,
|
||||
@ -359,6 +362,85 @@ function normalizeClawdComposerText(value: string) {
|
||||
.trim();
|
||||
}
|
||||
|
||||
type ClawdComposerSubmissionSnapshot = {
|
||||
chatId: string;
|
||||
expiresAt: number;
|
||||
startedAt: number;
|
||||
submittedTexts: string[];
|
||||
};
|
||||
|
||||
function isClawdSubmittedComposerText(
|
||||
snapshot: ClawdComposerSubmissionSnapshot,
|
||||
value: string
|
||||
) {
|
||||
const normalizedValue = normalizeClawdComposerText(value);
|
||||
if (!normalizedValue) return false;
|
||||
|
||||
return snapshot.submittedTexts.some(
|
||||
(text) => normalizeClawdComposerText(text) === normalizedValue
|
||||
);
|
||||
}
|
||||
|
||||
function parseClawdComposerSubmissionSnapshot(
|
||||
value: unknown
|
||||
): ClawdComposerSubmissionSnapshot | null {
|
||||
if (!value || typeof value !== "object") return null;
|
||||
const snapshot = value as Partial<ClawdComposerSubmissionSnapshot>;
|
||||
const chatId = String(snapshot.chatId || "").trim();
|
||||
const startedAt = Number(snapshot.startedAt);
|
||||
const expiresAt = Number(snapshot.expiresAt);
|
||||
const submittedTexts = Array.isArray(snapshot.submittedTexts)
|
||||
? snapshot.submittedTexts
|
||||
.map((text) => normalizeClawdComposerText(String(text || "")))
|
||||
.filter(Boolean)
|
||||
: [];
|
||||
|
||||
if (!chatId || !Number.isFinite(startedAt) || !Number.isFinite(expiresAt)) {
|
||||
return null;
|
||||
}
|
||||
if (submittedTexts.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
chatId,
|
||||
expiresAt,
|
||||
startedAt,
|
||||
submittedTexts,
|
||||
};
|
||||
}
|
||||
|
||||
function readStoredClawdComposerSubmissionSnapshot() {
|
||||
if (typeof window === "undefined") return null;
|
||||
try {
|
||||
const raw = window.sessionStorage.getItem(
|
||||
CLAWD_SUBMITTED_COMPOSER_STORAGE_KEY
|
||||
);
|
||||
if (!raw) return null;
|
||||
return parseClawdComposerSubmissionSnapshot(JSON.parse(raw));
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function writeStoredClawdComposerSubmissionSnapshot(
|
||||
snapshot: ClawdComposerSubmissionSnapshot | null
|
||||
) {
|
||||
if (typeof window === "undefined") return;
|
||||
try {
|
||||
if (!snapshot) {
|
||||
window.sessionStorage.removeItem(CLAWD_SUBMITTED_COMPOSER_STORAGE_KEY);
|
||||
return;
|
||||
}
|
||||
window.sessionStorage.setItem(
|
||||
CLAWD_SUBMITTED_COMPOSER_STORAGE_KEY,
|
||||
JSON.stringify(snapshot)
|
||||
);
|
||||
} catch {
|
||||
// Ignore session storage failures; the in-memory guard still handles normal tab switches.
|
||||
}
|
||||
}
|
||||
|
||||
function shouldShowClawdProcessingHint(
|
||||
message: string,
|
||||
hasTableFiles: boolean,
|
||||
@ -1650,6 +1732,7 @@ export function CanvasChatPanel({
|
||||
[searchParams]
|
||||
);
|
||||
const [input, setInput] = useState("");
|
||||
const inputRef = useRef(input);
|
||||
const [attachments, setAttachments] = useState<PickedObjectAttachment[]>([]);
|
||||
const [selectedModelId, setSelectedModelId] = useState<string>(
|
||||
effectiveAgentEnabled ? "auto" : ""
|
||||
@ -1762,11 +1845,11 @@ export function CanvasChatPanel({
|
||||
const historyScrollTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const forceHistoryHydrationRef = useRef<string | null>(null);
|
||||
const copiedUserMessageRef = useRef<CopiedUserMessagePayload | null>(null);
|
||||
const pendingClawdSubmissionRef = useRef<{
|
||||
chatId: string;
|
||||
submittedTexts: string[];
|
||||
startedAt: number;
|
||||
} | null>(null);
|
||||
const composerUserEditedAtRef = useRef(0);
|
||||
const pendingClawdSubmissionRef =
|
||||
useRef<ClawdComposerSubmissionSnapshot | null>(null);
|
||||
const recentClawdSubmissionRef =
|
||||
useRef<ClawdComposerSubmissionSnapshot | null>(null);
|
||||
|
||||
const replaceChatSessionQuery = useCallback(
|
||||
(sessionId: string) => {
|
||||
@ -2104,6 +2187,10 @@ export function CanvasChatPanel({
|
||||
setSelectedSkillTagId(null);
|
||||
}, [activeChatId]);
|
||||
|
||||
useEffect(() => {
|
||||
inputRef.current = input;
|
||||
}, [input]);
|
||||
|
||||
const clearComposerEditor = useCallback(() => {
|
||||
setInput("");
|
||||
const editableDiv = textareaRef.current as HTMLDivElement | null;
|
||||
@ -2112,37 +2199,68 @@ export function CanvasChatPanel({
|
||||
}
|
||||
}, []);
|
||||
|
||||
const clearRestoredClawdComposerText = useCallback(() => {
|
||||
const getActiveClawdSubmissionSnapshot = useCallback(() => {
|
||||
const now = Date.now();
|
||||
const pendingSubmission = pendingClawdSubmissionRef.current;
|
||||
if (pendingSubmission) {
|
||||
return pendingSubmission;
|
||||
}
|
||||
|
||||
const recentSubmission =
|
||||
recentClawdSubmissionRef.current ||
|
||||
readStoredClawdComposerSubmissionSnapshot();
|
||||
if (!recentSubmission) {
|
||||
return null;
|
||||
}
|
||||
recentClawdSubmissionRef.current = recentSubmission;
|
||||
|
||||
if (recentSubmission.expiresAt <= now) {
|
||||
recentClawdSubmissionRef.current = null;
|
||||
writeStoredClawdComposerSubmissionSnapshot(null);
|
||||
return null;
|
||||
}
|
||||
|
||||
return recentSubmission;
|
||||
}, []);
|
||||
|
||||
const clearRestoredClawdComposerText = useCallback(() => {
|
||||
const submissionSnapshot = getActiveClawdSubmissionSnapshot();
|
||||
const editableDiv = textareaRef.current as HTMLDivElement | null;
|
||||
if (!pendingSubmission || !editableDiv) return;
|
||||
if (pendingSubmission.chatId !== activeChatIdRef.current) return;
|
||||
if (!submissionSnapshot || !editableDiv) return;
|
||||
if (submissionSnapshot.chatId !== activeChatIdRef.current) return;
|
||||
if (composerUserEditedAtRef.current > submissionSnapshot.startedAt) return;
|
||||
|
||||
const editorText = normalizeClawdComposerText(
|
||||
getComposerPlainTextFromElement(editableDiv)
|
||||
);
|
||||
if (!editorText) {
|
||||
if (editableDiv.childNodes.length > 0) {
|
||||
const stateText = normalizeClawdComposerText(inputRef.current);
|
||||
if (!editorText && !stateText) {
|
||||
if (pendingClawdSubmissionRef.current && editableDiv.childNodes.length > 0) {
|
||||
clearComposerEditor();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const submittedTextSet = new Set(
|
||||
pendingSubmission.submittedTexts
|
||||
.map((text) => normalizeClawdComposerText(text))
|
||||
.filter(Boolean)
|
||||
const editorWasRestored = isClawdSubmittedComposerText(
|
||||
submissionSnapshot,
|
||||
editorText
|
||||
);
|
||||
if (!submittedTextSet.has(editorText)) return;
|
||||
const stateWasRestored = isClawdSubmittedComposerText(
|
||||
submissionSnapshot,
|
||||
stateText
|
||||
);
|
||||
if (!editorWasRestored && !stateWasRestored) return;
|
||||
|
||||
clearComposerEditor();
|
||||
}, [clearComposerEditor]);
|
||||
}, [clearComposerEditor, getActiveClawdSubmissionSnapshot]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === "undefined") return;
|
||||
|
||||
const clearAfterRestore = () => {
|
||||
window.setTimeout(clearRestoredClawdComposerText, 0);
|
||||
[0, 120, 500, 1500].forEach((delay) => {
|
||||
window.setTimeout(clearRestoredClawdComposerText, delay);
|
||||
});
|
||||
};
|
||||
const handleVisibilityChange = () => {
|
||||
if (document.visibilityState === "visible") {
|
||||
@ -9452,16 +9570,22 @@ export function CanvasChatPanel({
|
||||
signature: sendSignature,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
pendingClawdSubmissionRef.current = {
|
||||
const clawdSubmissionStartedAt = Date.now();
|
||||
const clawdSubmissionSnapshot: ClawdComposerSubmissionSnapshot = {
|
||||
chatId: activeChatId,
|
||||
expiresAt:
|
||||
clawdSubmissionStartedAt + CLAWD_SUBMITTED_COMPOSER_RECOVERY_TTL_MS,
|
||||
startedAt: clawdSubmissionStartedAt,
|
||||
submittedTexts: [
|
||||
textContent,
|
||||
baseRequestMessage,
|
||||
requestMessage,
|
||||
optimisticMessageText,
|
||||
],
|
||||
startedAt: Date.now(),
|
||||
};
|
||||
pendingClawdSubmissionRef.current = clawdSubmissionSnapshot;
|
||||
recentClawdSubmissionRef.current = clawdSubmissionSnapshot;
|
||||
writeStoredClawdComposerSubmissionSnapshot(clawdSubmissionSnapshot);
|
||||
setClawdProcessingHint(nextProcessingHint);
|
||||
setIsClawdSubmitting(true);
|
||||
setMessages((previousMessages) =>
|
||||
@ -9471,6 +9595,9 @@ export function CanvasChatPanel({
|
||||
])
|
||||
);
|
||||
clearComposerEditor();
|
||||
[0, 120, 500, 1500].forEach((delay) => {
|
||||
window.setTimeout(clearRestoredClawdComposerText, delay);
|
||||
});
|
||||
setAttachments([]);
|
||||
setClawdTableFiles([]);
|
||||
clearSelection();
|
||||
@ -9536,6 +9663,11 @@ export function CanvasChatPanel({
|
||||
hasHydratedHistoryRef.current = true;
|
||||
} catch (error) {
|
||||
console.error("[CanvasChat] Claw request failed:", error);
|
||||
pendingClawdSubmissionRef.current = null;
|
||||
if (recentClawdSubmissionRef.current === clawdSubmissionSnapshot) {
|
||||
recentClawdSubmissionRef.current = null;
|
||||
writeStoredClawdComposerSubmissionSnapshot(null);
|
||||
}
|
||||
setMessages((previousMessages) =>
|
||||
previousMessages.filter((message) => message.id !== userMessageId)
|
||||
);
|
||||
@ -9565,6 +9697,14 @@ export function CanvasChatPanel({
|
||||
}
|
||||
} finally {
|
||||
pendingClawdSubmissionRef.current = null;
|
||||
if (recentClawdSubmissionRef.current === clawdSubmissionSnapshot) {
|
||||
const refreshedSubmissionSnapshot = {
|
||||
...clawdSubmissionSnapshot,
|
||||
expiresAt: Date.now() + CLAWD_SUBMITTED_COMPOSER_RECOVERY_TTL_MS,
|
||||
};
|
||||
recentClawdSubmissionRef.current = refreshedSubmissionSnapshot;
|
||||
writeStoredClawdComposerSubmissionSnapshot(refreshedSubmissionSnapshot);
|
||||
}
|
||||
setIsClawdSubmitting(false);
|
||||
setClawdProcessingHint(null);
|
||||
releaseSendGuard(activeChatId);
|
||||
@ -9899,6 +10039,7 @@ export function CanvasChatPanel({
|
||||
setSendGuard,
|
||||
setMessages,
|
||||
clearComposerEditor,
|
||||
clearRestoredClawdComposerText,
|
||||
shouldBlockDuplicateSend,
|
||||
status,
|
||||
mutate,
|
||||
@ -13309,6 +13450,18 @@ export function CanvasChatPanel({
|
||||
.replace(/\u200b/g, " ")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
const activeClawdSubmission =
|
||||
getActiveClawdSubmissionSnapshot();
|
||||
const isRestoredSubmittedClawdText =
|
||||
activeClawdSubmission !== null &&
|
||||
activeClawdSubmission.chatId === activeChatIdRef.current &&
|
||||
isClawdSubmittedComposerText(
|
||||
activeClawdSubmission,
|
||||
textContent
|
||||
);
|
||||
if (!isRestoredSubmittedClawdText) {
|
||||
composerUserEditedAtRef.current = Date.now();
|
||||
}
|
||||
setInput(textContent);
|
||||
if ((!isVideoModel || videoFrameSlots.length === 0) && !hasAgentUploadSlots) {
|
||||
syncAttachmentsWithEditor(target);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user