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;
}
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;
// Constants for token and tool alerts
@@ -220,8 +232,25 @@ export default function ChatInput({
const [queuedMessages, setQueuedMessages] = useState<QueuedMessage[]>([]);
const queuePausedRef = useRef(false);
const editingMessageIdRef = useRef<string | null>(null);
const sendAfterStopMessageIdRef = useRef<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 dropdownRef: React.RefObject<HTMLDivElement> = useRef<HTMLDivElement>(
null
@@ -315,20 +344,40 @@ export default function ChatInput({
// Queue processing
useEffect(() => {
if (wasLoadingRef.current && !isLoading && queuedMessages.length > 0) {
// After an interruption, we should process the interruption message immediately
// The queue is only truly paused if there was an interruption AND we want to keep it paused
const shouldProcessQueue = !queuePausedRef.current || lastInterruption;
const pendingSendAfterStopId = sendAfterStopMessageIdRef.current;
const messageToSend = pendingSendAfterStopId
? 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) {
const nextMessage = queuedMessages[0];
LocalMessageStorage.addMessage(nextMessage.content);
handleSubmit({ msg: nextMessage.content, images: nextMessage.images });
LocalMessageStorage.addMessage(messageToSend.content);
handleSubmit({ msg: messageToSend.content, images: messageToSend.images });
if (shouldSendAfterStop) {
clearPendingSendAfterStop(messageToSend.id);
}
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 (newQueue.length === 0) {
queuePausedRef.current = false;
setLastInterruption(null);
clearQueueState();
} else if (shouldSendAfterStop) {
pauseRemainingQueue();
}
return newQueue;
});
@@ -338,12 +387,20 @@ export default function ChatInput({
setLastInterruption(null);
// Keep the queue paused after sending the interruption message
// User can manually resume if they want to continue with queued messages
queuePausedRef.current = true;
pauseRemainingQueue();
}
}
}
wasLoadingRef.current = isLoading;
}, [isLoading, queuedMessages, handleSubmit, lastInterruption]);
}, [
isLoading,
queuedMessages,
handleSubmit,
lastInterruption,
clearPendingSendAfterStop,
clearQueueState,
pauseRemainingQueue,
]);
const [mentionPopover, setMentionPopover] = useState<{
isOpen: boolean;
position: { x: number; y: number };
@@ -934,7 +991,7 @@ export default function ChatInput({
if (interruptionMatch && interruptionMatch.shouldInterrupt) {
setLastInterruption(interruptionMatch.matchedText);
if (onStop) onStop();
queuePausedRef.current = true;
pauseRemainingQueue();
// 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
@@ -1227,13 +1284,13 @@ export default function ChatInput({
// Queue management functions - no storage persistence, only in-memory
const handleRemoveQueuedMessage = (messageId: string) => {
clearPendingSendAfterStop(messageId);
setQueuedMessages((prev) => prev.filter((msg) => msg.id !== messageId));
};
const handleClearQueue = () => {
setQueuedMessages([]);
queuePausedRef.current = false;
setLastInterruption(null);
clearQueueState();
};
const handleReorderMessages = (reorderedMessages: QueuedMessage[]) => {
@@ -1250,20 +1307,17 @@ export default function ChatInput({
const messageToSend = queuedMessages.find((msg) => msg.id === messageId);
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();
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 = () => {