import React, { useMemo, useState } from 'react'; import { GitBranch, MessagesSquare, Sparkles } from 'lucide-react'; import { api, apiData } from '../services/api'; import { useLlmConfig } from '../services/useLlmConfig'; import ModelGate from './ModelGate'; /** Action Trace analysis: build a similarity graph from two demo runs, blend * the heatmap/thinking channels, and ask the graph AI questions. */ const DEMO = { runs: [ { run_id: 'r1', persona_id: 'p1', steps: [ { action: 'agentClick', x: 0.5, y: 0.2, think: 'easy to find the button' }, { action: 'agentScroll', x: 0.5, y: 0.6, think: 'scrolling for details' }, ] }, { run_id: 'r2', persona_id: 'p2', steps: [ { action: 'agentClick', x: 0.5, y: 0.2, think: 'text too small cannot read' }, { action: 'agentScroll', x: 0.5, y: 0.6, think: 'lost where is checkout' }, ] }, ], }; const AnalysisGraph: React.FC = () => { const [graphId, setGraphId] = useState(''); const [graph, setGraph] = useState(null); const [blend, setBlend] = useState(0.5); // 0 = heatmap, 1 = thinking const [decisions, setDecisions] = useState([]); const [qa, setQa] = useState([]); const [busy, setBusy] = useState(false); const llm = useLlmConfig(); const build = async () => { setBusy(true); try { const env = await api('/api/analysis/action-trace', { body: DEMO }); setGraph(env.data); setGraphId(env.artifact_id!); const dec = await apiData('/api/analysis/decisions', { body: { action_trace_graph_id: env.artifact_id } }); setDecisions(dec.findings || []); } finally { setBusy(false); } }; const askGraph = async () => { const data = await apiData('/api/graph-research/qa', { body: { graph_id: graphId } }); setQa(data.qa || []); }; const blended = useMemo(() => { if (!graph?.similarities) return []; return graph.similarities.map((s: any) => ({ ...s, score: (1 - blend) * s.heatmap_similarity + blend * s.thinking_similarity, })); }, [graph, blend]); return (

Action Trace Analysis

{!graph ? (
Analyze runs to compare how personas acted vs. how they thought.
) : (
{/* Blend slider — the core insight */}
Heatmap similarityThinking similarity
setBlend(+e.target.value)} className="w-full accent-teal-500" />
{blended.map((s: any, i: number) => (
{s.a} ↔ {s.b}
{(s.score * 100).toFixed(0)}%
))}
{/* Decision findings — lead with the answer */}
Decisions
{decisions.length === 0 ?

No divergences found.

: decisions.map((d, i) => (
{d.decision_candidate}
{d.signal}
runs: {d.runs.join(', ')} · {d.kind}
))}
{/* AI Graph Answers */}
Graph Answers
{!llm.textConfigured &&
} {qa.length === 0 ?

Ask the graph for grounded Q&A {llm.textConfigured ? '(LLM-generated)' : '(templated without a model)'}.

: qa.map((item, i) => (
{item.question}
{item.answer}
))}
)}
); }; export default AnalysisGraph;