@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 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 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
80
+ function HeadlessStreamingMessage({ stream }) {
81
+ const streamingData = useStreamingMessageData({
82
+ stream,
83
+ messageId: 'streaming-msg',
84
+ role: 'assistant',
85
+ })
107
86
 
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
- }
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
- 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
- ```
110
+ import { useMessage } from '@v0-sdk/react'
111
+ import { Text, Box } from 'ink'
197
112
 
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.
249
-
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
132
+ ## Available Hooks
265
133
 
266
- ### Supported Content Types
134
+ ### Core Hooks
267
135
 
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
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 Customization
140
+ ### Component Hooks
277
141
 
278
- The `components` prop allows you to override any part of the rendering:
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
- ```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
- ```
150
+ ## Available Components
300
151
 
301
- ### Default Styling
152
+ All components are optional JSX renderers that work with DOM environments. For headless usage, use the corresponding hooks instead.
302
153
 
303
- The components use Tailwind CSS classes by default but can work with any CSS framework:
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
- 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
164
+ ## Supported Task Types
309
165
 
310
- ## Backward Compatibility
166
+ The package automatically handles all v0 Platform API task types:
311
167
 
312
- The package maintains backward compatibility with previous versions:
168
+ ### Explicitly Supported Tasks
313
169
 
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'
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
- // These are all equivalent
321
- <Message content={content} />
322
- <MessageRenderer content={content} />
323
- <V0MessageRenderer content={content} />
324
- ```
179
+ ### Future-Proof Support
325
180
 
326
- ## Examples
181
+ Any new task type following the `task-*-v1` pattern will be automatically supported with:
327
182
 
328
- ### Complete Chat Interface
183
+ - Auto-generated readable titles
184
+ - Appropriate icon selection
185
+ - Proper task section rendering
186
+ - Graceful fallback handling
329
187
 
330
- ```tsx
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 Theme Example
190
+ ### Custom Components
362
191
 
363
192
  ```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)
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
- 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' },
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
- ### Error Handling
215
+ ### Headless Custom Rendering
420
216
 
421
217
  ```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
- )
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
- The package is written in TypeScript and includes comprehensive type definitions. All components and props are fully typed for the best development experience.
242
+ Full TypeScript support with exported types:
452
243
 
453
244
  ```tsx
454
245
  import type {
455
- MessageProps,
456
- CodeBlockProps,
457
- MathPartProps,
458
- MessageBinaryFormat,
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
- ## Requirements
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
- - React 18+ or React 19+
472
- - Modern browser with ES2020+ support
473
- - TypeScript 4.5+ (if using TypeScript)
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 2.0
299
+ Apache-2.0