/** * Gateway API Service Client * * Handles all network requests to the FastAPI gateway backend. * Provides endpoints for document CRUD, rendering, metadata retrieval, and edits. */ export interface DocumentInfo { id: string; filename: string; sizeBytes: number; totalPages: number; uploadedAt: string; status: 'processing' | 'ready' | 'error'; } export interface RenderParams { documentId: string; pageIndex: number; zoom: number; rotation: number; } export interface EditOperation { type: 'add_text' | 'delete_text' | 'draw_shape' | 'add_annotation'; pageIndex: number; data: unknown; } class GatewayService { private baseUrl: string; constructor() { // In dev environment, FastAPI gateway runs on port 8000 by default. this.baseUrl = import.meta.env.VITE_GATEWAY_URL || 'http://127.0.0.1:8000'; } async getHealth(): Promise<{ status: string; version: string; engine_available: boolean }> { const response = await fetch(`${this.baseUrl}/health`); if (!response.ok) throw new Error(`Health check failed: ${response.statusText}`); return response.json(); } async listDocuments(): Promise { try { const response = await fetch(`${this.baseUrl}/documents`); if (response.status === 501) { return this.getMockDocuments(); } if (!response.ok) throw new Error(`Failed to list documents: ${response.statusText}`); return response.json(); } catch (err) { // Fallback if backend is not running at all return this.getMockDocuments(); } } async uploadDocument(file: File): Promise { const formData = new FormData(); formData.append('file', file); const response = await fetch(`${this.baseUrl}/documents`, { method: 'POST', body: formData, }); if (response.status === 501) { // Simulate upload for Phase 0 scaffolding return new Promise((resolve) => { setTimeout(() => { resolve({ id: `doc_${Math.random().toString(36).substr(2, 9)}`, filename: file.name, sizeBytes: file.size, totalPages: 5, // Mocked total pages uploadedAt: new Date().toISOString(), status: 'ready', }); }, 1000); }); } if (!response.ok) throw new Error(`Upload failed: ${response.statusText}`); return response.json(); } async getDocument(id: string): Promise { try { const response = await fetch(`${this.baseUrl}/documents/${id}`); if (response.status === 501) { const mock = this.getMockDocuments().find(d => d.id === id); if (!mock) throw new Error('Document not found'); return mock; } if (!response.ok) throw new Error(`Failed to fetch document metadata: ${response.statusText}`); return response.json(); } catch (err) { const mock = this.getMockDocuments().find(d => d.id === id); if (!mock) throw new Error('Document not found'); return mock; } } async deleteDocument(id: string): Promise<{ success: boolean }> { const response = await fetch(`${this.baseUrl}/documents/${id}`, { method: 'DELETE', }); if (response.status === 501) { return { success: true }; } if (!response.ok) throw new Error(`Failed to delete document: ${response.statusText}`); return response.json(); } async renderPage(params: RenderParams): Promise { try { const query = new URLSearchParams({ page: params.pageIndex.toString(), zoom: params.zoom.toString(), rotation: params.rotation.toString(), }).toString(); const url = `${this.baseUrl}/render/${params.documentId}?${query}`; const response = await fetch(url); if (response.status === 501) { return this.generateMockPage(params.pageIndex); } if (!response.ok) throw new Error(`Page render failed: ${response.statusText}`); const blob = await response.blob(); return URL.createObjectURL(blob); } catch (err) { return this.generateMockPage(params.pageIndex); } } async applyEdits(documentId: string, operations: EditOperation[]): Promise<{ success: boolean; newDocumentId: string }> { const response = await fetch(`${this.baseUrl}/edits/${documentId}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ operations }), }); if (response.status === 501) { return { success: true, newDocumentId: `${documentId}_edited` }; } if (!response.ok) throw new Error(`Failed to apply edits: ${response.statusText}`); return response.json(); } private getMockDocuments(): DocumentInfo[] { return [ { id: 'sample-doc-1', filename: 'Quarterly_Financial_Report.pdf', sizeBytes: 1024 * 1024 * 3.4, // 3.4MB totalPages: 12, uploadedAt: new Date(Date.now() - 1000 * 60 * 60 * 3).toISOString(), status: 'ready', }, { id: 'sample-doc-2', filename: 'Engineering_Specification_v4.pdf', sizeBytes: 1024 * 1024 * 18.2, // 18.2MB totalPages: 54, uploadedAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 2).toISOString(), status: 'ready', }, { id: 'sample-doc-3', filename: 'Tenant_Lease_Agreement_Final.pdf', sizeBytes: 1024 * 245, // 245KB totalPages: 4, uploadedAt: new Date(Date.now() - 1000 * 60 * 45).toISOString(), status: 'ready', } ]; } private generateMockPage(pageIndex: number): string { // Generate a premium vector representation of a mock document page const width = 800; const height = 1100; const svg = ` DocQube PDF Engine PAGE ${pageIndex + 1} OF THE SPECIFICATION SECTION ${pageIndex * 2 + 1}.1 High-Performance Rendering Architecture ${pageIndex % 2 === 0 ? ` Engine Performance Vectors (Mock Data) ` : ` #include <pdfengine/core.hpp> int main() { pdf::Engine engine; engine.load_document("quarterly.pdf"); return engine.render_page(1, 2.0); } `} DocQube Core Engine (Gate G0a scaffolding) PAGE ${pageIndex + 1} `; return `data:image/svg+xml;utf8,${encodeURIComponent(svg.trim())}`; } } export const gatewayService = new GatewayService();