@v0-sdk/react 0.2.1 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +198 -376
- package/dist/index.cjs +763 -548
- package/dist/index.d.ts +219 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +755 -550
- package/package.json +11 -11
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
|
-
|
|
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
|
|
37
|
-
children: children
|
|
38
|
-
});
|
|
48
|
+
return React__default.default.createElement(React__default.default.Fragment, {}, children);
|
|
39
49
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
90
|
+
// This uses minimal DOM-specific attributes for maximum compatibility
|
|
91
|
+
return React__default.default.createElement('span', {
|
|
66
92
|
className: props.className,
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
|
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
|
|
95
|
-
children: children
|
|
96
|
-
});
|
|
167
|
+
return React__default.default.createElement(React__default.default.Fragment, {}, children);
|
|
97
168
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
|
204
|
-
|
|
205
|
-
|
|
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
|
|
209
|
-
children: children
|
|
210
|
-
});
|
|
245
|
+
return React__default.default.createElement(React__default.default.Fragment, {}, children);
|
|
211
246
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
|
281
|
+
function getTypeIcon(type, title) {
|
|
266
282
|
// Check title content for specific cases
|
|
267
283
|
if (title?.includes('No issues found')) {
|
|
268
|
-
return
|
|
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
|
|
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
|
|
285
|
-
name: 'search'
|
|
286
|
-
}) : /*#__PURE__*/ jsxRuntime.jsx(Icon, {
|
|
287
|
-
name: "search"
|
|
288
|
-
});
|
|
292
|
+
return 'search';
|
|
289
293
|
case 'task-search-repo-v1':
|
|
290
|
-
return
|
|
291
|
-
name: 'folder'
|
|
292
|
-
}) : /*#__PURE__*/ jsxRuntime.jsx(Icon, {
|
|
293
|
-
name: "folder"
|
|
294
|
-
});
|
|
294
|
+
return 'folder';
|
|
295
295
|
case 'task-diagnostics-v1':
|
|
296
|
-
return
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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
|
|
303
|
-
name: 'wrench'
|
|
304
|
-
}) : /*#__PURE__*/ jsxRuntime.jsx(Icon, {
|
|
305
|
-
name: "wrench"
|
|
306
|
-
});
|
|
304
|
+
return 'wrench';
|
|
307
305
|
}
|
|
308
306
|
}
|
|
309
|
-
function
|
|
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
|
|
313
|
-
|
|
314
|
-
|
|
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
|
|
318
|
-
|
|
319
|
-
|
|
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
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
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
|
|
342
|
-
|
|
343
|
-
|
|
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
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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
|
|
369
|
-
|
|
370
|
-
|
|
359
|
+
return {
|
|
360
|
+
...baseData,
|
|
361
|
+
content: 'Checking for issues...'
|
|
362
|
+
};
|
|
371
363
|
}
|
|
372
364
|
if (part.status === 'complete' && part.issues === 0) {
|
|
373
|
-
return
|
|
374
|
-
|
|
375
|
-
|
|
365
|
+
return {
|
|
366
|
+
...baseData,
|
|
367
|
+
isComplete: true,
|
|
368
|
+
issues: part.issues,
|
|
369
|
+
content: '✅ No issues found'
|
|
370
|
+
};
|
|
376
371
|
}
|
|
377
372
|
}
|
|
378
|
-
return
|
|
379
|
-
|
|
380
|
-
|
|
373
|
+
return {
|
|
374
|
+
...baseData,
|
|
375
|
+
content: JSON.stringify(part)
|
|
376
|
+
};
|
|
381
377
|
}
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
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
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
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
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
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
|
-
|
|
470
|
-
|
|
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
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
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
|
-
|
|
492
|
-
|
|
493
|
-
|
|
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
|
-
|
|
507
|
-
|
|
508
|
-
|
|
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
|
-
|
|
522
|
-
|
|
523
|
-
|
|
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
|
-
|
|
537
|
-
|
|
538
|
-
|
|
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
|
-
|
|
552
|
-
|
|
553
|
-
|
|
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
|
|
566
|
-
|
|
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
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
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
|
-
//
|
|
584
|
-
function
|
|
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
|
|
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
|
|
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
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
698
|
+
}).filter(Boolean);
|
|
699
|
+
return {
|
|
700
|
+
elements,
|
|
701
|
+
messageId,
|
|
702
|
+
role,
|
|
703
|
+
streaming,
|
|
704
|
+
isLastMessage
|
|
705
|
+
};
|
|
629
706
|
}
|
|
630
|
-
//
|
|
631
|
-
function
|
|
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
|
|
637
|
-
const key =
|
|
638
|
-
return
|
|
639
|
-
}).filter(Boolean)
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
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
|
-
//
|
|
646
|
-
function
|
|
724
|
+
// Process individual elements into headless data structure
|
|
725
|
+
function processElement(element, key, components) {
|
|
647
726
|
if (typeof element === 'string') {
|
|
648
|
-
return
|
|
649
|
-
|
|
650
|
-
|
|
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
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
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
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
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
|
|
679
|
-
|
|
680
|
-
|
|
766
|
+
return {
|
|
767
|
+
type: 'text',
|
|
768
|
+
key,
|
|
769
|
+
data: children[0] || ''
|
|
770
|
+
};
|
|
681
771
|
}
|
|
682
|
-
//
|
|
683
|
-
const
|
|
772
|
+
// Process children
|
|
773
|
+
const processedChildren = children.map((child, childIndex)=>{
|
|
684
774
|
const childKey = `${key}-child-${childIndex}`;
|
|
685
|
-
return
|
|
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
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
873
|
-
// Handle
|
|
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
|
|
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
|
|
964
|
-
children: errorComponent(error)
|
|
965
|
-
});
|
|
1145
|
+
return React__default.default.createElement(React__default.default.Fragment, {}, errorComponent(streamingData.error));
|
|
966
1146
|
}
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
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
|
|
979
|
-
children: loadingComponent
|
|
980
|
-
});
|
|
1161
|
+
return React__default.default.createElement(React__default.default.Fragment, {}, loadingComponent);
|
|
981
1162
|
}
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
1010
|
-
children: children
|
|
1011
|
-
});
|
|
1211
|
+
return React__default.default.createElement(React__default.default.Fragment, {}, children);
|
|
1012
1212
|
}
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
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;
|