commit caa3746d361a85880ce223a304c1bdfe2c3891df Author: Zhongwei Li Date: Sun Nov 30 08:23:53 2025 +0800 Initial commit diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..734d765 --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,12 @@ +{ + "name": "ai-sdk-ui", + "description": "Build React chat interfaces and AI-powered UIs with Vercel AI SDK v5. Provides useChat, useCompletion, and useObject hooks for streaming responses, managing conversation state, and handling file attachments. Use when: implementing chat interfaces, streaming AI completions, managing message state in Next.js apps, or troubleshooting useChat failed to parse stream or useChat no response errors.", + "version": "1.0.0", + "author": { + "name": "Jeremy Dawes", + "email": "jeremy@jezweb.net" + }, + "skills": [ + "./" + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..a39ef2c --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# ai-sdk-ui + +Build React chat interfaces and AI-powered UIs with Vercel AI SDK v5. Provides useChat, useCompletion, and useObject hooks for streaming responses, managing conversation state, and handling file attachments. Use when: implementing chat interfaces, streaming AI completions, managing message state in Next.js apps, or troubleshooting useChat failed to parse stream or useChat no response errors. diff --git a/SKILL.md b/SKILL.md new file mode 100644 index 0000000..2449574 --- /dev/null +++ b/SKILL.md @@ -0,0 +1,510 @@ +--- +name: ai-sdk-ui +description: | + Build React chat interfaces with Vercel AI SDK v5/v6. Covers v6 beta (agent integration, tool approval, + auto-submit), v4→v5 migration (breaking changes), useChat/useCompletion/useObject/useAssistant hooks, + and 12 UI error solutions (stream parsing, stale body values, React update depth). + + Use when: implementing AI SDK v5/v6 chat UIs, migrating v4→v5, troubleshooting "useChat failed to parse + stream", "useChat no response", or "stale body values" errors, or integrating OpenAI assistants. +license: MIT +metadata: + version: 1.1.0 + last_verified: 2025-11-22 + ai_sdk_version: 5.0.99 stable / 6.0.0-beta.108 + breaking_changes: true (v4→v5 migration guide included) + production_tested: true + keywords: + - ai sdk ui + - ai sdk v6 beta + - ai sdk 6 + - vercel ai sdk ui + - useChat hook + - useCompletion hook + - useObject hook + - useAssistant hook + - react ai chat + - ai chat interface + - streaming ai ui + - nextjs ai chat + - react streaming + - ai sdk react + - agent integration + - tool approval workflow + - human in the loop ui + - chat message state + - ai file attachments + - message persistence + - useChat error + - streaming failed ui + - parse stream error + - useChat no response + - stale body values + - react maximum update depth + - react ai hooks + - nextjs app router ai + - nextjs pages router ai + - ai chat component + - streaming response react + - react ai completion + - openai assistant ui +--- + +# AI SDK UI - Frontend React Hooks + +Frontend React hooks for AI-powered user interfaces with Vercel AI SDK v5/v6. + +**Version**: AI SDK v5.0.99 (Stable) / v6.0.0-beta.108 (Beta) +**Framework**: React 18+, Next.js 14+ +**Last Updated**: 2025-11-22 + +--- + +## AI SDK 6 Beta (November 2025) + +**Status:** Beta (stable release planned end of 2025) +**Latest:** ai@6.0.0-beta.108 (Nov 22, 2025) +**Migration:** Minimal breaking changes from v5 → v6 + +### New UI Features in v6 Beta + +**1. Agent Integration** +Type-safe messaging with agents using `InferAgentUIMessage`: + +```tsx +import { useChat } from '@ai-sdk/react'; +import type { InferAgentUIMessage } from 'ai'; +import { myAgent } from './agent'; + +export default function AgentChat() { + const { messages, sendMessage } = useChat>({ + api: '/api/chat', + }); + // messages are now type-checked against agent schema +} +``` + +**2. Tool Approval Workflows (Human-in-the-Loop)** +Request user confirmation before executing tools: + +```tsx +import { useChat } from '@ai-sdk/react'; +import { useState } from 'react'; + +export default function ChatWithApproval() { + const { messages, sendMessage, addToolApprovalResponse } = useChat({ + api: '/api/chat', + }); + + const handleApprove = (toolCallId: string) => { + addToolApprovalResponse({ + toolCallId, + approved: true, // or false to deny + }); + }; + + return ( +
+ {messages.map(message => ( +
+ {message.toolInvocations?.map(tool => ( + tool.state === 'awaiting-approval' && ( +
+

Approve tool call: {tool.toolName}?

+ + +
+ ) + ))} +
+ ))} +
+ ); +} +``` + +**3. Auto-Submit Capability** +Automatically continue conversation after handling approvals: + +```tsx +import { useChat, lastAssistantMessageIsCompleteWithApprovalResponses } from '@ai-sdk/react'; + +export default function AutoSubmitChat() { + const { messages, sendMessage } = useChat({ + api: '/api/chat', + sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses, + // Automatically resubmit after all approval responses provided + }); +} +``` + +**4. Structured Output in Chat** +Generate structured data alongside tool calling (previously only available in `useObject`): + +```tsx +import { useChat } from '@ai-sdk/react'; +import { z } from 'zod'; + +const schema = z.object({ + summary: z.string(), + sentiment: z.enum(['positive', 'neutral', 'negative']), +}); + +export default function StructuredChat() { + const { messages, sendMessage } = useChat({ + api: '/api/chat', + // Server can now stream structured output with chat messages + }); +} +``` + +--- + +## useChat Hook - v4 → v5 Breaking Changes + +**CRITICAL: useChat no longer manages input state in v5!** + +**v4 (OLD - DON'T USE):** +```tsx +const { messages, input, handleInputChange, handleSubmit, append } = useChat(); + +
+ +
+``` + +**v5 (NEW - CORRECT):** +```tsx +const { messages, sendMessage } = useChat(); +const [input, setInput] = useState(''); + +
{ + e.preventDefault(); + sendMessage({ content: input }); + setInput(''); +}}> + setInput(e.target.value)} /> +
+``` + +**Summary of v5 Changes:** +1. **Input management removed**: `input`, `handleInputChange`, `handleSubmit` no longer exist +2. **`append()` → `sendMessage()`**: New method for sending messages +3. **`onResponse` removed**: Use `onFinish` instead +4. **`initialMessages` → controlled mode**: Use `messages` prop for full control +5. **`maxSteps` removed**: Handle on server-side only + +See `references/use-chat-migration.md` for complete migration guide. + +--- + +## useAssistant Hook + +Interact with OpenAI-compatible assistant APIs with automatic UI state management. + +**Import:** +```tsx +import { useAssistant } from '@ai-sdk/react'; +``` + +**Basic Usage:** +```tsx +'use client'; +import { useAssistant } from '@ai-sdk/react'; +import { useState, FormEvent } from 'react'; + +export default function AssistantChat() { + const { messages, sendMessage, isLoading, error } = useAssistant({ + api: '/api/assistant', + }); + const [input, setInput] = useState(''); + + const handleSubmit = (e: FormEvent) => { + e.preventDefault(); + sendMessage({ content: input }); + setInput(''); + }; + + return ( +
+ {messages.map(m => ( +
+ {m.role}: {m.content} +
+ ))} +
+ setInput(e.target.value)} + disabled={isLoading} + /> +
+ {error &&
{error.message}
} +
+ ); +} +``` + +**Use Cases:** +- Building OpenAI Assistant-powered UIs +- Managing assistant threads and runs +- Streaming assistant responses with UI state management +- File search and code interpreter integrations + +See official docs for complete API reference: https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-assistant + +--- + +## Top UI Errors & Solutions + +See `references/top-ui-errors.md` for complete documentation. Quick reference: + +### 1. useChat Failed to Parse Stream + +**Error**: `SyntaxError: Unexpected token in JSON at position X` + +**Cause**: API route not returning proper stream format. + +**Solution**: +```typescript +// ✅ CORRECT +return result.toDataStreamResponse(); + +// ❌ WRONG +return new Response(result.textStream); +``` + +### 2. useChat No Response + +**Cause**: API route not streaming correctly. + +**Solution**: +```typescript +// App Router - use toDataStreamResponse() +export async function POST(req: Request) { + const result = streamText({ /* ... */ }); + return result.toDataStreamResponse(); // ✅ +} + +// Pages Router - use pipeDataStreamToResponse() +export default async function handler(req, res) { + const result = streamText({ /* ... */ }); + return result.pipeDataStreamToResponse(res); // ✅ +} +``` + +### 3. Streaming Not Working When Deployed + +**Cause**: Deployment platform buffering responses. + +**Solution**: Vercel auto-detects streaming. Other platforms may need configuration. + +### 4. Stale Body Values with useChat + +**Cause**: `body` option captured at first render only. + +**Solution**: +```typescript +// ❌ WRONG - body captured once +const { userId } = useUser(); +const { messages } = useChat({ + body: { userId }, // Stale! +}); + +// ✅ CORRECT - use controlled mode +const { userId } = useUser(); +const { messages, sendMessage } = useChat(); + +sendMessage({ + content: input, + data: { userId }, // Fresh on each send +}); +``` + +### 5. React Maximum Update Depth + +**Cause**: Infinite loop in useEffect. + +**Solution**: +```typescript +// ❌ WRONG +useEffect(() => { + saveMessages(messages); +}, [messages, saveMessages]); // saveMessages triggers re-render! + +// ✅ CORRECT +useEffect(() => { + saveMessages(messages); +}, [messages]); // Only depend on messages +``` + +See `references/top-ui-errors.md` for 7 more common errors. + +--- + +## Streaming Best Practices + +### Performance + +**Always use streaming for better UX:** +```tsx +// ✅ GOOD - Streaming (shows tokens as they arrive) +const { messages } = useChat({ api: '/api/chat' }); + +// ❌ BAD - Non-streaming (user waits for full response) +const response = await fetch('/api/chat', { method: 'POST' }); +``` + +### UX Patterns + +**Show loading states:** +```tsx +{isLoading &&
AI is typing...
} +``` + +**Provide stop button:** +```tsx +{isLoading && } +``` + +**Auto-scroll to latest message:** +```tsx +useEffect(() => { + messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); +}, [messages]); +``` + +**Disable input while loading:** +```tsx + +``` + +See `references/streaming-patterns.md` for comprehensive best practices. + +--- + +## When to Use This Skill + +### Use ai-sdk-ui When: +- Building React chat interfaces +- Implementing AI completions in UI +- Streaming AI responses to frontend +- Building Next.js AI applications +- Handling chat message state +- Displaying tool calls in UI +- Managing file attachments with AI +- Migrating from v4 to v5 (UI hooks) +- Encountering useChat/useCompletion errors + +### Don't Use When: +- Need backend AI functionality → Use **ai-sdk-core** instead +- Building non-React frontends (Svelte, Vue) → Check official docs +- Need Generative UI / RSC → See https://ai-sdk.dev/docs/ai-sdk-rsc +- Building native apps → Different SDK required + +### Related Skills: +- **ai-sdk-core** - Backend text generation, structured output, tools, agents +- Compose both for full-stack AI applications + +--- + +## Package Versions + +**Stable (v5):** +```json +{ + "dependencies": { + "ai": "^5.0.99", + "@ai-sdk/react": "^1.0.0", + "@ai-sdk/openai": "^2.0.68", + "react": "^18.2.0", + "zod": "^3.23.8" + } +} +``` + +**Beta (v6):** +```json +{ + "dependencies": { + "ai": "6.0.0-beta.108", + "@ai-sdk/react": "beta", + "@ai-sdk/openai": "beta" + } +} +``` + +**Version Notes:** +- AI SDK v5.0.99 (stable, Nov 2025) +- AI SDK v6.0.0-beta.108 (beta, Nov 22, 2025) - minimal breaking changes +- React 18+ (React 19 supported) +- Next.js 14+ recommended (13.4+ works) +- Zod 3.23.8+ for schema validation + +--- + +## Links to Official Documentation + +**Core UI Hooks:** +- AI SDK UI Overview: https://ai-sdk.dev/docs/ai-sdk-ui/overview +- useChat: https://ai-sdk.dev/docs/ai-sdk-ui/chatbot +- useCompletion: https://ai-sdk.dev/docs/ai-sdk-ui/completion +- useObject: https://ai-sdk.dev/docs/ai-sdk-ui/object-generation + +**Advanced Topics (Link Only):** +- Generative UI (RSC): https://ai-sdk.dev/docs/ai-sdk-rsc/overview +- Stream Protocols: https://ai-sdk.dev/docs/ai-sdk-ui/stream-protocols +- Message Metadata: https://ai-sdk.dev/docs/ai-sdk-ui/message-metadata + +**Next.js Integration:** +- Next.js App Router: https://ai-sdk.dev/docs/getting-started/nextjs-app-router +- Next.js Pages Router: https://ai-sdk.dev/docs/getting-started/nextjs-pages-router + +**Migration & Troubleshooting:** +- v4→v5 Migration: https://ai-sdk.dev/docs/migration-guides/migration-guide-5-0 +- Troubleshooting: https://ai-sdk.dev/docs/troubleshooting +- Common Issues: https://ai-sdk.dev/docs/troubleshooting/common-issues + +**Vercel Deployment:** +- Vercel Functions: https://vercel.com/docs/functions +- Streaming on Vercel: https://vercel.com/docs/functions/streaming + +--- + +## Templates + +This skill includes the following templates in `templates/`: + +1. **use-chat-basic.tsx** - Basic chat with manual input (v5 pattern) +2. **use-chat-tools.tsx** - Chat with tool calling UI rendering +3. **use-chat-attachments.tsx** - File attachments support +4. **use-completion-basic.tsx** - Basic text completion +5. **use-object-streaming.tsx** - Streaming structured data +6. **nextjs-chat-app-router.tsx** - Next.js App Router complete example +7. **nextjs-chat-pages-router.tsx** - Next.js Pages Router complete example +8. **nextjs-api-route.ts** - API route for both App and Pages Router +9. **message-persistence.tsx** - Save/load chat history +10. **custom-message-renderer.tsx** - Custom message components with markdown +11. **package.json** - Dependencies template + +## Reference Documents + +See `references/` for: + +- **use-chat-migration.md** - Complete v4→v5 migration guide +- **streaming-patterns.md** - UI streaming best practices +- **top-ui-errors.md** - 12 common UI errors with solutions +- **nextjs-integration.md** - Next.js setup patterns +- **links-to-official-docs.md** - Organized links to official docs + +--- + +**Production Tested**: WordPress Auditor (https://wordpress-auditor.webfonts.workers.dev) +**Last Updated**: 2025-11-22 diff --git a/plugin.lock.json b/plugin.lock.json new file mode 100644 index 0000000..6dfa832 --- /dev/null +++ b/plugin.lock.json @@ -0,0 +1,113 @@ +{ + "$schema": "internal://schemas/plugin.lock.v1.json", + "pluginId": "gh:jezweb/claude-skills:skills/ai-sdk-ui", + "normalized": { + "repo": null, + "ref": "refs/tags/v20251128.0", + "commit": "4189d9240bad63fd23fccb1f0f68cda8ec84d48d", + "treeHash": "cb67c6ada9700cedb51d477af91e388eb5970bd05d6fb118286822a3f8ef8c56", + "generatedAt": "2025-11-28T10:19:00.492265Z", + "toolVersion": "publish_plugins.py@0.2.0" + }, + "origin": { + "remote": "git@github.com:zhongweili/42plugin-data.git", + "branch": "master", + "commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390", + "repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data" + }, + "manifest": { + "name": "ai-sdk-ui", + "description": "Build React chat interfaces and AI-powered UIs with Vercel AI SDK v5. Provides useChat, useCompletion, and useObject hooks for streaming responses, managing conversation state, and handling file attachments. Use when: implementing chat interfaces, streaming AI completions, managing message state in Next.js apps, or troubleshooting useChat failed to parse stream or useChat no response errors.", + "version": "1.0.0" + }, + "content": { + "files": [ + { + "path": "README.md", + "sha256": "16c745ea28d1ac6c2261492de7ef7947589aa2ab5df40085ff212a0449e6747f" + }, + { + "path": "SKILL.md", + "sha256": "f74568e578f984992ef065b4e5ec60adf99b50ebc0027127732211e0d425e42d" + }, + { + "path": "references/streaming-patterns.md", + "sha256": "7b9b8ad389ee55d84f90f75baa0b6f5e7f5c5ff27bb7f0ae885322b37cdd47d3" + }, + { + "path": "references/use-chat-migration.md", + "sha256": "a6aeb078c686188e373c2565c9d402467d1d2faa21acfd605fe90af2664f8bfe" + }, + { + "path": "references/nextjs-integration.md", + "sha256": "3df6c80298dcf09ba3def099af5f2e6c3933e0e161042e5452471c0c7cd4de79" + }, + { + "path": "references/links-to-official-docs.md", + "sha256": "6891227db63f083547da6c5cb5c9bfb151814bfa83b722a67c95a1d6036ea89f" + }, + { + "path": "references/top-ui-errors.md", + "sha256": "f49f9d2a61abc12536b5e43b304bcfd4de5a0953ffee1aa25c525aa1f54bac0b" + }, + { + "path": "scripts/check-versions.sh", + "sha256": "781f480b6c2405c4f062ce3500f57860bc345872e125afc4393f63c221fe7362" + }, + { + "path": ".claude-plugin/plugin.json", + "sha256": "a28f1111a39df0d7788291136803ea5355342ffb2fc1724c8276bfdd67812291" + }, + { + "path": "templates/nextjs-chat-pages-router.tsx", + "sha256": "ccb155185947bf3874aad8f564911c3e7d8b343a2ee7b4746a4de35cf7707ff7" + }, + { + "path": "templates/use-chat-attachments.tsx", + "sha256": "7651909327acd1de2d4a2e19e2b6ff3c800b0d043bd30a69a6f6bef51b424646" + }, + { + "path": "templates/nextjs-api-route.ts", + "sha256": "acc3c1b2ec582d38eb92fc42376f628e58aaf10c76948ded53a37c9011b303dc" + }, + { + "path": "templates/use-chat-tools.tsx", + "sha256": "1d01f43f6cf8fe45efd9d2a66b19137de353bc7e49bc565cee52827a495c640c" + }, + { + "path": "templates/package.json", + "sha256": "043a261ef1a334ad105c8997cef99e856d6e66c14c114b05159bc959317413aa" + }, + { + "path": "templates/use-object-streaming.tsx", + "sha256": "23361eb78fe5e63a44aae9f49a5f5782d842840d32a5e97b98cde26eb761628a" + }, + { + "path": "templates/custom-message-renderer.tsx", + "sha256": "20f8885a52f95ee02d3c4ba940f13ab7bf48c428d4f3730cc13c071b6f09a594" + }, + { + "path": "templates/use-completion-basic.tsx", + "sha256": "55a0ab32fb77edb1e6298def56022f364ebf3a138cdd5c00e691030f4b7a1f12" + }, + { + "path": "templates/use-chat-basic.tsx", + "sha256": "5b8b0074cc4f0413b9dd52021f85b79be0dffe8f501639be36191c579f6ecbcb" + }, + { + "path": "templates/message-persistence.tsx", + "sha256": "12d440fb4a242ecb5d258d773f65d8bd25794cbef73cfef5c61fd38ecd15c98e" + }, + { + "path": "templates/nextjs-chat-app-router.tsx", + "sha256": "5fc82c7efcd43caa2fae59f2ad66793039efbba59868f230b18c518f4b57f59c" + } + ], + "dirSha256": "cb67c6ada9700cedb51d477af91e388eb5970bd05d6fb118286822a3f8ef8c56" + }, + "security": { + "scannedAt": null, + "scannerVersion": null, + "flags": [] + } +} \ No newline at end of file diff --git a/references/links-to-official-docs.md b/references/links-to-official-docs.md new file mode 100644 index 0000000..52fccc4 --- /dev/null +++ b/references/links-to-official-docs.md @@ -0,0 +1,116 @@ +# AI SDK UI - Official Documentation Links + +Organized links to official AI SDK UI and React hooks documentation. + +**Last Updated**: 2025-10-22 + +--- + +## AI SDK UI Documentation + +### Core Hooks + +- **AI SDK UI Overview:** https://ai-sdk.dev/docs/ai-sdk-ui/overview +- **useChat:** https://ai-sdk.dev/docs/ai-sdk-ui/chatbot +- **useCompletion:** https://ai-sdk.dev/docs/ai-sdk-ui/completion +- **useObject:** https://ai-sdk.dev/docs/ai-sdk-ui/object-generation + +### Advanced Topics (Not Replicated in This Skill) + +- **Generative UI (RSC):** https://ai-sdk.dev/docs/ai-sdk-rsc/overview +- **Stream Protocols:** https://ai-sdk.dev/docs/ai-sdk-ui/stream-protocols +- **Message Metadata:** https://ai-sdk.dev/docs/ai-sdk-ui/message-metadata +- **Custom Transports:** https://ai-sdk.dev/docs/ai-sdk-ui/transports + +--- + +## Next.js Integration + +- **Next.js App Router:** https://ai-sdk.dev/docs/getting-started/nextjs-app-router +- **Next.js Pages Router:** https://ai-sdk.dev/docs/getting-started/nextjs-pages-router +- **Next.js Documentation:** https://nextjs.org/docs + +--- + +## Migration & Troubleshooting + +- **v4 → v5 Migration Guide:** https://ai-sdk.dev/docs/migration-guides/migration-guide-5-0 +- **Troubleshooting Guide:** https://ai-sdk.dev/docs/troubleshooting +- **Common Issues:** https://ai-sdk.dev/docs/troubleshooting/common-issues +- **All Error Types (28 total):** https://ai-sdk.dev/docs/reference/ai-sdk-errors + +--- + +## API Reference + +- **useChat API:** https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-chat +- **useCompletion API:** https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-completion +- **useObject API:** https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-object + +--- + +## Vercel Deployment + +- **Vercel Functions:** https://vercel.com/docs/functions +- **Streaming on Vercel:** https://vercel.com/docs/functions/streaming +- **Environment Variables:** https://vercel.com/docs/projects/environment-variables +- **AI SDK 5.0 Release:** https://vercel.com/blog/ai-sdk-5 + +--- + +## GitHub & Community + +- **GitHub Repository:** https://github.com/vercel/ai +- **GitHub Issues:** https://github.com/vercel/ai/issues +- **GitHub Discussions:** https://github.com/vercel/ai/discussions +- **Discord Community:** https://discord.gg/vercel + +--- + +## TypeScript & React + +- **TypeScript Handbook:** https://www.typescriptlang.org/docs/ +- **React Documentation:** https://react.dev + +--- + +## Complementary Skills + +For complete AI SDK coverage, also see: + +- **ai-sdk-core skill:** Backend text generation, structured output, tools, agents +- **cloudflare-workers-ai skill:** Native Cloudflare Workers AI binding (no multi-provider) + +--- + +## Quick Navigation + +### I want to... + +**Build a chat interface:** +- Docs: https://ai-sdk.dev/docs/ai-sdk-ui/chatbot +- Template: `templates/use-chat-basic.tsx` + +**Stream text completions:** +- Docs: https://ai-sdk.dev/docs/ai-sdk-ui/completion +- Template: `templates/use-completion-basic.tsx` + +**Generate structured output:** +- Docs: https://ai-sdk.dev/docs/ai-sdk-ui/object-generation +- Template: `templates/use-object-streaming.tsx` + +**Migrate from v4:** +- Docs: https://ai-sdk.dev/docs/migration-guides/migration-guide-5-0 +- Reference: `references/use-chat-migration.md` + +**Fix a UI error:** +- Reference: `references/top-ui-errors.md` +- Docs: https://ai-sdk.dev/docs/reference/ai-sdk-errors + +**Deploy to production:** +- Reference: `references/nextjs-integration.md` +- Docs: https://vercel.com/docs/functions/streaming + +--- + +**Last Updated**: 2025-10-22 diff --git a/references/nextjs-integration.md b/references/nextjs-integration.md new file mode 100644 index 0000000..13c8439 --- /dev/null +++ b/references/nextjs-integration.md @@ -0,0 +1,247 @@ +# AI SDK UI - Next.js Integration + +Complete guide for integrating AI SDK UI with Next.js. + +**Last Updated**: 2025-10-22 + +--- + +## App Router (Next.js 13+) + +### Directory Structure + +``` +app/ +├── api/ +│ └── chat/ +│ └── route.ts # API route +├── chat/ +│ └── page.tsx # Chat page (Client Component) +└── layout.tsx +``` + +### Chat Page (Client Component) + +```tsx +// app/chat/page.tsx +'use client'; // REQUIRED + +import { useChat } from 'ai/react'; +import { useState } from 'react'; + +export default function ChatPage() { + const { messages, sendMessage, isLoading } = useChat({ + api: '/api/chat', + }); + const [input, setInput] = useState(''); + + const handleSubmit = (e) => { + e.preventDefault(); + sendMessage({ content: input }); + setInput(''); + }; + + return ( +
+ {messages.map(m =>
{m.content}
)} +
+ setInput(e.target.value)} /> +
+
+ ); +} +``` + +### API Route + +```typescript +// app/api/chat/route.ts +import { streamText } from 'ai'; +import { openai } from '@ai-sdk/openai'; + +export async function POST(req: Request) { + const { messages } = await req.json(); + + const result = streamText({ + model: openai('gpt-4-turbo'), + messages, + }); + + return result.toDataStreamResponse(); // App Router method +} +``` + +--- + +## Pages Router (Next.js 12 and earlier) + +### Directory Structure + +``` +pages/ +├── api/ +│ └── chat.ts # API route +└── chat.tsx # Chat page +``` + +### Chat Page + +```tsx +// pages/chat.tsx +import { useChat } from 'ai/react'; +import { useState } from 'react'; + +export default function ChatPage() { + const { messages, sendMessage, isLoading } = useChat({ + api: '/api/chat', + }); + const [input, setInput] = useState(''); + + const handleSubmit = (e) => { + e.preventDefault(); + sendMessage({ content: input }); + setInput(''); + }; + + return ( +
+ {messages.map(m =>
{m.content}
)} +
+ setInput(e.target.value)} /> +
+
+ ); +} +``` + +### API Route + +```typescript +// pages/api/chat.ts +import type { NextApiRequest, NextApiResponse } from 'next'; +import { streamText } from 'ai'; +import { openai } from '@ai-sdk/openai'; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + const { messages } = req.body; + + const result = streamText({ + model: openai('gpt-4-turbo'), + messages, + }); + + return result.pipeDataStreamToResponse(res); // Pages Router method +} +``` + +--- + +## Key Differences + +| Feature | App Router | Pages Router | +|---------|------------|--------------| +| Route Handler | `app/api/chat/route.ts` | `pages/api/chat.ts` | +| Stream Method | `toDataStreamResponse()` | `pipeDataStreamToResponse()` | +| Client Directive | Requires `'use client'` | Not required | +| Server Components | Supported | Not supported | + +--- + +## Environment Variables + +### .env.local + +```bash +# Required +OPENAI_API_KEY=sk-... +ANTHROPIC_API_KEY=sk-ant-... +GOOGLE_GENERATIVE_AI_API_KEY=... + +# Optional +NODE_ENV=development +``` + +### Accessing in API Routes + +```typescript +// App Router +export async function POST(req: Request) { + const apiKey = process.env.OPENAI_API_KEY; + // ... +} + +// Pages Router +export default async function handler(req, res) { + const apiKey = process.env.OPENAI_API_KEY; + // ... +} +``` + +--- + +## Deployment to Vercel + +### 1. Add Environment Variables + +In Vercel Dashboard: +1. Go to Settings → Environment Variables +2. Add `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, etc. +3. Select environments (Production, Preview, Development) + +### 2. Deploy + +```bash +npm run build +vercel deploy +``` + +Vercel auto-detects streaming and configures appropriately. + +### 3. Verify Streaming + +Check response headers: +- `Transfer-Encoding: chunked` +- `X-Vercel-Streaming: true` + +**Docs**: https://vercel.com/docs/functions/streaming + +--- + +## Common Issues + +### Issue: "useChat is not defined" + +**Cause**: Not importing from correct package. + +**Fix**: +```tsx +import { useChat } from 'ai/react'; // ✅ Correct +import { useChat } from 'ai'; // ❌ Wrong +``` + +### Issue: "Cannot use 'use client' directive" + +**Cause**: Using `'use client'` in Pages Router. + +**Fix**: Remove `'use client'` - only needed in App Router. + +### Issue: "API route returns 405 Method Not Allowed" + +**Cause**: Using GET instead of POST. + +**Fix**: Ensure API route exports `POST` function (App Router) or checks `req.method === 'POST'` (Pages Router). + +--- + +## Official Documentation + +- **App Router**: https://ai-sdk.dev/docs/getting-started/nextjs-app-router +- **Pages Router**: https://ai-sdk.dev/docs/getting-started/nextjs-pages-router +- **Next.js Docs**: https://nextjs.org/docs + +--- + +**Last Updated**: 2025-10-22 diff --git a/references/streaming-patterns.md b/references/streaming-patterns.md new file mode 100644 index 0000000..12ea3f1 --- /dev/null +++ b/references/streaming-patterns.md @@ -0,0 +1,433 @@ +# AI SDK UI - Streaming Best Practices + +UI patterns and best practices for streaming AI responses. + +**Last Updated**: 2025-10-22 + +--- + +## Performance + +### Always Use Streaming for Long-Form Content + +```tsx +// ✅ GOOD: Streaming provides better perceived performance +const { messages } = useChat({ api: '/api/chat' }); + +// ❌ BAD: Blocking - user waits for entire response +const response = await fetch('/api/chat', { method: 'POST' }); +``` + +**Why?** +- Users see tokens as they arrive +- Perceived performance is much faster +- Users can start reading before response completes +- Can stop generation early + +--- + +## UX Patterns + +### 1. Show Loading States + +```tsx +const { messages, isLoading } = useChat(); + +{isLoading && ( +
+
+
+
+
+)} +``` + +### 2. Provide Stop Button + +```tsx +const { isLoading, stop } = useChat(); + +{isLoading && ( + +)} +``` + +### 3. Auto-Scroll to Latest Message + +```tsx +const messagesEndRef = useRef(null); + +useEffect(() => { + messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); +}, [messages]); + +
+``` + +### 4. Disable Input While Loading + +```tsx + setInput(e.target.value)} + disabled={isLoading} // Prevent new messages while generating + className="disabled:bg-gray-100" +/> +``` + +### 5. Handle Empty States + +```tsx +{messages.length === 0 ? ( +
+

