@v0-sdk/react 0.2.1 → 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 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 Usage
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={apiResponse.id}
32
- role={apiResponse.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
- <div>
61
- {stream && (
62
- <StreamingMessage
63
- stream={stream}
64
- messageId="demo-message"
65
- role="assistant"
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
- ### Using the Streaming Hook
75
-
76
- For more control, use the `useStreamingMessage` hook directly:
56
+ ### Headless Hooks (React Native/Ink/TUI)
77
57
 
78
58
  ```tsx
79
- import { useStreamingMessage, Message } from '@v0-sdk/react'
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
- if (error) return <div>Error: {error}</div>
62
+ function HeadlessMessage({ content }) {
63
+ const messageData = useMessage({
64
+ content,
65
+ messageId: 'msg-1',
66
+ role: 'assistant',
67
+ })
90
68
 
91
69
  return (
92
- <div>
93
- <div>Status: {isStreaming ? 'Streaming...' : 'Complete'}</div>
94
- <Message
95
- content={content}
96
- streaming={isStreaming}
97
- isLastMessage={true}
98
- messageId="custom-streaming"
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
- ### Custom Component Styling
107
-
108
- The `Message` component supports custom component renderers for complete control over styling and behavior:
109
-
110
- ```tsx
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
- }
80
+ function HeadlessStreamingMessage({ stream }) {
81
+ const streamingData = useStreamingMessageData({
82
+ stream,
83
+ messageId: 'streaming-msg',
84
+ role: 'assistant',
85
+ })
124
86
 
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
- function StyledMessage({ apiResponse }) {
135
- const content = JSON.parse(apiResponse.content)
91
+ if (streamingData.isStreaming && !streamingData.messageData) {
92
+ return <Text>Loading...</Text>
93
+ }
136
94
 
137
95
  return (
138
- <Message
139
- content={content}
140
- messageId={apiResponse.id}
141
- role={apiResponse.role}
142
- components={{
143
- CodeBlock: CustomCodeBlock,
144
- MathPart: CustomMathPart,
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
- ## API Reference
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
- CodeBlock,
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
- #### MessageProps
113
+ function CliMessage({ content }) {
114
+ const messageData = useMessage({
115
+ content,
116
+ messageId: 'cli-msg',
117
+ role: 'assistant',
118
+ })
251
119
 
252
- ```typescript
253
- interface MessageProps {
254
- content: MessageBinaryFormat
255
- messageId?: string
256
- role?: 'user' | 'assistant' | 'system' | 'tool'
257
- streaming?: boolean
258
- isLastMessage?: boolean
259
- className?: string
260
- components?: ComponentOverrides
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
- ## Features
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
- The `components` prop allows you to override any part of the rendering:
134
+ ### Core Hooks
279
135
 
280
- ```tsx
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
- ```
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
- The components use Tailwind CSS classes by default but can work with any CSS framework:
140
+ ### Component Hooks
304
141
 
305
- 1. **Tailwind CSS**: Works out of the box
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
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
- ## Backward Compatibility
150
+ ## Available Components
311
151
 
312
- The package maintains backward compatibility with previous versions:
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
- // These are all equivalent
321
- <Message content={content} />
322
- <MessageRenderer content={content} />
323
- <V0MessageRenderer content={content} />
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
- ## Examples
164
+ ## Customization
327
165
 
328
- ### Complete Chat Interface
166
+ ### Custom Components
329
167
 
330
168
  ```tsx
331
169
  import { Message } from '@v0-sdk/react'
332
170
 
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
- ```
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
- CodeBlock: DarkCodeBlock,
407
- MathPart: ElegantMath,
408
- h1: { className: 'text-3xl font-bold text-gray-800 mb-4' },
409
- h2: { className: 'text-2xl font-semibold text-gray-700 mb-3' },
410
- p: { className: 'text-gray-600 leading-relaxed mb-4' },
411
- blockquote: { className: 'border-l-4 border-blue-500 pl-4 italic' },
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
- ### Error Handling
191
+ ### Headless Custom Rendering
420
192
 
421
193
  ```tsx
422
- import { Message } from '@v0-sdk/react'
423
-
424
- function SafeMessageRenderer({ apiResponse }) {
425
- try {
426
- const content = JSON.parse(apiResponse.content)
427
-
428
- return (
429
- <Message
430
- content={content}
431
- messageId={apiResponse.id}
432
- role={apiResponse.role}
433
- />
434
- )
435
- } catch (error) {
436
- console.error('Failed to parse message content:', error)
437
- return (
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
- The package is written in TypeScript and includes comprehensive type definitions. All components and props are fully typed for the best development experience.
218
+ Full TypeScript support with exported types:
452
219
 
453
220
  ```tsx
454
221
  import type {
455
- MessageProps,
456
- CodeBlockProps,
457
- MathPartProps,
458
- MessageBinaryFormat,
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
- ## Requirements
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
- - React 18+ or React 19+
472
- - Modern browser with ES2020+ support
473
- - TypeScript 4.5+ (if using TypeScript)
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 2.0
275
+ Apache-2.0