feat: add live H1-H7 trace explorer

This commit is contained in:
thanhnv
2026-07-10 23:17:23 +09:00
parent 7cb592a32f
commit 1345d4c930
11 changed files with 496 additions and 28 deletions
@@ -1,5 +1,6 @@
import { type KeyboardEvent, useMemo, useState } from 'react';
import { useMutation, useQuery } from '@tanstack/react-query';
import { Link } from 'react-router-dom';
import { api, ChatAction, ChatAgent, ChatAnswer, ChatHistoryTurn, ChatModelProvider, ChatStreamPhase, SettingsActor } from '../lib/api';
import { Card, StatusBadge } from '../components/ui/Card';
import { ModelConnectionPanel } from '../components/chat/ModelConnectionPanel';
@@ -295,7 +296,7 @@ export function Chat() {
<div className="mt-5 text-[10px] font-bold uppercase tracking-[0.15em] text-slate-400">Latest verification</div>
<div className="mt-2 space-y-2">
<div className="rounded-xl border border-slate-200 bg-white p-3"><div className="text-xs text-slate-400">Audit anchor</div><div className="mt-1 break-all font-mono text-[11px] text-slate-700">{auditHash(last).slice(0, 22)}{auditHash(last) !== 'n/a' ? '…' : ''}</div></div>
{last && <><div className="rounded-xl border border-slate-200 bg-white p-3"><div className="flex flex-wrap gap-1.5"><StatusBadge value={last.mode} /><StatusBadge value={last.risk} /><StatusBadge value={last.decision} /><StatusBadge value={last.certified ? 'certified' : 'held'} /></div><div className="mt-2 text-xs text-slate-500">{last.router?.reason ?? 'Policy route verified'}{last.synthesis?.fallback_from ? ` · fell back from ${last.synthesis.fallback_from} to local` : ''}</div></div><Card title="Evidence" className="p-3.5"><div className="space-y-2">{last.sources.slice(0, 3).map((source) => <div key={`${source.path}-${source.line ?? ''}`} className="rounded-lg bg-slate-50 p-2.5"><div className="truncate text-xs font-medium text-slate-700">{source.title || source.path}</div><div className="mt-1 text-[11px] leading-4 text-slate-500">{source.preview || source.excerpt || 'Verified source'}</div></div>)}{last.sources.length === 0 && <div className="text-xs text-slate-500">No source was returned for this decision.</div>}</div></Card></>}
{last && <><div className="rounded-xl border border-slate-200 bg-white p-3"><div className="flex flex-wrap gap-1.5"><StatusBadge value={last.mode} /><StatusBadge value={last.risk} /><StatusBadge value={last.decision} /><StatusBadge value={last.certified ? 'certified' : 'held'} /></div><div className="mt-2 text-xs text-slate-500">{last.router?.reason ?? 'Policy route verified'}{last.synthesis?.fallback_from ? ` · fell back from ${last.synthesis.fallback_from} to local` : ''}</div>{last.trace_id && <Link to={`/runs?trace=${encodeURIComponent(last.trace_id)}`} className="mt-3 flex w-full items-center justify-center gap-2 rounded-lg border border-blue-200 bg-blue-50 px-3 py-2 text-xs font-semibold text-blue-700 transition-colors hover:bg-blue-100">Open live H1–H7 trace <Glyph name="chevron" /></Link>}</div><Card title="Evidence" className="p-3.5"><div className="space-y-2">{last.sources.slice(0, 3).map((source) => <div key={`${source.path}-${source.line ?? ''}`} className="rounded-lg bg-slate-50 p-2.5"><div className="truncate text-xs font-medium text-slate-700">{source.title || source.path}</div><div className="mt-1 text-[11px] leading-4 text-slate-500">{source.preview || source.excerpt || 'Verified source'}</div></div>)}{last.sources.length === 0 && <div className="text-xs text-slate-500">No source was returned for this decision.</div>}</div></Card></>}
</div>
<div className="mt-5 text-[10px] font-bold uppercase tracking-[0.15em] text-slate-400">Registered actions</div>
@@ -1,31 +1,53 @@
import { useQuery } from '@tanstack/react-query';
import { api } from '../lib/api';
import { useSearchParams } from 'react-router-dom';
import { api, type HarnessRunRecord } from '../lib/api';
import { Card, StatusBadge } from '../components/ui/Card';
import { TraceExplorer } from '../components/trace/TraceExplorer';
export function Runs() {
const { data, isLoading } = useQuery({ queryKey: ['runs'], queryFn: () => api.runs(100) });
if (isLoading || !data) return <div className="text-gray-500">Loading…</div>;
const [searchParams, setSearchParams] = useSearchParams();
const selectedTrace = searchParams.get('trace') ?? '';
if (isLoading || !data) return <div className="text-slate-500">Loading…</div>;
return (
<Card title={`Recent runs (${data.count})`}>
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead><tr className="text-left text-gray-500 border-b border-gray-200">
<th className="py-2">time</th><th>step</th><th>status</th><th>latency</th><th>tokens</th><th>cost</th>
</tr></thead>
<tbody>
{data.runs.map((r: any, i: number) => (
<tr key={i} className="border-b border-gray-100">
<td className="py-2 text-gray-500">{r.timestamp?.replace('T', ' ').replace('Z', '')}</td>
<td className="text-gray-700">{r.step ?? r.harness}</td>
<td><StatusBadge value={r.status ?? '—'} /></td>
<td>{r.latency_ms ?? '—'} ms</td>
<td>{r.total_tokens ?? '—'}</td>
<td>${Number(r.cost_estimate ?? 0).toFixed(5)}</td>
</tr>
))}
</tbody>
</table>
</div>
</Card>
<div className="space-y-5">
{selectedTrace && <TraceExplorer traceId={selectedTrace} onClose={() => setSearchParams({})} />}
<Card
title={`Recent runs (${data.count})`}
right={<span className="text-xs font-normal normal-case tracking-normal text-slate-400">Select a run to inspect H1–H7</span>}
>
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead><tr className="border-b border-slate-200 text-left text-slate-500">
<th className="py-2">time</th><th>step</th><th>status</th><th>latency</th><th>tokens</th><th>cost</th><th className="text-right">trace</th>
</tr></thead>
<tbody>
{data.runs.map((run: HarnessRunRecord, index: number) => (
<tr key={`${run.trace_id ?? 'run'}-${index}`} className="border-b border-slate-100 transition-colors hover:bg-slate-50/80">
<td className="py-2 text-slate-500">{run.timestamp?.replace('T', ' ').replace('Z', '')}</td>
<td className="text-slate-700">{run.step ?? run.harness}</td>
<td><StatusBadge value={run.status ?? '—'} /></td>
<td>{run.latency_ms ?? '—'} ms</td>
<td>{run.total_tokens ?? '—'}</td>
<td>${Number(run.cost_estimate ?? 0).toFixed(5)}</td>
<td className="py-2 text-right">
{run.trace_id ? (
<button
type="button"
onClick={() => setSearchParams({ trace: run.trace_id ?? '' })}
className="rounded-lg border border-blue-200 bg-blue-50 px-3 py-1.5 text-xs font-semibold text-blue-700 transition-colors hover:bg-blue-100"
>
Open H1–H7
</button>
) : <span className="text-slate-300">—</span>}
</td>
</tr>
))}
</tbody>
</table>
</div>
</Card>
</div>
);
}