"use client"; import React, { useState, useRef, useEffect, useMemo } from "react"; import DiagnosticsView from "@/components/diagnostics-view"; import GraphUpdateView from "@/components/graph-update-view"; import SituationGraphView from "@/components/situation-graph-view"; // ── Status message pools for loading feedback ──────────────── const INITIAL_MESSAGES = [ { min: 0, text: "Reading your situation" }, { min: 10, text: "Building a structured understanding" }, { min: 25, text: "Identifying what is known and still unclear" }, { min: 45, text: "Selecting the next useful question" }, ]; const UPDATE_MESSAGES = [ { min: 0, text: "Considering your answer" }, { min: 10, text: "Updating the situation" }, { min: 25, text: "Checking what changed" }, { min: 45, text: "Choosing the next question" }, ]; function useLoadingStatus(messages, isLoading) { const [elapsed, setElapsed] = useState(0); const startRef = useRef(null); useEffect(() => { if (isLoading) { startRef.current = Date.now(); const iv = setInterval(() => { setElapsed(Math.floor((Date.now() - startRef.current) / 1000)); }, 1000); return () => clearInterval(iv); } else { setElapsed(0); startRef.current = null; } }, [isLoading]); const currentMessage = useMemo(() => { if (!messages || messages.length === 0) return ""; let msg = messages[0].text; for (const m of messages) { if (elapsed >= m.min) msg = m.text; } return msg; }, [messages, elapsed]); return { elapsed, currentMessage }; } // ── Spinner component ─────────────────────────────────────── function ActivitySpinner() { return ( ); } // ── Situation card ──────────────────────────────────────────── function SituationCard({ centralStatement }) { if (!centralStatement) return null; return (

Your situation

{centralStatement}

); } // ── Current understanding card ──────────────────────────────── function CurrentUnderstanding({ currentSummary }) { if (currentSummary) { return (

Current understanding

{currentSummary}

); } return (

We have started to separate what is known from what still needs checking.

); } // ── Current focus card ──────────────────────────────────────── function CurrentFocus({ graph }) { if (!graph?.activeUnknownNodeId || !graph.nodes?.length) return null; const activeNode = graph.nodes.find( (n) => n.id === graph.activeUnknownNodeId ); if (!activeNode) return null; // Find the unknown label that maps to activeUnknownNodeId from selectedQuestion or nodes const statusText = activeNode.status === "resolved" ? "Answered" : "Under investigation"; return (

What we are working out

{activeNode.label}

{activeNode.description && activeNode.description !== activeNode.label && (

Why it matters: {activeNode.description}

)} {statusText}
); } // ── Next question card (prominent) ──────────────────────────── function NextQuestionCard({ selectedQuestion }) { if (!selectedQuestion) return null; const q = typeof selectedQuestion === "string" ? selectedQuestion : selectedQuestion.question; if (!q) return null; return (

Next question

{q}

); } // ── Reasoning progress card ──────────────────────────────────── function ReasoningProgress({ graph }) { if (!graph?.nodes?.length) return null; const unknowns = graph.nodes.filter((n) => n.kind === "unknown"); const remainingCount = unknowns.filter((u) => u.status !== "resolved").length; return (

Reasoning progress

{remainingCount > 0 ? (

We have identified {remainingCount} area{remainingCount === 1 ? "" : "s"} that still need investigation.

) : (

All areas under investigation are now complete.

)} {graph.activeUnknownNodeId && (() => { const activeNode = graph.nodes.find((n) => n.id === graph.activeUnknownNodeId); if (!activeNode) return null; return ( <>

Current focus

{activeNode.label}

{activeNode.description && activeNode.description !== activeNode.label && (

Why this matters: {activeNode.description}

)} ); })()} {!graph.activeUnknownNodeId && remainingCount === 0 && (

There is no active area of investigation at the moment.

)}
); } // ── No question state ───────────────────────────────────────── function NoQuestionMessage({ noQuestionReason }) { let message = "There is no next question at the moment."; if (noQuestionReason) { const reason = String(noQuestionReason); if (reason.toLowerCase().includes("satisfied") || reason.toLowerCase().includes("complete")) { message += " The situation has been fully investigated."; } else if (reason.toLowerCase().includes("insufficient")) { message += " We need more information to determine the next step."; } else { message += " " + reason; } } return (

{message}

); } // ── Loading overlay (for both start and update) ─────────────── function LoadingOverlay({ isLoading, elapsed, currentMessage, variant }) { if (!isLoading) return null; const messages = variant === "update" ? UPDATE_MESSAGES : INITIAL_MESSAGES; let statusText = messages[0].text; for (const m of messages) { if (elapsed >= m.min) statusText = m.text; } return (
Working through your situation

{statusText}

This has been running for {elapsed}s. {variant === "initial" && elapsed >= 45 && ( This can take around a minute with the current local model. )}

); } // ── Developer details disclosure ────────────────────────────── function DeveloperDetails({ graph, selectedQuestion, diagnostics, newlySurfacedNodeIds, updateResult }) { return (
Developer details
{graph && ( )} {updateResult && ( )} {diagnostics && }
); } // ── Main workspace component ────────────────────────────────── export default function ReasoningWorkspace({ status, updateStatus, result, answer, setAnswer, onAnswerSubmit, }) { const { elapsed: startElapsed, currentMessage: startMsg } = useLoadingStatus( INITIAL_MESSAGES, status === "loading" ); const { elapsed: updateElapsed, currentMessage: updateMsg } = useLoadingStatus( UPDATE_MESSAGES, updateStatus === "loading" ); const canAnswer = status === "success" && updateStatus === "idle" && Boolean(result?.situationGraph) && Boolean(result?.selectedQuestion); const selectedQ = result?.selectedQuestion ?? null; const graph = result?.situationGraph ?? null; const diagnostics = result?.diagnostics ?? null; const newlySurfacedNodeIds = result?.newlySurfacedNodeIds || []; const noQuestionReason = diagnostics?.noQuestionReason ?? null; return (
{/* ── Loading overlays ─────────────────────────────── */} {updateStatus === "loading" && (
)} {/* ── User-facing workspace ────────────────────────── */} {(status === "success" || status === "error") && !graph ? (
{noQuestionReason ? "Validation failed — no structured graph output was produced." : "The analysis completed but did not produce a structured result."}
) : ( <> {/* When analysis succeeded but there's no question to answer */} {status === "success" && !canAnswer && graph && ( )} {graph && } {graph && } {graph && } {canAnswer && } {graph && } {/* ── Answer form ──────────────────────────────── */} {canAnswer && (