Skip to main content
AI client code is full of repeating plumbing — 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 custom endpoint).
  • 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
Use it for chat-like flows where the model emits tokens incrementally. The hook manages an AbortController per call and decodes the response body itself.
Canonical implementation: src/components/demos/chat-demo.tsx.

useTask(options)

Long-running task: POST to start, then poll until terminal. Signature
Use it for image generation, video, document processing — anything that doesn’t fit in one HTTP request. The parse callback decouples the hook from your endpoint shape.
Canonical implementation: src/components/demos/image-demo.tsx.

useCredits(endpoint?)

Read the signed-in user’s balance. Signature
Use it for the header credit badge, balance check before showing CTAs, refresh after checkout. The default endpoint is /api/credits/balance.
The <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).
Canonical implementation: src/components/demos/document-demo.tsx.

Verify they work

  1. Boot the dev server, sign in.
  2. Open /playground/chat. Type. Observe text painting tokens — that’s useGeneration streaming.
  3. Open /playground/image. Submit a prompt. The status pill goes queued → running → succeeded — that’s useTask.
  4. Open /dashboard/usage. The list refreshes and Load more extends it — that’s useHistory.

Common pitfalls

  • Streaming endpoint returns JSON, not text. useGeneration reads the body as UTF-8 chunks. If your route does res.json() you’ll get one big aggregated string instead of streaming. Use new Response(stream) or text/plain content type.
  • Forgetting to await generate(). It returns a Promise resolving to the final text — fire-and-forget is fine, but you can’t show errors without try/catch.
  • useTask.parse returning the wrong status string. The hook only stops polling on 'succeeded' | 'failed' | 'cancelled'. Anything else means “keep polling”.
  • useCredits showing stale balance. Call refetch() after any AI call (the manager consumes credits asynchronously, the badge won’t update on its own).

Official docs