/* Typed client for the kirche-einfach API (served via vite /api proxy). */ export interface FlowDefinition { id: string; name: string; description: string; status: string; source_path: string | null; continuation_flow_id: string | null; created_at: string; } export interface FlowVersion { id: string; flow_id: string; version_no: number; langflow_json: unknown; lfx_flow_id: string | null; deployed_at: string | null; author: string; created_at: string; } export interface RunOutput { result?: string; component?: string; type?: string; error?: string; [key: string]: unknown; } export interface Run { id: string; version_id: string; tenant_id: string; status: string; input: unknown; output: RunOutput | null; session_id: string | null; created_at: string; finished_at: string | null; } export interface ApprovalTask { id: string; run_id: string; version_id: string; tenant_id: string; title: string; payload: unknown; continuation_flow_id: string | null; approver: string | null; status: string; decision_note: string; created_at: string; decided_at: string | null; } export interface Decision { task: ApprovalTask; continuation_run: Run | null; } export interface SyncReport { added: string[]; updated: string[]; deleted: string[]; skipped: string[]; pulled: string[]; errors: Record; } export interface Me { id: string; email: string; name: string; role: string; tenant_id: string; } export interface AccessRequest { id: string; name: string; email: string; reason: string; status: string; created_at: string; decided_at: string | null; decided_by: string | null; } export interface InputSchema { flow_id: string; version_no: number; json_schema: Record; ui_schema: Record; fallback: string | null; } export class ApiError extends Error { status: number; constructor(status: number, message: string) { super(message); this.status = status; } } async function req(path: string, init?: RequestInit): Promise { const res = await fetch(path, { // Session cookie (HttpOnly) must travel with every request. credentials: "include", headers: { "Content-Type": "application/json" }, ...init, }); if (!res.ok) { let detail = res.statusText; try { const body: unknown = await res.json(); if ( typeof body === "object" && body !== null && "detail" in body && typeof (body as { detail: unknown }).detail === "string" ) { detail = (body as { detail: string }).detail; } } catch { /* keep statusText */ } throw new ApiError(res.status, detail); } return (await res.json()) as T; } export const LANGFLOW_URL = (import.meta.env.VITE_LANGFLOW_URL as string | undefined) ?? "http://localhost:7860"; export const api = { listFlows: () => req("/api/flows"), listVersions: (flowId: string) => req(`/api/flows/${flowId}/versions`), deployVersion: (flowId: string, versionNo: number) => req(`/api/flows/${flowId}/versions/${versionNo}/deploy`, { method: "POST", }), runFlow: (flowId: string, input: unknown) => req(`/api/flows/${flowId}/run`, { method: "POST", // Tenant comes from the session server-side; no tenant box anymore. body: JSON.stringify({ input }), }), getInputSchema: (flowId: string) => req(`/api/flows/${flowId}/input-schema`), listRuns: (flowId: string) => req(`/api/flows/${flowId}/runs`), listApprovals: () => req("/api/approvals"), decide: (taskId: string, decision: "approve" | "reject", note: string) => req(`/api/approvals/${taskId}/decision`, { method: "POST", // Identity comes from the session; the server records the user. body: JSON.stringify({ decision, note }), }), importFlow: (langflowFlowId: string, name?: string) => req<{ definition: FlowDefinition; version: FlowVersion }>( "/api/flows/import", { method: "POST", body: JSON.stringify({ langflow_flow_id: langflowFlowId, ...(name ? { name } : {}), }), }, ), listLangflow: () => req<{ id: string; name: string }[]>("/api/flows/langflow/available"), syncFlows: () => req("/api/flows/sync", { method: "POST", }), me: async (): Promise => { try { return await req("/api/auth/me"); } catch (e) { if (e instanceof ApiError && e.status === 401) return null; throw e; } }, consumeMagic: (token: string) => req("/api/auth/magic/consume", { method: "POST", body: JSON.stringify({ token }), }), requestMagicLink: (email: string) => req<{ message: string; email_sent: boolean }>("/api/auth/magic/request", { method: "POST", body: JSON.stringify({ email }), }), logout: () => req<{ message: string }>("/api/auth/logout", { method: "POST" }), requestAccess: (name: string, email: string, reason: string) => req("/api/access/requests", { method: "POST", body: JSON.stringify({ name, email, reason }), }), listAccessRequests: (status?: string) => req( `/api/access/requests${status ? `?status=${status}` : ""}`, ), decideAccess: (id: string, approve: boolean) => req<{ request: AccessRequest; email_sent: boolean }>( `/api/access/requests/${id}/${approve ? "approve" : "reject"}`, { method: "POST" }, ), };