Start a conversation

+

Ask me anything!

+
+) : ( + // Messages list +)} +``` + +--- + +## Error Handling + +### 1. Display Errors to Users + +```tsx +const { error } = useChat(); + +{error && ( +
+ Error: {error.message} +
+)} +``` + +### 2. Provide Retry Functionality + +```tsx +const { error, reload } = useChat(); + +{error && ( +
+ {error.message} + +
+)} +``` + +### 3. Handle Network Failures Gracefully + +```tsx +useChat({ + onError: (error) => { + console.error('Chat error:', error); + // Log to monitoring service (Sentry, etc.) + // Show user-friendly message + }, +}); +``` + +### 4. Log Errors for Debugging + +```tsx +useChat({ + onError: (error) => { + const errorLog = { + timestamp: new Date().toISOString(), + message: error.message, + url: window.location.href, + }; + console.error('AI SDK Error:', errorLog); + // Send to Sentry/Datadog/etc. + }, +}); +``` + +--- + +## Message Rendering + +### 1. Support Markdown + +Use `react-markdown` for rich content: + +```tsx +import ReactMarkdown from 'react-markdown'; + +{messages.map(m => ( + {m.content} +))} +``` + +### 2. Handle Code Blocks + +```tsx +import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; + + + {String(children)} + + ) : ( + + {children} + + ); + }, + }} +> + {message.content} + +``` + +### 3. Display Tool Calls Visually + +```tsx +{message.toolInvocations?.map((tool, idx) => ( +
+
Tool: {tool.toolName}
+
Args: {JSON.stringify(tool.args)}
+ {tool.result && ( +
Result: {JSON.stringify(tool.result)}
+ )} +
+))} +``` + +### 4. Show Timestamps + +```tsx +
+ {new Date(message.createdAt).toLocaleTimeString()} +
+``` + +### 5. Group Messages by Role + +```tsx +{messages.reduce((groups, message, idx) => { + const prevMessage = messages[idx - 1]; + const showRole = !prevMessage || prevMessage.role !== message.role; + + return [ + ...groups, +
+ {showRole &&
{message.role}
} +
{message.content}
+
+ ]; +}, [])} +``` + +--- + +## State Management + +### 1. Persist Chat History + +```tsx +const chatId = 'chat-123'; + +const { messages } = useChat({ + id: chatId, + initialMessages: loadFromLocalStorage(chatId), +}); + +useEffect(() => { + saveToLocalStorage(chatId, messages); +}, [messages, chatId]); +``` + +### 2. Clear Chat Functionality + +```tsx +const { setMessages } = useChat(); + +const clearChat = () => { + if (confirm('Clear chat history?')) { + setMessages([]); + } +}; +``` + +### 3. Export/Import Conversations + +```tsx +const exportChat = () => { + const json = JSON.stringify(messages, null, 2); + const blob = new Blob([json], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `chat-${Date.now()}.json`; + a.click(); +}; + +const importChat = (file: File) => { + const reader = new FileReader(); + reader.onload = (e) => { + const imported = JSON.parse(e.target?.result as string); + setMessages(imported); + }; + reader.readAsText(file); +}; +``` + +### 4. Handle Multiple Chats (Routing) + +```tsx +// Use URL params for chat ID +const searchParams = useSearchParams(); +const chatId = searchParams.get('chatId') || 'default'; + +const { messages } = useChat({ + id: chatId, + initialMessages: loadMessages(chatId), +}); + +// Navigation +New Chat +``` + +--- + +## Advanced Patterns + +### 1. Debounced Input for Completions + +```tsx +import { useDebouncedCallback } from 'use-debounce'; + +const { complete } = useCompletion(); + +const debouncedComplete = useDebouncedCallback((value) => { + complete(value); +}, 500); + + debouncedComplete(e.target.value)} /> +``` + +### 2. Optimistic Updates + +```tsx +const { messages, sendMessage } = useChat(); + +const optimisticSend = (content: string) => { + // Add user message immediately + const tempMessage = { + id: `temp-${Date.now()}`, + role: 'user', + content, + }; + + setMessages([...messages, tempMessage]); + + // Send to server + sendMessage({ content }); +}; +``` + +### 3. Custom Message Formatting + +```tsx +const formatMessage = (content: string) => { + // Replace @mentions + content = content.replace(/@(\w+)/g, '@$1'); + + // Replace URLs + content = content.replace( + /(https?:\/\/[^\s]+)/g, + '$1' + ); + + return content; +}; + +
+``` + +### 4. Typing Indicators + +```tsx +const [isTyping, setIsTyping] = useState(false); + +useChat({ + onFinish: () => setIsTyping(false), +}); + +const handleSend = (content: string) => { + setIsTyping(true); + sendMessage({ content }); +}; + +{isTyping &&
AI is typing...
} +``` + +--- + +## Performance Optimization + +### 1. Virtualize Long Message Lists + +```tsx +import { FixedSizeList } from 'react-window'; + + + {({ index, style }) => ( +
+ {messages[index].content} +
+ )} +
+``` + +### 2. Lazy Load Message History + +```tsx +const [page, setPage] = useState(1); +const messagesPerPage = 50; + +const visibleMessages = messages.slice( + (page - 1) * messagesPerPage, + page * messagesPerPage +); +``` + +### 3. Memoize Message Rendering + +```tsx +import { memo } from 'react'; + +const MessageComponent = memo(({ message }: { message: Message }) => { + return
{message.content}
; +}); + +{messages.map(m => )} +``` + +--- + +## Official Documentation + +- **AI SDK UI Overview**: https://ai-sdk.dev/docs/ai-sdk-ui/overview +- **Streaming Protocols**: https://ai-sdk.dev/docs/ai-sdk-ui/stream-protocols +- **Message Metadata**: https://ai-sdk.dev/docs/ai-sdk-ui/message-metadata + +--- + +**Last Updated**: 2025-10-22 diff --git a/references/top-ui-errors.md b/references/top-ui-errors.md new file mode 100644 index 0000000..3a29598 --- /dev/null +++ b/references/top-ui-errors.md @@ -0,0 +1,303 @@ +# AI SDK UI - Top 12 Errors & Solutions + +Common AI SDK UI errors with actionable solutions. + +**Last Updated**: 2025-10-22 + +--- + +## 1. useChat Failed to Parse Stream + +**Error**: `SyntaxError: Unexpected token in JSON at position X` + +**Cause**: API route not returning proper stream format. + +**Solution**: +```typescript +// ✅ CORRECT (App Router) +export async function POST(req: Request) { + const result = streamText({ /* ... */ }); + return result.toDataStreamResponse(); // Correct method +} + +// ✅ CORRECT (Pages Router) +export default async function handler(req, res) { + const result = streamText({ /* ... */ }); + return result.pipeDataStreamToResponse(res); // Correct method +} + +// ❌ WRONG +return new Response(result.textStream); // Missing stream protocol +``` + +--- + +## 2. useChat No Response + +**Cause**: API route not streaming correctly or wrong method. + +**Solution**: +```typescript +// Check 1: Are you using the right method? +// App Router: toDataStreamResponse() +// Pages Router: pipeDataStreamToResponse() + +// Check 2: Is your API route returning a Response? +export async function POST(req: Request) { + const result = streamText({ model: openai('gpt-4'), messages }); + return result.toDataStreamResponse(); // Must return this! +} + +// Check 3: Check network tab - is the request completing? +// If status is 200 but no data: likely streaming issue +``` + +--- + +## 3. Unclosed Streams + +**Cause**: Stream not properly closed in API. + +**Solution**: +```typescript +// ✅ GOOD: SDK handles closing automatically +export async function POST(req: Request) { + const result = streamText({ model: openai('gpt-4'), messages }); + return result.toDataStreamResponse(); +} + +// ❌ BAD: Manual stream handling (error-prone) +const encoder = new TextEncoder(); +const stream = new ReadableStream({ + async start(controller) { + // ...must manually close! + controller.close(); + } +}); +``` + +**GitHub Issue**: #4123 + +--- + +## 4. Streaming Not Working When Deployed + +**Cause**: Deployment platform buffering responses. + +**Solution**: +- **Vercel**: Auto-detects streaming (no config needed) +- **Netlify**: Ensure Edge Functions enabled +- **Cloudflare Workers**: Use `toDataStreamResponse()` +- **Other platforms**: Check for response buffering settings + +```typescript +// Vercel - works out of the box +export async function POST(req: Request) { + const result = streamText({ /* ... */ }); + return result.toDataStreamResponse(); +} +``` + +**Docs**: https://vercel.com/docs/functions/streaming + +--- + +## 5. Streaming Not Working When Proxied + +**Cause**: Proxy (nginx, Cloudflare, etc.) buffering responses. + +**Solution**: + +**Nginx**: +```nginx +location /api/ { + proxy_pass http://localhost:3000; + proxy_buffering off; # Disable buffering + proxy_cache off; +} +``` + +**Cloudflare**: Disable "Auto Minify" in dashboard + +--- + +## 6. Strange Stream Output (0:... characters) + +**Error**: Seeing raw stream protocol like `0:"Hello"` in browser. + +**Cause**: Not using correct hook or consuming stream directly. + +**Solution**: +```tsx +// ✅ CORRECT: Use useChat hook +const { messages } = useChat({ api: '/api/chat' }); + +// ❌ WRONG: Consuming stream directly +const response = await fetch('/api/chat'); +const reader = response.body.getReader(); // Don't do this! +``` + +--- + +## 7. Stale Body Values with useChat + +**Cause**: `body` captured at first render only. + +**Solution**: +```tsx +// ❌ BAD: body captured once +const { userId } = useUser(); +const { messages } = useChat({ + body: { userId }, // Stale! Won't update if userId changes +}); + +// ✅ GOOD: Use data in sendMessage +const { userId } = useUser(); +const { messages, sendMessage } = useChat(); + +sendMessage({ + content: input, + data: { userId }, // Fresh value on each send +}); +``` + +--- + +## 8. Custom Headers Not Working with useChat + +**Cause**: Headers not passed correctly. + +**Solution**: +```tsx +// ✅ CORRECT +const { messages } = useChat({ + headers: { + 'Authorization': `Bearer ${token}`, + 'X-Custom-Header': 'value', + }, +}); + +// OR use fetch options +const { messages } = useChat({ + fetch: (url, options) => { + return fetch(url, { + ...options, + headers: { + ...options.headers, + 'Authorization': `Bearer ${token}`, + }, + }); + }, +}); +``` + +--- + +## 9. React Maximum Update Depth + +**Error**: `Maximum update depth exceeded` + +**Cause**: Infinite loop in useEffect. + +**Solution**: +```tsx +// ❌ BAD: Infinite loop +const saveMessages = (messages) => { /* ... */ }; + +useEffect(() => { + saveMessages(messages); +}, [messages, saveMessages]); // saveMessages changes every render! + +// ✅ GOOD: Only depend on messages +useEffect(() => { + localStorage.setItem('messages', JSON.stringify(messages)); +}, [messages]); // saveMessages not needed in deps +``` + +--- + +## 10. Repeated Assistant Messages + +**Cause**: Duplicate message handling or multiple sendMessage calls. + +**Solution**: +```tsx +// ❌ BAD: Calling sendMessage multiple times +const handleSubmit = (e) => { + e.preventDefault(); + sendMessage({ content: input }); + sendMessage({ content: input }); // Duplicate! +}; + +// ✅ GOOD: Single call +const handleSubmit = (e) => { + e.preventDefault(); + if (!input.trim()) return; // Guard + sendMessage({ content: input }); + setInput(''); +}; +``` + +--- + +## 11. onFinish Not Called When Stream Aborted + +**Cause**: Stream abort doesn't trigger onFinish callback. + +**Solution**: +```tsx +const { stop } = useChat({ + onFinish: (message) => { + console.log('Finished:', message); + }, +}); + +// Handle abort separately +const handleStop = () => { + stop(); + console.log('Stream aborted by user'); + // Do cleanup here +}; +``` + +--- + +## 12. Type Error with Message Parts (v5) + +**Error**: `Property 'parts' does not exist on type 'Message'` + +**Cause**: v5 changed message structure for tool calls. + +**Solution**: +```tsx +// ✅ CORRECT (v5) +messages.map(message => { + // Use content for simple messages + if (message.content) { + return
{message.content}
; + } + + // Use toolInvocations for tool calls + if (message.toolInvocations) { + return message.toolInvocations.map(tool => ( +
+ Tool: {tool.toolName} +
+ )); + } +}); + +// ❌ WRONG (v4 style) +message.toolCalls // Doesn't exist in v5 +``` + +--- + +## For More Errors + +See complete error reference (28 total types): +https://ai-sdk.dev/docs/reference/ai-sdk-errors + +--- + +**Last Updated**: 2025-10-22 diff --git a/references/use-chat-migration.md b/references/use-chat-migration.md new file mode 100644 index 0000000..f11482d --- /dev/null +++ b/references/use-chat-migration.md @@ -0,0 +1,432 @@ +# useChat v4 → v5 Migration Guide + +Complete guide to migrating from AI SDK v4 to v5 for UI hooks. + +**Last Updated**: 2025-10-22 +**Applies to**: AI SDK v5.0+ + +--- + +## Critical Breaking Change + +**BREAKING: useChat no longer manages input state!** + +In v4, `useChat` provided `input`, `handleInputChange`, and `handleSubmit`. In v5, you must manage input state manually using `useState`. + +--- + +## Quick Migration Checklist + +- [ ] Replace `input`, `handleInputChange`, `handleSubmit` with manual state +- [ ] Change `append()` to `sendMessage()` +- [ ] Replace `onResponse` with `onFinish` +- [ ] Move `initialMessages` to controlled mode with `messages` prop +- [ ] Remove `maxSteps` (handle server-side) +- [ ] Update message rendering for parts structure (if using tools) + +--- + +## 1. Input State Management (CRITICAL) + +### v4 (OLD) + +```tsx +import { useChat } from 'ai/react'; + +export default function Chat() { + const { messages, input, handleInputChange, handleSubmit } = useChat({ + api: '/api/chat', + }); + + return ( +
+ {messages.map(m =>
{m.content}
)} +
+ +
+
+ ); +} +``` + +### v5 (NEW) + +```tsx +import { useChat } from 'ai/react'; +import { useState, FormEvent } from 'react'; + +export default function Chat() { + const { messages, sendMessage } = useChat({ + api: '/api/chat', + }); + + // Manual input state + const [input, setInput] = useState(''); + + const handleSubmit = (e: FormEvent) => { + e.preventDefault(); + sendMessage({ content: input }); + setInput(''); + }; + + return ( +
+ {messages.map(m =>
{m.content}
)} +
+ setInput(e.target.value)} + /> +
+
+ ); +} +``` + +**Why?** +- More control over input handling +- Easier to add features like debouncing, validation, etc. +- Consistent with React patterns + +--- + +## 2. append() → sendMessage() + +### v4 (OLD) + +```tsx +const { append } = useChat(); + +// Append a message +append({ + role: 'user', + content: 'Hello', +}); +``` + +### v5 (NEW) + +```tsx +const { sendMessage } = useChat(); + +// Send a message (role is assumed to be 'user') +sendMessage({ + content: 'Hello', +}); + +// With attachments +sendMessage({ + content: 'Analyze this image', + experimental_attachments: [ + { name: 'image.png', contentType: 'image/png', url: 'blob:...' }, + ], +}); +``` + +**Why?** +- Clearer API: `sendMessage` is more intuitive than `append` +- Supports attachments natively +- Role is always 'user' (no need to specify) + +--- + +## 3. onResponse → onFinish + +### v4 (OLD) + +```tsx +const { messages } = useChat({ + onResponse: (response) => { + console.log('Response received:', response); + }, +}); +``` + +### v5 (NEW) + +```tsx +const { messages } = useChat({ + onFinish: (message, options) => { + console.log('Response finished:', message); + console.log('Finish reason:', options.finishReason); + console.log('Usage:', options.usage); + }, +}); +``` + +**Why?** +- `onResponse` fired too early (when response started) +- `onFinish` fires when response is complete +- Provides more context (usage, finish reason) + +--- + +## 4. initialMessages → Controlled Mode + +### v4 (OLD) + +```tsx +const { messages } = useChat({ + initialMessages: [ + { role: 'system', content: 'You are a helpful assistant.' }, + ], +}); +``` + +### v5 (NEW - Option 1: Uncontrolled) + +```tsx +const { messages } = useChat({ + // Use initialMessages for read-only initialization + initialMessages: [ + { role: 'system', content: 'You are a helpful assistant.' }, + ], +}); +``` + +### v5 (NEW - Option 2: Controlled) + +```tsx +const [messages, setMessages] = useState([ + { role: 'system', content: 'You are a helpful assistant.' }, +]); + +const { sendMessage } = useChat({ + messages, // Pass messages for controlled mode + onUpdate: ({ messages }) => { + setMessages(messages); // Sync state + }, +}); +``` + +**Why?** +- Clearer distinction between controlled and uncontrolled +- Easier to persist messages to database + +--- + +## 5. maxSteps Removed + +### v4 (OLD) + +```tsx +const { messages } = useChat({ + maxSteps: 5, // Limit agent steps +}); +``` + +### v5 (NEW) + +Handle `maxSteps` (or `stopWhen`) on the **server-side** only: + +```typescript +// app/api/chat/route.ts +import { streamText, stopWhen } from 'ai'; + +export async function POST(req: Request) { + const { messages } = await req.json(); + + const result = streamText({ + model: openai('gpt-4'), + messages, + maxSteps: 5, // Handle on server + }); + + return result.toDataStreamResponse(); +} +``` + +**Why?** +- Server has more control over costs +- Prevents client-side bypass +- Consistent with v5 architecture + +--- + +## 6. Message Structure (for Tools) + +### v4 (OLD) + +```tsx +// Simple message structure +{ + id: '1', + role: 'assistant', + content: 'The weather is sunny', + toolCalls: [...] // Tool calls as separate property +} +``` + +### v5 (NEW) + +```tsx +// Parts-based structure +{ + id: '1', + role: 'assistant', + content: 'The weather is sunny', // Still exists for simple messages + parts: [ + { type: 'text', content: 'The weather is' }, + { type: 'tool-call', toolName: 'getWeather', args: { location: 'SF' } }, + { type: 'tool-result', toolName: 'getWeather', result: { temp: 72 } }, + { type: 'text', content: 'sunny' }, + ] +} +``` + +**Rendering v5 Messages:** + +```tsx +messages.map(message => { + // For simple text messages, use content + if (message.content) { + return
{message.content}
; + } + + // For tool calls, use toolInvocations + if (message.toolInvocations) { + return message.toolInvocations.map(tool => ( +
+ Tool: {tool.toolName} + Args: {JSON.stringify(tool.args)} + Result: {JSON.stringify(tool.result)} +
+ )); + } +}); +``` + +--- + +## 7. Other Removed/Changed Properties + +### Removed in v5 + +- `input` - Use manual `useState` +- `handleInputChange` - Use `onChange={(e) => setInput(e.target.value)}` +- `handleSubmit` - Use custom submit handler +- `onResponse` - Use `onFinish` instead + +### Renamed in v5 + +- `append()` → `sendMessage()` +- `initialMessages` → Still exists, but use `messages` prop for controlled mode + +### Added in v5 + +- `sendMessage()` - New way to send messages +- `experimental_attachments` - File attachments support +- `toolInvocations` - Simplified tool call rendering + +--- + +## Common Migration Patterns + +### Pattern 1: Basic Chat + +**v4:** +```tsx +const { messages, input, handleInputChange, handleSubmit } = useChat(); +
+ +
+``` + +**v5:** +```tsx +const { messages, sendMessage } = useChat(); +const [input, setInput] = useState(''); + +
{ + e.preventDefault(); + sendMessage({ content: input }); + setInput(''); +}}> + setInput(e.target.value)} /> +
+``` + +### Pattern 2: With Initial Messages + +**v4:** +```tsx +const { messages } = useChat({ + initialMessages: loadFromStorage(), +}); +``` + +**v5:** +```tsx +const { messages } = useChat({ + initialMessages: loadFromStorage(), // Still works +}); +``` + +### Pattern 3: With Response Callback + +**v4:** +```tsx +useChat({ + onResponse: (res) => console.log('Started'), +}); +``` + +**v5:** +```tsx +useChat({ + onFinish: (msg, opts) => { + console.log('Finished'); + console.log('Tokens:', opts.usage.totalTokens); + }, +}); +``` + +--- + +## Migration Troubleshooting + +### Error: "input is undefined" + +**Cause**: You're using v5 but trying to access `input` from `useChat`. + +**Fix**: Add manual input state: +```tsx +const [input, setInput] = useState(''); +``` + +### Error: "append is not a function" + +**Cause**: `append()` was renamed to `sendMessage()` in v5. + +**Fix**: Replace all instances of `append()` with `sendMessage()`. + +### Error: "handleSubmit is undefined" + +**Cause**: v5 doesn't provide `handleSubmit`. + +**Fix**: Create custom submit handler: +```tsx +const handleSubmit = (e: FormEvent) => { + e.preventDefault(); + sendMessage({ content: input }); + setInput(''); +}; +``` + +### Warning: "onResponse is deprecated" + +**Cause**: v5 removed `onResponse`. + +**Fix**: Use `onFinish` instead. + +--- + +## Official Migration Resources + +- **v5 Migration Guide**: https://ai-sdk.dev/docs/migration-guides/migration-guide-5-0 +- **useChat API Reference**: https://ai-sdk.dev/docs/reference/ai-sdk-ui/use-chat +- **v5 Release Notes**: https://vercel.com/blog/ai-sdk-5 + +--- + +**Last Updated**: 2025-10-22 diff --git a/scripts/check-versions.sh b/scripts/check-versions.sh new file mode 100755 index 0000000..eab14e8 --- /dev/null +++ b/scripts/check-versions.sh @@ -0,0 +1,70 @@ +#!/bin/bash +# Check installed AI SDK UI package versions against latest +# Usage: ./scripts/check-versions.sh + +echo "===================================" +echo " AI SDK UI - Version Checker" +echo "===================================" +echo "" + +packages=( + "ai" + "@ai-sdk/openai" + "@ai-sdk/anthropic" + "@ai-sdk/google" + "react" + "react-dom" + "next" + "zod" +) + +echo "Checking package versions..." +echo "" + +for package in "${packages[@]}"; do + echo "📦 $package" + + # Get installed version + installed=$(npm list "$package" --depth=0 2>/dev/null | grep "$package" | awk -F@ '{print $NF}') + + if [ -z "$installed" ]; then + echo " ❌ Not installed" + else + echo " ✅ Installed: $installed" + fi + + # Get latest version + latest=$(npm view "$package" version 2>/dev/null) + + if [ -z "$latest" ]; then + echo " ⚠️ Could not fetch latest version" + else + echo " 📌 Latest: $latest" + + # Compare versions + if [ "$installed" = "$latest" ]; then + echo " ✨ Up to date!" + elif [ -n "$installed" ]; then + echo " ⬆️ Update available" + fi + fi + + echo "" +done + +echo "===================================" +echo " Recommended Versions (AI SDK v5)" +echo "===================================" +echo "" +echo "ai: ^5.0.76" +echo "@ai-sdk/openai: ^2.0.53" +echo "@ai-sdk/anthropic: ^2.0.0" +echo "@ai-sdk/google: ^2.0.0" +echo "react: ^18.2.0" +echo "react-dom: ^18.2.0" +echo "next: ^14.0.0" +echo "zod: ^3.23.8" +echo "" +echo "To update all packages:" +echo "npm install ai@latest @ai-sdk/openai@latest @ai-sdk/anthropic@latest @ai-sdk/google@latest react@latest react-dom@latest next@latest zod@latest" +echo "" diff --git a/templates/custom-message-renderer.tsx b/templates/custom-message-renderer.tsx new file mode 100644 index 0000000..d6f2b50 --- /dev/null +++ b/templates/custom-message-renderer.tsx @@ -0,0 +1,269 @@ +/** + * AI SDK UI - Custom Message Renderer + * + * Demonstrates: + * - Markdown rendering (react-markdown) + * - Code syntax highlighting (react-syntax-highlighter) + * - Custom message components + * - Copy code button + * - Timestamp display + * - User avatars + * + * Dependencies: + * npm install react-markdown react-syntax-highlighter + * npm install --save-dev @types/react-syntax-highlighter + * + * Usage: + * 1. Install dependencies + * 2. Copy this component + * 3. Use in your chat + */ + +'use client'; + +import { useChat } from 'ai/react'; +import { useState, FormEvent } from 'react'; +import type { Message } from 'ai'; +import ReactMarkdown from 'react-markdown'; +import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; +import { oneDark } from 'react-syntax-highlighter/dist/cjs/styles/prism'; + +// Custom message renderer component +function MessageRenderer({ message }: { message: Message }) { + const [copied, setCopied] = useState(false); + + const copyCode = (code: string) => { + navigator.clipboard.writeText(code); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + + return ( +
+
+ {/* Avatar & name */} +
+
+ {message.role === 'user' ? 'U' : 'AI'} +
+ + {message.role === 'user' ? 'You' : 'Assistant'} + +
+ + {/* Message content with markdown */} +
+ + + {codeString} + + +
+ ) : ( + + {children} + + ); + }, + }} + > + {message.content} + +
+ + {/* Timestamp */} +
+ {new Date(message.createdAt || Date.now()).toLocaleTimeString()} +
+
+
+ ); +} + +// Main chat component +export default function ChatWithCustomRenderer() { + const { messages, sendMessage, isLoading, error } = useChat({ + api: '/api/chat', + }); + const [input, setInput] = useState(''); + + const handleSubmit = (e: FormEvent) => { + e.preventDefault(); + if (!input.trim()) return; + + sendMessage({ content: input }); + setInput(''); + }; + + return ( +
+ {/* Header */} +
+

