mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix(site/src/pages/AgentsPage): clear retry banner on stream forward progress (#23653)
When a provider request fails and retries, the "Retrying request" banner lingered in the UI after the retry succeeded. This happened because `retryState` was only cleared on explicit `status` events (`running`, `pending`, `waiting`), not when the stream resumed with `message_part` or `message` events. Since the backend does not publish a dedicated"retry resolved" event, the banner stayed visible for the entire duration of the successful response. Add `store.clearRetryState()` calls to the `message_part`, `message`, and `status` event handlers so the banner disappears as soon as content flows again. Closes https://github.com/coder/coder/issues/23624
This commit is contained in:
@@ -1828,6 +1828,92 @@ describe("useChatStore", () => {
|
||||
expect(result.current.retryState).toBeNull();
|
||||
});
|
||||
|
||||
it("clears retryState when message_part arrives after retry", async () => {
|
||||
immediateAnimationFrame();
|
||||
|
||||
const chatID = "chat-retry-message-part";
|
||||
const mockSocket = createMockSocket();
|
||||
mockWatchChatReturn(mockSocket);
|
||||
|
||||
const queryClient = createTestQueryClient();
|
||||
const wrapper = ({ children }: PropsWithChildren) => (
|
||||
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
||||
);
|
||||
const setChatErrorReason = vi.fn();
|
||||
const clearChatErrorReason = vi.fn();
|
||||
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const { store } = useChatStore({
|
||||
chatID,
|
||||
chatMessages: [],
|
||||
chatRecord: makeChat(chatID),
|
||||
chatMessagesData: {
|
||||
messages: [],
|
||||
queued_messages: [],
|
||||
has_more: false,
|
||||
},
|
||||
chatQueuedMessages: [],
|
||||
setChatErrorReason,
|
||||
clearChatErrorReason,
|
||||
});
|
||||
return {
|
||||
retryState: useChatSelector(store, selectRetryState),
|
||||
streamState: useChatSelector(store, selectStreamState),
|
||||
};
|
||||
},
|
||||
{ wrapper },
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(watchChat).toHaveBeenCalledWith(chatID, undefined);
|
||||
});
|
||||
|
||||
act(() => {
|
||||
mockSocket.emitData({
|
||||
type: "retry",
|
||||
chat_id: chatID,
|
||||
retry: {
|
||||
attempt: 1,
|
||||
error: "rate limited",
|
||||
kind: "rate_limit",
|
||||
provider: "anthropic",
|
||||
delay_ms: 3000,
|
||||
retrying_at: "2025-01-01T00:00:30.000Z",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.retryState).toEqual({
|
||||
attempt: 1,
|
||||
error: "rate limited",
|
||||
kind: "rate_limit",
|
||||
provider: "anthropic",
|
||||
delayMs: 3000,
|
||||
retryingAt: "2025-01-01T00:00:30.000Z",
|
||||
});
|
||||
});
|
||||
|
||||
act(() => {
|
||||
mockSocket.emitData({
|
||||
type: "message_part",
|
||||
chat_id: chatID,
|
||||
message_part: {
|
||||
role: "assistant",
|
||||
part: { type: "text", text: "retry recovered" },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.retryState).toBeNull();
|
||||
expect(result.current.streamState?.blocks).toEqual([
|
||||
{ type: "response", text: "retry recovered" },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
it("routes status events for other chatIDs to subagent overrides", async () => {
|
||||
immediateAnimationFrame();
|
||||
|
||||
|
||||
@@ -946,6 +946,7 @@ export const useChatStore = (
|
||||
}
|
||||
const part = streamEvent.message_part?.part;
|
||||
if (part) {
|
||||
store.clearRetryState();
|
||||
cancelScheduledStreamReset();
|
||||
partsBuf.push(part);
|
||||
}
|
||||
@@ -962,6 +963,7 @@ export const useChatStore = (
|
||||
if (streamEvent.chat_id && streamEvent.chat_id !== chatID) {
|
||||
continue;
|
||||
}
|
||||
store.clearRetryState();
|
||||
pendingMessages.push(message);
|
||||
if (
|
||||
message.id !== undefined &&
|
||||
@@ -997,6 +999,7 @@ export const useChatStore = (
|
||||
continue;
|
||||
}
|
||||
|
||||
store.clearRetryState();
|
||||
store.setChatStatus(nextStatus);
|
||||
if (nextStatus === "pending" || nextStatus === "waiting") {
|
||||
store.clearStreamState();
|
||||
|
||||
Reference in New Issue
Block a user