/** Typed API client for the Virtual Foreman backend. */ import { API_URL } from "./config"; export interface TechStep { order: number; title: string; description: string; duration_hours?: number | null; required_acts: string[]; dependencies: number[]; weather_sensitive: boolean; } export interface TechStack { source_input: string; inferred_summary: string; steps: TechStep[]; required_documents: string[]; missing_info_prompts: string[]; model_used: string; generated_at: string; } export interface InferredWorkResponse { tech_stack: TechStack; request_id: string; } // ----- Blueprint extraction ----- export interface GridAxis { label: string; order: number; } export interface BlueprintDimensions { length_m?: number | null; width_m?: number | null; height_m?: number | null; total_area_m2?: number | null; } export interface BlueprintSheet { sheet_number: string; title: string; scale?: string | null; dimensions?: BlueprintDimensions | null; room_labels: string[]; grid_axes: GridAxis[]; notes_ru: string[]; } export interface BlueprintExtraction { project_hint: string; sheets: BlueprintSheet[]; inferred_disciplines: string[]; missing_info_prompts: string[]; file_name: string; file_size_bytes: number; mime_type: string; model_used: string; generated_at: string; } export interface BlueprintInferenceResponse { extraction: BlueprintExtraction; request_id: string; } // ----- Projects ----- export interface ProjectPayload { contract_no: string; object_name: string; client: string; technical_supervisor: string; contractor: string; work_producer: string; chief_engineer: string; start_date?: string; end_date_estimated?: string; } export interface Project extends ProjectPayload { id: string; created_at: string; persisted?: boolean; warning?: string; } // ----- Inference history ----- export interface InferenceHistoryRow { id: string; project_id: string | null; request_id: string; source_text: string | null; payload: unknown; model_used: string | null; created_at: string; } // ----- Document templates ----- export interface DocumentContext { [key: string]: unknown; } async function postJSON(path: string, body: unknown): Promise { const res = await fetch(`${API_URL}${path}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); if (!res.ok) { const detail = await res.text(); throw new Error(`API ${path} failed: ${res.status} ${detail}`); } return (await res.json()) as T; } async function getJSON(path: string): Promise { const res = await fetch(`${API_URL}${path}`, { method: "GET", headers: { Accept: "application/json" }, }); if (!res.ok) { const detail = await res.text(); throw new Error(`API ${path} failed: ${res.status} ${detail}`); } return (await res.json()) as T; } async function delJSON(path: string): Promise { const res = await fetch(`${API_URL}${path}`, { method: "DELETE", headers: { Accept: "application/json" }, }); if (!res.ok) { const detail = await res.text(); throw new Error(`API ${path} failed: ${res.status} ${detail}`); } return (await res.json()) as T; } async function postForm( path: string, form: FormData, opts: { signal?: AbortSignal } = {} ): Promise { const res = await fetch(`${API_URL}${path}`, { method: "POST", body: form, signal: opts.signal, }); if (!res.ok) { const detail = await res.text(); throw new Error(`API ${path} failed: ${res.status} ${detail}`); } return (await res.json()) as T; } export async function inferWork(payload: { raw_text: string; date_from: string; date_to: string; axes?: string; project_id?: string | null; }): Promise { return postJSON("/work/infer", payload); } export async function inferBlueprint( file: File, project_id?: string | null, opts: { signal?: AbortSignal } = {} ): Promise { const form = new FormData(); form.append("file", file); if (project_id) form.append("project_id", project_id); return postForm( "/work/blueprint", form, opts ); } export async function createProject(payload: ProjectPayload): Promise { return postJSON("/projects", payload); } export async function listProjects(): Promise { return getJSON("/projects"); } export async function getProject(project_id: string): Promise { return getJSON(`/projects/${project_id}`); } export async function deleteProject(project_id: string): Promise<{ deleted: boolean; id: string }> { return delJSON<{ deleted: boolean; id: string }>(`/projects/${project_id}`); } export async function listInferenceHistory(project_id?: string): Promise { const q = project_id ? `?project_id=${encodeURIComponent(project_id)}` : ""; return getJSON(`/inference/history${q}`); } /** POST /documents/render — get back a .docx file as Blob. */ export async function renderDocument( template_name: string, project_id: string, context: DocumentContext = {} ): Promise { const res = await fetch(`${API_URL}/documents/render`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ template_name, project_id, context }), }); if (!res.ok) { const detail = await res.text(); throw new Error(`API /documents/render failed: ${res.status} ${detail}`); } return res.blob(); } export async function listDocumentTemplates(): Promise<{ supported: string[] }> { return getJSON<{ supported: string[] }>("/documents/templates"); } // ----- Project documentation (D-light) ----- export interface PageExcerpt { page_no: number; stamp: string | null; title_guess: string | null; char_count: number; text_preview: string; } export interface UploadBlueprintStatus { upload_id: string | null; file_name: string; mime_type: string; page_count: number; has_native_text: boolean; stored_in_supabase: boolean; preview_cached: boolean; pages: PageExcerpt[]; } export interface SheetIndexEntry { page_no: number; stamp: string | null; title_guess: string | null; } export interface ProjectSummaryTotals { sheet_count: number; text_pages: number; sheets_with_stamp: number; } export interface ProjectSummary { object_name: string; object_address: string | null; project_section: string | null; totals: ProjectSummaryTotals; sheets_index: SheetIndexEntry[]; spec_summary: string; missing_info_prompts: string[]; } export interface ProjectSummaryResponse { upload_id: string; model_used: string; page_count: number; generated_at: string; summary: ProjectSummary; } export interface PagePreview { upload_id: string; page_no: number; mime: string; width: number; height: number; data_b64: string; } /** Phase-1 — ingest a blueprint PDF (or image) and persist all pages. */ export async function uploadBlueprint( file: File, project_id?: string | null, opts: { signal?: AbortSignal } = {} ): Promise { const form = new FormData(); form.append("file", file); if (project_id) form.append("project_id", project_id); return postForm( "/uploads/blueprint", form, opts ); } /** Read the ingest status + per-page record list. */ export async function getBlueprintStatus( upload_id: string ): Promise { return getJSON( `/uploads/blueprint/${upload_id}` ); } /** Render page_no of the cached PDF to a PNG (base64). */ export async function getPagePreview( upload_id: string, page_no: number ): Promise { return getJSON( `/uploads/blueprint/${upload_id}/pages/${page_no}/preview` ); } /** Phase-2 text-only — run the LLM over all extracted pages. */ export async function extractProjectText( upload_id: string ): Promise { return postJSON( `/uploads/blueprint/${upload_id}/extract-text`, {} ); } /** Phase-2 vision — VLM only on the selected pages. */ export async function extractProjectSheets( upload_id: string, pages: number[] ): Promise { return postJSON( `/uploads/blueprint/${upload_id}/extract-sheets`, { pages } ); } // ===== v2.0 — Project-aware blueprint aggregation ===== export interface UploadRow { upload_id: string; file_name: string; mime_type: string; sha256: string; page_count: number; has_native_text: boolean; status: string; created_at: string | null; } export interface UploadsListResponse { project_id: string; uploads_count: number; uploads: UploadRow[]; generated_at: string; } export interface AggregatedPage { upload_id: string; upload_file_name: string; page_no: number; char_count: number; stamp: string | null; title_guess: string | null; is_empty: boolean; section_guess: string | null; text_preview: string; } export interface UploadSummary { upload_id: string; file_name: string; mime_type: string; sha256: string; page_count: number; total_chars: number; section_floor: string | null; sections: string[]; } export interface DokumentatsiyaResponse { project_id: string; aggregate_sha256: string; total_uploads: number; total_pages: number; total_chars: number; overall_project_section: string | null; upload_summaries: UploadSummary[]; pages: AggregatedPage[]; cached_summary: ProjectSummary | null; cached_summary_model: string | null; generated_at: string; } export interface ExtractAllResponse { upload_id: string; // field reuse — UI sees project_id here model_used: string; page_count: number; generated_at: string; summary: ProjectSummary; } export interface SheetExtractPair { upload_id: string | null; page_no: number; } /** GET /projects/{id}/uploads. */ export async function listProjectUploads(project_id: string): Promise { return getJSON(`/projects/${project_id}/uploads`); } /** GET /projects/{id}/dokumentatsiya. */ export async function projectDokumentatsiya(project_id: string): Promise { return getJSON(`/projects/${project_id}/dokumentatsiya`); } /** POST /projects/{id}/extract-all. */ export async function extractProjectAll( project_id: string, opts: { force?: boolean; max_chars?: number } = {} ): Promise { const qs = new URLSearchParams(); if (opts.force) qs.set("force", "true"); if (typeof opts.max_chars === "number") qs.set("max_chars", String(opts.max_chars)); const path = `/projects/${project_id}/extract-all${qs.toString() ? `?${qs}` : ""}`; return postJSON(path, {}); } /** POST /projects/{id}/extract-sheets. */ export async function extractProjectSheetsV2( project_id: string, pairs: SheetExtractPair[] ): Promise { return postJSON( `/projects/${project_id}/extract-sheets`, { pages: pairs } ); } /** POST /uploads/blueprint (project-scoped upload). */ export async function uploadBlueprintV2( file: File, project_id: string, opts: { signal?: AbortSignal } = {} ): Promise { const form = new FormData(); form.append("file", file); form.append("project_id", project_id); return postForm("/uploads/blueprint", form, opts); }