fix: queued message lost when clicking sending now (#9074)
This commit is contained in:
@@ -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 = () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user