Files
canvas-frontend/src/components/Telemetry.tsx
T
shad 3deb1b8888
build-and-publish / test (push) Has been cancelled
build-and-publish / image (push) Has been cancelled
fix(oracle-l8-r4): scrub demo.flow-master.ai from Telemetry + Landing
Oracle round-9 watch-out: same liveMeta.fetchedFrom leak class
existed in two more user-visible surfaces:

- src/components/Telemetry.tsx: 'source' block rendered the host
  string. Now hard-coded 'EA2'.
- src/scenes/Landing.tsx: hero paragraph said 'runs end-to-end
  through EA2 on <host>'. Dropped the host suffix entirely.

liveMeta import removed from Telemetry (no other refs). Landing
still uses liveMeta for the workItems/distinctDefs stat counters.
2026-06-14 21:02:52 +04:00

135 lines
5.4 KiB
TypeScript

// Bottom telemetry strip.
// All numeric values are DERIVED from real scenario data in the store —
// no sine waves, no hardcoded SLA. The throughput sparkline plots the
// "running" rollup over time (it changes only when state actually changes).
// The "ui tick" dot is a UI heartbeat labelled as such.
import { useEffect, useMemo, useState } from "react";
import { useApp, scenarioById } from "../state/store";
import { Pulse, Spark } from "./icons";
function Sparkline({ values, color, label }: { values: number[]; color: string; label: string }) {
const w = 120, h = 28, pad = 2;
if (!values.length) return <svg width={w} height={h} aria-label={label} />;
const max = Math.max(...values, 1);
const min = Math.min(...values, 0);
const span = Math.max(1, max - min);
const pts = values
.map((v, i) => {
const x = pad + ((w - pad * 2) * i) / Math.max(1, values.length - 1);
const y = h - pad - ((v - min) / span) * (h - pad * 2);
return `${x.toFixed(1)},${y.toFixed(1)}`;
})
.join(" ");
return (
<svg width={w} height={h} className="spark" aria-label={label}>
<polyline points={pts} fill="none" stroke={color} strokeWidth={1.6} />
</svg>
);
}
const HIST_LEN = 24;
export default function Telemetry() {
const scenarioId = useApp((s) => s.scenarioId);
const scenarios = useApp((s) => s.scenarios);
const mode = useApp((s) => s.mode);
const sc = scenarioById(scenarioId);
// Real rollup across every loaded scenario.
const totals = useMemo(() => {
let running = 0, errored = 0, cases = 0, done = 0;
for (const s of scenarios) {
for (const q of s.queue) {
cases += 1;
if (q.status === "running" || q.status === "waiting_for_user" || q.status === "waiting_for_agent") running += 1;
else if (q.status === "errored" || q.status === "failed") errored += 1;
else if (q.status === "completed" || q.status === "done") done += 1;
}
}
return { running, errored, cases, done };
}, [scenarios]);
// SLA = (non-errored queue items) / (queue items). Real, derived.
const sla = totals.cases === 0 ? 1 : 1 - totals.errored / totals.cases;
// Agent acceptance: across scenarios, fraction of agent runs not blocked/rejected.
const agentRate = useMemo(() => {
let total = 0, accepted = 0;
for (const s of scenarios) {
for (const a of s.agentRuns) {
total += 1;
if (a.status !== "proposed") accepted += 1;
}
}
return total === 0 ? 1 : accepted / total;
}, [scenarios]);
// Sparkline = history of `running` totals. Updates only when totals change.
const [history, setHistory] = useState<number[]>(() => Array(HIST_LEN).fill(totals.running));
useEffect(() => {
const t = setInterval(() => {
setHistory((h) => [...h.slice(1), totals.running]);
}, 1500);
return () => clearInterval(t);
}, [totals.running]);
return (
<div className="telemetry" data-anchor="telemetry">
<div className="t-block">
<span className="t-eyebrow"><Pulse size={11} /> running over time</span>
<Sparkline values={history} color="var(--run)" label="running cases over the last 36 seconds" />
<span className="t-v mono">{totals.running} now</span>
</div>
<div className="t-divider" />
<div className="t-block">
<span className="t-eyebrow">cases</span>
<span className="t-v mono">{totals.cases}</span>
</div>
<div className="t-block">
<span className="t-eyebrow">running</span>
<span className="t-v mono" style={{ color: "var(--run)" }}>{totals.running}</span>
</div>
<div className="t-block">
<span className="t-eyebrow">errored</span>
<span className="t-v mono" style={{ color: totals.errored > 0 ? "var(--block)" : "var(--text-2)" }}>{totals.errored}</span>
</div>
<div className="t-divider" />
<div className="t-block" title="derived: non-errored / total queue items">
<span className="t-eyebrow"><Spark size={11} /> sla (derived)</span>
<Gauge value={sla} />
<span className="t-v mono">{(sla * 100).toFixed(1)}%</span>
</div>
<div className="t-divider" />
<div className="t-block" title="derived: fraction of agent runs not in 'proposed' state">
<span className="t-eyebrow">agent acceptance (derived)</span>
<Gauge value={agentRate} accent="var(--ok)" />
<span className="t-v mono">{(agentRate * 100).toFixed(0)}%</span>
</div>
<div className="t-spacer" />
<div className="t-block">
<span className="t-eyebrow">scenario</span>
<span className="t-v">{sc?.family.label ?? "—"}</span>
</div>
<div className="t-block">
<span className="t-eyebrow">mode</span>
<span className={`mode-pill mode-${mode}`}>{mode === "live" ? "LIVE" : "SNAPSHOT"}</span>
</div>
<div className="t-block">
<span className="t-eyebrow">source</span>
<span className="t-v mono">EA2</span>
</div>
<div className="t-tick" aria-label="ui tick" title="UI heartbeat — pulses every 1.5s, does not represent backend activity" />
</div>
);
}
function Gauge({ value, accent = "var(--run)" }: { value: number; accent?: string }) {
const w = 60, h = 12;
return (
<div className="gauge" style={{ width: w, height: h }}>
<div className="gauge-fill" style={{ width: `${Math.min(100, Math.max(0, value * 100))}%`, background: accent }} />
</div>
);
}