AI Text Reference
Use the AI text hooks to build chat interfaces, stream prose, and generate structured JSON data. All 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.useAIObjectgenerates structured data that matches a JSON schema. It does not stream partial objects.
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>}
<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.
const { messages, setMessages, sendMessage, isLoading, error, clearError } =
useAIChat(options);
Options:
initialMessages(Message[], default[]): Initial conversation history.systemPrompt(string, default""): Default instruction for all chat requests.enableWebSearch(boolean, defaultfalse): Default web-search setting.onError((error: Error) => void): Custom error handler.onFinish((assistantMessage: Message, allMessages: Message[]) => void): Called when streaming completes.
sendMessage(content, options) accepts a string plus optional systemPrompt, enableWebSearch, context, attachments, and model. It returns Promise<{ assistantMessage: Message; allMessages: Message[] } | undefined>.
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>}
</div>
);
}
useAIText
useAIText returns one streaming text value for single-turn generation.
const { textStream, setTextStream, generateText, isLoading, error, clearError } =
useAIText(options);
Options:
initialText(string, default""): Initial text value.systemPrompt(string, default""): Default instruction for all requests.enableWebSearch(boolean, defaultfalse): Default web-search setting.onError((error: Error) => void): Custom error handler.onFinish((text: string) => void): Called with the final generated text.
generateText(options) requires prompt, accepts systemPrompt, enableWebSearch, context, attachments, and model, and returns Promise<string | undefined>.
useAIObject
useAIObject returns structured JSON data matching a schema.
const { generateObject, isLoading, error, clearError } = useAIObject(options);
Options:
systemPrompt(string, default""): Default instruction for all requests.enableWebSearch(boolean, defaultfalse): Default web-search setting.onError((error: Error) => void): Custom error handler.onFinish((object: T) => void): Called with the generated object.
generateObject<T>(options) requires:
prompt(string): Description of the object to generate.schema(object): JSON Schema for the output.
It also accepts systemPrompt, enableWebSearch, context, attachments, and model, and returns Promise<T | undefined>.
Schemas must follow the structured-output subset:
- The root type must be
"object". - Every object must set
"additionalProperties": false. - Every property must be listed in
required; optional fields are not supported. - Supported types are
"string","number","integer","boolean","object", and"array". enumvalues must be strings.
import { useAIObject } from "@/hooks/use-ai";
import { usePersistentItem } from "@/hooks/use-persistent-item";
type Plan = {
title: string;
tasks: string[];
priority: "low" | "medium" | "high";
};
export default function App() {
const [plan, setPlan] = usePersistentItem<Plan | null>("plan", null);
const { generateObject, isLoading, error } = useAIObject();
const handleGeneratePlan = async (goal: string) => {
const result = await generateObject<Plan>({
prompt: `Generate a plan for: ${goal}`,
schema: {
type: "object",
properties: {
title: { type: "string" },
tasks: { type: "array", items: { type: "string" } },
priority: { type: "string", enum: ["low", "medium", "high"] },
},
required: ["title", "tasks", "priority"],
additionalProperties: false,
},
});
if (result) setPlan(result);
};
return (
<div>
{isLoading && <p>Generating...</p>}
{error && <p className="text-destructive">{error.message}</p>}
{plan && <pre>{JSON.stringify(plan, null, 2)}</pre>}
</div>
);
}
Attachments and Web Search
All three text hooks accept attachments in per-request options. Use files directly from local inputs, uploaded file URLs, or YouTube URLs:
const { sendMessage } = useAIChat();
await generateText({
prompt: "Summarize this video",
attachments: [{ url: youtubeUrl }],
});
await sendMessage("What are the key points in this PDF?", {
attachments: [pdfFile],
});
await generateObject({
prompt: "Extract the receipt details",
schema: receiptSchema,
attachments: [receiptImageFile],
});
Set enableWebSearch: true for prompts that need current information, such as weather, news, or live facts.
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 ModelInfo[] 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.
All models are available on every plan; 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.
- Keep object schemas small and explicit, and include
additionalProperties: falseon every object.