@v0-sdk/react 0.2.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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,297 @@ 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';
297
+ case 'task-generate-design-inspiration-v1':
298
+ return 'wrench';
299
+ case 'task-read-file-v1':
300
+ return 'folder';
301
+ case 'task-coding-v1':
302
+ return 'wrench';
301
303
  default:
302
- return iconRenderer ? /*#__PURE__*/ React__default.default.createElement(iconRenderer, {
303
- name: 'wrench'
304
- }) : /*#__PURE__*/ jsxRuntime.jsx(Icon, {
305
- name: "wrench"
306
- });
304
+ return 'wrench';
307
305
  }
308
306
  }
309
- function renderTaskPart(part, index, iconRenderer) {
307
+ function processTaskPart(part, index) {
308
+ const baseData = {
309
+ type: part.type,
310
+ status: part.status,
311
+ content: null
312
+ };
310
313
  if (part.type === 'search-web') {
311
314
  if (part.status === 'searching') {
312
- return /*#__PURE__*/ jsxRuntime.jsx("div", {
313
- children: `Searching "${part.query}"`
314
- }, index);
315
+ return {
316
+ ...baseData,
317
+ isSearching: true,
318
+ query: part.query,
319
+ content: `Searching "${part.query}"`
320
+ };
315
321
  }
316
322
  if (part.status === 'analyzing') {
317
- return /*#__PURE__*/ jsxRuntime.jsx("div", {
318
- children: `Analyzing ${part.count} results...`
319
- }, index);
323
+ return {
324
+ ...baseData,
325
+ isAnalyzing: true,
326
+ count: part.count,
327
+ content: `Analyzing ${part.count} results...`
328
+ };
320
329
  }
321
330
  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);
331
+ return {
332
+ ...baseData,
333
+ isComplete: true,
334
+ answer: part.answer,
335
+ sources: part.sources,
336
+ content: part.answer
337
+ };
337
338
  }
338
339
  }
339
340
  if (part.type === 'search-repo') {
340
341
  if (part.status === 'searching') {
341
- return /*#__PURE__*/ jsxRuntime.jsx("div", {
342
- children: `Searching "${part.query}"`
343
- }, index);
342
+ return {
343
+ ...baseData,
344
+ isSearching: true,
345
+ query: part.query,
346
+ content: `Searching "${part.query}"`
347
+ };
344
348
  }
345
349
  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);
350
+ return {
351
+ ...baseData,
352
+ files: part.files,
353
+ content: 'Reading files'
354
+ };
364
355
  }
365
356
  }
366
357
  if (part.type === 'diagnostics') {
367
358
  if (part.status === 'checking') {
368
- return /*#__PURE__*/ jsxRuntime.jsx("div", {
369
- children: "Checking for issues..."
370
- }, index);
359
+ return {
360
+ ...baseData,
361
+ content: 'Checking for issues...'
362
+ };
371
363
  }
372
364
  if (part.status === 'complete' && part.issues === 0) {
373
- return /*#__PURE__*/ jsxRuntime.jsx("div", {
374
- children: "✅ No issues found"
375
- }, index);
365
+ return {
366
+ ...baseData,
367
+ isComplete: true,
368
+ issues: part.issues,
369
+ content: '✅ No issues found'
370
+ };
376
371
  }
377
372
  }
