'use client';
import React from 'react';
import { Activity, CheckCircle2, ChevronDown, Circle, FileText, LoaderCircle, PanelRightOpen, Sparkles, TerminalSquare, Users, X } from 'lucide-react';
import { Badge } from '@/components/ui/badge';
import { ScrollArea } from '@/components/ui/scroll-area';
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 TaskUiModel,
type TaskUiStatus,
} from '@/lib/task-ui-model';
import { containedLongTextClass, containedPreservedLongTextClass } from '@/lib/text-wrapping';
import type { BackendTask, SessionProcessProjection, TaskTimelineCard } from '@/types';
function StatusBadge({ status }: { status: TaskUiStatus }) {
const { locale } = useAppI18n();
return (
{taskUiStatusLabel(status, locale)}
);
}
function ProgressCard({
icon,
title,
label,
status,
children,
}: {
icon: React.ReactNode;
title: string;
label: string;
status: TaskUiStatus;
children: React.ReactNode;
}) {
const { locale } = useAppI18n();
const [open, setOpen] = React.useState(true);
return (
{open ? (
{children}
) : null}
);
}
function SummarySection({ model }: { model: TaskUiModel }) {
return (
{model.summary.summary}
);
}
function SkillSection({ model }: { model: TaskUiModel }) {
const { locale } = useAppI18n();
if (model.skills.length === 0) {
return {pickAppText(locale, '暂无 Skill 选择', 'No skill selected yet')}
;
}
const primary = model.skills[0];
return (
{primary.name}
{primary.createdAt ?
{formatTaskRuntimeTime(primary.createdAt, locale)}
: null}
{primary.summary ? (
{primary.summary}
) : null}
);
}
function ToolsSection({ model }: { model: TaskUiModel }) {
const { locale } = useAppI18n();
const attempts = model.attempts.filter((attempt) => attempt.tools.length > 0);
if (attempts.length === 0) {
return {pickAppText(locale, '暂无工具调用', 'No tool calls yet')}
;
}
return (
{attempts.map((attempt) => (
{attempt.title}
{attempt.tools.length} calls
{attempt.tools.map((tool) => (
{tool.toolName}
{tool.status === 'done' ? : tool.status === 'running' ? : }
{taskUiStatusLabel(tool.status, locale)}
{formatSidebarToolDuration(tool, locale)}
))}
))}
);
}
function formatSidebarToolDuration(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 flattenAgents(model: TaskUiModel) {
const output: Array<{ id: string; name: string; status: TaskUiStatus; depth: number }> = [];
const visit = (node: TaskUiModel['agentTree'][number], depth: number) => {
output.push({ id: node.runId, name: node.title || node.name, status: node.status, depth });
node.children.forEach((child) => visit(child, depth + 1));
};
model.agentTree.forEach((node) => visit(node, 0));
return output;
}
function AgentSection({ model }: { model: TaskUiModel }) {
const { locale } = useAppI18n();
const rows = flattenAgents(model).slice(0, 6);
if (!model.team.hasTeam) {
return (
{pickAppText(locale, '本轮为 Main Agent 单线程执行,未启动 Agent Team。', 'This run uses the Main Agent only; no Agent Team was started.')}
);
}
if (rows.length === 0) {
return {pickAppText(locale, 'Agent Team 已启动,等待节点数据', 'Agent Team started; waiting for node data')}
;
}
return (
{rows[0]?.name}
{rows.map((node) => (
{node.name}
{node.status === 'done' ? : node.status === 'running' ? : }
{taskUiStatusLabel(node.status, locale)}
))}
);
}
function statusForSummary(task: BackendTask): TaskUiStatus {
if (task.status === 'awaiting_acceptance' || task.status === 'closed') return 'done';
if (task.status === 'running') return 'running';
return 'waiting';
}
function primarySkillName(model: TaskUiModel) {
return model.skills[0]?.name || '';
}
function hasExecutionStructure(model: TaskUiModel): boolean {
return model.team.hasTeam || model.agentTree.length > 0;
}
function toolStatus(model: TaskUiModel): TaskUiStatus {
if (model.tools.some((tool) => tool.status === 'running')) return 'running';
if (model.tools.some((tool) => tool.status === 'error')) return 'error';
return model.tools.length ? 'done' : 'waiting';
}
function agentStatus(model: TaskUiModel): TaskUiStatus {
if (model.agentTree.some((node) => node.status === 'running' || node.children.some((child) => child.status === 'running'))) return 'running';
if (!model.team.hasTeam) return 'waiting';
if (model.agentTree.some((node) => node.status === 'error' || node.children.some((child) => child.status === 'error'))) return 'error';
return model.agentTree.length ? 'done' : model.team.status;
}
function ProgressPanel({
task,
process,
cards,
isLive,
onClose,
}: {
task: BackendTask | null;
process: SessionProcessProjection | null;
cards: TaskTimelineCard[];
isLive: boolean;
onClose?: () => void;
}) {
const { locale } = useAppI18n();
const model = task
? buildTaskUiModel({
task,
process: process ?? { runs: [], events: [], artifacts: [] },
cards,
locale,
})
: null;
return (
{pickAppText(locale, '当前会话的运行进度', 'Current Session Progress')}
{isLive ? : null}
{isLive
? pickAppText(locale, '任务时间线实时更新', 'Task timeline updates live')
: pickAppText(locale, '与任务详情时间线一致', 'Matches the Task detail timeline')}
{onClose ? (
) : null}
{model ? (
}
title={pickAppText(locale, '任务摘要', 'Task summary')}
label={model.summary.title}
status={task ? statusForSummary(task) : 'waiting'}
>
{model.skills.length > 0 ? (
}
title={pickAppText(locale, 'Skill 选择', 'Skill selection')}
label={primarySkillName(model)}
status={model.skills[0]?.status || 'waiting'}
>
) : null}
{model.tools.length > 0 ? (
}
title={pickAppText(locale, '工具调用', 'Tool calls')}
label={pickAppText(locale, `${model.tools.length} 个工具调用`, `${model.tools.length} tool calls`)}
status={toolStatus(model)}
>
) : null}
{hasExecutionStructure(model) ? (
}
title={pickAppText(locale, '执行结构', 'Execution structure')}
label={
model.team.hasTeam
? pickAppText(locale, `Agent Team · ${model.team.outcome}`, `Agent Team · ${model.team.outcome}`)
: pickAppText(locale, 'Agent run', 'Agent run')
}
status={agentStatus(model)}
>
) : null}
) : (
{pickAppText(locale, '当前会话暂无运行任务', 'No running task in this session')}
)}
);
}
export function CurrentSessionProgressSidebar({
task,
process,
cards,
isLive,
}: {
task: BackendTask | null;
process: SessionProcessProjection | null;
cards: TaskTimelineCard[];
isLive: boolean;
}) {
const { locale } = useAppI18n();
const [mobileOpen, setMobileOpen] = React.useState(false);
return (
<>
{mobileOpen ? (
) : null}
>
);
}