2025-09-08 20:01:49 +08:00
|
|
|
/**
|
|
|
|
|
* @license
|
2025-09-09 16:06:43 +08:00
|
|
|
* Copyright 2025 Qwen
|
2025-09-08 20:01:49 +08:00
|
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
*/
|
|
|
|
|
|
2025-09-09 15:53:10 +08:00
|
|
|
import React, { useMemo } from 'react';
|
2025-09-08 20:01:49 +08:00
|
|
|
import { Box, Text } from 'ink';
|
2025-09-10 13:41:28 +08:00
|
|
|
import { Colors } from '../../../colors.js';
|
2025-09-15 14:11:31 +08:00
|
|
|
import type {
|
2025-09-09 15:53:10 +08:00
|
|
|
TaskResultDisplay,
|
|
|
|
|
SubagentStatsSummary,
|
|
|
|
|
} from '@qwen-code/qwen-code-core';
|
2025-09-10 13:41:28 +08:00
|
|
|
import { theme } from '../../../semantic-colors.js';
|
|
|
|
|
import { useKeypress } from '../../../hooks/useKeypress.js';
|
|
|
|
|
import { COLOR_OPTIONS } from '../constants.js';
|
|
|
|
|
import { fmtDuration } from '../utils.js';
|
2025-09-11 15:16:52 +08:00
|
|
|
import { ToolConfirmationMessage } from '../../messages/ToolConfirmationMessage.js';
|
2025-09-09 15:53:10 +08:00
|
|
|
|
2025-09-10 13:41:28 +08:00
|
|
|
export type DisplayMode = 'default' | 'verbose';
|
2025-09-08 20:01:49 +08:00
|
|
|
|
2025-09-10 13:41:28 +08:00
|
|
|
export interface AgentExecutionDisplayProps {
|
2025-09-08 20:01:49 +08:00
|
|
|
data: TaskResultDisplay;
|
2025-09-11 15:16:52 +08:00
|
|
|
availableHeight?: number;
|
|
|
|
|
childWidth?: number;
|
2025-09-08 20:01:49 +08:00
|
|
|
}
|
|
|
|
|
|
2025-09-09 15:53:10 +08:00
|
|
|
const getStatusColor = (
|
2025-09-11 15:16:52 +08:00
|
|
|
status:
|
|
|
|
|
| TaskResultDisplay['status']
|
|
|
|
|
| 'executing'
|
|
|
|
|
| 'success'
|
|
|
|
|
| 'awaiting_approval',
|
2025-09-09 15:53:10 +08:00
|
|
|
) => {
|
|
|
|
|
switch (status) {
|
|
|
|
|
case 'running':
|
|
|
|
|
case 'executing':
|
2025-09-11 15:16:52 +08:00
|
|
|
case 'awaiting_approval':
|
2025-09-09 15:53:10 +08:00
|
|
|
return theme.status.warning;
|
|
|
|
|
case 'completed':
|
|
|
|
|
case 'success':
|
|
|
|
|
return theme.status.success;
|
2025-09-10 13:41:28 +08:00
|
|
|
case 'cancelled':
|
|
|
|
|
return theme.status.warning;
|
2025-09-09 15:53:10 +08:00
|
|
|
case 'failed':
|
|
|
|
|
return theme.status.error;
|
|
|
|
|
default:
|
|
|
|
|
return Colors.Gray;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getStatusText = (status: TaskResultDisplay['status']) => {
|
|
|
|
|
switch (status) {
|
|
|
|
|
case 'running':
|
|
|
|
|
return 'Running';
|
|
|
|
|
case 'completed':
|
|
|
|
|
return 'Completed';
|
2025-09-10 13:41:28 +08:00
|
|
|
case 'cancelled':
|
|
|
|
|
return 'User Cancelled';
|
2025-09-09 15:53:10 +08:00
|
|
|
case 'failed':
|
|
|
|
|
return 'Failed';
|
|
|
|
|
default:
|
|
|
|
|
return 'Unknown';
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-09-10 13:41:28 +08:00
|
|
|
const MAX_TOOL_CALLS = 5;
|
|
|
|
|
const MAX_TASK_PROMPT_LINES = 5;
|
|
|
|
|
|
2025-09-08 20:01:49 +08:00
|
|
|
/**
|
|
|
|
|
* Component to display subagent execution progress and results.
|
|
|
|
|
* This is now a pure component that renders the provided SubagentExecutionResultDisplay data.
|
|
|
|
|
* Real-time updates are handled by the parent component updating the data prop.
|
|
|
|
|
*/
|
2025-09-10 13:41:28 +08:00
|
|
|
export const AgentExecutionDisplay: React.FC<AgentExecutionDisplayProps> = ({
|
|
|
|
|
data,
|
2025-09-11 15:16:52 +08:00
|
|
|
availableHeight,
|
|
|
|
|
childWidth,
|
2025-09-10 13:41:28 +08:00
|
|
|
}) => {
|
2025-09-09 15:53:10 +08:00
|
|
|
const [displayMode, setDisplayMode] = React.useState<DisplayMode>('default');
|
2025-09-08 20:01:49 +08:00
|
|
|
|
2025-09-09 15:53:10 +08:00
|
|
|
const agentColor = useMemo(() => {
|
|
|
|
|
const colorOption = COLOR_OPTIONS.find(
|
|
|
|
|
(option) => option.name === data.subagentColor,
|
|
|
|
|
);
|
|
|
|
|
return colorOption?.value || theme.text.accent;
|
|
|
|
|
}, [data.subagentColor]);
|
2025-09-08 20:01:49 +08:00
|
|
|
|
2025-09-09 15:53:10 +08:00
|
|
|
const footerText = React.useMemo(() => {
|
|
|
|
|
// This component only listens to keyboard shortcut events when the subagent is running
|
|
|
|
|
if (data.status !== 'running') return '';
|
2025-09-08 20:01:49 +08:00
|
|
|
|
2025-09-09 15:53:10 +08:00
|
|
|
if (displayMode === 'verbose') return 'Press ctrl+r to show less.';
|
|
|
|
|
|
|
|
|
|
if (displayMode === 'default') {
|
2025-09-10 13:41:28 +08:00
|
|
|
const hasMoreLines =
|
|
|
|
|
data.taskPrompt.split('\n').length > MAX_TASK_PROMPT_LINES;
|
|
|
|
|
const hasMoreToolCalls =
|
|
|
|
|
data.toolCalls && data.toolCalls.length > MAX_TOOL_CALLS;
|
2025-09-09 15:53:10 +08:00
|
|
|
|
|
|
|
|
if (hasMoreToolCalls || hasMoreLines) {
|
2025-09-10 13:41:28 +08:00
|
|
|
return 'Press ctrl+r to show more.';
|
2025-09-09 15:53:10 +08:00
|
|
|
}
|
|
|
|
|
return '';
|
|
|
|
|
}
|
|
|
|
|
return '';
|
|
|
|
|
}, [displayMode, data.toolCalls, data.taskPrompt, data.status]);
|
|
|
|
|
|
2025-09-10 13:41:28 +08:00
|
|
|
// Handle ctrl+r keypresses to control display mode
|
2025-09-09 15:53:10 +08:00
|
|
|
useKeypress(
|
|
|
|
|
(key) => {
|
2025-09-10 13:41:28 +08:00
|
|
|
if (key.ctrl && key.name === 'r') {
|
2025-09-09 15:53:10 +08:00
|
|
|
setDisplayMode((current) =>
|
2025-09-10 13:41:28 +08:00
|
|
|
current === 'default' ? 'verbose' : 'default',
|
2025-09-09 15:53:10 +08:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
{ isActive: true },
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Box flexDirection="column" paddingX={1} gap={1}>
|
|
|
|
|
{/* Header with subagent name and status */}
|
|
|
|
|
<Box flexDirection="row">
|
|
|
|
|
<Text bold color={agentColor}>
|
|
|
|
|
{data.subagentName}
|
|
|
|
|
</Text>
|
|
|
|
|
<StatusDot status={data.status} />
|
|
|
|
|
<StatusIndicator status={data.status} />
|
|
|
|
|
</Box>
|
|
|
|
|
|
|
|
|
|
{/* Task description */}
|
|
|
|
|
<TaskPromptSection
|
|
|
|
|
taskPrompt={data.taskPrompt}
|
|
|
|
|
displayMode={displayMode}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
{/* Progress section for running tasks */}
|
|
|
|
|
{data.status === 'running' &&
|
|
|
|
|
data.toolCalls &&
|
|
|
|
|
data.toolCalls.length > 0 && (
|
|
|
|
|
<Box flexDirection="column">
|
|
|
|
|
<ToolCallsList
|
|
|
|
|
toolCalls={data.toolCalls}
|
|
|
|
|
displayMode={displayMode}
|
|
|
|
|
/>
|
|
|
|
|
</Box>
|
|
|
|
|
)}
|
|
|
|
|
|
2025-09-11 15:16:52 +08:00
|
|
|
{/* Inline approval prompt when awaiting confirmation */}
|
|
|
|
|
{data.pendingConfirmation && (
|
|
|
|
|
<Box flexDirection="column">
|
|
|
|
|
<ToolConfirmationMessage
|
|
|
|
|
confirmationDetails={data.pendingConfirmation}
|
|
|
|
|
isFocused={true}
|
|
|
|
|
availableTerminalHeight={availableHeight}
|
|
|
|
|
terminalWidth={childWidth ?? 80}
|
|
|
|
|
/>
|
|
|
|
|
</Box>
|
|
|
|
|
)}
|
|
|
|
|
|
2025-09-09 15:53:10 +08:00
|
|
|
{/* Results section for completed/failed tasks */}
|
2025-09-10 13:41:28 +08:00
|
|
|
{(data.status === 'completed' ||
|
|
|
|
|
data.status === 'failed' ||
|
|
|
|
|
data.status === 'cancelled') && (
|
2025-09-15 14:11:31 +08:00
|
|
|
<ResultsSection data={data} displayMode={displayMode} />
|
|
|
|
|
)}
|
2025-09-09 15:53:10 +08:00
|
|
|
|
|
|
|
|
{/* Footer with keyboard shortcuts */}
|
|
|
|
|
{footerText && (
|
|
|
|
|
<Box flexDirection="row">
|
|
|
|
|
<Text color={Colors.Gray}>{footerText}</Text>
|
|
|
|
|
</Box>
|
|
|
|
|
)}
|
|
|
|
|
</Box>
|
|
|
|
|
);
|
|
|
|
|
};
|
2025-09-08 20:01:49 +08:00
|
|
|
|
|
|
|
|
/**
|
2025-09-09 15:53:10 +08:00
|
|
|
* Task prompt section with truncation support
|
2025-09-08 20:01:49 +08:00
|
|
|
*/
|
2025-09-09 15:53:10 +08:00
|
|
|
const TaskPromptSection: React.FC<{
|
|
|
|
|
taskPrompt: string;
|
|
|
|
|
displayMode: DisplayMode;
|
|
|
|
|
}> = ({ taskPrompt, displayMode }) => {
|
|
|
|
|
const lines = taskPrompt.split('\n');
|
|
|
|
|
const shouldTruncate = lines.length > 10;
|
|
|
|
|
const showFull = displayMode === 'verbose';
|
2025-09-10 13:41:28 +08:00
|
|
|
const displayLines = showFull ? lines : lines.slice(0, MAX_TASK_PROMPT_LINES);
|
2025-09-08 20:01:49 +08:00
|
|
|
|
|
|
|
|
return (
|
2025-09-09 15:53:10 +08:00
|
|
|
<Box flexDirection="column" gap={1}>
|
|
|
|
|
<Box flexDirection="row">
|
|
|
|
|
<Text color={theme.text.primary}>Task Detail: </Text>
|
|
|
|
|
{shouldTruncate && displayMode === 'default' && (
|
2025-09-10 14:35:08 +08:00
|
|
|
<Text color={Colors.Gray}>
|
|
|
|
|
{' '}
|
|
|
|
|
Showing the first {MAX_TASK_PROMPT_LINES} lines.
|
|
|
|
|
</Text>
|
2025-09-09 15:53:10 +08:00
|
|
|
)}
|
|
|
|
|
</Box>
|
|
|
|
|
<Box paddingLeft={1}>
|
|
|
|
|
<Text wrap="wrap">
|
|
|
|
|
{displayLines.join('\n') + (shouldTruncate && !showFull ? '...' : '')}
|
|
|
|
|
</Text>
|
|
|
|
|
</Box>
|
2025-09-08 20:01:49 +08:00
|
|
|
</Box>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
2025-09-09 15:53:10 +08:00
|
|
|
/**
|
|
|
|
|
* Status dot component with similar height as text
|
|
|
|
|
*/
|
|
|
|
|
const StatusDot: React.FC<{
|
|
|
|
|
status: TaskResultDisplay['status'];
|
|
|
|
|
}> = ({ status }) => (
|
|
|
|
|
<Box marginLeft={1} marginRight={1}>
|
|
|
|
|
<Text color={getStatusColor(status)}>●</Text>
|
|
|
|
|
</Box>
|
|
|
|
|
);
|
|
|
|
|
|
2025-09-08 20:01:49 +08:00
|
|
|
/**
|
|
|
|
|
* Status indicator component
|
|
|
|
|
*/
|
|
|
|
|
const StatusIndicator: React.FC<{
|
|
|
|
|
status: TaskResultDisplay['status'];
|
|
|
|
|
}> = ({ status }) => {
|
2025-09-09 15:53:10 +08:00
|
|
|
const color = getStatusColor(status);
|
|
|
|
|
const text = getStatusText(status);
|
|
|
|
|
return <Text color={color}>{text}</Text>;
|
2025-09-08 20:01:49 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
2025-09-09 15:53:10 +08:00
|
|
|
* Tool calls list - format consistent with ToolInfo in ToolMessage.tsx
|
2025-09-08 20:01:49 +08:00
|
|
|
*/
|
2025-09-09 15:53:10 +08:00
|
|
|
const ToolCallsList: React.FC<{
|
|
|
|
|
toolCalls: TaskResultDisplay['toolCalls'];
|
|
|
|
|
displayMode: DisplayMode;
|
|
|
|
|
}> = ({ toolCalls, displayMode }) => {
|
|
|
|
|
const calls = toolCalls || [];
|
2025-09-10 13:41:28 +08:00
|
|
|
const shouldTruncate = calls.length > MAX_TOOL_CALLS;
|
2025-09-09 15:53:10 +08:00
|
|
|
const showAll = displayMode === 'verbose';
|
2025-09-10 13:41:28 +08:00
|
|
|
const displayCalls = showAll ? calls : calls.slice(-MAX_TOOL_CALLS); // Show last 5
|
2025-09-09 15:53:10 +08:00
|
|
|
|
|
|
|
|
// Reverse the order to show most recent first
|
|
|
|
|
const reversedDisplayCalls = [...displayCalls].reverse();
|
2025-09-08 20:01:49 +08:00
|
|
|
|
2025-09-09 15:53:10 +08:00
|
|
|
return (
|
|
|
|
|
<Box flexDirection="column">
|
|
|
|
|
<Box flexDirection="row" marginBottom={1}>
|
|
|
|
|
<Text color={theme.text.primary}>Tools:</Text>
|
|
|
|
|
{shouldTruncate && displayMode === 'default' && (
|
|
|
|
|
<Text color={Colors.Gray}>
|
|
|
|
|
{' '}
|
2025-09-10 13:41:28 +08:00
|
|
|
Showing the last {MAX_TOOL_CALLS} of {calls.length} tools.
|
2025-09-09 15:53:10 +08:00
|
|
|
</Text>
|
|
|
|
|
)}
|
|
|
|
|
</Box>
|
|
|
|
|
{reversedDisplayCalls.map((toolCall, index) => (
|
2025-09-09 16:06:43 +08:00
|
|
|
<ToolCallItem key={`${toolCall.name}-${index}`} toolCall={toolCall} />
|
|
|
|
|
))}
|
2025-09-08 20:01:49 +08:00
|
|
|
</Box>
|
2025-09-09 15:53:10 +08:00
|
|
|
);
|
|
|
|
|
};
|
2025-09-08 20:01:49 +08:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Individual tool call item - consistent with ToolInfo format
|
|
|
|
|
*/
|
2025-09-09 15:53:10 +08:00
|
|
|
const ToolCallItem: React.FC<{
|
2025-09-08 20:01:49 +08:00
|
|
|
toolCall: {
|
|
|
|
|
name: string;
|
2025-09-11 15:16:52 +08:00
|
|
|
status: 'executing' | 'awaiting_approval' | 'success' | 'failed';
|
2025-09-08 20:01:49 +08:00
|
|
|
error?: string;
|
|
|
|
|
args?: Record<string, unknown>;
|
|
|
|
|
result?: string;
|
2025-09-09 15:53:10 +08:00
|
|
|
resultDisplay?: string;
|
|
|
|
|
description?: string;
|
2025-09-08 20:01:49 +08:00
|
|
|
};
|
|
|
|
|
}> = ({ toolCall }) => {
|
|
|
|
|
const STATUS_INDICATOR_WIDTH = 3;
|
|
|
|
|
|
|
|
|
|
// Map subagent status to ToolCallStatus-like display
|
|
|
|
|
const statusIcon = React.useMemo(() => {
|
2025-09-09 15:53:10 +08:00
|
|
|
const color = getStatusColor(toolCall.status);
|
2025-09-08 20:01:49 +08:00
|
|
|
switch (toolCall.status) {
|
|
|
|
|
case 'executing':
|
2025-09-09 15:53:10 +08:00
|
|
|
return <Text color={color}>⊷</Text>; // Using same as ToolMessage
|
2025-09-11 15:16:52 +08:00
|
|
|
case 'awaiting_approval':
|
|
|
|
|
return <Text color={theme.status.warning}>?</Text>;
|
2025-09-08 20:01:49 +08:00
|
|
|
case 'success':
|
2025-09-12 17:52:23 +08:00
|
|
|
return <Text color={color}>✓</Text>;
|
2025-09-08 20:01:49 +08:00
|
|
|
case 'failed':
|
|
|
|
|
return (
|
2025-09-09 15:53:10 +08:00
|
|
|
<Text color={color} bold>
|
2025-09-08 20:01:49 +08:00
|
|
|
x
|
|
|
|
|
</Text>
|
|
|
|
|
);
|
|
|
|
|
default:
|
2025-09-09 15:53:10 +08:00
|
|
|
return <Text color={color}>o</Text>;
|
2025-09-08 20:01:49 +08:00
|
|
|
}
|
|
|
|
|
}, [toolCall.status]);
|
|
|
|
|
|
2025-09-09 15:53:10 +08:00
|
|
|
const description = React.useMemo(() => {
|
|
|
|
|
if (!toolCall.description) return '';
|
|
|
|
|
const firstLine = toolCall.description.split('\n')[0];
|
|
|
|
|
return firstLine.length > 80
|
|
|
|
|
? firstLine.substring(0, 80) + '...'
|
|
|
|
|
: firstLine;
|
|
|
|
|
}, [toolCall.description]);
|
2025-09-08 20:01:49 +08:00
|
|
|
|
2025-09-09 15:53:10 +08:00
|
|
|
// Get first line of resultDisplay for truncated output
|
2025-09-08 20:01:49 +08:00
|
|
|
const truncatedOutput = React.useMemo(() => {
|
2025-09-09 15:53:10 +08:00
|
|
|
if (!toolCall.resultDisplay) return '';
|
|
|
|
|
const firstLine = toolCall.resultDisplay.split('\n')[0];
|
2025-09-08 20:01:49 +08:00
|
|
|
return firstLine.length > 80
|
|
|
|
|
? firstLine.substring(0, 80) + '...'
|
|
|
|
|
: firstLine;
|
2025-09-09 15:53:10 +08:00
|
|
|
}, [toolCall.resultDisplay]);
|
2025-09-08 20:01:49 +08:00
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Box flexDirection="column" paddingLeft={1} marginBottom={0}>
|
|
|
|
|
{/* First line: status icon + tool name + description (consistent with ToolInfo) */}
|
|
|
|
|
<Box flexDirection="row">
|
|
|
|
|
<Box minWidth={STATUS_INDICATOR_WIDTH}>{statusIcon}</Box>
|
|
|
|
|
<Text wrap="truncate-end">
|
2025-09-09 20:53:53 +08:00
|
|
|
<Text>{toolCall.name}</Text>{' '}
|
2025-09-08 20:01:49 +08:00
|
|
|
<Text color={Colors.Gray}>{description}</Text>
|
|
|
|
|
{toolCall.error && (
|
2025-09-09 15:53:10 +08:00
|
|
|
<Text color={theme.status.error}> - {toolCall.error}</Text>
|
2025-09-08 20:01:49 +08:00
|
|
|
)}
|
|
|
|
|
</Text>
|
|
|
|
|
</Box>
|
|
|
|
|
|
|
|
|
|
{/* Second line: truncated returnDisplay output */}
|
|
|
|
|
{truncatedOutput && (
|
|
|
|
|
<Box flexDirection="row" paddingLeft={STATUS_INDICATOR_WIDTH}>
|
|
|
|
|
<Text color={Colors.Gray}>{truncatedOutput}</Text>
|
|
|
|
|
</Box>
|
|
|
|
|
)}
|
|
|
|
|
</Box>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Execution summary details component
|
|
|
|
|
*/
|
|
|
|
|
const ExecutionSummaryDetails: React.FC<{
|
|
|
|
|
data: TaskResultDisplay;
|
2025-09-09 15:53:10 +08:00
|
|
|
displayMode: DisplayMode;
|
|
|
|
|
}> = ({ data, displayMode: _displayMode }) => {
|
|
|
|
|
const stats = data.executionSummary;
|
|
|
|
|
|
|
|
|
|
if (!stats) {
|
2025-09-08 20:01:49 +08:00
|
|
|
return (
|
|
|
|
|
<Box flexDirection="column" paddingLeft={1}>
|
|
|
|
|
<Text color={Colors.Gray}>• No summary available</Text>
|
|
|
|
|
</Box>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Box flexDirection="column" paddingLeft={1}>
|
|
|
|
|
<Text>
|
2025-09-09 15:53:10 +08:00
|
|
|
• <Text>Duration: {fmtDuration(stats.totalDurationMs)}</Text>
|
2025-09-08 20:01:49 +08:00
|
|
|
</Text>
|
|
|
|
|
<Text>
|
2025-09-09 15:53:10 +08:00
|
|
|
• <Text>Rounds: {stats.rounds}</Text>
|
2025-09-08 20:01:49 +08:00
|
|
|
</Text>
|
|
|
|
|
<Text>
|
2025-09-09 15:53:10 +08:00
|
|
|
• <Text>Tokens: {stats.totalTokens.toLocaleString()}</Text>
|
2025-09-08 20:01:49 +08:00
|
|
|
</Text>
|
|
|
|
|
</Box>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Tool usage statistics component
|
|
|
|
|
*/
|
|
|
|
|
const ToolUsageStats: React.FC<{
|
2025-09-09 15:53:10 +08:00
|
|
|
executionSummary?: SubagentStatsSummary;
|
|
|
|
|
}> = ({ executionSummary }) => {
|
|
|
|
|
if (!executionSummary) {
|
|
|
|
|
return (
|
|
|
|
|
<Box flexDirection="column" paddingLeft={1}>
|
|
|
|
|
<Text color={Colors.Gray}>• No tool usage data available</Text>
|
|
|
|
|
</Box>
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-09-08 20:01:49 +08:00
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<Box flexDirection="column" paddingLeft={1}>
|
|
|
|
|
<Text>
|
2025-09-09 17:52:07 +08:00
|
|
|
• <Text>Total Calls:</Text> {executionSummary.totalToolCalls}
|
2025-09-08 20:01:49 +08:00
|
|
|
</Text>
|
|
|
|
|
<Text>
|
2025-09-09 17:52:07 +08:00
|
|
|
• <Text>Success Rate:</Text>{' '}
|
2025-09-09 15:53:10 +08:00
|
|
|
<Text color={Colors.AccentGreen}>
|
|
|
|
|
{executionSummary.successRate.toFixed(1)}%
|
|
|
|
|
</Text>{' '}
|
|
|
|
|
(
|
|
|
|
|
<Text color={Colors.AccentGreen}>
|
|
|
|
|
{executionSummary.successfulToolCalls} success
|
|
|
|
|
</Text>
|
|
|
|
|
,{' '}
|
|
|
|
|
<Text color={Colors.AccentRed}>
|
|
|
|
|
{executionSummary.failedToolCalls} failed
|
|
|
|
|
</Text>
|
|
|
|
|
)
|
2025-09-08 20:01:49 +08:00
|
|
|
</Text>
|
|
|
|
|
</Box>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Results section for completed executions - matches the clean layout from the image
|
|
|
|
|
*/
|
|
|
|
|
const ResultsSection: React.FC<{
|
|
|
|
|
data: TaskResultDisplay;
|
2025-09-09 15:53:10 +08:00
|
|
|
displayMode: DisplayMode;
|
|
|
|
|
}> = ({ data, displayMode }) => (
|
|
|
|
|
<Box flexDirection="column" gap={1}>
|
2025-09-08 20:01:49 +08:00
|
|
|
{/* Tool calls section - clean list format */}
|
2025-09-09 15:53:10 +08:00
|
|
|
{data.toolCalls && data.toolCalls.length > 0 && (
|
|
|
|
|
<ToolCallsList toolCalls={data.toolCalls} displayMode={displayMode} />
|
2025-09-08 20:01:49 +08:00
|
|
|
)}
|
|
|
|
|
|
2025-09-10 13:41:28 +08:00
|
|
|
{/* Execution Summary section - hide when cancelled */}
|
2025-09-11 15:16:52 +08:00
|
|
|
{data.status === 'completed' && (
|
2025-09-10 13:41:28 +08:00
|
|
|
<Box flexDirection="column">
|
|
|
|
|
<Box flexDirection="row" marginBottom={1}>
|
|
|
|
|
<Text color={theme.text.primary}>Execution Summary:</Text>
|
|
|
|
|
</Box>
|
|
|
|
|
<ExecutionSummaryDetails data={data} displayMode={displayMode} />
|
2025-09-08 20:01:49 +08:00
|
|
|
</Box>
|
2025-09-10 13:41:28 +08:00
|
|
|
)}
|
2025-09-08 20:01:49 +08:00
|
|
|
|
2025-09-10 13:41:28 +08:00
|
|
|
{/* Tool Usage section - hide when cancelled */}
|
2025-09-11 15:16:52 +08:00
|
|
|
{data.status === 'completed' && data.executionSummary && (
|
2025-09-08 20:01:49 +08:00
|
|
|
<Box flexDirection="column">
|
|
|
|
|
<Box flexDirection="row" marginBottom={1}>
|
2025-09-09 16:06:43 +08:00
|
|
|
<Text color={theme.text.primary}>Tool Usage:</Text>
|
2025-09-08 20:01:49 +08:00
|
|
|
</Box>
|
2025-09-09 15:53:10 +08:00
|
|
|
<ToolUsageStats executionSummary={data.executionSummary} />
|
2025-09-08 20:01:49 +08:00
|
|
|
</Box>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* Error reason for failed tasks */}
|
2025-09-10 13:41:28 +08:00
|
|
|
{data.status === 'cancelled' && (
|
2025-09-09 15:53:10 +08:00
|
|
|
<Box flexDirection="row">
|
2025-09-10 13:41:28 +08:00
|
|
|
<Text color={theme.status.warning}>⏹ User Cancelled</Text>
|
2025-09-08 20:01:49 +08:00
|
|
|
</Box>
|
|
|
|
|
)}
|
2025-09-11 15:16:52 +08:00
|
|
|
{data.status === 'failed' && (
|
|
|
|
|
<Box flexDirection="row">
|
|
|
|
|
<Text color={theme.status.error}>Task Failed: </Text>
|
|
|
|
|
<Text color={theme.status.error}>{data.terminateReason}</Text>
|
|
|
|
|
</Box>
|
|
|
|
|
)}
|
2025-09-08 20:01:49 +08:00
|
|
|
</Box>
|
|
|
|
|
);
|