378
- return /*#__PURE__*/ jsxRuntime.jsx("div", {
379
- children: JSON.stringify(part)
380
- }, index);
373
+ return {
374
+ ...baseData,
375
+ content: JSON.stringify(part)
376
+ };
381
377
  }
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 }) {
378
+ function renderTaskPartContent(partData, index, iconRenderer) {
379
+ if (partData.type === 'search-web' && partData.isComplete && partData.sources) {
380
+ return React__default.default.createElement('div', {
381
+ key: index
382
+ }, 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', {
383
+ key: sourceIndex,
384
+ href: source.url,
385
+ target: '_blank',
386
+ rel: 'noopener noreferrer'
387
+ }, source.title))) : null);
388
+ }
389
+ if (partData.type === 'search-repo' && partData.files) {
390
+ return React__default.default.createElement('div', {
391
+ key: index
392
+ }, React__default.default.createElement('span', {}, partData.content), partData.files.map((file, fileIndex)=>React__default.default.createElement('span', {
393
+ key: fileIndex
394
+ }, iconRenderer ? React__default.default.createElement(iconRenderer, {
395
+ name: 'file-text'
396
+ }) : React__default.default.createElement(Icon, {
397
+ name: 'file-text'
398
+ }), ' ', file)));
399
+ }
400
+ return React__default.default.createElement('div', {
401
+ key: index
402
+ }, partData.content);
403
+ }
404
+ // Headless hook for task section
405
+ function useTaskSection({ title, type, parts = [], collapsed: initialCollapsed = true, onCollapse }) {
386
406
  const [internalCollapsed, setInternalCollapsed] = React.useState(initialCollapsed);
387
407
  const collapsed = onCollapse ? initialCollapsed : internalCollapsed;
388
408
  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
409
  // Count meaningful parts (parts that would render something)
396
410
  const meaningfulParts = parts.filter((part)=>{
397
411
  // Check if the part would render meaningful content
@@ -405,174 +419,231 @@ function renderTaskPart(part, index, iconRenderer) {
405
419
  if (part.type === 'finished-web-search' && part.answer) return true;
406
420
  if (part.type === 'diagnostics-passed') return true;
407
421
  if (part.type === 'fetching-diagnostics') return true;
408
- // Add more meaningful part types as needed
409
422
  return false;
410
423
  });
424
+ const processedParts = parts.map(processTaskPart);
425
+ return {
426
+ data: {
427
+ title: title || 'Task',
428
+ type,
429
+ parts,
430
+ collapsed,
431
+ meaningfulParts,
432
+ shouldShowCollapsible: meaningfulParts.length > 1,
433
+ iconName: getTypeIcon(type, title)
434
+ },
435
+ collapsed,
436
+ handleCollapse,
437
+ processedParts
438
+ };
439
+ }
440
+ /**
441
+ * Generic task section component
442
+ * Renders a collapsible task section with basic structure - consumers provide styling
443
+ *
444
+ * For headless usage, use the useTaskSection hook instead.
445
+ */ function TaskSection({ title, type, parts = [], collapsed: initialCollapsed = true, onCollapse, className, children, iconRenderer, taskIcon, chevronRightIcon, chevronDownIcon }) {
446
+ const { data, collapsed, handleCollapse, processedParts } = useTaskSection({
447
+ title,
448
+ type,
449
+ parts,
450
+ collapsed: initialCollapsed,
451
+ onCollapse
452
+ });
453
+ // If children provided, use that (allows complete customization)
454
+ if (children) {
455
+ return React__default.default.createElement(React__default.default.Fragment, {}, children);
456
+ }
411
457
  // 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
- });
458
+ if (!data.shouldShowCollapsible && data.meaningfulParts.length === 1) {
459
+ const partData = processTaskPart(data.meaningfulParts[0]);
460
+ return React__default.default.createElement('div', {
461
+ className,
462
+ 'data-component': 'task-section-inline'
463
+ }, React__default.default.createElement('div', {
464
+ 'data-part': true
465
+ }, renderTaskPartContent(partData, 0, iconRenderer)));
421
466
  }
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
- });
467
+ // Uses React.createElement for maximum compatibility across environments
468
+ return React__default.default.createElement('div', {
469
+ className,
470
+ 'data-component': 'task-section'
471
+ }, React__default.default.createElement('button', {
472
+ onClick: handleCollapse,
473
+ 'data-expanded': !collapsed,
474
+ 'data-button': true
475
+ }, React__default.default.createElement('div', {
476
+ 'data-icon-container': true
477
+ }, React__default.default.createElement('div', {
478
+ 'data-task-icon': true
479
+ }, taskIcon || (iconRenderer ? React__default.default.createElement(iconRenderer, {
480
+ name: data.iconName
481
+ }) : React__default.default.createElement(Icon, {
482
+ name: data.iconName
483
+ }))), collapsed ? chevronRightIcon || (iconRenderer ? React__default.default.createElement(iconRenderer, {
484
+ name: 'chevron-right'
485
+ }) : React__default.default.createElement(Icon, {
486
+ name: 'chevron-right'
487
+ })) : chevronDownIcon || (iconRenderer ? React__default.default.createElement(iconRenderer, {
488
+ name: 'chevron-down'
489
+ }) : React__default.default.createElement(Icon, {
490
+ name: 'chevron-down'
491
+ }))), React__default.default.createElement('span', {
492
+ 'data-title': true
493
+ }, data.title)), !collapsed ? React__default.default.createElement('div', {
494
+ 'data-content': true
495
+ }, React__default.default.createElement('div', {
496
+ 'data-parts-container': true
497
+ }, processedParts.map((partData, index)=>React__default.default.createElement('div', {
498
+ key: index,
499
+ 'data-part': true
500
+ }, renderTaskPartContent(partData, index, iconRenderer))))) : null);
467
501
  }
