"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 (
{ setHovered(true); }} onMouseLeave={(): void => { setHovered(false); }} style={{ display: "flex", alignItems: "center", gap: 10, padding: "7px 10px", borderRadius: "var(--r-sm)", border: `1px solid ${hovered ? "var(--ms-border-700)" : "var(--border)"}`, background: hovered ? "var(--surface)" : "var(--bg-mid)", transition: "border-color 0.15s, background 0.15s", }} >
{agent.initials}
{agent.name}
{agent.task}
{agent.statusLabel}
); } interface OrchCardProps { session: OrchestratorSession; } function OrchCard({ session }: OrchCardProps): ReactElement { return (
{session.orchId} {session.name} {session.badge} {session.duration}
{session.progress > 0 && (
)}
{session.agents.map((agent) => ( ))}
); } /* ------------------------------------------------------------------ */ /* Main export */ /* ------------------------------------------------------------------ */ export function OrchestratorSessions({ jobs }: OrchestratorSessionsProps): ReactElement { const sessions = jobs ? jobs.map(mapJobToSession) : []; const activeCount = jobs ? jobs.filter( (j) => j.status.toLowerCase() === "running" || j.status.toLowerCase() === "active" ).length : 0; return ( 0 ? `${String(activeCount)} of ${String(sessions.length)} jobs running` : "No active sessions" } actions={ sessions.length > 0 ? ( {String(activeCount)} active ) : undefined } />
{sessions.length > 0 ? ( sessions.map((session) => ) ) : (
No active sessions
)}
); }