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; export const useGenerateManager = () => { const client = useAtelierClient(); const [, bumpRefresh] = useAtelierRefresh(); const [providers, setProviders] = useState([]); const [model, setModel] = useState(''); 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); useEffect(() => { client .listProviders() .then((rows) => { setProviders(rows); const usable = rows.find((row) => row.is_default && row.configured && row.enabled) ?? rows.find((row) => row.configured && row.enabled); if (usable) setModel(usable.model); }) .catch((err: Error) => setError(err.message)); // Loaded once on mount — the Providers panel is where they // are managed, and this panel does not need to react to edits made there in real time. // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // Derived in the hook, not in the component. const usableProviders = providers.filter((provider) => provider.configured && provider.enabled); // One model per configured provider, deduped: two providers on the same model is legitimate (two // keys, two accounts) and the request picks between them by model alone. const models = [...new Set(usableProviders.map((provider) => provider.model))]; const ready = Boolean(prompt.trim()) && Boolean(model) && !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, models, model, setModel, prompt, setPrompt, n, setN, size, setSize, quality, setQuality, background, setBackground, job, running, error, ready, generate, }; }; export type GenerateManager = ReturnType;