From b721e7a32d6bd3e0a918eabe4ec9b3d43daa4c9d Mon Sep 17 00:00:00 2001 From: yyh <92089059+lyzno1@users.noreply.github.com> Date: Wed, 15 Jul 2026 16:51:21 +0800 Subject: [PATCH] fix(web): align agent activity disclosure (#38999) --- .../__tests__/hooks.spec.tsx | 9 + .../base/chat/chat-with-history/hooks.tsx | 13 + .../agent-roster-response-content.spec.tsx | 496 +++++++++++++++-- .../chat/chat/answer/__tests__/more.spec.tsx | 7 + .../chat/answer/__tests__/operation.spec.tsx | 116 +++- .../answer/agent-roster-response-content.tsx | 510 ++++++++---------- .../components/base/chat/chat/answer/more.tsx | 16 +- .../base/chat/chat/answer/operation.tsx | 50 +- .../preview/__tests__/chat.spec.tsx | 53 ++ .../components/preview/chat-runtime.tsx | 21 + web/i18n/ar-TN/agent-v-2.json | 3 + web/i18n/de-DE/agent-v-2.json | 3 + web/i18n/en-US/agent-v-2.json | 3 + web/i18n/es-ES/agent-v-2.json | 3 + web/i18n/fa-IR/agent-v-2.json | 3 + web/i18n/fr-FR/agent-v-2.json | 3 + web/i18n/hi-IN/agent-v-2.json | 3 + web/i18n/id-ID/agent-v-2.json | 3 + web/i18n/it-IT/agent-v-2.json | 3 + web/i18n/ja-JP/agent-v-2.json | 3 + web/i18n/ko-KR/agent-v-2.json | 3 + web/i18n/nl-NL/agent-v-2.json | 3 + web/i18n/pl-PL/agent-v-2.json | 3 + web/i18n/pt-BR/agent-v-2.json | 3 + web/i18n/ro-RO/agent-v-2.json | 3 + web/i18n/ru-RU/agent-v-2.json | 3 + web/i18n/sl-SI/agent-v-2.json | 3 + web/i18n/th-TH/agent-v-2.json | 3 + web/i18n/tr-TR/agent-v-2.json | 3 + web/i18n/uk-UA/agent-v-2.json | 3 + web/i18n/vi-VN/agent-v-2.json | 3 + web/i18n/zh-Hans/agent-v-2.json | 3 + web/i18n/zh-Hant/agent-v-2.json | 3 + 33 files changed, 1007 insertions(+), 353 deletions(-) diff --git a/web/app/components/base/chat/chat-with-history/__tests__/hooks.spec.tsx b/web/app/components/base/chat/chat-with-history/__tests__/hooks.spec.tsx index e84f05ddbff..bd3b8dccdac 100644 --- a/web/app/components/base/chat/chat-with-history/__tests__/hooks.spec.tsx +++ b/web/app/components/base/chat/chat-with-history/__tests__/hooks.spec.tsx @@ -1870,6 +1870,9 @@ describe('useChatWithHistory', () => { id: 'msg-files', query: 'Question with files', answer: 'Answer with files', + answer_tokens: 10, + message_tokens: 5, + provider_response_latency: 66, message_files: [ { id: 'file-user-1', @@ -1967,6 +1970,12 @@ describe('useChatWithHistory', () => { expect(messageWithFiles?.children?.[0]?.agent_thoughts?.[0]?.message_files).toHaveLength(1) const normalAnswerNode = messageWithFiles?.children?.[0] + expect(normalAnswerNode?.more).toEqual({ + time: '', + tokens: 15, + latency: '66.00', + tokens_per_second: '0.15', + }) const pausedAnswerNode = result!.current.appPrevChatTree.find( (item) => item.id === 'question-msg-paused-branch', )?.children?.[0] diff --git a/web/app/components/base/chat/chat-with-history/hooks.tsx b/web/app/components/base/chat/chat-with-history/hooks.tsx index bf0bdff7413..c37f477d143 100644 --- a/web/app/components/base/chat/chat-with-history/hooks.tsx +++ b/web/app/components/base/chat/chat-with-history/hooks.tsx @@ -61,6 +61,18 @@ function getFormattedChatList(messages: any[]) { }) const answerFiles = item.message_files?.filter((file: any) => file.belongs_to === 'assistant') || [] + const answerTokens = item.answer_tokens ?? 0 + const messageTokens = item.message_tokens ?? 0 + const latency = Number(item.provider_response_latency) + const more = + item.provider_response_latency == null || !Number.isFinite(latency) + ? undefined + : { + time: '', + tokens: answerTokens + messageTokens, + latency: latency.toFixed(2), + tokens_per_second: latency > 0 ? (answerTokens / latency).toFixed(2) : undefined, + } const humanInputFormDataList: HumanInputFormData[] = [] const humanInputFilledFormDataList: HumanInputFilledFormData[] = [] let workflowRunId = '' @@ -111,6 +123,7 @@ function getFormattedChatList(messages: any[]) { humanInputFormDataList, humanInputFilledFormDataList, workflow_run_id: workflowRunId, + more, }) }) return newChatList diff --git a/web/app/components/base/chat/chat/answer/__tests__/agent-roster-response-content.spec.tsx b/web/app/components/base/chat/chat/answer/__tests__/agent-roster-response-content.spec.tsx index 859127055ac..4c77e735383 100644 --- a/web/app/components/base/chat/chat/answer/__tests__/agent-roster-response-content.spec.tsx +++ b/web/app/components/base/chat/chat/answer/__tests__/agent-roster-response-content.spec.tsx @@ -1,5 +1,6 @@ import type { ChatItem } from '../../../types' -import { fireEvent, render, screen, waitFor } from '@testing-library/react' +import { render, screen, waitFor } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import { vi } from 'vitest' import { AgentRosterResponseContent } from '../agent-roster-response-content' @@ -9,13 +10,59 @@ vi.mock('react-i18next', async () => { return { ...actual, ...createReactI18nextMock({ - 'common.chat.thought': 'Thought', + 'agentV2.agentDetail.configure.answer.thinking': 'Thinking', + 'agentV2.agentDetail.configure.answer.duration.minute': '{{count}}m', + 'agentV2.agentDetail.configure.answer.duration.second': '{{count}}s', + 'agentV2.agentDetail.configure.answer.activity.ranCommands': 'Ran commands', + 'agentV2.agentDetail.configure.answer.activity.runningCommands': 'Running commands', }), } }) +const thinkingOnlyItem = { + id: 'answer-thinking-only', + content: '', + isAnswer: true, + agent_response_parts: [ + { + type: 'thought', + thought: { + id: 'thought-thinking-only', + thought: 'internal thought should not render', + tool: '', + tool_input: '', + observation: '', + message_id: 'answer-thinking-only', + conversation_id: 'conversation-thinking-only', + position: 1, + }, + }, + ], +} satisfies ChatItem + describe('AgentRosterResponseContent', () => { + it('should keep the live thinking status before visible activity arrives', () => { + render() + + expect(screen.getByRole('button', { name: /Thinking/ })).toHaveAttribute( + 'aria-expanded', + 'true', + ) + expect(screen.queryByText('internal thought should not render')).not.toBeInTheDocument() + }) + + it('should keep a stopped thinking status without exposing internal thought', () => { + render() + + expect(screen.getByRole('button', { name: 'Thinking' })).toHaveAttribute( + 'aria-expanded', + 'false', + ) + expect(screen.queryByText('internal thought should not render')).not.toBeInTheDocument() + }) + it('should render historical agent thought answer as markdown instead of thought process', async () => { + const user = userEvent.setup() const item = { id: 'answer-history', content: '', @@ -37,55 +84,73 @@ describe('AgentRosterResponseContent', () => { render() - expect(screen.getByRole('button', { name: /workFinished/i })).toBeInTheDocument() + const processToggle = screen.getByRole('button', { name: 'Thinking' }) + expect(processToggle).toHaveAttribute('aria-expanded', 'false') expect(screen.queryByText('history answer')).not.toBeInTheDocument() - fireEvent.click(screen.getByRole('button', { name: /workFinished/i })) + await user.click(processToggle) + expect(processToggle).toHaveAttribute('aria-expanded', 'true') await waitFor(() => { - expect(screen.getByTestId('agent-roster-response-content')).toHaveTextContent( - 'history answer', - ) + expect(screen.getByText('history answer')).toBeInTheDocument() }) expect(screen.queryByText('internal thought should not render')).not.toBeInTheDocument() }) - it('should render new agent response parts in event order when thoughts and messages interleave', async () => { + it('should preserve historical answer whitespace when rendering markdown', async () => { + const user = userEvent.setup() + const item = { + id: 'answer-history-code', + content: '', + isAnswer: true, + agent_thoughts: [ + { + id: 'thought-history-code', + thought: 'internal thought should not render', + answer: ' const answer = 42', + tool: '', + tool_input: '', + observation: '', + message_id: 'answer-history-code', + conversation_id: 'conversation-history-code', + position: 1, + }, + ], + } satisfies ChatItem + + render() + await user.click(screen.getByRole('button', { name: 'Thinking' })) + + await waitFor(() => { + expect(screen.getByText('const answer = 42').tagName).toBe('CODE') + }) + }) + + it('should keep one collapsible thinking timeline while response parts interleave', async () => { + const user = userEvent.setup() const item = { id: 'answer-1', content: 'first answer second answer', isAnswer: true, - agent_thoughts: [ - { - id: 'thought-1', - thought: 'first thought', - tool: '', - tool_input: '', - observation: '', - message_id: 'answer-1', - conversation_id: 'conversation-1', - position: 1, - }, - { - id: 'thought-2', - thought: 'second thought', - tool: '', - tool_input: '', - observation: '', - message_id: 'answer-1', - conversation_id: 'conversation-1', - position: 2, - }, - ], agent_response_parts: [ { type: 'thought', thought: { id: 'thought-1', - thought: 'first thought', - tool: '', + thought: 'raw first thought', + tool: 'load_tools', tool_input: '', + tool_labels: { + load_tools: { + en_US: 'Loaded tools', + zh_Hans: '已加载工具', + }, + shell_run: { + en_US: 'Ran commands', + zh_Hans: '运行了命令', + }, + }, observation: '', message_id: 'answer-1', conversation_id: 'conversation-1', @@ -100,9 +165,19 @@ describe('AgentRosterResponseContent', () => { type: 'thought', thought: { id: 'thought-2', - thought: 'second thought', - tool: '', + thought: 'raw second thought', + tool: 'shell_run', tool_input: '', + tool_labels: { + load_tools: { + en_US: 'Loaded tools', + zh_Hans: '已加载工具', + }, + shell_run: { + en_US: 'Ran commands', + zh_Hans: '运行了命令', + }, + }, observation: '', message_id: 'answer-1', conversation_id: 'conversation-1', @@ -118,17 +193,28 @@ describe('AgentRosterResponseContent', () => { render() + const processToggle = screen.getByRole('button', { name: /Thinking/ }) + expect(processToggle).toHaveAttribute('aria-expanded', 'true') + await waitFor(() => { expect(screen.getByTestId('agent-roster-response-content')).toHaveTextContent('second answer') }) const content = screen.getByTestId('agent-roster-response-content').textContent ?? '' - expect(content.indexOf('first thought')).toBeLessThan(content.indexOf('first answer')) - expect(content.indexOf('first answer')).toBeLessThan(content.indexOf('second thought')) - expect(content.indexOf('second thought')).toBeLessThan(content.indexOf('second answer')) + expect(content.indexOf('Loaded tools')).toBeLessThan(content.indexOf('first answer')) + expect(content.indexOf('first answer')).toBeLessThan(content.indexOf('Ran commands')) + expect(content.indexOf('Ran commands')).toBeLessThan(content.indexOf('second answer')) + expect(screen.queryByText('raw first thought')).not.toBeInTheDocument() + expect(screen.queryByText('raw second thought')).not.toBeInTheDocument() + + await user.click(processToggle) + + expect(processToggle).toHaveAttribute('aria-expanded', 'false') + expect(screen.queryByText('Loaded tools')).not.toBeInTheDocument() }) - it('should show THOUGHT as the header when a thought process is expanded', () => { + it('should keep activity labels stable when their details are expanded', async () => { + const user = userEvent.setup() const item = { id: 'answer-expanded-thought', content: '', @@ -136,10 +222,16 @@ describe('AgentRosterResponseContent', () => { agent_thoughts: [ { id: 'thought-expanded', - thought: 'visible thought summary', - tool: '', - tool_input: '', - observation: '', + thought: '', + tool: 'shell_run', + tool_input: '{"command":"ls"}', + tool_labels: { + shell_run: { + en_US: 'Ran commands', + zh_Hans: '运行了命令', + }, + }, + observation: 'README.md', message_id: 'answer-expanded-thought', conversation_id: 'conversation-expanded-thought', position: 1, @@ -147,8 +239,326 @@ describe('AgentRosterResponseContent', () => { ], } satisfies ChatItem + render() + + await user.click(screen.getByRole('button', { name: 'Thinking' })) + + const activityToggle = screen.getByRole('button', { name: 'Ran commands' }) + expect(activityToggle).toHaveAttribute('aria-expanded', 'false') + + await user.click(activityToggle) + + expect(activityToggle).toHaveAttribute('aria-expanded', 'true') + expect(screen.getByRole('button', { name: 'Ran commands' })).toBe(activityToggle) + expect(screen.getByText('{"command":"ls"}')).toBeInTheDocument() + }) + + it('should use the shell activity fallback only when no descriptive label is available', async () => { + const user = userEvent.setup() + const item = { + id: 'answer-shell-labels', + content: '', + isAnswer: true, + agent_thoughts: [ + { + id: 'thought-shell-fallback', + thought: '', + tool: 'shell_run', + tool_input: 'pwd', + observation: '/workspace', + message_id: 'answer-shell-labels', + conversation_id: 'conversation-shell-labels', + position: 1, + }, + { + id: 'thought-shell-described', + thought: '', + tool: 'shell_run', + tool_input: 'mkdir skill', + tool_labels: { + shell_run: { + en_US: 'Scaffold skill directory', + zh_Hans: '创建技能目录', + }, + }, + observation: '', + message_id: 'answer-shell-labels', + conversation_id: 'conversation-shell-labels', + position: 2, + }, + ], + } satisfies ChatItem + + render() + + await user.click(screen.getByRole('button', { name: 'Thinking' })) + + expect(screen.getByRole('button', { name: 'Ran commands' })).toBeInTheDocument() + expect(screen.getByRole('button', { name: 'Scaffold skill directory' })).toBeInTheDocument() + }) + + it('should describe an unfinished shell run as running commands', () => { + const item = { + id: 'answer-running-shell', + content: '', + isAnswer: true, + agent_response_parts: [ + { + type: 'thought', + thought: { + id: 'thought-running-shell', + thought: '', + tool: 'shell_run', + tool_input: 'pnpm test', + observation: '', + message_id: 'answer-running-shell', + conversation_id: 'conversation-running-shell', + position: 1, + }, + }, + ], + } satisfies ChatItem + render() - expect(screen.getByRole('button', { name: 'THOUGHT' })).toBeInTheDocument() + expect(screen.getByRole('button', { name: 'Running commands' })).toBeInTheDocument() + }) + + it('should keep the live activity disclosure open when public messages arrive and completion starts', () => { + const thought = { + id: 'thought-transition', + thought: 'raw thinking', + tool: 'shell_run', + tool_input: '{"command":"ls"}', + tool_labels: { + shell_run: { + en_US: 'Ran commands', + zh_Hans: '运行了命令', + }, + }, + observation: 'README.md', + message_id: 'answer-transition', + conversation_id: 'conversation-transition', + position: 1, + } + const item = { + id: 'answer-transition', + content: '', + isAnswer: true, + agent_response_parts: [ + { + type: 'thought', + thought, + }, + ], + } satisfies ChatItem + + const { rerender } = render() + + expect(screen.getByRole('button', { name: /Thinking/ })).toHaveAttribute( + 'aria-expanded', + 'true', + ) + + const itemWithMessage = { + ...item, + agent_response_parts: [ + ...item.agent_response_parts, + { + type: 'message', + content: 'public answer', + }, + ], + } satisfies ChatItem + + rerender() + + expect(screen.getByRole('button', { name: /Thinking/ })).toHaveAttribute( + 'aria-expanded', + 'true', + ) + expect(screen.getByText('public answer')).toBeInTheDocument() + + rerender() + + expect(screen.getByRole('button', { name: /Thinking/ })).toHaveAttribute( + 'aria-expanded', + 'true', + ) + + rerender( + , + ) + + expect(screen.getByRole('button', { name: 'Thinking' })).toHaveAttribute( + 'aria-expanded', + 'false', + ) + expect(screen.getByText('final answer')).toBeInTheDocument() + expect(screen.queryByRole('button', { name: 'Ran commands' })).not.toBeInTheDocument() + }) + + it('should preserve a manual live activity collapse as new events arrive', async () => { + const user = userEvent.setup() + const item = { + id: 'answer-manual-collapse', + content: '', + isAnswer: true, + agent_response_parts: [ + { + type: 'thought', + thought: { + id: 'thought-manual-collapse', + thought: '', + tool: 'shell_run', + tool_input: 'pwd', + observation: '/workspace', + message_id: 'answer-manual-collapse', + conversation_id: 'conversation-manual-collapse', + position: 1, + }, + }, + ], + } satisfies ChatItem + + const { rerender } = render() + const processToggle = screen.getByRole('button', { name: /Thinking/ }) + + await user.click(processToggle) + expect(processToggle).toHaveAttribute('aria-expanded', 'false') + + rerender( + , + ) + + expect(screen.getByRole('button', { name: /Thinking/ })).toHaveAttribute( + 'aria-expanded', + 'false', + ) + expect(screen.queryByText('new progress update')).not.toBeInTheDocument() + }) + + it('should keep a historical answer and its activity in the same process entry', async () => { + const user = userEvent.setup() + const item = { + id: 'answer-history-with-activity', + content: 'final answer', + isAnswer: true, + agent_thoughts: [ + { + id: 'thought-history-with-activity', + thought: 'internal thought should not render', + answer: 'public progress update', + tool: 'shell_run', + tool_input: 'pwd', + observation: '/workspace', + message_id: 'answer-history-with-activity', + conversation_id: 'conversation-history-with-activity', + position: 1, + }, + ], + } satisfies ChatItem + + render() + + await user.click(screen.getByRole('button', { name: 'Thinking' })) + + expect(screen.getByText('public progress update')).toBeInTheDocument() + expect(screen.getByRole('button', { name: 'Ran commands' })).toBeInTheDocument() + expect(screen.getByText('final answer')).toBeInTheDocument() + expect(screen.queryByText('internal thought should not render')).not.toBeInTheDocument() + }) + + it('should render a live message directly when there is no activity', () => { + const item = { + id: 'answer-live-message-only', + content: '', + isAnswer: true, + agent_response_parts: [{ type: 'message', content: 'direct answer' }], + } satisfies ChatItem + + render() + + expect(screen.queryByRole('button', { name: /Thinking/ })).not.toBeInTheDocument() + expect(screen.getByText('direct answer')).toBeInTheDocument() + }) + + it('should omit the activity disclosure for a completed response without activity', () => { + const item = { + id: 'answer-without-activity', + content: 'final answer', + isAnswer: true, + } satisfies ChatItem + + render() + + expect(screen.queryByRole('button', { name: /Thinking/ })).not.toBeInTheDocument() + expect(screen.getByText('final answer')).toBeInTheDocument() + }) + + it('should keep the completed thinking label and render the final answer outside it', async () => { + const user = userEvent.setup() + const item = { + id: 'answer-complete', + content: 'final answer', + isAnswer: true, + more: { + time: '', + tokens: 0, + latency: 66, + }, + agent_thoughts: [ + { + id: 'thought-complete', + thought: 'raw process detail', + tool: 'shell_run', + tool_input: '{"command":"ls"}', + tool_labels: { + shell_run: { + en_US: 'Ran commands', + zh_Hans: '运行了命令', + }, + }, + observation: 'README.md', + message_id: 'answer-complete', + conversation_id: 'conversation-complete', + position: 1, + }, + ], + } satisfies ChatItem + + render() + + const processToggle = screen.getByRole('button', { + name: 'Thinking · {{count}}m{{count}}s', + }) + expect(processToggle).toHaveAttribute('aria-expanded', 'false') + expect(screen.queryByText('raw process detail')).not.toBeInTheDocument() + expect(screen.queryByRole('button', { name: 'Ran commands' })).not.toBeInTheDocument() + expect(screen.getByText('final answer')).toBeInTheDocument() + + await user.click(processToggle) + + expect(processToggle).toHaveAttribute('aria-expanded', 'true') + expect(screen.getByRole('button', { name: 'Ran commands' })).toBeInTheDocument() + expect(screen.getByText('final answer')).toBeInTheDocument() }) }) diff --git a/web/app/components/base/chat/chat/answer/__tests__/more.spec.tsx b/web/app/components/base/chat/chat/answer/__tests__/more.spec.tsx index 6ff162f09f6..abec4435fa9 100644 --- a/web/app/components/base/chat/chat/answer/__tests__/more.spec.tsx +++ b/web/app/components/base/chat/chat/answer/__tests__/more.spec.tsx @@ -41,6 +41,13 @@ describe('More', () => { expect(screen.queryByTestId('more-tps')).not.toBeInTheDocument() }) + it('should omit the timestamp separator when time is empty', () => { + render() + + expect(screen.queryByTestId('more-time')).not.toBeInTheDocument() + expect(screen.queryByText('·')).not.toBeInTheDocument() + }) + it('should render nothing inside container if more prop is missing', () => { render() const containerDiv = screen.getByTestId('more-container') diff --git a/web/app/components/base/chat/chat/answer/__tests__/operation.spec.tsx b/web/app/components/base/chat/chat/answer/__tests__/operation.spec.tsx index e9d9fd4d723..32d338ae24f 100644 --- a/web/app/components/base/chat/chat/answer/__tests__/operation.spec.tsx +++ b/web/app/components/base/chat/chat/answer/__tests__/operation.spec.tsx @@ -26,7 +26,7 @@ const { mockSetShowAnnotationFullModal, mockProviderContext, mockT, mockAddAnnot vi.mock('copy-to-clipboard', () => ({ default: vi.fn() })) vi.mock('@langgenius/dify-ui/toast', () => ({ - default: { notify: vi.fn() }, + toast: { success: vi.fn() }, })) vi.mock('@/context/modal-context', () => ({ @@ -125,7 +125,11 @@ vi.mock( ) vi.mock('@/app/components/base/new-audio-button', () => ({ - default: () => , + default: ({ value }: { value: string }) => ( + + ), })) vi.mock('@/app/components/base/chat/chat/log', () => ({ @@ -209,6 +213,29 @@ const baseItem: ChatItem = { isAnswer: true, } +const createInterruptedItem = (messages: string[]): ChatItem => { + const thought = { + id: '1', + thought: 'internal thought should not be used', + tool: '', + tool_input: '', + observation: '', + message_id: '', + conversation_id: '', + position: 0, + } + + return { + ...baseItem, + content: '', + agent_response_parts: [ + { type: 'thought', thought }, + ...messages.map((content) => ({ type: 'message' as const, content })), + ], + agent_thoughts: [thought], + } +} + const baseProps: OperationProps = { item: baseItem, question: 'What is this?', @@ -279,6 +306,26 @@ describe('Operation', () => { expect(screen.getByTestId('annotation-ctrl'))!.toBeInTheDocument() }) + it('should hide content-dependent actions for an interrupted response without public content', () => { + mockContextValue.config = makeChatConfig({ + text_to_speech: { enabled: true }, + supportAnnotation: true, + annotation_reply: { + id: 'ar-1', + score_threshold: 0.5, + embedding_model: { embedding_provider_name: '', embedding_model_name: '' }, + enabled: true, + }, + }) + + renderOperation({ ...baseProps, item: createInterruptedItem([]) }) + + expect(screen.queryByTestId('audio-btn')).not.toBeInTheDocument() + expect(screen.queryByRole('button', { name: 'operation.copy' })).not.toBeInTheDocument() + expect(screen.queryByTestId('annotation-ctrl')).not.toBeInTheDocument() + expect(screen.getByRole('button', { name: 'operation.regenerate' })).toBeInTheDocument() + }) + it('should hide annotation button when chat is readonly', () => { mockContextValue.readonly = true mockContextValue.config = makeChatConfig({ @@ -344,11 +391,11 @@ describe('Operation', () => { expect(copy).toHaveBeenCalledWith('Hello world') }) - it('should aggregate agent_thoughts for copy content', async () => { + it('should copy the visible answer instead of agent thought summaries', async () => { const user = userEvent.setup() const item: ChatItem = { ...baseItem, - content: 'ignored', + content: 'Final answer', agent_thoughts: [ { id: '1', @@ -374,7 +421,39 @@ describe('Operation', () => { } renderOperation({ ...baseProps, item }) await user.click(screen.getByRole('button', { name: 'operation.copy' })) - expect(copy).toHaveBeenCalledWith('Hello World') + expect(copy).toHaveBeenCalledWith('Final answer') + }) + + it('should copy public response parts after an interrupted response', async () => { + const user = userEvent.setup() + const item = createInterruptedItem(['First public update', 'Second public update']) + renderOperation({ ...baseProps, item }) + await user.click(screen.getByRole('button', { name: 'operation.copy' })) + expect(copy).toHaveBeenCalledWith('First public update\n\nSecond public update') + }) + + it('should copy public thought answers for legacy messages without content', async () => { + const user = userEvent.setup() + const item: ChatItem = { + ...baseItem, + content: '', + agent_thoughts: [ + { + id: '1', + thought: 'internal thought should not be copied', + answer: 'Public legacy answer', + tool: '', + tool_input: '', + observation: '', + message_id: '', + conversation_id: '', + position: 0, + }, + ], + } + renderOperation({ ...baseProps, item }) + await user.click(screen.getByRole('button', { name: 'operation.copy' })) + expect(copy).toHaveBeenCalledWith('Public legacy answer') }) }) @@ -911,6 +990,22 @@ describe('Operation', () => { ) }) + it('should annotate public response parts instead of internal thoughts', async () => { + const user = userEvent.setup() + const item = createInterruptedItem(['Public interrupted answer']) + + renderOperation({ ...baseProps, item }) + await user.click(screen.getByTestId('annotation-add-btn')) + + expect(mockContextValue.onAnnotationAdded).toHaveBeenCalledWith( + 'ann-new', + 'Test User', + 'What is this?', + 'Public interrupted answer', + 0, + ) + }) + it('should show annotation full modal when limit reached', async () => { const user = userEvent.setup() mockProviderContext.enableBilling = true @@ -1000,6 +1095,17 @@ describe('Operation', () => { expect(screen.getByTestId('audio-btn'))!.toBeInTheDocument() }) + it('should send public response parts to TTS instead of internal thoughts', () => { + const item = createInterruptedItem(['Public interrupted answer']) + + renderOperation({ ...baseProps, item }) + + expect(screen.getByTestId('audio-btn')).toHaveAttribute( + 'data-value', + 'Public interrupted answer', + ) + }) + it('should not show audio button for humanInputFormDataList', () => { const item = { ...baseItem, humanInputFormDataList: [{}] } as ChatItem renderOperation({ ...baseProps, item }) diff --git a/web/app/components/base/chat/chat/answer/agent-roster-response-content.tsx b/web/app/components/base/chat/chat/answer/agent-roster-response-content.tsx index 9a08f5aa050..c6eb12d9680 100644 --- a/web/app/components/base/chat/chat/answer/agent-roster-response-content.tsx +++ b/web/app/components/base/chat/chat/answer/agent-roster-response-content.tsx @@ -1,11 +1,13 @@ -import type { ReactNode } from 'react' import type { ThoughtItem } from '@/app/components/base/chat/chat/type' import type { ChatItem } from '@/app/components/base/chat/types' -import { cn } from '@langgenius/dify-ui/cn' +import type { Locale } from '@/i18n-config' +import { Collapsible, CollapsiblePanel, CollapsibleTrigger } from '@langgenius/dify-ui/collapsible' import { useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { FileList } from '@/app/components/base/file-uploader' import { Markdown } from '@/app/components/base/markdown' +import { renderI18nObject } from '@/i18n-config' +import { getLanguage } from '@/i18n-config/language' type AgentRosterResponseContentProps = { item: ChatItem @@ -13,7 +15,11 @@ type AgentRosterResponseContentProps = { content?: string } -type ToolProcess = { +const SHELL_TOOL_NAMES = new Set(['shell_run', 'shell_wait', 'shell_input', 'shell_interrupt']) + +type ToolActivity = { + kind: 'shell' | 'tool' + key: string name: string label: string input: string @@ -21,6 +27,18 @@ type ToolProcess = { isFinished: boolean } +type AgentActivityEntry = + | { + type: 'message' + content: string + key: string + } + | { + type: 'thought' + thought: ThoughtItem + key: string + } + function readIndexedValue(value: string, isArray: boolean, index: number) { if (!isArray) return value @@ -32,7 +50,11 @@ function readIndexedValue(value: string, isArray: boolean, index: number) { } } -function getToolProcesses(thought: ThoughtItem, responding?: boolean): ToolProcess[] { +function getToolActivities( + thought: ThoughtItem, + language: string, + responding?: boolean, +): ToolActivity[] { let toolNames = [thought.tool] let isArray = false @@ -44,9 +66,13 @@ function getToolProcesses(thought: ThoughtItem, responding?: boolean): ToolProce } } catch {} + const labelLanguage = getLanguage(language as Locale) + return toolNames.filter(Boolean).map((name, index) => ({ + kind: SHELL_TOOL_NAMES.has(name) ? 'shell' : 'tool', + key: `${name}-${index}`, name, - label: thought.tool_labels?.toolName?.en_US ?? name, + label: renderI18nObject(thought.tool_labels?.[name] ?? {}, labelLanguage) || name, input: readIndexedValue(thought.tool_input, isArray, index), output: readIndexedValue(thought.observation, isArray, index), isFinished: !!thought.observation || !responding, @@ -55,10 +81,11 @@ function getToolProcesses(thought: ThoughtItem, responding?: boolean): ToolProce function formatDuration(seconds: number, t: ReturnType>['t']) { const safeSeconds = Math.max(0, seconds) - if (safeSeconds < 60) + if (safeSeconds < 60) { return t(($) => $['agentDetail.configure.answer.duration.second'], { count: Number(safeSeconds.toFixed(2)), }) + } const minutes = Math.floor(safeSeconds / 60) const remainingSeconds = Math.floor(safeSeconds % 60) @@ -66,31 +93,11 @@ function formatDuration(seconds: number, t: ReturnType $['agentDetail.configure.answer.duration.minute'], { count: minutes }), t(($) => $['agentDetail.configure.answer.duration.second'], { count: remainingSeconds }), - ].join(' ') + ].join('') } -function formatLatencyDuration( - latency: NonNullable['latency'], - t: ReturnType>['t'], -) { - const numericLatency = Number(latency) - if (!Number.isNaN(numericLatency)) return formatDuration(numericLatency, t) - - return String(latency) -} - -function getCompletedTitle( - latency: NonNullable['latency'] | undefined, - t: ReturnType>['t'], -) { - const numericLatency = Number(latency) - if (latency != null && !Number.isNaN(numericLatency) && numericLatency > 0) { - return t(($) => $['agentDetail.configure.answer.workedFor'], { - duration: formatLatencyDuration(latency, t), - }) - } - - return t(($) => $['agentDetail.configure.answer.workFinished']) +function getThoughtKey(thought: ThoughtItem) { + return thought.id || `${thought.message_id}-${thought.position}` } function hashString(value: string) { @@ -100,10 +107,45 @@ function hashString(value: string) { return (hash >>> 0).toString(36) } -function getAgentResponsePartBaseKey(part: NonNullable[number]) { - if (part.type === 'message') return `message-${part.content.length}-${hashString(part.content)}` +function hasVisibleActivity(thought: ThoughtItem) { + return !!thought.tool || !!thought.message_files?.length +} - return `thought-${part.thought.id || `${part.thought.message_id}-${part.thought.position}`}` +function getAgentActivityEntries(item: ChatItem): AgentActivityEntry[] { + if (item.agent_response_parts?.length) { + const keyOccurrences = new Map() + + return item.agent_response_parts.flatMap((part) => { + const baseKey = + part.type === 'message' + ? `message-${part.content.length}-${hashString(part.content)}` + : `thought-${getThoughtKey(part.thought)}` + const occurrence = keyOccurrences.get(baseKey) ?? 0 + keyOccurrences.set(baseKey, occurrence + 1) + const key = occurrence ? `${baseKey}-${occurrence}` : baseKey + + if (part.type === 'message') + return part.content ? [{ type: 'message', content: part.content, key }] : [] + + return hasVisibleActivity(part.thought) + ? [{ type: 'thought', thought: part.thought, key }] + : [] + }) + } + + return [...(item.agent_thoughts ?? [])] + .sort((left, right) => left.position - right.position) + .flatMap((thought) => { + const parts: AgentActivityEntry[] = [] + const key = getThoughtKey(thought) + const answer = thought.answer + if (answer?.trim()) parts.push({ type: 'message', content: answer, key: `message-${key}` }) + + if (hasVisibleActivity(thought)) + parts.push({ type: 'thought', thought, key: `thought-${key}` }) + + return parts + }) } function useWorkingDuration(enabled?: boolean) { @@ -113,7 +155,7 @@ function useWorkingDuration(enabled?: boolean) { useEffect(() => { if (!enabled) return - startedAtRef.current = Date.now() + startedAtRef.current ??= Date.now() const timer = window.setInterval(() => { setNow(Date.now()) }, 1000) @@ -121,197 +163,115 @@ function useWorkingDuration(enabled?: boolean) { return () => window.clearInterval(timer) }, [enabled]) - const elapsedSeconds = - enabled && startedAtRef.current !== null - ? Math.max(0, Math.floor((now - startedAtRef.current) / 1000)) - : 0 - - return elapsedSeconds + if (!enabled || startedAtRef.current === null) return 0 + return Math.max(0, Math.floor((now - startedAtRef.current) / 1000)) } -function ProcessShell({ - children, - collapsed, - expandedTitle, - icon, - title, - defaultOpen = false, -}: { - children?: ReactNode - collapsed?: boolean - expandedTitle?: ReactNode - icon: ReactNode - title: ReactNode - defaultOpen?: boolean -}) { - const [open, setOpen] = useState(defaultOpen) - const canExpand = !!children - const expanded = canExpand && open && !collapsed - +function ResponseMessage({ content }: { content: string }) { return (
- - {expanded && ( -
-
-
{children}
-
- )} +
) } -function ThoughtProcess({ thought, defaultOpen }: { thought: ThoughtItem; defaultOpen?: boolean }) { +function ToolActivityItem({ tool }: { tool: ToolActivity }) { const { t } = useTranslation() - const thoughtTitle = t(($) => $['chat.thought'], { ns: 'common' }) - const summary = thought.thought.trim() || thoughtTitle + const hasDetails = !!tool.input || !!tool.output + const label = + tool.name === 'shell_run' && tool.label === tool.name + ? tool.isFinished + ? t(($) => $['agentDetail.configure.answer.activity.ranCommands'], { ns: 'agentV2' }) + : t(($) => $['agentDetail.configure.answer.activity.runningCommands'], { + ns: 'agentV2', + }) + : tool.label - return ( - } - title={summary} - expandedTitle={thoughtTitle.toUpperCase()} - defaultOpen={defaultOpen} - > - - - ) -} - -function ToolProcessCard({ tool }: { tool: ToolProcess }) { - const { t } = useTranslation() - const [open, setOpen] = useState(false) - - return ( -
- - {open && ( -
-
-
- {t(($) => $['thought.requestTitle'], { ns: 'tools' })} + {label} + + {hasDetails && ( + + )} + + ) + + return ( +
+ {hasDetails ? ( + + + {content} + + +
+ {!!tool.input && ( +
+
+ {t(($) => $['thought.requestTitle'], { ns: 'tools' })} +
+
+ {tool.input} +
+
+ )} + {!!tool.output && ( +
+
+ {t(($) => $['thought.responseTitle'], { ns: 'tools' })} +
+
+ {tool.output} +
+
+ )}
-
- {tool.input} -
-
-
-
- {t(($) => $['thought.responseTitle'], { ns: 'tools' })} -
-
- {tool.output} -
-
+ + + ) : ( +
+ {content}
)}
) } -function AgentThoughtsProcessList({ item, responding }: { item: ChatItem; responding?: boolean }) { - return ( -
- {item.agent_thoughts?.map((thought, index) => ( - - ))} -
- ) -} - -function AgentThoughtProcessItem({ +function AgentActivityItem({ thought, responding, - defaultOpen, }: { thought: ThoughtItem responding?: boolean - defaultOpen?: boolean }) { - const tools = getToolProcesses(thought, responding) - const answer = thought.answer?.trim() + const { i18n } = useTranslation() + const tools = getToolActivities(thought, i18n.language, responding) return ( -
- {answer && ( -
- -
- )} - {!answer && thought.thought && } +
{tools.map((tool) => ( - + ))} {!!thought.message_files?.length && ( () - - return ( -
- {item.agent_response_parts?.map((part, index) => { - const baseKey = getAgentResponsePartBaseKey(part) - const occurrence = keyOccurrences.get(baseKey) ?? 0 - keyOccurrences.set(baseKey, occurrence + 1) - const partKey = occurrence ? `${baseKey}-${occurrence}` : baseKey - - if (part.type === 'message') { - if (!part.content) return null - - return ( -
- -
- ) - } - - return ( - - ) - })} -
- ) -} - -function AgentThoughtsProcessGroup({ item, responding }: { item: ChatItem; responding?: boolean }) { +function AgentActivityDisclosure({ + item, + entries, + responding, + defaultOpen, +}: { + item: ChatItem + entries: AgentActivityEntry[] + responding?: boolean + defaultOpen?: boolean +}) { const { t } = useTranslation('agentV2') - const [open, setOpen] = useState(false) - const workingDuration = formatDuration(useWorkingDuration(responding), t) - const completedTitle = getCompletedTitle(item.more?.latency, t) - - if (responding) { - return ( -
-
- - {t(($) => $['agentDetail.configure.answer.workingFor'], { duration: workingDuration })} - -
- -
- ) - } + const workingDuration = useWorkingDuration(responding) + const latency = Number(item.more?.latency) + const duration = responding + ? formatDuration(workingDuration, t) + : Number.isFinite(latency) && latency > 0 + ? formatDuration(latency, t) + : undefined + const thinking = t(($) => $['agentDetail.configure.answer.thinking']) + const title = duration ? `${thinking} · ${duration}` : thinking return ( -
- - {open && } -
+ + + +
+ {entries.map((entry) => + entry.type === 'message' ? ( + + ) : ( + + ), + )} +
+
+ ) } @@ -404,37 +342,47 @@ export function AgentRosterResponseContent({ responding, content, }: AgentRosterResponseContentProps) { - const { annotation, agent_thoughts } = item - - if (annotation?.logAnnotation) { + if (item.annotation?.logAnnotation) { return ( ) } + const entries = getAgentActivityEntries(item) + const hasLiveResponseParts = !!item.agent_response_parts?.length + const hasThinkingStatus = + entries.length === 0 && !!item.agent_response_parts?.some((part) => part.type === 'thought') + const hasActivity = + hasThinkingStatus || + (hasLiveResponseParts ? entries.some((entry) => entry.type === 'thought') : entries.length > 0) + const standaloneMessages = hasLiveResponseParts + ? hasActivity + ? [] + : entries.flatMap((entry) => (entry.type === 'message' ? [entry] : [])) + : content + ? [{ type: 'message' as const, content, key: 'final-answer' }] + : [] + return ( -
- {!!item.agent_response_parts?.length && ( - - )} - {!item.agent_response_parts?.length && ( - <> - {!!agent_thoughts?.length && ( - - )} - {content && ( -
- -
- )} - +
+ {hasActivity && ( + 0)} + /> )} + {standaloneMessages.map((message) => ( + + ))}
) } diff --git a/web/app/components/base/chat/chat/answer/more.tsx b/web/app/components/base/chat/chat/answer/more.tsx index 2e6749fdb4a..952c2f3ad91 100644 --- a/web/app/components/base/chat/chat/answer/more.tsx +++ b/web/app/components/base/chat/chat/answer/more.tsx @@ -40,10 +40,18 @@ const More: FC = ({ more }) => { {`${more.tokens_per_second} tokens/s`}
)} -
·
-
- {more.time} -
+ {!!more.time && ( + <> +
·
+
+ {more.time} +
+ + )} )}
diff --git a/web/app/components/base/chat/chat/answer/operation.tsx b/web/app/components/base/chat/chat/answer/operation.tsx index 99c64991227..a2bed1066c1 100644 --- a/web/app/components/base/chat/chat/answer/operation.tsx +++ b/web/app/components/base/chat/chat/answer/operation.tsx @@ -42,6 +42,23 @@ const feedbackTooltipClassName = 'max-w-[260px]' const answerActiveFlexClassName = 'group-hover:flex group-has-[[data-popup-open]]:flex' const answerActiveBlockClassName = 'group-hover:block group-has-[[data-popup-open]]:block' +function joinPublicContent(blocks: Array) { + return blocks.filter((block): block is string => !!block?.trim()).join('\n\n') +} + +function getPublicResponseContent(item: ChatItem) { + if (item.content.trim()) return item.content + + const responseContent = joinPublicContent( + item.agent_response_parts?.map((part) => + part.type === 'message' ? part.content : undefined, + ) ?? [], + ) + if (responseContent) return responseContent + + return joinPublicContent(item.agent_thoughts?.map((thought) => thought.answer) ?? []) +} + const FeedbackTooltip = ({ content, children }: FeedbackTooltipProps) => { return ( @@ -74,16 +91,8 @@ function Operation({ const [isShowReplyModal, setIsShowReplyModal] = useState(false) const [isShowFeedbackModal, setIsShowFeedbackModal] = useState(false) const [feedbackContent, setFeedbackContent] = useState('') - const { - id, - isOpeningStatement, - content: messageContent, - annotation, - feedback, - adminFeedback, - agent_thoughts, - humanInputFormDataList, - } = item + const { id, isOpeningStatement, annotation, feedback, adminFeedback, humanInputFormDataList } = + item const [userLocalFeedback, setUserLocalFeedback] = useState(feedback) const [adminLocalFeedback, setAdminLocalFeedback] = useState(adminFeedback) const [feedbackTarget, setFeedbackTarget] = useState<'user' | 'admin'>('user') @@ -91,11 +100,8 @@ function Operation({ const userFeedback = feedback - const content = useMemo(() => { - if (agent_thoughts?.length) return agent_thoughts.reduce((acc, cur) => acc + cur.thought, '') - - return messageContent - }, [agent_thoughts, messageContent]) + const content = getPublicResponseContent(item) + const hasPublicContent = !!content.trim() const displayUserFeedback = userLocalFeedback ?? userFeedback @@ -110,6 +116,7 @@ function Operation({ !readonly && !!onAnnotationAdded && !!onAnnotationEdited && !!onAnnotationRemoved const shouldShowAnnotationAction = canManageAnnotation && + hasPublicContent && !!config?.supportAnnotation && !!config.annotation_reply?.enabled && !humanInputFormDataList?.length @@ -177,7 +184,7 @@ function Operation({ let width = 0 if (!isOpeningStatement) width += 26 if (!isOpeningStatement && showPromptLog) width += 28 + 8 - if (!isOpeningStatement && config?.text_to_speech?.enabled) width += 26 + if (!isOpeningStatement && config?.text_to_speech?.enabled && hasPublicContent) width += 26 if (!isOpeningStatement && shouldShowAnnotationAction) width += 26 if (shouldShowUserFeedbackBar) width += hasUserFeedback ? 28 + 8 : 60 + 8 if (shouldShowAdminFeedbackBar) @@ -187,6 +194,7 @@ function Operation({ }, [ config?.text_to_speech?.enabled, hasAdminFeedback, + hasPublicContent, hasUserFeedback, isOpeningStatement, shouldShowAdminFeedbackBar, @@ -365,10 +373,12 @@ function Operation({ )} data-testid="operation-actions" > - {config?.text_to_speech?.enabled && !humanInputFormDataList?.length && ( - - )} - {!humanInputFormDataList?.length && ( + {config?.text_to_speech?.enabled && + hasPublicContent && + !humanInputFormDataList?.length && ( + + )} + {hasPublicContent && !humanInputFormDataList?.length && ( { diff --git a/web/features/agent-v2/agent-detail/configure/components/preview/__tests__/chat.spec.tsx b/web/features/agent-v2/agent-detail/configure/components/preview/__tests__/chat.spec.tsx index 681275f3d32..da0b3872769 100644 --- a/web/features/agent-v2/agent-detail/configure/components/preview/__tests__/chat.spec.tsx +++ b/web/features/agent-v2/agent-detail/configure/components/preview/__tests__/chat.spec.tsx @@ -775,6 +775,59 @@ describe('AgentPreviewChat', () => { }) }) + it('should preserve tool labels when formatting chat history', async () => { + chatMessagesGetMock.mockResolvedValue({ + data: [ + { + id: 'message-with-tool-label', + conversation_id: 'conversation-1', + query: 'run pwd', + answer: '', + inputs: {}, + message: [], + message_files: [], + agent_thoughts: [ + { + id: 'thought-with-tool-label', + message_id: 'message-with-tool-label', + thought: '', + answer: '', + tool: 'shell_run', + tool_input: 'pwd', + tool_labels: { + shell_run: { + en_US: 'Ran commands', + zh_Hans: '运行了命令', + }, + }, + observation: '/workspace', + position: 1, + }, + ], + feedbacks: [], + status: 'success', + from_source: 'console', + }, + ], + }) + + renderPreviewChat({ conversationId: 'conversation-1' }) + + await waitFor(() => { + const formattedTree = useChatMock.mock.calls.find((call) => { + const chatTree = call[2] + return JSON.stringify(chatTree).includes('thought-with-tool-label') + })?.[2] + + expect(formattedTree?.[0]?.children?.[0]?.agent_thoughts?.[0]?.tool_labels).toEqual({ + shell_run: { + en_US: 'Ran commands', + zh_Hans: '运行了命令', + }, + }) + }) + }) + it('should notify the owner when a send settles with an error', async () => { const onSendInterrupted = vi.fn() renderPreviewChat({ diff --git a/web/features/agent-v2/agent-detail/configure/components/preview/chat-runtime.tsx b/web/features/agent-v2/agent-detail/configure/components/preview/chat-runtime.tsx index fb9a95ddd52..47be15ebef8 100644 --- a/web/features/agent-v2/agent-detail/configure/components/preview/chat-runtime.tsx +++ b/web/features/agent-v2/agent-detail/configure/components/preview/chat-runtime.tsx @@ -271,12 +271,33 @@ const toLogMessages = ( ] } +function toToolLabels(value: AgentThought['tool_labels']): ThoughtItem['tool_labels'] { + if (!value || typeof value !== 'object' || Array.isArray(value)) return undefined + + const toolLabels: NonNullable = {} + for (const [name, label] of Object.entries(value)) { + if (!label || typeof label !== 'object' || Array.isArray(label)) continue + + const enUS = 'en_US' in label ? label.en_US : undefined + const zhHans = 'zh_Hans' in label ? label.zh_Hans : undefined + if (typeof enUS !== 'string' || typeof zhHans !== 'string') continue + + toolLabels[name] = { en_US: enUS, zh_Hans: zhHans } + for (const [locale, localizedLabel] of Object.entries(label)) { + if (typeof localizedLabel === 'string') toolLabels[name][locale] = localizedLabel + } + } + + return Object.keys(toolLabels).length ? toolLabels : undefined +} + const toAgentThoughtItem = (thought: AgentThought, conversationId: string): ThoughtItem => ({ id: thought.id, tool: thought.tool ?? '', thought: thought.thought ?? '', answer: thought.answer ?? '', tool_input: thought.tool_input ?? '', + tool_labels: toToolLabels(thought.tool_labels), message_id: thought.message_id, conversation_id: conversationId, observation: thought.observation ?? '', diff --git a/web/i18n/ar-TN/agent-v-2.json b/web/i18n/ar-TN/agent-v-2.json index 90e903e0306..ac180a47d4e 100644 --- a/web/i18n/ar-TN/agent-v-2.json +++ b/web/i18n/ar-TN/agent-v-2.json @@ -63,8 +63,11 @@ "agentDetail.configure.advancedSettings.envEditor.valuePlaceholder": "Value", "agentDetail.configure.advancedSettings.label": "الإعدادات المتقدمة", "agentDetail.configure.advancedSettings.toggle": "تبديل الإعدادات المتقدمة", + "agentDetail.configure.answer.activity.ranCommands": "تم تشغيل الأوامر", + "agentDetail.configure.answer.activity.runningCommands": "جارٍ تشغيل الأوامر", "agentDetail.configure.answer.duration.minute": "{{count}} د", "agentDetail.configure.answer.duration.second": "{{count}} ث", + "agentDetail.configure.answer.thinking": "جارٍ التفكير", "agentDetail.configure.answer.workFinished": "اكتمل العمل", "agentDetail.configure.answer.workedFor": "عمل لمدة {{duration}}", "agentDetail.configure.answer.workingFor": "يعمل منذ {{duration}}", diff --git a/web/i18n/de-DE/agent-v-2.json b/web/i18n/de-DE/agent-v-2.json index cc8761b67bf..5f0adec66fc 100644 --- a/web/i18n/de-DE/agent-v-2.json +++ b/web/i18n/de-DE/agent-v-2.json @@ -63,8 +63,11 @@ "agentDetail.configure.advancedSettings.envEditor.valuePlaceholder": "Value", "agentDetail.configure.advancedSettings.label": "Erweiterte Einstellungen", "agentDetail.configure.advancedSettings.toggle": "Erweiterte Einstellungen umschalten", + "agentDetail.configure.answer.activity.ranCommands": "Befehle ausgeführt", + "agentDetail.configure.answer.activity.runningCommands": "Befehle werden ausgeführt", "agentDetail.configure.answer.duration.minute": "{{count}} Min.", "agentDetail.configure.answer.duration.second": "{{count}} s", + "agentDetail.configure.answer.thinking": "Denkt nach", "agentDetail.configure.answer.workFinished": "Arbeit abgeschlossen", "agentDetail.configure.answer.workedFor": "Gearbeitet für {{duration}}", "agentDetail.configure.answer.workingFor": "Arbeitet seit {{duration}}", diff --git a/web/i18n/en-US/agent-v-2.json b/web/i18n/en-US/agent-v-2.json index 82f258f2dc1..186ce131be0 100644 --- a/web/i18n/en-US/agent-v-2.json +++ b/web/i18n/en-US/agent-v-2.json @@ -63,8 +63,11 @@ "agentDetail.configure.advancedSettings.envEditor.valuePlaceholder": "Value", "agentDetail.configure.advancedSettings.label": "Advanced Settings", "agentDetail.configure.advancedSettings.toggle": "Toggle advanced settings", + "agentDetail.configure.answer.activity.ranCommands": "Ran commands", + "agentDetail.configure.answer.activity.runningCommands": "Running commands", "agentDetail.configure.answer.duration.minute": "{{count}}m", "agentDetail.configure.answer.duration.second": "{{count}}s", + "agentDetail.configure.answer.thinking": "Thinking", "agentDetail.configure.answer.workFinished": "Work finished", "agentDetail.configure.answer.workedFor": "Worked for {{duration}}", "agentDetail.configure.answer.workingFor": "Working for {{duration}}", diff --git a/web/i18n/es-ES/agent-v-2.json b/web/i18n/es-ES/agent-v-2.json index 79641e1130b..fe1b31e3e36 100644 --- a/web/i18n/es-ES/agent-v-2.json +++ b/web/i18n/es-ES/agent-v-2.json @@ -63,8 +63,11 @@ "agentDetail.configure.advancedSettings.envEditor.valuePlaceholder": "Value", "agentDetail.configure.advancedSettings.label": "Configuración avanzada", "agentDetail.configure.advancedSettings.toggle": "Alternar configuración avanzada", + "agentDetail.configure.answer.activity.ranCommands": "Comandos ejecutados", + "agentDetail.configure.answer.activity.runningCommands": "Ejecutando comandos", "agentDetail.configure.answer.duration.minute": "{{count}} min", "agentDetail.configure.answer.duration.second": "{{count}} s", + "agentDetail.configure.answer.thinking": "Pensando", "agentDetail.configure.answer.workFinished": "Trabajo finalizado", "agentDetail.configure.answer.workedFor": "Trabajó durante {{duration}}", "agentDetail.configure.answer.workingFor": "Trabajando durante {{duration}}", diff --git a/web/i18n/fa-IR/agent-v-2.json b/web/i18n/fa-IR/agent-v-2.json index b5b117ad296..ce6298c5755 100644 --- a/web/i18n/fa-IR/agent-v-2.json +++ b/web/i18n/fa-IR/agent-v-2.json @@ -63,8 +63,11 @@ "agentDetail.configure.advancedSettings.envEditor.valuePlaceholder": "Value", "agentDetail.configure.advancedSettings.label": "تنظیمات پیشرفته", "agentDetail.configure.advancedSettings.toggle": "تغییر تنظیمات پیشرفته", + "agentDetail.configure.answer.activity.ranCommands": "دستورات اجرا شدند", + "agentDetail.configure.answer.activity.runningCommands": "در حال اجرای دستورات", "agentDetail.configure.answer.duration.minute": "{{count}} دقیقه", "agentDetail.configure.answer.duration.second": "{{count}} ثانیه", + "agentDetail.configure.answer.thinking": "در حال فکر کردن", "agentDetail.configure.answer.workFinished": "کار تمام شد", "agentDetail.configure.answer.workedFor": "به مدت {{duration}} کار کرد", "agentDetail.configure.answer.workingFor": "در حال کار به مدت {{duration}}", diff --git a/web/i18n/fr-FR/agent-v-2.json b/web/i18n/fr-FR/agent-v-2.json index 23918d2ab19..f02b2b17454 100644 --- a/web/i18n/fr-FR/agent-v-2.json +++ b/web/i18n/fr-FR/agent-v-2.json @@ -63,8 +63,11 @@ "agentDetail.configure.advancedSettings.envEditor.valuePlaceholder": "Value", "agentDetail.configure.advancedSettings.label": "Paramètres avancés", "agentDetail.configure.advancedSettings.toggle": "Basculer les paramètres avancés", + "agentDetail.configure.answer.activity.ranCommands": "Commandes exécutées", + "agentDetail.configure.answer.activity.runningCommands": "Exécution des commandes", "agentDetail.configure.answer.duration.minute": "{{count}} min", "agentDetail.configure.answer.duration.second": "{{count}} s", + "agentDetail.configure.answer.thinking": "Réflexion en cours", "agentDetail.configure.answer.workFinished": "Travail terminé", "agentDetail.configure.answer.workedFor": "A travaillé pendant {{duration}}", "agentDetail.configure.answer.workingFor": "Travaille depuis {{duration}}", diff --git a/web/i18n/hi-IN/agent-v-2.json b/web/i18n/hi-IN/agent-v-2.json index 89d96b83144..8599c7a5c30 100644 --- a/web/i18n/hi-IN/agent-v-2.json +++ b/web/i18n/hi-IN/agent-v-2.json @@ -63,8 +63,11 @@ "agentDetail.configure.advancedSettings.envEditor.valuePlaceholder": "Value", "agentDetail.configure.advancedSettings.label": "उन्नत सेटिंग्स", "agentDetail.configure.advancedSettings.toggle": "उन्नत सेटिंग्स टॉगल करें", + "agentDetail.configure.answer.activity.ranCommands": "कमांड चलाए गए", + "agentDetail.configure.answer.activity.runningCommands": "कमांड चलाए जा रहे हैं", "agentDetail.configure.answer.duration.minute": "{{count}} मिनट", "agentDetail.configure.answer.duration.second": "{{count}} सेकंड", + "agentDetail.configure.answer.thinking": "सोच रहा है", "agentDetail.configure.answer.workFinished": "काम पूरा हुआ", "agentDetail.configure.answer.workedFor": "{{duration}} तक काम किया", "agentDetail.configure.answer.workingFor": "{{duration}} से काम कर रहा है", diff --git a/web/i18n/id-ID/agent-v-2.json b/web/i18n/id-ID/agent-v-2.json index 3368803109a..e371cc8f24a 100644 --- a/web/i18n/id-ID/agent-v-2.json +++ b/web/i18n/id-ID/agent-v-2.json @@ -63,8 +63,11 @@ "agentDetail.configure.advancedSettings.envEditor.valuePlaceholder": "Value", "agentDetail.configure.advancedSettings.label": "Pengaturan Lanjutan", "agentDetail.configure.advancedSettings.toggle": "Alihkan pengaturan lanjutan", + "agentDetail.configure.answer.activity.ranCommands": "Perintah dijalankan", + "agentDetail.configure.answer.activity.runningCommands": "Menjalankan perintah", "agentDetail.configure.answer.duration.minute": "{{count}} mnt", "agentDetail.configure.answer.duration.second": "{{count}} dtk", + "agentDetail.configure.answer.thinking": "Sedang berpikir", "agentDetail.configure.answer.workFinished": "Pekerjaan selesai", "agentDetail.configure.answer.workedFor": "Bekerja selama {{duration}}", "agentDetail.configure.answer.workingFor": "Bekerja selama {{duration}}", diff --git a/web/i18n/it-IT/agent-v-2.json b/web/i18n/it-IT/agent-v-2.json index 45136224402..1a926b697d9 100644 --- a/web/i18n/it-IT/agent-v-2.json +++ b/web/i18n/it-IT/agent-v-2.json @@ -63,8 +63,11 @@ "agentDetail.configure.advancedSettings.envEditor.valuePlaceholder": "Value", "agentDetail.configure.advancedSettings.label": "Impostazioni avanzate", "agentDetail.configure.advancedSettings.toggle": "Attiva/disattiva impostazioni avanzate", + "agentDetail.configure.answer.activity.ranCommands": "Comandi eseguiti", + "agentDetail.configure.answer.activity.runningCommands": "Esecuzione dei comandi", "agentDetail.configure.answer.duration.minute": "{{count}} min", "agentDetail.configure.answer.duration.second": "{{count}} s", + "agentDetail.configure.answer.thinking": "Sto pensando", "agentDetail.configure.answer.workFinished": "Lavoro terminato", "agentDetail.configure.answer.workedFor": "Ha lavorato per {{duration}}", "agentDetail.configure.answer.workingFor": "Sta lavorando da {{duration}}", diff --git a/web/i18n/ja-JP/agent-v-2.json b/web/i18n/ja-JP/agent-v-2.json index a184b41ae19..ecd92caed6c 100644 --- a/web/i18n/ja-JP/agent-v-2.json +++ b/web/i18n/ja-JP/agent-v-2.json @@ -63,8 +63,11 @@ "agentDetail.configure.advancedSettings.envEditor.valuePlaceholder": "Value", "agentDetail.configure.advancedSettings.label": "詳細設定", "agentDetail.configure.advancedSettings.toggle": "詳細設定の表示を切り替え", + "agentDetail.configure.answer.activity.ranCommands": "コマンドを実行しました", + "agentDetail.configure.answer.activity.runningCommands": "コマンドを実行中", "agentDetail.configure.answer.duration.minute": "{{count}}分", "agentDetail.configure.answer.duration.second": "{{count}}秒", + "agentDetail.configure.answer.thinking": "思考中", "agentDetail.configure.answer.workFinished": "作業が完了しました", "agentDetail.configure.answer.workedFor": "{{duration}} 実行しました", "agentDetail.configure.answer.workingFor": "{{duration}} 実行中", diff --git a/web/i18n/ko-KR/agent-v-2.json b/web/i18n/ko-KR/agent-v-2.json index 1180793ec7b..a1d7327e76b 100644 --- a/web/i18n/ko-KR/agent-v-2.json +++ b/web/i18n/ko-KR/agent-v-2.json @@ -63,8 +63,11 @@ "agentDetail.configure.advancedSettings.envEditor.valuePlaceholder": "Value", "agentDetail.configure.advancedSettings.label": "고급 설정", "agentDetail.configure.advancedSettings.toggle": "고급 설정 전환", + "agentDetail.configure.answer.activity.ranCommands": "명령 실행됨", + "agentDetail.configure.answer.activity.runningCommands": "명령 실행 중", "agentDetail.configure.answer.duration.minute": "{{count}}분", "agentDetail.configure.answer.duration.second": "{{count}}초", + "agentDetail.configure.answer.thinking": "생각 중", "agentDetail.configure.answer.workFinished": "작업 완료", "agentDetail.configure.answer.workedFor": "{{duration}} 동안 작업함", "agentDetail.configure.answer.workingFor": "{{duration}} 동안 작업 중", diff --git a/web/i18n/nl-NL/agent-v-2.json b/web/i18n/nl-NL/agent-v-2.json index b1536660288..e21dfc68be5 100644 --- a/web/i18n/nl-NL/agent-v-2.json +++ b/web/i18n/nl-NL/agent-v-2.json @@ -63,8 +63,11 @@ "agentDetail.configure.advancedSettings.envEditor.valuePlaceholder": "Value", "agentDetail.configure.advancedSettings.label": "Geavanceerde instellingen", "agentDetail.configure.advancedSettings.toggle": "Geavanceerde instellingen in/uit", + "agentDetail.configure.answer.activity.ranCommands": "Opdrachten uitgevoerd", + "agentDetail.configure.answer.activity.runningCommands": "Opdrachten uitvoeren", "agentDetail.configure.answer.duration.minute": "{{count}} min", "agentDetail.configure.answer.duration.second": "{{count}} s", + "agentDetail.configure.answer.thinking": "Aan het nadenken", "agentDetail.configure.answer.workFinished": "Werk voltooid", "agentDetail.configure.answer.workedFor": "Gewerkt gedurende {{duration}}", "agentDetail.configure.answer.workingFor": "Bezig gedurende {{duration}}", diff --git a/web/i18n/pl-PL/agent-v-2.json b/web/i18n/pl-PL/agent-v-2.json index 57e466f923e..3967666dd35 100644 --- a/web/i18n/pl-PL/agent-v-2.json +++ b/web/i18n/pl-PL/agent-v-2.json @@ -63,8 +63,11 @@ "agentDetail.configure.advancedSettings.envEditor.valuePlaceholder": "Value", "agentDetail.configure.advancedSettings.label": "Ustawienia zaawansowane", "agentDetail.configure.advancedSettings.toggle": "Przełącz ustawienia zaawansowane", + "agentDetail.configure.answer.activity.ranCommands": "Uruchomiono polecenia", + "agentDetail.configure.answer.activity.runningCommands": "Uruchamianie poleceń", "agentDetail.configure.answer.duration.minute": "{{count}} min", "agentDetail.configure.answer.duration.second": "{{count}} s", + "agentDetail.configure.answer.thinking": "Myśli", "agentDetail.configure.answer.workFinished": "Praca zakończona", "agentDetail.configure.answer.workedFor": "Pracował przez {{duration}}", "agentDetail.configure.answer.workingFor": "Pracuje od {{duration}}", diff --git a/web/i18n/pt-BR/agent-v-2.json b/web/i18n/pt-BR/agent-v-2.json index ed68fcc6645..d0b4042975e 100644 --- a/web/i18n/pt-BR/agent-v-2.json +++ b/web/i18n/pt-BR/agent-v-2.json @@ -63,8 +63,11 @@ "agentDetail.configure.advancedSettings.envEditor.valuePlaceholder": "Value", "agentDetail.configure.advancedSettings.label": "Configurações avançadas", "agentDetail.configure.advancedSettings.toggle": "Alternar configurações avançadas", + "agentDetail.configure.answer.activity.ranCommands": "Comandos executados", + "agentDetail.configure.answer.activity.runningCommands": "Executando comandos", "agentDetail.configure.answer.duration.minute": "{{count}} min", "agentDetail.configure.answer.duration.second": "{{count}} s", + "agentDetail.configure.answer.thinking": "Pensando", "agentDetail.configure.answer.workFinished": "Trabalho concluído", "agentDetail.configure.answer.workedFor": "Trabalhou por {{duration}}", "agentDetail.configure.answer.workingFor": "Trabalhando há {{duration}}", diff --git a/web/i18n/ro-RO/agent-v-2.json b/web/i18n/ro-RO/agent-v-2.json index 3b5c06ae5fa..1228cb2d3d7 100644 --- a/web/i18n/ro-RO/agent-v-2.json +++ b/web/i18n/ro-RO/agent-v-2.json @@ -63,8 +63,11 @@ "agentDetail.configure.advancedSettings.envEditor.valuePlaceholder": "Value", "agentDetail.configure.advancedSettings.label": "Setări avansate", "agentDetail.configure.advancedSettings.toggle": "Comută setările avansate", + "agentDetail.configure.answer.activity.ranCommands": "Comenzi executate", + "agentDetail.configure.answer.activity.runningCommands": "Se execută comenzile", "agentDetail.configure.answer.duration.minute": "{{count}} min", "agentDetail.configure.answer.duration.second": "{{count}} s", + "agentDetail.configure.answer.thinking": "Se gândește", "agentDetail.configure.answer.workFinished": "Lucrare finalizată", "agentDetail.configure.answer.workedFor": "A lucrat timp de {{duration}}", "agentDetail.configure.answer.workingFor": "Lucrează de {{duration}}", diff --git a/web/i18n/ru-RU/agent-v-2.json b/web/i18n/ru-RU/agent-v-2.json index d1e7ba02978..391d73eb7ca 100644 --- a/web/i18n/ru-RU/agent-v-2.json +++ b/web/i18n/ru-RU/agent-v-2.json @@ -63,8 +63,11 @@ "agentDetail.configure.advancedSettings.envEditor.valuePlaceholder": "Value", "agentDetail.configure.advancedSettings.label": "Расширенные настройки", "agentDetail.configure.advancedSettings.toggle": "Переключить расширенные настройки", + "agentDetail.configure.answer.activity.ranCommands": "Команды выполнены", + "agentDetail.configure.answer.activity.runningCommands": "Выполнение команд", "agentDetail.configure.answer.duration.minute": "{{count}} мин", "agentDetail.configure.answer.duration.second": "{{count}} с", + "agentDetail.configure.answer.thinking": "Размышляет", "agentDetail.configure.answer.workFinished": "Работа завершена", "agentDetail.configure.answer.workedFor": "Работал {{duration}}", "agentDetail.configure.answer.workingFor": "Работает {{duration}}", diff --git a/web/i18n/sl-SI/agent-v-2.json b/web/i18n/sl-SI/agent-v-2.json index 95debe91008..e215bdee067 100644 --- a/web/i18n/sl-SI/agent-v-2.json +++ b/web/i18n/sl-SI/agent-v-2.json @@ -63,8 +63,11 @@ "agentDetail.configure.advancedSettings.envEditor.valuePlaceholder": "Value", "agentDetail.configure.advancedSettings.label": "Napredne nastavitve", "agentDetail.configure.advancedSettings.toggle": "Preklopi napredne nastavitve", + "agentDetail.configure.answer.activity.ranCommands": "Ukazi izvedeni", + "agentDetail.configure.answer.activity.runningCommands": "Izvajanje ukazov", "agentDetail.configure.answer.duration.minute": "{{count}} min", "agentDetail.configure.answer.duration.second": "{{count}} s", + "agentDetail.configure.answer.thinking": "Razmišlja", "agentDetail.configure.answer.workFinished": "Delo končano", "agentDetail.configure.answer.workedFor": "Delal {{duration}}", "agentDetail.configure.answer.workingFor": "Dela {{duration}}", diff --git a/web/i18n/th-TH/agent-v-2.json b/web/i18n/th-TH/agent-v-2.json index 8c2fa4025ad..f90adf7f7d4 100644 --- a/web/i18n/th-TH/agent-v-2.json +++ b/web/i18n/th-TH/agent-v-2.json @@ -63,8 +63,11 @@ "agentDetail.configure.advancedSettings.envEditor.valuePlaceholder": "Value", "agentDetail.configure.advancedSettings.label": "การตั้งค่าขั้นสูง", "agentDetail.configure.advancedSettings.toggle": "สลับการตั้งค่าขั้นสูง", + "agentDetail.configure.answer.activity.ranCommands": "เรียกใช้คำสั่งแล้ว", + "agentDetail.configure.answer.activity.runningCommands": "กำลังเรียกใช้คำสั่ง", "agentDetail.configure.answer.duration.minute": "{{count}} นาที", "agentDetail.configure.answer.duration.second": "{{count}} วินาที", + "agentDetail.configure.answer.thinking": "กำลังคิด", "agentDetail.configure.answer.workFinished": "งานเสร็จสิ้น", "agentDetail.configure.answer.workedFor": "ทำงานเป็นเวลา {{duration}}", "agentDetail.configure.answer.workingFor": "กำลังทำงานเป็นเวลา {{duration}}", diff --git a/web/i18n/tr-TR/agent-v-2.json b/web/i18n/tr-TR/agent-v-2.json index cec9fb5b972..3bda42a44dc 100644 --- a/web/i18n/tr-TR/agent-v-2.json +++ b/web/i18n/tr-TR/agent-v-2.json @@ -63,8 +63,11 @@ "agentDetail.configure.advancedSettings.envEditor.valuePlaceholder": "Value", "agentDetail.configure.advancedSettings.label": "Gelişmiş Ayarlar", "agentDetail.configure.advancedSettings.toggle": "Gelişmiş ayarları değiştir", + "agentDetail.configure.answer.activity.ranCommands": "Komutlar çalıştırıldı", + "agentDetail.configure.answer.activity.runningCommands": "Komutlar çalıştırılıyor", "agentDetail.configure.answer.duration.minute": "{{count}} dk", "agentDetail.configure.answer.duration.second": "{{count}} sn", + "agentDetail.configure.answer.thinking": "Düşünüyor", "agentDetail.configure.answer.workFinished": "İş tamamlandı", "agentDetail.configure.answer.workedFor": "{{duration}} çalıştı", "agentDetail.configure.answer.workingFor": "{{duration}} çalışıyor", diff --git a/web/i18n/uk-UA/agent-v-2.json b/web/i18n/uk-UA/agent-v-2.json index a751096e38b..b9191507c0e 100644 --- a/web/i18n/uk-UA/agent-v-2.json +++ b/web/i18n/uk-UA/agent-v-2.json @@ -63,8 +63,11 @@ "agentDetail.configure.advancedSettings.envEditor.valuePlaceholder": "Value", "agentDetail.configure.advancedSettings.label": "Розширені налаштування", "agentDetail.configure.advancedSettings.toggle": "Перемкнути розширені налаштування", + "agentDetail.configure.answer.activity.ranCommands": "Команди виконано", + "agentDetail.configure.answer.activity.runningCommands": "Виконання команд", "agentDetail.configure.answer.duration.minute": "{{count}} хв", "agentDetail.configure.answer.duration.second": "{{count}} с", + "agentDetail.configure.answer.thinking": "Розмірковує", "agentDetail.configure.answer.workFinished": "Роботу завершено", "agentDetail.configure.answer.workedFor": "Працював {{duration}}", "agentDetail.configure.answer.workingFor": "Працює {{duration}}", diff --git a/web/i18n/vi-VN/agent-v-2.json b/web/i18n/vi-VN/agent-v-2.json index 7c4c7de31e3..5c9b4b9c51e 100644 --- a/web/i18n/vi-VN/agent-v-2.json +++ b/web/i18n/vi-VN/agent-v-2.json @@ -63,8 +63,11 @@ "agentDetail.configure.advancedSettings.envEditor.valuePlaceholder": "Value", "agentDetail.configure.advancedSettings.label": "Cài đặt nâng cao", "agentDetail.configure.advancedSettings.toggle": "Bật/tắt cài đặt nâng cao", + "agentDetail.configure.answer.activity.ranCommands": "Đã chạy lệnh", + "agentDetail.configure.answer.activity.runningCommands": "Đang chạy lệnh", "agentDetail.configure.answer.duration.minute": "{{count}} phút", "agentDetail.configure.answer.duration.second": "{{count}} giây", + "agentDetail.configure.answer.thinking": "Đang suy nghĩ", "agentDetail.configure.answer.workFinished": "Công việc đã hoàn tất", "agentDetail.configure.answer.workedFor": "Đã làm việc trong {{duration}}", "agentDetail.configure.answer.workingFor": "Đang làm việc trong {{duration}}", diff --git a/web/i18n/zh-Hans/agent-v-2.json b/web/i18n/zh-Hans/agent-v-2.json index cba4b5e6c75..6c29caac01a 100644 --- a/web/i18n/zh-Hans/agent-v-2.json +++ b/web/i18n/zh-Hans/agent-v-2.json @@ -63,8 +63,11 @@ "agentDetail.configure.advancedSettings.envEditor.valuePlaceholder": "Value", "agentDetail.configure.advancedSettings.label": "高级设置", "agentDetail.configure.advancedSettings.toggle": "展开或收起高级设置", + "agentDetail.configure.answer.activity.ranCommands": "已运行命令", + "agentDetail.configure.answer.activity.runningCommands": "正在运行命令", "agentDetail.configure.answer.duration.minute": "{{count}}分", "agentDetail.configure.answer.duration.second": "{{count}}秒", + "agentDetail.configure.answer.thinking": "思考过程", "agentDetail.configure.answer.workFinished": "工作已完成", "agentDetail.configure.answer.workedFor": "工作了 {{duration}}", "agentDetail.configure.answer.workingFor": "工作中 {{duration}}", diff --git a/web/i18n/zh-Hant/agent-v-2.json b/web/i18n/zh-Hant/agent-v-2.json index e1a69c01014..d0de3ea9efd 100644 --- a/web/i18n/zh-Hant/agent-v-2.json +++ b/web/i18n/zh-Hant/agent-v-2.json @@ -63,8 +63,11 @@ "agentDetail.configure.advancedSettings.envEditor.valuePlaceholder": "Value", "agentDetail.configure.advancedSettings.label": "進階設定", "agentDetail.configure.advancedSettings.toggle": "展開或收合進階設定", + "agentDetail.configure.answer.activity.ranCommands": "已執行命令", + "agentDetail.configure.answer.activity.runningCommands": "正在執行命令", "agentDetail.configure.answer.duration.minute": "{{count}}分", "agentDetail.configure.answer.duration.second": "{{count}}秒", + "agentDetail.configure.answer.thinking": "思考過程", "agentDetail.configure.answer.workFinished": "工作已完成", "agentDetail.configure.answer.workedFor": "工作了 {{duration}}", "agentDetail.configure.answer.workingFor": "工作中 {{duration}}",