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