'use client'; import React from 'react'; import { BarChart3, CheckCircle2, ChevronDown, Clock3, Database, Download, Eye, FileImage, FileJson, FileText, Globe2, Grid2X2, ListFilter, Network, PackageOpen, RefreshCw, ShieldCheck, Table2, UserRound, } from 'lucide-react'; import type { TaskFeedbackType } from '@/components/task-detail/TaskAcceptanceCard'; import type { TaskResultAcceptance } from '@/components/task-detail/TaskTimelineCard'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { formatTaskRuntimeDuration, formatTaskRuntimeTime } from '@/components/task-runtime/TaskRuntimeShared'; import { pickAppText } from '@/lib/i18n/core'; import { useAppI18n } from '@/lib/i18n/provider'; import { buildTaskUiModel, taskUiStatusClass, taskUiStatusLabel, type TaskUiAgentNode, type TaskUiArtifact, type TaskUiAttempt, type TaskUiModel, type TaskUiStatus, type TaskUiStep, } from '@/lib/task-ui-model'; import { containedLongTextClass, containedPreservedLongTextClass } from '@/lib/text-wrapping'; import type { BackendTask, SessionProcessProjection, TaskTimelineCard } from '@/types'; type Props = { task: BackendTask; process: SessionProcessProjection; cards: TaskTimelineCard[]; isLive: boolean; resultAcceptance?: TaskResultAcceptance; reviewTargetId?: string; }; function StatusBadge({ status, compact = false }: { status: TaskUiStatus; compact?: boolean }) { const { locale } = useAppI18n(); return ( {taskUiStatusLabel(status, locale)} ); } function Section({ title, children, action, className = '', }: { title: string; children: React.ReactNode; action?: React.ReactNode; className?: string; }) { return (

{title}

{action}
{children}
); } function EmptyState({ children }: { children: React.ReactNode }) { return (
{children}
); } function iconForStep(kind: TaskUiStep['kind']) { if (kind === 'skill') return Grid2X2; if (kind === 'tool') return Clock3; if (kind === 'agent') return Network; if (kind === 'artifact') return FileText; if (kind === 'result') return BarChart3; return FileText; } function statusDotClass(status: TaskUiStatus) { if (status === 'done') return 'bg-[#22733A]'; if (status === 'running') return 'bg-[#C47B00]'; if (status === 'error') return 'bg-[#9D3D2F]'; if (status === 'cancelled') return 'bg-[#756A64]'; return 'bg-[#8D8782]'; } function ExecutionFlow({ model }: { model: TaskUiModel }) { const { locale } = useAppI18n(); const steps = model.steps.slice(0, 6); const columnClass = steps.length >= 6 ? 'grid-cols-6' : steps.length >= 4 ? 'grid-cols-4' : steps.length >= 2 ? 'grid-cols-2' : 'grid-cols-1'; return (
查看详情 } >
{steps.length > 1 ?
: null} {steps.map((step) => { const Icon = iconForStep(step.kind); return (
{step.status === 'done' ? : }

{step.title}

{step.createdAt ? {formatTaskRuntimeTime(step.createdAt, locale)} : null}
{step.summary ? (

{step.summary}

) : null}
); })}
); } function progressColor(status: TaskUiStatus) { if (status === 'done') return '#137333'; if (status === 'running') return '#D48500'; if (status === 'error') return '#9D3D2F'; if (status === 'cancelled') return '#756A64'; return '#E3DFDC'; } function AgentCard({ agent, root = false }: { agent: TaskUiAgentNode; root?: boolean }) { return (
{agent.title || agent.name}
{agent.progress}%
); } function AgentDAG({ model }: { model: TaskUiModel }) { const { locale } = useAppI18n(); const roots = model.agentTree; const root = roots.find((node) => node.children.length > 0) ?? roots[0]; const children = root?.children.length ? root.children : roots.filter((node) => node.runId !== root?.runId); const visibleChildren = children.slice(0, 5); if (!model.team.hasTeam) { return null; } return (
{model.team.outcome}
} >
{roots.length === 0 ? ( {pickAppText(locale, '暂无 Agent Team 数据', 'No Agent Team data yet')} ) : (
{visibleChildren.length > 0 ? ( <>
{visibleChildren.map((child, index) => (
))} ) : null}
{visibleChildren.length > 0 ? (
{visibleChildren.map((agent) => ( ))}
) : null}
)}
); } function RunPath({ model, selectedAttemptId, onSelectAttempt, }: { model: TaskUiModel; selectedAttemptId: string | null; onSelectAttempt: (attemptId: string) => void; }) { const { locale } = useAppI18n(); const [expandedIds, setExpandedIds] = React.useState>(() => new Set()); const attempts = model.attempts.filter((attempt) => attempt.runs.length > 0 || attempt.tools.length > 0 || attempt.result); if (attempts.length === 0) return null; return (
{attempts.length} runs } >
{attempts.map((attempt) => (
onSelectAttempt(attempt.id)} onKeyDown={(event) => { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); onSelectAttempt(attempt.id); } }} role="group" tabIndex={0} aria-label={pickAppText(locale, `选择${attempt.title}`, `Select ${attempt.title}`)} className={`rounded-lg border p-4 transition-colors ${ selectedAttemptId === attempt.id ? 'border-[#1D1715] bg-white shadow-[0_6px_18px_rgba(31,24,20,0.06)]' : 'border-[#E1DCD8] bg-[#FBFAF9]' } cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#1D1715] focus-visible:ring-offset-2`} >

{attempt.title}

{formatTaskRuntimeTime(attempt.startedAt, locale)} {attempt.finishedAt ? ` · ${formatAttemptDuration(attempt.startedAt, attempt.finishedAt, locale)}` : ''}
{attempt.tools.length} tools
{attempt.runs.length > 0 ? (
{attempt.runs.map((run, index) => ( {index > 0 ? : null} {run.actorName || run.title} ))}
) : null} {attempt.result && expandedIds.has(attempt.id) ? (
{pickAppText(locale, '本次结果', 'Attempt result')}

{attempt.result.summary || attempt.result.title}

) : null}
))}
); } function formatAttemptDuration(startedAt: string, finishedAt: string, locale: string): string { const startMs = new Date(startedAt).getTime(); const finishMs = new Date(finishedAt).getTime(); if (Number.isNaN(startMs) || Number.isNaN(finishMs) || finishMs < startMs) return '-'; return formatTaskRuntimeDuration(finishMs - startMs, locale); } function toolsForAttempt(model: TaskUiModel, selectedAttemptId: string | null): TaskUiAttempt { return ( model.attempts.find((attempt) => attempt.id === selectedAttemptId) ?? model.attempts.at(-1) ?? { id: 'all', index: 1, title: 'Agent', status: 'waiting', startedAt: '', runs: [], tools: model.tools, } ); } function ToolCalls({ model, selectedAttemptId }: { model: TaskUiModel; selectedAttemptId: string | null }) { const { locale } = useAppI18n(); const selectedAttempt = toolsForAttempt(model, selectedAttemptId); const agents = Array.from(new Set(selectedAttempt.tools.map((tool) => tool.actorName || 'Agent'))); const [selectedAgent, setSelectedAgent] = React.useState(null); const activeAgent = selectedAgent && agents.includes(selectedAgent) ? selectedAgent : agents[0] ?? 'Agent'; const visibleTools = selectedAttempt.tools.filter((tool) => (tool.actorName || 'Agent') === activeAgent); return (
{selectedAttempt.title}
} > {selectedAttempt.tools.length === 0 ? (
{pickAppText(locale, '暂无工具调用', 'No tool calls yet')}
) : (
工具名称 摘要 状态 运行时间
{visibleTools.map((tool) => (
{tool.toolName} {tool.summary} {formatToolDuration(tool, locale)}
))}
)} ); } function formatToolDuration(tool: TaskUiModel['tools'][number], locale: string): string { if (typeof tool.durationMs === 'number') { return formatTaskRuntimeDuration(tool.durationMs, locale); } if (tool.status === 'running' && tool.createdAt) { const startMs = new Date(tool.createdAt).getTime(); if (!Number.isNaN(startMs)) { return formatTaskRuntimeDuration(Date.now() - startMs, locale); } } return '-'; } function iconForArtifact(artifact: TaskUiArtifact) { if (artifact.type === 'json') return FileJson; if (artifact.type === 'image') return FileImage; return FileText; } function WorkspaceFiles({ model }: { model: TaskUiModel }) { const { locale } = useAppI18n(); return (
} >
{model.artifacts.length === 0 ? (
{pickAppText(locale, '暂无 Workspace 文件', 'No workspace files yet')}
) : ( <>
文件名 类型 大小 状态 操作
{model.artifacts.slice(0, 6).map((artifact) => { const Icon = iconForArtifact(artifact); return (
{artifact.title}
{artifact.type.toUpperCase()} {artifact.sizeLabel || '-'}
); })} {model.artifacts.length > 6 ? ( ) : null}
)}
); } function ResultPanel({ model, resultAcceptance, reviewTargetId, }: { model: TaskUiModel; resultAcceptance?: TaskResultAcceptance; reviewTargetId?: string; }) { const { locale } = useAppI18n(); const [busyAction, setBusyAction] = React.useState(null); const submit = async (type: TaskFeedbackType) => { if (!resultAcceptance || busyAction) return; setBusyAction(type); try { await resultAcceptance.onSubmit(type); } finally { setBusyAction(null); } }; return (
}>
{model.result.summary ? (

{model.result.summary}

{model.result.bullets.length > 0 ? (
{model.result.bullets.map((item, index) => { const Icon = [Globe2, Table2, BarChart3, ShieldCheck][index % 4]; return (
{item}
); })}
) : null}
) : ( {pickAppText(locale, '暂无本轮结果', 'No result for this run yet')} )}
); } export function TaskExecutionWorkspace({ task, process, cards, resultAcceptance, reviewTargetId }: Props) { const { locale } = useAppI18n(); const model = React.useMemo( () => buildTaskUiModel({ task, process, cards, locale }), [cards, locale, process, task], ); const latestAttemptId = model.attempts.at(-1)?.id ?? null; const [selectedAttemptState, setSelectedAttemptState] = React.useState(latestAttemptId); const selectedAttemptId = model.attempts.some((attempt) => attempt.id === selectedAttemptState) ? selectedAttemptState : latestAttemptId; return (
); }