@v0-sdk/react 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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
@@ -869,8 +1004,8 @@ class StreamStateManager {
869
1004
  this.setComplete(true);
870
1005
  options.onComplete?.(currentContent);
871
1006
  return;
872
- } else if (parsedData.object === 'chat' && parsedData.id) {
873
- // Handle the initial chat data message
1007
+ } else if (parsedData.object && parsedData.object.startsWith('chat')) {
1008
+ // Handle chat metadata messages (chat, chat.title, chat.name, etc.)
874
1009
  options.onChatData?.(parsedData);
875
1010
  continue;
876
1011
  } else if (parsedData.delta) {
@@ -915,9 +1050,33 @@ class StreamStateManager {
915
1050
  return state;
916
1051
  }
917
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
+ }
918
1075
  /**
919
1076
  * Component for rendering streaming message content from v0 API
920
1077
  *
1078
+ * For headless usage, use the useStreamingMessageData hook instead.
1079
+ *
921
1080
  * @example
922
1081
  * ```tsx
923
1082
  * import { v0 } from 'v0-sdk'
@@ -950,73 +1109,98 @@ class StreamStateManager {
950
1109
  * )
951
1110
  * }
952
1111
  * ```
953
- */ function StreamingMessage({ stream, showLoadingIndicator = true, loadingComponent, errorComponent, onChunk, onComplete, onError, onChatData, ...messageProps }) {
954
- 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,
955
1115
  onChunk,
956
1116
  onComplete,
957
1117
  onError,
958
- onChatData
1118
+ onChatData,
1119
+ ...messageProps
959
1120
  });
960
1121
  // Handle error state
961
- if (error) {
1122
+ if (streamingData.error) {
962
1123
  if (errorComponent) {
963
- return /*#__PURE__*/ jsxRuntime.jsx(jsxRuntime.Fragment, {
964
- children: errorComponent(error)
965
- });
1124
+ return React__default.default.createElement(React__default.default.Fragment, {}, errorComponent(streamingData.error));
966
1125
  }
967
- return /*#__PURE__*/ jsxRuntime.jsxs("div", {
968
- className: "text-red-500 p-4 border border-red-200 rounded",
969
- children: [
970
- "Error: ",
971
- error
972
- ]
973
- });
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}`);
974
1136
  }
975
1137
  // Handle loading state
976
- if (showLoadingIndicator && isStreaming && content.length === 0) {
1138
+ if (showLoadingIndicator && streamingData.isStreaming && streamingData.content.length === 0) {
977
1139
  if (loadingComponent) {
978
- return /*#__PURE__*/ jsxRuntime.jsx(jsxRuntime.Fragment, {
979
- children: loadingComponent
980
- });
1140
+ return React__default.default.createElement(React__default.default.Fragment, {}, loadingComponent);
981
1141
  }
982
- return /*#__PURE__*/ jsxRuntime.jsxs("div", {
983
- className: "flex items-center space-x-2 text-gray-500",
984
- children: [
985
- /*#__PURE__*/ jsxRuntime.jsx("div", {
986
- className: "animate-spin h-4 w-4 border-2 border-gray-300 border-t-gray-600 rounded-full"
987
- }),
988
- /*#__PURE__*/ jsxRuntime.jsx("span", {
989
- children: "Loading..."
990
- })
991
- ]
992
- });
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...'));
993
1162
  }
994
1163
  // Render the message content
995
- return /*#__PURE__*/ jsxRuntime.jsx(Message, {
1164
+ return React__default.default.createElement(Message, {
996
1165
  ...messageProps,
997
- content: content,
998
- streaming: isStreaming,
999
- isLastMessage: true
1166
+ content: streamingData.content,
1167
+ streaming: streamingData.isStreaming,
1168
+ isLastMessage: true,
1169
+ className
1000
1170
  });
1001
1171
  }
1002
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
+ }
1003
1182
  /**
1004
1183
  * Generic math renderer component
1005
1184
  * Renders plain math content by default - consumers should provide their own math rendering
1006
- */ 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 }) {
1007
1188
  // If children provided, use that (allows complete customization)
1008
1189
  if (children) {
1009
- return /*#__PURE__*/ jsxRuntime.jsx(jsxRuntime.Fragment, {
1010
- children: children
1011
- });
1190
+ return React__default.default.createElement(React__default.default.Fragment, {}, children);
1012
1191
  }
1013
- // Simple fallback - just render plain math content
1014
- const Element = inline ? 'span' : 'div';
1015
- return /*#__PURE__*/ jsxRuntime.jsx(Element, {
1016
- className: className,
1017
- "data-math-inline": inline,
1018
- children: content
1192
+ const mathData = useMath({
1193
+ content,
1194
+ inline,
1195
+ displayMode
1019
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);
1020
1204
  }
1021
1205
 
1022
1206
  exports.AssistantMessageContentPart = ContentPartRenderer;
@@ -1025,6 +1209,7 @@ exports.CodeProjectBlock = CodeProjectPart;
1025
1209
  exports.CodeProjectPart = CodeProjectPart;
1026
1210
  exports.ContentPartRenderer = ContentPartRenderer;
1027
1211
  exports.Icon = Icon;
1212
+ exports.IconProvider = IconProvider;
1028
1213
  exports.MathPart = MathPart;
1029
1214
  exports.MathRenderer = MathPart;
1030
1215
  exports.Message = Message;
@@ -1034,4 +1219,13 @@ exports.StreamingMessage = StreamingMessage;
1034
1219
  exports.TaskSection = TaskSection;
1035
1220
  exports.ThinkingSection = ThinkingSection;
1036
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;
1037
1228
  exports.useStreamingMessage = useStreamingMessage;
1229
+ exports.useStreamingMessageData = useStreamingMessageData;
1230
+ exports.useTaskSection = useTaskSection;
1231
+ exports.useThinkingSection = useThinkingSection;