| 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'] |
|
|
| |
| api.interceptors.request.use((config) => { |
| const token = localStorage.getItem('access_token') |
| if (token) config.headers.Authorization = `Bearer ${token}` |
| return config |
| }) |
|
|
| |
| 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 |
| } |
|
|
| |
| 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), |
| } |
|
|
| |
| 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}`), |
| } |
|
|
| |
| 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), |
| } |
|
|
| |
| 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}`), |
| } |
|
|
| |
| 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), |
| } |
|
|
| |
| export const healthApi = { |
| check: () => api.get('/health').then(r => r.data), |
| } |
|
|
| export default api |
|
|