331 lines
6.9 KiB
Markdown
331 lines
6.9 KiB
Markdown
# Les 14: AI Chat Interface & Streaming
|
|
|
|
---
|
|
|
|
## Hoofdstuk
|
|
**Deel 4: Advanced AI & Deployment** (Les 13-18)
|
|
|
|
## Beschrijving
|
|
Bouwen van professionele chat interfaces met streaming responses. Message rendering, markdown support, error handling, loading states, en UX patterns voor AI-powered features.
|
|
|
|
---
|
|
|
|
## Te Behandelen (~45 min)
|
|
|
|
- Chat UI patterns en best practices
|
|
- useChat hook deep dive (state, loading, error)
|
|
- Streaming response handling en real-time updates
|
|
- Message rendering strategies en optimizations
|
|
- Markdown rendering in chat messages
|
|
- Error handling en error boundaries
|
|
- Loading states en skeleton loaders
|
|
- User input validation and sanitization
|
|
- Accessibility in chat interfaces (ARIA labels)
|
|
- Message persistence (localStorage of database)
|
|
- Performance optimization
|
|
|
|
---
|
|
|
|
### useChat Hook Deep Dive
|
|
|
|
**State management met useChat:**
|
|
|
|
```typescript
|
|
'use client'
|
|
import { useChat } from 'ai/react'
|
|
|
|
export function ChatComponent() {
|
|
const {
|
|
messages, // All messages in conversation
|
|
input, // Current input text
|
|
handleInputChange, // Update input
|
|
handleSubmit, // Send message
|
|
isLoading, // Is AI responding?
|
|
error, // Any errors?
|
|
} = useChat({
|
|
api: '/api/chat',
|
|
initialMessages: [], // Optional: pre-load messages
|
|
})
|
|
|
|
return (
|
|
<>
|
|
<div>
|
|
{messages.map((msg) => (
|
|
<div key={msg.id}>
|
|
<strong>{msg.role}:</strong> {msg.content}
|
|
</div>
|
|
))}
|
|
</div>
|
|
{isLoading && <div>AI is thinking...</div>}
|
|
{error && <div>Error: {error.message}</div>}
|
|
<form onSubmit={handleSubmit}>
|
|
<input
|
|
value={input}
|
|
onChange={handleInputChange}
|
|
disabled={isLoading}
|
|
/>
|
|
<button type="submit" disabled={isLoading}>Send</button>
|
|
</form>
|
|
</>
|
|
)
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Message Rendering Patterns
|
|
|
|
**Basic pattern:**
|
|
```typescript
|
|
<div className="space-y-4">
|
|
{messages.map((msg) => (
|
|
<div
|
|
key={msg.id}
|
|
className={msg.role === 'user' ? 'ml-auto' : 'mr-auto'}
|
|
>
|
|
<div className="bg-gray-200 p-3 rounded">
|
|
{msg.content}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
```
|
|
|
|
**With markdown rendering:**
|
|
```typescript
|
|
import ReactMarkdown from 'react-markdown'
|
|
|
|
<div className="bg-gray-200 p-3 rounded">
|
|
<ReactMarkdown>{msg.content}</ReactMarkdown>
|
|
</div>
|
|
```
|
|
|
|
**With message types:**
|
|
```typescript
|
|
{messages.map((msg) => (
|
|
<div key={msg.id} className={msg.role === 'user' ? 'user-message' : 'ai-message'}>
|
|
{msg.content}
|
|
</div>
|
|
))}
|
|
```
|
|
|
|
---
|
|
|
|
### Error Handling
|
|
|
|
**Structured error handling:**
|
|
|
|
```typescript
|
|
try {
|
|
const response = await fetch('/api/chat', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ messages }),
|
|
})
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`API error: ${response.status}`)
|
|
}
|
|
|
|
// Handle streaming...
|
|
} catch (error) {
|
|
console.error('Chat error:', error)
|
|
setError({
|
|
message: 'Failed to send message',
|
|
code: error instanceof Error ? error.message : 'unknown'
|
|
})
|
|
}
|
|
```
|
|
|
|
**Error boundary:**
|
|
```typescript
|
|
<ErrorBoundary fallback={<div>Chat error occurred</div>}>
|
|
<ChatComponent />
|
|
</ErrorBoundary>
|
|
```
|
|
|
|
---
|
|
|
|
### Loading States
|
|
|
|
**Skeleton loader:**
|
|
```typescript
|
|
function MessageSkeleton() {
|
|
return (
|
|
<div className="animate-pulse">
|
|
<div className="bg-gray-300 h-4 rounded w-48 mb-2" />
|
|
<div className="bg-gray-300 h-4 rounded w-64" />
|
|
</div>
|
|
)
|
|
}
|
|
|
|
{isLoading && <MessageSkeleton />}
|
|
```
|
|
|
|
---
|
|
|
|
### Input Validation
|
|
|
|
**Validate before sending:**
|
|
|
|
```typescript
|
|
function handleSubmit(e: React.FormEvent) {
|
|
e.preventDefault()
|
|
|
|
// Trim whitespace
|
|
const trimmedInput = input.trim()
|
|
|
|
// Validate non-empty
|
|
if (!trimmedInput) {
|
|
setError('Message cannot be empty')
|
|
return
|
|
}
|
|
|
|
// Validate length
|
|
if (trimmedInput.length > 1000) {
|
|
setError('Message too long (max 1000 chars)')
|
|
return
|
|
}
|
|
|
|
// Send message
|
|
handleSubmit(e)
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Message Persistence
|
|
|
|
**Save to localStorage:**
|
|
|
|
```typescript
|
|
const [messages, setMessages] = useState(() => {
|
|
const saved = localStorage.getItem('chat_history')
|
|
return saved ? JSON.parse(saved) : []
|
|
})
|
|
|
|
// Save whenever messages change
|
|
useEffect(() => {
|
|
localStorage.setItem('chat_history', JSON.stringify(messages))
|
|
}, [messages])
|
|
```
|
|
|
|
**Save to database:**
|
|
```typescript
|
|
const saveMessage = async (message: Message) => {
|
|
await fetch('/api/messages', {
|
|
method: 'POST',
|
|
body: JSON.stringify({
|
|
content: message.content,
|
|
role: message.role,
|
|
userId: user.id,
|
|
}),
|
|
})
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Tools
|
|
- Vercel AI SDK
|
|
- React Markdown
|
|
- Cursor
|
|
- TypeScript
|
|
|
|
---
|
|
|
|
## Lesopdracht (2 uur, klassikaal)
|
|
|
|
### Build Professional Chat Interface
|
|
|
|
**Groepsdiscussie (15 min):**
|
|
Bespreek klassikaal de Vercel AI SDK ervaringen uit Les 13 - welke tool calling patterns werkten goed?
|
|
|
|
**Deel 1: Chat Component (45 min)**
|
|
|
|
Build components/ChatInterface.tsx:
|
|
1. Use useChat hook
|
|
2. Render messages with proper styling
|
|
3. User vs AI message styling
|
|
4. Input form with validation
|
|
5. Tailwind + shadcn/ui components
|
|
|
|
**Deel 2: Markdown & Error Handling (30 min)**
|
|
|
|
1. Install react-markdown: `npm install react-markdown`
|
|
2. Render AI responses with markdown
|
|
3. Add error boundary
|
|
4. Show error messages to user
|
|
5. Proper loading states
|
|
|
|
**Deel 3: UX Improvements (30 min)**
|
|
|
|
1. Auto-scroll to latest message
|
|
2. Disable input while loading
|
|
3. Show message count/token usage
|
|
4. Add clear chat history button
|
|
5. Save messages to localStorage
|
|
|
|
**Deel 4: Testing (15 min)**
|
|
|
|
Test chat interface locally with various inputs and error scenarios.
|
|
|
|
### Deliverable
|
|
- Werkende chat interface component
|
|
- Markdown rendering working
|
|
- Error handling implemented
|
|
- LocalStorage persistence
|
|
- GitHub commit with chat UI
|
|
|
|
---
|
|
|
|
## Huiswerk (2 uur)
|
|
|
|
### Integrate Chat into Your Project
|
|
|
|
**Deel 1: Project Integration (1 uur)**
|
|
|
|
1. Add chat component to your app
|
|
2. Connect to your API route with tools
|
|
3. Style to match your design
|
|
4. Test with actual tools/integrations
|
|
5. Fix any bugs
|
|
|
|
**Deel 2: Enhanced Features (30 min)**
|
|
|
|
Add one of these:
|
|
- Message copy button
|
|
- Regenerate response option
|
|
- Clear history confirmation
|
|
- Export chat history
|
|
- Message timestamps
|
|
|
|
**Deel 3: Performance & Polish (30 min)**
|
|
|
|
1. Optimize re-renders (useMemo, useCallback)
|
|
2. Virtual scrolling for long chats
|
|
3. Better accessibility (keyboard nav)
|
|
4. Mobile responsive tweaks
|
|
5. Update docs/AI-DECISIONS.md
|
|
|
|
### Deliverable
|
|
- Chat fully integrated in project
|
|
- Enhanced features implemented
|
|
- Performance optimized
|
|
- GitHub commits with improvements
|
|
|
|
---
|
|
|
|
## Leerdoelen
|
|
|
|
Na deze les kan de student:
|
|
- useChat hook volledig begrijpen en gebruiken
|
|
- Professionele chat UI patterns implementeren
|
|
- Markdown rendering in chat messages
|
|
- Error handling en error boundaries toepassen
|
|
- Loading states en skeletons bouwen
|
|
- User input valideren en sanitizen
|
|
- Message persistence (localStorage/DB)
|
|
- Accessibility in chat interfaces verbeteren
|
|
- Performance optimizations toepassen
|
|
- Complete chat feature in Next.js app integreren
|