mirror of
https://github.com/run-llama/chat-ui.git
synced 2026-07-21 11:25:22 -04:00
docs: add docs (#100)
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@llamaindex/chat-ui-docs': patch
|
||||
---
|
||||
|
||||
Add comprehensive MDX documentation covering all components, widgets, hooks, annotations, and examples
|
||||
@@ -0,0 +1,649 @@
|
||||
---
|
||||
title: Annotations
|
||||
description: Working with rich content annotations for multimedia and interactive chat experiences
|
||||
---
|
||||
|
||||
Annotations are the key to creating rich, interactive chat experiences beyond simple text. They allow you to embed images, files, sources, events, and custom content types directly into chat messages.
|
||||
|
||||
## Annotation System Overview
|
||||
|
||||
Annotations are structured data attached to messages that widgets can render as rich content. The system supports both built-in annotation types and custom annotations for domain-specific content.
|
||||
|
||||
### Message Structure with Annotations
|
||||
|
||||
```typescript
|
||||
interface Message {
|
||||
id: string
|
||||
role: 'user' | 'assistant' | 'system'
|
||||
content: string
|
||||
annotations?: JSONValue[]
|
||||
}
|
||||
```
|
||||
|
||||
### Built-in Annotation Types
|
||||
|
||||
The library provides several built-in annotation types:
|
||||
|
||||
- **IMAGE** - Image data with URLs
|
||||
- **DOCUMENT_FILE** - File attachments and metadata
|
||||
- **SOURCES** - Citation and source references
|
||||
- **EVENTS** - Process events and function calls
|
||||
- **AGENT_EVENTS** - Agent-specific events with progress
|
||||
- **ARTIFACT** - Interactive code and document artifacts
|
||||
- **SUGGESTED_QUESTIONS** - Follow-up question suggestions
|
||||
|
||||
## Image Annotations
|
||||
|
||||
Display images within chat messages with zoom and preview functionality.
|
||||
|
||||
### Creating Image Annotations
|
||||
|
||||
```typescript
|
||||
// Server-side: Add image annotation to message
|
||||
const imageAnnotation = {
|
||||
type: 'IMAGE',
|
||||
data: {
|
||||
url: 'https://example.com/chart.png',
|
||||
alt: 'Sales chart for Q4 2024',
|
||||
},
|
||||
}
|
||||
|
||||
return {
|
||||
role: 'assistant',
|
||||
content: 'Here is the sales chart you requested:',
|
||||
annotations: [imageAnnotation],
|
||||
}
|
||||
```
|
||||
|
||||
### Streaming Image Annotations
|
||||
|
||||
```typescript
|
||||
// In your API route
|
||||
const stream = new ReadableStream({
|
||||
start(controller) {
|
||||
// Send text content first
|
||||
controller.enqueue(
|
||||
encoder.encode('0:"Here is the chart you requested:\\n"\\n')
|
||||
)
|
||||
|
||||
// Send image annotation
|
||||
const annotation = {
|
||||
type: 'IMAGE',
|
||||
data: {
|
||||
url: '/api/generated-chart.png',
|
||||
alt: 'Generated sales chart',
|
||||
},
|
||||
}
|
||||
|
||||
controller.enqueue(encoder.encode(`8:${JSON.stringify([annotation])}\\n`))
|
||||
|
||||
controller.close()
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Client-side Rendering
|
||||
|
||||
Images automatically render when using the content components:
|
||||
|
||||
```tsx
|
||||
<ChatMessage message={message}>
|
||||
<ChatMessage.Content>
|
||||
<ChatMessage.Content.Markdown />
|
||||
<ChatMessage.Content.Image />{' '}
|
||||
{/* Automatically renders IMAGE annotations */}
|
||||
</ChatMessage.Content>
|
||||
</ChatMessage>
|
||||
```
|
||||
|
||||
## File Annotations
|
||||
|
||||
Display file attachments with download links and preview capabilities.
|
||||
|
||||
### Document File Annotations
|
||||
|
||||
```typescript
|
||||
const fileAnnotation = {
|
||||
type: 'DOCUMENT_FILE',
|
||||
data: {
|
||||
files: [
|
||||
{
|
||||
id: 'doc1',
|
||||
name: 'quarterly-report.pdf',
|
||||
type: 'application/pdf',
|
||||
url: '/files/quarterly-report.pdf',
|
||||
size: 2048576, // 2MB in bytes
|
||||
metadata: {
|
||||
title: 'Q4 2024 Quarterly Report',
|
||||
author: 'Finance Team',
|
||||
pages: 25,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'doc2',
|
||||
name: 'data-analysis.csv',
|
||||
type: 'text/csv',
|
||||
url: '/files/data-analysis.csv',
|
||||
size: 1024000,
|
||||
metadata: {
|
||||
rows: 5000,
|
||||
columns: 12,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### File Upload Integration
|
||||
|
||||
```typescript
|
||||
// Handle file uploads in your API
|
||||
export async function POST(request: Request) {
|
||||
const formData = await request.formData()
|
||||
const files = formData.getAll('files') as File[]
|
||||
|
||||
const fileAnnotations = await Promise.all(
|
||||
files.map(async file => {
|
||||
const url = await uploadFile(file) // Your upload logic
|
||||
|
||||
return {
|
||||
type: 'DOCUMENT_FILE',
|
||||
data: {
|
||||
files: [
|
||||
{
|
||||
id: generateId(),
|
||||
name: file.name,
|
||||
type: file.type,
|
||||
url,
|
||||
size: file.size,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
return streamResponse(content, fileAnnotations)
|
||||
}
|
||||
```
|
||||
|
||||
## Source Annotations
|
||||
|
||||
Display citations and source references with document grouping.
|
||||
|
||||
### Creating Source Annotations
|
||||
|
||||
```typescript
|
||||
const sourceAnnotation = {
|
||||
type: 'sources',
|
||||
data: {
|
||||
nodes: [
|
||||
{
|
||||
id: 'source1',
|
||||
url: '/documents/research-paper.pdf',
|
||||
metadata: {
|
||||
title: 'Machine Learning in Healthcare',
|
||||
author: 'Dr. Jane Smith',
|
||||
page_number: 15,
|
||||
section: 'Methodology',
|
||||
published_date: '2024-01-15',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'source2',
|
||||
url: '/documents/clinical-study.pdf',
|
||||
metadata: {
|
||||
title: 'Clinical Trial Results',
|
||||
author: 'Medical Research Institute',
|
||||
page_number: 8,
|
||||
figure: 'Figure 3.2',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Citation in Content
|
||||
|
||||
Reference sources directly in your content using citation syntax:
|
||||
|
||||
```typescript
|
||||
const content = `
|
||||
Based on recent research [^1], machine learning shows promising results
|
||||
in medical diagnosis. The clinical trial data [^2] supports these findings
|
||||
with a 95% accuracy rate.
|
||||
|
||||
[^1]: Machine Learning in Healthcare, p. 15
|
||||
[^2]: Clinical Trial Results, Figure 3.2
|
||||
`
|
||||
|
||||
return {
|
||||
role: 'assistant',
|
||||
content,
|
||||
annotations: [sourceAnnotation],
|
||||
}
|
||||
```
|
||||
|
||||
## Event Annotations
|
||||
|
||||
Display process events, function calls, and system activities.
|
||||
|
||||
### Basic Events
|
||||
|
||||
```typescript
|
||||
const eventAnnotation = {
|
||||
type: 'events',
|
||||
data: [
|
||||
{
|
||||
type: 'function_call',
|
||||
name: 'search_database',
|
||||
args: {
|
||||
query: 'machine learning papers',
|
||||
limit: 10,
|
||||
},
|
||||
result: 'Found 8 relevant papers',
|
||||
timestamp: '2024-01-15T10:30:00Z',
|
||||
},
|
||||
{
|
||||
type: 'tool_use',
|
||||
name: 'calculate_statistics',
|
||||
args: {
|
||||
dataset: 'user_engagement',
|
||||
},
|
||||
result: {
|
||||
mean: 4.2,
|
||||
median: 4.1,
|
||||
std_dev: 0.8,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
### Agent Events with Progress
|
||||
|
||||
```typescript
|
||||
const agentEventAnnotation = {
|
||||
type: 'agent_events',
|
||||
data: {
|
||||
agent_name: 'Research Assistant',
|
||||
total_steps: 4,
|
||||
current_step: 2,
|
||||
progress: 50,
|
||||
events: [
|
||||
{
|
||||
step: 1,
|
||||
name: 'Search Documents',
|
||||
status: 'completed',
|
||||
result: 'Found 15 relevant documents',
|
||||
},
|
||||
{
|
||||
step: 2,
|
||||
name: 'Analyze Content',
|
||||
status: 'in_progress',
|
||||
progress: 75,
|
||||
},
|
||||
{
|
||||
step: 3,
|
||||
name: 'Generate Summary',
|
||||
status: 'pending',
|
||||
},
|
||||
{
|
||||
step: 4,
|
||||
name: 'Create Recommendations',
|
||||
status: 'pending',
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Artifact Annotations
|
||||
|
||||
Create interactive code and document artifacts that users can edit.
|
||||
|
||||
### Code Artifacts
|
||||
|
||||
```typescript
|
||||
const codeArtifact = {
|
||||
type: 'artifact',
|
||||
data: {
|
||||
type: 'code',
|
||||
data: {
|
||||
title: 'Data Analysis Script',
|
||||
file_name: 'analyze_data.py',
|
||||
language: 'python',
|
||||
code: `
|
||||
import pandas as pd
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
def analyze_sales_data(file_path):
|
||||
# Load data
|
||||
df = pd.read_csv(file_path)
|
||||
|
||||
# Calculate monthly totals
|
||||
monthly_sales = df.groupby('month')['sales'].sum()
|
||||
|
||||
# Create visualization
|
||||
plt.figure(figsize=(10, 6))
|
||||
monthly_sales.plot(kind='bar')
|
||||
plt.title('Monthly Sales Analysis')
|
||||
plt.ylabel('Sales ($)')
|
||||
plt.show()
|
||||
|
||||
return monthly_sales
|
||||
|
||||
# Usage
|
||||
sales_data = analyze_sales_data('sales.csv')
|
||||
print(sales_data)
|
||||
`,
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Document Artifacts
|
||||
|
||||
```typescript
|
||||
const documentArtifact = {
|
||||
type: 'artifact',
|
||||
data: {
|
||||
type: 'document',
|
||||
data: {
|
||||
title: 'Project Proposal',
|
||||
content: `
|
||||
# AI-Powered Analytics Platform
|
||||
|
||||
## Executive Summary
|
||||
|
||||
This proposal outlines the development of an AI-powered analytics platform
|
||||
designed to help businesses make data-driven decisions.
|
||||
|
||||
## Key Features
|
||||
|
||||
- **Real-time Data Processing**: Stream analytics with sub-second latency
|
||||
- **Machine Learning Models**: Automated insight generation
|
||||
- **Interactive Dashboards**: Self-service analytics for business users
|
||||
|
||||
## Implementation Timeline
|
||||
|
||||
### Phase 1 (Months 1-3)
|
||||
- Core platform development
|
||||
- Basic ML model integration
|
||||
|
||||
### Phase 2 (Months 4-6)
|
||||
- Advanced analytics features
|
||||
- Dashboard creation tools
|
||||
|
||||
## Budget Estimate
|
||||
|
||||
Total project cost: $250,000
|
||||
`,
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Suggested Questions
|
||||
|
||||
Provide interactive follow-up questions to guide the conversation.
|
||||
|
||||
```typescript
|
||||
const suggestedQuestionsAnnotation = {
|
||||
type: 'suggested_questions',
|
||||
data: {
|
||||
questions: [
|
||||
'Can you explain the methodology in more detail?',
|
||||
'What are the potential limitations of this approach?',
|
||||
'How does this compare to traditional methods?',
|
||||
'What would be the next steps for implementation?',
|
||||
],
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Custom Annotations
|
||||
|
||||
Create domain-specific annotations for specialized content.
|
||||
|
||||
### Weather Widget Example
|
||||
|
||||
```typescript
|
||||
// Define custom annotation type
|
||||
interface WeatherAnnotation {
|
||||
type: 'weather'
|
||||
data: {
|
||||
location: string
|
||||
temperature: number
|
||||
condition: string
|
||||
humidity: number
|
||||
windSpeed: number
|
||||
forecast?: Array<{
|
||||
day: string
|
||||
high: number
|
||||
low: number
|
||||
condition: string
|
||||
}>
|
||||
}
|
||||
}
|
||||
|
||||
// Create annotation
|
||||
const weatherAnnotation: WeatherAnnotation = {
|
||||
type: 'weather',
|
||||
data: {
|
||||
location: 'San Francisco, CA',
|
||||
temperature: 22,
|
||||
condition: 'sunny',
|
||||
humidity: 65,
|
||||
windSpeed: 12,
|
||||
forecast: [
|
||||
{ day: 'Tomorrow', high: 24, low: 18, condition: 'cloudy' },
|
||||
{ day: 'Wednesday', high: 26, low: 20, condition: 'sunny' },
|
||||
],
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Widget Implementation
|
||||
|
||||
```tsx
|
||||
import { useChatMessage, getCustomAnnotations } from '@llamaindex/chat-ui'
|
||||
|
||||
interface WeatherData {
|
||||
location: string
|
||||
temperature: number
|
||||
condition: string
|
||||
humidity: number
|
||||
windSpeed: number
|
||||
}
|
||||
|
||||
export function CustomWeatherWidget() {
|
||||
const { message } = useChatMessage()
|
||||
|
||||
const weatherData = getCustomAnnotations<WeatherData>(
|
||||
message.annotations,
|
||||
'weather'
|
||||
)
|
||||
|
||||
if (!weatherData[0]) return null
|
||||
|
||||
const data = weatherData[0]
|
||||
|
||||
return (
|
||||
<div className="my-4 rounded-lg border border-blue-200 bg-blue-50 p-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex h-12 w-12 items-center justify-center rounded-full bg-blue-100">
|
||||
<WeatherIcon condition={data.condition} />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h3 className="font-semibold text-blue-900">{data.location}</h3>
|
||||
<div className="flex items-center gap-4 text-sm text-blue-700">
|
||||
<span className="text-2xl font-bold">{data.temperature}°C</span>
|
||||
<span>{data.condition}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3 grid grid-cols-2 gap-4 text-sm text-blue-600">
|
||||
<div className="flex items-center gap-2">
|
||||
<span>💧 Humidity:</span>
|
||||
<span className="font-medium">{data.humidity}%</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span>🌬️ Wind:</span>
|
||||
<span className="font-medium">{data.windSpeed} km/h</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function WeatherIcon({ condition }: { condition: string }) {
|
||||
const iconMap: Record<string, string> = {
|
||||
sunny: '☀️',
|
||||
cloudy: '☁️',
|
||||
rainy: '🌧️',
|
||||
snowy: '❄️',
|
||||
stormy: '⛈️',
|
||||
}
|
||||
|
||||
return (
|
||||
<span className="text-2xl">{iconMap[condition.toLowerCase()] || '🌤️'}</span>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Using Custom Widgets
|
||||
|
||||
```tsx
|
||||
import { ChatMessage } from '@llamaindex/chat-ui'
|
||||
import { CustomWeatherWidget } from './custom-weather-widget'
|
||||
|
||||
function MessageWithCustomWidgets({ message }) {
|
||||
return (
|
||||
<ChatMessage message={message}>
|
||||
<ChatMessage.Content>
|
||||
<ChatMessage.Content.Markdown />
|
||||
<ChatMessage.Content.Image />
|
||||
<ChatMessage.Content.Source />
|
||||
<CustomWeatherWidget /> {/* Add custom widget */}
|
||||
</ChatMessage.Content>
|
||||
</ChatMessage>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Annotation Utilities
|
||||
|
||||
### getCustomAnnotations
|
||||
|
||||
Extract custom annotations from a message:
|
||||
|
||||
```tsx
|
||||
import { getCustomAnnotations } from '@llamaindex/chat-ui'
|
||||
|
||||
function useWeatherData() {
|
||||
const { message } = useChatMessage()
|
||||
|
||||
return getCustomAnnotations<WeatherData>(message.annotations, 'weather')
|
||||
}
|
||||
```
|
||||
|
||||
## Server-side Implementation
|
||||
|
||||
### Streaming Annotations
|
||||
|
||||
```typescript
|
||||
// API route for streaming with annotations
|
||||
export async function POST(request: Request) {
|
||||
const { messages } = await request.json()
|
||||
|
||||
const stream = new ReadableStream({
|
||||
async start(controller) {
|
||||
const encoder = new TextEncoder()
|
||||
|
||||
// Send initial text
|
||||
controller.enqueue(
|
||||
encoder.encode('0:"Let me analyze the weather data for you.\\n"\\n')
|
||||
)
|
||||
|
||||
// Process and get weather data
|
||||
const weatherData = await getWeatherData(location)
|
||||
|
||||
// Send weather annotation
|
||||
const annotation = {
|
||||
type: 'weather',
|
||||
data: weatherData,
|
||||
}
|
||||
|
||||
controller.enqueue(encoder.encode(`8:${JSON.stringify([annotation])}\\n`))
|
||||
|
||||
// Send follow-up text
|
||||
controller.enqueue(
|
||||
encoder.encode('0:"Would you like a detailed forecast?"\\n')
|
||||
)
|
||||
|
||||
controller.close()
|
||||
},
|
||||
})
|
||||
|
||||
return new Response(stream, {
|
||||
headers: {
|
||||
'Content-Type': 'text/plain; charset=utf-8',
|
||||
'X-Vercel-AI-Data-Stream': 'v1',
|
||||
},
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### Annotation Processing
|
||||
|
||||
```typescript
|
||||
// Process different annotation types
|
||||
function createAnnotations(data: any) {
|
||||
const annotations = []
|
||||
|
||||
// Add image if generated
|
||||
if (data.imageUrl) {
|
||||
annotations.push({
|
||||
type: 'IMAGE',
|
||||
data: {
|
||||
url: data.imageUrl,
|
||||
alt: data.imageDescription,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Add sources if referenced
|
||||
if (data.sources?.length > 0) {
|
||||
annotations.push({
|
||||
type: 'sources',
|
||||
data: {
|
||||
nodes: data.sources.map(source => ({
|
||||
id: source.id,
|
||||
url: source.url,
|
||||
metadata: source.metadata,
|
||||
})),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Add custom weather data
|
||||
if (data.weather) {
|
||||
annotations.push({
|
||||
type: 'weather',
|
||||
data: data.weather,
|
||||
})
|
||||
}
|
||||
|
||||
return annotations
|
||||
}
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Artifacts](./artifacts) - Learn about interactive code and document artifacts
|
||||
- [Widgets](./widgets) - Explore widget implementation details
|
||||
- [Examples](./examples) - See complete annotation examples
|
||||
- [Customization](./customization) - Style and customize annotation appearance
|
||||
@@ -0,0 +1,580 @@
|
||||
---
|
||||
title: Artifacts
|
||||
description: Interactive code and document editing with version management
|
||||
---
|
||||
|
||||
Artifacts are interactive, editable content that appears in the chat canvas. They allow users to view, edit, and interact with generated code and documents directly within the chat interface, similar to OpenAI's ChatGPT canvas feature.
|
||||
|
||||
## Artifact System Overview
|
||||
|
||||
The artifact system consists of:
|
||||
|
||||
- **Artifact Annotations** - Data structure for artifact content
|
||||
- **ChatCanvas** - Side panel for displaying artifacts
|
||||
- **Artifact Viewers** - Components for rendering different artifact types
|
||||
- **Version Management** - Track changes and revisions
|
||||
- **Edit Capabilities** - In-place editing with syntax highlighting
|
||||
|
||||
### Artifact Types
|
||||
|
||||
The system supports two main artifact types:
|
||||
|
||||
1. **Code Artifacts** - Interactive code editing with syntax highlighting
|
||||
2. **Document Artifacts** - Rich text document editing with markdown support
|
||||
|
||||
## Code Artifacts
|
||||
|
||||
Code artifacts provide interactive code editing with full syntax highlighting and language support.
|
||||
|
||||
### Creating Code Artifacts
|
||||
|
||||
```typescript
|
||||
// Server-side: Create code artifact annotation
|
||||
const codeArtifact = {
|
||||
type: 'artifact',
|
||||
data: {
|
||||
type: 'code',
|
||||
data: {
|
||||
title: 'Data Visualization Script',
|
||||
file_name: 'visualize_data.py',
|
||||
language: 'python',
|
||||
code: `
|
||||
import matplotlib.pyplot as plt
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
|
||||
def create_sales_chart(data_file):
|
||||
"""Create a sales visualization chart."""
|
||||
# Load the data
|
||||
df = pd.read_csv(data_file)
|
||||
|
||||
# Prepare the data
|
||||
monthly_sales = df.groupby('month')['revenue'].sum()
|
||||
|
||||
# Create the visualization
|
||||
plt.figure(figsize=(12, 6))
|
||||
bars = plt.bar(monthly_sales.index, monthly_sales.values,
|
||||
color='steelblue', alpha=0.8)
|
||||
|
||||
# Customize the chart
|
||||
plt.title('Monthly Sales Revenue', fontsize=16, fontweight='bold')
|
||||
plt.xlabel('Month', fontsize=12)
|
||||
plt.ylabel('Revenue ($)', fontsize=12)
|
||||
plt.xticks(rotation=45)
|
||||
|
||||
# Add value labels on bars
|
||||
for bar in bars:
|
||||
height = bar.get_height()
|
||||
plt.text(bar.get_x() + bar.get_width()/2., height,
|
||||
f'${height:,.0f}', ha='center', va='bottom')
|
||||
|
||||
plt.tight_layout()
|
||||
plt.grid(True, alpha=0.3)
|
||||
plt.show()
|
||||
|
||||
return monthly_sales
|
||||
|
||||
# Example usage
|
||||
if __name__ == "__main__":
|
||||
sales_data = create_sales_chart('sales_data.csv')
|
||||
print(f"Total annual revenue: ${sales_data.sum():,.2f}")
|
||||
`
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Streaming Code Artifacts
|
||||
|
||||
```typescript
|
||||
// In your API route
|
||||
export async function POST(request: Request) {
|
||||
const { messages } = await request.json()
|
||||
|
||||
const stream = new ReadableStream({
|
||||
async start(controller) {
|
||||
const encoder = new TextEncoder()
|
||||
|
||||
// Send initial response
|
||||
controller.enqueue(
|
||||
encoder.encode(
|
||||
'0:"I\'ll create a Python script for data visualization:\\n"\\n'
|
||||
)
|
||||
)
|
||||
|
||||
// Generate code (could be from LLM)
|
||||
const generatedCode = await generateCodeWithLLM(messages)
|
||||
|
||||
// Send code artifact
|
||||
const artifact = {
|
||||
type: 'artifact',
|
||||
data: {
|
||||
type: 'code',
|
||||
data: {
|
||||
title: 'Data Visualization Script',
|
||||
file_name: 'chart_generator.py',
|
||||
language: 'python',
|
||||
code: generatedCode,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
controller.enqueue(encoder.encode(`8:${JSON.stringify([artifact])}\\n`))
|
||||
|
||||
// Send follow-up text
|
||||
controller.enqueue(
|
||||
encoder.encode(
|
||||
'0:"You can edit this code directly in the canvas. Would you like me to explain any part of it?"\\n'
|
||||
)
|
||||
)
|
||||
|
||||
controller.close()
|
||||
},
|
||||
})
|
||||
|
||||
return new Response(stream, {
|
||||
headers: {
|
||||
'Content-Type': 'text/plain; charset=utf-8',
|
||||
'X-Vercel-AI-Data-Stream': 'v1',
|
||||
},
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### Supported Languages
|
||||
|
||||
The code artifact system supports syntax highlighting for:
|
||||
|
||||
- **JavaScript/TypeScript** - `.js`, `.ts`, `.jsx`, `.tsx`
|
||||
- **Python** - `.py`, `.pyx`
|
||||
- **CSS/SCSS** - `.css`, `.scss`, `.sass`
|
||||
- **HTML** - `.html`, `.htm`
|
||||
- **JSON** - `.json`
|
||||
- **Markdown** - `.md`, `.mdx`
|
||||
- **SQL** - `.sql`
|
||||
- **Shell/Bash** - `.sh`, `.bash`
|
||||
|
||||
### Code Artifact Features
|
||||
|
||||
```tsx
|
||||
// The canvas automatically provides these features:
|
||||
// - Syntax highlighting
|
||||
// - Line numbers
|
||||
// - Code folding
|
||||
// - Search and replace
|
||||
// - Auto-indentation
|
||||
// - Bracket matching
|
||||
// - Error highlighting (for supported languages)
|
||||
```
|
||||
|
||||
## Document Artifacts
|
||||
|
||||
Document artifacts provide rich text editing with markdown support and real-time preview.
|
||||
|
||||
### Creating Document Artifacts
|
||||
|
||||
```typescript
|
||||
const documentArtifact = {
|
||||
type: 'artifact',
|
||||
data: {
|
||||
type: 'document',
|
||||
data: {
|
||||
title: 'API Integration Guide',
|
||||
content: `
|
||||
# API Integration Guide
|
||||
|
||||
## Overview
|
||||
|
||||
This guide walks you through integrating our REST API into your application.
|
||||
|
||||
## Authentication
|
||||
|
||||
All API requests require authentication using API keys:
|
||||
|
||||
\`\`\`bash
|
||||
curl -H "Authorization: Bearer YOUR_API_KEY" \\
|
||||
https://api.example.com/v1/users
|
||||
\`\`\`
|
||||
|
||||
## Endpoints
|
||||
|
||||
### User Management
|
||||
|
||||
#### Get User Profile
|
||||
|
||||
\`\`\`
|
||||
GET /v1/users/{userId}
|
||||
\`\`\`
|
||||
|
||||
**Response:**
|
||||
\`\`\`json
|
||||
{
|
||||
"id": "user_123",
|
||||
"name": "John Doe",
|
||||
"email": "john@example.com",
|
||||
"created_at": "2024-01-15T10:30:00Z"
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
#### Update User Profile
|
||||
|
||||
\`\`\`
|
||||
PUT /v1/users/{userId}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"name": "John Smith",
|
||||
"email": "john.smith@example.com"
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
### Data Operations
|
||||
|
||||
#### List Records
|
||||
|
||||
\`\`\`
|
||||
GET /v1/records?limit=20&offset=0
|
||||
\`\`\`
|
||||
|
||||
**Query Parameters:**
|
||||
- \`limit\`: Number of records to return (default: 20, max: 100)
|
||||
- \`offset\`: Number of records to skip (default: 0)
|
||||
- \`sort\`: Sort field (default: created_at)
|
||||
- \`order\`: Sort order (asc/desc, default: desc)
|
||||
|
||||
## Error Handling
|
||||
|
||||
The API uses standard HTTP status codes:
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 200 | Success |
|
||||
| 400 | Bad Request |
|
||||
| 401 | Unauthorized |
|
||||
| 404 | Not Found |
|
||||
| 500 | Internal Server Error |
|
||||
|
||||
**Error Response Format:**
|
||||
\`\`\`json
|
||||
{
|
||||
"error": {
|
||||
"code": "INVALID_REQUEST",
|
||||
"message": "The request is missing required parameters",
|
||||
"details": {
|
||||
"missing_fields": ["name", "email"]
|
||||
}
|
||||
}
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
## Rate Limiting
|
||||
|
||||
- **Free Tier**: 100 requests per hour
|
||||
- **Pro Tier**: 1,000 requests per hour
|
||||
- **Enterprise**: Custom limits
|
||||
|
||||
Rate limit headers are included in all responses:
|
||||
|
||||
\`\`\`
|
||||
X-RateLimit-Limit: 1000
|
||||
X-RateLimit-Remaining: 999
|
||||
X-RateLimit-Reset: 1640995200
|
||||
\`\`\`
|
||||
|
||||
## SDKs and Libraries
|
||||
|
||||
We provide official SDKs for popular languages:
|
||||
|
||||
### JavaScript/Node.js
|
||||
|
||||
\`\`\`bash
|
||||
npm install @example/api-client
|
||||
\`\`\`
|
||||
|
||||
\`\`\`javascript
|
||||
import { ApiClient } from '@example/api-client'
|
||||
|
||||
const client = new ApiClient({
|
||||
apiKey: 'your-api-key'
|
||||
})
|
||||
|
||||
const user = await client.users.get('user_123')
|
||||
console.log(user)
|
||||
\`\`\`
|
||||
|
||||
### Python
|
||||
|
||||
\`\`\`bash
|
||||
pip install example-api-client
|
||||
\`\`\`
|
||||
|
||||
\`\`\`python
|
||||
from example_api import ApiClient
|
||||
|
||||
client = ApiClient(api_key='your-api-key')
|
||||
user = client.users.get('user_123')
|
||||
print(user)
|
||||
\`\`\`
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always use HTTPS** for API requests
|
||||
2. **Store API keys securely** - never commit them to version control
|
||||
3. **Implement retry logic** with exponential backoff
|
||||
4. **Cache responses** when appropriate to reduce API calls
|
||||
5. **Monitor rate limits** and implement proper handling
|
||||
|
||||
## Webhooks
|
||||
|
||||
Configure webhooks to receive real-time notifications:
|
||||
|
||||
\`\`\`json
|
||||
{
|
||||
"url": "https://yourapp.com/webhooks/api",
|
||||
"events": ["user.created", "user.updated", "user.deleted"],
|
||||
"secret": "webhook-secret-key"
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
## Support
|
||||
|
||||
- **Documentation**: https://docs.example.com
|
||||
- **Support Email**: support@example.com
|
||||
- **Status Page**: https://status.example.com
|
||||
|
||||
Happy coding! 🚀
|
||||
`,
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Document Features
|
||||
|
||||
Document artifacts provide:
|
||||
|
||||
- **Rich Text Editing** - WYSIWYG editor with markdown output
|
||||
- **Live Preview** - Real-time rendering of markdown content
|
||||
- **Formatting Tools** - Bold, italic, headers, lists, links
|
||||
- **Code Blocks** - Syntax-highlighted code snippets
|
||||
- **Tables** - Table creation and editing
|
||||
- **Math Support** - LaTeX math rendering
|
||||
- **Image Embedding** - Image insertion and management
|
||||
|
||||
## Canvas Integration
|
||||
|
||||
### Basic Canvas Setup
|
||||
|
||||
```tsx
|
||||
import { ChatSection, ChatCanvas } from '@llamaindex/chat-ui'
|
||||
|
||||
function ChatWithCanvas() {
|
||||
const handler = useChat({ api: '/api/chat' })
|
||||
|
||||
return (
|
||||
<ChatSection handler={handler} className="flex h-full">
|
||||
<div className="flex-1">
|
||||
<ChatMessages />
|
||||
<ChatInput />
|
||||
</div>
|
||||
<ChatCanvas className="w-1/2 border-l" />
|
||||
</ChatSection>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Canvas Auto-Show
|
||||
|
||||
The canvas automatically appears when artifacts are present:
|
||||
|
||||
```tsx
|
||||
// Canvas appears automatically when message contains artifacts
|
||||
<ChatMessage message={messageWithArtifact}>
|
||||
<ChatMessage.Content>
|
||||
<ChatMessage.Content.Markdown />
|
||||
<ChatMessage.Content.Artifact /> {/* Triggers canvas display */}
|
||||
</ChatMessage.Content>
|
||||
</ChatMessage>
|
||||
```
|
||||
|
||||
## Version Management
|
||||
|
||||
Artifacts support version tracking and restoration:
|
||||
|
||||
### Version History
|
||||
|
||||
```tsx
|
||||
import { useChatCanvas } from '@llamaindex/chat-ui'
|
||||
|
||||
function ArtifactVersions() {
|
||||
const { currentArtifact, updateArtifact } = useChatCanvas()
|
||||
|
||||
if (!currentArtifact) return null
|
||||
|
||||
const versions = currentArtifact.versions || []
|
||||
|
||||
const restoreVersion = (version: any) => {
|
||||
updateArtifact({
|
||||
...currentArtifact,
|
||||
content: version.content,
|
||||
versions: [
|
||||
...versions,
|
||||
{
|
||||
id: generateId(),
|
||||
content: currentArtifact.content,
|
||||
timestamp: new Date().toISOString(),
|
||||
description: 'Current version backup',
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<h3 className="font-semibold">Version History</h3>
|
||||
{versions.map((version, index) => (
|
||||
<div key={version.id} className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium">
|
||||
Version {versions.length - index}
|
||||
</p>
|
||||
<p className="text-xs text-gray-500">
|
||||
{new Date(version.timestamp).toLocaleString()}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => restoreVersion(version)}
|
||||
className="text-sm text-blue-600 hover:underline"
|
||||
>
|
||||
Restore
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Save Changes
|
||||
|
||||
```tsx
|
||||
function SaveArtifactChanges() {
|
||||
const { currentArtifact, updateArtifact } = useChatCanvas()
|
||||
const [hasChanges, setHasChanges] = useState(false)
|
||||
|
||||
const saveChanges = () => {
|
||||
if (!currentArtifact || !hasChanges) return
|
||||
|
||||
// Create version backup
|
||||
const newVersion = {
|
||||
id: generateId(),
|
||||
content: currentArtifact.originalContent,
|
||||
timestamp: new Date().toISOString(),
|
||||
description: 'Auto-saved version',
|
||||
}
|
||||
|
||||
updateArtifact({
|
||||
...currentArtifact,
|
||||
versions: [...(currentArtifact.versions || []), newVersion],
|
||||
originalContent: currentArtifact.content,
|
||||
})
|
||||
|
||||
setHasChanges(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={saveChanges}
|
||||
disabled={!hasChanges}
|
||||
className="rounded bg-blue-600 px-3 py-1 text-white disabled:opacity-50"
|
||||
>
|
||||
{hasChanges ? 'Save Changes' : 'Saved'}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Artifact Actions
|
||||
|
||||
### Export Functionality
|
||||
|
||||
```tsx
|
||||
function ArtifactExport() {
|
||||
const { currentArtifact } = useChatCanvas()
|
||||
|
||||
const exportAsFile = () => {
|
||||
if (!currentArtifact) return
|
||||
|
||||
const content =
|
||||
currentArtifact.type === 'code'
|
||||
? currentArtifact.code
|
||||
: currentArtifact.content
|
||||
|
||||
const filename =
|
||||
currentArtifact.type === 'code'
|
||||
? currentArtifact.file_name
|
||||
: `${currentArtifact.title}.md`
|
||||
|
||||
const blob = new Blob([content], {
|
||||
type: 'text/plain;charset=utf-8',
|
||||
})
|
||||
|
||||
const url = URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = filename
|
||||
link.click()
|
||||
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={exportAsFile}
|
||||
className="flex items-center gap-2 rounded border px-3 py-1 text-sm"
|
||||
>
|
||||
<DownloadIcon className="h-4 w-4" />
|
||||
Export
|
||||
</button>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Copy to Clipboard
|
||||
|
||||
```tsx
|
||||
import { useCopyToClipboard } from '@llamaindex/chat-ui'
|
||||
|
||||
function CopyArtifact() {
|
||||
const { currentArtifact } = useChatCanvas()
|
||||
const { copyToClipboard, isCopied } = useCopyToClipboard()
|
||||
|
||||
const handleCopy = () => {
|
||||
if (!currentArtifact) return
|
||||
|
||||
const content =
|
||||
currentArtifact.type === 'code'
|
||||
? currentArtifact.code
|
||||
: currentArtifact.content
|
||||
|
||||
copyToClipboard(content)
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={handleCopy}
|
||||
className="flex items-center gap-2 rounded border px-3 py-1 text-sm"
|
||||
>
|
||||
<CopyIcon className="h-4 w-4" />
|
||||
{isCopied ? 'Copied!' : 'Copy'}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Examples](./examples) - See complete artifact implementations
|
||||
- [Customization](./customization) - Style and customize artifact appearance
|
||||
- [Widgets](./widgets) - Explore related widget functionality
|
||||
- [Annotations](./annotations) - Understand the annotation system
|
||||
@@ -0,0 +1,423 @@
|
||||
---
|
||||
title: Core Components
|
||||
description: Detailed guide to the main chat interface components
|
||||
---
|
||||
|
||||
The core components form the foundation of any chat interface built with LlamaIndex Chat UI. These components work together to provide a complete chat experience.
|
||||
|
||||
## ChatSection
|
||||
|
||||
The `ChatSection` is the root component that provides context and layout for all other chat components.
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```tsx
|
||||
import { ChatSection } from '@llamaindex/chat-ui'
|
||||
import { useChat } from 'ai/react'
|
||||
|
||||
function MyChat() {
|
||||
const handler = useChat({ api: '/api/chat' })
|
||||
return <ChatSection handler={handler} />
|
||||
}
|
||||
```
|
||||
|
||||
### Props
|
||||
|
||||
```typescript
|
||||
interface ChatSectionProps {
|
||||
handler: ChatHandler
|
||||
className?: string
|
||||
children?: React.ReactNode
|
||||
}
|
||||
```
|
||||
|
||||
- **handler**: Chat handler from `useChat` or custom implementation
|
||||
- **className**: Custom CSS classes for styling
|
||||
- **children**: Custom layout (defaults to `ChatMessages` + `ChatInput`)
|
||||
|
||||
### Default Layout
|
||||
|
||||
When no children are provided, `ChatSection` renders:
|
||||
|
||||
```tsx
|
||||
<>
|
||||
<ChatMessages />
|
||||
<ChatInput />
|
||||
</>
|
||||
```
|
||||
|
||||
### Custom Layout
|
||||
|
||||
```tsx
|
||||
<ChatSection handler={handler} className="flex-row gap-4">
|
||||
<div className="flex-1">
|
||||
<ChatMessages />
|
||||
<ChatInput />
|
||||
</div>
|
||||
<ChatCanvas className="w-1/3" />
|
||||
</ChatSection>
|
||||
```
|
||||
|
||||
## ChatMessages
|
||||
|
||||
The `ChatMessages` component displays the conversation history with automatic scrolling and loading states.
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```tsx
|
||||
import { ChatMessages } from '@llamaindex/chat-ui'
|
||||
|
||||
function MessageHistory() {
|
||||
return (
|
||||
<ChatMessages>
|
||||
<ChatMessages.List>
|
||||
{/* Custom message rendering */}
|
||||
</ChatMessages.List>
|
||||
</ChatMessages>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Sub-components
|
||||
|
||||
#### ChatMessages.List
|
||||
|
||||
Contains the actual message list with scroll management:
|
||||
|
||||
```tsx
|
||||
<ChatMessages.List className="px-4 py-6">
|
||||
{messages.map((message, index) => (
|
||||
<ChatMessage key={index} message={message} />
|
||||
))}
|
||||
</ChatMessages.List>
|
||||
```
|
||||
|
||||
#### ChatMessages.Loading
|
||||
|
||||
Shows loading indicator when chat is processing:
|
||||
|
||||
```tsx
|
||||
<ChatMessages.Loading>
|
||||
<div className="animate-pulse">Thinking...</div>
|
||||
</ChatMessages.Loading>
|
||||
```
|
||||
|
||||
#### ChatMessages.Empty
|
||||
|
||||
Displays when no messages exist:
|
||||
|
||||
```tsx
|
||||
<ChatMessages.Empty
|
||||
heading="Welcome to Chat"
|
||||
subheading="Start a conversation by typing below"
|
||||
/>
|
||||
```
|
||||
|
||||
#### ChatMessages.Actions
|
||||
|
||||
Action buttons for the message list:
|
||||
|
||||
```tsx
|
||||
<ChatMessages.Actions>
|
||||
<button onClick={reload}>Reload</button>
|
||||
<button onClick={stop}>Stop</button>
|
||||
</ChatMessages.Actions>
|
||||
```
|
||||
|
||||
### Features
|
||||
|
||||
- **Auto-scroll** - Automatically scrolls to new messages
|
||||
- **Loading states** - Shows when chat is processing
|
||||
- **Empty state** - Customizable welcome message
|
||||
- **Performance** - Optimized for large message histories
|
||||
|
||||
## ChatMessage
|
||||
|
||||
Individual message component with full annotation support and role-based rendering.
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```tsx
|
||||
import { ChatMessage } from '@llamaindex/chat-ui'
|
||||
|
||||
function CustomMessage({ message, isLast }) {
|
||||
return (
|
||||
<ChatMessage message={message} isLast={isLast}>
|
||||
<ChatMessage.Avatar>
|
||||
<UserAvatar role={message.role} />
|
||||
</ChatMessage.Avatar>
|
||||
<ChatMessage.Content>
|
||||
<ChatMessage.Content.Markdown />
|
||||
<ChatMessage.Content.Artifact />
|
||||
</ChatMessage.Content>
|
||||
<ChatMessage.Actions />
|
||||
</ChatMessage>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Props
|
||||
|
||||
```typescript
|
||||
interface ChatMessageProps {
|
||||
message: Message
|
||||
isLast?: boolean
|
||||
className?: string
|
||||
children?: React.ReactNode
|
||||
}
|
||||
```
|
||||
|
||||
### Sub-components
|
||||
|
||||
#### ChatMessage.Avatar
|
||||
|
||||
User/assistant avatar display:
|
||||
|
||||
```tsx
|
||||
<ChatMessage.Avatar>
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-blue-500">
|
||||
{message.role === 'user' ? 'U' : 'AI'}
|
||||
</div>
|
||||
</ChatMessage.Avatar>
|
||||
```
|
||||
|
||||
#### ChatMessage.Content
|
||||
|
||||
Main content area with annotation support:
|
||||
|
||||
```tsx
|
||||
<ChatMessage.Content isLoading={isLoading} append={append}>
|
||||
<ChatMessage.Content.Markdown />
|
||||
<ChatMessage.Content.Image />
|
||||
<ChatMessage.Content.Artifact />
|
||||
<ChatMessage.Content.Source />
|
||||
<ChatMessage.Content.Event />
|
||||
<ChatMessage.Content.AgentEvent />
|
||||
<ChatMessage.Content.DocumentFile />
|
||||
<ChatMessage.Content.SuggestedQuestions />
|
||||
</ChatMessage.Content>
|
||||
```
|
||||
|
||||
#### ChatMessage.Actions
|
||||
|
||||
Message-level actions (copy, regenerate, etc.):
|
||||
|
||||
```tsx
|
||||
<ChatMessage.Actions>
|
||||
<button onClick={copyMessage}>Copy</button>
|
||||
<button onClick={regenerate}>Regenerate</button>
|
||||
</ChatMessage.Actions>
|
||||
```
|
||||
|
||||
### Content Types
|
||||
|
||||
The content system supports multiple annotation types:
|
||||
|
||||
- **Markdown** - Rich text with LaTeX support
|
||||
- **Image** - Image display with preview
|
||||
- **Artifact** - Interactive code/document editing
|
||||
- **Source** - Citation and source links
|
||||
- **Event** - Process events and status
|
||||
- **AgentEvent** - Agent-specific events with progress
|
||||
- **DocumentFile** - File attachments
|
||||
- **SuggestedQuestions** - Follow-up question suggestions
|
||||
|
||||
## ChatInput
|
||||
|
||||
Input component with file upload support and keyboard shortcuts.
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```tsx
|
||||
import { ChatInput } from '@llamaindex/chat-ui'
|
||||
|
||||
function MessageInput() {
|
||||
return (
|
||||
<ChatInput>
|
||||
<ChatInput.Form>
|
||||
<ChatInput.Field placeholder="Type your message..." />
|
||||
<ChatInput.Upload />
|
||||
<ChatInput.Submit />
|
||||
</ChatInput.Form>
|
||||
</ChatInput>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Sub-components
|
||||
|
||||
#### ChatInput.Form
|
||||
|
||||
Form wrapper with submit handling:
|
||||
|
||||
```tsx
|
||||
<ChatInput.Form onSubmit={handleSubmit}>
|
||||
{/* Input components */}
|
||||
</ChatInput.Form>
|
||||
```
|
||||
|
||||
#### ChatInput.Field
|
||||
|
||||
Auto-resizing textarea with keyboard shortcuts:
|
||||
|
||||
```tsx
|
||||
<ChatInput.Field
|
||||
placeholder="Type your message..."
|
||||
disabled={isLoading}
|
||||
className="min-h-12 max-h-32"
|
||||
/>
|
||||
```
|
||||
|
||||
**Keyboard Shortcuts:**
|
||||
- `Enter` - Submit message
|
||||
- `Shift + Enter` - New line
|
||||
- `Escape` - Clear input
|
||||
|
||||
#### ChatInput.Upload
|
||||
|
||||
File upload trigger:
|
||||
|
||||
```tsx
|
||||
<ChatInput.Upload>
|
||||
<button type="button">
|
||||
<PaperclipIcon className="h-4 w-4" />
|
||||
</button>
|
||||
</ChatInput.Upload>
|
||||
```
|
||||
|
||||
#### ChatInput.Submit
|
||||
|
||||
Submit button with loading state:
|
||||
|
||||
```tsx
|
||||
<ChatInput.Submit>
|
||||
<button type="submit" disabled={isLoading}>
|
||||
<SendIcon className="h-4 w-4" />
|
||||
</button>
|
||||
</ChatInput.Submit>
|
||||
```
|
||||
|
||||
### Features
|
||||
|
||||
- **Auto-resize** - Textarea grows with content
|
||||
- **File upload** - Drag & drop or click to upload
|
||||
- **Keyboard shortcuts** - Intuitive message sending
|
||||
- **Loading states** - Disabled during processing
|
||||
- **Validation** - Built-in input validation
|
||||
|
||||
## ChatCanvas
|
||||
|
||||
Side panel component for displaying interactive artifacts like code and documents.
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```tsx
|
||||
import { ChatCanvas } from '@llamaindex/chat-ui'
|
||||
|
||||
function ChatWithCanvas() {
|
||||
return (
|
||||
<div className="flex h-full">
|
||||
<div className="flex-1">
|
||||
<ChatMessages />
|
||||
<ChatInput />
|
||||
</div>
|
||||
<ChatCanvas className="w-1/2" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Features
|
||||
|
||||
- **Slide Animation** - Smooth slide-in when artifacts appear
|
||||
- **Version Management** - Track changes to artifacts
|
||||
- **Edit Support** - Interactive editing for code and documents
|
||||
- **Responsive** - Adapts to different screen sizes
|
||||
|
||||
### Artifact Types
|
||||
|
||||
#### Code Artifacts
|
||||
|
||||
```tsx
|
||||
<ChatCanvas>
|
||||
<ChatCanvas.CodeArtifact
|
||||
filename="example.py"
|
||||
language="python"
|
||||
code="print('Hello, world!')"
|
||||
/>
|
||||
</ChatCanvas>
|
||||
```
|
||||
|
||||
#### Document Artifacts
|
||||
|
||||
```tsx
|
||||
<ChatCanvas>
|
||||
<ChatCanvas.DocumentArtifact
|
||||
title="Meeting Notes"
|
||||
content="# Meeting Summary\n\n..."
|
||||
/>
|
||||
</ChatCanvas>
|
||||
```
|
||||
|
||||
## Component Composition
|
||||
|
||||
### Full Custom Layout
|
||||
|
||||
```tsx
|
||||
function AdvancedChat() {
|
||||
const handler = useChat({ api: '/api/chat' })
|
||||
|
||||
return (
|
||||
<ChatSection handler={handler} className="flex h-full">
|
||||
<div className="flex flex-1 flex-col">
|
||||
<header className="border-b p-4">
|
||||
<h1>My Chat App</h1>
|
||||
</header>
|
||||
|
||||
<ChatMessages className="flex-1">
|
||||
<ChatMessages.List className="p-4">
|
||||
{/* Custom message rendering */}
|
||||
</ChatMessages.List>
|
||||
<ChatMessages.Loading>
|
||||
<CustomLoader />
|
||||
</ChatMessages.Loading>
|
||||
</ChatMessages>
|
||||
|
||||
<div className="border-t p-4">
|
||||
<ChatInput>
|
||||
<ChatInput.Form className="flex gap-2">
|
||||
<ChatInput.Field className="flex-1" />
|
||||
<ChatInput.Upload />
|
||||
<ChatInput.Submit />
|
||||
</ChatInput.Form>
|
||||
</ChatInput>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ChatCanvas className="w-1/2 border-l" />
|
||||
</ChatSection>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Context Access
|
||||
|
||||
All components have access to chat context through hooks:
|
||||
|
||||
```tsx
|
||||
import { useChatUI, useChatMessage } from '@llamaindex/chat-ui'
|
||||
|
||||
function CustomComponent() {
|
||||
const { messages, isLoading, append } = useChatUI()
|
||||
const { message } = useChatMessage() // Only in message context
|
||||
|
||||
// Component logic
|
||||
}
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Widgets](./widgets) - Learn about specialized content widgets
|
||||
- [Annotations](./annotations) - Implement rich content support
|
||||
- [Hooks](./hooks) - Understand the hook system
|
||||
- [Customization](./customization) - Style and theme the components
|
||||
@@ -0,0 +1,718 @@
|
||||
---
|
||||
title: Customization
|
||||
description: Style and customize the appearance and behavior of chat components
|
||||
---
|
||||
|
||||
LlamaIndex Chat UI is built with customization in mind. You can style components, override default behaviors, and create custom themes to match your application's design system.
|
||||
|
||||
## Styling System
|
||||
|
||||
The library uses Tailwind CSS for styling and provides several customization approaches:
|
||||
|
||||
### CSS Variables
|
||||
|
||||
The library exposes CSS variables for easy theming:
|
||||
|
||||
```css
|
||||
/* Custom theme variables */
|
||||
:root {
|
||||
--chat-primary: #3b82f6;
|
||||
--chat-secondary: #6b7280;
|
||||
--chat-background: #ffffff;
|
||||
--chat-surface: #f9fafb;
|
||||
--chat-border: #e5e7eb;
|
||||
--chat-text: #1f2937;
|
||||
--chat-text-secondary: #6b7280;
|
||||
--chat-success: #10b981;
|
||||
--chat-warning: #f59e0b;
|
||||
--chat-error: #ef4444;
|
||||
}
|
||||
|
||||
/* Dark theme */
|
||||
[data-theme="dark"] {
|
||||
--chat-primary: #60a5fa;
|
||||
--chat-secondary: #9ca3af;
|
||||
--chat-background: #111827;
|
||||
--chat-surface: #1f2937;
|
||||
--chat-border: #374151;
|
||||
--chat-text: #f9fafb;
|
||||
--chat-text-secondary: #d1d5db;
|
||||
}
|
||||
```
|
||||
|
||||
### Component-Level Styling
|
||||
|
||||
Override component styles using className props:
|
||||
|
||||
```tsx
|
||||
import { ChatSection, ChatMessages, ChatInput } from '@llamaindex/chat-ui'
|
||||
|
||||
function CustomStyledChat() {
|
||||
return (
|
||||
<ChatSection
|
||||
handler={handler}
|
||||
className="bg-gradient-to-b from-blue-50 to-white"
|
||||
>
|
||||
<ChatMessages className="bg-white/80 backdrop-blur rounded-lg shadow-lg">
|
||||
<ChatMessages.List className="space-y-6 p-6">
|
||||
{/* Custom message rendering */}
|
||||
</ChatMessages.List>
|
||||
</ChatMessages>
|
||||
|
||||
<ChatInput className="bg-white border-2 border-blue-200 rounded-xl p-4">
|
||||
<ChatInput.Form className="flex items-end gap-3">
|
||||
<ChatInput.Field
|
||||
className="flex-1 border-none bg-gray-50 rounded-lg px-4 py-2"
|
||||
placeholder="Ask me anything..."
|
||||
/>
|
||||
<ChatInput.Submit className="bg-blue-600 hover:bg-blue-700 text-white rounded-lg px-4 py-2">
|
||||
Send
|
||||
</ChatInput.Submit>
|
||||
</ChatInput.Form>
|
||||
</ChatInput>
|
||||
</ChatSection>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Message Customization
|
||||
|
||||
### Custom Message Layout
|
||||
|
||||
```tsx
|
||||
import { ChatMessage, useChatMessage } from '@llamaindex/chat-ui'
|
||||
|
||||
function CustomMessageLayout() {
|
||||
const { message, isLast } = useChatMessage()
|
||||
|
||||
return (
|
||||
<ChatMessage
|
||||
message={message}
|
||||
isLast={isLast}
|
||||
className={`
|
||||
${message.role === 'user' ? 'ml-8' : 'mr-8'}
|
||||
transition-all duration-200 hover:shadow-md
|
||||
`}
|
||||
>
|
||||
<div className={`
|
||||
flex gap-3 p-4 rounded-2xl
|
||||
${message.role === 'user'
|
||||
? 'bg-blue-600 text-white ml-auto'
|
||||
: 'bg-gray-100 text-gray-900'
|
||||
}
|
||||
`}>
|
||||
<ChatMessage.Avatar>
|
||||
<CustomAvatar role={message.role} />
|
||||
</ChatMessage.Avatar>
|
||||
|
||||
<div className="flex-1">
|
||||
<ChatMessage.Content>
|
||||
<ChatMessage.Content.Markdown />
|
||||
<ChatMessage.Content.Image />
|
||||
<ChatMessage.Content.Source />
|
||||
</ChatMessage.Content>
|
||||
|
||||
<ChatMessage.Actions>
|
||||
<CustomMessageActions />
|
||||
</ChatMessage.Actions>
|
||||
</div>
|
||||
</div>
|
||||
</ChatMessage>
|
||||
)
|
||||
}
|
||||
|
||||
function CustomAvatar({ role }: { role: string }) {
|
||||
const avatarClass = role === 'user'
|
||||
? 'bg-blue-500 text-white'
|
||||
: 'bg-gray-300 text-gray-700'
|
||||
|
||||
return (
|
||||
<div className={`
|
||||
h-10 w-10 rounded-full flex items-center justify-center
|
||||
text-sm font-semibold ${avatarClass}
|
||||
`}>
|
||||
{role === 'user' ? '👤' : '🤖'}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Message Role Styling
|
||||
|
||||
```tsx
|
||||
function RoleBasedMessage() {
|
||||
const { message } = useChatMessage()
|
||||
|
||||
const roleStyles = {
|
||||
user: {
|
||||
container: 'justify-end',
|
||||
bubble: 'bg-blue-600 text-white rounded-l-2xl rounded-tr-2xl',
|
||||
maxWidth: 'max-w-[80%]'
|
||||
},
|
||||
assistant: {
|
||||
container: 'justify-start',
|
||||
bubble: 'bg-white border shadow-sm rounded-r-2xl rounded-tl-2xl',
|
||||
maxWidth: 'max-w-[85%]'
|
||||
},
|
||||
system: {
|
||||
container: 'justify-center',
|
||||
bubble: 'bg-yellow-50 border border-yellow-200 rounded-lg text-yellow-800',
|
||||
maxWidth: 'max-w-[70%]'
|
||||
}
|
||||
}
|
||||
|
||||
const styles = roleStyles[message.role] || roleStyles.assistant
|
||||
|
||||
return (
|
||||
<div className={`flex ${styles.container} mb-4`}>
|
||||
<div className={`${styles.bubble} ${styles.maxWidth} p-4`}>
|
||||
<ChatMessage.Content>
|
||||
<ChatMessage.Content.Markdown />
|
||||
</ChatMessage.Content>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Input Customization
|
||||
|
||||
### Custom Input Design
|
||||
|
||||
```tsx
|
||||
import { ChatInput, useChatInput } from '@llamaindex/chat-ui'
|
||||
|
||||
function CustomChatInput() {
|
||||
const { input, setInput, handleSubmit, isLoading } = useChatInput()
|
||||
|
||||
return (
|
||||
<div className="relative border-t bg-white p-4">
|
||||
<div className="mx-auto max-w-4xl">
|
||||
<ChatInput>
|
||||
<ChatInput.Form className="relative flex items-end gap-3">
|
||||
{/* Custom input field with enhanced styling */}
|
||||
<div className="relative flex-1">
|
||||
<ChatInput.Field
|
||||
className="
|
||||
w-full resize-none rounded-2xl border border-gray-300
|
||||
bg-white px-4 py-3 pr-12 text-sm
|
||||
focus:border-blue-500 focus:outline-none focus:ring-2
|
||||
focus:ring-blue-500/20 disabled:opacity-50
|
||||
max-h-32 min-h-[44px]
|
||||
"
|
||||
placeholder="Type your message..."
|
||||
disabled={isLoading}
|
||||
/>
|
||||
|
||||
{/* Character counter */}
|
||||
<div className="absolute bottom-1 right-12 text-xs text-gray-400">
|
||||
{input.length}/2000
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Upload button */}
|
||||
<ChatInput.Upload>
|
||||
<button
|
||||
type="button"
|
||||
className="
|
||||
flex h-11 w-11 items-center justify-center rounded-xl
|
||||
border border-gray-300 bg-white hover:bg-gray-50
|
||||
transition-colors disabled:opacity-50
|
||||
"
|
||||
disabled={isLoading}
|
||||
>
|
||||
<PaperclipIcon className="h-5 w-5 text-gray-600" />
|
||||
</button>
|
||||
</ChatInput.Upload>
|
||||
|
||||
{/* Submit button */}
|
||||
<ChatInput.Submit>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isLoading || !input.trim()}
|
||||
className="
|
||||
flex h-11 w-11 items-center justify-center rounded-xl
|
||||
bg-blue-600 text-white hover:bg-blue-700
|
||||
transition-colors disabled:opacity-50 disabled:cursor-not-allowed
|
||||
"
|
||||
>
|
||||
{isLoading ? (
|
||||
<LoadingSpinner className="h-5 w-5" />
|
||||
) : (
|
||||
<SendIcon className="h-5 w-5" />
|
||||
)}
|
||||
</button>
|
||||
</ChatInput.Submit>
|
||||
</ChatInput.Form>
|
||||
</ChatInput>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Input with Suggestions
|
||||
|
||||
```tsx
|
||||
function InputWithSuggestions() {
|
||||
const [suggestions, setSuggestions] = useState([])
|
||||
const { input, setInput } = useChatInput()
|
||||
|
||||
const commonSuggestions = [
|
||||
"How can I help you today?",
|
||||
"Explain this concept",
|
||||
"Write some code for",
|
||||
"Summarize this document"
|
||||
]
|
||||
|
||||
const filteredSuggestions = commonSuggestions.filter(s =>
|
||||
s.toLowerCase().includes(input.toLowerCase()) && input.length > 0
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<ChatInput>
|
||||
<ChatInput.Form>
|
||||
<ChatInput.Field
|
||||
onFocus={() => setSuggestions(filteredSuggestions)}
|
||||
onBlur={() => setTimeout(() => setSuggestions([]), 150)}
|
||||
/>
|
||||
<ChatInput.Submit />
|
||||
</ChatInput.Form>
|
||||
</ChatInput>
|
||||
|
||||
{suggestions.length > 0 && (
|
||||
<div className="absolute bottom-full left-0 right-0 mb-2">
|
||||
<div className="bg-white border rounded-lg shadow-lg max-h-32 overflow-y-auto">
|
||||
{suggestions.map((suggestion, index) => (
|
||||
<button
|
||||
key={index}
|
||||
className="w-full px-3 py-2 text-left hover:bg-gray-50 text-sm"
|
||||
onClick={() => {
|
||||
setInput(suggestion)
|
||||
setSuggestions([])
|
||||
}}
|
||||
>
|
||||
{suggestion}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Canvas Customization
|
||||
|
||||
### Custom Canvas Layout
|
||||
|
||||
```tsx
|
||||
import { ChatCanvas, useChatCanvas } from '@llamaindex/chat-ui'
|
||||
|
||||
function CustomCanvas() {
|
||||
const { currentArtifact, isVisible, hideCanvas } = useChatCanvas()
|
||||
|
||||
if (!isVisible || !currentArtifact) return null
|
||||
|
||||
return (
|
||||
<div className="w-1/2 bg-gray-50 border-l flex flex-col">
|
||||
{/* Custom header */}
|
||||
<div className="flex items-center justify-between p-4 border-b bg-white">
|
||||
<div>
|
||||
<h2 className="font-semibold text-gray-900">
|
||||
{currentArtifact.title}
|
||||
</h2>
|
||||
<p className="text-sm text-gray-500">
|
||||
{currentArtifact.type === 'code' ? 'Code Editor' : 'Document'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<CustomCanvasActions />
|
||||
<button
|
||||
onClick={hideCanvas}
|
||||
className="p-1 hover:bg-gray-100 rounded"
|
||||
>
|
||||
<XIcon className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Custom content area */}
|
||||
<div className="flex-1 overflow-hidden">
|
||||
<ChatCanvas className="h-full">
|
||||
{/* Canvas content renders here */}
|
||||
</ChatCanvas>
|
||||
</div>
|
||||
|
||||
{/* Custom footer */}
|
||||
<div className="border-t bg-white p-3">
|
||||
<div className="flex items-center justify-between text-sm text-gray-500">
|
||||
<span>
|
||||
{currentArtifact.type === 'code'
|
||||
? `${currentArtifact.language} • ${currentArtifact.file_name}`
|
||||
: 'Markdown Document'
|
||||
}
|
||||
</span>
|
||||
<span>
|
||||
Last modified: {new Date().toLocaleTimeString()}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Widget Styling
|
||||
|
||||
### Custom Markdown Styling
|
||||
|
||||
```tsx
|
||||
import { Markdown } from '@llamaindex/chat-ui/widgets'
|
||||
|
||||
function CustomMarkdown({ children }: { children: string }) {
|
||||
return (
|
||||
<div className="prose prose-sm max-w-none">
|
||||
<style jsx>{`
|
||||
.prose {
|
||||
--tw-prose-body: #374151;
|
||||
--tw-prose-headings: #111827;
|
||||
--tw-prose-links: #3b82f6;
|
||||
--tw-prose-bold: #111827;
|
||||
--tw-prose-code: #dc2626;
|
||||
--tw-prose-pre-bg: #f3f4f6;
|
||||
--tw-prose-pre-code: #374151;
|
||||
}
|
||||
|
||||
.prose code {
|
||||
background: #f3f4f6;
|
||||
padding: 0.125rem 0.25rem;
|
||||
border-radius: 0.25rem;
|
||||
font-size: 0.875em;
|
||||
}
|
||||
|
||||
.prose blockquote {
|
||||
border-left: 4px solid #3b82f6;
|
||||
background: #eff6ff;
|
||||
padding: 1rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
`}</style>
|
||||
|
||||
<Markdown>{children}</Markdown>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Code Block Styling
|
||||
|
||||
```tsx
|
||||
import { CodeBlock } from '@llamaindex/chat-ui/widgets'
|
||||
|
||||
function StyledCodeBlock({ code, language, filename }: {
|
||||
code: string
|
||||
language: string
|
||||
filename?: string
|
||||
}) {
|
||||
return (
|
||||
<div className="my-4 overflow-hidden rounded-lg border border-gray-200">
|
||||
{filename && (
|
||||
<div className="flex items-center justify-between bg-gray-50 px-4 py-2">
|
||||
<span className="text-sm font-medium text-gray-700">
|
||||
{filename}
|
||||
</span>
|
||||
<span className="text-xs text-gray-500 uppercase">
|
||||
{language}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="relative">
|
||||
<CodeBlock
|
||||
code={code}
|
||||
language={language}
|
||||
className="bg-gray-900 text-gray-100"
|
||||
showLineNumbers={true}
|
||||
/>
|
||||
|
||||
{/* Custom copy button */}
|
||||
<button
|
||||
className="absolute top-2 right-2 p-2 bg-gray-800 hover:bg-gray-700 rounded"
|
||||
onClick={() => navigator.clipboard.writeText(code)}
|
||||
>
|
||||
<CopyIcon className="h-4 w-4 text-gray-300" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Theme System
|
||||
|
||||
### Theme Provider
|
||||
|
||||
```tsx
|
||||
import { createContext, useContext, useState } from 'react'
|
||||
|
||||
interface Theme {
|
||||
mode: 'light' | 'dark'
|
||||
primaryColor: string
|
||||
borderRadius: 'none' | 'sm' | 'md' | 'lg'
|
||||
fontSize: 'sm' | 'base' | 'lg'
|
||||
}
|
||||
|
||||
const ThemeContext = createContext<{
|
||||
theme: Theme
|
||||
updateTheme: (updates: Partial<Theme>) => void
|
||||
}>({
|
||||
theme: {
|
||||
mode: 'light',
|
||||
primaryColor: 'blue',
|
||||
borderRadius: 'md',
|
||||
fontSize: 'base'
|
||||
},
|
||||
updateTheme: () => {}
|
||||
})
|
||||
|
||||
export function ThemeProvider({ children }: { children: React.ReactNode }) {
|
||||
const [theme, setTheme] = useState<Theme>({
|
||||
mode: 'light',
|
||||
primaryColor: 'blue',
|
||||
borderRadius: 'md',
|
||||
fontSize: 'base'
|
||||
})
|
||||
|
||||
const updateTheme = (updates: Partial<Theme>) => {
|
||||
setTheme(prev => ({ ...prev, ...updates }))
|
||||
}
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={{ theme, updateTheme }}>
|
||||
<div
|
||||
className={`theme-${theme.mode} theme-${theme.primaryColor}`}
|
||||
data-theme={theme.mode}
|
||||
data-radius={theme.borderRadius}
|
||||
data-font-size={theme.fontSize}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</ThemeContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export const useTheme = () => useContext(ThemeContext)
|
||||
```
|
||||
|
||||
### Themed Components
|
||||
|
||||
```tsx
|
||||
function ThemedChatSection() {
|
||||
const { theme } = useTheme()
|
||||
const handler = useChat({ api: '/api/chat' })
|
||||
|
||||
const themeClasses = {
|
||||
light: 'bg-white text-gray-900',
|
||||
dark: 'bg-gray-900 text-white'
|
||||
}
|
||||
|
||||
const radiusClasses = {
|
||||
none: 'rounded-none',
|
||||
sm: 'rounded-sm',
|
||||
md: 'rounded-md',
|
||||
lg: 'rounded-lg'
|
||||
}
|
||||
|
||||
const fontSizeClasses = {
|
||||
sm: 'text-sm',
|
||||
base: 'text-base',
|
||||
lg: 'text-lg'
|
||||
}
|
||||
|
||||
return (
|
||||
<ChatSection
|
||||
handler={handler}
|
||||
className={`
|
||||
${themeClasses[theme.mode]}
|
||||
${fontSizeClasses[theme.fontSize]}
|
||||
transition-all duration-200
|
||||
`}
|
||||
>
|
||||
<ChatMessages
|
||||
className={`
|
||||
${radiusClasses[theme.borderRadius]}
|
||||
border border-current/10
|
||||
`}
|
||||
/>
|
||||
|
||||
<ChatInput
|
||||
className={`
|
||||
${radiusClasses[theme.borderRadius]}
|
||||
border border-current/20
|
||||
`}
|
||||
/>
|
||||
</ChatSection>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Animation and Transitions
|
||||
|
||||
### Message Animations
|
||||
|
||||
```tsx
|
||||
import { motion, AnimatePresence } from 'framer-motion'
|
||||
|
||||
function AnimatedMessages() {
|
||||
const { messages } = useChatUI()
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{messages.map((message, index) => (
|
||||
<motion.div
|
||||
key={message.id}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -20 }}
|
||||
transition={{ duration: 0.3, delay: index * 0.1 }}
|
||||
>
|
||||
<ChatMessage message={message}>
|
||||
{/* Message content */}
|
||||
</ChatMessage>
|
||||
</motion.div>
|
||||
))}
|
||||
</AnimatePresence>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Typing Indicator
|
||||
|
||||
```tsx
|
||||
function TypingIndicator() {
|
||||
const { isLoading } = useChatUI()
|
||||
|
||||
if (!isLoading) return null
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.8 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0.8 }}
|
||||
className="flex items-center gap-2 p-4"
|
||||
>
|
||||
<div className="flex gap-1">
|
||||
{[0, 1, 2].map((i) => (
|
||||
<motion.div
|
||||
key={i}
|
||||
className="h-2 w-2 bg-gray-400 rounded-full"
|
||||
animate={{
|
||||
scale: [1, 1.2, 1],
|
||||
opacity: [0.5, 1, 0.5]
|
||||
}}
|
||||
transition={{
|
||||
duration: 1.5,
|
||||
repeat: Infinity,
|
||||
delay: i * 0.2
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<span className="text-sm text-gray-500">AI is typing...</span>
|
||||
</motion.div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Responsive Design
|
||||
|
||||
### Mobile-First Layout
|
||||
|
||||
```tsx
|
||||
function ResponsiveChatLayout() {
|
||||
const [isMobile, setIsMobile] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const checkMobile = () => setIsMobile(window.innerWidth < 768)
|
||||
checkMobile()
|
||||
window.addEventListener('resize', checkMobile)
|
||||
return () => window.removeEventListener('resize', checkMobile)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<ChatSection
|
||||
handler={handler}
|
||||
className={isMobile ? 'flex-col h-full' : 'flex-row h-full'}
|
||||
>
|
||||
<div className={isMobile ? 'flex-1' : 'flex-1 flex flex-col'}>
|
||||
<ChatMessages className={isMobile ? 'flex-1' : 'flex-1'} />
|
||||
<ChatInput className={isMobile ? 'sticky bottom-0' : ''} />
|
||||
</div>
|
||||
|
||||
{!isMobile && <ChatCanvas className="w-1/2" />}
|
||||
</ChatSection>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Accessibility Customization
|
||||
|
||||
### Enhanced Accessibility
|
||||
|
||||
```tsx
|
||||
function AccessibleChat() {
|
||||
const { messages, isLoading } = useChatUI()
|
||||
|
||||
return (
|
||||
<div
|
||||
role="log"
|
||||
aria-live="polite"
|
||||
aria-label="Chat conversation"
|
||||
className="relative"
|
||||
>
|
||||
<ChatMessages>
|
||||
<ChatMessages.List
|
||||
role="log"
|
||||
aria-busy={isLoading}
|
||||
>
|
||||
{messages.map((message, index) => (
|
||||
<div
|
||||
key={message.id}
|
||||
role="article"
|
||||
aria-label={`Message from ${message.role}`}
|
||||
tabIndex={0}
|
||||
className="focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
>
|
||||
<ChatMessage message={message} />
|
||||
</div>
|
||||
))}
|
||||
</ChatMessages.List>
|
||||
</ChatMessages>
|
||||
|
||||
<ChatInput>
|
||||
<ChatInput.Form>
|
||||
<ChatInput.Field
|
||||
aria-label="Type your message"
|
||||
aria-describedby="chat-input-help"
|
||||
/>
|
||||
<div id="chat-input-help" className="sr-only">
|
||||
Press Enter to send, Shift+Enter for new line
|
||||
</div>
|
||||
<ChatInput.Submit aria-label="Send message" />
|
||||
</ChatInput.Form>
|
||||
</ChatInput>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Examples](./examples) - See complete customization examples
|
||||
- [Core Components](./core-components) - Understand component structure for customization
|
||||
- [Widgets](./widgets) - Customize widget appearance and behavior
|
||||
- [Hooks](./hooks) - Use hooks for dynamic customization
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
title: Examples
|
||||
description: Real-world implementation examples and use cases
|
||||
---
|
||||
|
||||
This section provides complete, working examples that demonstrate how to implement various features and use cases with LlamaIndex Chat UI.
|
||||
|
||||
## Examples
|
||||
|
||||
[Examples](https://github.com/run-llama/chat-ui/tree/main/examples)
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Explore the [NextJS Example](https://github.com/run-llama/chat-ui/tree/main/examples/nextjs) in the repository
|
||||
- Check out the [FastAPI Example](https://github.com/run-llama/chat-ui/tree/main/examples/fastapi) for backend integration
|
||||
- Review the [Core Components](./core-components) documentation for detailed API references
|
||||
- See [Customization](./customization) for styling and theming options
|
||||
@@ -0,0 +1,282 @@
|
||||
---
|
||||
title: Getting Started
|
||||
description: Learn how to install and set up LlamaIndex Chat UI in your project
|
||||
---
|
||||
|
||||
This guide will help you get started with LlamaIndex Chat UI, from installation to building your first chat interface.
|
||||
|
||||
## Quick Start
|
||||
|
||||
The fastest way to add a chatbot to your project is using the Shadcn CLI command:
|
||||
|
||||
```shell
|
||||
npx shadcn@latest add https://ui.llamaindex.ai/r/chat.json
|
||||
```
|
||||
|
||||
## Manual Installation
|
||||
|
||||
Install the package using your preferred package manager:
|
||||
|
||||
```shell
|
||||
npm install @llamaindex/chat-ui
|
||||
|
||||
yarn add @llamaindex/chat-ui
|
||||
|
||||
pnpm add @llamaindex/chat-ui
|
||||
|
||||
bun add @llamaindex/chat-ui
|
||||
```
|
||||
|
||||
## Peer Dependencies
|
||||
|
||||
Chat UI requires React 18+:
|
||||
|
||||
```shell
|
||||
npm install react react-dom
|
||||
```
|
||||
|
||||
## Basic Setup
|
||||
|
||||
### 1. Configure Tailwind CSS
|
||||
|
||||
**For Tailwind CSS version 4.x**, update `globals.css` to include the chat-ui components (update the relative path to node_modules if necessary):
|
||||
|
||||
```css
|
||||
@source '../node_modules/@llamaindex/chat-ui/**/*.{ts,tsx}';
|
||||
```
|
||||
|
||||
**For Tailwind CSS version 3.x**, add the following to your `tailwind.config.ts` file:
|
||||
|
||||
```javascript
|
||||
// tailwind.config.js
|
||||
module.exports = {
|
||||
content: [
|
||||
'./app/**/*.{js,ts,jsx,tsx}',
|
||||
'./node_modules/@llamaindex/chat-ui/**/*.{ts,tsx}',
|
||||
],
|
||||
// ... rest of config
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Import Styles
|
||||
|
||||
Import the CSS styles in your app's root file (e.g., `_app.tsx` or `layout.tsx`):
|
||||
|
||||
```tsx
|
||||
import '@llamaindex/chat-ui/styles/markdown.css' // code, latex and custom markdown styling
|
||||
import '@llamaindex/chat-ui/styles/pdf.css' // pdf styling
|
||||
```
|
||||
|
||||
The `markdown.css` file includes styling for code blocks using [highlight.js](https://highlightjs.org/) with the `atom-one-dark` theme by default, [katex](https://katex.org/) for latex, and [pdf-viewer](https://github.com/run-llama/pdf-viewer) for PDF files. You can use any highlight.js theme by copying [their CSS](https://github.com/highlightjs/highlight.js/tree/main/src/styles/) to your project and importing it.
|
||||
|
||||
### 3. Create a Chat API Route
|
||||
|
||||
Set up an API route to handle chat requests. Here's an example using Next.js:
|
||||
|
||||
```typescript
|
||||
// app/api/chat/route.ts
|
||||
import { NextResponse } from 'next/server'
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const { messages } = await request.json()
|
||||
|
||||
// Your chat logic here
|
||||
const response = await generateChatResponse(messages)
|
||||
|
||||
return new Response(response, {
|
||||
headers: {
|
||||
'Content-Type': 'text/plain; charset=utf-8',
|
||||
'X-Vercel-AI-Data-Stream': 'v1',
|
||||
},
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Create Your Chat Component
|
||||
|
||||
The easiest way to get started is to connect the whole `ChatSection` component with `useChat` hook from [vercel/ai](https://github.com/vercel/ai):
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
|
||||
import { ChatSection } from '@llamaindex/chat-ui'
|
||||
import { useChat } from 'ai/react'
|
||||
|
||||
export default function Chat() {
|
||||
const handler = useChat({
|
||||
api: '/api/chat',
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="h-screen">
|
||||
<ChatSection handler={handler} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Component Composition
|
||||
|
||||
Components are designed to be composable. You can use them as is with the simple `ChatSection`, or extend them with your own children components:
|
||||
|
||||
```tsx
|
||||
import { ChatSection, ChatMessages, ChatInput } from '@llamaindex/chat-ui'
|
||||
import LlamaCloudSelector from './components/LlamaCloudSelector' // your custom component
|
||||
import { useChat } from 'ai/react'
|
||||
|
||||
const ChatExample = () => {
|
||||
const handler = useChat()
|
||||
return (
|
||||
<ChatSection handler={handler}>
|
||||
<ChatMessages />
|
||||
<ChatInput>
|
||||
<ChatInput.Form className="bg-lime-500">
|
||||
<ChatInput.Field type="textarea" />
|
||||
<ChatInput.Upload />
|
||||
<LlamaCloudSelector /> {/* custom component */}
|
||||
<ChatInput.Submit />
|
||||
</ChatInput.Form>
|
||||
</ChatInput>
|
||||
</ChatSection>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Your custom component can use the `useChatUI` hook to send additional data to the chat API endpoint:
|
||||
|
||||
```tsx
|
||||
import { useChatUI } from '@llamaindex/chat-ui'
|
||||
|
||||
const LlamaCloudSelector = () => {
|
||||
const { requestData, setRequestData } = useChatUI()
|
||||
return (
|
||||
<div>
|
||||
<select
|
||||
value={requestData?.model}
|
||||
onChange={e => setRequestData({ model: e.target.value })}
|
||||
>
|
||||
<option value="llama-3.1-70b-instruct">Pipeline 1</option>
|
||||
<option value="llama-3.1-8b-instruct">Pipeline 2</option>
|
||||
</select>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Styling
|
||||
|
||||
### Components
|
||||
|
||||
`chat-ui` components are based on [shadcn](https://ui.shadcn.com/) components using Tailwind CSS.
|
||||
|
||||
You can override the default styles by changing CSS variables in the `globals.css` file of your Tailwind CSS configuration. For example, to change the background and foreground colors:
|
||||
|
||||
```css
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 84% 4.9%;
|
||||
}
|
||||
```
|
||||
|
||||
For a list of all available CSS variables, please refer to the [Shadcn Theme Config](https://ui.shadcn.com/themes).
|
||||
|
||||
Additionally, you can also override each component's styles by setting custom classes in the `className` prop. For example, setting the background color of the `ChatInput.Form` component:
|
||||
|
||||
```tsx
|
||||
import { ChatSection, ChatMessages, ChatInput } from '@llamaindex/chat-ui'
|
||||
import { useChat } from 'ai/react'
|
||||
|
||||
const ChatExample = () => {
|
||||
const handler = useChat()
|
||||
return (
|
||||
<ChatSection handler={handler}>
|
||||
<ChatMessages />
|
||||
<ChatInput>
|
||||
<ChatInput.Preview />
|
||||
<ChatInput.Form className="bg-lime-500">
|
||||
<ChatInput.Field type="textarea" />
|
||||
<ChatInput.Upload />
|
||||
<ChatInput.Submit />
|
||||
</ChatInput.Form>
|
||||
</ChatInput>
|
||||
</ChatSection>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Advanced Features
|
||||
|
||||
### Custom Layout
|
||||
|
||||
For more control over the layout, compose components manually:
|
||||
|
||||
```tsx
|
||||
import {
|
||||
ChatSection,
|
||||
ChatMessages,
|
||||
ChatInput,
|
||||
ChatCanvas,
|
||||
} from '@llamaindex/chat-ui'
|
||||
|
||||
function CustomChat() {
|
||||
const handler = useChat({ api: '/api/chat' })
|
||||
|
||||
return (
|
||||
<ChatSection handler={handler} className="flex-row gap-4">
|
||||
<div className="flex-1">
|
||||
<ChatMessages />
|
||||
<ChatInput />
|
||||
</div>
|
||||
<ChatCanvas className="w-1/3" />
|
||||
</ChatSection>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### With Initial Messages
|
||||
|
||||
Provide initial context or welcome messages:
|
||||
|
||||
```tsx
|
||||
const handler = useChat({
|
||||
api: '/api/chat',
|
||||
initialMessages: [
|
||||
{
|
||||
id: '1',
|
||||
role: 'assistant',
|
||||
content: 'Hello! How can I help you today?',
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
### Language Renderer Support
|
||||
|
||||
For any language that the LLM generates, you can specify a custom renderer to render the output. For example, you can render mermaid code as SVG using a custom renderer.
|
||||
|
||||
## Next Steps
|
||||
|
||||
Now that you have a basic chat interface running:
|
||||
|
||||
1. **Explore Components** - Learn about [Core Components](./core-components) for customization
|
||||
2. **Add Rich Content** - Implement [Annotations](./annotations) for images, files, and sources
|
||||
3. **Enable Artifacts** - Set up [Artifacts](./artifacts) for interactive code and documents
|
||||
4. **Customize Styling** - Read the [Customization](./customization) guide for theming
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
**Styles not loading**: Make sure you've imported the CSS files in your app root and configured Tailwind CSS properly.
|
||||
|
||||
**TypeScript errors**: Ensure you have the correct peer dependencies and TypeScript configuration.
|
||||
|
||||
**Build errors**: Check that your bundler supports the package's export conditions.
|
||||
|
||||
**Chat not working**: Verify your API route is returning the correct response format for the Vercel AI SDK.
|
||||
|
||||
### Getting Help
|
||||
|
||||
- Check the [Examples](./examples) for working implementations
|
||||
- Review the component documentation for detailed API references
|
||||
- Open an issue on GitHub for bugs or feature requests
|
||||
@@ -0,0 +1,504 @@
|
||||
---
|
||||
title: Hooks
|
||||
description: Understanding the hook system for accessing chat state and functionality
|
||||
---
|
||||
|
||||
The hook system provides access to chat state, context, and functionality throughout the component tree. These hooks enable deep customization and integration with the chat interface.
|
||||
|
||||
## Context Hooks
|
||||
|
||||
### useChatUI
|
||||
|
||||
The primary hook for accessing chat state and handlers to modify the chat state.
|
||||
|
||||
```tsx
|
||||
import { useChatUI } from '@llamaindex/chat-ui'
|
||||
|
||||
function CustomChatComponent() {
|
||||
const {
|
||||
messages,
|
||||
input,
|
||||
setInput,
|
||||
isLoading,
|
||||
error,
|
||||
append,
|
||||
reload,
|
||||
stop,
|
||||
setMessages,
|
||||
requestData,
|
||||
setRequestData,
|
||||
} = useChatUI()
|
||||
|
||||
const handleSendMessage = async () => {
|
||||
await append({
|
||||
role: 'user',
|
||||
content: input,
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>Messages: {messages.length}</p>
|
||||
<p>Loading: {isLoading ? 'Yes' : 'No'}</p>
|
||||
{error && <p>Error: {error.message}</p>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
**Returned Properties:**
|
||||
|
||||
- `messages` - Array of chat messages
|
||||
- `input` - Current input value
|
||||
- `setInput` - Function to update input
|
||||
- `isLoading` - Loading state boolean
|
||||
- `error` - Error object if any
|
||||
- `append` - Function to add message
|
||||
- `reload` - Function to reload last message
|
||||
- `stop` - Function to stop current generation
|
||||
- `setMessages` - Function to update message array
|
||||
- `requestData` - Additional request data
|
||||
- `setRequestData` - Function to update request data
|
||||
|
||||
### useChatMessage
|
||||
|
||||
Access the current message context within message components.
|
||||
|
||||
```tsx
|
||||
import { useChatMessage } from '@llamaindex/chat-ui'
|
||||
|
||||
function CustomMessageContent() {
|
||||
const { message, isLast } = useChatMessage()
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>Role: {message.role}</p>
|
||||
<p>Content: {message.content}</p>
|
||||
<p>Is last message: {isLast ? 'Yes' : 'No'}</p>
|
||||
{message.annotations && (
|
||||
<p>Has annotations: {message.annotations.length}</p>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
**Returned Properties:**
|
||||
|
||||
- `message` - Current message object
|
||||
- `isLast` - Boolean indicating if this is the last message
|
||||
|
||||
### useChatInput
|
||||
|
||||
Access input form state and handlers.
|
||||
|
||||
```tsx
|
||||
import { useChatInput } from '@llamaindex/chat-ui'
|
||||
|
||||
function CustomInputField() {
|
||||
const {
|
||||
input,
|
||||
setInput,
|
||||
handleSubmit,
|
||||
isLoading,
|
||||
onFileUpload,
|
||||
uploadedFiles,
|
||||
} = useChatInput()
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<textarea
|
||||
value={input}
|
||||
onChange={e => setInput(e.target.value)}
|
||||
disabled={isLoading}
|
||||
placeholder="Type your message..."
|
||||
/>
|
||||
<p>Uploaded files: {uploadedFiles.length}</p>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
**Returned Properties:**
|
||||
|
||||
- `input` - Current input value
|
||||
- `setInput` - Function to update input
|
||||
- `handleSubmit` - Form submit handler
|
||||
- `isLoading` - Loading state
|
||||
- `onFileUpload` - File upload handler
|
||||
- `uploadedFiles` - Array of uploaded files
|
||||
|
||||
### useChatMessages
|
||||
|
||||
Access messages list state and handlers.
|
||||
|
||||
```tsx
|
||||
import { useChatMessages } from '@llamaindex/chat-ui'
|
||||
|
||||
function CustomMessageList() {
|
||||
const { messages, isLoading, reload, stop, isEmpty, scrollToBottom } =
|
||||
useChatMessages()
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="messages">
|
||||
{messages.map((msg, i) => (
|
||||
<div key={i}>{msg.content}</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{isEmpty && <p>No messages yet</p>}
|
||||
|
||||
<button onClick={reload} disabled={isLoading}>
|
||||
Reload
|
||||
</button>
|
||||
<button onClick={stop}>Stop</button>
|
||||
<button onClick={scrollToBottom}>Scroll to Bottom</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
**Returned Properties:**
|
||||
|
||||
- `messages` - Array of messages
|
||||
- `isLoading` - Loading state
|
||||
- `reload` - Reload last message
|
||||
- `stop` - Stop generation
|
||||
- `isEmpty` - Boolean if no messages
|
||||
- `scrollToBottom` - Function to scroll to bottom
|
||||
|
||||
### useChatCanvas
|
||||
|
||||
Access artifact canvas state and handlers.
|
||||
|
||||
```tsx
|
||||
import { useChatCanvas } from '@llamaindex/chat-ui'
|
||||
|
||||
function CustomCanvasControls() {
|
||||
const {
|
||||
currentArtifact,
|
||||
isVisible,
|
||||
showCanvas,
|
||||
hideCanvas,
|
||||
updateArtifact,
|
||||
artifacts,
|
||||
} = useChatCanvas()
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>Canvas visible: {isVisible ? 'Yes' : 'No'}</p>
|
||||
<p>Current artifact: {currentArtifact?.title || 'None'}</p>
|
||||
<p>Total artifacts: {artifacts.length}</p>
|
||||
|
||||
<button onClick={showCanvas}>Show Canvas</button>
|
||||
<button onClick={hideCanvas}>Hide Canvas</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
**Returned Properties:**
|
||||
|
||||
- `currentArtifact` - Currently displayed artifact
|
||||
- `isVisible` - Canvas visibility state
|
||||
- `showCanvas` - Function to show canvas
|
||||
- `hideCanvas` - Function to hide canvas
|
||||
- `updateArtifact` - Function to update artifact
|
||||
- `artifacts` - Array of all artifacts
|
||||
|
||||
## Utility Hooks
|
||||
|
||||
### useFile
|
||||
|
||||
Handle file uploads and processing.
|
||||
|
||||
```tsx
|
||||
import { useFile } from '@llamaindex/chat-ui'
|
||||
|
||||
function FileUploadComponent() {
|
||||
const {
|
||||
uploadFile,
|
||||
uploadFiles,
|
||||
isUploading,
|
||||
uploadedFiles,
|
||||
removeFile,
|
||||
clearFiles,
|
||||
} = useFile({
|
||||
maxSize: 10 * 1024 * 1024, // 10MB
|
||||
accept: ['image/*', 'application/pdf'],
|
||||
multiple: true,
|
||||
})
|
||||
|
||||
const handleFileSelect = async (files: FileList) => {
|
||||
await uploadFiles(Array.from(files))
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<input
|
||||
type="file"
|
||||
multiple
|
||||
onChange={e => handleFileSelect(e.target.files!)}
|
||||
/>
|
||||
|
||||
{isUploading && <p>Uploading...</p>}
|
||||
|
||||
<div>
|
||||
{uploadedFiles.map((file, i) => (
|
||||
<div key={i}>
|
||||
<span>{file.name}</span>
|
||||
<button onClick={() => removeFile(i)}>Remove</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<button onClick={clearFiles}>Clear All</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
**Options:**
|
||||
|
||||
- `maxSize` - Maximum file size in bytes
|
||||
- `accept` - Array of accepted MIME types
|
||||
- `multiple` - Allow multiple files
|
||||
- `onUpload` - Upload completion callback
|
||||
- `onError` - Error callback
|
||||
|
||||
**Returned Properties:**
|
||||
|
||||
- `uploadFile` - Upload single file
|
||||
- `uploadFiles` - Upload multiple files
|
||||
- `isUploading` - Upload state
|
||||
- `uploadedFiles` - Array of uploaded files
|
||||
- `removeFile` - Remove file by index
|
||||
- `clearFiles` - Clear all files
|
||||
|
||||
### useCopyToClipboard
|
||||
|
||||
Copy content to clipboard with feedback.
|
||||
|
||||
```tsx
|
||||
import { useCopyToClipboard } from '@llamaindex/chat-ui'
|
||||
|
||||
function CopyButton({ content }) {
|
||||
const { copyToClipboard, isCopied } = useCopyToClipboard({
|
||||
timeout: 2000, // Reset after 2 seconds
|
||||
})
|
||||
|
||||
const handleCopy = () => {
|
||||
copyToClipboard(content)
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={handleCopy}
|
||||
className={isCopied ? 'text-green-600' : 'text-gray-600'}
|
||||
>
|
||||
{isCopied ? 'Copied!' : 'Copy'}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
**Options:**
|
||||
|
||||
- `timeout` - Time before resetting copied state
|
||||
|
||||
**Returned Properties:**
|
||||
|
||||
- `copyToClipboard` - Function to copy text
|
||||
- `isCopied` - Boolean indicating if recently copied
|
||||
|
||||
## Hook Composition
|
||||
|
||||
### Custom Chat Hook
|
||||
|
||||
Create custom hooks by composing existing ones:
|
||||
|
||||
```tsx
|
||||
import { useChatUI, useChatMessages } from '@llamaindex/chat-ui'
|
||||
|
||||
function useCustomChat() {
|
||||
const chatUI = useChatUI()
|
||||
const messages = useChatMessages()
|
||||
|
||||
const sendMessageWithMetadata = async (content: string, metadata: any) => {
|
||||
await chatUI.append(
|
||||
{
|
||||
role: 'user',
|
||||
content,
|
||||
},
|
||||
{ data: metadata }
|
||||
)
|
||||
}
|
||||
|
||||
const getLastAssistantMessage = () => {
|
||||
return chatUI.messages.filter(m => m.role === 'assistant').pop()
|
||||
}
|
||||
|
||||
return {
|
||||
...chatUI,
|
||||
...messages,
|
||||
sendMessageWithMetadata,
|
||||
getLastAssistantMessage,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Message Analytics Hook
|
||||
|
||||
Track message interactions:
|
||||
|
||||
```tsx
|
||||
import { useChatMessage, useChatUI } from '@llamaindex/chat-ui'
|
||||
|
||||
function useMessageAnalytics() {
|
||||
const { message } = useChatMessage()
|
||||
const { requestData, setRequestData } = useChatUI()
|
||||
|
||||
const trackMessageView = () => {
|
||||
// Track message viewed
|
||||
console.log('Message viewed:', message.id)
|
||||
}
|
||||
|
||||
const trackMessageCopy = () => {
|
||||
// Track message copied
|
||||
setRequestData({
|
||||
...requestData,
|
||||
copiedMessages: [...(requestData?.copiedMessages || []), message.id],
|
||||
})
|
||||
}
|
||||
|
||||
const trackMessageRegenerate = () => {
|
||||
// Track message regenerated
|
||||
console.log('Message regenerated:', message.id)
|
||||
}
|
||||
|
||||
return {
|
||||
trackMessageView,
|
||||
trackMessageCopy,
|
||||
trackMessageRegenerate,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Hook Usage Patterns
|
||||
|
||||
### Conditional Rendering
|
||||
|
||||
```tsx
|
||||
function ConditionalComponent() {
|
||||
const { isLoading, error, messages } = useChatUI()
|
||||
|
||||
if (isLoading) return <LoadingSpinner />
|
||||
if (error) return <ErrorMessage error={error} />
|
||||
if (messages.length === 0) return <EmptyState />
|
||||
|
||||
return <MessageList />
|
||||
}
|
||||
```
|
||||
|
||||
### Event Handling
|
||||
|
||||
```tsx
|
||||
function EventHandler() {
|
||||
const { append, setRequestData } = useChatUI()
|
||||
|
||||
const handleQuestionClick = async (question: string) => {
|
||||
// Track question selection
|
||||
setRequestData({ selectedQuestion: question })
|
||||
|
||||
// Send message
|
||||
await append({
|
||||
role: 'user',
|
||||
content: question,
|
||||
})
|
||||
}
|
||||
|
||||
return <SuggestedQuestions onQuestionSelect={handleQuestionClick} />
|
||||
}
|
||||
```
|
||||
|
||||
### Data Transformation
|
||||
|
||||
```tsx
|
||||
function MessageStats() {
|
||||
const { messages } = useChatUI()
|
||||
|
||||
const stats = useMemo(() => {
|
||||
const userMessages = messages.filter(m => m.role === 'user').length
|
||||
const assistantMessages = messages.filter(
|
||||
m => m.role === 'assistant'
|
||||
).length
|
||||
const totalChars = messages.reduce((sum, m) => sum + m.content.length, 0)
|
||||
|
||||
return { userMessages, assistantMessages, totalChars }
|
||||
}, [messages])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>User messages: {stats.userMessages}</p>
|
||||
<p>Assistant messages: {stats.assistantMessages}</p>
|
||||
<p>Total characters: {stats.totalChars}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Hook Context Boundaries
|
||||
|
||||
### Context Availability
|
||||
|
||||
Different hooks are available in different contexts:
|
||||
|
||||
```tsx
|
||||
function App() {
|
||||
return (
|
||||
<ChatSection handler={handler}>
|
||||
{/* useChatUI available here */}
|
||||
<ChatMessages>
|
||||
{/* useChatMessages available here */}
|
||||
<ChatMessages.List>
|
||||
{messages.map(message => (
|
||||
<ChatMessage key={message.id} message={message}>
|
||||
{/* useChatMessage available here */}
|
||||
<CustomMessageContent />
|
||||
</ChatMessage>
|
||||
))}
|
||||
</ChatMessages.List>
|
||||
</ChatMessages>
|
||||
|
||||
<ChatInput>
|
||||
{/* useChatInput available here */}
|
||||
<CustomInputField />
|
||||
</ChatInput>
|
||||
</ChatSection>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Error Boundaries
|
||||
|
||||
Handle hook errors gracefully:
|
||||
|
||||
```tsx
|
||||
function SafeHookUsage() {
|
||||
try {
|
||||
const { messages } = useChatUI()
|
||||
return <MessageList messages={messages} />
|
||||
} catch (error) {
|
||||
console.error('Hook error:', error)
|
||||
return <ErrorFallback />
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Annotations](./annotations) - Learn how hooks work with annotation data
|
||||
- [Customization](./customization) - Use hooks for custom styling and behavior
|
||||
- [Examples](./examples) - See complete examples using hooks
|
||||
- [Core Components](./core-components) - Understand component-hook relationships
|
||||
@@ -0,0 +1,89 @@
|
||||
---
|
||||
title: LlamaIndex Chat UI
|
||||
description: A React component library for building modern chat interfaces with LLM applications
|
||||
---
|
||||
|
||||
LlamaIndex Chat UI is a comprehensive React component library designed for building sophisticated chat interfaces in LLM applications. It provides pre-built, customizable components that integrate seamlessly with any backend by using the Vercel's [Streaming Protocol](https://ai-sdk.dev/docs/ai-sdk-ui/stream-protocol).
|
||||
|
||||
## Key Features
|
||||
|
||||
- **Complete Chat Interface** - Full-featured chat components with message history, input, and OpenAI-style canvas
|
||||
- **Rich Annotations** - Support for images, files, sources, events, and custom annotations
|
||||
- **Interactive Artifacts** - Code and document artifacts with editing and version management
|
||||
- **File Upload Support** - Built-in handling for multiple file types (PDF, images, documents)
|
||||
- **Beautiful** - Built on shadcn/ui for beautiful UI
|
||||
- **Composable Architecture** - Highly customizable components that can be composed together
|
||||
- **TypeScript First** - Full type safety and excellent developer experience
|
||||
- **Vercel Streaming Protocol** - Designed to work seamlessly with `useChat` hook in the frontend and any backend that implements the protocol
|
||||
|
||||
## Core Components
|
||||
|
||||
- **ChatSection** - Main container that provides context and layout
|
||||
- **ChatMessages** - Scrollable message history with loading states
|
||||
- **ChatMessage** - Individual message display with role-based styling
|
||||
- **ChatInput** - Input interface with file upload and keyboard shortcuts
|
||||
- **ChatCanvas** - Side panel for displaying interactive code or document artifacts
|
||||
|
||||
## Widget System
|
||||
|
||||
The library includes a comprehensive widget system for handling various content types:
|
||||
|
||||
- **Content Widgets** - Markdown, code blocks, image display
|
||||
- **Annotation Widgets** - Sources, events, suggested questions
|
||||
- **Interactive Widgets** - File upload, document editing, code editing
|
||||
|
||||
## Getting Started
|
||||
|
||||
```shell
|
||||
npm install @llamaindex/chat-ui
|
||||
|
||||
yarn add @llamaindex/chat-ui
|
||||
|
||||
pnpm add @llamaindex/chat-ui
|
||||
```
|
||||
|
||||
## Quick Example
|
||||
|
||||
```tsx
|
||||
import { ChatSection } from '@llamaindex/chat-ui'
|
||||
import { useChat } from 'ai/react'
|
||||
|
||||
export default function MyChat() {
|
||||
const handler = useChat({
|
||||
api: '/api/chat',
|
||||
})
|
||||
|
||||
return <ChatSection handler={handler} />
|
||||
}
|
||||
```
|
||||
|
||||
This creates a complete chat interface with:
|
||||
|
||||
- Message history display
|
||||
- User input with file upload
|
||||
- Loading states and error handling
|
||||
- Support for rich content and annotations
|
||||
|
||||
## Architecture
|
||||
|
||||
The library follows a composable architecture where you can:
|
||||
|
||||
1. **Use the complete solution** - `ChatSection` provides everything out of the box
|
||||
2. **Compose custom layouts** - Mix and match components for custom designs
|
||||
3. **Extend with widgets** - Add specialized content handling
|
||||
4. **Create custom annotations** - Build domain-specific content types
|
||||
|
||||
## Integration
|
||||
|
||||
Chat UI is designed to work with any chat backend that implements Vercel's [Streaming Protocol](https://ai-sdk.dev/docs/ai-sdk-ui/stream-protocol), to simplify getting started we provide examples for:
|
||||
|
||||
- **NextJS** - [NextJS Example](https://github.com/run-llama/chat-ui/tree/main/examples/nextjs)
|
||||
- **FastAPI** - [FastAPI Example](https://github.com/run-llama/chat-ui/tree/main/examples/fastapi)
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Getting Started](./getting-started) - Installation and basic setup
|
||||
- [Core Components](./core-components) - Detailed guide to main components
|
||||
- [Widgets](./widgets) - Comprehensive widget documentation
|
||||
- [Annotations](./annotations) - Working with rich content
|
||||
- [Examples](./examples) - Real-world implementation examples
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"title": "Chat UI",
|
||||
"description": "React component library for building chat interfaces",
|
||||
"root": true,
|
||||
"defaultOpen": true,
|
||||
"pages": [
|
||||
"index",
|
||||
"getting-started",
|
||||
"core-components",
|
||||
"widgets",
|
||||
"hooks",
|
||||
"annotations",
|
||||
"artifacts",
|
||||
"customization",
|
||||
"examples"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,535 @@
|
||||
---
|
||||
title: Widgets
|
||||
description: Comprehensive guide to specialized content widgets for rich chat experiences
|
||||
---
|
||||
|
||||
Widgets are specialized components for displaying and interacting with different types of content in chat messages. They provide rich functionality beyond simple text, enabling multimedia, interactive elements, and custom annotations.
|
||||
|
||||
## Content Widgets
|
||||
|
||||
### Markdown
|
||||
|
||||
Renders rich text with LaTeX math support, syntax highlighting, and citations.
|
||||
|
||||
```tsx
|
||||
import { Markdown } from '@llamaindex/chat-ui/widgets'
|
||||
|
||||
function RichText({ content }) {
|
||||
return (
|
||||
<Markdown>
|
||||
{content}
|
||||
</Markdown>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- **LaTeX Math** - Inline and block math rendering with KaTeX
|
||||
- **Code Highlighting** - Syntax highlighting with highlight.js
|
||||
- **Citations** - Clickable citation links
|
||||
- **Custom Renderers** - Extensible rendering pipeline
|
||||
|
||||
**Example Content:**
|
||||
```markdown
|
||||
# Mathematical Formula
|
||||
|
||||
The quadratic formula is: $x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}$
|
||||
|
||||
## Code Example
|
||||
|
||||
```python
|
||||
def fibonacci(n):
|
||||
if n <= 1:
|
||||
return n
|
||||
return fibonacci(n-1) + fibonacci(n-2)
|
||||
```
|
||||
|
||||
This algorithm has O(2^n) complexity [^1].
|
||||
|
||||
[^1]: Introduction to Algorithms, Chapter 15
|
||||
```
|
||||
|
||||
### CodeBlock
|
||||
|
||||
Displays syntax-highlighted code with copy functionality.
|
||||
|
||||
```tsx
|
||||
import { CodeBlock } from '@llamaindex/chat-ui/widgets'
|
||||
|
||||
function CodeDisplay({ code, language, filename }) {
|
||||
return (
|
||||
<CodeBlock
|
||||
code={code}
|
||||
language={language}
|
||||
filename={filename}
|
||||
showLineNumbers={true}
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
**Props:**
|
||||
- `code` - Source code string
|
||||
- `language` - Programming language for highlighting
|
||||
- `filename` - Optional filename display
|
||||
- `showLineNumbers` - Show/hide line numbers
|
||||
|
||||
### CodeEditor
|
||||
|
||||
Interactive code editor with multiple language support.
|
||||
|
||||
```tsx
|
||||
import { CodeEditor } from '@llamaindex/chat-ui/widgets'
|
||||
|
||||
function InteractiveCode({ initialCode, language, onChange }) {
|
||||
return (
|
||||
<CodeEditor
|
||||
value={initialCode}
|
||||
language={language}
|
||||
onChange={onChange}
|
||||
theme="dark"
|
||||
extensions={['search', 'fold']}
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
**Supported Languages:**
|
||||
- JavaScript/TypeScript
|
||||
- Python
|
||||
- CSS/SCSS
|
||||
- HTML
|
||||
- JSON
|
||||
- Markdown
|
||||
|
||||
**Features:**
|
||||
- **Syntax Highlighting** - Real-time highlighting
|
||||
- **Auto-completion** - Language-aware suggestions
|
||||
- **Code Folding** - Collapse code blocks
|
||||
- **Search & Replace** - Built-in search functionality
|
||||
- **Multiple Themes** - Light and dark themes
|
||||
|
||||
### DocumentEditor
|
||||
|
||||
Rich text document editor with markdown support.
|
||||
|
||||
```tsx
|
||||
import { DocumentEditor } from '@llamaindex/chat-ui/widgets'
|
||||
|
||||
function DocumentEdit({ content, onChange, title }) {
|
||||
return (
|
||||
<DocumentEditor
|
||||
value={content}
|
||||
onChange={onChange}
|
||||
title={title}
|
||||
placeholder="Start writing..."
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- **WYSIWYG Editing** - Visual editing with markdown output
|
||||
- **Formatting Tools** - Bold, italic, lists, headers
|
||||
- **Link Support** - Insert and edit links
|
||||
- **Image Support** - Embed images
|
||||
- **Live Preview** - Real-time markdown preview
|
||||
|
||||
## Annotation Widgets
|
||||
|
||||
### ChatImage
|
||||
|
||||
Displays images with preview and zoom functionality.
|
||||
|
||||
```tsx
|
||||
import { ChatImage } from '@llamaindex/chat-ui/widgets'
|
||||
|
||||
// From annotation data
|
||||
const imageAnnotation = {
|
||||
type: 'IMAGE',
|
||||
data: {
|
||||
url: 'https://example.com/image.jpg',
|
||||
alt: 'Description of the image'
|
||||
}
|
||||
}
|
||||
|
||||
function ImageDisplay() {
|
||||
return <ChatImage /> // Automatically renders from context
|
||||
}
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- **Zoom & Pan** - Interactive image viewing
|
||||
- **Lazy Loading** - Performance optimization
|
||||
- **Alt Text** - Accessibility support
|
||||
- **Error Handling** - Graceful fallback for broken images
|
||||
|
||||
### ChatFiles
|
||||
|
||||
Displays file attachments with download and preview.
|
||||
|
||||
```tsx
|
||||
import { ChatFiles } from '@llamaindex/chat-ui/widgets'
|
||||
|
||||
// From annotation data
|
||||
const fileAnnotation = {
|
||||
type: 'DOCUMENT_FILE',
|
||||
data: {
|
||||
files: [
|
||||
{
|
||||
id: '1',
|
||||
name: 'report.pdf',
|
||||
type: 'application/pdf',
|
||||
url: '/files/report.pdf',
|
||||
size: 1024000
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
function FileDisplay() {
|
||||
return <ChatFiles /> // Automatically renders from context
|
||||
}
|
||||
```
|
||||
|
||||
**Supported File Types:**
|
||||
- **PDF** - Inline viewer
|
||||
- **Images** - Thumbnail preview
|
||||
- **Text Files** - Content preview
|
||||
- **CSV** - Data table preview
|
||||
- **Word Documents** - Document preview
|
||||
|
||||
### ChatSources
|
||||
|
||||
Displays source citations with document grouping.
|
||||
|
||||
```tsx
|
||||
import { ChatSources } from '@llamaindex/chat-ui/widgets'
|
||||
|
||||
// From annotation data
|
||||
const sourceAnnotation = {
|
||||
type: 'sources',
|
||||
data: {
|
||||
nodes: [
|
||||
{
|
||||
id: '1',
|
||||
url: '/documents/paper.pdf',
|
||||
metadata: {
|
||||
title: 'Research Paper',
|
||||
page_number: 5
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
function SourceDisplay() {
|
||||
return <ChatSources /> // Automatically renders from context
|
||||
}
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- **Document Grouping** - Groups citations by document
|
||||
- **Page Numbers** - Shows specific page references
|
||||
- **Click to View** - Opens source documents
|
||||
- **Metadata Display** - Shows title, author, date
|
||||
|
||||
### ChatEvents
|
||||
|
||||
Displays collapsible process events and status updates.
|
||||
|
||||
```tsx
|
||||
import { ChatEvents } from '@llamaindex/chat-ui/widgets'
|
||||
|
||||
// From annotation data
|
||||
const eventAnnotation = {
|
||||
type: 'events',
|
||||
data: [
|
||||
{
|
||||
type: 'function_call',
|
||||
name: 'search_documents',
|
||||
args: { query: 'machine learning' },
|
||||
result: 'Found 15 relevant documents'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
function EventDisplay() {
|
||||
return <ChatEvents /> // Automatically renders from context
|
||||
}
|
||||
```
|
||||
|
||||
### ChatAgentEvents
|
||||
|
||||
Displays agent-specific events with progress tracking.
|
||||
|
||||
```tsx
|
||||
import { ChatAgentEvents } from '@llamaindex/chat-ui/widgets'
|
||||
|
||||
// From annotation data
|
||||
const agentAnnotation = {
|
||||
type: 'agent_events',
|
||||
data: {
|
||||
agent_name: 'Research Assistant',
|
||||
progress: 75,
|
||||
current_step: 'Analyzing documents',
|
||||
events: [
|
||||
{ step: 'Searching', status: 'completed' },
|
||||
{ step: 'Analyzing', status: 'in_progress' },
|
||||
{ step: 'Summarizing', status: 'pending' }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
function AgentEventDisplay() {
|
||||
return <ChatAgentEvents /> // Automatically renders from context
|
||||
}
|
||||
```
|
||||
|
||||
### SuggestedQuestions
|
||||
|
||||
Interactive follow-up question suggestions.
|
||||
|
||||
```tsx
|
||||
import { SuggestedQuestions } from '@llamaindex/chat-ui/widgets'
|
||||
|
||||
// From annotation data
|
||||
const suggestionsAnnotation = {
|
||||
type: 'suggested_questions',
|
||||
data: {
|
||||
questions: [
|
||||
'Can you explain this in more detail?',
|
||||
'What are the practical applications?',
|
||||
'How does this compare to other approaches?'
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
function QuestionSuggestions() {
|
||||
return <SuggestedQuestions /> // Automatically renders from context
|
||||
}
|
||||
```
|
||||
|
||||
### StarterQuestions
|
||||
|
||||
Initial conversation starters for empty chat states.
|
||||
|
||||
```tsx
|
||||
import { StarterQuestions } from '@llamaindex/chat-ui/widgets'
|
||||
|
||||
const starterQuestions = [
|
||||
'How can I improve my code?',
|
||||
'Explain machine learning concepts',
|
||||
'Help me debug this error',
|
||||
'What are best practices for React?'
|
||||
]
|
||||
|
||||
function ChatStarters() {
|
||||
return (
|
||||
<StarterQuestions
|
||||
questions={starterQuestions}
|
||||
onQuestionSelect={handleQuestionSelect}
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Utility Widgets
|
||||
|
||||
### FileUploader
|
||||
|
||||
Drag-and-drop file upload with validation.
|
||||
|
||||
```tsx
|
||||
import { FileUploader } from '@llamaindex/chat-ui/widgets'
|
||||
|
||||
function UploadArea({ onFilesSelected }) {
|
||||
return (
|
||||
<FileUploader
|
||||
accept={{
|
||||
'image/*': ['.png', '.jpg', '.jpeg'],
|
||||
'application/pdf': ['.pdf'],
|
||||
'text/*': ['.txt', '.md']
|
||||
}}
|
||||
maxSize={10 * 1024 * 1024} // 10MB
|
||||
onFiles={onFilesSelected}
|
||||
multiple={true}
|
||||
>
|
||||
<div className="border-2 border-dashed p-8 text-center">
|
||||
<p>Drag files here or click to upload</p>
|
||||
</div>
|
||||
</FileUploader>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- **Drag & Drop** - Intuitive file selection
|
||||
- **File Validation** - Type and size checking
|
||||
- **Multiple Files** - Batch upload support
|
||||
- **Progress Tracking** - Upload progress display
|
||||
- **Error Handling** - Validation error messages
|
||||
|
||||
### ImagePreview
|
||||
|
||||
Image preview with zoom and manipulation.
|
||||
|
||||
```tsx
|
||||
import { ImagePreview } from '@llamaindex/chat-ui/widgets'
|
||||
|
||||
function PreviewImage({ src, alt }) {
|
||||
return (
|
||||
<ImagePreview
|
||||
src={src}
|
||||
alt={alt}
|
||||
className="max-w-md"
|
||||
enableZoom={true}
|
||||
showControls={true}
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### DocumentInfo
|
||||
|
||||
Document metadata display with formatting.
|
||||
|
||||
```tsx
|
||||
import { DocumentInfo } from '@llamaindex/chat-ui/widgets'
|
||||
|
||||
function DocInfo({ document }) {
|
||||
return (
|
||||
<DocumentInfo
|
||||
title={document.title}
|
||||
author={document.author}
|
||||
date={document.date}
|
||||
pages={document.pages}
|
||||
size={document.size}
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Citation
|
||||
|
||||
Individual citation component with linking.
|
||||
|
||||
```tsx
|
||||
import { Citation } from '@llamaindex/chat-ui/widgets'
|
||||
|
||||
function CitationLink({ source, index }) {
|
||||
return (
|
||||
<Citation
|
||||
number={index + 1}
|
||||
title={source.title}
|
||||
url={source.url}
|
||||
metadata={source.metadata}
|
||||
onClick={() => openSource(source)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Widget Integration
|
||||
|
||||
### Automatic Rendering
|
||||
|
||||
Widgets automatically render based on message annotations:
|
||||
|
||||
```tsx
|
||||
import { ChatMessage } from '@llamaindex/chat-ui'
|
||||
|
||||
function MessageWithWidgets({ message }) {
|
||||
return (
|
||||
<ChatMessage message={message}>
|
||||
<ChatMessage.Content>
|
||||
<ChatMessage.Content.Markdown />
|
||||
<ChatMessage.Content.Image /> {/* Renders ChatImage */}
|
||||
<ChatMessage.Content.Source /> {/* Renders ChatSources */}
|
||||
<ChatMessage.Content.Event /> {/* Renders ChatEvents */}
|
||||
</ChatMessage.Content>
|
||||
</ChatMessage>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Manual Widget Usage
|
||||
|
||||
Use widgets independently for custom layouts:
|
||||
|
||||
```tsx
|
||||
import {
|
||||
Markdown,
|
||||
CodeBlock,
|
||||
ChatImage,
|
||||
SuggestedQuestions
|
||||
} from '@llamaindex/chat-ui/widgets'
|
||||
|
||||
function CustomMessageLayout({ message }) {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<Markdown>{message.content}</Markdown>
|
||||
|
||||
{message.code && (
|
||||
<CodeBlock
|
||||
code={message.code}
|
||||
language="python"
|
||||
/>
|
||||
)}
|
||||
|
||||
{message.imageUrl && (
|
||||
<ChatImage
|
||||
src={message.imageUrl}
|
||||
alt="Generated image"
|
||||
/>
|
||||
)}
|
||||
|
||||
<SuggestedQuestions
|
||||
questions={message.suggestions}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Widget Creation
|
||||
|
||||
Create custom widgets by following the pattern:
|
||||
|
||||
```tsx
|
||||
import { useChatMessage, getCustomAnnotations } from '@llamaindex/chat-ui'
|
||||
|
||||
interface WeatherData {
|
||||
location: string
|
||||
temperature: number
|
||||
condition: string
|
||||
}
|
||||
|
||||
function CustomWeatherWidget() {
|
||||
const { message } = useChatMessage()
|
||||
|
||||
const weatherData = getCustomAnnotations<WeatherData>(
|
||||
message.annotations,
|
||||
'weather'
|
||||
)
|
||||
|
||||
if (!weatherData[0]) return null
|
||||
|
||||
const data = weatherData[0]
|
||||
|
||||
return (
|
||||
<div className="rounded-lg bg-blue-50 p-4">
|
||||
<h3 className="font-semibold">{data.location}</h3>
|
||||
<p className="text-2xl">{data.temperature}°C</p>
|
||||
<p className="text-sm text-gray-600">{data.condition}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Annotations](./annotations) - Learn how to create and send annotation data
|
||||
- [Artifacts](./artifacts) - Implement interactive code and document artifacts
|
||||
- [Hooks](./hooks) - Understand the widget hook system
|
||||
- [Customization](./customization) - Style and customize widget appearance
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "@llamaindex/chat-ui-docs",
|
||||
"version": "0.0.1",
|
||||
"files": [
|
||||
"chat-ui"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
@@ -2,3 +2,4 @@ packages:
|
||||
- "apps/*"
|
||||
- "packages/*"
|
||||
- "examples/**"
|
||||
- "docs/*"
|
||||
|
||||
Reference in New Issue
Block a user