Spaces:
Running
Running
| /** 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<T>(path: string, body: unknown): Promise<T> { | |
| 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<T>(path: string): Promise<T> { | |
| 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<T>(path: string): Promise<T> { | |
| 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<T>( | |
| path: string, | |
| form: FormData, | |
| opts: { signal?: AbortSignal } = {} | |
| ): Promise<T> { | |
| 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<InferredWorkResponse> { | |
| return postJSON<InferredWorkResponse>("/work/infer", payload); | |
| } | |
| export async function inferBlueprint( | |
| file: File, | |
| project_id?: string | null, | |
| opts: { signal?: AbortSignal } = {} | |
| ): Promise<BlueprintInferenceResponse> { | |
| const form = new FormData(); | |
| form.append("file", file); | |
| if (project_id) form.append("project_id", project_id); | |
| return postForm<BlueprintInferenceResponse>( | |
| "/work/blueprint", | |
| form, | |
| opts | |
| ); | |
| } | |
| export async function createProject(payload: ProjectPayload): Promise<Project> { | |
| return postJSON<Project>("/projects", payload); | |
| } | |
| export async function listProjects(): Promise<Project[]> { | |
| return getJSON<Project[]>("/projects"); | |
| } | |
| export async function getProject(project_id: string): Promise<Project> { | |
| return getJSON<Project>(`/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<InferenceHistoryRow[]> { | |
| const q = project_id ? `?project_id=${encodeURIComponent(project_id)}` : ""; | |
| return getJSON<InferenceHistoryRow[]>(`/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<Blob> { | |
| 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<UploadBlueprintStatus> { | |
| const form = new FormData(); | |
| form.append("file", file); | |
| if (project_id) form.append("project_id", project_id); | |
| return postForm<UploadBlueprintStatus>( | |
| "/uploads/blueprint", | |
| form, | |
| opts | |
| ); | |
| } | |
| /** Read the ingest status + per-page record list. */ | |
| export async function getBlueprintStatus( | |
| upload_id: string | |
| ): Promise<UploadBlueprintStatus> { | |
| return getJSON<UploadBlueprintStatus>( | |
| `/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<PagePreview> { | |
| return getJSON<PagePreview>( | |
| `/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<ProjectSummaryResponse> { | |
| return postJSON<ProjectSummaryResponse>( | |
| `/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<ProjectSummaryResponse> { | |
| return postJSON<ProjectSummaryResponse>( | |
| `/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<UploadsListResponse> { | |
| return getJSON<UploadsListResponse>(`/projects/${project_id}/uploads`); | |
| } | |
| /** GET /projects/{id}/dokumentatsiya. */ | |
| export async function projectDokumentatsiya(project_id: string): Promise<DokumentatsiyaResponse> { | |
| return getJSON<DokumentatsiyaResponse>(`/projects/${project_id}/dokumentatsiya`); | |
| } | |
| /** POST /projects/{id}/extract-all. */ | |
| export async function extractProjectAll( | |
| project_id: string, | |
| opts: { force?: boolean; max_chars?: number } = {} | |
| ): Promise<ExtractAllResponse> { | |
| 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<ExtractAllResponse>(path, {}); | |
| } | |
| /** POST /projects/{id}/extract-sheets. */ | |
| export async function extractProjectSheetsV2( | |
| project_id: string, | |
| pairs: SheetExtractPair[] | |
| ): Promise<ExtractAllResponse> { | |
| return postJSON<ExtractAllResponse>( | |
| `/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<UploadBlueprintStatus> { | |
| const form = new FormData(); | |
| form.append("file", file); | |
| form.append("project_id", project_id); | |
| return postForm<UploadBlueprintStatus>("/uploads/blueprint", form, opts); | |
| } | |