// Optional server bridge for the Blueprint Details workspace. // // The workspace is driven entirely by the in-browser engine + store (C0/C1/C2) — these fetch // wrappers are the OPTIONAL enhancement path: when a control plane is configured they sync / // fetch real designer output, and on any failure they fall back to the local engine. Nothing on // the render path depends on them. import { apiBaseUrl } from "./api-client"; import { authHeaders } from "./auth-token"; import * as engine from "@/lib/blueprint-engine"; export type { ArchitectureNode, BlueprintDetailsData, DesignerBatch, DesignerCandidate, DesignerChatMessage, FilePlanItem, } from "@/types/blueprint-state"; import type { BlueprintDetailsData, DesignerCandidate } from "@/types/blueprint-state"; // Local engine derivations re-exported for back-compat with existing callers. export const deriveLocalDetails = engine.generate; export const deriveLocalCandidates = engine.generateCandidates; /** The 3 blueprint cards. `useDesigner=false` (toggle off) / offline → local engine. */ export async function fetchDesignerCandidates( idea: string, opts: { useDesigner?: boolean } = {}, ): Promise { if (opts.useDesigner === false) return engine.generateCandidates(idea); try { const url = `${apiBaseUrl}/api/v1/blueprints/designer-candidates?idea=${encodeURIComponent(idea)}`; const res = await fetch(url, { headers: { ...authHeaders() } }); if (!res.ok) throw new Error(`designer-candidates ${res.status}`); const body = (await res.json()) as { candidates?: DesignerCandidate[] }; return body.candidates && body.candidates.length ? body.candidates : engine.generateCandidates(idea); } catch { return engine.generateCandidates(idea); } } /** Optional: fetch real designer Details. Offline / toggle off → local engine. */ export async function fetchBlueprintDetails( candidateId: string, idea: string, opts: { useDesigner?: boolean } = {}, ): Promise { if (opts.useDesigner === false) return engine.generate(candidateId, idea); try { const url = `${apiBaseUrl}/api/v1/blueprints/${candidateId}/details?idea=${encodeURIComponent(idea)}`; const res = await fetch(url, { headers: { ...authHeaders() } }); if (!res.ok) throw new Error(`details ${res.status}`); return (await res.json()) as BlueprintDetailsData; } catch { return engine.generate(candidateId, idea); } } /** Optional server refinement; the workspace already applied the local engine result. */ export async function sendBlueprintChat( candidateId: string, idea: string, message: string, ): Promise<{ reply: string; details: BlueprintDetailsData }> { try { const url = `${apiBaseUrl}/api/v1/blueprints/${candidateId}/chat`; const res = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json", ...authHeaders() }, body: JSON.stringify({ idea, message }), }); if (!res.ok) throw new Error(`chat ${res.status}`); return (await res.json()) as { reply: string; details: BlueprintDetailsData }; } catch { const res = engine.apply(engine.generate(candidateId, idea), message); return { reply: res.reply, details: res.data }; } } export async function saveBlueprintDetails( candidateId: string, idea: string, details: BlueprintDetailsData, buildId?: string, ): Promise { try { const url = `${apiBaseUrl}/api/v1/blueprints/${candidateId}/save`; const res = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json", ...authHeaders() }, body: JSON.stringify({ idea, build_id: buildId, details }), }); if (!res.ok) throw new Error(`save ${res.status}`); return (await res.json()) as BlueprintDetailsData; } catch { return details; // offline: kept locally (localStorage persistence is C4) } } /** Restore a previously-saved blueprint + chat for a build (owner-scoped). */ export async function fetchSavedBlueprint( candidateId: string, buildId: string, ): Promise { try { const url = `${apiBaseUrl}/api/v1/blueprints/${candidateId}/saved?build_id=${encodeURIComponent(buildId)}`; const res = await fetch(url, { headers: { ...authHeaders() } }); if (!res.ok) throw new Error(`saved ${res.status}`); const body = (await res.json()) as { found: boolean; details: BlueprintDetailsData | null }; return body.found ? body.details : null; } catch { return null; } }