diff --git a/web-ui/src/components/cases/agent-activity-feed.tsx b/web-ui/src/components/cases/agent-activity-feed.tsx index a7ebe5d..b546b1b 100644 --- a/web-ui/src/components/cases/agent-activity-feed.tsx +++ b/web-ui/src/components/cases/agent-activity-feed.tsx @@ -5,7 +5,6 @@ import { Button } from "@/components/ui/button"; import { Textarea } from "@/components/ui/textarea"; import { Badge } from "@/components/ui/badge"; import { Markdown } from "@/components/ui/markdown"; -import { AgentStatusWidget } from "@/components/cases/agent-status-widget"; import { useAgentActivity, useSendComment, @@ -17,6 +16,7 @@ import type { InteractionQuestion, InteractionTask, PaperclipComment, + PaperclipAgentStatus, } from "@/lib/api/agents"; import { formatRelative } from "@/lib/format-date"; import { toast } from "sonner"; @@ -82,6 +82,104 @@ function issueStatusLabel(status: string) { return ISSUE_STATUS_LABELS[status] ?? status; } +/* ── Agent roster (mockup 18i) — who's doing what right now ────── */ + +const AGENT_STATUS_LABEL: Record = { + active: "פעיל", + running: "פעיל", + idle: "ממתין", + error: "שגיאה", +}; + +function agentIsActive(s: string) { + return s === "active" || s === "running"; +} + +function agentStatusDot(s: string) { + return agentIsActive(s) + ? "bg-success shadow-[0_0_6px_rgba(74,124,89,0.6)]" + : s === "error" + ? "bg-danger" + : "bg-rule"; +} + +function agentStatusTone(s: string) { + return agentIsActive(s) + ? "bg-success-bg text-success" + : s === "error" + ? "bg-danger-bg text-danger" + : "bg-rule-soft text-ink-muted"; +} + +function AgentRoster({ + agents, + comments, +}: { + agents: PaperclipAgentStatus[]; + comments: PaperclipComment[]; +}) { + if (!agents.length) return null; + + // "current activity" per agent = its most recent comment body (by name/role). + const latest = new Map(); + for (const a of agents) { + let best: PaperclipComment | null = null; + let bestAt = -1; + for (const c of comments) { + const mine = + (c.agent_name && c.agent_name === a.name) || + (c.agent_role && c.agent_role === a.role); + if (!mine) continue; + const at = c.created_at ? new Date(c.created_at).getTime() : 0; + if (at >= bestAt) { + bestAt = at; + best = c; + } + } + if (best) latest.set(a.id, best.body); + } + + const activeCount = agents.filter((a) => agentIsActive(a.status)).length; + + return ( +
+
+ +

סוכני התיק

+ + {activeCount} פעילים מתוך {agents.length} + +
+
+ {agents.map((a) => { + const active = agentIsActive(a.status); + const activity = latest.get(a.id); + return ( +
+
+ + {a.name} + + {AGENT_STATUS_LABEL[a.status] ?? a.status} + +
+
{roleLabel(a.role)}
+
+ {activity || "—"} +
+
+ ); + })} +
+
+ ); +} + /* ── Time formatting ─────────────────────────────────────────── */ function timeAgo(iso: string | null): string { @@ -690,7 +788,9 @@ export function AgentActivityFeed({ const comments = data.comments ?? []; const interactions = data.interactions ?? []; - const pendingCount = interactions.filter((i) => i.status === "pending").length; + // Pending interactions are surfaced at the top (awaiting-you, mockup 18i) and + // NOT repeated in the timeline below. + const pending = interactions.filter((i) => i.status === "pending"); // Unified, time-sorted feed: comments + interactions interleaved. type FeedItem = @@ -703,11 +803,13 @@ export function AgentActivityFeed({ at: c.created_at ? new Date(c.created_at).getTime() : 0, comment: c, })), - ...interactions.map((i) => ({ - kind: "interaction", - at: i.created_at ? new Date(i.created_at).getTime() : 0, - interaction: i, - })), + ...interactions + .filter((i) => i.status !== "pending") + .map((i) => ({ + kind: "interaction", + at: i.created_at ? new Date(i.created_at).getTime() : 0, + interaction: i, + })), ].sort((a, b) => a.at - b.at); // An issue is "active" if it's not done/cancelled. When everything is closed @@ -719,15 +821,27 @@ export function AgentActivityFeed({ return (
{/* agent roster — who's doing what right now (mockup 18i) */} -
- -
+ - {/* pending interactions surfaced to the top (mockup 18i — awaiting you) */} - {pendingCount > 0 && ( -
- - ממתין לתשובתך — {pendingCount} {pendingCount === 1 ? "בקשה" : "בקשות"} בהמשך הזרם + {/* pending interactions surfaced to the top — the actual forms, not a + pointer (mockup 18i — awaiting you). Excluded from the timeline. */} + {pending.length > 0 && ( +
+
+ + ממתין לתשובתך + + {pending.length} + +
+ {pending.map((i) => ( + + ))}
)}