import type { ModelInfo } from '../types/model'; import type { ActivationData, AnomalyData, CircuitData, SAEData, SAEInfoResponse, StructuralData, WeightData } from '../types/scan'; import type { PerturbResult, PatchResult } from '../types/perturb'; import type { CausalTraceResult } from '../types/causalTrace'; import type { DiagnosticReport, ReportRequest } from '../types/report'; import type { BatteryResult, TestCase } from '../types/battery'; import type { TokenStatus, CacheStatus, HubSearchResult } from '../types/settings'; const BASE_URL = import.meta.env.VITE_API_BASE_URL || '/api'; class ApiError extends Error { status: number; constructor(status: number, message: string) { super(message); this.name = 'ApiError'; this.status = status; } } async function request(path: string, options?: RequestInit): Promise { const res = await fetch(`${BASE_URL}${path}`, { headers: { 'Content-Type': 'application/json' }, ...options, }); if (!res.ok) { const body = await res.json().catch(() => ({ detail: res.statusText })); throw new ApiError(res.status, body.detail || res.statusText); } return res.json(); } export interface ModelListEntry { model_id: string; display_name: string; family: string; params: string; tl_compat: boolean; gated: boolean; is_loaded: boolean; source?: 'registry' | 'dynamic'; } export const api = { model: { list: () => request('/model/list'), load: (model_id: string, device = 'auto') => request('/model/load', { method: 'POST', body: JSON.stringify({ model_id, device }), }), info: () => request('/model/info'), unload: () => request<{ status: string }>('/model/unload', { method: 'DELETE' }), search: (q: string, limit = 20, tlOnly = false) => request( `/model/search?q=${encodeURIComponent(q)}&limit=${limit}&tl_only=${tlOnly}`, ), }, scan: { structural: (signal?: AbortSignal) => request('/scan/structural', { method: 'POST', signal }), weights: (layers?: string[], signal?: AbortSignal) => request('/scan/weights', { method: 'POST', body: JSON.stringify({ layers: layers ?? null }), signal, }), activation: (prompt: string, signal?: AbortSignal) => request('/scan/activation', { method: 'POST', body: JSON.stringify({ prompt }), signal, }), circuits: (prompt: string, targetTokenIdx = -1, signal?: AbortSignal) => request('/scan/circuits', { method: 'POST', body: JSON.stringify({ prompt, target_token_idx: targetTokenIdx }), signal, }), anomaly: (prompt: string, signal?: AbortSignal) => request('/scan/anomaly', { method: 'POST', body: JSON.stringify({ prompt }), signal, }), }, perturb: { zero: (component: string, prompt: string) => request('/perturb/zero', { method: 'POST', body: JSON.stringify({ component, prompt }), }), amplify: (component: string, prompt: string, factor = 2.0) => request('/perturb/amplify', { method: 'POST', body: JSON.stringify({ component, factor, prompt }), }), ablate: (component: string, prompt: string) => request('/perturb/ablate', { method: 'POST', body: JSON.stringify({ component, prompt }), }), patch: (cleanPrompt: string, corruptPrompt: string, component: string, targetIdx = -1) => request('/perturb/patch', { method: 'POST', body: JSON.stringify({ clean_prompt: cleanPrompt, corrupt_prompt: corruptPrompt, component, target_token_idx: targetIdx, }), }), causalTrace: (cleanPrompt: string, corruptPrompt: string, targetIdx = -1) => request('/perturb/causal-trace', { method: 'POST', body: JSON.stringify({ clean_prompt: cleanPrompt, corrupt_prompt: corruptPrompt, target_token_idx: targetIdx, }), }), reset: () => request<{ status: string }>('/perturb/reset', { method: 'POST' }), }, report: { generate: (req: ReportRequest = {}) => request('/report/generate', { method: 'POST', body: JSON.stringify(req), }), }, battery: { run: (categories?: string[], locale?: string, includeSae?: boolean, saeLayer?: number | null) => request('/battery/run', { method: 'POST', body: JSON.stringify({ categories: categories ?? null, locale: locale ?? 'en', include_sae: includeSae ?? false, sae_layer: saeLayer ?? null, }), }), tests: () => request('/battery/tests'), }, sae: { info: () => request('/sae/info'), support: () => request>('/sae/support'), scan: (prompt: string, layerIdx: number, topK = 20) => request('/sae/scan', { method: 'POST', body: JSON.stringify({ prompt, layer_idx: layerIdx, top_k: topK }), }), }, collab: { create: (displayName = 'Host') => request<{ session_id: string; host_id: string; join_url: string }>( `/collab/create?display_name=${encodeURIComponent(displayName)}`, { method: 'POST' }, ), get: (sessionId: string) => request<{ session_id: string; host_id: string; host_name: string; participant_count: number; }>(`/collab/${sessionId}`), delete: (sessionId: string) => request<{ status: string }>(`/collab/${sessionId}`, { method: 'DELETE' }), list: () => request< Array<{ session_id: string; host_name: string; participant_count: number; created_at: string; }> >('/collab/list'), }, settings: { updateToken: (token: string) => request('/settings/token', { method: 'POST', body: JSON.stringify({ token }), }), clearToken: () => request('/settings/token', { method: 'DELETE' }), tokenStatus: () => request('/settings/token/status'), cacheStatus: () => request('/settings/cache'), clearCache: () => request<{ status: string }>('/settings/cache', { method: 'DELETE' }), }, };