import axios, { AxiosError } from 'axios' import type { QueryResponse, Report, SearchFilters, FeedbackPayload, GraphData, StreamEvent, } from '@/types' import { useAuthStore } from '@/stores/auth' const API_BASE = import.meta.env.VITE_API_URL || '/api' const client = axios.create({ baseURL: API_BASE, timeout: 60000, headers: { 'Content-Type': 'application/json', }, withCredentials: true, }) // Inject the JWT on every request client.interceptors.request.use((config) => { const auth = useAuthStore() if (auth.accessToken) { config.headers.Authorization = `Bearer ${auth.accessToken}` } return config }) // Track rate-limit headers + show the auth modal on 429 client.interceptors.response.use( (response) => { const auth = useAuthStore() auth.updateRateFromHeaders(response.headers as unknown as Headers) return response }, (error: AxiosError) => { const auth = useAuthStore() // Feed any rate headers we DID get back into the store if (error.response?.headers) { try { auth.updateRateFromHeaders(error.response.headers as unknown as Headers) } catch { // axios headers are not always Headers — that's fine } } if (error.response?.status === 429) { // Open the signup modal so the user can convert auth.openAuthModal() throw new Error('Daily limit reached. Sign up for 25× more queries.') } if (error.response?.status === 401) { // Token rejected — clear it and force re-auth auth.$reset?.() throw new Error('Your session has expired. Please sign in again.') } throw error } ) export const api = { // Health health: () => client.get('/health'), // RAG query (non-streaming) query: async ( query: string, filters?: SearchFilters, language: string = 'en', mode: string = 'concise' ) => { const response = await client.post('/query', { query, language, mode, filters, }) return response.data }, // Streaming query (SSE) queryStream: ( query: string, onEvent: (event: StreamEvent) => void, filters?: SearchFilters ): EventSource => { const auth = useAuthStore() const url = new URL(`${API_BASE}/agent/stream`, window.location.origin) // EventSource doesn't support custom headers, so we pass the JWT // as a query param fallback (the server may ignore if it prefers headers). if (auth.accessToken) { url.searchParams.set('access_token', auth.accessToken) } const eventSource = new EventSource(url.toString()) eventSource.onmessage = (event) => { try { const data = JSON.parse(event.data) onEvent(data) } catch { onEvent({ event: 'error', data: { message: 'Bad event payload' } }) } } eventSource.onerror = () => { onEvent({ event: 'error', data: { message: 'Stream connection failed' } }) eventSource.close() } return eventSource }, // Documents getDocuments: async (params?: { auditee?: string fy?: string limit?: number offset?: number }) => { const response = await client.get('/documents', { params }) return response.data }, getReport: async (id: string) => { const response = await client.get(`/documents/${id}`) return response.data }, // Graph graphQuery: async (cypher: string) => { const response = await client.post('/graph/query', { cypher, read_only: true, }) return response.data }, getEntityGraph: async (entityId: string) => { const response = await client.get(`/graph/entity/${entityId}`) return response.data }, // Feedback submitFeedback: async (payload: FeedbackPayload) => { const response = await client.post('/feedback', payload) return response.data }, // Suggestions getSuggestions: async (query: string) => { const response = await client.get('/suggestions', { params: { q: query }, }) return response.data }, } export default api