fix: queued message lost when clicking sending now (#9074)

This commit is contained in:
Lifei Zhou
2026-05-07 22:30:58 +10:00
committed by GitHub
parent 7891e2f34d
commit 875e0c0ff7
+81 -27
View File
@@ -52,6 +52,18 @@ interface PastedImage {
error?: string; error?: string;
} }
const moveQueuedMessageToFront = (
messages: QueuedMessage[],
messageId: string
): QueuedMessage[] => {
const selectedMessage = messages.find((msg) => msg.id === messageId);
if (!selectedMessage) return messages;
return [selectedMessage, ...messages.filter((msg) => msg.id !== messageId)];
};
const removeQueuedMessage = (messages: QueuedMessage[], messageId: string): QueuedMessage[] =>
messages.filter((msg) => msg.id !== messageId);
const MAX_IMAGES_PER_MESSAGE = 10; const MAX_IMAGES_PER_MESSAGE = 10;
// Constants for token and tool alerts // Constants for token and tool alerts
@@ -220,8 +232,25 @@ export default function ChatInput({
const [queuedMessages, setQueuedMessages] = useState<QueuedMessage[]>([]); const [queuedMessages, setQueuedMessages] = useState<QueuedMessage[]>([]);
const queuePausedRef = useRef(false); const queuePausedRef = useRef(false);
const editingMessageIdRef = useRef<string | null>(null); const editingMessageIdRef = useRef<string | null>(null);
const sendAfterStopMessageIdRef = useRef<string | null>(null);
const [lastInterruption, setLastInterruption] = useState<string | null>(null); const [lastInterruption, setLastInterruption] = useState<string | null>(null);
const pauseRemainingQueue = useCallback(() => {
queuePausedRef.current = true;
}, []);
const clearPendingSendAfterStop = useCallback((messageId?: string) => {
if (!messageId || sendAfterStopMessageIdRef.current === messageId) {
sendAfterStopMessageIdRef.current = null;
}
}, []);
const clearQueueState = useCallback(() => {
queuePausedRef.current = false;
sendAfterStopMessageIdRef.current = null;
setLastInterruption(null);
}, []);
const { alerts, addAlert, clearAlerts } = useAlerts(); const { alerts, addAlert, clearAlerts } = useAlerts();
const dropdownRef: React.RefObject<HTMLDivElement> = useRef<HTMLDivElement>( const dropdownRef: React.RefObject<HTMLDivElement> = useRef<HTMLDivElement>(
null null
@@ -315,20 +344,40 @@ export default function ChatInput({
// Queue processing // Queue processing
useEffect(() => { useEffect(() => {
if (wasLoadingRef.current && !isLoading && queuedMessages.length > 0) { if (wasLoadingRef.current && !isLoading && queuedMessages.length > 0) {
// After an interruption, we should process the interruption message immediately const pendingSendAfterStopId = sendAfterStopMessageIdRef.current;
// The queue is only truly paused if there was an interruption AND we want to keep it paused const messageToSend = pendingSendAfterStopId
const shouldProcessQueue = !queuePausedRef.current || lastInterruption; ? queuedMessages.find((message) => message.id === pendingSendAfterStopId)
: queuedMessages[0];
if (pendingSendAfterStopId && !messageToSend) {
clearPendingSendAfterStop(pendingSendAfterStopId);
wasLoadingRef.current = isLoading;
return;
}
if (!messageToSend) {
wasLoadingRef.current = isLoading;
return;
}
const shouldSendAfterStop = pendingSendAfterStopId === messageToSend.id;
const shouldProcessQueue = !queuePausedRef.current || lastInterruption || shouldSendAfterStop;
if (shouldProcessQueue) { if (shouldProcessQueue) {
const nextMessage = queuedMessages[0]; LocalMessageStorage.addMessage(messageToSend.content);
LocalMessageStorage.addMessage(nextMessage.content); handleSubmit({ msg: messageToSend.content, images: messageToSend.images });
handleSubmit({ msg: nextMessage.content, images: nextMessage.images }); if (shouldSendAfterStop) {
clearPendingSendAfterStop(messageToSend.id);
}
setQueuedMessages((prev) => { setQueuedMessages((prev) => {
const newQueue = prev.slice(1); const newQueue = shouldSendAfterStop
? removeQueuedMessage(prev, messageToSend.id)
: prev.slice(1);
// If queue becomes empty after processing, clear the paused state // If queue becomes empty after processing, clear the paused state
if (newQueue.length === 0) { if (newQueue.length === 0) {
queuePausedRef.current = false; clearQueueState();
setLastInterruption(null); } else if (shouldSendAfterStop) {
pauseRemainingQueue();
} }
return newQueue; return newQueue;
}); });
@@ -338,12 +387,20 @@ export default function ChatInput({
setLastInterruption(null); setLastInterruption(null);
// Keep the queue paused after sending the interruption message // Keep the queue paused after sending the interruption message
// User can manually resume if they want to continue with queued messages // User can manually resume if they want to continue with queued messages
queuePausedRef.current = true; pauseRemainingQueue();
} }
} }
} }
wasLoadingRef.current = isLoading; wasLoadingRef.current = isLoading;
}, [isLoading, queuedMessages, handleSubmit, lastInterruption]); }, [
isLoading,
queuedMessages,
handleSubmit,
lastInterruption,
clearPendingSendAfterStop,
clearQueueState,
pauseRemainingQueue,
]);
const [mentionPopover, setMentionPopover] = useState<{ const [mentionPopover, setMentionPopover] = useState<{
isOpen: boolean; isOpen: boolean;
position: { x: number; y: number }; position: { x: number; y: number };
@@ -934,7 +991,7 @@ export default function ChatInput({
if (interruptionMatch && interruptionMatch.shouldInterrupt) { if (interruptionMatch && interruptionMatch.shouldInterrupt) {
setLastInterruption(interruptionMatch.matchedText); setLastInterruption(interruptionMatch.matchedText);
if (onStop) onStop(); if (onStop) onStop();
queuePausedRef.current = true; pauseRemainingQueue();
// For interruptions, we need to queue the message to be sent after the stop completes // For interruptions, we need to queue the message to be sent after the stop completes
// rather than trying to send it immediately while the system is still loading // rather than trying to send it immediately while the system is still loading
@@ -1227,13 +1284,13 @@ export default function ChatInput({
// Queue management functions - no storage persistence, only in-memory // Queue management functions - no storage persistence, only in-memory
const handleRemoveQueuedMessage = (messageId: string) => { const handleRemoveQueuedMessage = (messageId: string) => {
clearPendingSendAfterStop(messageId);
setQueuedMessages((prev) => prev.filter((msg) => msg.id !== messageId)); setQueuedMessages((prev) => prev.filter((msg) => msg.id !== messageId));
}; };
const handleClearQueue = () => { const handleClearQueue = () => {
setQueuedMessages([]); setQueuedMessages([]);
queuePausedRef.current = false; clearQueueState();
setLastInterruption(null);
}; };
const handleReorderMessages = (reorderedMessages: QueuedMessage[]) => { const handleReorderMessages = (reorderedMessages: QueuedMessage[]) => {
@@ -1250,20 +1307,17 @@ export default function ChatInput({
const messageToSend = queuedMessages.find((msg) => msg.id === messageId); const messageToSend = queuedMessages.find((msg) => msg.id === messageId);
if (!messageToSend) return; if (!messageToSend) return;
// Stop current processing and temporarily pause queue to prevent double-send if (!isLoading) {
setQueuedMessages((prev) => removeQueuedMessage(prev, messageId));
LocalMessageStorage.addMessage(messageToSend.content);
handleSubmit({ msg: messageToSend.content, images: messageToSend.images });
return;
}
sendAfterStopMessageIdRef.current = messageId;
pauseRemainingQueue();
setQueuedMessages((prev) => moveQueuedMessageToFront(prev, messageId));
if (onStop) onStop(); if (onStop) onStop();
const wasPaused = queuePausedRef.current;
queuePausedRef.current = true;
// Remove the message from queue and send it immediately
setQueuedMessages((prev) => prev.filter((msg) => msg.id !== messageId));
LocalMessageStorage.addMessage(messageToSend.content);
handleSubmit({ msg: messageToSend.content, images: messageToSend.images });
// Restore previous pause state after a brief delay to prevent race condition
setTimeout(() => {
queuePausedRef.current = wasPaused;
}, 100);
}; };
const handleResumeQueue = () => { const handleResumeQueue = () => {