diff --git a/frontend/src/features/tasks/components/MessagesArea.tsx b/frontend/src/features/tasks/components/MessagesArea.tsx index 4d74b210..f6dc2f8c 100644 --- a/frontend/src/features/tasks/components/MessagesArea.tsx +++ b/frontend/src/features/tasks/components/MessagesArea.tsx @@ -7,7 +7,7 @@ import { useEffect, useLayoutEffect, useRef, useState } from 'react'; import { useTaskContext } from '../contexts/taskContext'; import type { TaskDetail, TaskDetailSubtask, Team, GitRepoInfo, GitBranch } from '@/types/api'; -import { Bot, User, Copy, Check, Download } from 'lucide-react'; +import { Bot, User, Copy, Check, Download, ChevronDown, ChevronRight } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { useTranslation } from '@/hooks/useTranslation'; import MarkdownEditor from '@uiw/react-markdown-editor'; @@ -124,6 +124,57 @@ const BubbleTools = ({ ); }; +// Collapsible tool calls component +const CollapsibleToolCalls = ({ toolCalls, t }: { toolCalls: string[]; t: (key: string) => string }) => { + const [isExpanded, setIsExpanded] = useState(false); + + // Parse tool call to extract tool name + const parseToolName = (toolCall: string): string => { + const match = toolCall.match(/\s*(\w+)/); + return match ? match[1] : 'Unknown'; + }; + + return ( +
+ + {isExpanded && ( +
+ {toolCalls.map((call, idx) => { + const toolName = parseToolName(call); + return ( +
+
+ {t('messages.tool_call')}: {toolName} +
+
+                  {call}
+                
+
+ ); + })} +
+ )} +
+ ); +}; + interface MessagesAreaProps { selectedTeam?: Team | null; selectedRepo?: GitRepoInfo | null; @@ -146,6 +197,29 @@ export default function MessagesArea({ const isUserNearBottomRef = useRef(true); const AUTO_SCROLL_THRESHOLD = 32; + // Helper function to detect and extract tool calls from content + const detectToolCalls = ( + content: string + ): { hasToolCalls: boolean; toolCallsCount: number; toolCalls: string[]; cleanedContent: string } => { + const toolCallRegex = /[\s\S]*?<\/tool_call>/g; + const matches = content.match(toolCallRegex); + + return { + hasToolCalls: matches !== null && matches.length > 0, + toolCallsCount: matches?.length || 0, + toolCalls: matches || [], + cleanedContent: matches ? content.replace(toolCallRegex, '').trim() : content, + }; + }; + + // Helper function to remove metadata lines from content + const removeMetaInfo = (content: string): string => { + return content + .replace(/^Current working directory:.*$/m, '') + .replace(/^project url:.*$/m, '') + .trim(); + }; + useEffect(() => { let intervalId: NodeJS.Timeout | null = null; @@ -632,7 +706,10 @@ export default function MessagesArea({ }; const renderAiMessage = (msg: Message, messageIndex: number) => { - const content = msg.content ?? ''; + let content = msg.content ?? ''; + + // Remove metadata first + content = removeMetaInfo(content); // Try to parse as clarification or final_prompt data try { @@ -675,14 +752,29 @@ export default function MessagesArea({ // Default rendering for normal messages if (!content.includes('${$$}$')) { - return renderPlainMessage(msg); + // Check for tool calls in plain messages + const { hasToolCalls, toolCalls, cleanedContent } = detectToolCalls(content); + if (hasToolCalls) { + return ( + <> + {cleanedContent &&
{cleanedContent}
} + + + ); + } + return renderPlainMessage({ ...msg, content: cleanedContent }); } const [prompt, result] = content.split('${$$}$'); + + // Check for tool calls in result + const { hasToolCalls, toolCalls, cleanedContent } = detectToolCalls(result); + return ( <> {prompt &&
{prompt}
} - {result && renderMarkdownResult(result, prompt)} + {hasToolCalls && } + {cleanedContent && renderMarkdownResult(cleanedContent, prompt)} ); }; diff --git a/frontend/src/i18n/locales/en/chat.json b/frontend/src/i18n/locales/en/chat.json index fa345914..5cbfae89 100644 --- a/frontend/src/i18n/locales/en/chat.json +++ b/frontend/src/i18n/locales/en/chat.json @@ -35,7 +35,9 @@ "subtask_completed": "Subtask completed", "subtask_failed": "Subtask failed", "unknown_error": "Unknown error", - "bot": "Bot" + "bot": "Bot", + "tool_calls_summary": "Called {count} tool(s): {tools}", + "tool_call": "Tool Call" }, "settings": { "model": "Model", diff --git a/frontend/src/i18n/locales/zh-CN/chat.json b/frontend/src/i18n/locales/zh-CN/chat.json index da43e668..2d6c8ee6 100644 --- a/frontend/src/i18n/locales/zh-CN/chat.json +++ b/frontend/src/i18n/locales/zh-CN/chat.json @@ -35,7 +35,9 @@ "subtask_completed": "子任务已完成", "subtask_failed": "子任务失败", "unknown_error": "未知错误", - "bot": "机器人" + "bot": "机器人", + "tool_calls_summary": "调用了 {count} 个工具:{tools}", + "tool_call": "工具调用" }, "settings": { "model": "模型",