);
}
@@ -152,49 +170,70 @@ function hasGenuineCompletion(graph) {
return true;
}
-// ── Investigation progress card ──────────────────────────────
-function InvestigationProgress({ graph, noQuestionReason: rwNoQuestionReason }) {
- if (!graph?.nodes?.length) return null;
-
- const resolvedIds = new Set(graph.resolvedNodeIds || []);
- const unknowns = graph.nodes.filter((n) => n.kind === "unknown");
- const remainingCount = unknowns.filter(
- (u) => u.status !== "resolved" && !resolvedIds.has(u.id),
- ).length;
- const activeNode = graph.activeUnknownNodeId
+// ── Current focus card ───────────────────────────────────────
+function CurrentFocusCard({ graph }) {
+ const activeNode = graph?.activeUnknownNodeId
? graph.nodes.find((n) => n.id === graph.activeUnknownNodeId)
: null;
- const isComplete = hasGenuineCompletion(graph);
+ if (!graph || !activeNode) return null;
return (
-
- {remainingCount > 0 && !isComplete ? (
-
- We are still building confidence about your situation.{" "}
- {remainingCount === 1
- ? "One area remains."
- : `${remainingCount} areas remain.`}
-
- ) : (
-
- All areas under investigation are now complete.
-
- )}
- {activeNode && (
- <>
-
- Current focus
-
-
{activeNode.label}
- {activeNode.description && activeNode.description !== activeNode.label && (
-
Why it matters: {activeNode.description}
- )}
- >
- )}
- {!activeNode && remainingCount === 0 && (
-
There is nothing further to investigate at this time.
- )}
+
+
+ Current focus
+
+
+ We are investigating one part of your situation at a time.
+ {activeNode && (
+ <>
+
+ Right now we are trying to understand{" "}
+ {activeNode.label}.
+ >
+ )}
+
+
+ );
+}
+
+// ── Investigation history card ────────────────────────────────
+function InvestigationHistoryCard({ turn }) {
+ return (
+
+
+ {new Date(turn.timestamp).toLocaleString(undefined, {
+ month: "short",
+ day: "numeric",
+ hour: "2-digit",
+ minute: "2-digit",
+ })}
+
+
+
{turn.question}
+
{turn.answer}
+ {turn.engineResponse && (
+
{turn.engineResponse}
+ )}
+
+
+ );
+}
+
+// ── Investigation history section ─────────────────────────────
+function InvestigationHistory({ turns }) {
+ if (!turns || turns.length === 0) return null;
+
+ return (
+
+
+ Investigation history
+
+
+ {turns.map((turn, idx) => (
+
+ ))}
+
);
}
@@ -251,13 +290,12 @@ function LoadingOverlay({ isLoading, elapsed, currentMessage, variant }) {
}
// ── Update acknowledgement ────────────────────────────────────
-function UpdateAcknowledgement({ answer, updateResult }) {
- if (!updateResult || !answer?.trim()) return null;
+function UpdateAcknowledgement({ updateResult }) {
+ if (!updateResult) return null;
- const hasResolvedNodes =
- updateResult.resolvedUnknownNodeIds && updateResult.resolvedUnknownNodeIds.length > 0;
- const hasAffectedNodes =
- updateResult.affectedNodeIds && updateResult.affectedNodeIds.length > 0;
+ const summary = updateResult.summary;
+ const hasResolvedNodes = updateResult.resolvedUnknownNodeIds?.length > 0;
+ const hasAffectedNodes = updateResult.affectedNodeIds?.length > 0;
const graph = updateResult.updatedSituationGraph;
function getNodeText(nodeId) {
@@ -274,34 +312,24 @@ function UpdateAcknowledgement({ answer, updateResult }) {
}
let changedText;
- if (hasResolvedNodes || hasAffectedNodes) {
+ if (hasResolvedNodes) {
const items = [];
- if (hasResolvedNodes) {
- for (const id of updateResult.resolvedUnknownNodeIds.slice(0, 5)) {
- items.push(getNodeText(id));
- }
- if (updateResult.resolvedUnknownNodeIds.length > 5) {
- items.push(`and ${updateResult.resolvedUnknownNodeIds.length - 5} more resolved`);
- }
+ for (const id of updateResult.resolvedUnknownNodeIds.slice(0, 3)) {
+ items.push(getNodeText(id));
}
- if (hasAffectedNodes && !hasResolvedNodes) {
- for (const id of updateResult.affectedNodeIds.slice(0, 5)) {
- items.push(getNodeText(id));
- }
- if (updateResult.affectedNodeIds.length > 5) {
- items.push(`and ${updateResult.affectedNodeIds.length - 5} more affected`);
- }
+ if (updateResult.resolvedUnknownNodeIds.length > 3) {
+ items.push(`and ${updateResult.resolvedUnknownNodeIds.length - 3} more resolved`);
}
- if (items.length === 0 && updateResult.changesApplied) {
- const parts = [];
- const ca = updateResult.changesApplied;
- if (ca.addedNodeCount) parts.push(`${ca.addedNodeCount} node(s) added`);
- if (ca.updatedNodeCount) parts.push(`${ca.updatedNodeCount} node(s) updated`);
- if (ca.resolvedUnknownCount) parts.push(`${ca.resolvedUnknownCount} unknown(s) resolved`);
- changedText = parts.join(", ");
- } else {
- changedText = items.join(". ") + ".";
+ changedText = items.join(". ") + ".";
+ } else if (hasAffectedNodes) {
+ const items = [];
+ for (const id of updateResult.affectedNodeIds.slice(0, 3)) {
+ items.push(getNodeText(id));
}
+ if (updateResult.affectedNodeIds.length > 3) {
+ items.push(`and ${updateResult.affectedNodeIds.length - 3} more affected`);
+ }
+ changedText = items.join(". ") + ".";
} else if (updateResult.changesApplied) {
const ca = updateResult.changesApplied;
const parts = [];
@@ -312,23 +340,11 @@ function UpdateAcknowledgement({ answer, updateResult }) {
changedText = parts.length > 0 ? parts.join(", ") : null;
}
- const summary = updateResult.summary || null;
- const displayChanged = summary || changedText || "Your answer has been added to the investigation.";
+ const displayMessage = summary || changedText || "Your answer has been added to the investigation.";
return (
-
-
-
- You told us
-
-
{answer}
-
-
-
- What changed
-
-
{displayChanged}
-
+
+ {displayMessage}
);
}
@@ -367,6 +383,35 @@ export default function ReasoningWorkspace({
onAnswerSubmit,
lastSubmittedAnswer,
}) {
+ const [investigationHistory, setInvestigationHistory] = useState([]);
+
+ // Capture the previous question before each new question is set
+ const prevQuestionRef = useRef(null);
+ const hasCapturedInitialQuestion = useRef(false);
+
+ useEffect(() => {
+ if (result?.selectedQuestion && !hasCapturedInitialQuestion.current) {
+ prevQuestionRef.current = result.selectedQuestion;
+ hasCapturedInitialQuestion.current = true;
+ }
+ }, [result?.selectedQuestion]);
+
+ // Append completed turn to history after a successful update
+ useEffect(() => {
+ if (updateStatus === "success" && lastSubmittedAnswer) {
+ const q = prevQuestionRef.current;
+ setInvestigationHistory((prev) => [
+ ...prev,
+ {
+ question: typeof q === "string" ? q : q?.question ?? "",
+ answer: lastSubmittedAnswer,
+ engineResponse: result?.summary || null,
+ timestamp: Date.now(),
+ },
+ ]);
+ }
+ }, [updateStatus, lastSubmittedAnswer]);
+
const { elapsed: startElapsed, currentMessage: startMsg } = useLoadingStatus(
INITIAL_MESSAGES,
status === "loading"
@@ -392,12 +437,7 @@ export default function ReasoningWorkspace({
const newlySurfacedNodeIds = result?.newlySurfacedNodeIds || [];
const noQuestionReason = diagnostics?.noQuestionReason ?? null;
- const remainingUnknowns = graph?.nodes?.filter(
- (n) => n.kind === "unknown" && n.status !== "resolved" && !(graph.resolvedNodeIds || []).includes(n.id),
- );
-
const genuineCompletion = hasGenuineCompletion(graph);
- const unresolvedRemaining = !genuineCompletion && remainingUnknowns ? remainingUnknowns.length > 0 : false;
return (
@@ -427,26 +467,27 @@ export default function ReasoningWorkspace({
) : (
<>
- {/* ── Post-update acknowledgement ─────────────── */}
- {updateStatus === "success" && graph && (
-
- )}
+ {/* Post-update acknowledgement */}
+ {updateStatus === "success" && canAnswer &&
}
- {/* Completion state (only when there is no next question and nothing remains) */}
+ {/* Completion state */}
{status === "success" && !canAnswer && graph && genuineCompletion && (
)}
- {status === "success" && !canAnswer && graph && unresolvedRemaining && updateStatus !== "success" && (
+ {status === "success" && !canAnswer && graph && !genuineCompletion && updateStatus !== "success" && (
There is no further question the engine can justify at the moment.
More evidence may be needed before a next step is clear.
)}
+ {/* Situation context */}
{graph &&
}
{graph &&
}
- {canAnswer &&
}
- {graph &&
}
+
+ {/* Active investigation (only when we have a question to answer) */}
+ {canAnswer &&
}
+ {canAnswer &&
}
{/* ── Answer form ──────────────────────────────── */}
{canAnswer && (
@@ -480,6 +521,9 @@ export default function ReasoningWorkspace({
)}
+ {/* ── Investigation history (below the answer form) ─ */}
+
+
{/* ── Developer details (collapsed by default) ─── */}
{(status === "success" || status === "error") && graph && (