@v0-sdk/react 0.2.1 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +198 -376
- package/dist/index.cjs +763 -548
- package/dist/index.d.ts +219 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +755 -550
- package/package.json +11 -11
package/README.md
CHANGED
|
@@ -2,7 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
> **⚠️ Developer Preview**: This SDK is currently in beta and is subject to change. Use in production at your own risk.
|
|
4
4
|
|
|
5
|
-
React components for rendering content from the v0 Platform API.
|
|
5
|
+
Headless React components and hooks for rendering content from the v0 Platform API.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Headless by design** - Works with React Native, TUIs, and any React renderer
|
|
10
|
+
- **Flexible rendering** - Use provided JSX components or build your own with headless hooks
|
|
11
|
+
- **Customizable** - Override any component or styling
|
|
12
|
+
- **Zero DOM dependencies** - No `react-dom` required
|
|
13
|
+
- **Streaming support** - Real-time message streaming with `useStreamingMessage`
|
|
14
|
+
- **Backward compatible** - Drop-in replacement for existing implementations
|
|
6
15
|
|
|
7
16
|
## Installation
|
|
8
17
|
|
|
@@ -16,462 +25,275 @@ pnpm add @v0-sdk/react
|
|
|
16
25
|
|
|
17
26
|
## Usage
|
|
18
27
|
|
|
19
|
-
### Basic
|
|
28
|
+
### Basic JSX Components (Web/React DOM)
|
|
20
29
|
|
|
21
30
|
```tsx
|
|
22
|
-
import { Message } from '@v0-sdk/react'
|
|
23
|
-
|
|
24
|
-
function ChatMessage({ apiResponse }) {
|
|
25
|
-
// Parse the content from the API response
|
|
26
|
-
const content = JSON.parse(apiResponse.content)
|
|
31
|
+
import { Message, StreamingMessage } from '@v0-sdk/react'
|
|
27
32
|
|
|
33
|
+
function ChatMessage({ content }) {
|
|
28
34
|
return (
|
|
29
35
|
<Message
|
|
30
36
|
content={content}
|
|
31
|
-
messageId=
|
|
32
|
-
role=
|
|
37
|
+
messageId="msg-1"
|
|
38
|
+
role="assistant"
|
|
39
|
+
className="my-message"
|
|
33
40
|
/>
|
|
34
41
|
)
|
|
35
42
|
}
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
### With Streaming
|
|
39
|
-
|
|
40
|
-
The package provides built-in support for streaming responses from v0 API:
|
|
41
|
-
|
|
42
|
-
```tsx
|
|
43
|
-
import { StreamingMessage } from '@v0-sdk/react'
|
|
44
|
-
import { createClient } from 'v0-sdk'
|
|
45
|
-
|
|
46
|
-
const v0 = createClient()
|
|
47
|
-
|
|
48
|
-
function ChatDemo() {
|
|
49
|
-
const [stream, setStream] = useState<ReadableStream<Uint8Array> | null>(null)
|
|
50
|
-
|
|
51
|
-
const handleSendMessage = async (message: string) => {
|
|
52
|
-
const response = await v0.chats.create({
|
|
53
|
-
message,
|
|
54
|
-
responseMode: 'experimental_stream',
|
|
55
|
-
})
|
|
56
|
-
setStream(response)
|
|
57
|
-
}
|
|
58
43
|
|
|
44
|
+
function StreamingChat({ stream }) {
|
|
59
45
|
return (
|
|
60
|
-
<
|
|
61
|
-
{stream
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
onComplete={(content) => handleCompletion(content)}
|
|
67
|
-
/>
|
|
68
|
-
)}
|
|
69
|
-
</div>
|
|
46
|
+
<StreamingMessage
|
|
47
|
+
stream={stream}
|
|
48
|
+
messageId="streaming-msg"
|
|
49
|
+
role="assistant"
|
|
50
|
+
onComplete={(content) => console.log('Complete:', content)}
|
|
51
|
+
/>
|
|
70
52
|
)
|
|
71
53
|
}
|
|
72
54
|
```
|
|
73
55
|
|
|
74
|
-
###
|
|
75
|
-
|
|
76
|
-
For more control, use the `useStreamingMessage` hook directly:
|
|
56
|
+
### Headless Hooks (React Native/Ink/TUI)
|
|
77
57
|
|
|
78
58
|
```tsx
|
|
79
|
-
import {
|
|
80
|
-
|
|
81
|
-
function CustomStreamingComponent({
|
|
82
|
-
stream,
|
|
83
|
-
}: {
|
|
84
|
-
stream: ReadableStream<Uint8Array>
|
|
85
|
-
}) {
|
|
86
|
-
const { content, isStreaming, error, isComplete } =
|
|
87
|
-
useStreamingMessage(stream)
|
|
59
|
+
import { useMessage, useStreamingMessageData } from '@v0-sdk/react'
|
|
60
|
+
import { Text, View } from 'react-native' // or any other renderer
|
|
88
61
|
|
|
89
|
-
|
|
62
|
+
function HeadlessMessage({ content }) {
|
|
63
|
+
const messageData = useMessage({
|
|
64
|
+
content,
|
|
65
|
+
messageId: 'msg-1',
|
|
66
|
+
role: 'assistant',
|
|
67
|
+
})
|
|
90
68
|
|
|
91
69
|
return (
|
|
92
|
-
<
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
role="assistant"
|
|
100
|
-
/>
|
|
101
|
-
</div>
|
|
70
|
+
<View>
|
|
71
|
+
{messageData.elements.map((element) => (
|
|
72
|
+
<Text key={element.key}>
|
|
73
|
+
{element.type === 'text' ? element.data : JSON.stringify(element)}
|
|
74
|
+
</Text>
|
|
75
|
+
))}
|
|
76
|
+
</View>
|
|
102
77
|
)
|
|
103
78
|
}
|
|
104
|
-
```
|
|
105
79
|
|
|
106
|
-
|
|
80
|
+
function HeadlessStreamingMessage({ stream }) {
|
|
81
|
+
const streamingData = useStreamingMessageData({
|
|
82
|
+
stream,
|
|
83
|
+
messageId: 'streaming-msg',
|
|
84
|
+
role: 'assistant',
|
|
85
|
+
})
|
|
107
86
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
import { Message, CodeBlock, MathPart } from '@v0-sdk/react'
|
|
112
|
-
|
|
113
|
-
// Custom code block with syntax highlighting
|
|
114
|
-
function CustomCodeBlock({ language, code, className }) {
|
|
115
|
-
return (
|
|
116
|
-
<div className="my-code-block">
|
|
117
|
-
<div className="code-header">{language}</div>
|
|
118
|
-
<pre className={className}>
|
|
119
|
-
<code>{code}</code>
|
|
120
|
-
</pre>
|
|
121
|
-
</div>
|
|
122
|
-
)
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// Custom math renderer
|
|
126
|
-
function CustomMathPart({ content, inline, className }) {
|
|
127
|
-
return (
|
|
128
|
-
<span className={`my-math ${inline ? 'inline' : 'block'} ${className}`}>
|
|
129
|
-
{content}
|
|
130
|
-
</span>
|
|
131
|
-
)
|
|
132
|
-
}
|
|
87
|
+
if (streamingData.error) {
|
|
88
|
+
return <Text style={{ color: 'red' }}>Error: {streamingData.error}</Text>
|
|
89
|
+
}
|
|
133
90
|
|
|
134
|
-
|
|
135
|
-
|
|
91
|
+
if (streamingData.isStreaming && !streamingData.messageData) {
|
|
92
|
+
return <Text>Loading...</Text>
|
|
93
|
+
}
|
|
136
94
|
|
|
137
95
|
return (
|
|
138
|
-
<
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
// Style HTML elements with simple className objects
|
|
146
|
-
p: { className: 'my-paragraph-styles' },
|
|
147
|
-
h1: { className: 'my-heading-styles' },
|
|
148
|
-
// Or use custom components for full control
|
|
149
|
-
blockquote: ({ children, ...props }) => (
|
|
150
|
-
<div className="my-custom-blockquote" {...props}>
|
|
151
|
-
{children}
|
|
152
|
-
</div>
|
|
153
|
-
),
|
|
154
|
-
}}
|
|
155
|
-
className="my-custom-message-styles"
|
|
156
|
-
/>
|
|
96
|
+
<View>
|
|
97
|
+
{streamingData.messageData?.elements.map((element) => (
|
|
98
|
+
<Text key={element.key}>
|
|
99
|
+
{element.type === 'text' ? element.data : JSON.stringify(element)}
|
|
100
|
+
</Text>
|
|
101
|
+
))}
|
|
102
|
+
</View>
|
|
157
103
|
)
|
|
158
104
|
}
|
|
159
105
|
```
|
|
160
106
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
### Message
|
|
164
|
-
|
|
165
|
-
The main component for rendering v0 Platform API message content.
|
|
166
|
-
|
|
167
|
-
#### Props
|
|
168
|
-
|
|
169
|
-
| Prop | Type | Default | Description |
|
|
170
|
-
| --------------- | --------------------------------------------- | ------------- | ---------------------------------------------------------------------------- |
|
|
171
|
-
| `content` | `MessageBinaryFormat` | **required** | The parsed content from the v0 Platform API (JSON.parse the 'content' field) |
|
|
172
|
-
| `messageId` | `string` | `'unknown'` | Optional message ID for tracking purposes |
|
|
173
|
-
| `role` | `'user' \| 'assistant' \| 'system' \| 'tool'` | `'assistant'` | Role of the message sender |
|
|
174
|
-
| `streaming` | `boolean` | `false` | Whether the message is currently being streamed |
|
|
175
|
-
| `isLastMessage` | `boolean` | `false` | Whether this is the last message in the conversation |
|
|
176
|
-
| `className` | `string` | `undefined` | Custom className for styling the root container |
|
|
177
|
-
| `components` | `ComponentOverrides` | `undefined` | Custom component renderers (see Custom Components section) |
|
|
178
|
-
|
|
179
|
-
### Individual Components
|
|
180
|
-
|
|
181
|
-
You can also use individual components directly:
|
|
107
|
+
### Ink CLI Example
|
|
182
108
|
|
|
183
109
|
```tsx
|
|
184
|
-
import {
|
|
185
|
-
|
|
186
|
-
MathPart,
|
|
187
|
-
ThinkingSection,
|
|
188
|
-
TaskSection,
|
|
189
|
-
CodeProjectPart
|
|
190
|
-
} from '@v0-sdk/react'
|
|
191
|
-
|
|
192
|
-
// Use components directly
|
|
193
|
-
<CodeBlock language="javascript" code="console.log('Hello')" />
|
|
194
|
-
<MathPart content="E = mc^2" inline />
|
|
195
|
-
<ThinkingSection title="Planning" thought="Let me think about this..." />
|
|
196
|
-
```
|
|
110
|
+
import { useMessage } from '@v0-sdk/react'
|
|
111
|
+
import { Text, Box } from 'ink'
|
|
197
112
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
| `filename` | `string` | `undefined` | Optional filename to display |
|
|
205
|
-
| `className` | `string` | `undefined` | Custom styling |
|
|
206
|
-
| `children` | `React.ReactNode` | `undefined` | Custom content (overrides code prop) |
|
|
207
|
-
|
|
208
|
-
#### MathPart
|
|
209
|
-
|
|
210
|
-
| Prop | Type | Default | Description |
|
|
211
|
-
| ------------- | ----------------- | ------------ | --------------------------------------- |
|
|
212
|
-
| `content` | `string` | **required** | The mathematical expression |
|
|
213
|
-
| `inline` | `boolean` | `false` | Whether to render inline or as block |
|
|
214
|
-
| `displayMode` | `boolean` | `undefined` | Alternative to inline for display mode |
|
|
215
|
-
| `className` | `string` | `undefined` | Custom styling |
|
|
216
|
-
| `children` | `React.ReactNode` | `undefined` | Custom content (overrides content prop) |
|
|
217
|
-
|
|
218
|
-
#### ThinkingSection
|
|
219
|
-
|
|
220
|
-
| Prop | Type | Default | Description |
|
|
221
|
-
| ------------ | ------------ | ----------- | ---------------------------- |
|
|
222
|
-
| `title` | `string` | `undefined` | Section title |
|
|
223
|
-
| `thought` | `string` | `undefined` | The thinking content |
|
|
224
|
-
| `duration` | `number` | `undefined` | Duration in milliseconds |
|
|
225
|
-
| `collapsed` | `boolean` | `false` | Whether section is collapsed |
|
|
226
|
-
| `onCollapse` | `() => void` | `undefined` | Collapse toggle handler |
|
|
227
|
-
| `className` | `string` | `undefined` | Custom styling |
|
|
228
|
-
|
|
229
|
-
#### TaskSection
|
|
230
|
-
|
|
231
|
-
| Prop | Type | Default | Description |
|
|
232
|
-
| ------------ | ------------ | ----------- | ---------------------------- |
|
|
233
|
-
| `title` | `string` | `undefined` | Section title |
|
|
234
|
-
| `type` | `string` | `undefined` | Task type |
|
|
235
|
-
| `parts` | `any[]` | `undefined` | Task content parts |
|
|
236
|
-
| `collapsed` | `boolean` | `false` | Whether section is collapsed |
|
|
237
|
-
| `onCollapse` | `() => void` | `undefined` | Collapse toggle handler |
|
|
238
|
-
| `className` | `string` | `undefined` | Custom styling |
|
|
239
|
-
|
|
240
|
-
### Types
|
|
241
|
-
|
|
242
|
-
#### MessageBinaryFormat
|
|
243
|
-
|
|
244
|
-
```typescript
|
|
245
|
-
type MessageBinaryFormat = [number, ...any[]][]
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
The binary format for message content as returned by the v0 Platform API. Each row is a tuple where the first element is the type and the rest are data.
|
|
249
|
-
|
|
250
|
-
#### MessageProps
|
|
113
|
+
function CliMessage({ content }) {
|
|
114
|
+
const messageData = useMessage({
|
|
115
|
+
content,
|
|
116
|
+
messageId: 'cli-msg',
|
|
117
|
+
role: 'assistant',
|
|
118
|
+
})
|
|
251
119
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
120
|
+
return (
|
|
121
|
+
<Box flexDirection="column">
|
|
122
|
+
{messageData.elements.map((element) => (
|
|
123
|
+
<Text key={element.key}>
|
|
124
|
+
{element.type === 'text' ? element.data : `[${element.type}]`}
|
|
125
|
+
</Text>
|
|
126
|
+
))}
|
|
127
|
+
</Box>
|
|
128
|
+
)
|
|
261
129
|
}
|
|
262
130
|
```
|
|
263
131
|
|
|
264
|
-
##
|
|
132
|
+
## Available Hooks
|
|
265
133
|
|
|
266
|
-
###
|
|
134
|
+
### Core Hooks
|
|
267
135
|
|
|
268
|
-
-
|
|
269
|
-
-
|
|
270
|
-
-
|
|
271
|
-
- **Thinking Sections**: Collapsible reasoning/thinking content
|
|
272
|
-
- **Task Sections**: Structured task and workflow content
|
|
273
|
-
- **Code Projects**: Multi-file code project display
|
|
274
|
-
- **Rich Components**: Full component customization support
|
|
136
|
+
- `useMessage(props)` - Process message content into headless data structure
|
|
137
|
+
- `useStreamingMessageData(props)` - Handle streaming messages with real-time updates
|
|
138
|
+
- `useStreamingMessage(stream, options)` - Low-level streaming hook
|
|
275
139
|
|
|
276
|
-
### Component
|
|
140
|
+
### Component Hooks
|
|
277
141
|
|
|
278
|
-
|
|
142
|
+
- `useIcon(props)` - Icon data and fallbacks
|
|
143
|
+
- `useCodeBlock(props)` - Code block processing
|
|
144
|
+
- `useMath(props)` - Math content processing
|
|
145
|
+
- `useThinkingSection(props)` - Thinking section state management
|
|
146
|
+
- `useTaskSection(props)` - Task section state and processing
|
|
147
|
+
- `useCodeProject(props)` - Code project structure
|
|
148
|
+
- `useContentPart(part)` - Content part analysis and processing
|
|
279
149
|
|
|
280
|
-
|
|
281
|
-
// Simple className-based styling
|
|
282
|
-
<Message
|
|
283
|
-
content={content}
|
|
284
|
-
components={{
|
|
285
|
-
p: { className: 'my-paragraph' },
|
|
286
|
-
h1: { className: 'text-2xl font-bold' }
|
|
287
|
-
}}
|
|
288
|
-
/>
|
|
289
|
-
|
|
290
|
-
// Full component replacement
|
|
291
|
-
<Message
|
|
292
|
-
content={content}
|
|
293
|
-
components={{
|
|
294
|
-
CodeBlock: MyCustomCodeBlock,
|
|
295
|
-
MathPart: MyCustomMathRenderer,
|
|
296
|
-
ThinkingSection: MyCustomThinking
|
|
297
|
-
}}
|
|
298
|
-
/>
|
|
299
|
-
```
|
|
150
|
+
## Available Components
|
|
300
151
|
|
|
301
|
-
|
|
152
|
+
All components are optional JSX renderers that work with DOM environments. For headless usage, use the corresponding hooks instead.
|
|
302
153
|
|
|
303
|
-
|
|
154
|
+
- `Message` - Main message renderer
|
|
155
|
+
- `StreamingMessage` - Streaming message with loading states
|
|
156
|
+
- `Icon` - Generic icon component with fallbacks
|
|
157
|
+
- `CodeBlock` - Code syntax highlighting
|
|
158
|
+
- `MathPart` - Math content rendering
|
|
159
|
+
- `ThinkingSection` - Collapsible thinking sections
|
|
160
|
+
- `TaskSection` - Collapsible task sections
|
|
161
|
+
- `CodeProjectPart` - Code project file browser
|
|
162
|
+
- `ContentPartRenderer` - Handles different content part types
|
|
304
163
|
|
|
305
|
-
|
|
306
|
-
2. **Custom CSS**: Use the `className` prop and `components` overrides
|
|
307
|
-
3. **CSS Modules**: Pass CSS module classes via `className` and `components`
|
|
308
|
-
4. **Styled Components**: Wrap components with styled-components
|
|
164
|
+
## Supported Task Types
|
|
309
165
|
|
|
310
|
-
|
|
166
|
+
The package automatically handles all v0 Platform API task types:
|
|
311
167
|
|
|
312
|
-
|
|
168
|
+
### Explicitly Supported Tasks
|
|
313
169
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
170
|
+
- `task-thinking-v1` - AI reasoning and thought processes
|
|
171
|
+
- `task-search-web-v1` - Web search operations with results
|
|
172
|
+
- `task-search-repo-v1` - Repository/codebase search functionality
|
|
173
|
+
- `task-diagnostics-v1` - Code analysis and issue detection
|
|
174
|
+
- `task-read-file-v1` - File reading operations
|
|
175
|
+
- `task-coding-v1` - Code generation and editing tasks
|
|
176
|
+
- `task-generate-design-inspiration-v1` - Design inspiration generation
|
|
177
|
+
- `task-start-v1` - Task initialization (usually hidden)
|
|
319
178
|
|
|
320
|
-
|
|
321
|
-
<Message content={content} />
|
|
322
|
-
<MessageRenderer content={content} />
|
|
323
|
-
<V0MessageRenderer content={content} />
|
|
324
|
-
```
|
|
179
|
+
### Future-Proof Support
|
|
325
180
|
|
|
326
|
-
|
|
181
|
+
Any new task type following the `task-*-v1` pattern will be automatically supported with:
|
|
327
182
|
|
|
328
|
-
|
|
183
|
+
- Auto-generated readable titles
|
|
184
|
+
- Appropriate icon selection
|
|
185
|
+
- Proper task section rendering
|
|
186
|
+
- Graceful fallback handling
|
|
329
187
|
|
|
330
|
-
|
|
331
|
-
import { Message } from '@v0-sdk/react'
|
|
332
|
-
|
|
333
|
-
function ChatInterface({ messages }) {
|
|
334
|
-
return (
|
|
335
|
-
<div className="chat-container">
|
|
336
|
-
{messages.map((message, index) => {
|
|
337
|
-
const content = JSON.parse(message.content)
|
|
338
|
-
const isLast = index === messages.length - 1
|
|
339
|
-
|
|
340
|
-
return (
|
|
341
|
-
<div key={message.id} className="message">
|
|
342
|
-
<div className="message-header">
|
|
343
|
-
<span className="role">{message.role}</span>
|
|
344
|
-
<span className="timestamp">{message.createdAt}</span>
|
|
345
|
-
</div>
|
|
346
|
-
<Message
|
|
347
|
-
content={content}
|
|
348
|
-
messageId={message.id}
|
|
349
|
-
role={message.role}
|
|
350
|
-
isLastMessage={isLast}
|
|
351
|
-
className="message-content"
|
|
352
|
-
/>
|
|
353
|
-
</div>
|
|
354
|
-
)
|
|
355
|
-
})}
|
|
356
|
-
</div>
|
|
357
|
-
)
|
|
358
|
-
}
|
|
359
|
-
```
|
|
188
|
+
## Customization
|
|
360
189
|
|
|
361
|
-
### Custom
|
|
190
|
+
### Custom Components
|
|
362
191
|
|
|
363
192
|
```tsx
|
|
364
|
-
import { Message
|
|
365
|
-
|
|
366
|
-
// Dark theme code block
|
|
367
|
-
function DarkCodeBlock({ language, code, filename }) {
|
|
368
|
-
return (
|
|
369
|
-
<div className="bg-gray-900 rounded-lg overflow-hidden">
|
|
370
|
-
{filename && (
|
|
371
|
-
<div className="bg-gray-800 px-4 py-2 text-gray-300 text-sm">
|
|
372
|
-
{filename}
|
|
373
|
-
</div>
|
|
374
|
-
)}
|
|
375
|
-
<div className="bg-gray-700 px-2 py-1 text-xs text-gray-400">
|
|
376
|
-
{language}
|
|
377
|
-
</div>
|
|
378
|
-
<pre className="p-4 text-green-400 overflow-x-auto">
|
|
379
|
-
<code>{code}</code>
|
|
380
|
-
</pre>
|
|
381
|
-
</div>
|
|
382
|
-
)
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
// Elegant math renderer
|
|
386
|
-
function ElegantMath({ content, inline }) {
|
|
387
|
-
return (
|
|
388
|
-
<span
|
|
389
|
-
className={`
|
|
390
|
-
${inline ? 'mx-1' : 'block text-center my-4'}
|
|
391
|
-
font-serif text-blue-600
|
|
392
|
-
`}
|
|
393
|
-
>
|
|
394
|
-
{content}
|
|
395
|
-
</span>
|
|
396
|
-
)
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
function ThemedChat({ apiResponse }) {
|
|
400
|
-
const content = JSON.parse(apiResponse.content)
|
|
193
|
+
import { Message } from '@v0-sdk/react'
|
|
401
194
|
|
|
195
|
+
function CustomMessage({ content }) {
|
|
402
196
|
return (
|
|
403
197
|
<Message
|
|
404
198
|
content={content}
|
|
405
199
|
components={{
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
200
|
+
// Override specific HTML elements
|
|
201
|
+
p: ({ children }) => <MyParagraph>{children}</MyParagraph>,
|
|
202
|
+
code: ({ children }) => <MyCode>{children}</MyCode>,
|
|
203
|
+
|
|
204
|
+
// Override v0-specific components
|
|
205
|
+
CodeBlock: ({ language, code }) => (
|
|
206
|
+
<MyCodeHighlighter lang={language}>{code}</MyCodeHighlighter>
|
|
207
|
+
),
|
|
208
|
+
Icon: ({ name }) => <MyIcon icon={name} />,
|
|
412
209
|
}}
|
|
413
|
-
className="max-w-4xl mx-auto p-6"
|
|
414
210
|
/>
|
|
415
211
|
)
|
|
416
212
|
}
|
|
417
213
|
```
|
|
418
214
|
|
|
419
|
-
###
|
|
215
|
+
### Headless Custom Rendering
|
|
420
216
|
|
|
421
217
|
```tsx
|
|
422
|
-
import {
|
|
423
|
-
|
|
424
|
-
function
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
<div className="error-message p-4 bg-red-50 border border-red-200 rounded">
|
|
439
|
-
<p className="text-red-700">Failed to render message content</p>
|
|
440
|
-
<pre className="text-xs text-red-600 mt-2 overflow-x-auto">
|
|
441
|
-
{error.message}
|
|
442
|
-
</pre>
|
|
443
|
-
</div>
|
|
444
|
-
)
|
|
218
|
+
import { useMessage } from '@v0-sdk/react'
|
|
219
|
+
|
|
220
|
+
function CustomHeadlessMessage({ content }) {
|
|
221
|
+
const messageData = useMessage({ content })
|
|
222
|
+
|
|
223
|
+
const renderElement = (element) => {
|
|
224
|
+
switch (element.type) {
|
|
225
|
+
case 'text':
|
|
226
|
+
return <MyText>{element.data}</MyText>
|
|
227
|
+
case 'code-project':
|
|
228
|
+
return <MyCodeProject {...element.data} />
|
|
229
|
+
case 'html':
|
|
230
|
+
return <MyHtmlElement {...element.data} />
|
|
231
|
+
default:
|
|
232
|
+
return null
|
|
233
|
+
}
|
|
445
234
|
}
|
|
235
|
+
|
|
236
|
+
return <MyContainer>{messageData.elements.map(renderElement)}</MyContainer>
|
|
446
237
|
}
|
|
447
238
|
```
|
|
448
239
|
|
|
449
|
-
## TypeScript
|
|
240
|
+
## TypeScript Support
|
|
450
241
|
|
|
451
|
-
|
|
242
|
+
Full TypeScript support with exported types:
|
|
452
243
|
|
|
453
244
|
```tsx
|
|
454
245
|
import type {
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
246
|
+
MessageData,
|
|
247
|
+
MessageElement,
|
|
248
|
+
StreamingMessageData,
|
|
249
|
+
IconData,
|
|
250
|
+
CodeBlockData,
|
|
251
|
+
// ... and many more
|
|
459
252
|
} from '@v0-sdk/react'
|
|
460
|
-
|
|
461
|
-
// Type-safe usage
|
|
462
|
-
const myMessage: MessageProps = {
|
|
463
|
-
content: parsedContent,
|
|
464
|
-
role: 'assistant',
|
|
465
|
-
streaming: false,
|
|
466
|
-
}
|
|
467
253
|
```
|
|
468
254
|
|
|
469
|
-
##
|
|
255
|
+
## Migration from Previous Versions
|
|
256
|
+
|
|
257
|
+
This version is backward compatible. Existing code will continue to work unchanged. To adopt headless patterns:
|
|
258
|
+
|
|
259
|
+
1. **Keep existing JSX components** for web/DOM environments
|
|
260
|
+
2. **Use headless hooks** for React Native, Ink, or custom renderers
|
|
261
|
+
3. **Gradually migrate** components as needed
|
|
470
262
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
263
|
+
## React Native Example
|
|
264
|
+
|
|
265
|
+
```tsx
|
|
266
|
+
import { useMessage, useIcon } from '@v0-sdk/react'
|
|
267
|
+
import { View, Text, ScrollView } from 'react-native'
|
|
268
|
+
|
|
269
|
+
function RNMessage({ content }) {
|
|
270
|
+
const messageData = useMessage({ content })
|
|
271
|
+
|
|
272
|
+
const renderElement = (element) => {
|
|
273
|
+
if (element.type === 'text') {
|
|
274
|
+
return <Text key={element.key}>{element.data}</Text>
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (element.type === 'html' && element.data.tagName === 'p') {
|
|
278
|
+
return (
|
|
279
|
+
<Text key={element.key} style={{ marginVertical: 8 }}>
|
|
280
|
+
{element.children?.map(renderElement)}
|
|
281
|
+
</Text>
|
|
282
|
+
)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return <Text key={element.key}>[{element.type}]</Text>
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return <ScrollView>{messageData.elements.map(renderElement)}</ScrollView>
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function RNIcon({ name }) {
|
|
292
|
+
const iconData = useIcon({ name })
|
|
293
|
+
return <Text>{iconData.fallback}</Text>
|
|
294
|
+
}
|
|
295
|
+
```
|
|
474
296
|
|
|
475
297
|
## License
|
|
476
298
|
|
|
477
|
-
Apache
|
|
299
|
+
Apache-2.0
|