chore: preserve initial reconstruction prototype

This commit is contained in:
2026-07-31 18:51:38 +01:00
commit a2f9e472ea
26 changed files with 8874 additions and 0 deletions
+50
View File
@@ -0,0 +1,50 @@
const ValidationIndicator = ({ status }) => {
const styles = {
valid: "text-green-600",
partial: "text-yellow-600",
invalid: "text-red-600",
};
const labels = {
valid: "✅ Validation passed",
partial: "⚠️ Partial validation",
invalid: "❌ Validation failed",
};
return (
<div className={`flex items-center gap-2 ${styles[status] || "text-gray-500"}`}>
<span className="font-medium">{labels[status] || status}</span>
</div>
);
};
export default function DiagnosticsView({ result }) {
const metrics = [
{ label: "Model", value: result.modelName || "?" },
{ label: "Duration", value: result.responseDurationMs != null ? `${result.responseDurationMs}ms` : "?" },
{ label: "Validation", value: <ValidationIndicator status={result.validationStatus || "invalid"} /> },
];
return (
<div className="rounded border border-gray-200 bg-gray-50 p-4">
<h2 className="mb-3 text-sm font-semibold text-gray-500">Diagnostics</h2>
<dl className="grid grid-cols-[auto_1fr] gap-x-4 gap-y-1.5 text-sm">
{metrics.map(({ label, value }) => (
<div key={label}>
<dt className="text-gray-500">{label}</dt>
<dd>{value}</dd>
</div>
))}
</dl>
{result.rawResponse && (
<details className="mt-4">
<summary className="cursor-pointer text-xs text-gray-500 underline hover:text-gray-700">
View raw model response
</summary>
<pre className="mt-2 max-h-60 overflow-auto rounded bg-gray-900 px-3 py-2 text-xs leading-relaxed text-green-400">
{result.rawResponse}
</pre>
</details>
)}
</div>
);
}
+70
View File
@@ -0,0 +1,70 @@
const categoryLabels = {
observations: "Direct Observations",
reportedClaims: "Reported Claims",
assumptions: "Unsupported Assumptions",
entities: "Entities",
transitions: "Transitions",
expectedButMissing: "Expected But Missing",
presentButUnexpected: "Present But Unexpected",
contradictions: "Contradictions",
openUncertainties: "Open Uncertainties",
};
const confidenceColor = {
low: "text-red-600 bg-red-50 border-red-200",
medium: "text-yellow-700 bg-yellow-50 border-yellow-200",
high: "text-green-700 bg-green-50 border-green-200",
};
const ConfidenceBadge = ({ level }) => (
<span className={`inline-block rounded-full border px-2 py-0.5 text-xs font-medium ${confidenceColor[level] || "text-gray-600 bg-gray-100"}`}>
{level}
</span>
);
function ItemList({ items, renderExtra }) {
if (!items?.length) return <p className="text-sm italic text-gray-400">None identified</p>;
return (
<ul className="space-y-2">
{items.map((item) => (
<li key={item.id} className="rounded border border-gray-200 bg-white px-3 py-2 text-sm">
<div className="flex items-center gap-2">
<span className="font-mono text-xs text-gray-400">#{item.id}</span>
<ConfidenceBadge level={item.confidence} />
</div>
<p className="mt-1">{item.description}</p>
{renderExtra && renderExtra(item)}
</li>
))}
</ul>
);
}
export default function ReconstructionView({ reconstruction, partial }) {
if (partial) {
return (
<div className="rounded-lg border border-yellow-300 bg-yellow-50 px-4 py-3 text-sm text-yellow-800">
Partial result some fields failed validation. Showing what was accepted.
</div>
);
}
const categories = Object.entries(categoryLabels).map(([key, label]) => ({
key,
label,
items: reconstruction[key],
}));
return (
<div className="space-y-1">
<h2 className="mb-3 text-lg font-semibold">Reconstruction</h2>
{categories.map(({ key, label, items }) => (
<div key={key} className="mb-4 rounded border border-gray-200 bg-white p-4">
<h3 className="mb-2 text-sm font-medium text-gray-600">{label}</h3>
<ItemList items={items} />
</div>
))}
</div>
);
}
+107
View File
@@ -0,0 +1,107 @@
"use client";
import { useState, useRef } from "react";
import ReconstructionView from "@/components/reconstruction-view";
import DiagnosticsView from "@/components/diagnostics-view";
const MAX_LENGTH = 10000;
export default function ScenarioForm() {
const [scenario, setScenario] = useState("");
const [status, setStatus] = useState("idle"); // idle | loading | error | success
const [result, setResult] = useState(null);
const textareaRef = useRef(null);
const handleSubmit = async (e) => {
e.preventDefault();
setStatus("loading");
setResult(null);
try {
const res = await fetch("/api/analyse", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ scenario }),
});
const data = await res.json();
if (res.ok && data.validationStatus === "valid") {
setStatus("success");
setResult(data);
} else {
setStatus("error");
setResult(data);
}
} catch (err) {
setStatus("error");
setResult({ error: err.message || "Network request failed" });
}
};
// Always show diagnostics when there's a result (even if validation failed)
const hasDiagnostics = result && (result.reconstruction || result.modelName || result.responseDurationMs !== undefined);
return (
<div className="space-y-6">
<form onSubmit={handleSubmit} className="space-y-4">
<textarea
ref={textareaRef}
value={scenario}
onChange={(e) => setScenario(e.target.value)}
placeholder="Describe the scenario you want analysed..."
rows={10}
className="w-full rounded-lg border border-gray-300 px-4 py-3 text-sm focus:border-gray-500 focus:outline-none focus:ring-2 focus:ring-gray-400"
/>
<div className="flex items-center justify-between">
<span className="text-xs text-gray-400">{scenario.length}/{MAX_LENGTH}</span>
<button
type="submit"
disabled={status === "loading" || !scenario.trim()}
className="rounded-lg bg-gray-900 px-6 py-2.5 text-sm font-medium text-white transition hover:bg-gray-700 disabled:cursor-not-allowed disabled:opacity-40"
>
{status === "loading" ? "Analysing..." : "Analyse"}
</button>
</div>
</form>
{status === "error" && (
<div className="space-y-3">
{result?.error && (
<div className="rounded-lg border border-red-300 bg-red-50 px-4 py-3 text-sm text-red-700 whitespace-pre-wrap">
Error: {result.error}
</div>
)}
{hasDiagnostics && result?.modelName && (
<dl className="grid grid-cols-[auto_1fr] gap-x-4 gap-y-1.5 text-sm">
<dt className="text-gray-500">Model</dt>
<dd>{result.modelName}</dd>
<dt className="text-gray-500">Duration</dt>
<dd>{result.responseDurationMs != null ? `${result.responseDurationMs}ms` : "?"}</dd>
</dl>
)}
</div>
)}
{status === "success" && result?.reconstruction && (
<div className="space-y-4">
<ReconstructionView reconstruction={result.reconstruction} />
<DiagnosticsView result={result} />
</div>
)}
{status === "error" && result?.reconstruction && (
<div className="space-y-3">
<div className="rounded-lg border border-yellow-300 bg-yellow-50 px-4 py-2 text-sm text-yellow-800">
Partial result some fields failed validation. Showing what was accepted.
</div>
<ReconstructionView reconstruction={result.reconstruction} partial />
</div>
)}
{status === "loading" && (
<div className="py-12 text-center text-sm text-gray-400">Waiting for model response...</div>
)}
</div>
);
}