468
502
 
469
- function ContentPartRenderer({ part, iconRenderer, thinkingSectionRenderer, taskSectionRenderer, brainIcon, chevronRightIcon, chevronDownIcon, searchIcon, folderIcon, settingsIcon, wrenchIcon }) {
470
- if (!part) return null;
503
+ // Headless hook for content part
504
+ function useContentPart(part) {
505
+ if (!part) {
506
+ return {
507
+ type: '',
508
+ parts: [],
509
+ metadata: {},
510
+ componentType: null
511
+ };
512
+ }
471
513
  const { type, parts = [], ...metadata } = part;
514
+ let componentType = 'unknown';
515
+ let title;
516
+ let iconName;
517
+ let thinkingData;
472
518
  switch(type){
473
519
  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
- }
520
+ componentType = 'thinking';
521
+ title = 'Thought';
522
+ const thinkingPart = parts.find((p)=>p.type === 'thinking-end');
523
+ thinkingData = {
524
+ duration: thinkingPart?.duration,
525
+ thought: thinkingPart?.thought
526
+ };
527
+ break;
489
528
  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
- }
529
+ componentType = 'task';
530
+ title = metadata.taskNameComplete || metadata.taskNameActive;
531
+ iconName = 'search';
532
+ break;
504
533
  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
- }
534
+ componentType = 'task';
535
+ title = metadata.taskNameComplete || metadata.taskNameActive;
536
+ iconName = 'folder';
537
+ break;
519
538
  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
- }
539
+ componentType = 'task';
540
+ title = metadata.taskNameComplete || metadata.taskNameActive;
541
+ iconName = 'settings';
542
+ break;
534
543
  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
- }
544
+ componentType = 'task';
545
+ title = metadata.taskNameComplete || metadata.taskNameActive || 'Reading file';
546
+ iconName = 'folder';
547
+ break;
549
548
  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
- }
549
+ componentType = 'task';
550
+ title = metadata.taskNameComplete || metadata.taskNameActive || 'Coding';
551
+ iconName = 'wrench';
552
+ break;
564
553
  case 'task-start-v1':
565
- // Usually just indicates task start - can be hidden or show as status
566
- return null;
554
+ componentType = null; // Usually just indicates task start - can be hidden
555
+ break;
556
+ case 'task-generate-design-inspiration-v1':
557
+ componentType = 'task';
558
+ title = metadata.taskNameComplete || metadata.taskNameActive || 'Generating Design Inspiration';
559
+ iconName = 'wrench';
560
+ break;
561
+ // Handle any other task-*-v1 patterns that might be added in the future
567
562
  default:
568
- return /*#__PURE__*/ jsxRuntime.jsxs("div", {
569
- "data-unknown-part-type": type,
570
- children: [
571
- "Unknown part type: ",
572
- type
573
- ]
574
- });
563
+ // Check if it's a task type we haven't explicitly handled yet
564
+ if (type && typeof type === 'string' && type.startsWith('task-') && type.endsWith('-v1')) {
565
+ componentType = 'task';
566
+ // Generate a readable title from the task type
567
+ const taskName = type.replace('task-', '').replace('-v1', '').split('-').map((word)=>word.charAt(0).toUpperCase() + word.slice(1)).join(' ');
568
+ title = metadata.taskNameComplete || metadata.taskNameActive || taskName;
569
+ iconName = 'wrench'; // Default icon for unknown task types
570
+ } else {
571
+ componentType = 'unknown';
572
+ }
573
+ break;
575
574
  }
