ai_api / src /app /(dashboard) /dashboard /compression /studio /CompressionCockpit.tsx
Yogesh
initial deploy
cd8bd0a
Raw
History Blame Contribute Delete
10.3 kB
"use client";
import { useMemo, useState } from "react";
import type { NodeTypes } from "@xyflow/react";
import { FlowCanvas } from "@/shared/components/flow/FlowCanvas";
import { EngineNode } from "./nodes/EngineNode";
import { IoNode } from "./nodes/IoNode";
import { compressionRunToFlow, type CompressionRunModel } from "./compressionFlowModel";
import { useCompressionReplay, type ReplaySpeed } from "./useCompressionReplay";
import { WaterfallInspector } from "./WaterfallInspector";
// ── View modes ────────────────────────────────────────────────────────────
type CockpitView = "canvas" | "waterfall";
// ── Static node type map (defined outside to avoid re-creation) ───────────
const NODE_TYPES: NodeTypes = {
engine: EngineNode as unknown as NodeTypes["engine"],
io: IoNode as unknown as NodeTypes["io"],
// The model also sets type "input"/"output" for IoNode β€” map both
input: IoNode as unknown as NodeTypes["input"],
output: IoNode as unknown as NodeTypes["output"],
};
// ── Helpers ───────────────────────────────────────────────────────────────
function fmt(n: number): string {
return n.toLocaleString();
}
// ── Speed selector ────────────────────────────────────────────────────────
const SPEEDS: ReplaySpeed[] = [0.3, 1, 3];
function SpeedButton({
s,
active,
onClick,
}: {
s: ReplaySpeed;
active: boolean;
onClick: () => void;
}) {
return (
<button
onClick={onClick}
className="px-2 py-0.5 rounded text-[11px] border transition-colors"
style={{
borderColor: active ? "var(--color-primary)" : "var(--color-border)",
color: active ? "var(--color-primary)" : "var(--color-text-muted)",
background: active ? "var(--color-primary-subtle)" : "transparent",
}}
>
{s}Γ—
</button>
);
}
// ── View toggle ───────────────────────────────────────────────────────────
function ViewButton({
label,
testId,
active,
onClick,
}: {
label: string;
testId: string;
active: boolean;
onClick: () => void;
}) {
return (
<button
onClick={onClick}
data-testid={testId}
aria-pressed={active}
className="px-2 py-0.5 rounded text-[11px] border transition-colors"
style={{
borderColor: active ? "var(--color-primary)" : "var(--color-border)",
color: active ? "var(--color-primary)" : "var(--color-text-muted)",
background: active ? "var(--color-primary-subtle)" : "transparent",
}}
>
{label}
</button>
);
}
// ── Empty state ───────────────────────────────────────────────────────────
function EmptyState() {
return (
<div
className="flex flex-col items-center justify-center h-full gap-3 text-muted"
data-testid="compression-cockpit-empty"
>
<span className="text-3xl opacity-40">⌁</span>
<p className="text-sm">No compression run available.</p>
<p className="text-xs opacity-60">Live data arrives via the WS compression channel.</p>
</div>
);
}
// ── Props ─────────────────────────────────────────────────────────────────
export interface CompressionCockpitProps {
/**
* The run to render. If omitted, an empty state is shown β€” the parent owns the
* live data (via `useLiveCompression`) and passes its latest run here.
*/
run?: CompressionRunModel | null;
}
// ── Main component ────────────────────────────────────────────────────────
/**
* CompressionCockpit β€” A3 cockpit view (Compression Studio, Tela A).
*
* Renders the compression pipeline as a ReactFlow canvas (A2) with:
* - A header showing run metadata (mode, comboId, total savings).
* - Replay controls (play/pause/reset + speed selector).
* - Graceful empty state when no run is available.
*
* Controlled component: it renders the `run` prop when supplied and the empty
* state otherwise. The parent owns the live data β€” it subscribes via the
* `useLiveCompression` hook and passes `lastRun` as `run` β€” which keeps this
* component WS-free and unit-testable with a static run.
*/
export function CompressionCockpit({ run: runProp }: CompressionCockpitProps) {
// Canvas (A2, ReactFlow) is the default; Waterfall (A1) is a plain-div list view of the same run.
const [view, setView] = useState<CockpitView>("canvas");
// Replay drives frame-by-frame animation (cosmetic β€” sub-ms compression is sync)
const { currentFrame, isPlaying, isComplete, speed, setSpeed, play, pause, reset } =
useCompressionReplay(runProp ?? null);
// Displayed run: replay frame while playing, else the full run
const displayRun = currentFrame ?? runProp ?? null;
// Build nodes/edges from the model
const { nodes, edges } = useMemo(() => {
if (!displayRun) return { nodes: [], edges: [] };
// compressionRunToFlow uses type "input"/"output" for IoNode β€” remap to "io"
const raw = compressionRunToFlow(displayRun);
const remappedNodes = raw.nodes.map((n) => {
if (n.type === "input") {
return { ...n, type: "io", data: { ...n.data, variant: "input" } };
}
if (n.type === "output") {
return {
...n,
type: "io",
data: { ...n.data, variant: "output", savingsPercent: displayRun.savingsPercent },
};
}
return n;
});
return { nodes: remappedNodes, edges: raw.edges };
}, [displayRun]);
const fitKey = displayRun ? `${displayRun.requestId}-${nodes.length}` : "empty";
if (!runProp) {
return <EmptyState />;
}
const run = runProp;
return (
<div className="flex flex-col h-full gap-2" data-testid="compression-cockpit">
{/* ── Header ─────────────────────────────────────────────────────── */}
<div className="flex items-center gap-3 px-3 py-2 rounded-lg border border-border bg-bg/60 shrink-0">
<span className="text-xs font-semibold text-muted uppercase tracking-wide">{run.mode}</span>
{run.comboId && (
<span className="text-xs text-muted px-1.5 py-0.5 rounded bg-border/40 font-mono">
{run.comboId}
</span>
)}
<span className="text-xs text-muted font-mono opacity-70 truncate">{run.requestId}</span>
<div className="ml-auto flex items-center gap-2">
{/* View toggle: ReactFlow canvas (A2) ↔ waterfall list (A1) */}
<div className="flex items-center gap-1" role="group" aria-label="Cockpit view">
<ViewButton
label="Canvas"
testId="cockpit-view-canvas"
active={view === "canvas"}
onClick={() => setView("canvas")}
/>
<ViewButton
label="Waterfall"
testId="cockpit-view-waterfall"
active={view === "waterfall"}
onClick={() => setView("waterfall")}
/>
</div>
<span className="text-[11px] text-muted">
{fmt(run.originalTokens)} β†’ {fmt(run.compressedTokens)} tok
</span>
<span className="text-xs font-bold" style={{ color: "#22c55e" }}>
βˆ’{run.savingsPercent.toFixed(1)}%
</span>
{isComplete && view === "canvas" && (
<span className="text-[10px] px-1.5 py-0.5 rounded border border-border text-muted">
replay done
</span>
)}
</div>
</div>
{/* ── Body: Canvas (A2) or Waterfall (A1) ──────────────────────────── */}
{view === "waterfall" ? (
<div className="flex-1 min-h-0 overflow-auto rounded-lg border border-border bg-bg/60 p-3">
<WaterfallInspector run={run} />
</div>
) : (
<div className="flex-1 min-h-0 rounded-lg overflow-hidden border border-border">
<FlowCanvas
nodes={nodes}
edges={edges}
nodeTypes={NODE_TYPES}
fitKey={fitKey}
className="h-full w-full"
/>
</div>
)}
{/* ── Replay controls (canvas only β€” the waterfall is static) ──────── */}
{view === "canvas" && (
<div className="flex items-center gap-2 px-3 py-1.5 rounded-lg border border-border bg-bg/60 shrink-0">
<button
onClick={isPlaying ? pause : play}
className="flex items-center gap-1.5 px-2.5 py-1 rounded border border-border text-xs hover:bg-border/30 transition-colors"
aria-label={isPlaying ? "Pause replay" : "Play replay"}
>
{isPlaying ? "⏸ Pause" : "β–· Replay"}
</button>
<button
onClick={reset}
className="px-2 py-1 rounded border border-border text-xs text-muted hover:bg-border/30 transition-colors"
aria-label="Reset replay"
>
⟳
</button>
<div className="flex items-center gap-1 ml-2">
{SPEEDS.map((s) => (
<SpeedButton key={s} s={s} active={speed === s} onClick={() => setSpeed(s)} />
))}
</div>
{displayRun && currentFrame && (
<span className="ml-auto text-[11px] text-muted">
step {displayRun?.steps.length ?? 0}/{run.steps.length}
</span>
)}
</div>
)}
</div>
);
}
export default CompressionCockpit;