import { useEffect, useState } from 'react'; import { useAtelierRefresh } from 'officerdev'; import { useAtelierClient } from '../lib/api'; import type { JobResponse, ProviderStatus } from '../lib/api'; export const SIZES = ['auto', '1024x1024', '1536x1024', '1024x1536'] as const; export const QUALITIES = ['auto', 'low', 'medium', 'high'] as const; export const BACKGROUNDS = ['auto', 'transparent', 'opaque'] as const; /** * The form. * * The MODEL is owned above, by the provider/model pair at the top of the panel — it is a choice about * what to generate with, made in the same place the provider is chosen, and duplicating it here gave * two answers to one question. Everything else on the form is this hook's. */ export const useGenerateManager = (providers: ProviderStatus[], model: string) => { const client = useAtelierClient(); const [, bumpRefresh] = useAtelierRefresh(); const [prompt, setPrompt] = useState(''); const [n, setN] = useState(1); const [size, setSize] = useState('auto'); const [quality, setQuality] = useState('auto'); const [background, setBackground] = useState('auto'); const [job, setJob] = useState(null); const [running, setRunning] = useState(false); const [error, setError] = useState(null); // A provider we hold a key for and have code for. Both are required and neither implies the other. const usableProviders = providers.filter((provider) => provider.configured && provider.usable); const ready = Boolean(prompt.trim()) && Boolean(model) && usableProviders.length > 0 && !running; async function generate() { if (!ready) return; setRunning(true); setError(null); setJob(null); try { // Only what the user actually chose. `auto` is the provider's own default, so sending it would // be inventing a parameter they did not set. const body: Record = { prompt: prompt.trim(), model, n }; if (size !== 'auto') body.size = size; if (quality !== 'auto') body.quality = quality; if (background !== 'auto') body.background = background; const { id } = await client.startGeneration(body); // Long-poll in a loop: each call waits up to 30s server-side, so this is one request per half // minute rather than a busy poll, and it survives a generation that takes longer. for (;;) { const result = await client.awaitGeneration(id, 30); if (result.atelier && ['succeeded', 'failed', 'partial'].includes(result.atelier.status)) { setJob(result); bumpRefresh(); return; } setJob(result); } } catch (err) { setError(err instanceof Error ? err.message : String(err)); } finally { setRunning(false); } } return { providers, usableProviders, prompt, setPrompt, n, setN, size, setSize, quality, setQuality, background, setBackground, job, running, error, ready, generate, }; }; export type GenerateManager = ReturnType;