Custom Message Renderer

+

+ With markdown, syntax highlighting, and copy buttons +

+
+ + {/* Messages */} +
+ {messages.length === 0 && ( +
+
+
+

+ Try asking for code examples +

+

+ Messages will render with markdown and syntax highlighting +

+
+ {[ + 'Write a Python function to sort a list', + 'Explain React hooks with code examples', + 'Show me a TypeScript interface example', + ].map((suggestion, idx) => ( + + ))} +
+
+
+ )} + + {messages.map((message) => ( + + ))} + + {isLoading && ( +
+
+
+
+
+
+
+
+
+ )} +
+ + {/* Error */} + {error && ( +
+ Error: {error.message} +
+ )} + + {/* Input */} +
+
+ setInput(e.target.value)} + placeholder="Ask for code examples..." + disabled={isLoading} + className="flex-1 p-3 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" + /> + +
+
+
+ ); +} + +// ============================================================================ +// Simpler Version (without react-markdown) +// ============================================================================ + +/* +// Simple markdown parsing without external dependencies +function SimpleMarkdownRenderer({ content }: { content: string }) { + // Basic markdown parsing + const parseMarkdown = (text: string) => { + // Code blocks + text = text.replace(/```(\w+)?\n([\s\S]*?)```/g, (_, lang, code) => { + return `
${code}
`; + }); + + // Inline code + text = text.replace(/`([^`]+)`/g, '$1'); + + // Bold + text = text.replace(/\*\*([^*]+)\*\*/g, '$1'); + + // Italic + text = text.replace(/\*([^*]+)\*/g, '$1'); + + // Line breaks + text = text.replace(/\n/g, '
'); + + return text; + }; + + return ( +
+ ); +} +*/ diff --git a/templates/message-persistence.tsx b/templates/message-persistence.tsx new file mode 100644 index 0000000..c87db35 --- /dev/null +++ b/templates/message-persistence.tsx @@ -0,0 +1,293 @@ +/** + * AI SDK UI - Message Persistence + * + * Demonstrates: + * - Saving chat history to localStorage + * - Loading previous conversations + * - Multiple chat sessions + * - Clear history functionality + * + * Features: + * - Auto-save on message changes + * - Persistent chat IDs + * - Load on mount + * - Clear/delete chats + * + * Usage: + * 1. Copy this component + * 2. Customize storage mechanism (localStorage, database, etc.) + * 3. Add chat history UI if needed + */ + +'use client'; + +import { useChat } from 'ai/react'; +import { useState, FormEvent, useEffect } from 'react'; +import type { Message } from 'ai'; + +// Storage key prefix +const STORAGE_KEY_PREFIX = 'ai-chat-'; + +// Helper functions for localStorage +const saveMessages = (chatId: string, messages: Message[]) => { + try { + localStorage.setItem( + `${STORAGE_KEY_PREFIX}${chatId}`, + JSON.stringify(messages) + ); + } catch (error) { + console.error('Failed to save messages:', error); + } +}; + +const loadMessages = (chatId: string): Message[] => { + try { + const stored = localStorage.getItem(`${STORAGE_KEY_PREFIX}${chatId}`); + return stored ? JSON.parse(stored) : []; + } catch (error) { + console.error('Failed to load messages:', error); + return []; + } +}; + +const clearMessages = (chatId: string) => { + try { + localStorage.removeItem(`${STORAGE_KEY_PREFIX}${chatId}`); + } catch (error) { + console.error('Failed to clear messages:', error); + } +}; + +const listChats = (): string[] => { + const chats: string[] = []; + for (let i = 0; i < localStorage.length; i++) { + const key = localStorage.key(i); + if (key?.startsWith(STORAGE_KEY_PREFIX)) { + chats.push(key.replace(STORAGE_KEY_PREFIX, '')); + } + } + return chats; +}; + +export default function PersistentChat() { + // Generate or use existing chat ID + const [chatId, setChatId] = useState(''); + const [isLoaded, setIsLoaded] = useState(false); + + // Initialize chat ID + useEffect(() => { + // Try to load from URL params or generate new + const params = new URLSearchParams(window.location.search); + const urlChatId = params.get('chatId'); + + if (urlChatId) { + setChatId(urlChatId); + } else { + // Generate new chat ID + const newChatId = `chat-${Date.now()}`; + setChatId(newChatId); + + // Update URL + const url = new URL(window.location.href); + url.searchParams.set('chatId', newChatId); + window.history.replaceState({}, '', url.toString()); + } + + setIsLoaded(true); + }, []); + + const { messages, setMessages, sendMessage, isLoading, error } = useChat({ + api: '/api/chat', + id: chatId, + initialMessages: isLoaded ? loadMessages(chatId) : [], + }); + + const [input, setInput] = useState(''); + + // Save messages whenever they change + useEffect(() => { + if (chatId && messages.length > 0) { + saveMessages(chatId, messages); + } + }, [messages, chatId]); + + const handleSubmit = (e: FormEvent) => { + e.preventDefault(); + if (!input.trim()) return; + + sendMessage({ content: input }); + setInput(''); + }; + + const handleClearChat = () => { + if (confirm('Are you sure you want to clear this chat?')) { + clearMessages(chatId); + setMessages([]); + } + }; + + const handleNewChat = () => { + const newChatId = `chat-${Date.now()}`; + setChatId(newChatId); + setMessages([]); + + // Update URL + const url = new URL(window.location.href); + url.searchParams.set('chatId', newChatId); + window.history.pushState({}, '', url.toString()); + }; + + if (!isLoaded) { + return
Loading...
; + } + + return ( +
+ {/* Header */} +
+
+

Persistent Chat

+

+ Chat ID: {chatId} +

+
+
+ + {messages.length > 0 && ( + + )} +
+
+ + {/* Messages */} +
+ {messages.length === 0 ? ( +
+
+
💾
+

+ Your conversation is saved +

+

+ All messages are automatically saved to localStorage +

+
+
+ ) : ( +
+ {messages.map((message) => ( +
+
+ {message.content} +
+
+ ))} + + {isLoading && ( +
+
+
+
+
+
+
+
+
+ )} +
+ )} +
+ + {/* Error */} + {error && ( +
+ Error: {error.message} +
+ )} + + {/* Input */} +
+
+
+ setInput(e.target.value)} + placeholder="Type a message..." + disabled={isLoading} + className="flex-1 p-3 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100" + /> + +
+
+ {messages.length > 0 && ( + <>Last saved: {new Date().toLocaleTimeString()} + )} +
+
+
+
+ ); +} + +// ============================================================================ +// Database Persistence Example (Supabase) +// ============================================================================ + +/* +import { createClient } from '@supabase/supabase-js'; + +const supabase = createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! +); + +const saveMessagesToDB = async (chatId: string, messages: Message[]) => { + const { error } = await supabase + .from('chat_messages') + .upsert({ chat_id: chatId, messages, updated_at: new Date() }); + + if (error) console.error('Save error:', error); +}; + +const loadMessagesFromDB = async (chatId: string): Promise => { + const { data, error } = await supabase + .from('chat_messages') + .select('messages') + .eq('chat_id', chatId) + .single(); + + if (error) { + console.error('Load error:', error); + return []; + } + + return data?.messages || []; +}; +*/ diff --git a/templates/nextjs-api-route.ts b/templates/nextjs-api-route.ts new file mode 100644 index 0000000..f2e5780 --- /dev/null +++ b/templates/nextjs-api-route.ts @@ -0,0 +1,248 @@ +/** + * Next.js API Routes for useChat + * + * Shows both App Router and Pages Router patterns. + * + * Key Difference: + * - App Router: Use toDataStreamResponse() + * - Pages Router: Use pipeDataStreamToResponse() + * + * This file includes both patterns for reference. + */ + +// ============================================================================ +// APP ROUTER (Next.js 13+) +// ============================================================================ +// Location: app/api/chat/route.ts + +import { streamText } from 'ai'; +import { openai } from '@ai-sdk/openai'; + +export async function POST(req: Request) { + const { messages } = await req.json(); + + const result = streamText({ + model: openai('gpt-4-turbo'), + messages, + system: 'You are a helpful AI assistant.', + maxOutputTokens: 1000, + temperature: 0.7, + }); + + // App Router: Use toDataStreamResponse() + return result.toDataStreamResponse(); +} + +// ============================================================================ +// PAGES ROUTER (Next.js 12 and earlier) +// ============================================================================ +// Location: pages/api/chat.ts + +/* +import type { NextApiRequest, NextApiResponse } from 'next'; +import { streamText } from 'ai'; +import { openai } from '@ai-sdk/openai'; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + if (req.method !== 'POST') { + return res.status(405).json({ error: 'Method not allowed' }); + } + + const { messages } = req.body; + + const result = streamText({ + model: openai('gpt-4-turbo'), + messages, + system: 'You are a helpful AI assistant.', + }); + + // Pages Router: Use pipeDataStreamToResponse() + return result.pipeDataStreamToResponse(res); +} +*/ + +// ============================================================================ +// WITH ANTHROPIC (Claude) +// ============================================================================ + +/* +import { anthropic } from '@ai-sdk/anthropic'; + +export async function POST(req: Request) { + const { messages } = await req.json(); + + const result = streamText({ + model: anthropic('claude-3-5-sonnet-20241022'), + messages, + }); + + return result.toDataStreamResponse(); +} +*/ + +// ============================================================================ +// WITH GOOGLE (Gemini) +// ============================================================================ + +/* +import { google } from '@ai-sdk/google'; + +export async function POST(req: Request) { + const { messages } = await req.json(); + + const result = streamText({ + model: google('gemini-1.5-pro'), + messages, + }); + + return result.toDataStreamResponse(); +} +*/ + +// ============================================================================ +// WITH CLOUDFLARE WORKERS AI +// ============================================================================ + +/* +// Requires: workers-ai-provider +import { createWorkersAI } from 'workers-ai-provider'; + +// For Cloudflare Workers (not Next.js): +export default { + async fetch(request, env) { + const { messages } = await request.json(); + const workersai = createWorkersAI({ binding: env.AI }); + + const result = streamText({ + model: workersai('@cf/meta/llama-3.1-8b-instruct'), + messages, + }); + + return result.toDataStreamResponse(); + }, +}; +*/ + +// ============================================================================ +// WITH ERROR HANDLING +// ============================================================================ + +/* +import { streamText } from 'ai'; +import { openai } from '@ai-sdk/openai'; + +export async function POST(req: Request) { + try { + const { messages } = await req.json(); + + const result = streamText({ + model: openai('gpt-4-turbo'), + messages, + }); + + return result.toDataStreamResponse(); + } catch (error) { + console.error('API error:', error); + return new Response( + JSON.stringify({ + error: 'An error occurred while processing your request.', + }), + { + status: 500, + headers: { 'Content-Type': 'application/json' }, + } + ); + } +} +*/ + +// ============================================================================ +// WITH TOOLS +// ============================================================================ + +/* +import { streamText, tool } from 'ai'; +import { openai } from '@ai-sdk/openai'; +import { z } from 'zod'; + +export async function POST(req: Request) { + const { messages } = await req.json(); + + const result = streamText({ + model: openai('gpt-4-turbo'), + messages, + tools: { + getWeather: tool({ + description: 'Get the current weather for a location', + inputSchema: z.object({ + location: z.string().describe('The city name'), + }), + execute: async ({ location }) => { + // Simulated weather API call + return { + location, + temperature: 72, + condition: 'sunny', + }; + }, + }), + }, + }); + + return result.toDataStreamResponse(); +} +*/ + +// ============================================================================ +// FOR useCompletion +// ============================================================================ +// Location: app/api/completion/route.ts + +/* +import { streamText } from 'ai'; +import { openai } from '@ai-sdk/openai'; + +export async function POST(req: Request) { + const { prompt } = await req.json(); + + const result = streamText({ + model: openai('gpt-3.5-turbo'), + prompt, + maxOutputTokens: 500, + }); + + return result.toDataStreamResponse(); +} +*/ + +// ============================================================================ +// FOR useObject +// ============================================================================ +// Location: app/api/recipe/route.ts + +/* +import { streamObject } from 'ai'; +import { openai } from '@ai-sdk/openai'; +import { z } from 'zod'; + +export async function POST(req: Request) { + const { prompt } = await req.json(); + + const result = streamObject({ + model: openai('gpt-4'), + schema: z.object({ + recipe: z.object({ + name: z.string(), + ingredients: z.array(z.string()), + instructions: z.array(z.string()), + }), + }), + prompt: `Generate a recipe for ${prompt}`, + }); + + return result.toTextStreamResponse(); +} +*/ diff --git a/templates/nextjs-chat-app-router.tsx b/templates/nextjs-chat-app-router.tsx new file mode 100644 index 0000000..9af1469 --- /dev/null +++ b/templates/nextjs-chat-app-router.tsx @@ -0,0 +1,238 @@ +/** + * Next.js App Router - Complete Chat Example + * + * Complete production-ready chat interface for Next.js App Router. + * + * Features: + * - v5 useChat with manual input management + * - Auto-scroll to bottom + * - Loading states & error handling + * - Stop generation button + * - Responsive design + * - Keyboard shortcuts (Enter to send, Cmd+K to clear) + * + * Directory structure: + * app/ + * ├── chat/ + * │ └── page.tsx (this file) + * └── api/ + * └── chat/ + * └── route.ts (see nextjs-api-route.ts) + * + * Usage: + * 1. Copy to app/chat/page.tsx + * 2. Create API route (see nextjs-api-route.ts) + * 3. Navigate to /chat + */ + +'use client'; + +import { useChat } from 'ai/react'; +import { useState, FormEvent, useRef, useEffect } from 'react'; + +export default function ChatPage() { + const { messages, sendMessage, isLoading, error, stop, reload } = useChat({ + api: '/api/chat', + onError: (error) => { + console.error('Chat error:', error); + }, + }); + + const [input, setInput] = useState(''); + const messagesEndRef = useRef(null); + const inputRef = useRef(null); + + // Auto-scroll to bottom when new messages arrive + useEffect(() => { + messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); + }, [messages]); + + // Focus input on mount + useEffect(() => { + inputRef.current?.focus(); + }, []); + + const handleSubmit = (e: FormEvent) => { + e.preventDefault(); + if (!input.trim() || isLoading) return; + + sendMessage({ content: input }); + setInput(''); + }; + + // Keyboard shortcuts + const handleKeyDown = (e: React.KeyboardEvent) => { + // Cmd+K or Ctrl+K to clear (focus input) + if ((e.metaKey || e.ctrlKey) && e.key === 'k') { + e.preventDefault(); + inputRef.current?.focus(); + } + }; + + return ( +
+ {/* Header */} +
+
+

