@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/dist/index.js CHANGED
@@ -1,49 +1,80 @@
1
- import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
2
- import React, { useContext, createContext, useState, useRef, useSyncExternalStore } from 'react';
1
+ import React, { createContext, useContext, useState, useRef, useSyncExternalStore } from 'react';
3
2
  import * as jsondiffpatch from 'jsondiffpatch';
4
3
 
4
+ // Headless hook for code block data
5
+ function useCodeBlock(props) {
6
+ const lines = props.code.split('\n');
7
+ return {
8
+ language: props.language,
9
+ code: props.code,
10
+ filename: props.filename,
11
+ lines,
12
+ lineCount: lines.length
13
+ };
14
+ }
5
15
  /**
6
16
  * Generic code block component
7
17
  * Renders plain code by default - consumers should provide their own styling and highlighting
8
- */ function CodeBlock({ language, code, className = '', children }) {
18
+ *
19
+ * For headless usage, use the useCodeBlock hook instead.
20
+ */ function CodeBlock({ language, code, className = '', children, filename }) {
9
21
  // If children provided, use that (allows complete customization)
10
22
  if (children) {
11
- return /*#__PURE__*/ jsx(Fragment, {
12
- children: children
13
- });
23
+ return React.createElement(React.Fragment, {}, children);
14
24
  }
15
- // Simple fallback - just render plain code
16
- return /*#__PURE__*/ jsx("pre", {
17
- className: className,
18
- "data-language": language,
19
- children: /*#__PURE__*/ jsx("code", {
20
- children: code
21
- })
25
+ const codeBlockData = useCodeBlock({
26
+ language,
27
+ code,
28
+ filename
22
29
  });
30
+ // Simple fallback - just render plain code
31
+ // Uses React.createElement for maximum compatibility across environments
32
+ return React.createElement('pre', {
33
+ className,
34
+ 'data-language': codeBlockData.language,
35
+ ...filename && {
36
+ 'data-filename': filename
37
+ }
38
+ }, React.createElement('code', {}, codeBlockData.code));
23
39
  }
24
40
 
25
41
  // Context for providing custom icon implementation
26
- const IconContext = /*#__PURE__*/ createContext(null);
42
+ const IconContext = createContext(null);
43
+ // Headless hook for icon data
44
+ function useIcon(props) {
45
+ return {
46
+ name: props.name,
47
+ fallback: getIconFallback(props.name),
48
+ ariaLabel: props.name.replace('-', ' ')
49
+ };
50
+ }
27
51
  /**
28
52
  * Generic icon component that can be customized by consumers.
29
53
  * By default, renders a simple fallback. Consumers should provide
30
54
  * their own icon implementation via context or props.
55
+ *
56
+ * For headless usage, use the useIcon hook instead.
31
57
  */ function Icon(props) {
32
58
  const CustomIcon = useContext(IconContext);
33
59
  // Use custom icon implementation if provided via context
34
60
  if (CustomIcon) {
35
- return /*#__PURE__*/ jsx(CustomIcon, {
36
- ...props
37
- });
61
+ return React.createElement(CustomIcon, props);
38
62
  }
63
+ const iconData = useIcon(props);
39
64
  // Fallback implementation - consumers should override this
40
- return /*#__PURE__*/ jsx("span", {
65
+ // This uses minimal DOM-specific attributes for maximum compatibility
66
+ return React.createElement('span', {
41
67
  className: props.className,
42
- "data-icon": props.name,
43
- suppressHydrationWarning: true,
44
- "aria-label": props.name.replace('-', ' '),
45
- children: getIconFallback(props.name)
46
- });
68
+ 'data-icon': iconData.name,
69
+ 'aria-label': iconData.ariaLabel
70
+ }, iconData.fallback);
71
+ }
72
+ /**
73
+ * Provider for custom icon implementation
74
+ */ function IconProvider({ children, component }) {
75
+ return React.createElement(IconContext.Provider, {
76
+ value: component
77
+ }, children);
47
78
  }
48
79
  function getIconFallback(name) {
49
80
  const iconMap = {
@@ -59,314 +90,291 @@ function getIconFallback(name) {
59
90
  return iconMap[name] || '•';
60
91
  }
61
92
 
93
+ // Headless hook for code project
94
+ function useCodeProject({ title, filename, code, language = 'typescript', collapsed: initialCollapsed = true }) {
95
+ const [collapsed, setCollapsed] = useState(initialCollapsed);
96
+ // Mock file structure - in a real implementation this could be dynamic
97
+ const files = [
98
+ {
99
+ name: filename || 'page.tsx',
100
+ path: 'app/page.tsx',
101
+ active: true
102
+ },
103
+ {
104
+ name: 'layout.tsx',
105
+ path: 'app/layout.tsx',
106
+ active: false
107
+ },
108
+ {
109
+ name: 'globals.css',
110
+ path: 'app/globals.css',
111
+ active: false
112
+ }
113
+ ];
114
+ return {
115
+ data: {
116
+ title: title || 'Code Project',
117
+ filename,
118
+ code,
119
+ language,
120
+ collapsed,
121
+ files
122
+ },
123
+ collapsed,
124
+ toggleCollapsed: ()=>setCollapsed(!collapsed)
125
+ };
126
+ }
62
127
  /**
63
128
  * Generic code project block component
64
129
  * Renders a collapsible code project with basic structure - consumers provide styling
130
+ *
131
+ * For headless usage, use the useCodeProject hook instead.
65
132
  */ function CodeProjectPart({ title, filename, code, language = 'typescript', collapsed: initialCollapsed = true, className, children, iconRenderer }) {
66
- const [collapsed, setCollapsed] = useState(initialCollapsed);
133
+ const { data, collapsed, toggleCollapsed } = useCodeProject({
134
+ title,
135
+ filename,
136
+ code,
137
+ language,
138
+ collapsed: initialCollapsed
139
+ });
67
140
  // If children provided, use that (allows complete customization)
68
141
  if (children) {
69
- return /*#__PURE__*/ jsx(Fragment, {
70
- children: children
71
- });
142
+ return React.createElement(React.Fragment, {}, children);
72
143
  }
73
- return /*#__PURE__*/ jsxs("div", {
74
- className: className,
75
- "data-component": "code-project-block",
76
- children: [
77
- /*#__PURE__*/ jsxs("button", {
78
- onClick: ()=>setCollapsed(!collapsed),
79
- "data-expanded": !collapsed,
80
- children: [
81
- /*#__PURE__*/ jsxs("div", {
82
- "data-header": true,
83
- children: [
84
- iconRenderer ? /*#__PURE__*/ React.createElement(iconRenderer, {
85
- name: 'folder'
86
- }) : /*#__PURE__*/ jsx(Icon, {
87
- name: "folder"
88
- }),
89
- /*#__PURE__*/ jsx("span", {
90
- "data-title": true,
91
- children: title || 'Code Project'
92
- })
93
- ]
94
- }),
95
- /*#__PURE__*/ jsx("span", {
96
- "data-version": true,
97
- children: "v1"
98
- })
99
- ]
100
- }),
101
- !collapsed && /*#__PURE__*/ jsxs("div", {
102
- "data-content": true,
103
- children: [
104
- /*#__PURE__*/ jsxs("div", {
105
- "data-file-list": true,
106
- children: [
107
- /*#__PURE__*/ jsxs("div", {
108
- "data-file": true,
109
- "data-active": true,
110
- children: [
111
- iconRenderer ? /*#__PURE__*/ React.createElement(iconRenderer, {
112
- name: 'file-text'
113
- }) : /*#__PURE__*/ jsx(Icon, {
114
- name: "file-text"
115
- }),
116
- /*#__PURE__*/ jsx("span", {
117
- "data-filename": true,
118
- children: filename
119
- }),
120
- /*#__PURE__*/ jsx("span", {
121
- "data-filepath": true,
122
- children: "app/page.tsx"
123
- })
124
- ]
125
- }),
126
- /*#__PURE__*/ jsxs("div", {
127
- "data-file": true,
128
- children: [
129
- iconRenderer ? /*#__PURE__*/ React.createElement(iconRenderer, {
130
- name: 'file-text'
131
- }) : /*#__PURE__*/ jsx(Icon, {
132
- name: "file-text"
133
- }),
134
- /*#__PURE__*/ jsx("span", {
135
- "data-filename": true,
136
- children: "layout.tsx"
137
- }),
138
- /*#__PURE__*/ jsx("span", {
139
- "data-filepath": true,
140
- children: "app/layout.tsx"
141
- })
142
- ]
143
- }),
144
- /*#__PURE__*/ jsxs("div", {
145
- "data-file": true,
146
- children: [
147
- iconRenderer ? /*#__PURE__*/ React.createElement(iconRenderer, {
148
- name: 'file-text'
149
- }) : /*#__PURE__*/ jsx(Icon, {
150
- name: "file-text"
151
- }),
152
- /*#__PURE__*/ jsx("span", {
153
- "data-filename": true,
154
- children: "globals.css"
155
- }),
156
- /*#__PURE__*/ jsx("span", {
157
- "data-filepath": true,
158
- children: "app/globals.css"
159
- })
160
- ]
161
- })
162
- ]
163
- }),
164
- code && /*#__PURE__*/ jsx(CodeBlock, {
165
- language: language,
166
- code: code
167
- })
168
- ]
169
- })
170
- ]
171
- });
144
+ // Uses React.createElement for maximum compatibility across environments
145
+ return React.createElement('div', {
146
+ className,
147
+ 'data-component': 'code-project-block'
148
+ }, React.createElement('button', {
149
+ onClick: toggleCollapsed,
150
+ 'data-expanded': !collapsed
151
+ }, React.createElement('div', {
152
+ 'data-header': true
153
+ }, iconRenderer ? React.createElement(iconRenderer, {
154
+ name: 'folder'
155
+ }) : React.createElement(Icon, {
156
+ name: 'folder'
157
+ }), React.createElement('span', {
158
+ 'data-title': true
159
+ }, data.title)), React.createElement('span', {
160
+ 'data-version': true
161
+ }, 'v1')), !collapsed ? React.createElement('div', {
162
+ 'data-content': true
163
+ }, React.createElement('div', {
164
+ 'data-file-list': true
165
+ }, data.files.map((file, index)=>React.createElement('div', {
166
+ key: index,
167
+ 'data-file': true,
168
+ ...file.active && {
169
+ 'data-active': true
170
+ }
171
+ }, iconRenderer ? React.createElement(iconRenderer, {
172
+ name: 'file-text'
173
+ }) : React.createElement(Icon, {
174
+ name: 'file-text'
175
+ }), React.createElement('span', {
176
+ 'data-filename': true
177
+ }, file.name), React.createElement('span', {
178
+ 'data-filepath': true
179
+ }, file.path)))), data.code ? React.createElement(CodeBlock, {
180
+ language: data.language,
181
+ code: data.code
182
+ }) : null) : null);
172
183
  }
173
184
 
185
+ // Headless hook for thinking section
186
+ function useThinkingSection({ title, duration, thought, collapsed: initialCollapsed = true, onCollapse }) {
187
+ const [internalCollapsed, setInternalCollapsed] = useState(initialCollapsed);
188
+ const collapsed = onCollapse ? initialCollapsed : internalCollapsed;
189
+ const handleCollapse = onCollapse || (()=>setInternalCollapsed(!internalCollapsed));
190
+ const paragraphs = thought ? thought.split('\n\n') : [];
191
+ const formattedDuration = duration ? `${Math.round(duration)}s` : undefined;
192
+ return {
193
+ data: {
194
+ title: title || 'Thinking',
195
+ duration,
196
+ thought,
197
+ collapsed,
198
+ paragraphs,
199
+ formattedDuration
200
+ },
201
+ collapsed,
202
+ handleCollapse
203
+ };
204
+ }
174
205
  /**
175
206
  * Generic thinking section component
176
207
  * Renders a collapsible section with basic structure - consumers provide styling
208
+ *
209
+ * For headless usage, use the useThinkingSection hook instead.
177
210
  */ function ThinkingSection({ title, duration, thought, collapsed: initialCollapsed = true, onCollapse, className, children, iconRenderer, brainIcon, chevronRightIcon, chevronDownIcon }) {
178
- const [internalCollapsed, setInternalCollapsed] = useState(initialCollapsed);
179
- const collapsed = onCollapse ? initialCollapsed : internalCollapsed;
180
- const handleCollapse = onCollapse || (()=>setInternalCollapsed(!internalCollapsed));
211
+ const { data, collapsed, handleCollapse } = useThinkingSection({
212
+ title,
213
+ duration,
214
+ thought,
215
+ collapsed: initialCollapsed,
216
+ onCollapse
217
+ });
181
218
  // If children provided, use that (allows complete customization)
182
219
  if (children) {
183
- return /*#__PURE__*/ jsx(Fragment, {
184
- children: children
185
- });
220
+ return React.createElement(React.Fragment, {}, children);
186
221
  }
187
- return /*#__PURE__*/ jsxs("div", {
188
- className: className,
189
- "data-component": "thinking-section",
190
- children: [
191
- /*#__PURE__*/ jsxs("button", {
192
- onClick: handleCollapse,
193
- "data-expanded": !collapsed,
194
- "data-button": true,
195
- children: [
196
- /*#__PURE__*/ jsx("div", {
197
- "data-icon-container": true,
198
- children: collapsed ? /*#__PURE__*/ jsxs(Fragment, {
199
- children: [
200
- brainIcon || (iconRenderer ? /*#__PURE__*/ React.createElement(iconRenderer, {
201
- name: 'brain'
202
- }) : /*#__PURE__*/ jsx(Icon, {
203
- name: "brain"
204
- })),
205
- chevronRightIcon || (iconRenderer ? /*#__PURE__*/ React.createElement(iconRenderer, {
206
- name: 'chevron-right'
207
- }) : /*#__PURE__*/ jsx(Icon, {
208
- name: "chevron-right"
209
- }))
210
- ]
211
- }) : chevronDownIcon || (iconRenderer ? /*#__PURE__*/ React.createElement(iconRenderer, {
212
- name: 'chevron-down'
213
- }) : /*#__PURE__*/ jsx(Icon, {
214
- name: "chevron-down"
215
- }))
216
- }),
217
- /*#__PURE__*/ jsxs("span", {
218
- "data-title": true,
219
- children: [
220
- title || 'Thinking',
221
- duration && ` for ${Math.round(duration)}s`
222
- ]
223
- })
224
- ]
225
- }),
226
- !collapsed && thought && /*#__PURE__*/ jsx("div", {
227
- "data-content": true,
228
- children: /*#__PURE__*/ jsx("div", {
229
- "data-thought-container": true,
230
- children: thought.split('\n\n').map((paragraph, index)=>/*#__PURE__*/ jsx("div", {
231
- "data-paragraph": true,
232
- children: paragraph
233
- }, index))
234
- })
235
- })
236
- ]
237
- });
222
+ // Uses React.createElement for maximum compatibility across environments
223
+ return React.createElement('div', {
224
+ className,
225
+ 'data-component': 'thinking-section'
226
+ }, React.createElement('button', {
227
+ onClick: handleCollapse,
228
+ 'data-expanded': !collapsed,
229
+ 'data-button': true
230
+ }, React.createElement('div', {
231
+ 'data-icon-container': true
232
+ }, collapsed ? React.createElement(React.Fragment, {}, brainIcon || (iconRenderer ? React.createElement(iconRenderer, {
233
+ name: 'brain'
234
+ }) : React.createElement(Icon, {
235
+ name: 'brain'
236
+ })), chevronRightIcon || (iconRenderer ? React.createElement(iconRenderer, {
237
+ name: 'chevron-right'
238
+ }) : React.createElement(Icon, {
239
+ name: 'chevron-right'
240
+ }))) : chevronDownIcon || (iconRenderer ? React.createElement(iconRenderer, {
241
+ name: 'chevron-down'
242
+ }) : React.createElement(Icon, {
243
+ name: 'chevron-down'
244
+ }))), React.createElement('span', {
245
+ 'data-title': true
246
+ }, data.title + (data.formattedDuration ? ` for ${data.formattedDuration}` : ''))), !collapsed && data.thought ? React.createElement('div', {
247
+ 'data-content': true
248
+ }, React.createElement('div', {
249
+ 'data-thought-container': true
250
+ }, data.paragraphs.map((paragraph, index)=>React.createElement('div', {
251
+ key: index,
252
+ 'data-paragraph': true
253
+ }, paragraph)))) : null);
238
254
  }
239
255
 
240
- function getTypeIcon(type, title, iconRenderer) {
256
+ function getTypeIcon(type, title) {
241
257
  // Check title content for specific cases
242
258
  if (title?.includes('No issues found')) {
243
- return iconRenderer ? /*#__PURE__*/ React.createElement(iconRenderer, {
244
- name: 'wrench'
245
- }) : /*#__PURE__*/ jsx(Icon, {
246
- name: "wrench"
247
- });
259
+ return 'wrench';
248
260
  }
249
261
  if (title?.includes('Analyzed codebase')) {
250
- return iconRenderer ? /*#__PURE__*/ React.createElement(iconRenderer, {
251
- name: 'search'
252
- }) : /*#__PURE__*/ jsx(Icon, {
253
- name: "search"
254
- });
262
+ return 'search';
255
263
  }
256
264
  // Fallback to type-based icons
257
265
  switch(type){
258
266
  case 'task-search-web-v1':
259
- return iconRenderer ? /*#__PURE__*/ React.createElement(iconRenderer, {
260
- name: 'search'
261
- }) : /*#__PURE__*/ jsx(Icon, {
262
- name: "search"
263
- });
267
+ return 'search';
264
268
  case 'task-search-repo-v1':
265
- return iconRenderer ? /*#__PURE__*/ React.createElement(iconRenderer, {
266
- name: 'folder'
267
- }) : /*#__PURE__*/ jsx(Icon, {
268
- name: "folder"
269
- });
269
+ return 'folder';
270
270
  case 'task-diagnostics-v1':
271
- return iconRenderer ? /*#__PURE__*/ React.createElement(iconRenderer, {
272
- name: 'settings'
273
- }) : /*#__PURE__*/ jsx(Icon, {
274
- name: "settings"
275
- });
271
+ return 'settings';
276
272
  default:
277
- return iconRenderer ? /*#__PURE__*/ React.createElement(iconRenderer, {
278
- name: 'wrench'
279
- }) : /*#__PURE__*/ jsx(Icon, {
280
- name: "wrench"
281
- });
273
+ return 'wrench';
282
274
  }
283
275
  }
284
- function renderTaskPart(part, index, iconRenderer) {
276
+ function processTaskPart(part, index) {
277
+ const baseData = {
278
+ type: part.type,
279
+ status: part.status,
280
+ content: null
281
+ };
285
282
  if (part.type === 'search-web') {
286
283
  if (part.status === 'searching') {
287
- return /*#__PURE__*/ jsx("div", {
288
- children: `Searching "${part.query}"`
289
- }, index);
284
+ return {
285
+ ...baseData,
286
+ isSearching: true,
287
+ query: part.query,
288
+ content: `Searching "${part.query}"`
289
+ };
290
290
  }
291
291
  if (part.status === 'analyzing') {
292
- return /*#__PURE__*/ jsx("div", {
293
- children: `Analyzing ${part.count} results...`
294
- }, index);
292
+ return {
293
+ ...baseData,
294
+ isAnalyzing: true,
295
+ count: part.count,
296
+ content: `Analyzing ${part.count} results...`
297
+ };
295
298
  }
296
299
  if (part.status === 'complete' && part.answer) {
297
- return /*#__PURE__*/ jsxs("div", {
298
- children: [
299
- /*#__PURE__*/ jsx("p", {
300
- children: part.answer
301
- }),
302
- part.sources && part.sources.length > 0 && /*#__PURE__*/ jsx("div", {
303
- children: part.sources.map((source, sourceIndex)=>/*#__PURE__*/ jsx("a", {
304
- href: source.url,
305
- target: "_blank",
306
- rel: "noopener noreferrer",
307
- children: source.title
308
- }, sourceIndex))
309
- })
310
- ]
311
- }, index);
300
+ return {
301
+ ...baseData,
302
+ isComplete: true,
303
+ answer: part.answer,
304
+ sources: part.sources,
305
+ content: part.answer
306
+ };
312
307
  }
313
308
  }
314
309
  if (part.type === 'search-repo') {
315
310
  if (part.status === 'searching') {
316
- return /*#__PURE__*/ jsx("div", {
317
- children: `Searching "${part.query}"`
318
- }, index);
311
+ return {
312
+ ...baseData,
313
+ isSearching: true,
314
+ query: part.query,
315
+ content: `Searching "${part.query}"`
316
+ };
319
317
  }
320
318
  if (part.status === 'reading' && part.files) {
321
- return /*#__PURE__*/ jsxs("div", {
322
- children: [
323
- /*#__PURE__*/ jsx("span", {
324
- children: "Reading files"
325
- }),
326
- part.files.map((file, fileIndex)=>/*#__PURE__*/ jsxs("span", {
327
- children: [
328
- iconRenderer ? /*#__PURE__*/ React.createElement(iconRenderer, {
329
- name: 'file-text'
330
- }) : /*#__PURE__*/ jsx(Icon, {
331
- name: "file-text"
332
- }),
333
- ' ',
334
- file
335
- ]
336
- }, fileIndex))
337
- ]
338
- }, index);
319
+ return {
320
+ ...baseData,
321
+ files: part.files,
322
+ content: 'Reading files'
323
+ };
339
324
  }
340
325
  }
341
326
  if (part.type === 'diagnostics') {
342
327
  if (part.status === 'checking') {
343
- return /*#__PURE__*/ jsx("div", {
344
- children: "Checking for issues..."
345
- }, index);
328
+ return {
329
+ ...baseData,
330
+ content: 'Checking for issues...'
331
+ };
346
332
  }
347
333
  if (part.status === 'complete' && part.issues === 0) {
348
- return /*#__PURE__*/ jsx("div", {
349
- children: "✅ No issues found"
350
- }, index);
334
+ return {
335
+ ...baseData,
336
+ isComplete: true,
337
+ issues: part.issues,
338
+ content: '✅ No issues found'
339
+ };
351
340
  }
352
341
  }
353
- return /*#__PURE__*/ jsx("div", {
354
- children: JSON.stringify(part)
355
- }, index);
342
+ return {
343
+ ...baseData,
344
+ content: JSON.stringify(part)
345
+ };
356
346
  }
357
- /**
358
- * Generic task section component
359
- * Renders a collapsible task section with basic structure - consumers provide styling
360
- */ function TaskSection({ title, type, parts = [], collapsed: initialCollapsed = true, onCollapse, className, children, iconRenderer, taskIcon, chevronRightIcon, chevronDownIcon }) {
347
+ function renderTaskPartContent(partData, index, iconRenderer) {
348
+ if (partData.type === 'search-web' && partData.isComplete && partData.sources) {
349
+ return React.createElement('div', {
350
+ key: index
351
+ }, React.createElement('p', {}, partData.content), partData.sources.length > 0 ? React.createElement('div', {}, partData.sources.map((source, sourceIndex)=>React.createElement('a', {
352
+ key: sourceIndex,
353
+ href: source.url,
354
+ target: '_blank',
355
+ rel: 'noopener noreferrer'
356
+ }, source.title))) : null);
357
+ }
358
+ if (partData.type === 'search-repo' && partData.files) {
359
+ return React.createElement('div', {
360
+ key: index
361
+ }, React.createElement('span', {}, partData.content), partData.files.map((file, fileIndex)=>React.createElement('span', {
362
+ key: fileIndex
363
+ }, iconRenderer ? React.createElement(iconRenderer, {
364
+ name: 'file-text'
365
+ }) : React.createElement(Icon, {
366
+ name: 'file-text'
367
+ }), ' ', file)));
368
+ }
369
+ return React.createElement('div', {
370
+ key: index
371
+ }, partData.content);
372
+ }
373
+ // Headless hook for task section
374
+ function useTaskSection({ title, type, parts = [], collapsed: initialCollapsed = true, onCollapse }) {
361
375
  const [internalCollapsed, setInternalCollapsed] = useState(initialCollapsed);
362
376
  const collapsed = onCollapse ? initialCollapsed : internalCollapsed;
363
377
  const handleCollapse = onCollapse || (()=>setInternalCollapsed(!internalCollapsed));
364
- // If children provided, use that (allows complete customization)
365
- if (children) {
366
- return /*#__PURE__*/ jsx(Fragment, {
367
- children: children
368
- });
369
- }
370
378
  // Count meaningful parts (parts that would render something)
371
379
  const meaningfulParts = parts.filter((part)=>{
372
380
  // Check if the part would render meaningful content
@@ -380,174 +388,216 @@ function renderTaskPart(part, index, iconRenderer) {
380
388
  if (part.type === 'finished-web-search' && part.answer) return true;
381
389
  if (part.type === 'diagnostics-passed') return true;
382
390
  if (part.type === 'fetching-diagnostics') return true;
383
- // Add more meaningful part types as needed
384
391
  return false;
385
392
  });
393
+ const processedParts = parts.map(processTaskPart);
394
+ return {
395
+ data: {
396
+ title: title || 'Task',
397
+ type,
398
+ parts,
399
+ collapsed,
400
+ meaningfulParts,
401
+ shouldShowCollapsible: meaningfulParts.length > 1,
402
+ iconName: getTypeIcon(type, title)
403
+ },
404
+ collapsed,
405
+ handleCollapse,
406
+ processedParts
407
+ };
408
+ }
409
+ /**
410
+ * Generic task section component
411
+ * Renders a collapsible task section with basic structure - consumers provide styling
412
+ *
413
+ * For headless usage, use the useTaskSection hook instead.
414
+ */ function TaskSection({ title, type, parts = [], collapsed: initialCollapsed = true, onCollapse, className, children, iconRenderer, taskIcon, chevronRightIcon, chevronDownIcon }) {
415
+ const { data, collapsed, handleCollapse, processedParts } = useTaskSection({
416
+ title,
417
+ type,
418
+ parts,
419
+ collapsed: initialCollapsed,
420
+ onCollapse
421
+ });
422
+ // If children provided, use that (allows complete customization)
423
+ if (children) {
424
+ return React.createElement(React.Fragment, {}, children);
425
+ }
386
426
  // If there's only one meaningful part, show just the content without the collapsible wrapper
387
- if (meaningfulParts.length === 1) {
388
- return /*#__PURE__*/ jsx("div", {
389
- className: className,
390
- "data-component": "task-section-inline",
391
- children: /*#__PURE__*/ jsx("div", {
392
- "data-part": true,
393
- children: renderTaskPart(meaningfulParts[0], 0, iconRenderer)
394
- })
395
- });
427
+ if (!data.shouldShowCollapsible && data.meaningfulParts.length === 1) {
428
+ const partData = processTaskPart(data.meaningfulParts[0]);
429
+ return React.createElement('div', {
430
+ className,
431
+ 'data-component': 'task-section-inline'
432
+ }, React.createElement('div', {
433
+ 'data-part': true
434
+ }, renderTaskPartContent(partData, 0, iconRenderer)));
396
435
  }
397
- return /*#__PURE__*/ jsxs("div", {
398
- className: className,
399
- "data-component": "task-section",
400
- children: [
401
- /*#__PURE__*/ jsxs("button", {
402
- onClick: handleCollapse,
403
- "data-expanded": !collapsed,
404
- "data-button": true,
405
- children: [
406
- /*#__PURE__*/ jsxs("div", {
407
- "data-icon-container": true,
408
- children: [
409
- /*#__PURE__*/ jsx("div", {
410
- "data-task-icon": true,
411
- children: taskIcon || getTypeIcon(type, title, iconRenderer)
412
- }),
413
- collapsed ? chevronRightIcon || (iconRenderer ? /*#__PURE__*/ React.createElement(iconRenderer, {
414
- name: 'chevron-right'
415
- }) : /*#__PURE__*/ jsx(Icon, {
416
- name: "chevron-right"
417
- })) : chevronDownIcon || (iconRenderer ? /*#__PURE__*/ React.createElement(iconRenderer, {
418
- name: 'chevron-down'
419
- }) : /*#__PURE__*/ jsx(Icon, {
420
- name: "chevron-down"
421
- }))
422
- ]
423
- }),
424
- /*#__PURE__*/ jsx("span", {
425
- "data-title": true,
426
- children: title || 'Task'
427
- })
428
- ]
429
- }),
430
- !collapsed && /*#__PURE__*/ jsx("div", {
431
- "data-content": true,
432
- children: /*#__PURE__*/ jsx("div", {
433
- "data-parts-container": true,
434
- children: parts.map((part, index)=>/*#__PURE__*/ jsx("div", {
435
- "data-part": true,
436
- children: renderTaskPart(part, index, iconRenderer)
437
- }, index))
438
- })
439
- })
440
- ]
441
- });
436
+ // Uses React.createElement for maximum compatibility across environments
437
+ return React.createElement('div', {
438
+ className,
439
+ 'data-component': 'task-section'
440
+ }, React.createElement('button', {
441
+ onClick: handleCollapse,
442
+ 'data-expanded': !collapsed,
443
+ 'data-button': true
444
+ }, React.createElement('div', {
445
+ 'data-icon-container': true
446
+ }, React.createElement('div', {
447
+ 'data-task-icon': true
448
+ }, taskIcon || (iconRenderer ? React.createElement(iconRenderer, {
449
+ name: data.iconName
450
+ }) : React.createElement(Icon, {
451
+ name: data.iconName
452
+ }))), collapsed ? chevronRightIcon || (iconRenderer ? React.createElement(iconRenderer, {
453
+ name: 'chevron-right'
454
+ }) : React.createElement(Icon, {
455
+ name: 'chevron-right'
456
+ })) : chevronDownIcon || (iconRenderer ? React.createElement(iconRenderer, {
457
+ name: 'chevron-down'
458
+ }) : React.createElement(Icon, {
459
+ name: 'chevron-down'
460
+ }))), React.createElement('span', {
461
+ 'data-title': true
462
+ }, data.title)), !collapsed ? React.createElement('div', {
463
+ 'data-content': true
464
+ }, React.createElement('div', {
465
+ 'data-parts-container': true
466
+ }, processedParts.map((partData, index)=>React.createElement('div', {
467
+ key: index,
468
+ 'data-part': true
469
+ }, renderTaskPartContent(partData, index, iconRenderer))))) : null);
442
470
  }
443
471
 
444
- function ContentPartRenderer({ part, iconRenderer, thinkingSectionRenderer, taskSectionRenderer, brainIcon, chevronRightIcon, chevronDownIcon, searchIcon, folderIcon, settingsIcon, wrenchIcon }) {
445
- if (!part) return null;
472
+ // Headless hook for content part
473
+ function useContentPart(part) {
474
+ if (!part) {
475
+ return {
476
+ type: '',
477
+ parts: [],
478
+ metadata: {},
479
+ componentType: null
480
+ };
481
+ }
446
482
  const { type, parts = [], ...metadata } = part;
483
+ let componentType = 'unknown';
484
+ let title;
485
+ let iconName;
486
+ let thinkingData;
447
487
  switch(type){
448
488
  case 'task-thinking-v1':
449
- {
450
- const thinkingPart = parts.find((p)=>p.type === 'thinking-end');
451
- const ThinkingComponent = thinkingSectionRenderer || ThinkingSection;
452
- const [collapsed, setCollapsed] = useState(true);
453
- return /*#__PURE__*/ jsx(ThinkingComponent, {
454
- title: "Thought",
455
- duration: thinkingPart?.duration,
456
- thought: thinkingPart?.thought,
457
- collapsed: collapsed,
458
- onCollapse: ()=>setCollapsed(!collapsed),
459
- brainIcon: brainIcon,
460
- chevronRightIcon: chevronRightIcon,
461
- chevronDownIcon: chevronDownIcon
462
- });
463
- }
489
+ componentType = 'thinking';
490
+ title = 'Thought';
491
+ const thinkingPart = parts.find((p)=>p.type === 'thinking-end');
492
+ thinkingData = {
493
+ duration: thinkingPart?.duration,
494
+ thought: thinkingPart?.thought
495
+ };
496
+ break;
464
497
  case 'task-search-web-v1':
465
- {
466
- const TaskComponent = taskSectionRenderer || TaskSection;
467
- const [collapsed, setCollapsed] = useState(true);
468
- return /*#__PURE__*/ jsx(TaskComponent, {
469
- title: metadata.taskNameComplete || metadata.taskNameActive,
470
- type: type,
471
- parts: parts,
472
- collapsed: collapsed,
473
- onCollapse: ()=>setCollapsed(!collapsed),
474
- taskIcon: searchIcon,
475
- chevronRightIcon: chevronRightIcon,
476
- chevronDownIcon: chevronDownIcon
477
- });
478
- }
498
+ componentType = 'task';
499
+ title = metadata.taskNameComplete || metadata.taskNameActive;
500
+ iconName = 'search';
501
+ break;
479
502
  case 'task-search-repo-v1':
480
- {
481
- const TaskComponent = taskSectionRenderer || TaskSection;
482
- const [collapsed, setCollapsed] = useState(true);
483
- return /*#__PURE__*/ jsx(TaskComponent, {
484
- title: metadata.taskNameComplete || metadata.taskNameActive,
485
- type: type,
486
- parts: parts,
487
- collapsed: collapsed,
488
- onCollapse: ()=>setCollapsed(!collapsed),
489
- taskIcon: folderIcon,
490
- chevronRightIcon: chevronRightIcon,
491
- chevronDownIcon: chevronDownIcon
492
- });
493
- }
503
+ componentType = 'task';
504
+ title = metadata.taskNameComplete || metadata.taskNameActive;
505
+ iconName = 'folder';
506
+ break;
494
507
  case 'task-diagnostics-v1':
495
- {
496
- const TaskComponent = taskSectionRenderer || TaskSection;
497
- const [collapsed, setCollapsed] = useState(true);
498
- return /*#__PURE__*/ jsx(TaskComponent, {
499
- title: metadata.taskNameComplete || metadata.taskNameActive,
500
- type: type,
501
- parts: parts,
502
- collapsed: collapsed,
503
- onCollapse: ()=>setCollapsed(!collapsed),
504
- taskIcon: settingsIcon,
505
- chevronRightIcon: chevronRightIcon,
506
- chevronDownIcon: chevronDownIcon
507
- });
508
- }
508
+ componentType = 'task';
509
+ title = metadata.taskNameComplete || metadata.taskNameActive;
510
+ iconName = 'settings';
511
+ break;
509
512
  case 'task-read-file-v1':
510
- {
511
- const TaskComponent = taskSectionRenderer || TaskSection;
512
- const [collapsed, setCollapsed] = useState(true);
513
- return /*#__PURE__*/ jsx(TaskComponent, {
514
- title: metadata.taskNameComplete || metadata.taskNameActive || 'Reading file',
515
- type: type,
516
- parts: parts,
517
- collapsed: collapsed,
518
- onCollapse: ()=>setCollapsed(!collapsed),
519
- taskIcon: folderIcon,
520
- chevronRightIcon: chevronRightIcon,
521
- chevronDownIcon: chevronDownIcon
522
- });
523
- }
513
+ componentType = 'task';
514
+ title = metadata.taskNameComplete || metadata.taskNameActive || 'Reading file';
515
+ iconName = 'folder';
516
+ break;
524
517
  case 'task-coding-v1':
525
- {
526
- const TaskComponent = taskSectionRenderer || TaskSection;
527
- const [collapsed, setCollapsed] = useState(true);
528
- return /*#__PURE__*/ jsx(TaskComponent, {
529
- title: metadata.taskNameComplete || metadata.taskNameActive || 'Coding',
530
- type: type,
531
- parts: parts,
532
- collapsed: collapsed,
533
- onCollapse: ()=>setCollapsed(!collapsed),
534
- taskIcon: wrenchIcon,
535
- chevronRightIcon: chevronRightIcon,
536
- chevronDownIcon: chevronDownIcon
537
- });
538
- }
518
+ componentType = 'task';
519
+ title = metadata.taskNameComplete || metadata.taskNameActive || 'Coding';
520
+ iconName = 'wrench';
521
+ break;
539
522
  case 'task-start-v1':
540
- // Usually just indicates task start - can be hidden or show as status
541
- return null;
523
+ componentType = null; // Usually just indicates task start - can be hidden
524
+ break;
542
525
  default:
543
- return /*#__PURE__*/ jsxs("div", {
544
- "data-unknown-part-type": type,
545
- children: [
546
- "Unknown part type: ",
547
- type
548
- ]
549
- });
526
+ componentType = 'unknown';
527
+ break;
528
+ }
529
+ return {
530
+ type,
531
+ parts,
532
+ metadata,
533
+ componentType,
534
+ title,
535
+ iconName,
536
+ thinkingData
537
+ };
538
+ }
539
+ /**
540
+ * Content part renderer that handles different types of v0 API content parts
541
+ *
542
+ * For headless usage, use the useContentPart hook instead.
543
+ */ function ContentPartRenderer({ part, iconRenderer, thinkingSectionRenderer, taskSectionRenderer, brainIcon, chevronRightIcon, chevronDownIcon, searchIcon, folderIcon, settingsIcon, wrenchIcon }) {
544
+ const contentData = useContentPart(part);
545
+ if (!contentData.componentType) {
546
+ return null;
547
+ }
548
+ if (contentData.componentType === 'thinking') {
549
+ const ThinkingComponent = thinkingSectionRenderer || ThinkingSection;
550
+ const [collapsed, setCollapsed] = useState(true);
551
+ return React.createElement(ThinkingComponent, {
552
+ title: contentData.title,
553
+ duration: contentData.thinkingData?.duration,
554
+ thought: contentData.thinkingData?.thought,
555
+ collapsed,
556
+ onCollapse: ()=>setCollapsed(!collapsed),
557
+ brainIcon,
558
+ chevronRightIcon,
559
+ chevronDownIcon
560
+ });
550
561
  }
562
+ if (contentData.componentType === 'task') {
563
+ const TaskComponent = taskSectionRenderer || TaskSection;
564
+ const [collapsed, setCollapsed] = useState(true);
565
+ // Map icon names to icon components
566
+ let taskIcon;
567
+ switch(contentData.iconName){
568
+ case 'search':
569
+ taskIcon = searchIcon;
570
+ break;
571
+ case 'folder':
572
+ taskIcon = folderIcon;
573
+ break;
574
+ case 'settings':
575
+ taskIcon = settingsIcon;
576
+ break;
577
+ case 'wrench':
578
+ taskIcon = wrenchIcon;
579
+ break;
580
+ default:
581
+ taskIcon = undefined;
582
+ break;
583
+ }
584
+ return React.createElement(TaskComponent, {
585
+ title: contentData.title,
586
+ type: contentData.type,
587
+ parts: contentData.parts,
588
+ collapsed,
589
+ onCollapse: ()=>setCollapsed(!collapsed),
590
+ taskIcon,
591
+ chevronRightIcon,
592
+ chevronDownIcon
593
+ });
594
+ }
595
+ if (contentData.componentType === 'unknown') {
596
+ return React.createElement('div', {
597
+ 'data-unknown-part-type': contentData.type
598
+ }, `Unknown part type: ${contentData.type}`);
599
+ }
600
+ return null;
551
601
  }
552
602
 
553
603
  // Utility function to merge class names
@@ -555,11 +605,17 @@ function cn(...classes) {
555
605
  return classes.filter(Boolean).join(' ');
556
606
  }
557
607
 
558
- // Simplified renderer that matches v0's exact approach
559
- function MessageImpl({ content, messageId = 'unknown', role: _role = 'assistant', streaming: _streaming = false, isLastMessage: _isLastMessage = false, className, components, renderers }) {
608
+ // Headless hook for processing message content
609
+ function useMessage({ content, messageId = 'unknown', role = 'assistant', streaming = false, isLastMessage = false, components, renderers }) {
560
610
  if (!Array.isArray(content)) {
561
611
  console.warn('MessageContent: content must be an array (MessageBinaryFormat)');
562
- return null;
612
+ return {
613
+ elements: [],
614
+ messageId,
615
+ role,
616
+ streaming,
617
+ isLastMessage
618
+ };
563
619
  }
564
620
  // Merge components and renderers (backward compatibility)
565
621
  const mergedComponents = {
@@ -583,10 +639,7 @@ function MessageImpl({ content, messageId = 'unknown', role: _role = 'assistant'
583
639
  const key = `${messageId}-${index}`;
584
640
  // Markdown data (type 0) - this is the main content
585
641
  if (type === 0) {
586
- return /*#__PURE__*/ jsx(Elements, {
587
- data: data,
588
- components: mergedComponents
589
- }, key);
642
+ return processElements(data, key, mergedComponents);
590
643
  }
591
644
  // Metadata (type 1) - extract context but don't render
592
645
  if (type === 1) {
@@ -596,33 +649,40 @@ function MessageImpl({ content, messageId = 'unknown', role: _role = 'assistant'
596
649
  }
597
650
  // Other types - v0 doesn't handle these in the main renderer
598
651
  return null;
599
- });
600
- return /*#__PURE__*/ jsx("div", {
601
- className: className,
602
- children: elements
603
- });
652
+ }).filter(Boolean);
653
+ return {
654
+ elements,
655
+ messageId,
656
+ role,
657
+ streaming,
658
+ isLastMessage
659
+ };
604
660
  }
605
- // This component handles the markdown data array (equivalent to v0's Elements component)
606
- function Elements({ data, components }) {
661
+ // Process elements into headless data structure
662
+ function processElements(data, keyPrefix, components) {
607
663
  // Handle case where data might not be an array due to streaming/patching
608
664
  if (!Array.isArray(data)) {
609
665
  return null;
610
666
  }
611
- const renderedElements = data.map((item, index)=>{
612
- const key = `element-${index}`;
613
- return renderElement(item, key, components);
614
- }).filter(Boolean) // Filter out null/undefined elements
615
- ;
616
- return /*#__PURE__*/ jsx(Fragment, {
617
- children: renderedElements
618
- });
667
+ const children = data.map((item, index)=>{
668
+ const key = `${keyPrefix}-${index}`;
669
+ return processElement(item, key, components);
670
+ }).filter(Boolean);
671
+ return {
672
+ type: 'component',
673
+ key: keyPrefix,
674
+ data: 'elements',
675
+ children
676
+ };
619
677
  }
620
- // Render individual elements (equivalent to v0's element rendering logic)
621
- function renderElement(element, key, components) {
678
+ // Process individual elements into headless data structure
679
+ function processElement(element, key, components) {
622
680
  if (typeof element === 'string') {
623
- return /*#__PURE__*/ jsx("span", {
624
- children: element
625
- }, key);
681
+ return {
682
+ type: 'text',
683
+ key,
684
+ data: element
685
+ };
626
686
  }
627
687
  if (!Array.isArray(element)) {
628
688
  return null;
@@ -633,69 +693,144 @@ function renderElement(element, key, components) {
633
693
  }
634
694
  // Handle special v0 Platform API elements
635
695
  if (tagName === 'AssistantMessageContentPart') {
636
- return /*#__PURE__*/ jsx(ContentPartRenderer, {
637
- part: props.part,
638
- iconRenderer: components?.Icon,
639
- thinkingSectionRenderer: components?.ThinkingSection,
640
- taskSectionRenderer: components?.TaskSection
641
- }, key);
696
+ return {
697
+ type: 'content-part',
698
+ key,
699
+ data: {
700
+ part: props.part,
701
+ iconRenderer: components?.Icon,
702
+ thinkingSectionRenderer: components?.ThinkingSection,
703
+ taskSectionRenderer: components?.TaskSection
704
+ }
705
+ };
642
706
  }
643
707
  if (tagName === 'Codeblock') {
644
- const CustomCodeProjectPart = components?.CodeProjectPart;
645
- const CodeProjectComponent = CustomCodeProjectPart || CodeProjectPart;
646
- return /*#__PURE__*/ jsx(CodeProjectComponent, {
647
- language: props.lang,
648
- code: children[0],
649
- iconRenderer: components?.Icon
650
- }, key);
708
+ return {
709
+ type: 'code-project',
710
+ key,
711
+ data: {
712
+ language: props.lang,
713
+ code: children[0],
714
+ iconRenderer: components?.Icon,
715
+ customRenderer: components?.CodeProjectPart
716
+ }
717
+ };
651
718
  }
652
719
  if (tagName === 'text') {
653
- return /*#__PURE__*/ jsx("span", {
654
- children: children[0] || ''
655
- }, key);
720
+ return {
721
+ type: 'text',
722
+ key,
723
+ data: children[0] || ''
724
+ };
656
725
  }
657
- // Render children
658
- const renderedChildren = children.map((child, childIndex)=>{
726
+ // Process children
727
+ const processedChildren = children.map((child, childIndex)=>{
659
728
  const childKey = `${key}-child-${childIndex}`;
660
- return renderElement(child, childKey, components);
729
+ return processElement(child, childKey, components);
661
730
  }).filter(Boolean);
662
731
  // Handle standard HTML elements
663
- const className = props?.className;
664
732
  const componentOrConfig = components?.[tagName];
665
- if (typeof componentOrConfig === 'function') {
666
- const Component = componentOrConfig;
667
- return /*#__PURE__*/ jsx(Component, {
668
- ...props,
669
- className: className,
670
- children: renderedChildren
671
- }, key);
672
- } else if (componentOrConfig && typeof componentOrConfig === 'object') {
673
- const mergedClassName = cn(className, componentOrConfig.className);
674
- return /*#__PURE__*/ React.createElement(tagName, {
675
- key,
676
- ...props,
677
- className: mergedClassName
678
- }, renderedChildren);
679
- } else {
680
- // Default HTML element rendering
681
- const elementProps = {
682
- key,
683
- ...props
684
- };
685
- if (className) {
686
- elementProps.className = className;
687
- }
688
- // Special handling for links
689
- if (tagName === 'a') {
690
- elementProps.target = '_blank';
691
- elementProps.rel = 'noopener noreferrer';
733
+ return {
734
+ type: 'html',
735
+ key,
736
+ data: {
737
+ tagName,
738
+ props,
739
+ componentOrConfig
740
+ },
741
+ children: processedChildren
742
+ };
743
+ }
744
+ // Default JSX renderer for backward compatibility
745
+ function MessageRenderer({ messageData, className }) {
746
+ const renderElement = (element)=>{
747
+ switch(element.type){
748
+ case 'text':
749
+ return React.createElement('span', {
750
+ key: element.key
751
+ }, element.data);
752
+ case 'content-part':
753
+ return React.createElement(ContentPartRenderer, {
754
+ key: element.key,
755
+ part: element.data.part,
756
+ iconRenderer: element.data.iconRenderer,
757
+ thinkingSectionRenderer: element.data.thinkingSectionRenderer,
758
+ taskSectionRenderer: element.data.taskSectionRenderer
759
+ });
760
+ case 'code-project':
761
+ const CustomCodeProjectPart = element.data.customRenderer;
762
+ const CodeProjectComponent = CustomCodeProjectPart || CodeProjectPart;
763
+ return React.createElement(CodeProjectComponent, {
764
+ key: element.key,
765
+ language: element.data.language,
766
+ code: element.data.code,
767
+ iconRenderer: element.data.iconRenderer
768
+ });
769
+ case 'html':
770
+ const { tagName, props, componentOrConfig } = element.data;
771
+ const renderedChildren = element.children?.map(renderElement);
772
+ if (typeof componentOrConfig === 'function') {
773
+ const Component = componentOrConfig;
774
+ return React.createElement(Component, {
775
+ key: element.key,
776
+ ...props,
777
+ className: props?.className
778
+ }, renderedChildren);
779
+ } else if (componentOrConfig && typeof componentOrConfig === 'object') {
780
+ const mergedClassName = cn(props?.className, componentOrConfig.className);
781
+ return React.createElement(tagName, {
782
+ key: element.key,
783
+ ...props,
784
+ className: mergedClassName
785
+ }, renderedChildren);
786
+ } else {
787
+ // Default HTML element rendering
788
+ const elementProps = {
789
+ key: element.key,
790
+ ...props
791
+ };
792
+ if (props?.className) {
793
+ elementProps.className = props.className;
794
+ }
795
+ // Special handling for links
796
+ if (tagName === 'a') {
797
+ elementProps.target = '_blank';
798
+ elementProps.rel = 'noopener noreferrer';
799
+ }
800
+ return React.createElement(tagName, elementProps, renderedChildren);
801
+ }
802
+ case 'component':
803
+ return React.createElement(React.Fragment, {
804
+ key: element.key
805
+ }, element.children?.map(renderElement));
806
+ default:
807
+ return null;
692
808
  }
693
- return /*#__PURE__*/ React.createElement(tagName, elementProps, renderedChildren);
694
- }
809
+ };
810
+ return React.createElement('div', {
811
+ className
812
+ }, messageData.elements.map(renderElement));
813
+ }
814
+ // Simplified renderer that matches v0's exact approach (backward compatibility)
815
+ function MessageImpl({ content, messageId = 'unknown', role = 'assistant', streaming = false, isLastMessage = false, className, components, renderers }) {
816
+ const messageData = useMessage({
817
+ content,
818
+ messageId,
819
+ role,
820
+ streaming,
821
+ isLastMessage,
822
+ components,
823
+ renderers
824
+ });
825
+ return React.createElement(MessageRenderer, {
826
+ messageData,
827
+ className
828
+ });
695
829
  }
696
830
  /**
697
831
  * Main component for rendering v0 Platform API message content
698
- */ const Message = /*#__PURE__*/ React.memo(MessageImpl);
832
+ * This is a backward-compatible JSX renderer. For headless usage, use the useMessage hook.
833
+ */ const Message = React.memo(MessageImpl);
699
834
 
700
835
  const jdf = jsondiffpatch.create({});
701
836
  // Exact copy of the patch function from v0/chat/lib/diffpatch.ts
@@ -755,7 +890,6 @@ class StreamStateManager {
755
890
  this.processStream = async (stream, options = {})=>{
756
891
  // Prevent processing the same stream multiple times
757
892
  if (this.processedStreams.has(stream)) {
758
- console.log('Stream already processed, skipping');
759
893
  return;
760
894
  }
761
895
  // Handle locked streams gracefully
@@ -810,13 +944,11 @@ class StreamStateManager {
810
944
  while(true){
811
945
  const { done, value } = await reader.read();
812
946
  if (done) {
813
- console.log('Stream reading completed');
814
947
  break;
815
948
  }
816
949
  const chunk = decoder.decode(value, {
817
950
  stream: true
818
951
  });
819
- console.log('Received raw chunk:', chunk);
820
952
  buffer += chunk;
821
953
  const lines = buffer.split('\n');
822
954
  buffer = lines.pop() || '';
@@ -824,13 +956,11 @@ class StreamStateManager {
824
956
  if (line.trim() === '') {
825
957
  continue;
826
958
  }
827
- console.log('Processing line:', line);
828
959
  // Handle SSE format (data: ...)
829
960
  let jsonData;
830
961
  if (line.startsWith('data: ')) {
831
962
  jsonData = line.slice(6); // Remove "data: " prefix
832
963
  if (jsonData === '[DONE]') {
833
- console.log('Stream marked as done via SSE');
834
964
  this.setComplete(true);
835
965
  options.onComplete?.(currentContent);
836
966
  return;
@@ -842,28 +972,21 @@ class StreamStateManager {
842
972
  try {
843
973
  // Parse the JSON data
844
974
  const parsedData = JSON.parse(jsonData);
845
- console.log('Parsed data:', JSON.stringify(parsedData, null, 2));
846
975
  // Handle v0 streaming format
847
976
  if (parsedData.type === 'connected') {
848
- console.log('Stream connected');
849
977
  continue;
850
978
  } else if (parsedData.type === 'done') {
851
- console.log('Stream marked as done');
852
979
  this.setComplete(true);
853
980
  options.onComplete?.(currentContent);
854
981
  return;
855
- } else if (parsedData.object === 'chat' && parsedData.id) {
856
- // Handle the initial chat data message
857
- console.log('Received chat data:', parsedData.id);
982
+ } else if (parsedData.object && parsedData.object.startsWith('chat')) {
983
+ // Handle chat metadata messages (chat, chat.title, chat.name, etc.)
858
984
  options.onChatData?.(parsedData);
859
985
  continue;
860
986
  } else if (parsedData.delta) {
861
987
  // Apply the delta using jsondiffpatch
862
- console.log('Applying delta to content:', JSON.stringify(currentContent, null, 2));
863
- console.log('Delta:', JSON.stringify(parsedData.delta, null, 2));
864
988
  const patchedContent = patch(currentContent, parsedData.delta);
865
989
  currentContent = Array.isArray(patchedContent) ? patchedContent : [];
866
- console.log('Patched content result:', JSON.stringify(currentContent, null, 2));
867
990
  this.updateContent(currentContent);
868
991
  options.onChunk?.(currentContent);
869
992
  }
@@ -896,16 +1019,39 @@ class StreamStateManager {
896
1019
  if (stream !== lastStreamRef.current) {
897
1020
  lastStreamRef.current = stream;
898
1021
  if (stream) {
899
- console.log('New stream detected, starting processing');
900
1022
  manager.processStream(stream, options);
901
1023
  }
902
1024
  }
903
1025
  return state;
904
1026
  }
905
1027
 
1028
+ // Headless hook for streaming message
1029
+ function useStreamingMessageData({ stream, messageId = 'unknown', role = 'assistant', components, renderers, onChunk, onComplete, onError, onChatData }) {
1030
+ const streamingState = useStreamingMessage(stream, {
1031
+ onChunk,
1032
+ onComplete,
1033
+ onError,
1034
+ onChatData
1035
+ });
1036
+ const messageData = streamingState.content.length > 0 ? useMessage({
1037
+ content: streamingState.content,
1038
+ messageId,
1039
+ role,
1040
+ streaming: streamingState.isStreaming,
1041
+ isLastMessage: true,
1042
+ components,
1043
+ renderers
1044
+ }) : null;
1045
+ return {
1046
+ ...streamingState,
1047
+ messageData
1048
+ };
1049
+ }
906
1050
  /**
907
1051
  * Component for rendering streaming message content from v0 API
908
1052
  *
1053
+ * For headless usage, use the useStreamingMessageData hook instead.
1054
+ *
909
1055
  * @example
910
1056
  * ```tsx
911
1057
  * import { v0 } from 'v0-sdk'
@@ -930,81 +1076,106 @@ class StreamStateManager {
930
1076
  * stream={stream}
931
1077
  * messageId="demo-message"
932
1078
  * role="assistant"
933
- * onComplete={(content) => console.log('Stream complete:', content)}
934
- * onChatData={(chatData) => console.log('Chat created:', chatData.id)}
1079
+ * onComplete={(content) => handleCompletion(content)}
1080
+ * onChatData={(chatData) => handleChatData(chatData)}
935
1081
  * />
936
1082
  * )}
937
1083
  * </div>
938
1084
  * )
939
1085
  * }
940
1086
  * ```
941
- */ function StreamingMessage({ stream, showLoadingIndicator = true, loadingComponent, errorComponent, onChunk, onComplete, onError, onChatData, ...messageProps }) {
942
- const { content, isStreaming, error, isComplete } = useStreamingMessage(stream, {
1087
+ */ function StreamingMessage({ stream, showLoadingIndicator = true, loadingComponent, errorComponent, onChunk, onComplete, onError, onChatData, className, ...messageProps }) {
1088
+ const streamingData = useStreamingMessageData({
1089
+ stream,
943
1090
  onChunk,
944
1091
  onComplete,
945
1092
  onError,
946
- onChatData
1093
+ onChatData,
1094
+ ...messageProps
947
1095
  });
948
1096
  // Handle error state
949
- if (error) {
1097
+ if (streamingData.error) {
950
1098
  if (errorComponent) {
951
- return /*#__PURE__*/ jsx(Fragment, {
952
- children: errorComponent(error)
953
- });
1099
+ return React.createElement(React.Fragment, {}, errorComponent(streamingData.error));
954
1100
  }
955
- return /*#__PURE__*/ jsxs("div", {
956
- className: "text-red-500 p-4 border border-red-200 rounded",
957
- children: [
958
- "Error: ",
959
- error
960
- ]
961
- });
1101
+ // Fallback error component using React.createElement for compatibility
1102
+ return React.createElement('div', {
1103
+ className: 'text-red-500 p-4 border border-red-200 rounded',
1104
+ style: {
1105
+ color: 'red',
1106
+ padding: '1rem',
1107
+ border: '1px solid #fecaca',
1108
+ borderRadius: '0.375rem'
1109
+ }
1110
+ }, `Error: ${streamingData.error}`);
962
1111
  }
963
1112
  // Handle loading state
964
- if (showLoadingIndicator && isStreaming && content.length === 0) {
1113
+ if (showLoadingIndicator && streamingData.isStreaming && streamingData.content.length === 0) {
965
1114
  if (loadingComponent) {
966
- return /*#__PURE__*/ jsx(Fragment, {
967
- children: loadingComponent
968
- });
1115
+ return React.createElement(React.Fragment, {}, loadingComponent);
969
1116
  }
970
- return /*#__PURE__*/ jsxs("div", {
971
- className: "flex items-center space-x-2 text-gray-500",
972
- children: [
973
- /*#__PURE__*/ jsx("div", {
974
- className: "animate-spin h-4 w-4 border-2 border-gray-300 border-t-gray-600 rounded-full"
975
- }),
976
- /*#__PURE__*/ jsx("span", {
977
- children: "Loading..."
978
- })
979
- ]
980
- });
1117
+ // Fallback loading component using React.createElement for compatibility
1118
+ return React.createElement('div', {
1119
+ className: 'flex items-center space-x-2 text-gray-500',
1120
+ style: {
1121
+ display: 'flex',
1122
+ alignItems: 'center',
1123
+ gap: '0.5rem',
1124
+ color: '#6b7280'
1125
+ }
1126
+ }, React.createElement('div', {
1127
+ className: 'animate-spin h-4 w-4 border-2 border-gray-300 border-t-gray-600 rounded-full',
1128
+ style: {
1129
+ animation: 'spin 1s linear infinite',
1130
+ height: '1rem',
1131
+ width: '1rem',
1132
+ border: '2px solid #d1d5db',
1133
+ borderTopColor: '#4b5563',
1134
+ borderRadius: '50%'
1135
+ }
1136
+ }), React.createElement('span', {}, 'Loading...'));
981
1137
  }
982
1138
  // Render the message content
983
- return /*#__PURE__*/ jsx(Message, {
1139
+ return React.createElement(Message, {
984
1140
  ...messageProps,
985
- content: content,
986
- streaming: isStreaming,
987
- isLastMessage: true
1141
+ content: streamingData.content,
1142
+ streaming: streamingData.isStreaming,
1143
+ isLastMessage: true,
1144
+ className
988
1145
  });
989
1146
  }
990
1147
 
1148
+ // Headless hook for math data
1149
+ function useMath(props) {
1150
+ return {
1151
+ content: props.content,
1152
+ inline: props.inline ?? false,
1153
+ displayMode: props.displayMode ?? !props.inline,
1154
+ processedContent: props.content
1155
+ };
1156
+ }
991
1157
  /**
992
1158
  * Generic math renderer component
993
1159
  * Renders plain math content by default - consumers should provide their own math rendering
994
- */ function MathPart({ content, inline = false, className = '', children }) {
1160
+ *
1161
+ * For headless usage, use the useMath hook instead.
1162
+ */ function MathPart({ content, inline = false, className = '', children, displayMode }) {
995
1163
  // If children provided, use that (allows complete customization)
996
1164
  if (children) {
997
- return /*#__PURE__*/ jsx(Fragment, {
998
- children: children
999
- });
1165
+ return React.createElement(React.Fragment, {}, children);
1000
1166
  }
1001
- // Simple fallback - just render plain math content
1002
- const Element = inline ? 'span' : 'div';
1003
- return /*#__PURE__*/ jsx(Element, {
1004
- className: className,
1005
- "data-math-inline": inline,
1006
- children: content
1167
+ const mathData = useMath({
1168
+ content,
1169
+ inline,
1170
+ displayMode
1007
1171
  });
1172
+ // Simple fallback - just render plain math content
1173
+ // Uses React.createElement for maximum compatibility across environments
1174
+ return React.createElement(mathData.inline ? 'span' : 'div', {
1175
+ className,
1176
+ 'data-math-inline': mathData.inline,
1177
+ 'data-math-display': mathData.displayMode
1178
+ }, mathData.processedContent);
1008
1179
  }
1009
1180
 
1010
- export { ContentPartRenderer as AssistantMessageContentPart, CodeBlock, CodeProjectPart as CodeProjectBlock, CodeProjectPart, ContentPartRenderer, Icon, MathPart, MathPart as MathRenderer, Message, Message as MessageContent, Message as MessageRenderer, StreamingMessage, TaskSection, ThinkingSection, Message as V0MessageRenderer, useStreamingMessage };
1181
+ export { ContentPartRenderer as AssistantMessageContentPart, CodeBlock, CodeProjectPart as CodeProjectBlock, CodeProjectPart, ContentPartRenderer, Icon, IconProvider, MathPart, MathPart as MathRenderer, Message, Message as MessageContent, Message as MessageRenderer, StreamingMessage, TaskSection, ThinkingSection, Message as V0MessageRenderer, useCodeBlock, useCodeProject, useContentPart, useIcon, useMath, useMessage, useStreamingMessage, useStreamingMessageData, useTaskSection, useThinkingSection };