575
+ return {
576
+ type,
577
+ parts,
578
+ metadata,
579
+ componentType,
580
+ title,
581
+ iconName,
582
+ thinkingData
583
+ };
584
+ }
585
+ /**
586
+ * Content part renderer that handles different types of v0 API content parts
587
+ *
588
+ * For headless usage, use the useContentPart hook instead.
589
+ */ function ContentPartRenderer({ part, iconRenderer, thinkingSectionRenderer, taskSectionRenderer, brainIcon, chevronRightIcon, chevronDownIcon, searchIcon, folderIcon, settingsIcon, wrenchIcon }) {
590
+ const contentData = useContentPart(part);
591
+ if (!contentData.componentType) {
592
+ return null;
593
+ }
594
+ if (contentData.componentType === 'thinking') {
595
+ const ThinkingComponent = thinkingSectionRenderer || ThinkingSection;
596
+ const [collapsed, setCollapsed] = React.useState(true);
597
+ return React__default.default.createElement(ThinkingComponent, {
598
+ title: contentData.title,
599
+ duration: contentData.thinkingData?.duration,
600
+ thought: contentData.thinkingData?.thought,
601
+ collapsed,
602
+ onCollapse: ()=>setCollapsed(!collapsed),
603
+ brainIcon,
604
+ chevronRightIcon,
605
+ chevronDownIcon
606
+ });
607
+ }
608
+ if (contentData.componentType === 'task') {
609
+ const TaskComponent = taskSectionRenderer || TaskSection;
610
+ const [collapsed, setCollapsed] = React.useState(true);
611
+ // Map icon names to icon components
612
+ let taskIcon;
613
+ switch(contentData.iconName){
614
+ case 'search':
615
+ taskIcon = searchIcon;
616
+ break;
617
+ case 'folder':
618
+ taskIcon = folderIcon;
619
+ break;
620
+ case 'settings':
621
+ taskIcon = settingsIcon;
622
+ break;
623
+ case 'wrench':
624
+ taskIcon = wrenchIcon;
625
+ break;
626
+ default:
627
+ taskIcon = undefined;
628
+ break;
629
+ }
630
+ return React__default.default.createElement(TaskComponent, {
631
+ title: contentData.title,
632
+ type: contentData.type,
633
+ parts: contentData.parts,
634
+ collapsed,
635
+ onCollapse: ()=>setCollapsed(!collapsed),
636
+ taskIcon,
637
+ chevronRightIcon,
638
+ chevronDownIcon
639
+ });
640
+ }
641
+ if (contentData.componentType === 'unknown') {
642
+ return React__default.default.createElement('div', {
643
+ 'data-unknown-part-type': contentData.type
644
+ }, `Unknown part type: ${contentData.type}`);
645
+ }
646
+ return null;
576
647
  }
577
648
 
578
649
  // Utility function to merge class names
@@ -580,11 +651,17 @@ function cn(...classes) {
580
651
  return classes.filter(Boolean).join(' ');
581
652
  }
