diff --git a/frontend-modern/src/components/AI/Chat/MessageItem.tsx b/frontend-modern/src/components/AI/Chat/MessageItem.tsx index 43e1b2c784..859a21e6d7 100644 --- a/frontend-modern/src/components/AI/Chat/MessageItem.tsx +++ b/frontend-modern/src/components/AI/Chat/MessageItem.tsx @@ -304,8 +304,21 @@ export const MessageItem: Component = (props) => { return !!evt.pendingTool; case 'tool_cancel': return !!evt.toolCancel; - case 'model_switch': - return !!evt.model?.trim(); + case 'model_switch': { + if (!evt.model?.trim()) return false; + if (!isSelectedModelRouteEvent(evt)) return true; + // A plain selected-route row repeats the route badge already shown in + // the turn header. Keep it only when it adds information: the header + // has no route, the route differs from the turn's final model, or a + // later switch makes the starting route part of the turn's story. + const hasRouteSwitch = (props.message.streamEvents || []).some( + (event) => + event.type === 'model_switch' && + !!event.model?.trim() && + !isSelectedModelRouteEvent(event), + ); + return hasRouteSwitch || !props.message.model || evt.model !== props.message.model; + } case 'approval': return !!evt.approval; case 'question': diff --git a/frontend-modern/src/components/AI/Chat/__tests__/MessageItem.test.tsx b/frontend-modern/src/components/AI/Chat/__tests__/MessageItem.test.tsx index 9208e15929..f36280b878 100644 --- a/frontend-modern/src/components/AI/Chat/__tests__/MessageItem.test.tsx +++ b/frontend-modern/src/components/AI/Chat/__tests__/MessageItem.test.tsx @@ -1170,6 +1170,70 @@ describe('MessageItem', () => { ).toHaveTextContent('Using Qwen 3.7 Plus via OpenRouter'); }); + it('hides the selected-route row when the turn header badge already shows the same route', () => { + const events: StreamDisplayEvent[] = [ + { + type: 'model_switch', + model: 'openrouter:qwen/qwen3.7-plus', + modelEvent: 'selected', + }, + ]; + + render(() => ( + + model === 'openrouter:qwen/qwen3.7-plus' ? 'Qwen 3.7 Plus via OpenRouter' : model + } + {...makeHandlers()} + /> + )); + + expect( + screen.queryByRole('status', { name: 'Assistant model route selected' }), + ).not.toBeInTheDocument(); + // The route is still visible exactly once, in the header badge. + expect(screen.getByText('Qwen 3.7 Plus via OpenRouter')).toBeInTheDocument(); + }); + + it('keeps the selected-route row when a later switch makes it part of the turn story', () => { + const events: StreamDisplayEvent[] = [ + { + type: 'model_switch', + model: 'openrouter:qwen/qwen3.7-plus', + modelEvent: 'selected', + }, + { + type: 'model_switch', + failedModel: 'openrouter:qwen/qwen3.7-plus', + model: 'gemini:gemini-3.1-flash-lite', + }, + ]; + + render(() => ( + model} + {...makeHandlers()} + /> + )); + + expect( + screen.getByRole('status', { name: 'Assistant model route selected' }), + ).toBeInTheDocument(); + expect( + screen.getByRole('status', { name: 'Assistant model route changed' }), + ).toBeInTheDocument(); + }); + it('does not render request-start progress as a transcript row next to selected model route evidence (footer-owned)', () => { const events: StreamDisplayEvent[] = [ {