150 lines
5.1 KiB
React
150 lines
5.1 KiB
React
"use client";
|
|
|
|
import React from "react";
|
|
|
|
function NodeBadge({ children, tone = "gray" }) {
|
|
const tones = {
|
|
gray: "border-gray-200 bg-gray-50 text-gray-700",
|
|
blue: "border-blue-200 bg-blue-50 text-blue-700",
|
|
green: "border-green-200 bg-green-50 text-green-700",
|
|
yellow: "border-yellow-200 bg-yellow-50 text-yellow-700",
|
|
red: "border-red-200 bg-red-50 text-red-700",
|
|
purple: "border-purple-200 bg-purple-50 text-purple-700",
|
|
};
|
|
|
|
return (
|
|
<span className={`rounded-full border px-2 py-0.5 text-xs ${tones[tone] || tones.gray}`}>
|
|
{children}
|
|
</span>
|
|
);
|
|
}
|
|
|
|
function NodeGroup({
|
|
title,
|
|
nodes,
|
|
resolvedNodeIds = new Set(),
|
|
newlySurfacedNodeIds = new Set(),
|
|
activeUnknownNodeId = null,
|
|
}) {
|
|
if (!nodes?.length) return null;
|
|
|
|
return (
|
|
<section className="rounded-lg border border-gray-200 bg-white p-4">
|
|
<h3 className="mb-3 text-sm font-semibold text-gray-700">
|
|
{title} ({nodes.length})
|
|
</h3>
|
|
<ul className="space-y-3">
|
|
{nodes.map((node) => (
|
|
<li key={node.id} className="rounded border border-gray-100 bg-gray-50 p-3 text-sm">
|
|
<div className="flex flex-wrap items-center gap-2">
|
|
<span className="font-medium text-gray-900">{node.label}</span>
|
|
<NodeBadge tone="blue">{node.status}</NodeBadge>
|
|
<NodeBadge tone="green">{node.confidence}</NodeBadge>
|
|
{resolvedNodeIds.has(node.id) && (
|
|
<NodeBadge tone="red">resolved unknown</NodeBadge>
|
|
)}
|
|
{newlySurfacedNodeIds.has(node.id) && (
|
|
<NodeBadge tone="purple">newly surfaced unknown</NodeBadge>
|
|
)}
|
|
{activeUnknownNodeId === node.id && (
|
|
<NodeBadge tone="yellow">active unknown</NodeBadge>
|
|
)}
|
|
{node.value != null && (
|
|
<NodeBadge tone="yellow">
|
|
{node.value}
|
|
{node.unit ? ` ${node.unit}` : ""}
|
|
</NodeBadge>
|
|
)}
|
|
</div>
|
|
{node.description && node.description !== node.label && (
|
|
<p className="mt-1 text-gray-600">{node.description}</p>
|
|
)}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
export default function SituationGraphView({
|
|
situationGraph,
|
|
selectedQuestion,
|
|
newlySurfacedNodeIds = [],
|
|
}) {
|
|
if (!situationGraph) return null;
|
|
|
|
const selectedQuestionText =
|
|
typeof selectedQuestion === "string"
|
|
? selectedQuestion
|
|
: selectedQuestion?.question ?? null;
|
|
|
|
const activeUnknown = situationGraph.activeUnknownNodeId
|
|
? situationGraph.nodes.find((node) => node.id === situationGraph.activeUnknownNodeId)
|
|
: null;
|
|
|
|
const nodesByKind = situationGraph.nodes.reduce((acc, node) => {
|
|
if (!acc[node.kind]) acc[node.kind] = [];
|
|
acc[node.kind].push(node);
|
|
return acc;
|
|
}, {});
|
|
|
|
const resolvedNodeIdSet = new Set(situationGraph.resolvedNodeIds || []);
|
|
const newlySurfacedNodeIdSet = new Set(newlySurfacedNodeIds || []);
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
{selectedQuestionText && (
|
|
<section className="rounded-lg border-2 border-green-300 bg-green-50 p-5">
|
|
<h2 className="mb-2 text-base font-bold text-green-800">Selected Question</h2>
|
|
<p className="text-base font-medium text-gray-900">{selectedQuestionText}</p>
|
|
</section>
|
|
)}
|
|
|
|
<section className="rounded-lg border border-gray-200 bg-white p-4">
|
|
<h2 className="mb-2 text-base font-semibold text-gray-900">Situation Graph</h2>
|
|
<dl className="space-y-2 text-sm">
|
|
<div>
|
|
<dt className="text-gray-500">Central statement</dt>
|
|
<dd className="font-medium text-gray-900">{situationGraph.centralStatement}</dd>
|
|
</div>
|
|
{situationGraph.currentSummary && (
|
|
<div>
|
|
<dt className="text-gray-500">Current summary</dt>
|
|
<dd className="text-gray-800">{situationGraph.currentSummary}</dd>
|
|
</div>
|
|
)}
|
|
{activeUnknown && (
|
|
<div>
|
|
<dt className="text-gray-500">Active unknown</dt>
|
|
<dd className="text-gray-900">{activeUnknown.label}</dd>
|
|
</div>
|
|
)}
|
|
<div>
|
|
<dt className="text-gray-500">Edge count</dt>
|
|
<dd className="text-gray-900">{situationGraph.edges.length}</dd>
|
|
</div>
|
|
</dl>
|
|
</section>
|
|
|
|
{Object.entries(nodesByKind).map(([kind, nodes]) => (
|
|
<NodeGroup
|
|
key={kind}
|
|
title={kind.replace(/_/g, " ")}
|
|
nodes={nodes}
|
|
resolvedNodeIds={resolvedNodeIdSet}
|
|
newlySurfacedNodeIds={newlySurfacedNodeIdSet}
|
|
activeUnknownNodeId={situationGraph.activeUnknownNodeId}
|
|
/>
|
|
))}
|
|
|
|
<details className="rounded-lg border border-gray-200 bg-gray-50 p-4">
|
|
<summary className="cursor-pointer text-sm font-medium text-gray-700 underline">
|
|
Raw graph JSON
|
|
</summary>
|
|
<pre className="mt-3 overflow-auto rounded bg-gray-900 p-3 text-xs text-green-400">
|
|
{JSON.stringify(situationGraph, null, 2)}
|
|
</pre>
|
|
</details>
|
|
</div>
|
|
);
|
|
} |