582
653
 
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 }) {
654
+ // Headless hook for processing message content
655
+ function useMessage({ content, messageId = 'unknown', role = 'assistant', streaming = false, isLastMessage = false, components, renderers }) {
585
656
  if (!Array.isArray(content)) {
586
657
  console.warn('MessageContent: content must be an array (MessageBinaryFormat)');
587
- return null;
658
+ return {
659
+ elements: [],
660
+ messageId,
661
+ role,
662
+ streaming,
663
+ isLastMessage
664
+ };
588
665
  }
589
666
  // Merge components and renderers (backward compatibility)
590
667
  const mergedComponents = {
@@ -608,10 +685,7 @@ function MessageImpl({ content, messageId = 'unknown', role: _role = 'assistant'
608
685
  const key = `${messageId}-${index}`;
609
686
  // Markdown data (type 0) - this is the main content
610
687
  if (type === 0) {
611
- return /*#__PURE__*/ jsxRuntime.jsx(Elements, {
612
- data: data,
613
- components: mergedComponents
614
- }, key);
688
+ return processElements(data, key, mergedComponents);
615
689
  }
616
690
  // Metadata (type 1) - extract context but don't render
617
691
  if (type === 1) {
@@ -621,33 +695,40 @@ function MessageImpl({ content, messageId = 'unknown', role: _role = 'assistant'
621
695
  }
622
696
  // Other types - v0 doesn't handle these in the main renderer
623
697
  return null;
624
- });
625
- return /*#__PURE__*/ jsxRuntime.jsx("div", {
626
- className: className,
627
- children: elements
628
- });
698
+ }).filter(Boolean);
699
+ return {
700
+ elements,
701
+ messageId,
702
+ role,
703
+ streaming,
704
+ isLastMessage
705
+ };
629
706
  }
630
- // This component handles the markdown data array (equivalent to v0's Elements component)
631
- function Elements({ data, components }) {
707
+ // Process elements into headless data structure
708
+ function processElements(data, keyPrefix, components) {
632
709
  // Handle case where data might not be an array due to streaming/patching
633
710
  if (!Array.isArray(data)) {
634
711
  return null;
635
712
  }
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
- });
713
+ const children = data.map((item, index)=>{
714
+ const key = `${keyPrefix}-${index}`;
715
+ return processElement(item, key, components);
716
+ }).filter(Boolean);
717
+ return {
718
+ type: 'component',
719
+ key: keyPrefix,
720
+ data: 'elements',
721
+ children
722
+ };
644
723
  }
645
- // Render individual elements (equivalent to v0's element rendering logic)
646
- function renderElement(element, key, components) {
724
+ // Process individual elements into headless data structure
725
+ function processElement(element, key, components) {
647
726
  if (typeof element === 'string') {
648
- return /*#__PURE__*/ jsxRuntime.jsx("span", {
649
- children: element
650
- }, key);
727
+ return {
728
+ type: 'text',
729
+ key,
730
+ data: element
731
+ };
651
732
  }
652
733
  if (!Array.isArray(element)) {
653
734
  return null;
@@ -658,69 +739,144 @@ function renderElement(element, key, components) {
658
739
  }
659
740
  // Handle special v0 Platform API elements
660
741
  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);
742
+ return {
743
+ type: 'content-part',
744
+ key,
745
+ data: {
746
+ part: props.part,
747
+ iconRenderer: components?.Icon,
748
+ thinkingSectionRenderer: components?.ThinkingSection,
749
+ taskSectionRenderer: components?.TaskSection
750
+ }
751
+ };
667
752
  }
668
753
  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);
754
+ return {
755
+ type: 'code-project',
756
+ key,
757
+ data: {
758
+ language: props.lang,
759
+ code: children[0],
760
+ iconRenderer: components?.Icon,
761
+ customRenderer: components?.CodeProjectPart
762
+ }
763
+ };
676
764
  }
677
765
  if (tagName === 'text') {
678
- return /*#__PURE__*/ jsxRuntime.jsx("span", {
679
- children: children[0] || ''
680
- }, key);
766
+ return {
767
+ type: 'text',
768
+ key,
769
+ data: children[0] || ''
770
+ };
681
771
  }
