import axios from 'axios' const apiBaseURL = import.meta.env.VITE_API_BASE_URL || '' const api = axios.create({ baseURL: `${apiBaseURL}/api/v1` }) const AUTH_PATHS = ['/auth/login', '/auth/register', '/auth/refresh'] // Attach access token to every request api.interceptors.request.use((config) => { const token = localStorage.getItem('access_token') if (token) config.headers.Authorization = `Bearer ${token}` return config }) // Auto-refresh on 401 api.interceptors.response.use( (res) => res, async (err) => { const original = err.config || {} const requestUrl = original.url || '' const isAuthRequest = AUTH_PATHS.some((path) => requestUrl.includes(path)) if (err.response?.status === 401 && !original._retry && !isAuthRequest) { original._retry = true try { const refresh = localStorage.getItem('refresh_token') if (!refresh) throw new Error('Missing refresh token') const { data } = await axios.post(`${apiBaseURL}/api/v1/auth/refresh`, { refresh_token: refresh }) localStorage.setItem('access_token', data.access_token) localStorage.setItem('refresh_token', data.refresh_token) original.headers.Authorization = `Bearer ${data.access_token}` return api(original) } catch { localStorage.clear() window.location.href = '/login' } } return Promise.reject(err) } ) export function getApiErrorMessage(err, fallback = 'Something went wrong') { const detail = err?.response?.data?.detail if (typeof detail === 'string' && detail.trim()) return detail if (Array.isArray(detail) && detail.length) { return detail.map((item) => item?.msg || item).filter(Boolean).join(', ') } return fallback } // ─── Auth ────────────────────────────────────────────────────────────────────── export const authApi = { register: (d) => api.post('/auth/register', d).then(r => r.data), login: (d) => api.post('/auth/login', d).then(r => r.data), me: () => api.get('/auth/me').then(r => r.data), } // ─── Conversations ───────────────────────────────────────────────────────────── export const convApi = { list: () => api.get('/conversations').then(r => r.data), create: (title = 'New Chat') => api.post('/conversations', { title }).then(r => r.data), messages: (id) => api.get(`/conversations/${id}/messages`).then(r => r.data), rename: (id, title) => api.patch(`/conversations/${id}`, { title }).then(r => r.data), delete: (id) => api.delete(`/conversations/${id}`), } // ─── Query ───────────────────────────────────────────────────────────────────── export const queryApi = { ask: (payload) => api.post('/query', payload).then(r => r.data), } export const audioApi = { transcribe: (formData) => api.post('/audio/transcribe', formData, { headers: { 'Content-Type': 'multipart/form-data' } }).then(r => r.data), speak: (payload) => api.post('/audio/speak', payload, { responseType: 'blob', }).then(r => r.data), } // ─── Documents ───────────────────────────────────────────────────────────────── export const docsApi = { list: (conversationId) => api.get('/documents', { params: conversationId ? { conversation_id: conversationId } : undefined, }).then(r => r.data), upload: (formData) => api.post('/documents/upload', formData, { headers: { 'Content-Type': 'multipart/form-data' } }).then(r => r.data), clearConversation: (conversationId) => api.delete(`/documents/conversation/${conversationId}`), delete: (id) => api.delete(`/documents/${id}`), } // ─── Admin ───────────────────────────────────────────────────────────────────── export const adminApi = { stats: () => api.get('/admin/stats').then(r => r.data), users: () => api.get('/admin/users').then(r => r.data), setRole: (id, role) => api.patch(`/admin/users/${id}/role`, null, { params: { role } }).then(r => r.data), toggle: (id) => api.patch(`/admin/users/${id}/toggle`).then(r => r.data), } // ─── Health ──────────────────────────────────────────────────────────────────── export const healthApi = { check: () => api.get('/health').then(r => r.data), } export default api