import { getHeaders, useClient } from 'hooks/useClient'; // Every call the panels make to this plugin's own sidecar, through `/api/atelier`. // // `useClient('/api/atelier')` gives typed verbs for JSON; `fileUrl` below is the one thing a client // call cannot do — put a bearer token on an ``. const ATELIER_API = '/api/atelier'; /** A configured connection: adapter + model + key. Several may share one adapter. */ export type ProviderStatus = { id: string; name: string; adapter: string; model: string; enabled: boolean; is_default: boolean; configured: boolean; key_source: 'database' | 'environment' | null; env_var: string; created_at: string; updated_at: string; }; /** The code side — what a new provider can be built on. */ export type Adapter = { name: string; label: string; models: string[]; default_model: string; env_var: string; supports_edit: boolean; }; /** Snake case, matching the server — one spelling per field across every endpoint. */ export type GenerationRow = { id: string; request_id: string; index: number; operation: 'generate' | 'edit'; status: 'queued' | 'running' | 'succeeded' | 'failed'; provider: string; model: string; prompt: string; options: Record | null; input_images: string[] | null; mask_image: string | null; image_path: string | null; url: string | null; width: number | null; height: number | null; has_alpha: boolean | null; opaque: boolean | null; cost_usd: string | null; duration_ms: number | null; error: string | null; created_at: string; }; export type JobResponse = { id?: string; status?: string; created?: number; data?: { url: string; revised_prompt?: string; b64_json?: string }[]; atelier?: { id: string; status: 'queued' | 'running' | 'succeeded' | 'failed' | 'partial'; cost_usd: string | null; duration_ms: number; images: { id: string; status: string; url: string | null; error: string | null }[]; }; error?: { message: string; param?: string | null }; }; export type Collection = { id: string; name: string; description: string | null; created_at: string; updated_at: string; }; export type CollectionSummary = Collection & { item_count: number; cover_image_path: string | null; cover_url: string | null }; export type CollectionDetail = Collection & { images: GenerationRow[] }; /** * `row.url` (e.g. `/files/`) with this session's bearer token appended, for an ``. * * Browsers cannot set an `Authorization` header on an `` request, so the token travels in the * query string instead — the same escape hatch `FileViewer/file-types.ts` and the music plugin's * `MusicPlayerHost` already use for exactly this reason (`permission-gate.ts` and `user-middleware.ts` * both read `?token=` as well as the header). */ export function fileUrl(path: string | null): string | null { if (!path) return null; const token = getHeaders()['Authorization']?.replace('Bearer ', '') ?? ''; return `${ATELIER_API}${path}?token=${encodeURIComponent(token)}`; } export function useAtelierClient() { const client = useClient(ATELIER_API); return { ...client, listProviders: () => client.get<{ data: ProviderStatus[] }>('providers').then((r) => r.data), listAdapters: () => client.get<{ data: Adapter[] }>('providers/adapters').then((r) => r.data), listGenerations: (limit = 60) => client.get<{ data: GenerationRow[] }>(`images?limit=${limit}`).then((r) => r.data), startGeneration: (body: Record) => client.post<{ id: string }>('images/generations', body), /** Long-polls up to `waitSeconds` server-side, so the panel does not run its own retry loop. */ awaitGeneration: (id: string, waitSeconds = 30) => client.get(`images/generations/${id}?wait=${waitSeconds}`), listCollections: () => client.get<{ data: CollectionSummary[] }>('collections').then((r) => r.data), getCollection: (id: string) => client.get<{ data: CollectionDetail }>(`collections/${id}`).then((r) => r.data), createCollection: (name: string) => client.post<{ data: Collection }>('collections', { name }).then((r) => r.data), deleteCollection: (id: string) => client.delete<{ data: unknown }>(`collections/${id}`), addToCollection: (id: string, generationIds: string[]) => client .post<{ data: { added: number; images: GenerationRow[] } }>(`collections/${id}/items`, { generationIds }) .then((r) => r.data), removeFromCollection: (id: string, generationId: string) => client.delete<{ data: { images: GenerationRow[] } }>(`collections/${id}/items/${generationId}`).then((r) => r.data), }; } export type UseAtelierClientType = ReturnType;