682
- // Render children
683
- const renderedChildren = children.map((child, childIndex)=>{
772
+ // Process children
773
+ const processedChildren = children.map((child, childIndex)=>{
684
774
  const childKey = `${key}-child-${childIndex}`;
685
- return renderElement(child, childKey, components);
775
+ return processElement(child, childKey, components);
686
776
  }).filter(Boolean);
687
777
  // Handle standard HTML elements
688
- const className = props?.className;
689
778
  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';
779
+ return {
780
+ type: 'html',
781
+ key,
782
+ data: {
783
+ tagName,
784
+ props,
785
+ componentOrConfig
786
+ },
787
+ children: processedChildren
788
+ };
789
+ }
790
+ // Default JSX renderer for backward compatibility
791
+ function MessageRenderer({ messageData, className }) {
792
+ const renderElement = (element)=>{
793
+ switch(element.type){
794
+ case 'text':
795
+ return React__default.default.createElement('span', {
796
+ key: element.key
797
+ }, element.data);
798
+ case 'content-part':
799
+ return React__default.default.createElement(ContentPartRenderer, {
800
+ key: element.key,
801
+ part: element.data.part,
802
+ iconRenderer: element.data.iconRenderer,
803
+ thinkingSectionRenderer: element.data.thinkingSectionRenderer,
804
+ taskSectionRenderer: element.data.taskSectionRenderer
805
+ });
806
+ case 'code-project':
807
+ const CustomCodeProjectPart = element.data.customRenderer;
808
+ const CodeProjectComponent = CustomCodeProjectPart || CodeProjectPart;
809
+ return React__default.default.createElement(CodeProjectComponent, {
810
+ key: element.key,
811
+ language: element.data.language,
812
+ code: element.data.code,
813
+ iconRenderer: element.data.iconRenderer
814
+ });
815
+ case 'html':
816
+ const { tagName, props, componentOrConfig } = element.data;
817
+ const renderedChildren = element.children?.map(renderElement);
818
+ if (typeof componentOrConfig === 'function') {
819
+ const Component = componentOrConfig;
820
+ return React__default.default.createElement(Component, {
821
+ key: element.key,
822
+ ...props,
823
+ className: props?.className
824
+ }, renderedChildren);
825
+ } else if (componentOrConfig && typeof componentOrConfig === 'object') {
826
+ const mergedClassName = cn(props?.className, componentOrConfig.className);
827
+ return React__default.default.createElement(tagName, {
828
+ key: element.key,
829
+ ...props,
830
+ className: mergedClassName
831
+ }, renderedChildren);
832
+ } else {
833
+ // Default HTML element rendering
834
+ const elementProps = {
835
+ key: element.key,
836
+ ...props
837
+ };
838
+ if (props?.className) {
839
+ elementProps.className = props.className;
840
+ }
841
+ // Special handling for links
842
+ if (tagName === 'a') {
843
+ elementProps.target = '_blank';
844
+ elementProps.rel = 'noopener noreferrer';
845
+ }
846
+ return React__default.default.createElement(tagName, elementProps, renderedChildren);
847
+ }
848
+ case 'component':
849
+ return React__default.default.createElement(React__default.default.Fragment, {
850
+ key: element.key
851
+ }, element.children?.map(renderElement));
852
+ default:
853
+ return null;
717
854
  }
718
- return /*#__PURE__*/ React__default.default.createElement(tagName, elementProps, renderedChildren);
719
- }
855
+ };
856
+ return React__default.default.createElement('div', {
857
+ className
858
+ }, messageData.elements.map(renderElement));
859
+ }
860
+ // Simplified renderer that matches v0's exact approach (backward compatibility)
861
+ function MessageImpl({ content, messageId = 'unknown', role = 'assistant', streaming = false, isLastMessage = false, className, components, renderers }) {
862
+ const messageData = useMessage({
863
+ content,
864
+ messageId,
865
+ role,
866
+ streaming,
867
+ isLastMessage,
868
+ components,
869
+ renderers
870
+ });
871
+ return React__default.default.createElement(MessageRenderer, {
872
+ messageData,
873
+ className
874
+ });
720
875
  }
721
876
  /**
722
877
  * Main component for rendering v0 Platform API message content
723
- */ const Message = /*#__PURE__*/ React__default.default.memo(MessageImpl);
878
+ * This is a backward-compatible JSX renderer. For headless usage, use the useMessage hook.
879
+ */ const Message = React__default.default.memo(MessageImpl);
724
880
 
725
881
  const jdf = jsondiffpatch__namespace.create({});
726
882
  // Exact copy of the patch function from v0/chat/lib/diffpatch.ts
