AI Text Reference
Use the AI text hooks to build chat interfaces and stream prose. Both hooks handle errors automatically and return undefined instead of throwing when a request fails.
Choosing a Hook
useAIChatbuilds multi-turn conversations. It manages a message array and streams assistant responses into that array.useAITextgenerates one streaming text value for stories, summaries, explanations, and other single-turn prose.- A yes/no, one category from a fixed set, or a score on an ordered rubric belongs in
useAIDecisions. That primitive is far cheaper and faster than text generation, and the probability can drive the interface. - Generated or extracted content those types cannot express belongs in
useAIObject: titles, lists, nested fields, and prose inside a schema. Do not put a boolean, a fixed category, or a rubric score in an object schema.
Import the hooks from the shared AI hook module:
import { useAIText } from '@/hooks/use-ai'
import { usePersistentItem } from '@/hooks/use-persistent-item'
import { Markdown } from '@/components/ui/markdown'
export default function App() {
const [story, setStory] = usePersistentItem('story', '')
const { textStream, setTextStream, generateText, isLoading, error } =
useAIText({ initialText: story })
const handleGenerate = async () => {
const result = await generateText({
prompt: 'Write a short story about a cat',
systemPrompt: 'You are a creative storyteller.',
})
if (result) setStory(result)
}
return (
<div>
<button onClick={handleGenerate} disabled={isLoading}>
Generate story
</button>
<button onClick={() => setTextStream('')}>Clear</button>
{error ? <p className="text-destructive">{error.message}</p> : null}
<Markdown>{textStream}</Markdown>
</div>
)
}
Render streamed AI text directly for responsive UI. Use the Markdown component so headings, lists, links, code blocks, and other Markdown formatting in AI responses display correctly. Use the resolved promise value for persistence or final processing.
useAIChat
useAIChat returns a streaming messages array and a sendMessage function. Pass the user's text content directly to sendMessage; the hook creates the user message and assistant placeholder for you.
Hook Options
initialMessages(Message[], default[]): Initial conversation history.
Returns
messages(Message[]): Conversation history, updated during streaming.setMessages((messages: Message[]) => void): Update messages locally without an API call.sendMessage(function): Add a user message and stream an assistant response.isLoading(boolean): Whether a request is in progress.error(Error | null): Current error.clearError(() => void): Clear the current error.
sendMessage Options
systemPrompt(string): Instruction that guides the AI.enableWebSearch(boolean): Allow the AI to search for current information.context(object): Additional JSON-serializable context.attachments(FileList,File[], or an array of file or URL objects): Images, PDFs, audio, video, or YouTube URLs.model(string): Model ID for the request.reasoningEffort("minimal","low","medium", or"high"): Reasoning depth for the request.
On success, sendMessage resolves to:
assistantMessage(Message): Final assistant response.allMessages(Message[]): Complete conversation including the new messages.
On failure, it resolves to undefined and never rejects.
import * as React from 'react'
import { useAIChat, type Message } from '@/hooks/use-ai'
import { usePersistentItem } from '@/hooks/use-persistent-item'
import { Markdown } from '@/components/ui/markdown'
export default function App() {
const [savedMessages, setSavedMessages] = usePersistentItem<Message[]>(
'messages',
[],
)
const [input, setInput] = React.useState('')
const { messages, sendMessage, isLoading, error } = useAIChat({
initialMessages: savedMessages,
})
const handleSubmit = async (event: React.FormEvent) => {
event.preventDefault()
const content = input.trim()
if (!content) return
setInput('')
const result = await sendMessage(content, {
systemPrompt: 'You are a helpful assistant.',
})
if (result) {
setSavedMessages(result.allMessages)
} else {
setInput(content)
}
}
return (
<div>
{messages.map((message) => (
<div key={message.id}>
<strong>{message.role === 'user' ? 'You' : 'Assistant'}:</strong>
<Markdown>{message.content}</Markdown>
</div>
))}
<form onSubmit={handleSubmit}>
<input
value={input}
onChange={(event) => setInput(event.target.value)}
disabled={isLoading}
/>
<button type="submit" disabled={isLoading}>
Send
</button>
</form>
{error ? <p className="text-destructive">{error.message}</p> : null}
</div>
)
}
useAIText
useAIText returns one streaming text value for single-turn generation.
Hook Options
initialText(string, default""): Initial text value.
Returns
textStream(string): Text updated during streaming.setTextStream((text: string) => void): Update the text locally without an API call.generateText(function): Generate streaming text.isLoading(boolean): Whether a request is in progress.error(Error | null): Current error.clearError(() => void): Clear the current error.
generateText Input
prompt(string, required): Text generation request.systemPrompt(string): Instruction that guides the AI.enableWebSearch(boolean): Allow the AI to search for current information.context(object): Additional JSON-serializable context.attachments(FileList,File[], or an array of file or URL objects): Images, PDFs, audio, video, or YouTube URLs.model(string): Model ID for the request.reasoningEffort("minimal","low","medium", or"high"): Reasoning depth for the request.
generateText resolves to the final generated text on success or undefined on failure. It never rejects.
Attachments and Web Search
useAIChat and useAIText accept attachments in per-request options. Use files directly from local inputs, uploaded file URLs, or YouTube URLs:
const { sendMessage } = useAIChat()
const { generateText } = useAIText()
await generateText({
prompt: 'Summarize this video',
attachments: [{ url: youtubeUrl }],
})
await sendMessage('What are the key points in this PDF?', {
attachments: [pdfFile],
})
Set enableWebSearch: true for prompts that need current information, such as weather, news, or live facts.
Reasoning Effort
Use reasoningEffort to trade latency for reasoning depth:
"minimal": Fastest, with the least reasoning."low": Light reasoning."medium": Balanced reasoning."high": Maximum reasoning depth.
If omitted, the platform uses its default.
Model Selection
When no model is specified, the platform chooses an efficient default model for the user's plan. To expose model choice, pass a model ID and build selectors from listTextModels():
import { listTextModels, useAIChat, useAIText } from '@/hooks/use-ai'
const models = listTextModels()
const [modelId, setModelId] = React.useState<string | undefined>(undefined)
const { generateText } = useAIText()
const { sendMessage } = useAIChat()
await generateText({
prompt: 'Summarize this article',
model: modelId,
})
await sendMessage('Summarize this article', { model: modelId })
listTextModels() returns an array of model objects with:
id: Model ID to pass asmodel.displayName: Human-readable display name.provider: Provider name.tier:"lite","standard", or"advanced".description: Short model description.capabilities: Capability strings such asweb_search,image_input,file_input,audio_input, andvideo_input.
Lite models are available on every plan. Standard models require Basic or Pro. Advanced models require Pro. More capable models cost more credits. Deprecated models automatically fall back to a recommended replacement.
Best Practices
- Render streamed state (
messagesortextStream) directly so the UI updates as tokens arrive. - Render AI-generated text with
Markdown, not a raw<div>, so Markdown formatting displays correctly. - Persist final promise results, not partial streaming state.
- Restore user input on failure if you clear it optimistically.
- Use
setMessagesandsetTextStreamfor local resets without making API calls. - Include clear instructions when using attachments so the model knows how to use each file or URL.