);
}
// ── Empty state ───────────────────────────────────────────────────────────
function EmptyState() {
const t = useTranslations("combos");
return (
⌁
{t("liveNoRun")}
{t("liveDataHint")}
);
}
// ── Disconnected banner ───────────────────────────────────────────────────
function DisconnectedBanner() {
const t = useTranslations("combos");
return (
●
{t("liveDisconnected")}
);
}
// ── Props ─────────────────────────────────────────────────────────────────
export interface ComboLiveStudioProps {
/**
* If provided, renders this run directly (static / test mode — no WS dependency).
* Mirror of how CompressionCockpit accepts `run?`.
*/
run?: ComboRunModel | null;
/**
* All raw combo events used to (a) populate the combo selector and
* (b) fold into a run for the selected combo.
* In live mode, callers supply events from `useLiveComboStatus().comboEvents`.
*/
comboEvents?: ComboEventInput[];
/**
* Available combo names for the combo selector.
* Defaults to the unique comboNames found in `comboEvents`.
*/
combos?: string[];
/**
* Whether the WebSocket connection is live. Defaults to true.
* When false, shows the "Live disabled" banner.
*/
isConnected?: boolean;
/**
* Per-provider circuit-breaker snapshot (`providerHealth` from
* GET /api/monitoring/health). When supplied, the cascade overlays the REAL
* breaker state (CB: OPEN · 41s) onto each target — U1b enrichment. Optional;
* absent → no breaker badges (graceful).
*/
providerHealth?: Record | null;
/**
* Per-provider connection-cooldown snapshot (`connectionHealth` from
* GET /api/monitoring/health). When supplied, the cascade overlays the REAL
* cooldown state (cooldown 2/3 · 28s) onto each target — U1b Slice 2. Optional;
* absent → no cooldown badges (graceful).
*/
connectionHealth?: Record | null;
}
// ── Main component ────────────────────────────────────────────────────────
/**
* ComboLiveStudio — Tela B: Combo/Routing Studio.
*
* Renders the combo routing cascade as a ReactFlow canvas:
* Request → StrategyNode → [ProviderCascadeNode × N] → ResponseNode
*
* Features:
* - Combo selector: picks which combo's latest run to display.
* - Single ⇄ Fleet toggle: Single = cascade flow; Fleet = radial provider
* aggregation over all `comboEvents` (last 60 s).
* - Graceful empty state when no run is available.
* - "Live disabled" banner when `isConnected` is false.
*
* Testable with a static `run` prop — no WS required.
*/
export function ComboLiveStudio({
run: runProp,
comboEvents = [],
combos: combosProp,
isConnected = true,
providerHealth,
connectionHealth,
}: ComboLiveStudioProps) {
const t = useTranslations("combos");
const [mode, setMode] = useState<"single" | "fleet">("single");
const [selectedCombo, setSelectedCombo] = useState("");
// Derive combo list from events when not supplied explicitly
const comboOptions = useMemo(() => {
if (combosProp) return combosProp;
const seen = new Set();
for (const ev of comboEvents) seen.add(ev.comboName);
return [...seen];
}, [combosProp, comboEvents]);
// Build the displayed run: static prop wins; otherwise fold live events.
// Finally overlay real circuit-breaker state (U1b) — a no-op when no health.
const displayRun = useMemo(() => {
let baseRun: ComboRunModel | null;
if (runProp !== undefined) {
baseRun = runProp ?? null;
} else if (!selectedCombo) {
baseRun = null;
} else {
const eventsForCombo = comboEvents
.filter((e) => e.comboName === selectedCombo)
.sort((a, b) => a.timestamp - b.timestamp);
baseRun =
eventsForCombo.length === 0
? null
: eventsForCombo.reduce(
(acc, ev) => reduceComboEvent(acc, ev),
null
);
}
// Compose overlays: breaker state first, then connection cooldown. Both are
// pure no-ops when their health map is absent, and they touch disjoint fields.
return enrichRunWithConnectionCooldown(
enrichRunWithBreakers(baseRun, providerHealth),
connectionHealth
);
}, [runProp, selectedCombo, comboEvents, providerHealth, connectionHealth]);
// Build ReactFlow graph from the current run
const { nodes, edges } = useMemo(() => {
if (!displayRun) return { nodes: [], edges: [] };
return comboRunToFlow(displayRun);
}, [displayRun]);
const fitKey = displayRun
? `${displayRun.comboName}-${displayRun.targets.length}-${displayRun.outcome}`
: "empty";
// Whether to show the combo selector (hidden when a static run prop is given)
const showSelector = runProp === undefined;
return (
setMode(next as "single" | "fleet")}
className="flex flex-col h-full gap-2"
data-testid="combo-live-studio"
>
{/* ── Toolbar ──────────────────────────────────────────────────────── */}