// Centralized API client — routes all requests through Vite proxy (/api → FastAPI 8000) // Falls back to Express 3000 in production via API_BASE_URL or relative path import { validateApiBaseUrl } from "../config"; const BASE = validateApiBaseUrl(); interface RequestOptions { method?: string; body?: any; timeout?: number; headers?: Record; } class ApiError extends Error { status: number; constructor(message: string, status: number) { super(message); this.name = "ApiError"; this.status = status; } } async function request(path: string, options: RequestOptions = {}): Promise { const { method = "GET", body, timeout = 30000, headers } = options; const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), timeout); try { const token = localStorage.getItem("workspace_auth_token"); const fetchOptions: RequestInit = { method, headers: { "Content-Type": "application/json", ...(token ? { "Authorization": `Bearer ${token}` } : {}), ...(headers || {}) }, signal: controller.signal, }; if (body && method !== "GET") { fetchOptions.body = JSON.stringify(body); } const response = await fetch(`${BASE}${path}`, fetchOptions); if (!response.ok) { const errDetail = await response.text().catch(() => ""); throw new ApiError( errDetail || `HTTP ${response.status}: ${response.statusText}`, response.status ); } return (await response.json()) as T; } finally { clearTimeout(timer); } } // ── AI Endpoints ── export interface AIProcessResponse { result: string; provider: string; model?: string; tokensConsumed?: number; } export interface AIChatResponse { result: string; provider: string; tokensConsumed?: number; } export function aiProcess( text: string, action: string, targetLang?: string, customOpenRouterKey?: string, customModel?: string ): Promise { return request("/api/ai/process", { method: "POST", body: { text, action, targetLang, customOpenRouterKey, customModel }, timeout: 60000, }); } export function aiChat( documentContent: string, history: { sender: string; text: string }[], message: string, customOpenRouterKey?: string, customModel?: string ): Promise { return request("/api/ai/chat", { method: "POST", body: { documentContent, history, message, customOpenRouterKey, customModel }, timeout: 60000, }); } // ── Sync Endpoints ── export interface SyncResponse { status: string; timestamp: string; newVersion: number; content: string; logs: string[]; activeCollaborators: number; } export function syncDocument( docId: string, content: string, version: number, teamChangesEnabled: boolean, title?: string ): Promise { return request("/api/sync", { method: "POST", body: { docId, content, version, teamChangesEnabled, title }, }); } export interface ServerDocument { id: string; title: string; content: string; version: number; lastSaved: string; } export function getDocuments(): Promise<{ documents: ServerDocument[] }> { return request("/api/documents"); } export function getDocument(docId: string): Promise { return request(`/api/documents/${docId}`); } export function deleteDocument(docId: string): Promise<{ status: string }> { return request(`/api/documents/${docId}`, { method: "DELETE" }); } // ── Preferences Endpoints ── export interface PreferencesSyncResponse { status: string; timestamp: string; preferences: Record; } export function syncPreferences( preferences: Record ): Promise { return request("/api/preferences/sync", { method: "POST", body: { preferences }, }); } export function getPreferences(): Promise<{ preferences: Record }> { return request("/api/preferences"); } // ── Upload & Convert Endpoints ── export interface ConvertResponse { job_id: string; status: string; } export interface ConvertStatusResponse { job_id: string; filename?: string; status: string; markdown?: string; error?: string; created_at?: string; } export async function uploadAndConvert( file: File ): Promise { const formData = new FormData(); formData.append("file", file); const response = await fetch(`${BASE}/api/convert`, { method: "POST", body: formData, }); if (!response.ok) { const errText = await response.text().catch(() => ""); throw new ApiError(errText || `Upload failed: HTTP ${response.status}`, response.status); } return response.json(); } export function getConvertStatus(jobId: string): Promise { return request(`/api/convert/status/${jobId}`); } // ── Spellcheck Endpoint ── export interface SpellcheckResponse { misspelled: string[]; } export function spellcheck(words: string[]): Promise { return request("/api/spellcheck", { method: "POST", body: { words }, }); } // ── Admin Endpoints ── export interface AdminStatsResponse { total_jobs: number; completed_jobs: number; failed_jobs: number; pending_jobs: number; total_users: number; guest_users: number; registered_users: number; total_documents: number; total_payments: number; preferences_count: number; system_uptime: string; configs: Record; recent_jobs: any[]; } export function adminVerify(password: string): Promise<{ status: string; token: string }> { return request("/api/admin/verify", { method: "POST", body: { password } }); } export function getAdminStats(token: string): Promise { return request("/api/admin/stats", { headers: { "Authorization": `Bearer ${token}` } }); } export function setAdminConfig(configs: Record, token: string): Promise<{ status: string; message: string }> { return request("/api/admin/config", { method: "POST", body: { configs }, headers: { "Authorization": `Bearer ${token}` } }); } export function purgeJobs(token: string): Promise<{ status: string; message: string }> { return request("/api/admin/purge/jobs", { method: "POST", headers: { "Authorization": `Bearer ${token}` } }); } export function purgeGuests(token: string): Promise<{ status: string; message: string }> { return request("/api/admin/purge/guests", { method: "POST", headers: { "Authorization": `Bearer ${token}` } }); } export function getAdminUsers(token: string, page: number = 1): Promise<{ total: number; users: any[] }> { return request(`/api/admin/users?page=${page}`, { headers: { "Authorization": `Bearer ${token}` } }); } export function updateAdminUser(userId: number, data: { tier?: string; display_name?: string }, token: string): Promise<{ status: string }> { return request(`/api/admin/users/${userId}`, { method: "PATCH", body: data, headers: { "Authorization": `Bearer ${token}` } }); } export function deleteAdminUser(userId: number, token: string): Promise<{ status: string }> { return request(`/api/admin/users/${userId}`, { method: "DELETE", headers: { "Authorization": `Bearer ${token}` } }); } export function getAdminDocuments(token: string, page: number = 1): Promise<{ total: number; documents: any[] }> { return request(`/api/admin/documents?page=${page}`, { headers: { "Authorization": `Bearer ${token}` } }); } export function deleteAdminDocument(docId: string, token: string): Promise<{ status: string }> { return request(`/api/admin/documents/${docId}`, { method: "DELETE", headers: { "Authorization": `Bearer ${token}` } }); } export function getAdminPayments(token: string, page: number = 1): Promise<{ total: number; payments: any[] }> { return request(`/api/admin/payments?page=${page}`, { headers: { "Authorization": `Bearer ${token}` } }); } export function getAdminAdmins(token: string): Promise<{ admins: any[] }> { return request("/api/admin/admins", { headers: { "Authorization": `Bearer ${token}` } }); } export function addAdmin(email: string, token: string): Promise<{ status: string; message: string }> { return request("/api/admin/admins/add", { method: "POST", body: { email }, headers: { "Authorization": `Bearer ${token}` } }); } export function removeAdmin(userId: number, token: string): Promise<{ status: string }> { return request(`/api/admin/admins/${userId}`, { method: "DELETE", headers: { "Authorization": `Bearer ${token}` } }); } export function getApiDocs(): Promise { return request("/api/docs"); } // ── Billing & Payment Endpoints ── export interface BillingConfig { active_provider: string; supported_providers: string[]; } export interface CheckoutResponse { status: string; url: string; provider: string; } export interface PaymentStatusResponse { status: string; tier: string; provider?: string; timestamp?: string; } export function getBillingConfig(): Promise { return request("/api/billing/config"); } export function createCheckoutSession( email: string, amount: number, provider?: string, successUrl?: string, cancelUrl?: string ): Promise { return request("/api/billing/create-checkout-session", { method: "POST", body: { email, amount, provider, success_url: successUrl, cancel_url: cancelUrl } }); } export function getPaymentStatus(email: string): Promise { return request(`/api/billing/status/${email}`); } // ── Authentication Endpoints ── export interface AuthResponse { status: string; token: string; user: { id: number; email: string | null; display_name: string; is_guest: boolean; tier: "free" | "pro" | "enterprise"; has_passkey: boolean; oauth_provider: string | null; }; } // Passkey Registration export function passkeyRegisterStart(displayName: string): Promise<{ options: any; challenge_key: string }> { return request("/api/auth/passkey/register/start", { method: "POST", body: { display_name: displayName } }); } export function passkeyRegisterFinish(challengeKey: string, credential: any): Promise { return request("/api/auth/passkey/register/finish", { method: "POST", body: { challenge_key: challengeKey, credential } }); } // Passkey Login export function passkeyLoginStart(): Promise<{ options: any; challenge_key: string }> { return request("/api/auth/passkey/login/start", { method: "POST" }); } export function passkeyLoginFinish(challengeKey: string, credential: any): Promise { return request("/api/auth/passkey/login/finish", { method: "POST", body: { challenge_key: challengeKey, credential } }); } // OAuth export function oauthStart(provider: string, redirectUri: string): Promise<{ url: string; provider: string }> { return request("/api/auth/oauth/start", { method: "POST", body: { provider, redirect_uri: redirectUri } }); } export function oauthCallback(provider: string, code: string, redirectUri: string): Promise { return request("/api/auth/oauth/callback", { method: "POST", body: { provider, code, redirect_uri: redirectUri } }); } // Guest (backward compat) export function authGuest(deviceUuid: string): Promise { return request("/api/auth/guest", { method: "POST", body: { device_uuid: deviceUuid } }); } export function getMe(token: string): Promise { return request("/api/auth/me", { headers: { "Authorization": `Bearer ${token}` } }); } export { ApiError };