import { API_DEFAULTS } from "../config/constants"; const BASE_URL = import.meta.env.VITE_ORCHESTRATION_URL ?? ''; // ── Sessions ──────────────────────────────────────────────── export async function fetchSessions(limit = API_DEFAULTS.SESSIONS_LIMIT, offset = API_DEFAULTS.OFFSET) { const res = await fetch(`${BASE_URL}/sessions?limit=${limit}&offset=${offset}`); if (!res.ok) throw new Error(`Failed to fetch sessions: ${res.status}`); return res.json(); } export async function fetchSessionHistory(sessionId, limit = API_DEFAULTS.HISTORY_LIMIT, offset = API_DEFAULTS.OFFSET) { const res = await fetch(`${BASE_URL}/sessions/${sessionId}/history?limit=${limit}&offset=${offset}`); if (!res.ok) throw new Error(`Failed to fetch history: ${res.status}`); return res.json(); } // ── Chat ──────────────────────────────────────────────────── export async function sendMessage(sessionId, message, model) { const res = await fetch(`${BASE_URL}/chat`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionId, message, model }), }); if (!res.ok) throw new Error(`Chat request failed: ${res.status}`); return res.json(); } // onChunk(text) called for each token // onDone({ model, tokenCount }) called when stream closes // returns an abort function — call it to cancel mid-stream /* export function streamMessage(sessionId, message, model, { onChunk, onDone, onError }) { const controller = new AbortController(); (async () => { try { const res = await fetch(`${BASE_URL}/chat/stream`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionId, message, model }), signal: controller.signal, }); if (!res.ok) throw new Error(`Stream request failed: ${res.status}`); const reader = res.body.getReader(); const decoder = new TextDecoder(); let buffer = ''; while (true) { const { done, value } = await reader.read(); if (done) break; // Append to buffer and split on double newline (SSE event delimiter) buffer += decoder.decode(value, { stream: true }); const events = buffer.split('\n\n'); buffer = events.pop(); // last item may be incomplete — keep in buffer for (const event of events) { const line = event.trim(); if (!line.startsWith('data: ')) continue; const raw = line.slice(6); try { const data = JSON.parse(raw); if (data.text) onChunk(data.text); if (data.done) onDone({ model: data.model ?? model, tokenCount: data.tokenCount ?? 0 }); if (data.error) onError(new Error(data.error)); } catch { // malformed JSON — skip } } } } catch (err) { if (err.name !== 'AbortError') onError(err); } })(); return () => controller.abort(); } */ export function streamMessage(sessionId, message, model, { onChunk, onDone, onError }) { const controller = new AbortController(); (async () => { try { const res = await fetch(`${BASE_URL}/chat/stream`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionId, message, model }), signal: controller.signal, }); if (!res.ok) throw new Error(`Stream request failed: ${res.status}`); const reader = res.body.getReader(); const decoder = new TextDecoder(); let buffer = ''; while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const events = buffer.split('\n\n'); buffer = events.pop() || ''; for (const event of events) { const lines = event.split('\n'); const dataLines = lines .filter(line => line.startsWith('data: ')) .map(line => line.slice(6)); if (dataLines.length === 0) continue; const raw = dataLines.join('\n').trim(); if (raw === '[DONE]') continue; try { const data = JSON.parse(raw); if (data.text) onChunk(data.text); if (data.done) onDone({ model: data.model ?? model, tokenCount: data.tokenCount ?? 0 }); if (data.error) onError(new Error(data.error)); } catch (err) { console.error('[chat-client] Failed to parse SSE event:', raw, err); } } } } catch (err) { if (err.name !== 'AbortError') onError(err); } })(); return () => controller.abort(); } export async function fetchModels() { const res = await fetch(`${BASE_URL}/models`); if(!res.ok) throw new Error(`Failted to fetch models: ${res.status}`); return res.json(); } /* export async function renameSession(sessionId, name) { const res = await fetch(`${BASE_URL}/sessions/${sessionId}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name }), }); if (!res.ok) throw new Error(`Failed to rename session: ${res.status}`); return res.json(); } */ export async function updateSession(sessionId, { name, projectId } = {}) { const res = await fetch(`${BASE_URL}/sessions/${sessionId}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name, projectId }), }); if (!res.ok) throw new Error(`Failed to update session: ${res.status}`); return res.json(); } export async function renameSession(sessionId, name) { return updateSession(sessionId, {name}) } export async function deleteSession(sessionId) { const res = await fetch(`${BASE_URL}/sessions/${sessionId}`, { method: 'DELETE', }); if (!res.ok) throw new Error(`Failed to delete session: ${res.status}`); } export async function fetchProjects() { const res = await fetch(`${BASE_URL}/projects`); if (!res.ok) throw new Error(`Failed to fetch projects: ${res.status}`); return res.json(); } export async function createProject({ name, description, colour, icon }) { const res = await fetch(`${BASE_URL}/projects`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name, description, colour, icon }), }); if (!res.ok) throw new Error(`Failed to create project: ${res.status}`); return res.json(); } export async function updateProject(id, { name, description, colour, icon }) { const res = await fetch(`${BASE_URL}/projects/${id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name, description, colour, icon }), }); if (!res.ok) throw new Error(`Failed to update project: ${res.status}`); return res.json(); } export async function deleteProject(id) { const res = await fetch(`${BASE_URL}/projects/${id}`, { method: 'DELETE' }); if (!res.ok) throw new Error(`Failed to delete project: ${res.status}`); } export async function updateSessionProject(sessionId, projectId) { const res = await fetch(`${BASE_URL}/sessions/${sessionId}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ projectId }), }); if (!res.ok) throw new Error(`Failed to update session project: ${res.status}`); return res.json(); }