AI Assistant

+

+ {messages.length > 0 + ? `${messages.length} message${messages.length === 1 ? '' : 's'}` + : 'Start a conversation'} +

+
+ {messages.length > 0 && !isLoading && ( + + )} +
+ + {/* Messages */} +
+ {messages.length === 0 ? ( + // Empty state +
+
+
💬
+
+

+ Start a conversation +

+

+ Ask me anything or try one of these: +

+
+
+ {[ + 'Explain quantum computing', + 'Write a haiku about coding', + 'Plan a trip to Tokyo', + ].map((suggestion, idx) => ( + + ))} +
+
+
+ ) : ( + // Messages list +
+ {messages.map((message, idx) => ( +
+
+ {/* Role label (only for assistant on first message) */} + {message.role === 'assistant' && idx === 1 && ( +
+ AI Assistant +
+ )} + + {/* Message content */} +
+ {message.content} +
+
+
+ ))} + + {/* Loading indicator */} + {isLoading && ( +
+
+
+
+
+
+
+
+ Thinking... +
+
+
+ )} + +
+
+ )} +
+ + {/* Error banner */} + {error && ( +
+
+
+ ⚠️ +
+
Error
+
{error.message}
+
+
+ +
+
+ )} + + {/* Input */} +
+
+
+ setInput(e.target.value)} + placeholder="Type a message..." + disabled={isLoading} + className="flex-1 p-3 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100" + /> + {isLoading ? ( + + ) : ( + + )} +
+
+ Press Enter to send • Cmd+K to focus input +
+
+
+
+ ); +} diff --git a/templates/nextjs-chat-pages-router.tsx b/templates/nextjs-chat-pages-router.tsx new file mode 100644 index 0000000..9bdade9 --- /dev/null +++ b/templates/nextjs-chat-pages-router.tsx @@ -0,0 +1,162 @@ +/** + * Next.js Pages Router - Complete Chat Example + * + * Complete production-ready chat interface for Next.js Pages Router. + * + * Features: + * - v5 useChat with manual input management + * - Auto-scroll to bottom + * - Loading states & error handling + * - Stop generation button + * - Responsive design + * + * Directory structure: + * pages/ + * ├── chat.tsx (this file) + * └── api/ + * └── chat.ts (see nextjs-api-route.ts) + * + * Usage: + * 1. Copy to pages/chat.tsx + * 2. Create API route at pages/api/chat.ts (see nextjs-api-route.ts) + * 3. Navigate to /chat + */ + +import { useChat } from 'ai/react'; +import { useState, FormEvent, useRef, useEffect } from 'react'; +import Head from 'next/head'; + +export default function ChatPage() { + const { messages, sendMessage, isLoading, error, stop } = useChat({ + api: '/api/chat', + }); + + const [input, setInput] = useState(''); + const messagesEndRef = useRef(null); + + // Auto-scroll to bottom + useEffect(() => { + messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); + }, [messages]); + + const handleSubmit = (e: FormEvent) => { + e.preventDefault(); + if (!input.trim() || isLoading) return; + + sendMessage({ content: input }); + setInput(''); + }; + + return ( + <> + + AI Chat + + + +
+ {/* Header */} +
+

AI Chat

+

+ Powered by AI SDK v5 (Pages Router) +

+
+ + {/* Messages */} +
+ {messages.length === 0 ? ( + // Empty state +
+
+
💬
+

+ Start a conversation +

+

+ Type a message below to begin +

+
+
+ ) : ( + // Messages list +
+ {messages.map((message) => ( +
+
+
{message.content}
+
+
+ ))} + + {isLoading && ( +
+
+
+
+
+
+
+
+
+ )} + +
+
+ )} +
+ + {/* Error */} + {error && ( +
+
+ Error: {error.message} +
+
+ )} + + {/* Input */} +
+
+ setInput(e.target.value)} + placeholder="Type a message..." + disabled={isLoading} + className="flex-1 p-3 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100" + /> + {isLoading ? ( + + ) : ( + + )} +
+
+
+ + ); +} diff --git a/templates/package.json b/templates/package.json new file mode 100644 index 0000000..b746c6e --- /dev/null +++ b/templates/package.json @@ -0,0 +1,45 @@ +{ + "name": "ai-sdk-ui-app", + "version": "0.1.0", + "private": true, + "description": "AI SDK UI application with React hooks for chat, completion, and streaming", + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint", + "type-check": "tsc --noEmit" + }, + "dependencies": { + "ai": "^5.0.95", + "@ai-sdk/openai": "^2.0.68", + "@ai-sdk/anthropic": "^2.0.45", + "@ai-sdk/google": "^2.0.38", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "next": "^14.0.0", + "zod": "^3.23.8" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", + "typescript": "^5.3.3", + "tailwindcss": "^4.0.0", + "@tailwindcss/vite": "^4.0.0", + "eslint": "^8.0.0", + "eslint-config-next": "^14.0.0" + }, + "optionalDependencies": { + "react-markdown": "^9.0.0", + "react-syntax-highlighter": "^15.5.0", + "@types/react-syntax-highlighter": "^15.5.0", + "workers-ai-provider": "^2.0.0" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=9.0.0" + }, + "packageManager": "npm@10.0.0", + "comment": "Engine versions specify minimum supported versions for compatibility" +} diff --git a/templates/use-chat-attachments.tsx b/templates/use-chat-attachments.tsx new file mode 100644 index 0000000..578edb1 --- /dev/null +++ b/templates/use-chat-attachments.tsx @@ -0,0 +1,230 @@ +/** + * AI SDK UI - Chat with File Attachments + * + * Demonstrates: + * - File upload with experimental_attachments + * - Image preview + * - Multiple file support + * - Sending files with messages + * + * Requires: + * - API route that handles multimodal inputs (GPT-4 Vision, Claude 3.5, etc.) + * - experimental_attachments feature (v5) + * + * Usage: + * 1. Set up API route with vision model + * 2. Copy this component + * 3. Customize file handling as needed + */ + +'use client'; + +import { useChat } from 'ai/react'; +import { useState, FormEvent } from 'react'; + +export default function ChatWithAttachments() { + const { messages, sendMessage, isLoading, error } = useChat({ + api: '/api/chat', + }); + const [input, setInput] = useState(''); + const [files, setFiles] = useState(null); + const [previewUrls, setPreviewUrls] = useState([]); + + // Handle file selection + const handleFileChange = (e: React.ChangeEvent) => { + const selectedFiles = e.target.files; + setFiles(selectedFiles); + + if (selectedFiles) { + // Create preview URLs + const urls = Array.from(selectedFiles).map((file) => + URL.createObjectURL(file) + ); + setPreviewUrls(urls); + } else { + setPreviewUrls([]); + } + }; + + // Handle form submission + const handleSubmit = (e: FormEvent) => { + e.preventDefault(); + if (!input.trim() && !files) return; + + sendMessage({ + content: input || 'Please analyze these images', + experimental_attachments: files + ? Array.from(files).map((file) => ({ + name: file.name, + contentType: file.type, + url: URL.createObjectURL(file), + })) + : undefined, + }); + + // Clean up + setInput(''); + setFiles(null); + previewUrls.forEach((url) => URL.revokeObjectURL(url)); + setPreviewUrls([]); + }; + + // Remove file + const removeFile = (index: number) => { + if (!files) return; + + const newFiles = Array.from(files).filter((_, i) => i !== index); + const dataTransfer = new DataTransfer(); + newFiles.forEach((file) => dataTransfer.items.add(file)); + + setFiles(dataTransfer.files); + + // Update preview URLs + URL.revokeObjectURL(previewUrls[index]); + setPreviewUrls(previewUrls.filter((_, i) => i !== index)); + }; + + return ( +
+ {/* Header */} +
+

AI Chat with File Attachments

+

+ Upload images and ask questions about them +

+
+ + {/* Messages */} +
+ {messages.map((message) => ( +
+ {/* Text content */} +
+
+ {message.content} +
+
+ + {/* Attachments */} + {message.experimental_attachments && + message.experimental_attachments.length > 0 && ( +
+
+ {message.experimental_attachments.map( + (attachment, idx) => ( +
+ {attachment.contentType?.startsWith('image/') ? ( + {attachment.name} + ) : ( +
+ {attachment.name} +
+ )} +
+ ) + )} +
+
+ )} +
+ ))} + + {isLoading && ( +
+
Processing...
+
+ )} +
+ + {/* Error */} + {error && ( +
+ Error: {error.message} +
+ )} + + {/* File preview */} + {previewUrls.length > 0 && ( +
+

+ Selected files ({previewUrls.length}): +

+
+ {previewUrls.map((url, idx) => ( +
+ {`Preview + +
+ ))} +
+
+ )} + + {/* Input */} +
+
+ {/* File input */} + + + {/* Text input */} +
+ setInput(e.target.value)} + placeholder="Ask a question about the images..." + disabled={isLoading} + className="flex-1 p-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" + /> + +
+
+
+
+ ); +} diff --git a/templates/use-chat-basic.tsx b/templates/use-chat-basic.tsx new file mode 100644 index 0000000..f1bb74f --- /dev/null +++ b/templates/use-chat-basic.tsx @@ -0,0 +1,133 @@ +/** + * AI SDK UI - Basic Chat Component (v5) + * + * Demonstrates: + * - useChat hook with v5 manual input management + * - Streaming chat messages + * - Loading states + * - Error handling + * - Auto-scroll to latest message + * + * CRITICAL v5 Change: useChat NO LONGER manages input state! + * You must manually manage input with useState. + * + * Usage: + * 1. Copy this component to your app + * 2. Create API route (see nextjs-api-route.ts) + * 3. Customize styling as needed + */ + +'use client'; + +import { useChat } from 'ai/react'; +import { useState, FormEvent, useRef, useEffect } from 'react'; + +export default function ChatBasic() { + // useChat hook - v5 style + const { messages, sendMessage, isLoading, error, stop } = useChat({ + api: '/api/chat', + }); + + // Manual input management (v5 requires this!) + const [input, setInput] = useState(''); + + // Auto-scroll to bottom + const messagesEndRef = useRef(null); + + useEffect(() => { + messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); + }, [messages]); + + // Handle form submission + const handleSubmit = (e: FormEvent) => { + e.preventDefault(); + if (!input.trim()) return; + + // v5: Use sendMessage instead of append + sendMessage({ content: input }); + setInput(''); + }; + + return ( +
+ {/* Header */} +
+

AI Chat

+
+ + {/* Messages */} +
+ {messages.map((message) => ( +
+
+ {message.content} +
+
+ ))} + + {/* Loading indicator */} + {isLoading && ( +
+
+
+
+
+
+
+
+
+ )} + +
+
+ + {/* Error message */} + {error && ( +
+ Error: {error.message} +
+ )} + + {/* Input */} +
+
+ setInput(e.target.value)} + placeholder="Type a message..." + disabled={isLoading} + className="flex-1 p-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100" + /> + {isLoading ? ( + + ) : ( + + )} +
+
+
+ ); +} diff --git a/templates/use-chat-tools.tsx b/templates/use-chat-tools.tsx new file mode 100644 index 0000000..11fc68e --- /dev/null +++ b/templates/use-chat-tools.tsx @@ -0,0 +1,166 @@ +/** + * AI SDK UI - Chat with Tool Calling + * + * Demonstrates: + * - Displaying tool calls in UI + * - Rendering tool arguments and results + * - Handling multi-step tool invocations + * - Visual distinction between messages and tool calls + * + * Requires: + * - API route with tools configured (see ai-sdk-core skill) + * - Backend using `tool()` helper + * + * Usage: + * 1. Set up API route with tools + * 2. Copy this component + * 3. Customize tool rendering as needed + */ + +'use client'; + +import { useChat } from 'ai/react'; +import { useState, FormEvent } from 'react'; + +export default function ChatWithTools() { + const { messages, sendMessage, isLoading, error } = useChat({ + api: '/api/chat', + }); + const [input, setInput] = useState(''); + + const handleSubmit = (e: FormEvent) => { + e.preventDefault(); + if (!input.trim()) return; + + sendMessage({ content: input }); + setInput(''); + }; + + return ( +
+ {/* Header */} +
+

AI Chat with Tools

+

+ Ask about weather, calculations, or search queries +

+
+ + {/* Messages */} +
+ {messages.map((message) => ( +
+ {/* Text content */} + {message.content && ( +
+
+ {message.content} +
+
+ )} + + {/* Tool invocations */} + {message.toolInvocations && message.toolInvocations.length > 0 && ( +
+
+ {message.toolInvocations.map((tool, idx) => ( +
+ {/* Tool name */} +
+
+ + Tool: {tool.toolName} + +
+ + {/* Tool state */} + {tool.state === 'call' && ( +
+ Calling with: +
+                            {JSON.stringify(tool.args, null, 2)}
+                          
+
+ )} + + {tool.state === 'result' && ( +
+ Arguments: +
+                            {JSON.stringify(tool.args, null, 2)}
+                          
+ Result: +
+                            {JSON.stringify(tool.result, null, 2)}
+                          
+
+ )} + + {tool.state === 'partial-call' && ( +
+ Preparing arguments... +
+ )} +
+ ))} +
+
+ )} +
+ ))} + + {isLoading && ( +
+
+
+
+
+
+
+
+
+ )} +
+ + {/* Error */} + {error && ( +
+ Error: {error.message} +
+ )} + + {/* Input */} +
+
+ setInput(e.target.value)} + placeholder="Try: 'What's the weather in San Francisco?'" + disabled={isLoading} + className="flex-1 p-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" + /> + +
+
+
+ ); +} diff --git a/templates/use-completion-basic.tsx b/templates/use-completion-basic.tsx new file mode 100644 index 0000000..d19939b --- /dev/null +++ b/templates/use-completion-basic.tsx @@ -0,0 +1,170 @@ +/** + * AI SDK UI - Basic Text Completion + * + * Demonstrates: + * - useCompletion hook for text generation + * - Streaming text completion + * - Loading states + * - Stop generation + * - Clear completion + * + * Use cases: + * - Text generation (blog posts, summaries, etc.) + * - Content expansion + * - Writing assistance + * + * Usage: + * 1. Copy this component to your app + * 2. Create /api/completion route (see references) + * 3. Customize UI as needed + */ + +'use client'; + +import { useCompletion } from 'ai/react'; +import { useState, FormEvent } from 'react'; + +export default function CompletionBasic() { + const { + completion, + complete, + isLoading, + error, + stop, + setCompletion, + } = useCompletion({ + api: '/api/completion', + }); + + const [input, setInput] = useState(''); + + const handleSubmit = (e: FormEvent) => { + e.preventDefault(); + if (!input.trim()) return; + + complete(input); + setInput(''); + }; + + const handleClear = () => { + setCompletion(''); + }; + + return ( +
+ {/* Header */} +
+

AI Text Completion

+

+ Enter a prompt to generate text with AI +

+
+ + {/* Input form */} +
+
+ +