// Typed API client — all calls go through here const BASE = import.meta.env.VITE_API_BASE async function request(method, path, body = null) { // Use dynamic import or grab from window if needed, // but we can also just import the store: const { useAuthStore } = await import('../store/authStore.js') const token = useAuthStore.getState().token const res = await fetch(`${BASE}${path}`, { method, headers: { 'Content-Type': 'application/json', ...(token && { 'Authorization': `Bearer ${token}` }) }, body: body ? JSON.stringify(body) : null }) if (!res.ok) { if (res.status === 401 && !path.includes('/auth/login')) { useAuthStore.getState().clearAuth() window.location.href = '/login' return } const errorData = await res.json().catch(() => ({})) throw new Error(errorData.detail || 'Request failed') } return res.json() } export const api = { // Auth login: (phone, pin) => request('POST', '/auth/login', { phone, pin }), forgotPin: (phone) => request('POST', '/auth/forgot-pin', { phone }), resetPin: (phone, otp, new_pin) => request('POST', '/auth/reset-pin', { phone, otp, new_pin }), uploadDocument: async (file) => { const { useAuthStore } = await import('../store/authStore.js') const token = useAuthStore.getState().token const formData = new FormData() formData.append('file', file) const res = await fetch(`${BASE}/documents/ocr`, { method: 'POST', headers: { ...(token && { 'Authorization': `Bearer ${token}` }) }, body: formData }) if (!res.ok) { const errorData = await res.json().catch(() => ({})) throw new Error(errorData.detail || 'Upload failed') } return res.json() }, register: (name, phone, pin, user_type = 'personal', consent = false) => request('POST', '/auth/register', { name, phone, pin, user_type, consent_data_storage: consent }), // Chat process: async (query, address, flavor, country) => { const response = await request('POST', '/process', { user_query: query, user_address: address, user_name: 'User', flavor: flavor.toUpperCase(), country_code: country }) if (response && response.status === 'queued') { const taskId = response.task_id return new Promise((resolve, reject) => { let wsProto = window.location.protocol === 'https:' ? 'wss:' : 'ws:' let wsHost = window.location.host let wsUrl if (BASE && BASE.startsWith('http')) { const url = new URL(BASE) wsProto = url.protocol === 'https:' ? 'wss:' : 'ws:' wsHost = url.host wsUrl = `${wsProto}//${wsHost}${url.pathname}/ws/tasks/${taskId}` } else { const pathPrefix = BASE || '/api' wsUrl = `${wsProto}//${wsHost}${pathPrefix}/ws/tasks/${taskId}` } let completed = false const socket = new WebSocket(wsUrl) socket.onmessage = (event) => { try { const data = JSON.parse(event.data) if (data.status === 'completed') { completed = true socket.close() resolve(data.result) } else if (data.status === 'failed') { completed = true socket.close() reject(new Error(data.error || 'Deep reasoning task failed')) } } catch (err) { completed = true socket.close() reject(err) } } socket.onerror = (error) => { if (!completed) { completed = true reject(error) } } socket.onclose = () => { if (!completed) { reject(new Error('WebSocket connection closed prematurely')) } } }) } return response }, listChats: () => request('GET', '/chats'), pinChat: (id) => request('PATCH', `/chats/${id}/pin`), // Projects listProjects: () => request('GET', '/projects'), createProject: (data) => request('POST', '/projects', data), // Memory recallMemory: () => request('GET', '/memory/recall'), listFacts: () => request('GET', '/memory/facts'), saveFact: (category, key, value) => request('POST', '/memory/facts', { category, key, value }), deleteFact: (category, key) => request('DELETE', `/memory/facts?category=${encodeURIComponent(category)}&key=${encodeURIComponent(key)}`), // Files listFiles: () => request('GET', '/files'), getFileToken: (filename) => request('GET', `/files/token/${encodeURIComponent(filename)}`), // Goals listGoals: () => request('GET', '/goals'), createGoal: (data) => request('POST', '/goals', data), deleteGoal: (id) => request('DELETE', `/goals/${id}`), // Institutional institutional: { getPortfolioRisk: () => request('GET', '/institutional/v1/risk/portfolio'), getTrades: () => request('GET', '/institutional/v1/trades'), getLogs: () => request('GET', '/institutional/v1/logs'), getApprovals: () => request('GET', '/institutional/v1/approvals'), getManifest: (orgId) => request('GET', `/institutional/v1/manifest?org_id=${orgId}`), getEntityContext: (entityId) => request('GET', `/institutional/v1/context/${entityId}`) } }