From 0cb2270ba0234754fd156256ff80191cbff835bd Mon Sep 17 00:00:00 2001 From: Marco Beretta <81851188+berry-13@users.noreply.github.com> Date: Fri, 24 Jul 2026 19:12:06 +0200 Subject: [PATCH] fix(client): handle singular grouped tool labels --- .../Chat/Messages/Content/ToolCallGroup.tsx | 11 +++- .../Content/__tests__/ToolCallGroup.test.tsx | 54 +++++++++++++++++++ client/src/locales/en/translation.json | 2 + 3 files changed, 65 insertions(+), 2 deletions(-) diff --git a/client/src/components/Chat/Messages/Content/ToolCallGroup.tsx b/client/src/components/Chat/Messages/Content/ToolCallGroup.tsx index 88bf8a90d1..af6c78925f 100644 --- a/client/src/components/Chat/Messages/Content/ToolCallGroup.tsx +++ b/client/src/components/Chat/Messages/Content/ToolCallGroup.tsx @@ -191,8 +191,7 @@ export default function ToolCallGroup({ /** `ask_user_question` calls form their own category, mirroring subagents: * a homogeneous group reads "Asking/Asked N questions" (never "Used N - * tools — ask_user_question") with a question glyph. A group only exists - * at count >= 2, so the plural is always grammatical. */ + * tools — ask_user_question") with a question glyph. */ const askQuestionCount = useMemo( () => toolNames.filter((n) => n === ASK_USER_QUESTION).length, [toolNames], @@ -327,11 +326,19 @@ export default function ToolCallGroup({ * category (with tense), everything else is the generic "Used N tools". */ const resolveGroupLabel = (): string => { if (allSubagents) { + if (count === 1) { + return localize(subagentsDone ? 'com_ui_subagent_complete' : 'com_ui_subagent_running'); + } return subagentsDone ? localize('com_ui_ran_n_agents', { 0: String(count) }) : localize('com_ui_running_n_agents', { 0: String(count) }); } if (allAskQuestions) { + if (count === 1) { + return localize( + askQuestionsDone ? 'com_ui_asked_one_question' : 'com_ui_asking_one_question', + ); + } return askQuestionsDone ? localize('com_ui_asked_n_questions', { 0: String(count) }) : localize('com_ui_asking_n_questions', { 0: String(count) }); diff --git a/client/src/components/Chat/Messages/Content/__tests__/ToolCallGroup.test.tsx b/client/src/components/Chat/Messages/Content/__tests__/ToolCallGroup.test.tsx index 147988a6b2..67feb873c8 100644 --- a/client/src/components/Chat/Messages/Content/__tests__/ToolCallGroup.test.tsx +++ b/client/src/components/Chat/Messages/Content/__tests__/ToolCallGroup.test.tsx @@ -17,6 +17,18 @@ jest.mock('~/hooks', () => ({ if (key === 'com_ui_asking_n_questions') { return `Asking ${values?.[0]} questions`; } + if (key === 'com_ui_asked_one_question') { + return 'Asked 1 question'; + } + if (key === 'com_ui_asking_one_question') { + return 'Asking 1 question'; + } + if (key === 'com_ui_subagent_complete') { + return 'Ran agent'; + } + if (key === 'com_ui_subagent_running') { + return 'Running agent'; + } if (key === 'com_ui_via_server') { return `via ${values?.[0]}`; } @@ -607,6 +619,48 @@ describe('ToolCallGroup image hoisting', () => { expect(screen.getByRole('button', { name: 'Asking 2 questions' })).toBeInTheDocument(); }); + it('uses a singular completed label for one question grouped with reasoning', () => { + renderGroup({ + ...baseProps, + parts: [{ part: makePart('q1', 'blue', 'ask_user_question'), idx: 0 }], + lastContentIdx: 0, + }); + + expect(screen.getByRole('button', { name: 'Asked 1 question' })).toBeInTheDocument(); + }); + + it('uses a singular active label for one question grouped with reasoning', () => { + renderGroup({ + ...baseProps, + isSubmitting: true, + parts: [{ part: makePart('q1', '', 'ask_user_question'), idx: 0 }], + lastContentIdx: 0, + }); + + expect(screen.getByRole('button', { name: 'Asking 1 question' })).toBeInTheDocument(); + }); + + it('uses a singular completed label for one subagent grouped with reasoning', () => { + renderGroup({ + ...baseProps, + parts: [{ part: makePart('a1', 'done', Constants.SUBAGENT), idx: 0 }], + lastContentIdx: 0, + }); + + expect(screen.getByRole('button', { name: 'Ran agent' })).toBeInTheDocument(); + }); + + it('uses a singular active label for one subagent grouped with reasoning', () => { + renderGroup({ + ...baseProps, + isSubmitting: true, + parts: [{ part: makePart('a1', '', Constants.SUBAGENT), idx: 0 }], + lastContentIdx: 0, + }); + + expect(screen.getByRole('button', { name: 'Running agent' })).toBeInTheDocument(); + }); + it('keeps the generic "Used N tools" label for a mixed group containing a question', () => { renderGroup({ ...baseProps, diff --git a/client/src/locales/en/translation.json b/client/src/locales/en/translation.json index 667f993a4a..9ae259db4a 100644 --- a/client/src/locales/en/translation.json +++ b/client/src/locales/en/translation.json @@ -920,8 +920,10 @@ "com_ui_ask_type_below_only": "Type your answer below", "com_ui_ask_user": "Ask User", "com_ui_asked": "Asked", + "com_ui_asked_one_question": "Asked 1 question", "com_ui_asked_n_questions": "Asked {{0}} questions", "com_ui_asking": "Asking", + "com_ui_asking_one_question": "Asking 1 question", "com_ui_asking_n_questions": "Asking {{0}} questions", "com_ui_assistant": "Assistant", "com_ui_assistant_delete_error": "There was an error deleting the assistant",