mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-13 16:14:57 +00:00
enhance(ui): improve realtime event cards with richer payload rendering
Split agent event cards into RunEventCard and ToolEventCard with full context rows (channel, delegation, parent agent, team task, call ID). Add channel badge and delegation ID to delegation lifecycle cards, completed task count to announce cards, task ID to task/message cards. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
9b9e99e72a
commit
a0f69a6fee
4 files changed
+124
-31
No files matched your search
@@ -11,44 +11,35 @@ export function AgentEventCard({ entry, resolveAgent }: Props) {
|
||||
const p = entry.payload as EnrichedAgentEventPayload;
|
||||
const subtype = p.type;
|
||||
|
||||
const subtypeBadgeVariant =
|
||||
if (subtype === "tool.call" || subtype === "tool.result") {
|
||||
return <ToolEventCard p={p} resolveAgent={resolveAgent} />;
|
||||
}
|
||||
|
||||
return <RunEventCard p={p} resolveAgent={resolveAgent} />;
|
||||
}
|
||||
|
||||
/** run.started / run.completed / run.failed / run.retrying */
|
||||
function RunEventCard({ p, resolveAgent }: { p: EnrichedAgentEventPayload; resolveAgent: Props["resolveAgent"] }) {
|
||||
const subtype = p.type;
|
||||
const variant =
|
||||
subtype === "run.completed"
|
||||
? ("success" as const)
|
||||
: subtype === "run.failed"
|
||||
? ("destructive" as const)
|
||||
: subtype === "run.started" || subtype === "run.retrying"
|
||||
? ("info" as const)
|
||||
: ("secondary" as const);
|
||||
: subtype === "run.retrying"
|
||||
? ("warning" as const)
|
||||
: ("info" as const);
|
||||
|
||||
return (
|
||||
<div className="space-y-1 text-sm">
|
||||
<div className="flex min-w-0 flex-wrap items-center gap-x-1.5 gap-y-0.5">
|
||||
<Badge variant={subtypeBadgeVariant} className="shrink-0 font-mono text-xs">
|
||||
<Badge variant={variant} className="shrink-0 font-mono text-xs">
|
||||
{subtype}
|
||||
</Badge>
|
||||
<span className="truncate font-medium">{resolveAgent(p.agentId)}</span>
|
||||
{p.delegationId && (
|
||||
<span className="shrink-0 text-xs text-muted-foreground">
|
||||
deleg: {p.delegationId.slice(0, 8)}
|
||||
</span>
|
||||
)}
|
||||
{p.parentAgentId && (
|
||||
<span className="truncate text-xs text-muted-foreground">
|
||||
parent: {resolveAgent(p.parentAgentId)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{(subtype === "tool.call" || subtype === "tool.result") && p.payload && (
|
||||
<div className="flex items-center gap-1 text-xs">
|
||||
{p.payload.name && (
|
||||
<span className="truncate font-mono font-medium">{p.payload.name}</span>
|
||||
)}
|
||||
{p.payload.is_error && (
|
||||
<Badge variant="destructive" className="shrink-0 text-xs">error</Badge>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<ContextRow p={p} resolveAgent={resolveAgent} />
|
||||
|
||||
{subtype === "run.failed" && p.payload?.error && (
|
||||
<p className="break-words text-xs text-destructive line-clamp-2">{p.payload.error}</p>
|
||||
@@ -56,3 +47,76 @@ export function AgentEventCard({ entry, resolveAgent }: Props) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/** tool.call / tool.result */
|
||||
function ToolEventCard({ p, resolveAgent }: { p: EnrichedAgentEventPayload; resolveAgent: Props["resolveAgent"] }) {
|
||||
const isResult = p.type === "tool.result";
|
||||
const toolName = p.payload?.name;
|
||||
const isError = isResult && p.payload?.is_error;
|
||||
|
||||
return (
|
||||
<div className="space-y-1 text-sm">
|
||||
<div className="flex min-w-0 flex-wrap items-center gap-x-1.5 gap-y-0.5">
|
||||
<Badge variant={isError ? "destructive" : "secondary"} className="shrink-0 font-mono text-xs">
|
||||
{p.type}
|
||||
</Badge>
|
||||
{toolName && (
|
||||
<span className="truncate font-mono font-medium">{toolName}</span>
|
||||
)}
|
||||
{isResult && (
|
||||
<Badge variant={isError ? "destructive" : "success"} className="shrink-0 text-xs">
|
||||
{isError ? "error" : "ok"}
|
||||
</Badge>
|
||||
)}
|
||||
<span className="shrink-0 text-muted-foreground">→</span>
|
||||
<span className="truncate">{resolveAgent(p.agentId)}</span>
|
||||
</div>
|
||||
|
||||
<ContextRow p={p} resolveAgent={resolveAgent} showCallId />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/** Shared context row: channel, delegation, parent, team task, call ID */
|
||||
function ContextRow({
|
||||
p,
|
||||
resolveAgent,
|
||||
showCallId,
|
||||
}: {
|
||||
p: EnrichedAgentEventPayload;
|
||||
resolveAgent: Props["resolveAgent"];
|
||||
showCallId?: boolean;
|
||||
}) {
|
||||
const hasContext = p.channel || p.delegationId || p.parentAgentId || p.teamTaskId || (showCallId && p.payload?.id);
|
||||
if (!hasContext) return null;
|
||||
|
||||
return (
|
||||
<div className="flex min-w-0 flex-wrap items-center gap-x-2 gap-y-0.5 text-xs text-muted-foreground">
|
||||
{p.channel && (
|
||||
<Badge variant="outline" className="shrink-0 text-xs">{p.channel}</Badge>
|
||||
)}
|
||||
{p.delegationId && (
|
||||
<ShortId label="deleg" id={p.delegationId} />
|
||||
)}
|
||||
{p.parentAgentId && (
|
||||
<span className="truncate">
|
||||
parent: <span className="font-medium text-foreground">{resolveAgent(p.parentAgentId)}</span>
|
||||
</span>
|
||||
)}
|
||||
{p.teamTaskId && (
|
||||
<ShortId label="task" id={p.teamTaskId} />
|
||||
)}
|
||||
{showCallId && p.payload?.id && (
|
||||
<ShortId label="call" id={p.payload.id} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ShortId({ label, id }: { label: string; id: string }) {
|
||||
return (
|
||||
<span className="shrink-0 text-xs text-muted-foreground">
|
||||
{label}: <span className="font-mono">{id.slice(0, 8)}</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -49,11 +49,19 @@ function DelegationLifecycleCard({ entry, resolveAgent }: Props) {
|
||||
{p.mode}
|
||||
</Badge>
|
||||
)}
|
||||
{p.channel && (
|
||||
<Badge variant="outline" className="shrink-0 text-xs">{p.channel}</Badge>
|
||||
)}
|
||||
</div>
|
||||
{p.task && <p className="break-words text-xs text-muted-foreground line-clamp-2">{p.task}</p>}
|
||||
<div className="flex flex-wrap items-center gap-x-2 gap-y-0.5 text-xs text-muted-foreground">
|
||||
{p.delegation_id && (
|
||||
<span className="shrink-0">
|
||||
deleg: <span className="font-mono">{p.delegation_id.slice(0, 8)}</span>
|
||||
</span>
|
||||
)}
|
||||
{p.elapsed_ms != null && p.elapsed_ms > 0 && <span>{formatDuration(p.elapsed_ms)}</span>}
|
||||
{p.error && <span className="break-all text-destructive">{p.error}</span>}
|
||||
{p.error && <span className="break-words text-destructive">{p.error}</span>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -97,11 +105,19 @@ function DelegationAccumulatedCard({ payload: p, resolveAgent }: { payload: Dele
|
||||
function DelegationAnnounceCard({ payload: p, resolveAgent }: { payload: DelegationAnnouncePayload } & ResolverProp) {
|
||||
return (
|
||||
<div className="space-y-1.5 text-sm">
|
||||
<p className="text-muted-foreground">
|
||||
<span className="font-medium text-foreground">{p.source_display_name || resolveAgent(p.source_agent_key)}</span>
|
||||
{" "}announcing {p.results.length} result(s)
|
||||
<span className="ml-1 text-xs">({formatDuration(p.total_elapsed_ms)})</span>
|
||||
</p>
|
||||
<div className="flex min-w-0 flex-wrap items-center gap-x-1.5 gap-y-0.5">
|
||||
<span className="font-medium">{p.source_display_name || resolveAgent(p.source_agent_key)}</span>
|
||||
<span className="text-muted-foreground">announcing {p.results.length} result(s)</span>
|
||||
<span className="text-xs text-muted-foreground">({formatDuration(p.total_elapsed_ms)})</span>
|
||||
{p.has_media && (
|
||||
<Badge variant="outline" className="shrink-0 text-xs">media</Badge>
|
||||
)}
|
||||
{p.completed_task_ids && p.completed_task_ids.length > 0 && (
|
||||
<Badge variant="success" className="shrink-0 text-xs">
|
||||
{p.completed_task_ids.length} task(s) auto-completed
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
{p.results.map((r) => (
|
||||
<div key={r.agent_key} className="min-w-0 text-xs">
|
||||
|
||||
@@ -27,6 +27,11 @@ export function MessageEventCard({ entry, resolveAgent }: Props) {
|
||||
{p.preview && (
|
||||
<p className="break-words text-xs text-muted-foreground line-clamp-2">{p.preview}</p>
|
||||
)}
|
||||
{p.task_id && (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
task: <span className="font-mono">{p.task_id.slice(0, 8)}</span>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -18,8 +18,16 @@ export function TaskEventCard({ entry, resolveAgent }: Props) {
|
||||
<Badge variant={taskStatusBadgeVariant(p.status)} className="shrink-0 text-xs">
|
||||
{p.status}
|
||||
</Badge>
|
||||
{p.channel && (
|
||||
<Badge variant="outline" className="shrink-0 text-xs">{p.channel}</Badge>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-x-2 gap-y-0.5 text-xs text-muted-foreground">
|
||||
{p.task_id && (
|
||||
<span className="shrink-0">
|
||||
task: <span className="font-mono">{p.task_id.slice(0, 8)}</span>
|
||||
</span>
|
||||
)}
|
||||
{p.owner_agent_key && <span className="truncate">Owner: {owner}</span>}
|
||||
{p.reason && <span className="break-words">Reason: {p.reason}</span>}
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user