fix(confidence-engine): preserve scenario during outage
This commit is contained in:
@@ -33,6 +33,28 @@ export async function submitScenarioForStartCase(fetchImpl, scenario) {
|
||||
});
|
||||
}
|
||||
|
||||
export function isUnavailableStartResponse(response, data) {
|
||||
return response.status === 503 &&
|
||||
data?.success === false &&
|
||||
data?.error === "Reasoning service is temporarily unavailable.";
|
||||
}
|
||||
|
||||
export function UnavailableStartPanel({ onRetry }) {
|
||||
return (
|
||||
<div className="rounded-lg border border-amber-300 bg-amber-50 px-4 py-3 text-sm text-amber-900">
|
||||
<p className="font-medium">Confidence Engine is temporarily unavailable.</p>
|
||||
<p className="mt-1">We couldn't process this right now. Your scenario is still here and you can try again.</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onRetry}
|
||||
className="mt-3 rounded-lg border border-amber-400 px-4 py-2 text-sm font-medium text-amber-900 transition hover:bg-amber-100"
|
||||
>
|
||||
Retry
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export async function submitAnswerForUpdateCase(
|
||||
fetchImpl,
|
||||
{ situationGraph, previousQuestion, answer, findings },
|
||||
@@ -672,8 +694,7 @@ export default function ScenarioForm({ investigationId, onNavigateToReport }) {
|
||||
updateStatus === "loading"
|
||||
);
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
const handleStart = async () => {
|
||||
setStatus("loading");
|
||||
setResult(null);
|
||||
setAnswer("");
|
||||
@@ -699,6 +720,8 @@ export default function ScenarioForm({ investigationId, onNavigateToReport }) {
|
||||
/* ── v0.59a — provenance: first meaningful change sets revision to 1 ── */
|
||||
setInvestigationRevision(1);
|
||||
persist({ id: investigationId, scenario, situationGraph: normalised.situationGraph, selectedQuestion: normalised.selectedQuestion, summary: data.summary ?? null, updatedAt: new Date().toISOString(), focusedContributions, findings: [], investigationReport, investigationRevision: 1 });
|
||||
} else if (isUnavailableStartResponse(res, data)) {
|
||||
setStatus("unavailable");
|
||||
} else {
|
||||
setStatus("error");
|
||||
setCurrentUnderstanding(data.summary ?? null);
|
||||
@@ -710,6 +733,11 @@ export default function ScenarioForm({ investigationId, onNavigateToReport }) {
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
void handleStart();
|
||||
};
|
||||
|
||||
const handleUpdate = async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -799,7 +827,7 @@ export default function ScenarioForm({ investigationId, onNavigateToReport }) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* ── Idle form for scenario input ─ */}
|
||||
{!result?.situationGraph && status === "idle" && (
|
||||
{!result?.situationGraph && (status === "idle" || status === "unavailable") && (
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
|
||||
{/* Two-column landing workspace */}
|
||||
@@ -864,6 +892,7 @@ export default function ScenarioForm({ investigationId, onNavigateToReport }) {
|
||||
<p className="mt-3 text-xs italic text-gray-400">
|
||||
You do not need all the answers yet.
|
||||
</p>
|
||||
{status === "unavailable" && <UnavailableStartPanel onRetry={handleStart} />}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -14,7 +14,9 @@ import ReasoningWorkspace, {
|
||||
} from "@/components/reasoning-workspace.jsx";
|
||||
import {
|
||||
ScenarioResultPanels,
|
||||
UnavailableStartPanel,
|
||||
UpdateErrorPanel,
|
||||
isUnavailableStartResponse,
|
||||
submitAnswerForUpdateCase,
|
||||
submitScenarioForStartCase,
|
||||
} from "@/components/scenario-form.jsx";
|
||||
@@ -372,6 +374,26 @@ function makeCommercialUpdateSuccess(overrides = {}) {
|
||||
}
|
||||
|
||||
describe("scenario-form UI helpers", () => {
|
||||
it("recognises only the established sanitized unavailable start response", () => {
|
||||
expect(isUnavailableStartResponse(
|
||||
{ status: 503 },
|
||||
{ success: false, error: "Reasoning service is temporarily unavailable." },
|
||||
)).toBe(true);
|
||||
expect(isUnavailableStartResponse(
|
||||
{ status: 500 },
|
||||
{ success: false, error: "Reasoning service is temporarily unavailable." },
|
||||
)).toBe(false);
|
||||
});
|
||||
|
||||
it("presents generic recovery without provider details", () => {
|
||||
const html = renderToStaticMarkup(<UnavailableStartPanel onRetry={vi.fn()} />);
|
||||
|
||||
expect(html).toContain("Confidence Engine is temporarily unavailable.");
|
||||
expect(html).toContain("Your scenario is still here and you can try again.");
|
||||
expect(html).toContain(">Retry<");
|
||||
expect(html).not.toMatch(/ollama|provider|local model|server|infrastructure/i);
|
||||
});
|
||||
|
||||
it("submits to /api/cases/start", async () => {
|
||||
const fetchImpl = vi.fn().mockResolvedValue({ ok: true });
|
||||
|
||||
|
||||
Reference in New Issue
Block a user