senti-beta / frontend /src /lib /api.js
joseph njoroge kariuki
feat: enforce zero-mock multi-user privacy, fix chat overlap, integrate ocr file upload, and launch telegram bot
5338d4a
// 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 }),
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}`)
}
}