Skip to content

Commit f0378ed

Browse files
Clear Ask when you leave while it is still answering.
Leaving the page aborted the request but left the loading flag set, so coming back showed Typing forever and the composer stayed disabled. The partial answer is now kept and the composer is usable again. Co-authored-by: Gáborjáni-Szabó Ádám <Programcode@users.noreply.github.com>
1 parent 4f9e0a0 commit f0378ed

2 files changed

Lines changed: 49 additions & 2 deletions

File tree

‎src/pages/AskPage.tsx‎

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,38 @@ export default function AskPage() {
8484
setAskLoading(false);
8585
};
8686

87+
// Leaving the page aborts the request. The in-flight handler then sees a new
88+
// request id and will not clear the flags, so do it here or Ask comes back
89+
// stuck on Typing with the composer disabled. A partial answer is kept.
90+
const leaveCleanupRef = useRef({
91+
abortActiveRequest,
92+
addAskMessage,
93+
setAskLoading,
94+
setAskStreamingAssistant,
95+
stoppedNote: t('ask.stopped', 'You stopped this answer before it finished.'),
96+
});
97+
leaveCleanupRef.current = {
98+
abortActiveRequest,
99+
addAskMessage,
100+
setAskLoading,
101+
setAskStreamingAssistant,
102+
stoppedNote: t('ask.stopped', 'You stopped this answer before it finished.'),
103+
};
87104
useEffect(() => () => {
88-
abortActiveRequest();
89-
}, [abortActiveRequest]);
105+
const cleanup = leaveCleanupRef.current;
106+
const partial = useSessionStore.getState().askStreamingAssistant;
107+
cleanup.abortActiveRequest();
108+
cleanup.setAskLoading(false);
109+
cleanup.setAskStreamingAssistant(null);
110+
const kept = partial?.content.trim();
111+
if (!kept) return;
112+
cleanup.addAskMessage({
113+
id: partial?.id,
114+
role: 'assistant',
115+
content: kept,
116+
output: { response: kept, followUpQuestions: [], uncertainties: [cleanup.stoppedNote] },
117+
});
118+
}, []);
90119

91120
// A reply still in flight belongs to the scope it was asked in; a new debate
92121
// or one-off mode starts a fresh conversation, so drop the late answer.

‎src/test/pages/AskAccountSwitch.test.tsx‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,22 @@ describe('Ask across an account change', () => {
6161
expect(screen.queryByText(/Account A private answer/)).not.toBeInTheDocument();
6262
expect(useSessionStore.getState().askStreamingAssistant).toBeNull();
6363
});
64+
65+
it('does not stay on Typing after you leave mid-answer', async () => {
66+
mocks.userId = 'user-a';
67+
enableOneOffMode();
68+
useSessionStore.getState().clearAskMessages();
69+
const view = render(<MemoryRouter initialEntries={['/app/ask']}><AskPage /></MemoryRouter>);
70+
fireEvent.change(screen.getByPlaceholderText('Ask your debate coach...'), { target: { value: 'How do I weigh cost?' } });
71+
fireEvent.click(screen.getByRole('button', { name: 'Send message' }));
72+
await waitFor(() => expect(mocks.live).toBeDefined());
73+
act(() => mocks.live!.onDraft({ response: 'A partial answer' }));
74+
expect(useSessionStore.getState().askLoading).toBe(true);
75+
76+
view.unmount();
77+
78+
expect(useSessionStore.getState().askLoading).toBe(false);
79+
expect(useSessionStore.getState().askStreamingAssistant).toBeNull();
80+
expect(useSessionStore.getState().askMessages.map((message) => message.content)).toContain('A partial answer');
81+
});
6482
});

0 commit comments

Comments
 (0)