import { Link } from "@tanstack/react-router"; import { motion } from "framer-motion"; import { Activity, AlertTriangle, CircleDot, Info, Sparkles, TimerReset } from "lucide-react"; import { QUEUE_REFETCH_ACTIVE_MS, QUEUE_REFETCH_IDLE_MS } from "@/api/queries"; import type { components } from "@/api/schema.d.ts"; import { Skeleton } from "@/components/shared/Skeleton"; import { Body, Caption, Muted, SectionHeading } from "@/components/ui/typography"; import { useDemo } from "@/hooks/useDemo"; import { type StaleQueueState, useStaleQueueDetection } from "@/hooks/useStaleQueueDetection"; import { COLOR } from "@/lib/constants"; import { formatCount } from "@/lib/utils"; type QueueStatus = components["schemas"]["QueueStatus"]; export interface DreamProgressPanelProps { workspaceId: string; data: QueueStatus | undefined; isLoading: boolean; error: Error | null; /** Override the stale-detection hook output (used by the dev showcase). */ staleOverride?: StaleQueueState; } function formatElapsed(ms: number): string { const totalSeconds = Math.floor(ms / 1000); const h = Math.floor(totalSeconds / 3600); const m = Math.floor((totalSeconds % 3600) / 60); const s = totalSeconds % 60; if (h > 0) return `${h}h ${m}m`; if (m > 0) return `${m}m ${s}s`; return `${s}s`; } export function DreamProgressPanel({ workspaceId, data, isLoading, error, staleOverride, }: DreamProgressPanelProps) { const { mask } = useDemo(); const detected = useStaleQueueDetection(data); const stale = staleOverride ?? detected; const inProgress = data?.in_progress_work_units ?? 0; const pending = data?.pending_work_units ?? 0; const completed = data?.completed_work_units ?? 0; const total = data?.total_work_units ?? 0; const active = inProgress + pending; const isActive = active > 0; const pollSeconds = isActive ? Math.round(QUEUE_REFETCH_ACTIVE_MS / 100) / 10 : Math.round(QUEUE_REFETCH_IDLE_MS / 100) / 10; if (error) { return ( Could not load queue status {error.message} ); } return ( {/* Header */}
Dreams in progress
{isActive ? ( ) : ( )} {isActive ? `${formatCount(active)} active` : "Idle"} · polling every {pollSeconds}s
{/* Stale warning */} {stale.isStale && stale.stalledSince !== null && (
Stalled for {formatElapsed(stale.elapsedMs)} without forward progress Work has been in-flight since {new Date(stale.stalledSince).toLocaleTimeString()}{" "} with no advance in the completed count. A specialist may be hung — check Honcho logs.
)} {/* Counts */}
{( [ { label: "Total", value: total, color: "var(--text-1)" }, { label: "Done", value: completed, color: COLOR.success }, { label: "In progress", value: inProgress, color: COLOR.warning }, { label: "Pending", value: pending, color: "var(--text-3)" }, ] as const ).map(({ label, value, color }) => (
{isLoading ? ( ) : (
{formatCount(value)}
)} {label}
))}
{/* API limitation note */}
Honcho's /queue/status exposes aggregate counts only. Per-dream observer/observed pair, specialist phase (deduction vs. induction), and token telemetry are tracked in{" "} plastic-labs/honcho#724 {" "} — once the API exposes them, this panel will surface them.
); } function SessionsTable({ workspaceId, sessions, mask, }: { workspaceId: string; sessions: QueueStatus["sessions"]; mask: (s: string) => string; }) { const entries = sessions ? Object.entries(sessions) : []; if (entries.length === 0) { return (
No session-scoped work tracked right now.
); } return (
{entries.length} session{entries.length !== 1 ? "s" : ""} with active work
{["Session", "Total", "Done", "In progress", "Pending"].map((h) => ( ))} {entries.map(([sid, s], i) => ( 0 ? "1px solid var(--border)" : undefined }}> ))}
{h}
{mask(sid)} {s.total_work_units} {s.completed_work_units} {s.in_progress_work_units} {s.pending_work_units}
); }