feat(pipeline-progress): expandable agent rows with output and error details
Click an agent row to toggle its expanded view; expanded rows surface outputSummary and any error text. Adds chevron icons, hover affordance, and a shrink-0 guard on status icons so they keep their size in flex layouts. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { CheckCircle2, Circle, Loader2, XCircle } from "lucide-react";
|
import { useState } from "react";
|
||||||
|
import { CheckCircle2, ChevronDown, ChevronRight, Circle, Loader2, XCircle } from "lucide-react";
|
||||||
import type { AgentStatus } from "@/hooks/use-pipeline-progress";
|
import type { AgentStatus } from "@/hooks/use-pipeline-progress";
|
||||||
|
|
||||||
const AGENT_LABELS: Record<string, string> = {
|
const AGENT_LABELS: Record<string, string> = {
|
||||||
@@ -23,44 +24,82 @@ function formatDuration(ms?: number) {
|
|||||||
function StatusIcon({ status }: { status: AgentStatus["status"] }) {
|
function StatusIcon({ status }: { status: AgentStatus["status"] }) {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case "completed":
|
case "completed":
|
||||||
return <CheckCircle2 className="h-5 w-5 text-green-500" />;
|
return <CheckCircle2 className="h-5 w-5 text-green-500 shrink-0" />;
|
||||||
case "running":
|
case "running":
|
||||||
return <Loader2 className="h-5 w-5 animate-spin text-blue-500" />;
|
return <Loader2 className="h-5 w-5 animate-spin text-blue-500 shrink-0" />;
|
||||||
case "failed":
|
case "failed":
|
||||||
return <XCircle className="h-5 w-5 text-red-500" />;
|
return <XCircle className="h-5 w-5 text-red-500 shrink-0" />;
|
||||||
default:
|
default:
|
||||||
return <Circle className="h-5 w-5 text-muted-foreground" />;
|
return <Circle className="h-5 w-5 text-muted-foreground shrink-0" />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function AgentRow({ agent }: { agent: AgentStatus }) {
|
||||||
|
const [expanded, setExpanded] = useState(false);
|
||||||
|
const hasDetails = agent.outputSummary || agent.error;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`rounded-lg border transition-colors ${hasDetails ? "cursor-pointer hover:bg-muted/50" : ""}`}
|
||||||
|
onClick={() => hasDetails && setExpanded(!expanded)}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-3 p-3">
|
||||||
|
<StatusIcon status={agent.status} />
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<div className="font-medium">
|
||||||
|
{AGENT_LABELS[agent.agentName] || agent.agentName}
|
||||||
|
</div>
|
||||||
|
{!expanded && agent.outputSummary && (
|
||||||
|
<p className="text-sm text-muted-foreground line-clamp-1">
|
||||||
|
{agent.outputSummary}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{!expanded && agent.error && (
|
||||||
|
<p className="text-sm text-red-500 line-clamp-1">{agent.error}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{agent.durationMs && (
|
||||||
|
<span className="text-sm text-muted-foreground shrink-0">
|
||||||
|
{formatDuration(agent.durationMs)}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{hasDetails && (
|
||||||
|
expanded
|
||||||
|
? <ChevronDown className="h-4 w-4 text-muted-foreground shrink-0" />
|
||||||
|
: <ChevronRight className="h-4 w-4 text-muted-foreground shrink-0" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{expanded && (
|
||||||
|
<div className="border-t px-3 py-3 space-y-2">
|
||||||
|
{agent.error && (
|
||||||
|
<div>
|
||||||
|
<div className="text-xs font-medium text-red-500 uppercase mb-1">Error</div>
|
||||||
|
<p className="text-sm text-red-500 whitespace-pre-wrap">{agent.error}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{agent.outputSummary && (
|
||||||
|
<div>
|
||||||
|
<div className="text-xs font-medium text-muted-foreground uppercase mb-1">Output</div>
|
||||||
|
<p className="text-sm text-muted-foreground whitespace-pre-wrap">{agent.outputSummary}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{agent.durationMs && (
|
||||||
|
<div>
|
||||||
|
<div className="text-xs font-medium text-muted-foreground uppercase mb-1">Duration</div>
|
||||||
|
<p className="text-sm text-muted-foreground">{formatDuration(agent.durationMs)}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function PipelineProgress({ agents }: { agents: AgentStatus[] }) {
|
export function PipelineProgress({ agents }: { agents: AgentStatus[] }) {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{agents.map((agent) => (
|
{agents.map((agent) => (
|
||||||
<div
|
<AgentRow key={agent.agentName} agent={agent} />
|
||||||
key={agent.agentName}
|
|
||||||
className="flex items-center gap-3 rounded-lg border p-3"
|
|
||||||
>
|
|
||||||
<StatusIcon status={agent.status} />
|
|
||||||
<div className="flex-1">
|
|
||||||
<div className="font-medium">
|
|
||||||
{AGENT_LABELS[agent.agentName] || agent.agentName}
|
|
||||||
</div>
|
|
||||||
{agent.outputSummary && (
|
|
||||||
<p className="text-sm text-muted-foreground line-clamp-1">
|
|
||||||
{agent.outputSummary}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
{agent.error && (
|
|
||||||
<p className="text-sm text-red-500 line-clamp-1">{agent.error}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{agent.durationMs && (
|
|
||||||
<span className="text-sm text-muted-foreground">
|
|
||||||
{formatDuration(agent.durationMs)}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user