feat(control-panel): embed H6 telemetry in goal view

This commit is contained in:
thanhnv
2026-07-21 23:38:59 +07:00
parent 4b6819f578
commit 4d5dca9400
2 changed files with 118 additions and 5 deletions
@@ -1,5 +1,6 @@
import { useEffect, useMemo, useState } from 'react';
import type { GoalJob, GoalStage, HarnessGateNode, HarnessTraceGraph } from '../../lib/api';
import { Link } from 'react-router-dom';
import { h6ReportExportUrl, type GoalJob, type GoalStage, type H6Report, type HarnessGateNode, type HarnessTraceGraph } from '../../lib/api';
import { StatusBadge } from '../ui/Card';
interface ModelInteractionDiagramProps {
@@ -7,6 +8,10 @@ interface ModelInteractionDiagramProps {
localStage?: GoalStage;
cloudStage?: GoalStage;
trace?: HarnessTraceGraph;
h6RunReport?: H6Report;
h6ProjectReport?: H6Report;
h6Loading?: boolean;
h6Error?: boolean;
}
const GATE_ORDER = ['H1-context', 'H2-tool', 'H3-eval', 'H4-security', 'H5-governance', 'H6-agentops', 'H7-orchestration'];
@@ -23,6 +28,20 @@ function formatReason(reason: string): string {
return reason.replaceAll('_', ' ').replace(/\b\w/g, (letter) => letter.toUpperCase());
}
function formatDuration(milliseconds: number): string {
if (!milliseconds) return '0 ms';
if (milliseconds < 1_000) return `${Math.round(milliseconds)} ms`;
const totalSeconds = Math.round(milliseconds / 1_000);
if (totalSeconds < 60) return `${totalSeconds}s`;
const minutes = Math.floor(totalSeconds / 60);
return `${minutes}m ${totalSeconds % 60}s`;
}
function formatCost(value: number): string {
if (!value) return '$0.0000';
return `$${value.toFixed(value < 0.01 ? 6 : 4)}`;
}
function AgentAvatar({ label, role, model, status, selected, onClick }: {
label: string;
role: string;
@@ -62,7 +81,77 @@ function ControlGate({ node, selected, onClick }: { node: HarnessGateNode; selec
);
}
function GateDetail({ node }: { node: HarnessGateNode }) {
function H6Summary({ node, goal, runReport, projectReport, loading, error }: {
node: HarnessGateNode;
goal: GoalJob;
runReport?: H6Report;
projectReport?: H6Report;
loading: boolean;
error: boolean;
}) {
const reportLink = `/reports/h6?run=${encodeURIComponent(goal.trace_id)}`;
const projectLink = `/reports/h6?project=${encodeURIComponent(goal.project)}`;
if (loading && !runReport) {
return <div className="mt-4 grid grid-cols-2 gap-2 sm:grid-cols-3" aria-label="Loading H6 run telemetry">{Array.from({ length: 6 }, (_, index) => <div key={index} className="h-16 animate-pulse rounded-xl bg-white/5 motion-reduce:animate-none" />)}</div>;
}
if (error || !runReport) {
return <div className="mt-4 rounded-xl border border-amber-300/30 bg-amber-300/10 p-3 text-xs leading-5 text-amber-100">Run telemetry could not be loaded here. <Link to={reportLink} className="font-semibold underline decoration-amber-300/50 underline-offset-4 hover:text-white focus:outline-none focus:ring-2 focus:ring-cyan-300">Open the full H6 report</Link> to inspect the source status.</div>;
}
const tokenTotal = runReport.summary.tokens.provider_total || runReport.summary.tokens.total;
const actualCost = runReport.summary.cost_usd.provider_actual;
const displayedCost = actualCost || runReport.summary.cost_usd.estimated;
const provider = runReport.details.by_provider.slice(0, 2).map((row) => row.key).join(', ') || 'Not attributed';
const metrics = [
['Max runtime', formatDuration(runReport.summary.latency_ms.max)],
['Tokens', tokenTotal.toLocaleString()],
[actualCost ? 'Actual cost' : 'Estimated cost', formatCost(displayedCost)],
['Retries', runReport.summary.retries.toLocaleString()],
['Provider calls', runReport.summary.provider_calls.toLocaleString()],
['Freshness', runReport.freshness.status],
];
return (
<div className="mt-4 space-y-3 border-t border-white/10 pt-4">
<div className="grid gap-2 sm:grid-cols-2">
<div className="rounded-xl border border-white/10 bg-slate-950/35 p-3">
<div className="text-[10px] font-bold uppercase tracking-[0.12em] text-slate-500">Run gate</div>
<div className="mt-2 flex items-center justify-between gap-2"><span className="font-mono text-[10px] text-slate-300">{goal.trace_id.slice(0, 12)}…</span><StatusBadge value={node.status} /></div>
</div>
<div className="rounded-xl border border-white/10 bg-slate-950/35 p-3">
<div className="text-[10px] font-bold uppercase tracking-[0.12em] text-slate-500">Project health</div>
<div className="mt-2 flex items-center justify-between gap-2"><span className="truncate text-[11px] font-medium text-slate-300">{goal.project}</span><StatusBadge value={projectReport?.verdict ?? 'no data'} /></div>
<div className="mt-1 text-[10px] text-slate-500">{projectReport?.summary.runs ?? 0} matching telemetry record(s)</div>
</div>
</div>
<div className="grid grid-cols-2 gap-2 sm:grid-cols-3">{metrics.map(([label, value]) => <div key={label} className="rounded-xl border border-white/10 bg-slate-950/35 px-3 py-2.5"><div className="text-[9px] font-bold uppercase tracking-[0.1em] text-slate-500">{label}</div><div className="mt-1 truncate font-mono text-xs font-semibold capitalize text-white">{value}</div></div>)}</div>
<div className="rounded-xl border border-white/10 bg-slate-950/35 p-3">
<div className="flex flex-wrap items-center justify-between gap-2"><div><div className="text-[10px] font-bold uppercase tracking-[0.12em] text-slate-500">Run telemetry verdict</div><div className="mt-1 text-xs text-slate-300">{runReport.summary.runs} matching record(s) · data quality {runReport.data_quality.status}</div></div><StatusBadge value={runReport.verdict} /></div>
<div className="mt-2 truncate text-[10px] text-slate-400" title={provider}>Provider/model: {provider}</div>
{runReport.findings[0] && <p className="mt-2 text-[11px] leading-5 text-amber-100">{runReport.findings[0].message}</p>}
</div>
<div className="flex flex-wrap gap-2">
<Link to={reportLink} className="rounded-lg bg-cyan-300 px-3 py-2 text-xs font-semibold text-slate-950 transition hover:bg-cyan-200 focus:outline-none focus:ring-2 focus:ring-cyan-300 focus:ring-offset-2 focus:ring-offset-slate-900">Open full H6 report</Link>
<a href={h6ReportExportUrl({ run: goal.trace_id }, 'json')} download className="rounded-lg border border-white/15 bg-white/5 px-3 py-2 text-xs font-semibold text-slate-100 transition hover:border-white/30 hover:bg-white/10 focus:outline-none focus:ring-2 focus:ring-cyan-300">Download run JSON</a>
<Link to={projectLink} className="rounded-lg border border-white/15 bg-white/5 px-3 py-2 text-xs font-semibold text-slate-300 transition hover:border-white/30 hover:bg-white/10 hover:text-white focus:outline-none focus:ring-2 focus:ring-cyan-300">Open project health</Link>
</div>
</div>
);
}
function GateDetail({ node, goal, h6RunReport, h6ProjectReport, h6Loading = false, h6Error = false }: {
node: HarnessGateNode;
goal: GoalJob;
h6RunReport?: H6Report;
h6ProjectReport?: H6Report;
h6Loading?: boolean;
h6Error?: boolean;
}) {
const tone = statusTone(node.status);
const evidence = Object.entries(node.evidence).filter(([, value]) => value !== null && value !== undefined).slice(0, 3);
return (
@@ -71,6 +160,7 @@ function GateDetail({ node }: { node: HarnessGateNode }) {
<p className="mt-3 text-sm leading-6 text-slate-200">{node.description}</p>
<div className="mt-3 rounded-xl bg-slate-950/40 p-3"><div className="text-[10px] font-bold uppercase tracking-[0.12em] text-slate-500">Observed impact</div><div className="mt-1 text-sm font-medium text-white">{formatReason(node.reason)}</div></div>
{evidence.length > 0 && <div className="mt-3 flex flex-wrap gap-2">{evidence.map(([key, value]) => <span key={key} className="max-w-full truncate rounded-lg border border-white/10 bg-slate-950/40 px-2 py-1 font-mono text-[10px] text-slate-300">{key}: {typeof value === 'string' ? value : JSON.stringify(value)}</span>)}</div>}
{node.id === 'H6-agentops' && <H6Summary node={node} goal={goal} runReport={h6RunReport} projectReport={h6ProjectReport} loading={h6Loading} error={h6Error} />}
</div>
);
}
@@ -93,7 +183,7 @@ export function GovernedOutcomePulse({ goal, trace }: { goal: GoalJob; trace?: H
);
}
export function ModelInteractionDiagram({ goal, localStage, cloudStage, trace }: ModelInteractionDiagramProps) {
export function ModelInteractionDiagram({ goal, localStage, cloudStage, trace, h6RunReport, h6ProjectReport, h6Loading = false, h6Error = false }: ModelInteractionDiagramProps) {
const nodes = useMemo(() => trace?.nodes ?? GATE_ORDER.map((id) => ({ id, title: id.replace('-', ' · '), description: 'Waiting for trace evidence', status: 'queued' as const, reason: 'Waiting for evidence', updated_at: null, evidence: {}, events: [] })), [trace]);
const preferred = [...nodes].reverse().find((node) => node.status === 'running' || node.status === 'error' || node.status === 'blocked' || node.status === 'warning')?.id ?? [...nodes].reverse().find((node) => node.status !== 'queued')?.id ?? 'H1-context';
const [focusedGate, setFocusedGate] = useState(preferred);
@@ -114,7 +204,7 @@ export function ModelInteractionDiagram({ goal, localStage, cloudStage, trace }:
<div className="min-w-0 rounded-2xl border border-white/10 bg-slate-900/60 p-4">
<div className="mb-3 flex items-center gap-2 text-[11px] font-bold uppercase tracking-[0.14em] text-slate-400"><span className="goal-control-signal h-2 w-2 rounded-full bg-cyan-300" /> Control spine · H1 → H7</div>
<div className="relative flex flex-wrap gap-2 before:absolute before:left-4 before:right-4 before:top-5 before:h-px before:bg-gradient-to-r before:from-indigo-500/30 before:via-cyan-300/50 before:to-emerald-400/30">{nodes.map((node) => <ControlGate key={node.id} node={node} selected={node.id === focusedGate} onClick={() => setFocusedGate(node.id)} />)}</div>
{focusedNode && <div className="mt-4"><GateDetail node={focusedNode} /></div>}
{focusedNode && <div className="mt-4"><GateDetail node={focusedNode} goal={goal} h6RunReport={h6RunReport} h6ProjectReport={h6ProjectReport} h6Loading={h6Loading} h6Error={h6Error} /></div>}
</div>
</div>
<div className="border-t border-white/10 bg-slate-900/80 px-5 py-3 text-xs text-slate-400">Color, motion and progress come from the live H1–H7 trace. When a gate stops, the outcome stops at that evidence boundary without inventing downstream failures.</div>
@@ -203,6 +203,20 @@ export function Goals() {
enabled: Boolean(selectedQuery.data?.trace_id),
refetchInterval: (query) => query.state.data?.terminal ? false : 1200,
});
const h6RunQuery = useQuery({
queryKey: ['reports', 'h6', 'goal-run', selectedQuery.data?.trace_id, selectedQuery.data?.updated_at],
queryFn: () => api.h6Report({ run: selectedQuery.data!.trace_id, limit: 20 }),
enabled: Boolean(selectedQuery.data?.trace_id),
retry: false,
refetchInterval: selectedQuery.data && !TERMINAL.has(selectedQuery.data.status) ? 2500 : false,
});
const h6ProjectQuery = useQuery({
queryKey: ['reports', 'h6', 'goal-project', selectedQuery.data?.project],
queryFn: () => api.h6Report({ project: selectedQuery.data!.project, limit: 20 }),
enabled: Boolean(selectedQuery.data?.project),
retry: false,
staleTime: 15_000,
});
const start = useMutation({
mutationFn: () => {
if (!actor) throw new Error('Authenticated session is unavailable.');
@@ -353,7 +367,16 @@ export function Goals() {
<StatusBadge value={selected.status} />
</div>
<ModelInteractionDiagram goal={selected} localStage={localStage} cloudStage={cloudStage} trace={traceQuery.data} />
<ModelInteractionDiagram
goal={selected}
localStage={localStage}
cloudStage={cloudStage}
trace={traceQuery.data}
h6RunReport={h6RunQuery.data}
h6ProjectReport={h6ProjectQuery.data}
h6Loading={h6RunQuery.isLoading}
h6Error={h6RunQuery.isError}
/>
<Card className="!rounded-[1.35rem]" title="Governed outcome" right={<StatusBadge value={selected.status} />}>
<GovernedOutcomePulse goal={selected} trace={traceQuery.data} />