experiment: facilitator view from reasoning graph
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
/**
|
||||
* InvestigationSummaryPanelV3 — Phase 4, Experiment 12
|
||||
* A user-facing facilitator view that translates the reasoning graph into
|
||||
* a concise, human-meaningful presentation.
|
||||
*
|
||||
* Design principles:
|
||||
* - The panel shows up to four sections: what we know, still investigating,
|
||||
* possible explanations, and a quiet summary.
|
||||
* - All content is grounded in existing graph fields. No invented facts.
|
||||
* - Epistemic labels are explicit (structural), not colour-dependent.
|
||||
* - The same panel remains useful during early, active and terminal states.
|
||||
*/
|
||||
|
||||
import { buildFacilitatorViewModel } from "@/lib/presentation/facilitator-view-adapter";
|
||||
|
||||
/* ── Item rendering ─────────────────────────────────────────────── */
|
||||
|
||||
/**
|
||||
* Render a single item with its structural label where applicable.
|
||||
*/
|
||||
function renderItem(item, isExplanation) {
|
||||
if (isExplanation && typeof item === "object") {
|
||||
return (
|
||||
<li key={item.text} className="flex items-start gap-2">
|
||||
<span className="mt-[3px] h-1.5 w-1.5 shrink-0 rounded-full bg-gray-400/50" />
|
||||
<span className="text-sm text-gray-700">{item.text}</span>
|
||||
<span className="ml-auto mt-[-2px] shrink-0 whitespace-nowrap text-[10px] font-medium tracking-wide text-gray-400">
|
||||
{item.label}
|
||||
</span>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<li key={item} className="flex items-start gap-2">
|
||||
<span className="mt-[3px] h-1.5 w-1.5 shrink-0 rounded-full bg-gray-400/50" />
|
||||
<span className="text-sm text-gray-700">{item}</span>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
/* ── Section components ──────────────────────────────────────────── */
|
||||
|
||||
function KnownSection({ title, items }) {
|
||||
if (!items || items.length === 0) return null;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h3 className="mb-2 text-[11px] font-medium tracking-widest uppercase text-gray-400">
|
||||
{title}
|
||||
</h3>
|
||||
<ul className="space-y-1.5">
|
||||
{items.map((item, i) => (
|
||||
<li key={i} className="flex items-start gap-2">
|
||||
<span className="mt-[3px] h-1.5 w-1.5 shrink-0 rounded-full bg-gray-500" />
|
||||
<span className="text-sm text-gray-700">{item}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function InvestigatingSection({ title, items }) {
|
||||
if (!items || items.length === 0) return null;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h3 className="mb-2 text-[11px] font-medium tracking-widest uppercase text-gray-400">
|
||||
{title}
|
||||
</h3>
|
||||
<ul className="space-y-1.5">
|
||||
{items.map((item, i) => (
|
||||
<li key={i} className="flex items-start gap-2">
|
||||
<span className="mt-[3px] h-1.5 w-1.5 shrink-0 rounded-full bg-gray-400/60" />
|
||||
<span className="text-sm text-gray-700">{item}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ExplanationSection({ items }) {
|
||||
if (!items || items.length === 0) return null;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h3 className="mb-2 text-[11px] font-medium tracking-widest uppercase text-gray-400">
|
||||
Possible explanations
|
||||
</h3>
|
||||
<ul className="space-y-1.5">
|
||||
{items.map((item, i) => renderItem(item, true))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function QuietSummary({ text }) {
|
||||
if (!text) return null;
|
||||
|
||||
return (
|
||||
<div className="pt-2 border-t border-gray-200/40">
|
||||
<p className="text-[10px] font-medium tracking-widest uppercase text-gray-300 mb-1.5">
|
||||
Investigation state
|
||||
</p>
|
||||
<p className="text-xs text-gray-400">{text}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/* ── Empty-state fallback ──────────────────────────────────────── */
|
||||
|
||||
function EmptyState() {
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<KnownSection title="What we know" items={[]} />
|
||||
<InvestigatingSection title="Still investigating" items={[]} />
|
||||
{/* Intentionally no Possible explanations section when empty */}
|
||||
<QuietSummary text={null} />
|
||||
<div className="flex items-start gap-2">
|
||||
<span className="mt-[3px] h-1.5 w-1.5 shrink-0 rounded-full bg-gray-400/50" />
|
||||
<p className="text-sm text-gray-500 italic">We are still establishing the basic facts.</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/* ── Main component ────────────────────────────────────────────── */
|
||||
|
||||
function InvestigationSummaryPanelV3({ graph, selectedQuestion, result }) {
|
||||
// Build the view model from the adapter
|
||||
const resolvedIds = new Set(graph?.resolvedNodeIds || []);
|
||||
|
||||
const viewModel = buildFacilitatorViewModel({
|
||||
nodes: graph?.nodes || [],
|
||||
resolvedIds,
|
||||
activeUnknownNodeId: graph?.activeUnknownNodeId || null,
|
||||
edges: graph?.edges || [],
|
||||
selectedQuestion,
|
||||
});
|
||||
|
||||
// Early state fallback
|
||||
if (!viewModel.known.hasItems && !viewModel.investigating.hasItems) {
|
||||
return <EmptyState />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="rounded-lg border border-gray-200/60 bg-gray-50/40 p-5 space-y-4">
|
||||
{/* What we know */}
|
||||
<KnownSection title={viewModel.known.title} items={viewModel.known.items} />
|
||||
|
||||
{/* Still investigating — or "Remaining cautions" in terminal state */}
|
||||
{!viewModel.investigating.shouldOmit && (
|
||||
<InvestigatingSection
|
||||
title={viewModel.investigating.title}
|
||||
items={viewModel.investigating.items}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Possible explanations */}
|
||||
{viewModel.explanations.hasItems && (
|
||||
<ExplanationSection items={viewModel.explanations.items} />
|
||||
)}
|
||||
|
||||
{/* Quiet reasoning summary */}
|
||||
<QuietSummary text={viewModel.summary.text} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default InvestigationSummaryPanelV3;
|
||||
@@ -6,6 +6,7 @@ import GraphUpdateView from "@/components/graph-update-view";
|
||||
import SituationGraphView from "@/components/situation-graph-view";
|
||||
import InvestigationSummaryPanel from "@/components/investigation-summary-panel";
|
||||
import InvestigationSummaryPanelV2 from "@/components/investigation-summary-panel-v2";
|
||||
import InvestigationSummaryPanelV3 from "@/components/investigation-summary-panel-v3";
|
||||
import InvestigationMap from "@/components/investigation-map";
|
||||
|
||||
// ── Technical summary detector (main view filters these) ───
|
||||
@@ -524,8 +525,8 @@ export default function ReasoningWorkspace({
|
||||
const [investigationHistory, setInvestigationHistory] = useState([]);
|
||||
const turnCounter = useRef(0);
|
||||
const pendingTurnRef = useRef(null);
|
||||
// ── Experiment 11: toggle between progress panel versions ──
|
||||
const [showVersionB, setShowVersionB] = useState(false);
|
||||
// ── Experiment 12: toggle between progress panel versions (temporary experimental UI) ──
|
||||
const [panelVariant, setPanelVariant] = useState("c");
|
||||
const { saveSession, loadSession } = useSessionPersistence();
|
||||
|
||||
// Persist workspace state on every successful update (Phase 3)
|
||||
@@ -718,28 +719,55 @@ export default function ReasoningWorkspace({
|
||||
{hasCurrentSummaryCondition && (
|
||||
<>
|
||||
<CurrentUnderstandingCard currentSummary={graph?.currentSummary || result?.updatedSituationGraph?.currentSummary} plainLanguage={propUnderstanding || null} />
|
||||
{/* ── Experiment 11: progress panel A / B toggle ── */}
|
||||
{/* ── Experiment 12: progress panel A / B / C toggle (temporary experimental UI) ── */}
|
||||
{hasGraph && (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-2" role="radiogroup" aria-label="Progress panel variant">
|
||||
<button
|
||||
onClick={() => setShowVersionB(false)}
|
||||
className={`text-xs transition ${!showVersionB ? "font-medium text-gray-700 underline" : "text-gray-400 hover:text-gray-500"}`}
|
||||
role="radio"
|
||||
aria-checked={panelVariant === "a"}
|
||||
onClick={() => setPanelVariant("a")}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "ArrowRight") setPanelVariant("b");
|
||||
if (e.key === "ArrowLeft") setPanelVariant("c");
|
||||
}}
|
||||
className={`text-xs transition ${panelVariant === "a" ? "font-medium text-gray-700 underline" : "text-gray-400 hover:text-gray-500"}`}
|
||||
>
|
||||
Panel A
|
||||
</button>
|
||||
<span className="text-gray-300">/</span>
|
||||
<button
|
||||
onClick={() => setShowVersionB(true)}
|
||||
className={`text-xs transition ${showVersionB ? "font-medium text-gray-700 underline" : "text-gray-400 hover:text-gray-500"}`}
|
||||
role="radio"
|
||||
aria-checked={panelVariant === "b"}
|
||||
onClick={() => setPanelVariant("b")}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "ArrowRight") setPanelVariant("c");
|
||||
if (e.key === "ArrowLeft") setPanelVariant("a");
|
||||
}}
|
||||
className={`text-xs transition ${panelVariant === "b" ? "font-medium text-gray-700 underline" : "text-gray-400 hover:text-gray-500"}`}
|
||||
>
|
||||
Panel B
|
||||
</button>
|
||||
<span className="text-gray-300">/</span>
|
||||
<button
|
||||
role="radio"
|
||||
aria-checked={panelVariant === "c"}
|
||||
onClick={() => setPanelVariant("c")}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "ArrowRight") setPanelVariant("a");
|
||||
if (e.key === "ArrowLeft") setPanelVariant("b");
|
||||
}}
|
||||
className={`text-xs transition ${panelVariant === "c" ? "font-medium text-gray-700 underline" : "text-gray-400 hover:text-gray-500"}`}
|
||||
>
|
||||
Panel C
|
||||
</button>
|
||||
</div>
|
||||
<div className="opacity-75">
|
||||
{showVersionB
|
||||
? <InvestigationSummaryPanelV2 graph={graph} selectedQuestion={selectedQ} result={result} updateStatus={updateStatus} />
|
||||
: <InvestigationSummaryPanel graph={graph} selectedQuestion={selectedQ} result={result} updateStatus={updateStatus} />
|
||||
{panelVariant === "a"
|
||||
? <InvestigationSummaryPanel graph={graph} selectedQuestion={selectedQ} result={result} updateStatus={updateStatus} />
|
||||
: panelVariant === "b"
|
||||
? <InvestigationSummaryPanelV2 graph={graph} selectedQuestion={selectedQ} result={result} updateStatus={updateStatus} />
|
||||
: <InvestigationSummaryPanelV3 graph={graph} selectedQuestion={selectedQ} result={result} />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user