diff --git a/src/frontend/src/api.ts b/src/frontend/src/api.ts new file mode 100644 index 0000000..c0ecea3 --- /dev/null +++ b/src/frontend/src/api.ts @@ -0,0 +1,118 @@ +// API 客户端 + +import type { + Task, + Project, + Observation, + Event, + DaemonStatus, +} from './types'; + +const API_BASE = '/api'; + +async function fetchJSON(url: string, options?: RequestInit): Promise { + const resp = await fetch(`${API_BASE}${url}`, { + headers: { 'Content-Type': 'application/json' }, + ...options, + }); + if (!resp.ok) { + throw new Error(`API error: ${resp.status} ${resp.statusText}`); + } + return resp.json(); +} + +// ---- Projects ---- + +export async function listProjects(): Promise { + return fetchJSON('/projects'); +} + +export async function getProject(projectId: string): Promise { + return fetchJSON(`/projects/${projectId}`); +} + +export async function createProject(data: Partial): Promise { + return fetchJSON('/projects', { + method: 'POST', + body: JSON.stringify(data), + }); +} + +// ---- Tasks ---- + +export async function listTasks(projectId: string): Promise { + return fetchJSON(`/projects/${projectId}/tasks`); +} + +export async function getTask(projectId: string, taskId: string): Promise { + return fetchJSON(`/projects/${projectId}/tasks/${taskId}`); +} + +export async function createTask( + projectId: string, + data: Partial +): Promise { + return fetchJSON(`/projects/${projectId}/tasks`, { + method: 'POST', + body: JSON.stringify(data), + }); +} + +export async function updateTask( + projectId: string, + taskId: string, + data: Partial +): Promise { + return fetchJSON(`/projects/${projectId}/tasks/${taskId}`, { + method: 'PATCH', + body: JSON.stringify(data), + }); +} + +// ---- Observations ---- + +export async function listObservations( + projectId: string, + taskId: string +): Promise { + return fetchJSON(`/projects/${projectId}/tasks/${taskId}/observations`); +} + +// ---- Events ---- + +export async function listEvents( + projectId: string, + taskId: string +): Promise { + return fetchJSON(`/projects/${projectId}/tasks/${taskId}/events`); +} + +// ---- Daemon ---- + +export async function getDaemonStatus(): Promise { + return fetchJSON('/daemon/status'); +} + +export async function triggerTick(): Promise<{ tick: number }> { + return fetchJSON('/daemon/tick', { method: 'POST' }); +} + +// ---- SSE ---- + +export function createEventSource( + onEvent: (data: unknown) => void, + onError?: () => void +): EventSource { + const es = new EventSource(`${API_BASE}/events`); + es.onmessage = (e) => { + try { + onEvent(JSON.parse(e.data)); + } catch { + onEvent(e.data); + } + }; + es.onerror = () => { + onError?.(); + }; + return es; +}