import type { BenchmarkQuestion, DeleteModelResultsResponse, DatasetOption, DatasetTemplateRow, OllamaAuthStatus, ResultsTableKey, ResultsResponse, RunStartResponse, RunStatusResponse, SloStatus } from "./types"; const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL ?? "/api"; const OLLAMA_API_KEY_HEADER = "X-Ollama-API-Key"; export class ApiError extends Error { status: number; payload: Record; constructor(status: number, message: string, payload: Record = {}) { super(message); this.name = "ApiError"; this.status = status; this.payload = payload; } } async function parseResponse(res: Response): Promise { if (!res.ok) { let detail = `Request failed (${res.status})`; let payload: Record = {}; try { payload = (await res.json()) as Record; const rawDetail = payload.detail; if (typeof rawDetail === "string" && rawDetail.trim()) { detail = rawDetail; } } catch { // keep default detail } throw new ApiError(res.status, detail, payload); } return (await res.json()) as T; } export function apiBaseUrl(): string { return API_BASE_URL; } export function runEventsUrl(runId: number, sessionId: string): string { return `${API_BASE_URL}/runs/${runId}/events?session_id=${encodeURIComponent(sessionId)}`; } export function datasetTemplateUrl(): string { return `${API_BASE_URL}/datasets/template`; } function buildHeaders(contentType?: string, ollamaApiKey?: string): HeadersInit { const headers: Record = {}; if (contentType) { headers["Content-Type"] = contentType; } if (typeof ollamaApiKey === "string" && ollamaApiKey.trim()) { headers[OLLAMA_API_KEY_HEADER] = ollamaApiKey.trim(); } return headers; } export async function getHealth(): Promise<{ status: string; version: string }> { return parseResponse(await fetch(`${API_BASE_URL}/health`, { cache: "no-store" })); } export async function getOllamaAuthStatus(): Promise { return parseResponse(await fetch(`${API_BASE_URL}/ollama/auth-status`, { cache: "no-store" })); } export async function getModels(ollamaApiKey?: string): Promise { const payload = await parseResponse<{ models: string[] }>( await fetch(`${API_BASE_URL}/models`, { cache: "no-store", headers: buildHeaders(undefined, ollamaApiKey) }) ); return payload.models; } export async function getDatasets(): Promise { const payload = await parseResponse<{ datasets: DatasetOption[] }>( await fetch(`${API_BASE_URL}/datasets`, { cache: "no-store" }) ); return payload.datasets; } export async function getQuestions(datasetKey: string): Promise<{ instruction: string; questions: BenchmarkQuestion[] }> { const payload = await parseResponse<{ dataset_key: string; instruction: string; questions: BenchmarkQuestion[]; }>(await fetch(`${API_BASE_URL}/questions?dataset_key=${encodeURIComponent(datasetKey)}`, { cache: "no-store" })); return { instruction: payload.instruction, questions: payload.questions }; } export async function getResults(datasetKey: string): Promise { return parseResponse( await fetch(`${API_BASE_URL}/results?dataset_key=${encodeURIComponent(datasetKey)}`, { cache: "no-store" }) ); } export async function startRun(payload: { session_id: string; dataset_key: string; question_id: string; models: string[]; system_prompt?: string; }, ollamaApiKey?: string): Promise { return parseResponse( await fetch(`${API_BASE_URL}/runs`, { method: "POST", headers: buildHeaders("application/json", ollamaApiKey), body: JSON.stringify(payload) }) ); } export async function stopRun(runId: number, sessionId: string): Promise<{ status: string }> { return parseResponse( await fetch(`${API_BASE_URL}/runs/${runId}/stop?session_id=${encodeURIComponent(sessionId)}`, { method: "POST" }) ); } export async function getRunStatus(runId: number, sessionId: string): Promise { return parseResponse( await fetch(`${API_BASE_URL}/runs/${runId}/status?session_id=${encodeURIComponent(sessionId)}`, { cache: "no-store" }) ); } export async function applyManualDecision(payload: { dataset_key: string; question_id: string; model: string; status: "success" | "fail" | "manual_review"; reason?: string; }): Promise<{ status: string; result: Record }> { return parseResponse( await fetch(`${API_BASE_URL}/results/manual`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) }) ); } export async function uploadDataset(file: File): Promise<{ dataset: DatasetOption }> { const form = new FormData(); form.append("file", file); return parseResponse( await fetch(`${API_BASE_URL}/datasets/upload`, { method: "POST", body: form }) ); } export async function deleteDataset(datasetKey: string): Promise<{ status: string; summary: Record }> { return parseResponse( await fetch(`${API_BASE_URL}/datasets/${encodeURIComponent(datasetKey)}`, { method: "DELETE" }) ); } export async function deleteModelResults(datasetKey: string, model: string): Promise { return parseResponse( await fetch( `${API_BASE_URL}/results/model?dataset_key=${encodeURIComponent(datasetKey)}&model=${encodeURIComponent(model)}`, { method: "DELETE" } ) ); } export async function getDatasetTemplate(): Promise { return parseResponse(await fetch(`${API_BASE_URL}/datasets/template`, { cache: "no-store" })); } export function exportLink(datasetKey: string, format: "json" | "xlsx"): string { return `${API_BASE_URL}/results/export?dataset_key=${encodeURIComponent(datasetKey)}&format=${format}`; } export function tableExportLink(datasetKey: string, table: ResultsTableKey, format: "json" | "xlsx"): string { return `${API_BASE_URL}/results/table_export?dataset_key=${encodeURIComponent(datasetKey)}&table=${encodeURIComponent(table)}&format=${format}`; } export async function getSloStatus(): Promise { return parseResponse(await fetch(`${API_BASE_URL}/ops/slo`, { cache: "no-store" })); } export async function resetSloStatus(): Promise<{ status: string; slo: SloStatus }> { return parseResponse( await fetch(`${API_BASE_URL}/ops/slo/reset`, { method: "POST" }) ); }