Fix queued send for inactive sessions (#513)

This commit is contained in:
Bohdan Triapitsyn
2026-02-26 00:29:13 +02:00
committed by GitHub
parent 9a2af4c4d9
commit 25ee06dae9
3 changed files with 195 additions and 43 deletions
+1 -43
View File
@@ -136,7 +136,6 @@ export const ChatInput: React.FC<ChatInputProps> = ({ onOpenSettings, scrollToBo
const acknowledgeSessionAbort = useSessionStore((state) => state.acknowledgeSessionAbort);
const abortPromptSessionId = useSessionStore((state) => state.abortPromptSessionId);
const clearAbortPrompt = useSessionStore((state) => state.clearAbortPrompt);
const sessionAbortFlags = useSessionStore((state) => state.sessionAbortFlags);
const attachedFiles = useSessionStore((state) => state.attachedFiles);
const addAttachedFile = useSessionStore((state) => state.addAttachedFile);
const addServerFile = useSessionStore((state) => state.addServerFile);
@@ -297,10 +296,8 @@ export const ChatInput: React.FC<ChatInputProps> = ({ onOpenSettings, scrollToBo
saveStoredDraft(currentSessionId, message);
}, [message, persistChatDraft, currentSessionId]);
// Session activity for auto-send on idle
// Session activity for queue availability and controls
const { phase: sessionPhase } = useCurrentSessionActivity();
const prevSessionPhaseRef = React.useRef(sessionPhase);
const autoSendTriggeredRef = React.useRef(false);
const handleTextareaPointerDownCapture = React.useCallback((event: React.PointerEvent<HTMLTextAreaElement>) => {
if (!isMobile) {
@@ -689,45 +686,6 @@ export const ChatInput: React.FC<ChatInputProps> = ({ onOpenSettings, scrollToBo
}
}, [inputMode, hasContent, currentSessionId, sessionPhase, queueModeEnabled, handleQueueMessage]);
// Auto-send queued messages when session becomes idle (but not after abort)
React.useEffect(() => {
const wasWorking = prevSessionPhaseRef.current === 'busy' || prevSessionPhaseRef.current === 'retry';
const isNowIdle = sessionPhase === 'idle';
// Check if session was recently aborted (within last 2 seconds)
const wasRecentlyAborted = currentSessionId && sessionAbortFlags.has(currentSessionId) && (() => {
const abortRecord = sessionAbortFlags.get(currentSessionId);
if (!abortRecord) return false;
const timeSinceAbort = Date.now() - abortRecord.timestamp;
return timeSinceAbort < 2000;
})();
// Detect transition from working to idle, but skip if aborted
if (wasWorking && isNowIdle && queuedMessages.length > 0 && !autoSendTriggeredRef.current && !wasRecentlyAborted) {
// Prevent double-triggering
autoSendTriggeredRef.current = true;
const targetSessionId = currentSessionId;
// Use setTimeout to avoid calling during render
setTimeout(() => {
const activeSessionId = useSessionStore.getState().currentSessionId;
const currentStatus = targetSessionId
? useSessionStore.getState().sessionStatus?.get(targetSessionId)
: null;
const stillIdle = currentStatus?.type === 'idle';
const sessionUnchanged = Boolean(targetSessionId) && activeSessionId === targetSessionId;
if (sessionUnchanged && stillIdle && targetSessionId && currentProviderId && currentModelId) {
void handleSubmitRef.current({ queuedOnly: true });
}
autoSendTriggeredRef.current = false;
}, 100);
}
prevSessionPhaseRef.current = sessionPhase;
}, [sessionPhase, queuedMessages.length, currentSessionId, currentProviderId, currentModelId, sessionAbortFlags]);
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
// Early return during IME composition to prevent interference with autocomplete.
// Uses keyCode === 229 fallback for WebKit where compositionend fires before keydown.