@v0-sdk/react 0.2.0 → 0.3.0
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 +180 -382
- package/dist/index.cjs +744 -563
- package/dist/index.d.ts +221 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +736 -565
- package/package.json +3 -5
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,251 @@ 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) => console.log('Stream complete:', 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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
}
|
|
80
|
+
function HeadlessStreamingMessage({ stream }) {
|
|
81
|
+
const streamingData = useStreamingMessageData({
|
|
82
|
+
stream,
|
|
83
|
+
messageId: 'streaming-msg',
|
|
84
|
+
role: 'assistant',
|
|
85
|
+
})
|
|
124
86
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
```
|
|
197
|
-
|
|
198
|
-
#### CodeBlock
|
|
199
|
-
|
|
200
|
-
| Prop | Type | Default | Description |
|
|
201
|
-
| ----------- | ----------------- | ------------ | -------------------------------------------- |
|
|
202
|
-
| `language` | `string` | **required** | Programming language for syntax highlighting |
|
|
203
|
-
| `code` | `string` | **required** | The code content to display |
|
|
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.
|
|
110
|
+
import { useMessage } from '@v0-sdk/react'
|
|
111
|
+
import { Text, Box } from 'ink'
|
|
249
112
|
|
|
250
|
-
|
|
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
|
-
##
|
|
265
|
-
|
|
266
|
-
### Supported Content Types
|
|
267
|
-
|
|
268
|
-
- **Markdown/Text Content**: Paragraphs, headings, lists, links, emphasis, code spans, etc.
|
|
269
|
-
- **Code Blocks**: Syntax-highlighted code blocks with filename support
|
|
270
|
-
- **Mathematical Expressions**: Inline and block math expressions
|
|
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
|
|
275
|
-
|
|
276
|
-
### Component Customization
|
|
132
|
+
## Available Hooks
|
|
277
133
|
|
|
278
|
-
|
|
134
|
+
### Core Hooks
|
|
279
135
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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
|
-
```
|
|
300
|
-
|
|
301
|
-
### Default Styling
|
|
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
|
|
302
139
|
|
|
303
|
-
|
|
140
|
+
### Component Hooks
|
|
304
141
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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
|
|
309
149
|
|
|
310
|
-
##
|
|
150
|
+
## Available Components
|
|
311
151
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
```tsx
|
|
315
|
-
// These all work and refer to the same component
|
|
316
|
-
import { Message } from '@v0-sdk/react'
|
|
317
|
-
import { MessageRenderer } from '@v0-sdk/react'
|
|
318
|
-
import { V0MessageRenderer } from '@v0-sdk/react'
|
|
152
|
+
All components are optional JSX renderers that work with DOM environments. For headless usage, use the corresponding hooks instead.
|
|
319
153
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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
|
|
325
163
|
|
|
326
|
-
##
|
|
164
|
+
## Customization
|
|
327
165
|
|
|
328
|
-
###
|
|
166
|
+
### Custom Components
|
|
329
167
|
|
|
330
168
|
```tsx
|
|
331
169
|
import { Message } from '@v0-sdk/react'
|
|
332
170
|
|
|
333
|
-
function
|
|
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
|
-
```
|
|
360
|
-
|
|
361
|
-
### Custom Theme Example
|
|
362
|
-
|
|
363
|
-
```tsx
|
|
364
|
-
import { Message, CodeBlock, MathPart } from '@v0-sdk/react'
|
|
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)
|
|
401
|
-
|
|
171
|
+
function CustomMessage({ content }) {
|
|
402
172
|
return (
|
|
403
173
|
<Message
|
|
404
174
|
content={content}
|
|
405
175
|
components={{
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
176
|
+
// Override specific HTML elements
|
|
177
|
+
p: ({ children }) => <MyParagraph>{children}</MyParagraph>,
|
|
178
|
+
code: ({ children }) => <MyCode>{children}</MyCode>,
|
|
179
|
+
|
|
180
|
+
// Override v0-specific components
|
|
181
|
+
CodeBlock: ({ language, code }) => (
|
|
182
|
+
<MyCodeHighlighter lang={language}>{code}</MyCodeHighlighter>
|
|
183
|
+
),
|
|
184
|
+
Icon: ({ name }) => <MyIcon icon={name} />,
|
|
412
185
|
}}
|
|
413
|
-
className="max-w-4xl mx-auto p-6"
|
|
414
186
|
/>
|
|
415
187
|
)
|
|
416
188
|
}
|
|
417
189
|
```
|
|
418
190
|
|
|
419
|
-
###
|
|
191
|
+
### Headless Custom Rendering
|
|
420
192
|
|
|
421
193
|
```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
|
-
)
|
|
194
|
+
import { useMessage } from '@v0-sdk/react'
|
|
195
|
+
|
|
196
|
+
function CustomHeadlessMessage({ content }) {
|
|
197
|
+
const messageData = useMessage({ content })
|
|
198
|
+
|
|
199
|
+
const renderElement = (element) => {
|
|
200
|
+
switch (element.type) {
|
|
201
|
+
case 'text':
|
|
202
|
+
return <MyText>{element.data}</MyText>
|
|
203
|
+
case 'code-project':
|
|
204
|
+
return <MyCodeProject {...element.data} />
|
|
205
|
+
case 'html':
|
|
206
|
+
return <MyHtmlElement {...element.data} />
|
|
207
|
+
default:
|
|
208
|
+
return null
|
|
209
|
+
}
|
|
445
210
|
}
|
|
211
|
+
|
|
212
|
+
return <MyContainer>{messageData.elements.map(renderElement)}</MyContainer>
|
|
446
213
|
}
|
|
447
214
|
```
|
|
448
215
|
|
|
449
|
-
## TypeScript
|
|
216
|
+
## TypeScript Support
|
|
450
217
|
|
|
451
|
-
|
|
218
|
+
Full TypeScript support with exported types:
|
|
452
219
|
|
|
453
220
|
```tsx
|
|
454
221
|
import type {
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
222
|
+
MessageData,
|
|
223
|
+
MessageElement,
|
|
224
|
+
StreamingMessageData,
|
|
225
|
+
IconData,
|
|
226
|
+
CodeBlockData,
|
|
227
|
+
// ... and many more
|
|
459
228
|
} from '@v0-sdk/react'
|
|
460
|
-
|
|
461
|
-
// Type-safe usage
|
|
462
|
-
const myMessage: MessageProps = {
|
|
463
|
-
content: parsedContent,
|
|
464
|
-
role: 'assistant',
|
|
465
|
-
streaming: false,
|
|
466
|
-
}
|
|
467
229
|
```
|
|
468
230
|
|
|
469
|
-
##
|
|
231
|
+
## Migration from Previous Versions
|
|
232
|
+
|
|
233
|
+
This version is backward compatible. Existing code will continue to work unchanged. To adopt headless patterns:
|
|
234
|
+
|
|
235
|
+
1. **Keep existing JSX components** for web/DOM environments
|
|
236
|
+
2. **Use headless hooks** for React Native, Ink, or custom renderers
|
|
237
|
+
3. **Gradually migrate** components as needed
|
|
470
238
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
239
|
+
## React Native Example
|
|
240
|
+
|
|
241
|
+
```tsx
|
|
242
|
+
import { useMessage, useIcon } from '@v0-sdk/react'
|
|
243
|
+
import { View, Text, ScrollView } from 'react-native'
|
|
244
|
+
|
|
245
|
+
function RNMessage({ content }) {
|
|
246
|
+
const messageData = useMessage({ content })
|
|
247
|
+
|
|
248
|
+
const renderElement = (element) => {
|
|
249
|
+
if (element.type === 'text') {
|
|
250
|
+
return <Text key={element.key}>{element.data}</Text>
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (element.type === 'html' && element.data.tagName === 'p') {
|
|
254
|
+
return (
|
|
255
|
+
<Text key={element.key} style={{ marginVertical: 8 }}>
|
|
256
|
+
{element.children?.map(renderElement)}
|
|
257
|
+
</Text>
|
|
258
|
+
)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return <Text key={element.key}>[{element.type}]</Text>
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return <ScrollView>{messageData.elements.map(renderElement)}</ScrollView>
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function RNIcon({ name }) {
|
|
268
|
+
const iconData = useIcon({ name })
|
|
269
|
+
return <Text>{iconData.fallback}</Text>
|
|
270
|
+
}
|
|
271
|
+
```
|
|
474
272
|
|
|
475
273
|
## License
|
|
476
274
|
|
|
477
|
-
Apache
|
|
275
|
+
Apache-2.0
|