@@ -869,8 +1025,8 @@ class StreamStateManager {
869
1025
  this.setComplete(true);
870
1026
  options.onComplete?.(currentContent);
871
1027
  return;
872
- } else if (parsedData.object === 'chat' && parsedData.id) {
873
- // Handle the initial chat data message
1028
+ } else if (parsedData.object && parsedData.object.startsWith('chat')) {
1029
+ // Handle chat metadata messages (chat, chat.title, chat.name, etc.)
874
1030
  options.onChatData?.(parsedData);
875
1031
  continue;
876
1032
  } else if (parsedData.delta) {
@@ -915,9 +1071,33 @@ class StreamStateManager {
915
1071
  return state;
916
1072
  }
917
1073
 
1074
+ // Headless hook for streaming message
1075
+ function useStreamingMessageData({ stream, messageId = 'unknown', role = 'assistant', components, renderers, onChunk, onComplete, onError, onChatData }) {
1076
+ const streamingState = useStreamingMessage(stream, {
1077
+ onChunk,
1078
+ onComplete,
1079
+ onError,
1080
+ onChatData
1081
+ });
1082
+ const messageData = streamingState.content.length > 0 ? useMessage({
1083
+ content: streamingState.content,
1084
+ messageId,
1085
+ role,
1086
+ streaming: streamingState.isStreaming,
1087
+ isLastMessage: true,
1088
+ components,
1089
+ renderers
1090
+ }) : null;
1091
+ return {
1092
+ ...streamingState,
1093
+ messageData
1094
+ };
1095
+ }
918
1096
  /**
919
1097
  * Component for rendering streaming message content from v0 API
920
1098
  *
1099
+ * For headless usage, use the useStreamingMessageData hook instead.
1100
+ *
921
1101
  * @example
922
1102
  * ```tsx
923
1103
  * import { v0 } from 'v0-sdk'
@@ -950,73 +1130,98 @@ class StreamStateManager {
950
1130
  * )
951
1131
  * }
952
1132
  * ```
953
- */ function StreamingMessage({ stream, showLoadingIndicator = true, loadingComponent, errorComponent, onChunk, onComplete, onError, onChatData, ...messageProps }) {
954
- const { content, isStreaming, error, isComplete } = useStreamingMessage(stream, {
1133
+ */ function StreamingMessage({ stream, showLoadingIndicator = true, loadingComponent, errorComponent, onChunk, onComplete, onError, onChatData, className, ...messageProps }) {
1134
+ const streamingData = useStreamingMessageData({
1135
+ stream,
955
1136
  onChunk,
956
1137
  onComplete,
957
1138
  onError,
958
- onChatData
1139
+ onChatData,
1140
+ ...messageProps
959
1141
  });
960
1142
  // Handle error state
961
- if (error) {
1143
+ if (streamingData.error) {
962
1144
  if (errorComponent) {
963
- return /*#__PURE__*/ jsxRuntime.jsx(jsxRuntime.Fragment, {
964
- children: errorComponent(error)
965
- });
1145
+ return React__default.default.createElement(React__default.default.Fragment, {}, errorComponent(streamingData.error));
966
1146
  }
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
- });
1147
+ // Fallback error component using React.createElement for compatibility
1148
+ return React__default.default.createElement('div', {
1149
+ className: 'text-red-500 p-4 border border-red-200 rounded',
1150
+ style: {
1151
+ color: 'red',
1152
+ padding: '1rem',
1153
+ border: '1px solid #fecaca',
1154
+ borderRadius: '0.375rem'
1155
+ }
1156
+ }, `Error: ${streamingData.error}`);
974
1157
  }
975
1158
  // Handle loading state
976
- if (showLoadingIndicator && isStreaming && content.length === 0) {
1159
+ if (showLoadingIndicator && streamingData.isStreaming && streamingData.content.length === 0) {
977
1160
  if (loadingComponent) {
978
- return /*#__PURE__*/ jsxRuntime.jsx(jsxRuntime.Fragment, {
979
- children: loadingComponent
980
- });
1161
+ return React__default.default.createElement(React__default.default.Fragment, {}, loadingComponent);
981
1162
  }
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
- });
1163
+ // Fallback loading component using React.createElement for compatibility
1164
+ return React__default.default.createElement('div', {
1165
+ className: 'flex items-center space-x-2 text-gray-500',
1166
+ style: {
1167
+ display: 'flex',
1168
+ alignItems: 'center',
1169
+ gap: '0.5rem',
1170
+ color: '#6b7280'
1171
+ }
1172
+ }, React__default.default.createElement('div', {
1173
+ className: 'animate-spin h-4 w-4 border-2 border-gray-300 border-t-gray-600 rounded-full',
1174
+ style: {
1175
+ animation: 'spin 1s linear infinite',
1176
+ height: '1rem',
1177
+ width: '1rem',
1178
+ border: '2px solid #d1d5db',
1179
+ borderTopColor: '#4b5563',
1180
+ borderRadius: '50%'
1181
+ }
1182
+ }), React__default.default.createElement('span', {}, 'Loading...'));
993
1183
  }
994
1184
  // Render the message content
995
- return /*#__PURE__*/ jsxRuntime.jsx(Message, {
1185
+ return React__default.default.createElement(Message, {
996
1186
  ...messageProps,
997
- content: content,
998
- streaming: isStreaming,
999
- isLastMessage: true
1187
+ content: streamingData.content,
1188
+ streaming: streamingData.isStreaming,
1189
+ isLastMessage: true,
1190
+ className
1000
1191
  });
1001
1192
  }
1002
1193
 
1194
+ // Headless hook for math data
1195
+ function useMath(props) {
1196
+ return {
1197
+ content: props.content,
1198
+ inline: props.inline ?? false,
1199
+ displayMode: props.displayMode ?? !props.inline,
1200
+ processedContent: props.content
1201
+ };
1202
+ }
1003
1203
  /**
1004
1204
  * Generic math renderer component
1005
1205
  * Renders plain math content by default - consumers should provide their own math rendering
1006
- */ function MathPart({ content, inline = false, className = '', children }) {
1206
+ *
1207
+ * For headless usage, use the useMath hook instead.
1208
+ */ function MathPart({ content, inline = false, className = '', children, displayMode }) {
1007
1209
  // If children provided, use that (allows complete customization)
1008
1210
  if (children) {
1009
- return /*#__PURE__*/ jsxRuntime.jsx(jsxRuntime.Fragment, {
1010
- children: children
1011
- });
1211
+ return React__default.default.createElement(React__default.default.Fragment, {}, children);
1012
1212
  }
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
1213
+ const mathData = useMath({
1214
+ content,
1215
+ inline,
1216
+ displayMode
1019
1217
  });
1218
+ // Simple fallback - just render plain math content
1219
+ // Uses React.createElement for maximum compatibility across environments
1220
+ return React__default.default.createElement(mathData.inline ? 'span' : 'div', {
1221
+ className,
1222
+ 'data-math-inline': mathData.inline,
1223
+ 'data-math-display': mathData.displayMode
1224
+ }, mathData.processedContent);
1020
1225
  }
1021
1226
 
1022
1227
  exports.AssistantMessageContentPart = ContentPartRenderer;
@@ -1025,6 +1230,7 @@ exports.CodeProjectBlock = CodeProjectPart;
1025
1230
  exports.CodeProjectPart = CodeProjectPart;
1026
1231
  exports.ContentPartRenderer = ContentPartRenderer;
1027
1232
  exports.Icon = Icon;
1233
+ exports.IconProvider = IconProvider;
1028
1234
  exports.MathPart = MathPart;
1029
1235
  exports.MathRenderer = MathPart;
1030
1236
  exports.Message = Message;
@@ -1034,4 +1240,13 @@ exports.StreamingMessage = StreamingMessage;
1034
1240
  exports.TaskSection = TaskSection;
1035
1241
  exports.ThinkingSection = ThinkingSection;
1036
1242
  exports.V0MessageRenderer = Message;
1243
+ exports.useCodeBlock = useCodeBlock;
1244
+ exports.useCodeProject = useCodeProject;
1245
+ exports.useContentPart = useContentPart;
1246
+ exports.useIcon = useIcon;
1247
+ exports.useMath = useMath;
1248
+ exports.useMessage = useMessage;
1037
1249
  exports.useStreamingMessage = useStreamingMessage;
1250
+ exports.useStreamingMessageData = useStreamingMessageData;
1251
+ exports.useTaskSection = useTaskSection;
1252
+ exports.useThinkingSection = useThinkingSection;