AbortController for cancellable
generations, decoding streamed responses chunk by chunk, polling long-running tasks,
paginating history. Without these abstractions, every demo or feature picks up the
same 30 lines of boilerplate.
vibestrap ships five React hooks under @/ai/hooks that wrap all of it. Your
components stay 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(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.
useHistory(endpoint?)
Cursor-paginated ai_call history for the current user.
Signature
AICallHistoryRow includes provider, model, operation, status,
inputTokens (nullable — image / video providers don’t return tokens),
outputTokens (same), 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).
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/