@/ai/hooks that wrap the boring parts —
AbortController, decoding streams, polling loops, cursor pagination — so your client
code stays focused on UI. Every hook is purely client-side; the server contract is a
plain JSON or text endpoint you can swap.
Prerequisites
- Familiarity with the providers doc — these hooks talk to the backend the manager exposes.
- API routes mounted at
/api/ai/chat,/api/ai/history,/api/credits/balance,/api/prompts/<slug>(the defaults — every hook accepts a customendpoint). - The canonical UI examples live in
src/components/demos/{chat-demo,image-demo,document-demo}.tsx.
API reference
useGeneration(options?)
Stream text from a server endpoint that emits raw UTF-8 chunks.
Signature
AbortController per call and decodes the response body itself.
src/components/demos/chat-demo.tsx.
useTask(options)
Long-running task: POST to start, then poll until terminal.
Signature
parse callback decouples the hook from your endpoint shape.
src/components/demos/image-demo.tsx.
useCredits(endpoint?)
Read the signed-in user’s balance.
Signature
/api/credits/balance.
<CreditsBadge /> primitive wraps this hook so most apps never call it directly.
usePrompt(slug)
Look up a registered prompt and render it client-side.
Signature
<PromptVariableForm /> primitive.
render substitutes {{variable}} placeholders. Server-side prompt rendering should
hit the registry directly — see Prompts.
useHistory(endpoint?)
Cursor-paginated ai_call history for the current user.
Signature
AICallHistoryRow includes provider, model, operation, status,
inputTokens, outputTokens, costMicroCents, totalMs, createdAt. Page size
is 20. Cursor is the last row’s createdAt (ISO).
src/components/demos/document-demo.tsx.
Verify they work
- Boot the dev server, sign in.
- Open
/playground/chat. Type. Observetextpainting tokens — that’suseGenerationstreaming. - Open
/playground/image. Submit a prompt. The status pill goesqueued → running → succeeded— that’suseTask. - Open
/dashboard/usage. The list refreshes andLoad moreextends it — that’suseHistory.
Common pitfalls
- Streaming endpoint returns JSON, not text.
useGenerationreads the body as UTF-8 chunks. If your route doesres.json()you’ll get one big aggregated string instead of streaming. Usenew Response(stream)ortext/plaincontent type. - Forgetting to
awaitgenerate(). It returns a Promise resolving to the final text — fire-and-forget is fine, but you can’t show errors withouttry/catch. useTask.parsereturning the wrong status string. The hook only stops polling on'succeeded' | 'failed' | 'cancelled'. Anything else means “keep polling”.useCreditsshowing stale balance. Callrefetch()after any AI call (the manager consumes credits asynchronously, the badge won’t update on its own).usePromptrace on slug change. Multiple slugs in flight from the same component can interleave. Usekey={slug}on the parent if you re-render with a new slug.
Official docs
- React refs / AbortController: react.dev
- Streams API: developer.mozilla.org/en-US/docs/Web/API/Streams_API
- Source:
src/ai/hooks/,src/components/demos/