"use client"; import { useState } from "react"; import type { ReactElement } from "react"; import { Card, SectionHeader, Badge, Dot } from "@mosaic/ui"; import type { ActiveJob } from "@/lib/api/dashboard"; /* ------------------------------------------------------------------ */ /* Internal display types */ /* ------------------------------------------------------------------ */ type DotVariant = "teal" | "blue" | "amber" | "red" | "muted"; type BadgeVariant = | "badge-teal" | "badge-amber" | "badge-red" | "badge-blue" | "badge-muted" | "badge-purple" | "badge-pulse"; interface AgentNode { id: string; initials: string; avatarColor: string; name: string; task: string; status: DotVariant; statusLabel: string; } interface OrchestratorSession { id: string; orchId: string; name: string; badge: string; badgeVariant: BadgeVariant; duration: string; progress: number; agents: AgentNode[]; } export interface OrchestratorSessionsProps { jobs?: ActiveJob[] | undefined; } /* ------------------------------------------------------------------ */ /* Mapping helpers */ /* ------------------------------------------------------------------ */ const STEP_COLORS: string[] = [ "rgba(47,128,255,0.15)", "rgba(20,184,166,0.15)", "rgba(245,158,11,0.15)", "rgba(139,92,246,0.15)", "rgba(229,72,77,0.15)", ]; function statusToDotVariant(status: string): DotVariant { const lower = status.toLowerCase(); if (lower === "running" || lower === "active" || lower === "completed") return "teal"; if (lower === "pending" || lower === "queued") return "blue"; if (lower === "waiting" || lower === "paused") return "amber"; if (lower === "failed" || lower === "error") return "red"; return "muted"; } function statusToBadgeVariant(status: string): BadgeVariant { const lower = status.toLowerCase(); if (lower === "running" || lower === "active") return "badge-teal"; if (lower === "pending" || lower === "queued") return "badge-blue"; if (lower === "waiting" || lower === "paused") return "badge-amber"; if (lower === "failed" || lower === "error") return "badge-red"; if (lower === "completed") return "badge-purple"; return "badge-muted"; } function formatDuration(isoDate: string): string { const now = Date.now(); const start = new Date(isoDate).getTime(); const diffMs = now - start; if (Number.isNaN(diffMs) || diffMs < 0) return "0m"; const totalMinutes = Math.floor(diffMs / 60_000); if (totalMinutes < 60) return `${String(totalMinutes)}m`; const hours = Math.floor(totalMinutes / 60); const minutes = totalMinutes % 60; return `${String(hours)}h ${String(minutes)}m`; } function initials(name: string): string { return name .split(/[\s\-_]+/) .slice(0, 2) .map((w) => w.charAt(0).toUpperCase()) .join(""); } function mapJobToSession(job: ActiveJob): OrchestratorSession { const agents: AgentNode[] = job.steps.map((step, idx) => ({ id: step.id, initials: initials(step.name), avatarColor: STEP_COLORS[idx % STEP_COLORS.length] ?? "rgba(100,116,139,0.15)", name: step.name, task: `Phase: ${step.phase}`, status: statusToDotVariant(step.status), statusLabel: step.status.toLowerCase(), })); return { id: job.id, orchId: job.id.length > 10 ? job.id.slice(0, 10).toUpperCase() : job.id.toUpperCase(), name: job.type, badge: job.status, badgeVariant: statusToBadgeVariant(job.status), duration: formatDuration(job.createdAt), progress: job.progressPercent, agents, }; } /* ------------------------------------------------------------------ */ /* Sub-components */ /* ------------------------------------------------------------------ */ interface AgentNodeItemProps { agent: AgentNode; } function AgentNodeItem({ agent }: AgentNodeItemProps): ReactElement { const [hovered, setHovered] = useState(false); return (