const BASE = import.meta.env.DEV ? '/api' : ''
// Global safety net: log unhandled rejections so the UI doesn't silently break.
window.addEventListener('unhandledrejection', (ev) => {
console.error('[API] Unhandled rejection:', ev.reason)
})
async function request(method, path, body) {
const opts = {
method,
headers: {},
credentials: 'include',
}
if (body !== undefined) {
opts.headers['Content-Type'] = 'application/json'
opts.body = JSON.stringify(body)
}
const res = await fetch(`${BASE}${path}`, opts)
// 401 must surface immediately: invalidate local auth so the login overlay appears.
if (res.status === 401) {
try {
const { useAuthStore } = await import('@/stores/auth.js')
const auth = useAuthStore()
auth.user = null
} catch {
// Pinia not ready — ignore, the login overlay won't appear but the error still throws
}
const text = await res.text().catch(() => '')
throw new Error(`${method} ${path} → 401: ${text}`)
}
if (!res.ok) {
const text = await res.text().catch(() => '')
throw new Error(`${method} ${path} → ${res.status}: ${text}`)
}
if (res.status === 204) return null
return res.json()
}
// ─── Auth ─────────────────────────────────────────────────────────────────
export function getMe() {
return request('GET', '/auth/me')
}
export function logout() {
return request('POST', '/auth/logout')
}
// ─── Profiles ──────────────────────────────────────────────────────────────
export function getProfiles() {
return request('GET', '/agents/profiles')
}
// ─── Sessions ──────────────────────────────────────────────────────────────
export function getSessions({ limit = 30, offset = 0, profileId = null, search = null } = {}) {
const params = new URLSearchParams({
limit: String(limit),
offset: String(offset),
})
if (profileId) params.set('profile_id', profileId)
if (search) params.set('search', search)
return request('GET', `/sessions?${params.toString()}`)
}
export function getSession(id) {
return request('GET', `/sessions/${id}`)
}
export function createSession(profileId) {
return request('POST', '/sessions', { profile_id: profileId })
}
export function deleteSession(id) {
return request('DELETE', `/sessions/${id}`)
}
export function pinSession(id, pinned) {
return request('PATCH', `/sessions/${id}/pin`, { pinned })
}
export function stopSession(id) {
return request('POST', `/sessions/${id}/stop`)
}
export function generateSessionName(id) {
return request('POST', `/sessions/${id}/generate-name`)
}
export function getSessionContent(id) {
return request('GET', `/sessions/${id}/content`)
}
// ─── Eval feedback ─────────────────────────────────────────────────────────
export function getFeedback(sessionId) {
return request('GET', `/eval/feedback/${sessionId}`)
}
export function setFeedback(sessionId, messageIndex, rating) {
return request('POST', '/eval/feedback', {
session_id: sessionId,
message_index: messageIndex,
rating
})
}
// ─── Files ─────────────────────────────────────────────────────────────────
export function listSessionFiles(sessionId) {
return request('GET', `/sessions/${sessionId}/files`)
}
export async function uploadFile(sessionId, file) {
const form = new FormData()
form.append('file', file)
const res = await fetch(`${BASE}/sessions/${sessionId}/files`, {
method: 'POST',
credentials: 'include',
body: form
})
if (!res.ok) {
const text = await res.text().catch(() => '')
throw new Error(`Upload failed: ${res.status}: ${text}`)
}
return res.json()
}