@v0-sdk/react 0.3.0 → 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 +24 -0
- package/dist/index.cjs +22 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -1
- package/package.json +9 -7
package/README.md
CHANGED
|
@@ -161,6 +161,30 @@ All components are optional JSX renderers that work with DOM environments. For h
|
|
|
161
161
|
- `CodeProjectPart` - Code project file browser
|
|
162
162
|
- `ContentPartRenderer` - Handles different content part types
|
|
163
163
|
|
|
164
|
+
## Supported Task Types
|
|
165
|
+
|
|
166
|
+
The package automatically handles all v0 Platform API task types:
|
|
167
|
+
|
|
168
|
+
### Explicitly Supported Tasks
|
|
169
|
+
|
|
170
|
+
- `task-thinking-v1` - AI reasoning and thought processes
|
|
171
|
+
- `task-search-web-v1` - Web search operations with results
|
|
172
|
+
- `task-search-repo-v1` - Repository/codebase search functionality
|
|
173
|
+
- `task-diagnostics-v1` - Code analysis and issue detection
|
|
174
|
+
- `task-read-file-v1` - File reading operations
|
|
175
|
+
- `task-coding-v1` - Code generation and editing tasks
|
|
176
|
+
- `task-generate-design-inspiration-v1` - Design inspiration generation
|
|
177
|
+
- `task-start-v1` - Task initialization (usually hidden)
|
|
178
|
+
|
|
179
|
+
### Future-Proof Support
|
|
180
|
+
|
|
181
|
+
Any new task type following the `task-*-v1` pattern will be automatically supported with:
|
|
182
|
+
|
|
183
|
+
- Auto-generated readable titles
|
|
184
|
+
- Appropriate icon selection
|
|
185
|
+
- Proper task section rendering
|
|
186
|
+
- Graceful fallback handling
|
|
187
|
+
|
|
164
188
|
## Customization
|
|
165
189
|
|
|
166
190
|
### Custom Components
|
package/dist/index.cjs
CHANGED
|
@@ -294,6 +294,12 @@ function getTypeIcon(type, title) {
|
|
|
294
294
|
return 'folder';
|
|
295
295
|
case 'task-diagnostics-v1':
|
|
296
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';
|
|
297
303
|
default:
|
|
298
304
|
return 'wrench';
|
|
299
305
|
}
|
|
@@ -547,8 +553,23 @@ function useContentPart(part) {
|
|
|
547
553
|
case 'task-start-v1':
|
|
548
554
|
componentType = null; // Usually just indicates task start - can be hidden
|
|
549
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
|
|
550
562
|
default:
|
|
551
|
-
|
|
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
|
+
}
|
|
552
573
|
break;
|
|
553
574
|
}
|
|
554
575
|
return {
|
package/dist/index.d.ts
CHANGED
|
@@ -315,7 +315,7 @@ declare function useIcon(props: IconProps): IconData;
|
|
|
315
315
|
*/
|
|
316
316
|
declare function Icon(props: IconProps): React$1.ReactElement<IconProps, string | React$1.JSXElementConstructor<any>> | React$1.DetailedReactHTMLElement<{
|
|
317
317
|
className: string;
|
|
318
|
-
'data-icon': "search" | "chevron-right" | "chevron-down" | "folder" | "settings" | "file-text" | "
|
|
318
|
+
'data-icon': "search" | "brain" | "chevron-right" | "chevron-down" | "folder" | "settings" | "file-text" | "wrench";
|
|
319
319
|
'aria-label': string;
|
|
320
320
|
}, HTMLElement>;
|
|
321
321
|
/**
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sources":["../src/types.ts","../src/components/message.ts","../src/hooks/use-streaming-message.ts","../src/components/streaming-message.ts","../src/components/icon.ts","../src/components/thinking-section.ts","../src/components/task-section.ts","../src/components/code-project-part.ts","../src/components/content-part-renderer.ts","../src/components/math-part.ts","../src/components/code-block.ts"],"sourcesContent":["/**\n * Binary format for message content as returned by the v0 Platform API\n * Each row is a tuple where the first element is the type and the rest are data\n */\nexport type MessageBinaryFormat = [number, ...any[]][]\n\n/**\n * Individual row in the message binary format\n */\nexport type MessageBinaryFormatRow = MessageBinaryFormat[number]\n\n/**\n * Props for the Message component\n */\nexport interface MessageProps {\n /**\n * The parsed content from the v0 Platform API\n * This should be the JSON.parsed value of the 'content' field from API responses\n */\n content: MessageBinaryFormat\n\n /**\n * Optional message ID for tracking purposes\n */\n messageId?: string\n\n /**\n * Role of the message sender\n */\n role?: 'user' | 'assistant' | 'system' | 'tool'\n\n /**\n * Whether the message is currently being streamed\n */\n streaming?: boolean\n\n /**\n * Whether this is the last message in the conversation\n */\n isLastMessage?: boolean\n\n /**\n * Custom className for styling the root container\n */\n className?: string\n\n /**\n * Custom component renderers (react-markdown style)\n * Override specific components by name\n * Can be either a React component or an object with className for simple styling\n */\n components?: {\n // Content components\n CodeBlock?: React.ComponentType<{\n language: string\n code: string\n className?: string\n }>\n MathPart?: React.ComponentType<{\n content: string\n inline?: boolean\n className?: string\n }>\n CodeProjectPart?: React.ComponentType<{\n title?: string\n filename?: string\n code?: string\n language?: string\n collapsed?: boolean\n className?: string\n }>\n ThinkingSection?: React.ComponentType<{\n title?: string\n duration?: number\n thought?: string\n collapsed?: boolean\n onCollapse?: () => void\n className?: string\n children?: React.ReactNode\n brainIcon?: React.ReactNode\n chevronRightIcon?: React.ReactNode\n chevronDownIcon?: React.ReactNode\n }>\n TaskSection?: React.ComponentType<{\n title?: string\n type?: string\n parts?: any[]\n collapsed?: boolean\n onCollapse?: () => void\n className?: string\n children?: React.ReactNode\n taskIcon?: React.ReactNode\n chevronRightIcon?: React.ReactNode\n chevronDownIcon?: React.ReactNode\n }>\n Icon?: React.ComponentType<{\n name:\n | 'chevron-right'\n | 'chevron-down'\n | 'search'\n | 'folder'\n | 'settings'\n | 'file-text'\n | 'brain'\n | 'wrench'\n className?: string\n }>\n\n // HTML elements (react-markdown style)\n // Can be either a React component or an object with className\n p?:\n | React.ComponentType<React.HTMLAttributes<HTMLParagraphElement>>\n | { className?: string }\n h1?:\n | React.ComponentType<React.HTMLAttributes<HTMLHeadingElement>>\n | { className?: string }\n h2?:\n | React.ComponentType<React.HTMLAttributes<HTMLHeadingElement>>\n | { className?: string }\n h3?:\n | React.ComponentType<React.HTMLAttributes<HTMLHeadingElement>>\n | { className?: string }\n h4?:\n | React.ComponentType<React.HTMLAttributes<HTMLHeadingElement>>\n | { className?: string }\n h5?:\n | React.ComponentType<React.HTMLAttributes<HTMLHeadingElement>>\n | { className?: string }\n h6?:\n | React.ComponentType<React.HTMLAttributes<HTMLHeadingElement>>\n | { className?: string }\n ul?:\n | React.ComponentType<React.HTMLAttributes<HTMLUListElement>>\n | { className?: string }\n ol?:\n | React.ComponentType<React.HTMLAttributes<HTMLOListElement>>\n | { className?: string }\n li?:\n | React.ComponentType<React.HTMLAttributes<HTMLLIElement>>\n | { className?: string }\n blockquote?:\n | React.ComponentType<React.HTMLAttributes<HTMLQuoteElement>>\n | { className?: string }\n code?:\n | React.ComponentType<React.HTMLAttributes<HTMLElement>>\n | { className?: string }\n pre?:\n | React.ComponentType<React.HTMLAttributes<HTMLPreElement>>\n | { className?: string }\n strong?:\n | React.ComponentType<React.HTMLAttributes<HTMLElement>>\n | { className?: string }\n em?:\n | React.ComponentType<React.HTMLAttributes<HTMLElement>>\n | { className?: string }\n a?:\n | React.ComponentType<React.AnchorHTMLAttributes<HTMLAnchorElement>>\n | { className?: string }\n hr?:\n | React.ComponentType<React.HTMLAttributes<HTMLHRElement>>\n | { className?: string }\n div?:\n | React.ComponentType<React.HTMLAttributes<HTMLDivElement>>\n | { className?: string }\n span?:\n | React.ComponentType<React.HTMLAttributes<HTMLSpanElement>>\n | { className?: string }\n }\n\n /**\n * @deprecated Use `components` instead. Will be removed in next major version.\n */\n renderers?: {\n CodeBlock?: React.ComponentType<{\n language: string\n code: string\n className?: string\n }>\n MathRenderer?: React.ComponentType<{\n content: string\n inline?: boolean\n className?: string\n }>\n MathPart?: React.ComponentType<{\n content: string\n inline?: boolean\n className?: string\n }>\n Icon?: React.ComponentType<{\n name:\n | 'chevron-right'\n | 'chevron-down'\n | 'search'\n | 'folder'\n | 'settings'\n | 'file-text'\n | 'brain'\n | 'wrench'\n className?: string\n }>\n }\n}\n\n// Backward compatibility exports\nexport type MessageRendererProps = MessageProps\nexport type V0MessageRendererProps = MessageProps\n// Note: MessageStyles/MessageRendererStyles/V0MessageRendererStyles removed as styles prop is no longer supported\n","import React from 'react'\nimport { MessageProps } from '../types'\nimport { MathPart } from './math-part'\nimport { CodeBlock } from './code-block'\nimport { CodeProjectPart } from './code-project-part'\nimport { ContentPartRenderer } from './content-part-renderer'\nimport { cn } from '../utils/cn'\n\n// Headless message data structure\nexport interface MessageData {\n elements: MessageElement[]\n messageId: string\n role: string\n streaming: boolean\n isLastMessage: boolean\n}\n\nexport interface MessageElement {\n type: 'text' | 'html' | 'component' | 'content-part' | 'code-project'\n key: string\n data: any\n props?: Record<string, any>\n children?: MessageElement[]\n}\n\n// Headless hook for processing message content\nexport function useMessage({\n content,\n messageId = 'unknown',\n role = 'assistant',\n streaming = false,\n isLastMessage = false,\n components,\n renderers, // deprecated\n}: Omit<MessageProps, 'className'>): MessageData {\n if (!Array.isArray(content)) {\n console.warn(\n 'MessageContent: content must be an array (MessageBinaryFormat)',\n )\n return {\n elements: [],\n messageId,\n role,\n streaming,\n isLastMessage,\n }\n }\n\n // Merge components and renderers (backward compatibility)\n const mergedComponents = {\n ...components,\n // Map legacy renderers to new component names\n ...(renderers?.CodeBlock && { CodeBlock: renderers.CodeBlock }),\n ...(renderers?.MathRenderer && { MathPart: renderers.MathRenderer }),\n ...(renderers?.MathPart && { MathPart: renderers.MathPart }),\n ...(renderers?.Icon && { Icon: renderers.Icon }),\n }\n\n // Process content exactly like v0's Renderer component\n const elements = content\n .map(([type, data], index) => {\n const key = `${messageId}-${index}`\n\n // Markdown data (type 0) - this is the main content\n if (type === 0) {\n return processElements(data, key, mergedComponents)\n }\n\n // Metadata (type 1) - extract context but don't render\n if (type === 1) {\n // In the future, we could extract sources/context here like v0 does\n // For now, just return null like v0's renderer\n return null\n }\n\n // Other types - v0 doesn't handle these in the main renderer\n return null\n })\n .filter(Boolean) as MessageElement[]\n\n return {\n elements,\n messageId,\n role,\n streaming,\n isLastMessage,\n }\n}\n\n// Process elements into headless data structure\nfunction processElements(\n data: any,\n keyPrefix: string,\n components?: MessageProps['components'],\n): MessageElement | null {\n // Handle case where data might not be an array due to streaming/patching\n if (!Array.isArray(data)) {\n return null\n }\n\n const children = data\n .map((item, index) => {\n const key = `${keyPrefix}-${index}`\n return processElement(item, key, components)\n })\n .filter(Boolean) as MessageElement[]\n\n return {\n type: 'component',\n key: keyPrefix,\n data: 'elements',\n children,\n }\n}\n\n// Process individual elements into headless data structure\nfunction processElement(\n element: any,\n key: string,\n components?: MessageProps['components'],\n): MessageElement | null {\n if (typeof element === 'string') {\n return {\n type: 'text',\n key,\n data: element,\n }\n }\n\n if (!Array.isArray(element)) {\n return null\n }\n\n const [tagName, props, ...children] = element\n\n if (!tagName) {\n return null\n }\n\n // Handle special v0 Platform API elements\n if (tagName === 'AssistantMessageContentPart') {\n return {\n type: 'content-part',\n key,\n data: {\n part: props.part,\n iconRenderer: components?.Icon,\n thinkingSectionRenderer: components?.ThinkingSection,\n taskSectionRenderer: components?.TaskSection,\n },\n }\n }\n\n if (tagName === 'Codeblock') {\n return {\n type: 'code-project',\n key,\n data: {\n language: props.lang,\n code: children[0],\n iconRenderer: components?.Icon,\n customRenderer: components?.CodeProjectPart,\n },\n }\n }\n\n if (tagName === 'text') {\n return {\n type: 'text',\n key,\n data: children[0] || '',\n }\n }\n\n // Process children\n const processedChildren = children\n .map((child, childIndex) => {\n const childKey = `${key}-child-${childIndex}`\n return processElement(child, childKey, components)\n })\n .filter(Boolean) as MessageElement[]\n\n // Handle standard HTML elements\n const componentOrConfig = components?.[tagName as keyof typeof components]\n\n return {\n type: 'html',\n key,\n data: {\n tagName,\n props,\n componentOrConfig,\n },\n children: processedChildren,\n }\n}\n\n// Default JSX renderer for backward compatibility\nfunction MessageRenderer({\n messageData,\n className,\n}: {\n messageData: MessageData\n className?: string\n}) {\n const renderElement = (element: MessageElement): React.ReactNode => {\n switch (element.type) {\n case 'text':\n return React.createElement('span', { key: element.key }, element.data)\n\n case 'content-part':\n return React.createElement(ContentPartRenderer, {\n key: element.key,\n part: element.data.part,\n iconRenderer: element.data.iconRenderer,\n thinkingSectionRenderer: element.data.thinkingSectionRenderer,\n taskSectionRenderer: element.data.taskSectionRenderer,\n })\n\n case 'code-project':\n const CustomCodeProjectPart = element.data.customRenderer\n const CodeProjectComponent = CustomCodeProjectPart || CodeProjectPart\n return React.createElement(CodeProjectComponent, {\n key: element.key,\n language: element.data.language,\n code: element.data.code,\n iconRenderer: element.data.iconRenderer,\n })\n\n case 'html':\n const { tagName, props, componentOrConfig } = element.data\n const renderedChildren = element.children?.map(renderElement)\n\n if (typeof componentOrConfig === 'function') {\n const Component = componentOrConfig\n return React.createElement(\n Component,\n {\n key: element.key,\n ...props,\n className: props?.className,\n },\n renderedChildren,\n )\n } else if (componentOrConfig && typeof componentOrConfig === 'object') {\n const mergedClassName = cn(\n props?.className,\n componentOrConfig.className,\n )\n return React.createElement(\n tagName,\n { key: element.key, ...props, className: mergedClassName },\n renderedChildren,\n )\n } else {\n // Default HTML element rendering\n const elementProps: Record<string, any> = {\n key: element.key,\n ...props,\n }\n if (props?.className) {\n elementProps.className = props.className\n }\n\n // Special handling for links\n if (tagName === 'a') {\n elementProps.target = '_blank'\n elementProps.rel = 'noopener noreferrer'\n }\n\n return React.createElement(tagName, elementProps, renderedChildren)\n }\n\n case 'component':\n return React.createElement(\n React.Fragment,\n { key: element.key },\n element.children?.map(renderElement),\n )\n\n default:\n return null\n }\n }\n\n return React.createElement(\n 'div',\n { className },\n messageData.elements.map(renderElement),\n )\n}\n\n// Simplified renderer that matches v0's exact approach (backward compatibility)\nfunction MessageImpl({\n content,\n messageId = 'unknown',\n role = 'assistant',\n streaming = false,\n isLastMessage = false,\n className,\n components,\n renderers, // deprecated\n}: MessageProps) {\n const messageData = useMessage({\n content,\n messageId,\n role,\n streaming,\n isLastMessage,\n components,\n renderers,\n })\n\n return React.createElement(MessageRenderer, { messageData, className })\n}\n\n/**\n * Main component for rendering v0 Platform API message content\n * This is a backward-compatible JSX renderer. For headless usage, use the useMessage hook.\n */\nexport const Message = React.memo(MessageImpl)\n","import { useRef, useSyncExternalStore } from 'react'\nimport { MessageBinaryFormat } from '../types'\nimport * as jsondiffpatch from 'jsondiffpatch'\n\nconst jdf = jsondiffpatch.create({})\n\n// Exact copy of the patch function from v0/chat/lib/diffpatch.ts\nfunction patch(original: any, delta: any) {\n const newObj = jdf.clone(original)\n\n // Check for our customized delta\n if (Array.isArray(delta) && delta[1] === 9 && delta[2] === 9) {\n // Get the path to the modified element\n const indexes = delta[0].slice(0, -1)\n // Get the string to be appended\n const value = delta[0].slice(-1)\n let obj = newObj as any\n for (const index of indexes) {\n if (typeof obj[index] === 'string') {\n obj[index] += value\n return newObj\n }\n obj = obj[index]\n }\n }\n\n // If not custom delta, apply standard jsondiffpatch-ing\n jdf.patch(newObj, delta)\n return newObj\n}\n\nexport interface StreamingMessageState {\n content: MessageBinaryFormat\n isStreaming: boolean\n error?: string\n isComplete: boolean\n}\n\nexport interface UseStreamingMessageOptions {\n onChunk?: (chunk: MessageBinaryFormat) => void\n onComplete?: (finalContent: MessageBinaryFormat) => void\n onError?: (error: string) => void\n onChatData?: (chatData: any) => void\n}\n\n// Stream state manager - isolated from React lifecycle\nclass StreamStateManager {\n private content: MessageBinaryFormat = []\n private isStreaming: boolean = false\n private error?: string\n private isComplete: boolean = false\n private callbacks = new Set<() => void>()\n private processedStreams = new WeakSet<ReadableStream<Uint8Array>>()\n private cachedState: StreamingMessageState | null = null\n\n subscribe = (callback: () => void) => {\n this.callbacks.add(callback)\n return () => {\n this.callbacks.delete(callback)\n }\n }\n\n private notifySubscribers = () => {\n // Invalidate cached state when state changes\n this.cachedState = null\n this.callbacks.forEach((callback) => callback())\n }\n\n getState = (): StreamingMessageState => {\n // Return cached state to prevent infinite re-renders\n if (this.cachedState === null) {\n this.cachedState = {\n content: this.content,\n isStreaming: this.isStreaming,\n error: this.error,\n isComplete: this.isComplete,\n }\n }\n return this.cachedState\n }\n\n processStream = async (\n stream: ReadableStream<Uint8Array>,\n options: UseStreamingMessageOptions = {},\n ): Promise<void> => {\n // Prevent processing the same stream multiple times\n if (this.processedStreams.has(stream)) {\n return\n }\n\n // Handle locked streams gracefully\n if (stream.locked) {\n console.warn('Stream is locked, cannot process')\n return\n }\n\n this.processedStreams.add(stream)\n this.reset()\n this.setStreaming(true)\n\n try {\n await this.readStream(stream, options)\n } catch (err) {\n const errorMessage =\n err instanceof Error ? err.message : 'Unknown streaming error'\n this.setError(errorMessage)\n options.onError?.(errorMessage)\n } finally {\n this.setStreaming(false)\n }\n }\n\n private reset = () => {\n this.content = []\n this.isStreaming = false\n this.error = undefined\n this.isComplete = false\n this.notifySubscribers()\n }\n\n private setStreaming = (streaming: boolean) => {\n this.isStreaming = streaming\n this.notifySubscribers()\n }\n\n private setError = (error: string) => {\n this.error = error\n this.notifySubscribers()\n }\n\n private setComplete = (complete: boolean) => {\n this.isComplete = complete\n this.notifySubscribers()\n }\n\n private updateContent = (newContent: MessageBinaryFormat) => {\n this.content = [...newContent]\n this.notifySubscribers()\n }\n\n private readStream = async (\n stream: ReadableStream<Uint8Array>,\n options: UseStreamingMessageOptions,\n ): Promise<void> => {\n const reader = stream.getReader()\n const decoder = new TextDecoder()\n let buffer = ''\n let currentContent: MessageBinaryFormat = []\n\n try {\n while (true) {\n const { done, value } = await reader.read()\n if (done) {\n break\n }\n\n const chunk = decoder.decode(value, { stream: true })\n buffer += chunk\n const lines = buffer.split('\\n')\n buffer = lines.pop() || ''\n\n for (const line of lines) {\n if (line.trim() === '') {\n continue\n }\n\n // Handle SSE format (data: ...)\n let jsonData: string\n if (line.startsWith('data: ')) {\n jsonData = line.slice(6) // Remove \"data: \" prefix\n if (jsonData === '[DONE]') {\n this.setComplete(true)\n options.onComplete?.(currentContent)\n return\n }\n } else {\n // Handle raw JSON lines (fallback)\n jsonData = line\n }\n\n try {\n // Parse the JSON data\n const parsedData = JSON.parse(jsonData)\n\n // Handle v0 streaming format\n if (parsedData.type === 'connected') {\n continue\n } else if (parsedData.type === 'done') {\n this.setComplete(true)\n options.onComplete?.(currentContent)\n return\n } else if (\n parsedData.object &&\n parsedData.object.startsWith('chat')\n ) {\n // Handle chat metadata messages (chat, chat.title, chat.name, etc.)\n options.onChatData?.(parsedData)\n continue\n } else if (parsedData.delta) {\n // Apply the delta using jsondiffpatch\n const patchedContent = patch(currentContent, parsedData.delta)\n currentContent = Array.isArray(patchedContent)\n ? (patchedContent as MessageBinaryFormat)\n : []\n\n this.updateContent(currentContent)\n options.onChunk?.(currentContent)\n }\n } catch (e) {\n console.warn('Failed to parse streaming data:', line, e)\n }\n }\n }\n\n this.setComplete(true)\n options.onComplete?.(currentContent)\n } finally {\n reader.releaseLock()\n }\n }\n}\n\n/**\n * Hook for handling streaming message content from v0 API using useSyncExternalStore\n */\nexport function useStreamingMessage(\n stream: ReadableStream<Uint8Array> | null,\n options: UseStreamingMessageOptions = {},\n): StreamingMessageState {\n // Create a stable stream manager instance\n const managerRef = useRef<StreamStateManager | null>(null)\n if (!managerRef.current) {\n managerRef.current = new StreamStateManager()\n }\n\n const manager = managerRef.current\n\n // Subscribe to state changes using useSyncExternalStore\n const state = useSyncExternalStore(\n manager.subscribe,\n manager.getState,\n manager.getState,\n )\n\n // Process stream when it changes\n const lastStreamRef = useRef<ReadableStream<Uint8Array> | null>(null)\n\n if (stream !== lastStreamRef.current) {\n lastStreamRef.current = stream\n if (stream) {\n manager.processStream(stream, options)\n }\n }\n\n return state\n}\n","import React from 'react'\nimport { Message, useMessage, MessageData } from './message'\nimport {\n useStreamingMessage,\n UseStreamingMessageOptions,\n StreamingMessageState,\n} from '../hooks/use-streaming-message'\nimport { MessageProps } from '../types'\n\nexport interface StreamingMessageProps\n extends Omit<MessageProps, 'content' | 'streaming' | 'isLastMessage'>,\n UseStreamingMessageOptions {\n /**\n * The streaming response from v0.chats.create() with responseMode: 'experimental_stream'\n */\n stream: ReadableStream<Uint8Array> | null\n\n /**\n * Show a loading indicator while no content has been received yet\n */\n showLoadingIndicator?: boolean\n\n /**\n * Custom loading component\n */\n loadingComponent?: React.ReactNode\n\n /**\n * Custom error component\n */\n errorComponent?: (error: string) => React.ReactNode\n}\n\n// Headless streaming message data\nexport interface StreamingMessageData extends StreamingMessageState {\n messageData: MessageData | null\n}\n\n// Headless hook for streaming message\nexport function useStreamingMessageData({\n stream,\n messageId = 'unknown',\n role = 'assistant',\n components,\n renderers,\n onChunk,\n onComplete,\n onError,\n onChatData,\n}: Omit<\n StreamingMessageProps,\n 'className' | 'showLoadingIndicator' | 'loadingComponent' | 'errorComponent'\n>): StreamingMessageData {\n const streamingState = useStreamingMessage(stream, {\n onChunk,\n onComplete,\n onError,\n onChatData,\n })\n\n const messageData =\n streamingState.content.length > 0\n ? useMessage({\n content: streamingState.content,\n messageId,\n role,\n streaming: streamingState.isStreaming,\n isLastMessage: true,\n components,\n renderers,\n })\n : null\n\n return {\n ...streamingState,\n messageData,\n }\n}\n\n/**\n * Component for rendering streaming message content from v0 API\n *\n * For headless usage, use the useStreamingMessageData hook instead.\n *\n * @example\n * ```tsx\n * import { v0 } from 'v0-sdk'\n * import { StreamingMessage } from '@v0-sdk/react'\n *\n * function ChatDemo() {\n * const [stream, setStream] = useState<ReadableStream<Uint8Array> | null>(null)\n *\n * const handleSubmit = async () => {\n * const response = await v0.chats.create({\n * message: 'Create a button component',\n * responseMode: 'experimental_stream'\n * })\n * setStream(response)\n * }\n *\n * return (\n * <div>\n * <button onClick={handleSubmit}>Send Message</button>\n * {stream && (\n * <StreamingMessage\n * stream={stream}\n * messageId=\"demo-message\"\n * role=\"assistant\"\n * onComplete={(content) => handleCompletion(content)}\n * onChatData={(chatData) => handleChatData(chatData)}\n * />\n * )}\n * </div>\n * )\n * }\n * ```\n */\nexport function StreamingMessage({\n stream,\n showLoadingIndicator = true,\n loadingComponent,\n errorComponent,\n onChunk,\n onComplete,\n onError,\n onChatData,\n className,\n ...messageProps\n}: StreamingMessageProps) {\n const streamingData = useStreamingMessageData({\n stream,\n onChunk,\n onComplete,\n onError,\n onChatData,\n ...messageProps,\n })\n\n // Handle error state\n if (streamingData.error) {\n if (errorComponent) {\n return React.createElement(\n React.Fragment,\n {},\n errorComponent(streamingData.error),\n )\n }\n // Fallback error component using React.createElement for compatibility\n return React.createElement(\n 'div',\n {\n className: 'text-red-500 p-4 border border-red-200 rounded',\n style: {\n color: 'red',\n padding: '1rem',\n border: '1px solid #fecaca',\n borderRadius: '0.375rem',\n },\n },\n `Error: ${streamingData.error}`,\n )\n }\n\n // Handle loading state\n if (\n showLoadingIndicator &&\n streamingData.isStreaming &&\n streamingData.content.length === 0\n ) {\n if (loadingComponent) {\n return React.createElement(React.Fragment, {}, loadingComponent)\n }\n // Fallback loading component using React.createElement for compatibility\n return React.createElement(\n 'div',\n {\n className: 'flex items-center space-x-2 text-gray-500',\n style: {\n display: 'flex',\n alignItems: 'center',\n gap: '0.5rem',\n color: '#6b7280',\n },\n },\n React.createElement('div', {\n className:\n 'animate-spin h-4 w-4 border-2 border-gray-300 border-t-gray-600 rounded-full',\n style: {\n animation: 'spin 1s linear infinite',\n height: '1rem',\n width: '1rem',\n border: '2px solid #d1d5db',\n borderTopColor: '#4b5563',\n borderRadius: '50%',\n },\n }),\n React.createElement('span', {}, 'Loading...'),\n )\n }\n\n // Render the message content\n return React.createElement(Message, {\n ...messageProps,\n content: streamingData.content,\n streaming: streamingData.isStreaming,\n isLastMessage: true,\n className,\n })\n}\n","import React, { createContext, useContext } from 'react'\n\n// Context for providing custom icon implementation\nconst IconContext = createContext<React.ComponentType<IconProps> | null>(null)\n\nexport interface IconProps {\n name:\n | 'chevron-right'\n | 'chevron-down'\n | 'search'\n | 'folder'\n | 'settings'\n | 'file-text'\n | 'brain'\n | 'wrench'\n className?: string\n}\n\n// Headless icon data\nexport interface IconData {\n name: IconProps['name']\n fallback: string\n ariaLabel: string\n}\n\n// Headless hook for icon data\nexport function useIcon(props: IconProps): IconData {\n return {\n name: props.name,\n fallback: getIconFallback(props.name),\n ariaLabel: props.name.replace('-', ' '),\n }\n}\n\n/**\n * Generic icon component that can be customized by consumers.\n * By default, renders a simple fallback. Consumers should provide\n * their own icon implementation via context or props.\n *\n * For headless usage, use the useIcon hook instead.\n */\nexport function Icon(props: IconProps) {\n const CustomIcon = useContext(IconContext)\n\n // Use custom icon implementation if provided via context\n if (CustomIcon) {\n return React.createElement(CustomIcon, props)\n }\n\n const iconData = useIcon(props)\n\n // Fallback implementation - consumers should override this\n // This uses minimal DOM-specific attributes for maximum compatibility\n return React.createElement(\n 'span',\n {\n className: props.className,\n 'data-icon': iconData.name,\n 'aria-label': iconData.ariaLabel,\n // Note: suppressHydrationWarning removed for React Native compatibility\n },\n iconData.fallback,\n )\n}\n\n/**\n * Provider for custom icon implementation\n */\nexport function IconProvider({\n children,\n component,\n}: {\n children: React.ReactNode\n component: React.ComponentType<IconProps>\n}) {\n return React.createElement(\n IconContext.Provider,\n { value: component },\n children,\n )\n}\n\nfunction getIconFallback(name: string): string {\n const iconMap: Record<string, string> = {\n 'chevron-right': '▶',\n 'chevron-down': '▼',\n search: '🔍',\n folder: '📁',\n settings: '⚙️',\n 'file-text': '📄',\n brain: '🧠',\n wrench: '🔧',\n }\n return iconMap[name] || '•'\n}\n","import React, { useState } from 'react'\nimport { Icon, IconProps } from './icon'\n\nexport interface ThinkingSectionProps {\n title?: string\n duration?: number\n thought?: string\n collapsed?: boolean\n onCollapse?: () => void\n className?: string\n children?: React.ReactNode\n iconRenderer?: React.ComponentType<IconProps>\n brainIcon?: React.ReactNode\n chevronRightIcon?: React.ReactNode\n chevronDownIcon?: React.ReactNode\n}\n\n// Headless thinking section data\nexport interface ThinkingSectionData {\n title: string\n duration?: number\n thought?: string\n collapsed: boolean\n paragraphs: string[]\n formattedDuration?: string\n}\n\n// Headless hook for thinking section\nexport function useThinkingSection({\n title,\n duration,\n thought,\n collapsed: initialCollapsed = true,\n onCollapse,\n}: Omit<\n ThinkingSectionProps,\n | 'className'\n | 'children'\n | 'iconRenderer'\n | 'brainIcon'\n | 'chevronRightIcon'\n | 'chevronDownIcon'\n>): {\n data: ThinkingSectionData\n collapsed: boolean\n handleCollapse: () => void\n} {\n const [internalCollapsed, setInternalCollapsed] = useState(initialCollapsed)\n const collapsed = onCollapse ? initialCollapsed : internalCollapsed\n const handleCollapse =\n onCollapse || (() => setInternalCollapsed(!internalCollapsed))\n\n const paragraphs = thought ? thought.split('\\n\\n') : []\n const formattedDuration = duration ? `${Math.round(duration)}s` : undefined\n\n return {\n data: {\n title: title || 'Thinking',\n duration,\n thought,\n collapsed,\n paragraphs,\n formattedDuration,\n },\n collapsed,\n handleCollapse,\n }\n}\n\n/**\n * Generic thinking section component\n * Renders a collapsible section with basic structure - consumers provide styling\n *\n * For headless usage, use the useThinkingSection hook instead.\n */\nexport function ThinkingSection({\n title,\n duration,\n thought,\n collapsed: initialCollapsed = true,\n onCollapse,\n className,\n children,\n iconRenderer,\n brainIcon,\n chevronRightIcon,\n chevronDownIcon,\n}: ThinkingSectionProps) {\n const { data, collapsed, handleCollapse } = useThinkingSection({\n title,\n duration,\n thought,\n collapsed: initialCollapsed,\n onCollapse,\n })\n\n // If children provided, use that (allows complete customization)\n if (children) {\n return React.createElement(React.Fragment, {}, children)\n }\n\n // Uses React.createElement for maximum compatibility across environments\n return React.createElement(\n 'div',\n {\n className,\n 'data-component': 'thinking-section',\n },\n React.createElement(\n 'button',\n {\n onClick: handleCollapse,\n 'data-expanded': !collapsed,\n 'data-button': true,\n },\n React.createElement(\n 'div',\n { 'data-icon-container': true },\n collapsed\n ? React.createElement(\n React.Fragment,\n {},\n brainIcon ||\n (iconRenderer\n ? React.createElement(iconRenderer, { name: 'brain' })\n : React.createElement(Icon, { name: 'brain' })),\n chevronRightIcon ||\n (iconRenderer\n ? React.createElement(iconRenderer, { name: 'chevron-right' })\n : React.createElement(Icon, { name: 'chevron-right' })),\n )\n : chevronDownIcon ||\n (iconRenderer\n ? React.createElement(iconRenderer, { name: 'chevron-down' })\n : React.createElement(Icon, { name: 'chevron-down' })),\n ),\n React.createElement(\n 'span',\n { 'data-title': true },\n data.title +\n (data.formattedDuration ? ` for ${data.formattedDuration}` : ''),\n ),\n ),\n !collapsed && data.thought\n ? React.createElement(\n 'div',\n { 'data-content': true },\n React.createElement(\n 'div',\n { 'data-thought-container': true },\n data.paragraphs.map((paragraph, index) =>\n React.createElement(\n 'div',\n { key: index, 'data-paragraph': true },\n paragraph,\n ),\n ),\n ),\n )\n : null,\n )\n}\n","import React, { useState } from 'react'\nimport { Icon, IconProps } from './icon'\n\nexport interface TaskSectionProps {\n title?: string\n type?: string\n parts?: any[]\n collapsed?: boolean\n onCollapse?: () => void\n className?: string\n children?: React.ReactNode\n iconRenderer?: React.ComponentType<IconProps>\n taskIcon?: React.ReactNode\n chevronRightIcon?: React.ReactNode\n chevronDownIcon?: React.ReactNode\n}\n\n// Headless task section data\nexport interface TaskSectionData {\n title: string\n type?: string\n parts: any[]\n collapsed: boolean\n meaningfulParts: any[]\n shouldShowCollapsible: boolean\n iconName: IconProps['name']\n}\n\n// Headless task part data\nexport interface TaskPartData {\n type: string\n status?: string\n content: React.ReactNode\n isSearching?: boolean\n isAnalyzing?: boolean\n isComplete?: boolean\n query?: string\n count?: number\n answer?: string\n sources?: Array<{ url: string; title: string }>\n files?: string[]\n issues?: number\n}\n\nfunction getTypeIcon(type?: string, title?: string): IconProps['name'] {\n // Check title content for specific cases\n if (title?.includes('No issues found')) {\n return 'wrench'\n }\n if (title?.includes('Analyzed codebase')) {\n return 'search'\n }\n\n // Fallback to type-based icons\n switch (type) {\n case 'task-search-web-v1':\n return 'search'\n case 'task-search-repo-v1':\n return 'folder'\n case 'task-diagnostics-v1':\n return 'settings'\n default:\n return 'wrench'\n }\n}\n\nfunction processTaskPart(part: any, index: number): TaskPartData {\n const baseData: TaskPartData = {\n type: part.type,\n status: part.status,\n content: null,\n }\n\n if (part.type === 'search-web') {\n if (part.status === 'searching') {\n return {\n ...baseData,\n isSearching: true,\n query: part.query,\n content: `Searching \"${part.query}\"`,\n }\n }\n if (part.status === 'analyzing') {\n return {\n ...baseData,\n isAnalyzing: true,\n count: part.count,\n content: `Analyzing ${part.count} results...`,\n }\n }\n if (part.status === 'complete' && part.answer) {\n return {\n ...baseData,\n isComplete: true,\n answer: part.answer,\n sources: part.sources,\n content: part.answer,\n }\n }\n }\n\n if (part.type === 'search-repo') {\n if (part.status === 'searching') {\n return {\n ...baseData,\n isSearching: true,\n query: part.query,\n content: `Searching \"${part.query}\"`,\n }\n }\n if (part.status === 'reading' && part.files) {\n return {\n ...baseData,\n files: part.files,\n content: 'Reading files',\n }\n }\n }\n\n if (part.type === 'diagnostics') {\n if (part.status === 'checking') {\n return {\n ...baseData,\n content: 'Checking for issues...',\n }\n }\n if (part.status === 'complete' && part.issues === 0) {\n return {\n ...baseData,\n isComplete: true,\n issues: part.issues,\n content: '✅ No issues found',\n }\n }\n }\n\n return {\n ...baseData,\n content: JSON.stringify(part),\n }\n}\n\nfunction renderTaskPartContent(\n partData: TaskPartData,\n index: number,\n iconRenderer?: React.ComponentType<IconProps>,\n): React.ReactNode {\n if (\n partData.type === 'search-web' &&\n partData.isComplete &&\n partData.sources\n ) {\n return React.createElement(\n 'div',\n { key: index },\n React.createElement('p', {}, partData.content),\n partData.sources.length > 0\n ? React.createElement(\n 'div',\n {},\n partData.sources.map((source, sourceIndex) =>\n React.createElement(\n 'a',\n {\n key: sourceIndex,\n href: source.url,\n target: '_blank',\n rel: 'noopener noreferrer',\n },\n source.title,\n ),\n ),\n )\n : null,\n )\n }\n\n if (partData.type === 'search-repo' && partData.files) {\n return React.createElement(\n 'div',\n { key: index },\n React.createElement('span', {}, partData.content),\n partData.files.map((file, fileIndex) =>\n React.createElement(\n 'span',\n { key: fileIndex },\n iconRenderer\n ? React.createElement(iconRenderer, { name: 'file-text' })\n : React.createElement(Icon, { name: 'file-text' }),\n ' ',\n file,\n ),\n ),\n )\n }\n\n return React.createElement('div', { key: index }, partData.content)\n}\n\n// Headless hook for task section\nexport function useTaskSection({\n title,\n type,\n parts = [],\n collapsed: initialCollapsed = true,\n onCollapse,\n}: Omit<\n TaskSectionProps,\n | 'className'\n | 'children'\n | 'iconRenderer'\n | 'taskIcon'\n | 'chevronRightIcon'\n | 'chevronDownIcon'\n>): {\n data: TaskSectionData\n collapsed: boolean\n handleCollapse: () => void\n processedParts: TaskPartData[]\n} {\n const [internalCollapsed, setInternalCollapsed] = useState(initialCollapsed)\n const collapsed = onCollapse ? initialCollapsed : internalCollapsed\n const handleCollapse =\n onCollapse || (() => setInternalCollapsed(!internalCollapsed))\n\n // Count meaningful parts (parts that would render something)\n const meaningfulParts = parts.filter((part) => {\n // Check if the part would render meaningful content\n if (part.type === 'search-web') {\n return (\n part.status === 'searching' ||\n part.status === 'analyzing' ||\n (part.status === 'complete' && part.answer)\n )\n }\n if (part.type === 'starting-repo-search' && part.query) return true\n if (part.type === 'select-files' && part.filePaths?.length > 0) return true\n if (part.type === 'starting-web-search' && part.query) return true\n if (part.type === 'got-results' && part.count) return true\n if (part.type === 'finished-web-search' && part.answer) return true\n if (part.type === 'diagnostics-passed') return true\n if (part.type === 'fetching-diagnostics') return true\n return false\n })\n\n const processedParts = parts.map(processTaskPart)\n\n return {\n data: {\n title: title || 'Task',\n type,\n parts,\n collapsed,\n meaningfulParts,\n shouldShowCollapsible: meaningfulParts.length > 1,\n iconName: getTypeIcon(type, title),\n },\n collapsed,\n handleCollapse,\n processedParts,\n }\n}\n\n/**\n * Generic task section component\n * Renders a collapsible task section with basic structure - consumers provide styling\n *\n * For headless usage, use the useTaskSection hook instead.\n */\nexport function TaskSection({\n title,\n type,\n parts = [],\n collapsed: initialCollapsed = true,\n onCollapse,\n className,\n children,\n iconRenderer,\n taskIcon,\n chevronRightIcon,\n chevronDownIcon,\n}: TaskSectionProps) {\n const { data, collapsed, handleCollapse, processedParts } = useTaskSection({\n title,\n type,\n parts,\n collapsed: initialCollapsed,\n onCollapse,\n })\n\n // If children provided, use that (allows complete customization)\n if (children) {\n return React.createElement(React.Fragment, {}, children)\n }\n\n // If there's only one meaningful part, show just the content without the collapsible wrapper\n if (!data.shouldShowCollapsible && data.meaningfulParts.length === 1) {\n const partData = processTaskPart(data.meaningfulParts[0], 0)\n return React.createElement(\n 'div',\n {\n className,\n 'data-component': 'task-section-inline',\n },\n React.createElement(\n 'div',\n { 'data-part': true },\n renderTaskPartContent(partData, 0, iconRenderer),\n ),\n )\n }\n\n // Uses React.createElement for maximum compatibility across environments\n return React.createElement(\n 'div',\n {\n className,\n 'data-component': 'task-section',\n },\n React.createElement(\n 'button',\n {\n onClick: handleCollapse,\n 'data-expanded': !collapsed,\n 'data-button': true,\n },\n React.createElement(\n 'div',\n { 'data-icon-container': true },\n React.createElement(\n 'div',\n { 'data-task-icon': true },\n taskIcon ||\n (iconRenderer\n ? React.createElement(iconRenderer, { name: data.iconName })\n : React.createElement(Icon, { name: data.iconName })),\n ),\n collapsed\n ? chevronRightIcon ||\n (iconRenderer\n ? React.createElement(iconRenderer, { name: 'chevron-right' })\n : React.createElement(Icon, { name: 'chevron-right' }))\n : chevronDownIcon ||\n (iconRenderer\n ? React.createElement(iconRenderer, { name: 'chevron-down' })\n : React.createElement(Icon, { name: 'chevron-down' })),\n ),\n React.createElement('span', { 'data-title': true }, data.title),\n ),\n !collapsed\n ? React.createElement(\n 'div',\n { 'data-content': true },\n React.createElement(\n 'div',\n { 'data-parts-container': true },\n processedParts.map((partData, index) =>\n React.createElement(\n 'div',\n { key: index, 'data-part': true },\n renderTaskPartContent(partData, index, iconRenderer),\n ),\n ),\n ),\n )\n : null,\n )\n}\n","import React, { useState } from 'react'\nimport { CodeBlock } from './code-block'\nimport { Icon, IconProps } from './icon'\n\nexport interface CodeProjectPartProps {\n title?: string\n filename?: string\n code?: string\n language?: string\n collapsed?: boolean\n className?: string\n children?: React.ReactNode\n iconRenderer?: React.ComponentType<IconProps>\n}\n\n// Headless code project data\nexport interface CodeProjectData {\n title: string\n filename?: string\n code?: string\n language: string\n collapsed: boolean\n files: Array<{\n name: string\n path: string\n active: boolean\n }>\n}\n\n// Headless hook for code project\nexport function useCodeProject({\n title,\n filename,\n code,\n language = 'typescript',\n collapsed: initialCollapsed = true,\n}: Omit<CodeProjectPartProps, 'className' | 'children' | 'iconRenderer'>): {\n data: CodeProjectData\n collapsed: boolean\n toggleCollapsed: () => void\n} {\n const [collapsed, setCollapsed] = useState(initialCollapsed)\n\n // Mock file structure - in a real implementation this could be dynamic\n const files = [\n {\n name: filename || 'page.tsx',\n path: 'app/page.tsx',\n active: true,\n },\n {\n name: 'layout.tsx',\n path: 'app/layout.tsx',\n active: false,\n },\n {\n name: 'globals.css',\n path: 'app/globals.css',\n active: false,\n },\n ]\n\n return {\n data: {\n title: title || 'Code Project',\n filename,\n code,\n language,\n collapsed,\n files,\n },\n collapsed,\n toggleCollapsed: () => setCollapsed(!collapsed),\n }\n}\n\n/**\n * Generic code project block component\n * Renders a collapsible code project with basic structure - consumers provide styling\n *\n * For headless usage, use the useCodeProject hook instead.\n */\nexport function CodeProjectPart({\n title,\n filename,\n code,\n language = 'typescript',\n collapsed: initialCollapsed = true,\n className,\n children,\n iconRenderer,\n}: CodeProjectPartProps) {\n const { data, collapsed, toggleCollapsed } = useCodeProject({\n title,\n filename,\n code,\n language,\n collapsed: initialCollapsed,\n })\n\n // If children provided, use that (allows complete customization)\n if (children) {\n return React.createElement(React.Fragment, {}, children)\n }\n\n // Uses React.createElement for maximum compatibility across environments\n return React.createElement(\n 'div',\n {\n className,\n 'data-component': 'code-project-block',\n },\n React.createElement(\n 'button',\n {\n onClick: toggleCollapsed,\n 'data-expanded': !collapsed,\n },\n React.createElement(\n 'div',\n { 'data-header': true },\n iconRenderer\n ? React.createElement(iconRenderer, { name: 'folder' })\n : React.createElement(Icon, { name: 'folder' }),\n React.createElement('span', { 'data-title': true }, data.title),\n ),\n React.createElement('span', { 'data-version': true }, 'v1'),\n ),\n !collapsed\n ? React.createElement(\n 'div',\n { 'data-content': true },\n React.createElement(\n 'div',\n { 'data-file-list': true },\n data.files.map((file, index) =>\n React.createElement(\n 'div',\n {\n key: index,\n 'data-file': true,\n ...(file.active && { 'data-active': true }),\n },\n iconRenderer\n ? React.createElement(iconRenderer, { name: 'file-text' })\n : React.createElement(Icon, { name: 'file-text' }),\n React.createElement(\n 'span',\n { 'data-filename': true },\n file.name,\n ),\n React.createElement(\n 'span',\n { 'data-filepath': true },\n file.path,\n ),\n ),\n ),\n ),\n data.code\n ? React.createElement(CodeBlock, {\n language: data.language,\n code: data.code,\n })\n : null,\n )\n : null,\n )\n}\n","import React, { useState } from 'react'\nimport { ThinkingSection } from './thinking-section'\nimport { TaskSection } from './task-section'\nimport { IconProps } from './icon'\n\nexport interface ContentPartRendererProps {\n part: any\n iconRenderer?: React.ComponentType<IconProps>\n thinkingSectionRenderer?: React.ComponentType<{\n title?: string\n duration?: number\n thought?: string\n collapsed?: boolean\n onCollapse?: () => void\n className?: string\n children?: React.ReactNode\n brainIcon?: React.ReactNode\n chevronRightIcon?: React.ReactNode\n chevronDownIcon?: React.ReactNode\n }>\n taskSectionRenderer?: React.ComponentType<{\n title?: string\n type?: string\n parts?: any[]\n collapsed?: boolean\n onCollapse?: () => void\n className?: string\n children?: React.ReactNode\n taskIcon?: React.ReactNode\n chevronRightIcon?: React.ReactNode\n chevronDownIcon?: React.ReactNode\n }>\n // Individual icon props for direct icon usage\n brainIcon?: React.ReactNode\n chevronRightIcon?: React.ReactNode\n chevronDownIcon?: React.ReactNode\n searchIcon?: React.ReactNode\n folderIcon?: React.ReactNode\n settingsIcon?: React.ReactNode\n wrenchIcon?: React.ReactNode\n}\n\n// Headless content part data\nexport interface ContentPartData {\n type: string\n parts: any[]\n metadata: Record<string, any>\n componentType: 'thinking' | 'task' | 'unknown' | null\n title?: string\n iconName?: IconProps['name']\n thinkingData?: {\n duration?: number\n thought?: string\n }\n}\n\n// Headless hook for content part\nexport function useContentPart(part: any): ContentPartData {\n if (!part) {\n return {\n type: '',\n parts: [],\n metadata: {},\n componentType: null,\n }\n }\n\n const { type, parts = [], ...metadata } = part\n\n let componentType: ContentPartData['componentType'] = 'unknown'\n let title: string | undefined\n let iconName: IconProps['name'] | undefined\n let thinkingData: ContentPartData['thinkingData']\n\n switch (type) {\n case 'task-thinking-v1':\n componentType = 'thinking'\n title = 'Thought'\n const thinkingPart = parts.find((p: any) => p.type === 'thinking-end')\n thinkingData = {\n duration: thinkingPart?.duration,\n thought: thinkingPart?.thought,\n }\n break\n\n case 'task-search-web-v1':\n componentType = 'task'\n title = metadata.taskNameComplete || metadata.taskNameActive\n iconName = 'search'\n break\n\n case 'task-search-repo-v1':\n componentType = 'task'\n title = metadata.taskNameComplete || metadata.taskNameActive\n iconName = 'folder'\n break\n\n case 'task-diagnostics-v1':\n componentType = 'task'\n title = metadata.taskNameComplete || metadata.taskNameActive\n iconName = 'settings'\n break\n\n case 'task-read-file-v1':\n componentType = 'task'\n title =\n metadata.taskNameComplete || metadata.taskNameActive || 'Reading file'\n iconName = 'folder'\n break\n\n case 'task-coding-v1':\n componentType = 'task'\n title = metadata.taskNameComplete || metadata.taskNameActive || 'Coding'\n iconName = 'wrench'\n break\n\n case 'task-start-v1':\n componentType = null // Usually just indicates task start - can be hidden\n break\n\n default:\n componentType = 'unknown'\n break\n }\n\n return {\n type,\n parts,\n metadata,\n componentType,\n title,\n iconName,\n thinkingData,\n }\n}\n\n/**\n * Content part renderer that handles different types of v0 API content parts\n *\n * For headless usage, use the useContentPart hook instead.\n */\nexport function ContentPartRenderer({\n part,\n iconRenderer,\n thinkingSectionRenderer,\n taskSectionRenderer,\n brainIcon,\n chevronRightIcon,\n chevronDownIcon,\n searchIcon,\n folderIcon,\n settingsIcon,\n wrenchIcon,\n}: ContentPartRendererProps) {\n const contentData = useContentPart(part)\n\n if (!contentData.componentType) {\n return null\n }\n\n if (contentData.componentType === 'thinking') {\n const ThinkingComponent = thinkingSectionRenderer || ThinkingSection\n const [collapsed, setCollapsed] = useState(true)\n\n return React.createElement(ThinkingComponent, {\n title: contentData.title,\n duration: contentData.thinkingData?.duration,\n thought: contentData.thinkingData?.thought,\n collapsed,\n onCollapse: () => setCollapsed(!collapsed),\n brainIcon,\n chevronRightIcon,\n chevronDownIcon,\n })\n }\n\n if (contentData.componentType === 'task') {\n const TaskComponent = taskSectionRenderer || TaskSection\n const [collapsed, setCollapsed] = useState(true)\n\n // Map icon names to icon components\n let taskIcon: React.ReactNode\n switch (contentData.iconName) {\n case 'search':\n taskIcon = searchIcon\n break\n case 'folder':\n taskIcon = folderIcon\n break\n case 'settings':\n taskIcon = settingsIcon\n break\n case 'wrench':\n taskIcon = wrenchIcon\n break\n default:\n taskIcon = undefined\n break\n }\n\n return React.createElement(TaskComponent, {\n title: contentData.title,\n type: contentData.type,\n parts: contentData.parts,\n collapsed,\n onCollapse: () => setCollapsed(!collapsed),\n taskIcon,\n chevronRightIcon,\n chevronDownIcon,\n })\n }\n\n if (contentData.componentType === 'unknown') {\n return React.createElement(\n 'div',\n { 'data-unknown-part-type': contentData.type },\n `Unknown part type: ${contentData.type}`,\n )\n }\n\n return null\n}\n","import React from 'react'\n\nexport interface MathPartProps {\n content: string\n inline?: boolean\n className?: string\n children?: React.ReactNode\n displayMode?: boolean\n}\n\n// Headless math data\nexport interface MathData {\n content: string\n inline: boolean\n displayMode: boolean\n processedContent: string\n}\n\n// Headless hook for math data\nexport function useMath(\n props: Omit<MathPartProps, 'className' | 'children'>,\n): MathData {\n return {\n content: props.content,\n inline: props.inline ?? false,\n displayMode: props.displayMode ?? !props.inline,\n processedContent: props.content, // Could be enhanced with math processing\n }\n}\n\n/**\n * Generic math renderer component\n * Renders plain math content by default - consumers should provide their own math rendering\n *\n * For headless usage, use the useMath hook instead.\n */\nexport function MathPart({\n content,\n inline = false,\n className = '',\n children,\n displayMode,\n}: MathPartProps) {\n // If children provided, use that (allows complete customization)\n if (children) {\n return React.createElement(React.Fragment, {}, children)\n }\n\n const mathData = useMath({ content, inline, displayMode })\n\n // Simple fallback - just render plain math content\n // Uses React.createElement for maximum compatibility across environments\n return React.createElement(\n mathData.inline ? 'span' : 'div',\n {\n className,\n 'data-math-inline': mathData.inline,\n 'data-math-display': mathData.displayMode,\n },\n mathData.processedContent,\n )\n}\n","import React from 'react'\n\nexport interface CodeBlockProps {\n language: string\n code: string\n className?: string\n children?: React.ReactNode\n filename?: string\n}\n\n// Headless code block data\nexport interface CodeBlockData {\n language: string\n code: string\n filename?: string\n lines: string[]\n lineCount: number\n}\n\n// Headless hook for code block data\nexport function useCodeBlock(\n props: Omit<CodeBlockProps, 'className' | 'children'>,\n): CodeBlockData {\n const lines = props.code.split('\\n')\n\n return {\n language: props.language,\n code: props.code,\n filename: props.filename,\n lines,\n lineCount: lines.length,\n }\n}\n\n/**\n * Generic code block component\n * Renders plain code by default - consumers should provide their own styling and highlighting\n *\n * For headless usage, use the useCodeBlock hook instead.\n */\nexport function CodeBlock({\n language,\n code,\n className = '',\n children,\n filename,\n}: CodeBlockProps) {\n // If children provided, use that (allows complete customization)\n if (children) {\n return React.createElement(React.Fragment, {}, children)\n }\n\n const codeBlockData = useCodeBlock({ language, code, filename })\n\n // Simple fallback - just render plain code\n // Uses React.createElement for maximum compatibility across environments\n return React.createElement(\n 'pre',\n {\n className,\n 'data-language': codeBlockData.language,\n ...(filename && { 'data-filename': filename }),\n },\n React.createElement('code', {}, codeBlockData.code),\n )\n}\n"],"names":[],"mappings":";;AAAA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACO;AACP;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACA;;AC5KA;AACP;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;;ACxBA;AACP;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;;ACZA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AC/EO;AACP;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;;AC3BO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;;ACnCO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;;ACtDO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;;ACpCO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AC9EO;AACP;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;;ACxBO;AACP;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;;;"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sources":["../src/types.ts","../src/components/message.ts","../src/hooks/use-streaming-message.ts","../src/components/streaming-message.ts","../src/components/icon.ts","../src/components/thinking-section.ts","../src/components/task-section.ts","../src/components/code-project-part.ts","../src/components/content-part-renderer.ts","../src/components/math-part.ts","../src/components/code-block.ts"],"sourcesContent":["/**\n * Binary format for message content as returned by the v0 Platform API\n * Each row is a tuple where the first element is the type and the rest are data\n */\nexport type MessageBinaryFormat = [number, ...any[]][]\n\n/**\n * Individual row in the message binary format\n */\nexport type MessageBinaryFormatRow = MessageBinaryFormat[number]\n\n/**\n * Props for the Message component\n */\nexport interface MessageProps {\n /**\n * The parsed content from the v0 Platform API\n * This should be the JSON.parsed value of the 'content' field from API responses\n */\n content: MessageBinaryFormat\n\n /**\n * Optional message ID for tracking purposes\n */\n messageId?: string\n\n /**\n * Role of the message sender\n */\n role?: 'user' | 'assistant' | 'system' | 'tool'\n\n /**\n * Whether the message is currently being streamed\n */\n streaming?: boolean\n\n /**\n * Whether this is the last message in the conversation\n */\n isLastMessage?: boolean\n\n /**\n * Custom className for styling the root container\n */\n className?: string\n\n /**\n * Custom component renderers (react-markdown style)\n * Override specific components by name\n * Can be either a React component or an object with className for simple styling\n */\n components?: {\n // Content components\n CodeBlock?: React.ComponentType<{\n language: string\n code: string\n className?: string\n }>\n MathPart?: React.ComponentType<{\n content: string\n inline?: boolean\n className?: string\n }>\n CodeProjectPart?: React.ComponentType<{\n title?: string\n filename?: string\n code?: string\n language?: string\n collapsed?: boolean\n className?: string\n }>\n ThinkingSection?: React.ComponentType<{\n title?: string\n duration?: number\n thought?: string\n collapsed?: boolean\n onCollapse?: () => void\n className?: string\n children?: React.ReactNode\n brainIcon?: React.ReactNode\n chevronRightIcon?: React.ReactNode\n chevronDownIcon?: React.ReactNode\n }>\n TaskSection?: React.ComponentType<{\n title?: string\n type?: string\n parts?: any[]\n collapsed?: boolean\n onCollapse?: () => void\n className?: string\n children?: React.ReactNode\n taskIcon?: React.ReactNode\n chevronRightIcon?: React.ReactNode\n chevronDownIcon?: React.ReactNode\n }>\n Icon?: React.ComponentType<{\n name:\n | 'chevron-right'\n | 'chevron-down'\n | 'search'\n | 'folder'\n | 'settings'\n | 'file-text'\n | 'brain'\n | 'wrench'\n className?: string\n }>\n\n // HTML elements (react-markdown style)\n // Can be either a React component or an object with className\n p?:\n | React.ComponentType<React.HTMLAttributes<HTMLParagraphElement>>\n | { className?: string }\n h1?:\n | React.ComponentType<React.HTMLAttributes<HTMLHeadingElement>>\n | { className?: string }\n h2?:\n | React.ComponentType<React.HTMLAttributes<HTMLHeadingElement>>\n | { className?: string }\n h3?:\n | React.ComponentType<React.HTMLAttributes<HTMLHeadingElement>>\n | { className?: string }\n h4?:\n | React.ComponentType<React.HTMLAttributes<HTMLHeadingElement>>\n | { className?: string }\n h5?:\n | React.ComponentType<React.HTMLAttributes<HTMLHeadingElement>>\n | { className?: string }\n h6?:\n | React.ComponentType<React.HTMLAttributes<HTMLHeadingElement>>\n | { className?: string }\n ul?:\n | React.ComponentType<React.HTMLAttributes<HTMLUListElement>>\n | { className?: string }\n ol?:\n | React.ComponentType<React.HTMLAttributes<HTMLOListElement>>\n | { className?: string }\n li?:\n | React.ComponentType<React.HTMLAttributes<HTMLLIElement>>\n | { className?: string }\n blockquote?:\n | React.ComponentType<React.HTMLAttributes<HTMLQuoteElement>>\n | { className?: string }\n code?:\n | React.ComponentType<React.HTMLAttributes<HTMLElement>>\n | { className?: string }\n pre?:\n | React.ComponentType<React.HTMLAttributes<HTMLPreElement>>\n | { className?: string }\n strong?:\n | React.ComponentType<React.HTMLAttributes<HTMLElement>>\n | { className?: string }\n em?:\n | React.ComponentType<React.HTMLAttributes<HTMLElement>>\n | { className?: string }\n a?:\n | React.ComponentType<React.AnchorHTMLAttributes<HTMLAnchorElement>>\n | { className?: string }\n hr?:\n | React.ComponentType<React.HTMLAttributes<HTMLHRElement>>\n | { className?: string }\n div?:\n | React.ComponentType<React.HTMLAttributes<HTMLDivElement>>\n | { className?: string }\n span?:\n | React.ComponentType<React.HTMLAttributes<HTMLSpanElement>>\n | { className?: string }\n }\n\n /**\n * @deprecated Use `components` instead. Will be removed in next major version.\n */\n renderers?: {\n CodeBlock?: React.ComponentType<{\n language: string\n code: string\n className?: string\n }>\n MathRenderer?: React.ComponentType<{\n content: string\n inline?: boolean\n className?: string\n }>\n MathPart?: React.ComponentType<{\n content: string\n inline?: boolean\n className?: string\n }>\n Icon?: React.ComponentType<{\n name:\n | 'chevron-right'\n | 'chevron-down'\n | 'search'\n | 'folder'\n | 'settings'\n | 'file-text'\n | 'brain'\n | 'wrench'\n className?: string\n }>\n }\n}\n\n// Backward compatibility exports\nexport type MessageRendererProps = MessageProps\nexport type V0MessageRendererProps = MessageProps\n// Note: MessageStyles/MessageRendererStyles/V0MessageRendererStyles removed as styles prop is no longer supported\n","import React from 'react'\nimport { MessageProps } from '../types'\nimport { MathPart } from './math-part'\nimport { CodeBlock } from './code-block'\nimport { CodeProjectPart } from './code-project-part'\nimport { ContentPartRenderer } from './content-part-renderer'\nimport { cn } from '../utils/cn'\n\n// Headless message data structure\nexport interface MessageData {\n elements: MessageElement[]\n messageId: string\n role: string\n streaming: boolean\n isLastMessage: boolean\n}\n\nexport interface MessageElement {\n type: 'text' | 'html' | 'component' | 'content-part' | 'code-project'\n key: string\n data: any\n props?: Record<string, any>\n children?: MessageElement[]\n}\n\n// Headless hook for processing message content\nexport function useMessage({\n content,\n messageId = 'unknown',\n role = 'assistant',\n streaming = false,\n isLastMessage = false,\n components,\n renderers, // deprecated\n}: Omit<MessageProps, 'className'>): MessageData {\n if (!Array.isArray(content)) {\n console.warn(\n 'MessageContent: content must be an array (MessageBinaryFormat)',\n )\n return {\n elements: [],\n messageId,\n role,\n streaming,\n isLastMessage,\n }\n }\n\n // Merge components and renderers (backward compatibility)\n const mergedComponents = {\n ...components,\n // Map legacy renderers to new component names\n ...(renderers?.CodeBlock && { CodeBlock: renderers.CodeBlock }),\n ...(renderers?.MathRenderer && { MathPart: renderers.MathRenderer }),\n ...(renderers?.MathPart && { MathPart: renderers.MathPart }),\n ...(renderers?.Icon && { Icon: renderers.Icon }),\n }\n\n // Process content exactly like v0's Renderer component\n const elements = content\n .map(([type, data], index) => {\n const key = `${messageId}-${index}`\n\n // Markdown data (type 0) - this is the main content\n if (type === 0) {\n return processElements(data, key, mergedComponents)\n }\n\n // Metadata (type 1) - extract context but don't render\n if (type === 1) {\n // In the future, we could extract sources/context here like v0 does\n // For now, just return null like v0's renderer\n return null\n }\n\n // Other types - v0 doesn't handle these in the main renderer\n return null\n })\n .filter(Boolean) as MessageElement[]\n\n return {\n elements,\n messageId,\n role,\n streaming,\n isLastMessage,\n }\n}\n\n// Process elements into headless data structure\nfunction processElements(\n data: any,\n keyPrefix: string,\n components?: MessageProps['components'],\n): MessageElement | null {\n // Handle case where data might not be an array due to streaming/patching\n if (!Array.isArray(data)) {\n return null\n }\n\n const children = data\n .map((item, index) => {\n const key = `${keyPrefix}-${index}`\n return processElement(item, key, components)\n })\n .filter(Boolean) as MessageElement[]\n\n return {\n type: 'component',\n key: keyPrefix,\n data: 'elements',\n children,\n }\n}\n\n// Process individual elements into headless data structure\nfunction processElement(\n element: any,\n key: string,\n components?: MessageProps['components'],\n): MessageElement | null {\n if (typeof element === 'string') {\n return {\n type: 'text',\n key,\n data: element,\n }\n }\n\n if (!Array.isArray(element)) {\n return null\n }\n\n const [tagName, props, ...children] = element\n\n if (!tagName) {\n return null\n }\n\n // Handle special v0 Platform API elements\n if (tagName === 'AssistantMessageContentPart') {\n return {\n type: 'content-part',\n key,\n data: {\n part: props.part,\n iconRenderer: components?.Icon,\n thinkingSectionRenderer: components?.ThinkingSection,\n taskSectionRenderer: components?.TaskSection,\n },\n }\n }\n\n if (tagName === 'Codeblock') {\n return {\n type: 'code-project',\n key,\n data: {\n language: props.lang,\n code: children[0],\n iconRenderer: components?.Icon,\n customRenderer: components?.CodeProjectPart,\n },\n }\n }\n\n if (tagName === 'text') {\n return {\n type: 'text',\n key,\n data: children[0] || '',\n }\n }\n\n // Process children\n const processedChildren = children\n .map((child, childIndex) => {\n const childKey = `${key}-child-${childIndex}`\n return processElement(child, childKey, components)\n })\n .filter(Boolean) as MessageElement[]\n\n // Handle standard HTML elements\n const componentOrConfig = components?.[tagName as keyof typeof components]\n\n return {\n type: 'html',\n key,\n data: {\n tagName,\n props,\n componentOrConfig,\n },\n children: processedChildren,\n }\n}\n\n// Default JSX renderer for backward compatibility\nfunction MessageRenderer({\n messageData,\n className,\n}: {\n messageData: MessageData\n className?: string\n}) {\n const renderElement = (element: MessageElement): React.ReactNode => {\n switch (element.type) {\n case 'text':\n return React.createElement('span', { key: element.key }, element.data)\n\n case 'content-part':\n return React.createElement(ContentPartRenderer, {\n key: element.key,\n part: element.data.part,\n iconRenderer: element.data.iconRenderer,\n thinkingSectionRenderer: element.data.thinkingSectionRenderer,\n taskSectionRenderer: element.data.taskSectionRenderer,\n })\n\n case 'code-project':\n const CustomCodeProjectPart = element.data.customRenderer\n const CodeProjectComponent = CustomCodeProjectPart || CodeProjectPart\n return React.createElement(CodeProjectComponent, {\n key: element.key,\n language: element.data.language,\n code: element.data.code,\n iconRenderer: element.data.iconRenderer,\n })\n\n case 'html':\n const { tagName, props, componentOrConfig } = element.data\n const renderedChildren = element.children?.map(renderElement)\n\n if (typeof componentOrConfig === 'function') {\n const Component = componentOrConfig\n return React.createElement(\n Component,\n {\n key: element.key,\n ...props,\n className: props?.className,\n },\n renderedChildren,\n )\n } else if (componentOrConfig && typeof componentOrConfig === 'object') {\n const mergedClassName = cn(\n props?.className,\n componentOrConfig.className,\n )\n return React.createElement(\n tagName,\n { key: element.key, ...props, className: mergedClassName },\n renderedChildren,\n )\n } else {\n // Default HTML element rendering\n const elementProps: Record<string, any> = {\n key: element.key,\n ...props,\n }\n if (props?.className) {\n elementProps.className = props.className\n }\n\n // Special handling for links\n if (tagName === 'a') {\n elementProps.target = '_blank'\n elementProps.rel = 'noopener noreferrer'\n }\n\n return React.createElement(tagName, elementProps, renderedChildren)\n }\n\n case 'component':\n return React.createElement(\n React.Fragment,\n { key: element.key },\n element.children?.map(renderElement),\n )\n\n default:\n return null\n }\n }\n\n return React.createElement(\n 'div',\n { className },\n messageData.elements.map(renderElement),\n )\n}\n\n// Simplified renderer that matches v0's exact approach (backward compatibility)\nfunction MessageImpl({\n content,\n messageId = 'unknown',\n role = 'assistant',\n streaming = false,\n isLastMessage = false,\n className,\n components,\n renderers, // deprecated\n}: MessageProps) {\n const messageData = useMessage({\n content,\n messageId,\n role,\n streaming,\n isLastMessage,\n components,\n renderers,\n })\n\n return React.createElement(MessageRenderer, { messageData, className })\n}\n\n/**\n * Main component for rendering v0 Platform API message content\n * This is a backward-compatible JSX renderer. For headless usage, use the useMessage hook.\n */\nexport const Message = React.memo(MessageImpl)\n","import { useRef, useSyncExternalStore } from 'react'\nimport { MessageBinaryFormat } from '../types'\nimport * as jsondiffpatch from 'jsondiffpatch'\n\nconst jdf = jsondiffpatch.create({})\n\n// Exact copy of the patch function from v0/chat/lib/diffpatch.ts\nfunction patch(original: any, delta: any) {\n const newObj = jdf.clone(original)\n\n // Check for our customized delta\n if (Array.isArray(delta) && delta[1] === 9 && delta[2] === 9) {\n // Get the path to the modified element\n const indexes = delta[0].slice(0, -1)\n // Get the string to be appended\n const value = delta[0].slice(-1)\n let obj = newObj as any\n for (const index of indexes) {\n if (typeof obj[index] === 'string') {\n obj[index] += value\n return newObj\n }\n obj = obj[index]\n }\n }\n\n // If not custom delta, apply standard jsondiffpatch-ing\n jdf.patch(newObj, delta)\n return newObj\n}\n\nexport interface StreamingMessageState {\n content: MessageBinaryFormat\n isStreaming: boolean\n error?: string\n isComplete: boolean\n}\n\nexport interface UseStreamingMessageOptions {\n onChunk?: (chunk: MessageBinaryFormat) => void\n onComplete?: (finalContent: MessageBinaryFormat) => void\n onError?: (error: string) => void\n onChatData?: (chatData: any) => void\n}\n\n// Stream state manager - isolated from React lifecycle\nclass StreamStateManager {\n private content: MessageBinaryFormat = []\n private isStreaming: boolean = false\n private error?: string\n private isComplete: boolean = false\n private callbacks = new Set<() => void>()\n private processedStreams = new WeakSet<ReadableStream<Uint8Array>>()\n private cachedState: StreamingMessageState | null = null\n\n subscribe = (callback: () => void) => {\n this.callbacks.add(callback)\n return () => {\n this.callbacks.delete(callback)\n }\n }\n\n private notifySubscribers = () => {\n // Invalidate cached state when state changes\n this.cachedState = null\n this.callbacks.forEach((callback) => callback())\n }\n\n getState = (): StreamingMessageState => {\n // Return cached state to prevent infinite re-renders\n if (this.cachedState === null) {\n this.cachedState = {\n content: this.content,\n isStreaming: this.isStreaming,\n error: this.error,\n isComplete: this.isComplete,\n }\n }\n return this.cachedState\n }\n\n processStream = async (\n stream: ReadableStream<Uint8Array>,\n options: UseStreamingMessageOptions = {},\n ): Promise<void> => {\n // Prevent processing the same stream multiple times\n if (this.processedStreams.has(stream)) {\n return\n }\n\n // Handle locked streams gracefully\n if (stream.locked) {\n console.warn('Stream is locked, cannot process')\n return\n }\n\n this.processedStreams.add(stream)\n this.reset()\n this.setStreaming(true)\n\n try {\n await this.readStream(stream, options)\n } catch (err) {\n const errorMessage =\n err instanceof Error ? err.message : 'Unknown streaming error'\n this.setError(errorMessage)\n options.onError?.(errorMessage)\n } finally {\n this.setStreaming(false)\n }\n }\n\n private reset = () => {\n this.content = []\n this.isStreaming = false\n this.error = undefined\n this.isComplete = false\n this.notifySubscribers()\n }\n\n private setStreaming = (streaming: boolean) => {\n this.isStreaming = streaming\n this.notifySubscribers()\n }\n\n private setError = (error: string) => {\n this.error = error\n this.notifySubscribers()\n }\n\n private setComplete = (complete: boolean) => {\n this.isComplete = complete\n this.notifySubscribers()\n }\n\n private updateContent = (newContent: MessageBinaryFormat) => {\n this.content = [...newContent]\n this.notifySubscribers()\n }\n\n private readStream = async (\n stream: ReadableStream<Uint8Array>,\n options: UseStreamingMessageOptions,\n ): Promise<void> => {\n const reader = stream.getReader()\n const decoder = new TextDecoder()\n let buffer = ''\n let currentContent: MessageBinaryFormat = []\n\n try {\n while (true) {\n const { done, value } = await reader.read()\n if (done) {\n break\n }\n\n const chunk = decoder.decode(value, { stream: true })\n buffer += chunk\n const lines = buffer.split('\\n')\n buffer = lines.pop() || ''\n\n for (const line of lines) {\n if (line.trim() === '') {\n continue\n }\n\n // Handle SSE format (data: ...)\n let jsonData: string\n if (line.startsWith('data: ')) {\n jsonData = line.slice(6) // Remove \"data: \" prefix\n if (jsonData === '[DONE]') {\n this.setComplete(true)\n options.onComplete?.(currentContent)\n return\n }\n } else {\n // Handle raw JSON lines (fallback)\n jsonData = line\n }\n\n try {\n // Parse the JSON data\n const parsedData = JSON.parse(jsonData)\n\n // Handle v0 streaming format\n if (parsedData.type === 'connected') {\n continue\n } else if (parsedData.type === 'done') {\n this.setComplete(true)\n options.onComplete?.(currentContent)\n return\n } else if (\n parsedData.object &&\n parsedData.object.startsWith('chat')\n ) {\n // Handle chat metadata messages (chat, chat.title, chat.name, etc.)\n options.onChatData?.(parsedData)\n continue\n } else if (parsedData.delta) {\n // Apply the delta using jsondiffpatch\n const patchedContent = patch(currentContent, parsedData.delta)\n currentContent = Array.isArray(patchedContent)\n ? (patchedContent as MessageBinaryFormat)\n : []\n\n this.updateContent(currentContent)\n options.onChunk?.(currentContent)\n }\n } catch (e) {\n console.warn('Failed to parse streaming data:', line, e)\n }\n }\n }\n\n this.setComplete(true)\n options.onComplete?.(currentContent)\n } finally {\n reader.releaseLock()\n }\n }\n}\n\n/**\n * Hook for handling streaming message content from v0 API using useSyncExternalStore\n */\nexport function useStreamingMessage(\n stream: ReadableStream<Uint8Array> | null,\n options: UseStreamingMessageOptions = {},\n): StreamingMessageState {\n // Create a stable stream manager instance\n const managerRef = useRef<StreamStateManager | null>(null)\n if (!managerRef.current) {\n managerRef.current = new StreamStateManager()\n }\n\n const manager = managerRef.current\n\n // Subscribe to state changes using useSyncExternalStore\n const state = useSyncExternalStore(\n manager.subscribe,\n manager.getState,\n manager.getState,\n )\n\n // Process stream when it changes\n const lastStreamRef = useRef<ReadableStream<Uint8Array> | null>(null)\n\n if (stream !== lastStreamRef.current) {\n lastStreamRef.current = stream\n if (stream) {\n manager.processStream(stream, options)\n }\n }\n\n return state\n}\n","import React from 'react'\nimport { Message, useMessage, MessageData } from './message'\nimport {\n useStreamingMessage,\n UseStreamingMessageOptions,\n StreamingMessageState,\n} from '../hooks/use-streaming-message'\nimport { MessageProps } from '../types'\n\nexport interface StreamingMessageProps\n extends Omit<MessageProps, 'content' | 'streaming' | 'isLastMessage'>,\n UseStreamingMessageOptions {\n /**\n * The streaming response from v0.chats.create() with responseMode: 'experimental_stream'\n */\n stream: ReadableStream<Uint8Array> | null\n\n /**\n * Show a loading indicator while no content has been received yet\n */\n showLoadingIndicator?: boolean\n\n /**\n * Custom loading component\n */\n loadingComponent?: React.ReactNode\n\n /**\n * Custom error component\n */\n errorComponent?: (error: string) => React.ReactNode\n}\n\n// Headless streaming message data\nexport interface StreamingMessageData extends StreamingMessageState {\n messageData: MessageData | null\n}\n\n// Headless hook for streaming message\nexport function useStreamingMessageData({\n stream,\n messageId = 'unknown',\n role = 'assistant',\n components,\n renderers,\n onChunk,\n onComplete,\n onError,\n onChatData,\n}: Omit<\n StreamingMessageProps,\n 'className' | 'showLoadingIndicator' | 'loadingComponent' | 'errorComponent'\n>): StreamingMessageData {\n const streamingState = useStreamingMessage(stream, {\n onChunk,\n onComplete,\n onError,\n onChatData,\n })\n\n const messageData =\n streamingState.content.length > 0\n ? useMessage({\n content: streamingState.content,\n messageId,\n role,\n streaming: streamingState.isStreaming,\n isLastMessage: true,\n components,\n renderers,\n })\n : null\n\n return {\n ...streamingState,\n messageData,\n }\n}\n\n/**\n * Component for rendering streaming message content from v0 API\n *\n * For headless usage, use the useStreamingMessageData hook instead.\n *\n * @example\n * ```tsx\n * import { v0 } from 'v0-sdk'\n * import { StreamingMessage } from '@v0-sdk/react'\n *\n * function ChatDemo() {\n * const [stream, setStream] = useState<ReadableStream<Uint8Array> | null>(null)\n *\n * const handleSubmit = async () => {\n * const response = await v0.chats.create({\n * message: 'Create a button component',\n * responseMode: 'experimental_stream'\n * })\n * setStream(response)\n * }\n *\n * return (\n * <div>\n * <button onClick={handleSubmit}>Send Message</button>\n * {stream && (\n * <StreamingMessage\n * stream={stream}\n * messageId=\"demo-message\"\n * role=\"assistant\"\n * onComplete={(content) => handleCompletion(content)}\n * onChatData={(chatData) => handleChatData(chatData)}\n * />\n * )}\n * </div>\n * )\n * }\n * ```\n */\nexport function StreamingMessage({\n stream,\n showLoadingIndicator = true,\n loadingComponent,\n errorComponent,\n onChunk,\n onComplete,\n onError,\n onChatData,\n className,\n ...messageProps\n}: StreamingMessageProps) {\n const streamingData = useStreamingMessageData({\n stream,\n onChunk,\n onComplete,\n onError,\n onChatData,\n ...messageProps,\n })\n\n // Handle error state\n if (streamingData.error) {\n if (errorComponent) {\n return React.createElement(\n React.Fragment,\n {},\n errorComponent(streamingData.error),\n )\n }\n // Fallback error component using React.createElement for compatibility\n return React.createElement(\n 'div',\n {\n className: 'text-red-500 p-4 border border-red-200 rounded',\n style: {\n color: 'red',\n padding: '1rem',\n border: '1px solid #fecaca',\n borderRadius: '0.375rem',\n },\n },\n `Error: ${streamingData.error}`,\n )\n }\n\n // Handle loading state\n if (\n showLoadingIndicator &&\n streamingData.isStreaming &&\n streamingData.content.length === 0\n ) {\n if (loadingComponent) {\n return React.createElement(React.Fragment, {}, loadingComponent)\n }\n // Fallback loading component using React.createElement for compatibility\n return React.createElement(\n 'div',\n {\n className: 'flex items-center space-x-2 text-gray-500',\n style: {\n display: 'flex',\n alignItems: 'center',\n gap: '0.5rem',\n color: '#6b7280',\n },\n },\n React.createElement('div', {\n className:\n 'animate-spin h-4 w-4 border-2 border-gray-300 border-t-gray-600 rounded-full',\n style: {\n animation: 'spin 1s linear infinite',\n height: '1rem',\n width: '1rem',\n border: '2px solid #d1d5db',\n borderTopColor: '#4b5563',\n borderRadius: '50%',\n },\n }),\n React.createElement('span', {}, 'Loading...'),\n )\n }\n\n // Render the message content\n return React.createElement(Message, {\n ...messageProps,\n content: streamingData.content,\n streaming: streamingData.isStreaming,\n isLastMessage: true,\n className,\n })\n}\n","import React, { createContext, useContext } from 'react'\n\n// Context for providing custom icon implementation\nconst IconContext = createContext<React.ComponentType<IconProps> | null>(null)\n\nexport interface IconProps {\n name:\n | 'chevron-right'\n | 'chevron-down'\n | 'search'\n | 'folder'\n | 'settings'\n | 'file-text'\n | 'brain'\n | 'wrench'\n className?: string\n}\n\n// Headless icon data\nexport interface IconData {\n name: IconProps['name']\n fallback: string\n ariaLabel: string\n}\n\n// Headless hook for icon data\nexport function useIcon(props: IconProps): IconData {\n return {\n name: props.name,\n fallback: getIconFallback(props.name),\n ariaLabel: props.name.replace('-', ' '),\n }\n}\n\n/**\n * Generic icon component that can be customized by consumers.\n * By default, renders a simple fallback. Consumers should provide\n * their own icon implementation via context or props.\n *\n * For headless usage, use the useIcon hook instead.\n */\nexport function Icon(props: IconProps) {\n const CustomIcon = useContext(IconContext)\n\n // Use custom icon implementation if provided via context\n if (CustomIcon) {\n return React.createElement(CustomIcon, props)\n }\n\n const iconData = useIcon(props)\n\n // Fallback implementation - consumers should override this\n // This uses minimal DOM-specific attributes for maximum compatibility\n return React.createElement(\n 'span',\n {\n className: props.className,\n 'data-icon': iconData.name,\n 'aria-label': iconData.ariaLabel,\n // Note: suppressHydrationWarning removed for React Native compatibility\n },\n iconData.fallback,\n )\n}\n\n/**\n * Provider for custom icon implementation\n */\nexport function IconProvider({\n children,\n component,\n}: {\n children: React.ReactNode\n component: React.ComponentType<IconProps>\n}) {\n return React.createElement(\n IconContext.Provider,\n { value: component },\n children,\n )\n}\n\nfunction getIconFallback(name: string): string {\n const iconMap: Record<string, string> = {\n 'chevron-right': '▶',\n 'chevron-down': '▼',\n search: '🔍',\n folder: '📁',\n settings: '⚙️',\n 'file-text': '📄',\n brain: '🧠',\n wrench: '🔧',\n }\n return iconMap[name] || '•'\n}\n","import React, { useState } from 'react'\nimport { Icon, IconProps } from './icon'\n\nexport interface ThinkingSectionProps {\n title?: string\n duration?: number\n thought?: string\n collapsed?: boolean\n onCollapse?: () => void\n className?: string\n children?: React.ReactNode\n iconRenderer?: React.ComponentType<IconProps>\n brainIcon?: React.ReactNode\n chevronRightIcon?: React.ReactNode\n chevronDownIcon?: React.ReactNode\n}\n\n// Headless thinking section data\nexport interface ThinkingSectionData {\n title: string\n duration?: number\n thought?: string\n collapsed: boolean\n paragraphs: string[]\n formattedDuration?: string\n}\n\n// Headless hook for thinking section\nexport function useThinkingSection({\n title,\n duration,\n thought,\n collapsed: initialCollapsed = true,\n onCollapse,\n}: Omit<\n ThinkingSectionProps,\n | 'className'\n | 'children'\n | 'iconRenderer'\n | 'brainIcon'\n | 'chevronRightIcon'\n | 'chevronDownIcon'\n>): {\n data: ThinkingSectionData\n collapsed: boolean\n handleCollapse: () => void\n} {\n const [internalCollapsed, setInternalCollapsed] = useState(initialCollapsed)\n const collapsed = onCollapse ? initialCollapsed : internalCollapsed\n const handleCollapse =\n onCollapse || (() => setInternalCollapsed(!internalCollapsed))\n\n const paragraphs = thought ? thought.split('\\n\\n') : []\n const formattedDuration = duration ? `${Math.round(duration)}s` : undefined\n\n return {\n data: {\n title: title || 'Thinking',\n duration,\n thought,\n collapsed,\n paragraphs,\n formattedDuration,\n },\n collapsed,\n handleCollapse,\n }\n}\n\n/**\n * Generic thinking section component\n * Renders a collapsible section with basic structure - consumers provide styling\n *\n * For headless usage, use the useThinkingSection hook instead.\n */\nexport function ThinkingSection({\n title,\n duration,\n thought,\n collapsed: initialCollapsed = true,\n onCollapse,\n className,\n children,\n iconRenderer,\n brainIcon,\n chevronRightIcon,\n chevronDownIcon,\n}: ThinkingSectionProps) {\n const { data, collapsed, handleCollapse } = useThinkingSection({\n title,\n duration,\n thought,\n collapsed: initialCollapsed,\n onCollapse,\n })\n\n // If children provided, use that (allows complete customization)\n if (children) {\n return React.createElement(React.Fragment, {}, children)\n }\n\n // Uses React.createElement for maximum compatibility across environments\n return React.createElement(\n 'div',\n {\n className,\n 'data-component': 'thinking-section',\n },\n React.createElement(\n 'button',\n {\n onClick: handleCollapse,\n 'data-expanded': !collapsed,\n 'data-button': true,\n },\n React.createElement(\n 'div',\n { 'data-icon-container': true },\n collapsed\n ? React.createElement(\n React.Fragment,\n {},\n brainIcon ||\n (iconRenderer\n ? React.createElement(iconRenderer, { name: 'brain' })\n : React.createElement(Icon, { name: 'brain' })),\n chevronRightIcon ||\n (iconRenderer\n ? React.createElement(iconRenderer, { name: 'chevron-right' })\n : React.createElement(Icon, { name: 'chevron-right' })),\n )\n : chevronDownIcon ||\n (iconRenderer\n ? React.createElement(iconRenderer, { name: 'chevron-down' })\n : React.createElement(Icon, { name: 'chevron-down' })),\n ),\n React.createElement(\n 'span',\n { 'data-title': true },\n data.title +\n (data.formattedDuration ? ` for ${data.formattedDuration}` : ''),\n ),\n ),\n !collapsed && data.thought\n ? React.createElement(\n 'div',\n { 'data-content': true },\n React.createElement(\n 'div',\n { 'data-thought-container': true },\n data.paragraphs.map((paragraph, index) =>\n React.createElement(\n 'div',\n { key: index, 'data-paragraph': true },\n paragraph,\n ),\n ),\n ),\n )\n : null,\n )\n}\n","import React, { useState } from 'react'\nimport { Icon, IconProps } from './icon'\n\nexport interface TaskSectionProps {\n title?: string\n type?: string\n parts?: any[]\n collapsed?: boolean\n onCollapse?: () => void\n className?: string\n children?: React.ReactNode\n iconRenderer?: React.ComponentType<IconProps>\n taskIcon?: React.ReactNode\n chevronRightIcon?: React.ReactNode\n chevronDownIcon?: React.ReactNode\n}\n\n// Headless task section data\nexport interface TaskSectionData {\n title: string\n type?: string\n parts: any[]\n collapsed: boolean\n meaningfulParts: any[]\n shouldShowCollapsible: boolean\n iconName: IconProps['name']\n}\n\n// Headless task part data\nexport interface TaskPartData {\n type: string\n status?: string\n content: React.ReactNode\n isSearching?: boolean\n isAnalyzing?: boolean\n isComplete?: boolean\n query?: string\n count?: number\n answer?: string\n sources?: Array<{ url: string; title: string }>\n files?: string[]\n issues?: number\n}\n\nfunction getTypeIcon(type?: string, title?: string): IconProps['name'] {\n // Check title content for specific cases\n if (title?.includes('No issues found')) {\n return 'wrench'\n }\n if (title?.includes('Analyzed codebase')) {\n return 'search'\n }\n\n // Fallback to type-based icons\n switch (type) {\n case 'task-search-web-v1':\n return 'search'\n case 'task-search-repo-v1':\n return 'folder'\n case 'task-diagnostics-v1':\n return 'settings'\n case 'task-generate-design-inspiration-v1':\n return 'wrench'\n case 'task-read-file-v1':\n return 'folder'\n case 'task-coding-v1':\n return 'wrench'\n default:\n return 'wrench'\n }\n}\n\nfunction processTaskPart(part: any, index: number): TaskPartData {\n const baseData: TaskPartData = {\n type: part.type,\n status: part.status,\n content: null,\n }\n\n if (part.type === 'search-web') {\n if (part.status === 'searching') {\n return {\n ...baseData,\n isSearching: true,\n query: part.query,\n content: `Searching \"${part.query}\"`,\n }\n }\n if (part.status === 'analyzing') {\n return {\n ...baseData,\n isAnalyzing: true,\n count: part.count,\n content: `Analyzing ${part.count} results...`,\n }\n }\n if (part.status === 'complete' && part.answer) {\n return {\n ...baseData,\n isComplete: true,\n answer: part.answer,\n sources: part.sources,\n content: part.answer,\n }\n }\n }\n\n if (part.type === 'search-repo') {\n if (part.status === 'searching') {\n return {\n ...baseData,\n isSearching: true,\n query: part.query,\n content: `Searching \"${part.query}\"`,\n }\n }\n if (part.status === 'reading' && part.files) {\n return {\n ...baseData,\n files: part.files,\n content: 'Reading files',\n }\n }\n }\n\n if (part.type === 'diagnostics') {\n if (part.status === 'checking') {\n return {\n ...baseData,\n content: 'Checking for issues...',\n }\n }\n if (part.status === 'complete' && part.issues === 0) {\n return {\n ...baseData,\n isComplete: true,\n issues: part.issues,\n content: '✅ No issues found',\n }\n }\n }\n\n return {\n ...baseData,\n content: JSON.stringify(part),\n }\n}\n\nfunction renderTaskPartContent(\n partData: TaskPartData,\n index: number,\n iconRenderer?: React.ComponentType<IconProps>,\n): React.ReactNode {\n if (\n partData.type === 'search-web' &&\n partData.isComplete &&\n partData.sources\n ) {\n return React.createElement(\n 'div',\n { key: index },\n React.createElement('p', {}, partData.content),\n partData.sources.length > 0\n ? React.createElement(\n 'div',\n {},\n partData.sources.map((source, sourceIndex) =>\n React.createElement(\n 'a',\n {\n key: sourceIndex,\n href: source.url,\n target: '_blank',\n rel: 'noopener noreferrer',\n },\n source.title,\n ),\n ),\n )\n : null,\n )\n }\n\n if (partData.type === 'search-repo' && partData.files) {\n return React.createElement(\n 'div',\n { key: index },\n React.createElement('span', {}, partData.content),\n partData.files.map((file, fileIndex) =>\n React.createElement(\n 'span',\n { key: fileIndex },\n iconRenderer\n ? React.createElement(iconRenderer, { name: 'file-text' })\n : React.createElement(Icon, { name: 'file-text' }),\n ' ',\n file,\n ),\n ),\n )\n }\n\n return React.createElement('div', { key: index }, partData.content)\n}\n\n// Headless hook for task section\nexport function useTaskSection({\n title,\n type,\n parts = [],\n collapsed: initialCollapsed = true,\n onCollapse,\n}: Omit<\n TaskSectionProps,\n | 'className'\n | 'children'\n | 'iconRenderer'\n | 'taskIcon'\n | 'chevronRightIcon'\n | 'chevronDownIcon'\n>): {\n data: TaskSectionData\n collapsed: boolean\n handleCollapse: () => void\n processedParts: TaskPartData[]\n} {\n const [internalCollapsed, setInternalCollapsed] = useState(initialCollapsed)\n const collapsed = onCollapse ? initialCollapsed : internalCollapsed\n const handleCollapse =\n onCollapse || (() => setInternalCollapsed(!internalCollapsed))\n\n // Count meaningful parts (parts that would render something)\n const meaningfulParts = parts.filter((part) => {\n // Check if the part would render meaningful content\n if (part.type === 'search-web') {\n return (\n part.status === 'searching' ||\n part.status === 'analyzing' ||\n (part.status === 'complete' && part.answer)\n )\n }\n if (part.type === 'starting-repo-search' && part.query) return true\n if (part.type === 'select-files' && part.filePaths?.length > 0) return true\n if (part.type === 'starting-web-search' && part.query) return true\n if (part.type === 'got-results' && part.count) return true\n if (part.type === 'finished-web-search' && part.answer) return true\n if (part.type === 'diagnostics-passed') return true\n if (part.type === 'fetching-diagnostics') return true\n return false\n })\n\n const processedParts = parts.map(processTaskPart)\n\n return {\n data: {\n title: title || 'Task',\n type,\n parts,\n collapsed,\n meaningfulParts,\n shouldShowCollapsible: meaningfulParts.length > 1,\n iconName: getTypeIcon(type, title),\n },\n collapsed,\n handleCollapse,\n processedParts,\n }\n}\n\n/**\n * Generic task section component\n * Renders a collapsible task section with basic structure - consumers provide styling\n *\n * For headless usage, use the useTaskSection hook instead.\n */\nexport function TaskSection({\n title,\n type,\n parts = [],\n collapsed: initialCollapsed = true,\n onCollapse,\n className,\n children,\n iconRenderer,\n taskIcon,\n chevronRightIcon,\n chevronDownIcon,\n}: TaskSectionProps) {\n const { data, collapsed, handleCollapse, processedParts } = useTaskSection({\n title,\n type,\n parts,\n collapsed: initialCollapsed,\n onCollapse,\n })\n\n // If children provided, use that (allows complete customization)\n if (children) {\n return React.createElement(React.Fragment, {}, children)\n }\n\n // If there's only one meaningful part, show just the content without the collapsible wrapper\n if (!data.shouldShowCollapsible && data.meaningfulParts.length === 1) {\n const partData = processTaskPart(data.meaningfulParts[0], 0)\n return React.createElement(\n 'div',\n {\n className,\n 'data-component': 'task-section-inline',\n },\n React.createElement(\n 'div',\n { 'data-part': true },\n renderTaskPartContent(partData, 0, iconRenderer),\n ),\n )\n }\n\n // Uses React.createElement for maximum compatibility across environments\n return React.createElement(\n 'div',\n {\n className,\n 'data-component': 'task-section',\n },\n React.createElement(\n 'button',\n {\n onClick: handleCollapse,\n 'data-expanded': !collapsed,\n 'data-button': true,\n },\n React.createElement(\n 'div',\n { 'data-icon-container': true },\n React.createElement(\n 'div',\n { 'data-task-icon': true },\n taskIcon ||\n (iconRenderer\n ? React.createElement(iconRenderer, { name: data.iconName })\n : React.createElement(Icon, { name: data.iconName })),\n ),\n collapsed\n ? chevronRightIcon ||\n (iconRenderer\n ? React.createElement(iconRenderer, { name: 'chevron-right' })\n : React.createElement(Icon, { name: 'chevron-right' }))\n : chevronDownIcon ||\n (iconRenderer\n ? React.createElement(iconRenderer, { name: 'chevron-down' })\n : React.createElement(Icon, { name: 'chevron-down' })),\n ),\n React.createElement('span', { 'data-title': true }, data.title),\n ),\n !collapsed\n ? React.createElement(\n 'div',\n { 'data-content': true },\n React.createElement(\n 'div',\n { 'data-parts-container': true },\n processedParts.map((partData, index) =>\n React.createElement(\n 'div',\n { key: index, 'data-part': true },\n renderTaskPartContent(partData, index, iconRenderer),\n ),\n ),\n ),\n )\n : null,\n )\n}\n","import React, { useState } from 'react'\nimport { CodeBlock } from './code-block'\nimport { Icon, IconProps } from './icon'\n\nexport interface CodeProjectPartProps {\n title?: string\n filename?: string\n code?: string\n language?: string\n collapsed?: boolean\n className?: string\n children?: React.ReactNode\n iconRenderer?: React.ComponentType<IconProps>\n}\n\n// Headless code project data\nexport interface CodeProjectData {\n title: string\n filename?: string\n code?: string\n language: string\n collapsed: boolean\n files: Array<{\n name: string\n path: string\n active: boolean\n }>\n}\n\n// Headless hook for code project\nexport function useCodeProject({\n title,\n filename,\n code,\n language = 'typescript',\n collapsed: initialCollapsed = true,\n}: Omit<CodeProjectPartProps, 'className' | 'children' | 'iconRenderer'>): {\n data: CodeProjectData\n collapsed: boolean\n toggleCollapsed: () => void\n} {\n const [collapsed, setCollapsed] = useState(initialCollapsed)\n\n // Mock file structure - in a real implementation this could be dynamic\n const files = [\n {\n name: filename || 'page.tsx',\n path: 'app/page.tsx',\n active: true,\n },\n {\n name: 'layout.tsx',\n path: 'app/layout.tsx',\n active: false,\n },\n {\n name: 'globals.css',\n path: 'app/globals.css',\n active: false,\n },\n ]\n\n return {\n data: {\n title: title || 'Code Project',\n filename,\n code,\n language,\n collapsed,\n files,\n },\n collapsed,\n toggleCollapsed: () => setCollapsed(!collapsed),\n }\n}\n\n/**\n * Generic code project block component\n * Renders a collapsible code project with basic structure - consumers provide styling\n *\n * For headless usage, use the useCodeProject hook instead.\n */\nexport function CodeProjectPart({\n title,\n filename,\n code,\n language = 'typescript',\n collapsed: initialCollapsed = true,\n className,\n children,\n iconRenderer,\n}: CodeProjectPartProps) {\n const { data, collapsed, toggleCollapsed } = useCodeProject({\n title,\n filename,\n code,\n language,\n collapsed: initialCollapsed,\n })\n\n // If children provided, use that (allows complete customization)\n if (children) {\n return React.createElement(React.Fragment, {}, children)\n }\n\n // Uses React.createElement for maximum compatibility across environments\n return React.createElement(\n 'div',\n {\n className,\n 'data-component': 'code-project-block',\n },\n React.createElement(\n 'button',\n {\n onClick: toggleCollapsed,\n 'data-expanded': !collapsed,\n },\n React.createElement(\n 'div',\n { 'data-header': true },\n iconRenderer\n ? React.createElement(iconRenderer, { name: 'folder' })\n : React.createElement(Icon, { name: 'folder' }),\n React.createElement('span', { 'data-title': true }, data.title),\n ),\n React.createElement('span', { 'data-version': true }, 'v1'),\n ),\n !collapsed\n ? React.createElement(\n 'div',\n { 'data-content': true },\n React.createElement(\n 'div',\n { 'data-file-list': true },\n data.files.map((file, index) =>\n React.createElement(\n 'div',\n {\n key: index,\n 'data-file': true,\n ...(file.active && { 'data-active': true }),\n },\n iconRenderer\n ? React.createElement(iconRenderer, { name: 'file-text' })\n : React.createElement(Icon, { name: 'file-text' }),\n React.createElement(\n 'span',\n { 'data-filename': true },\n file.name,\n ),\n React.createElement(\n 'span',\n { 'data-filepath': true },\n file.path,\n ),\n ),\n ),\n ),\n data.code\n ? React.createElement(CodeBlock, {\n language: data.language,\n code: data.code,\n })\n : null,\n )\n : null,\n )\n}\n","import React, { useState } from 'react'\nimport { ThinkingSection } from './thinking-section'\nimport { TaskSection } from './task-section'\nimport { IconProps } from './icon'\n\nexport interface ContentPartRendererProps {\n part: any\n iconRenderer?: React.ComponentType<IconProps>\n thinkingSectionRenderer?: React.ComponentType<{\n title?: string\n duration?: number\n thought?: string\n collapsed?: boolean\n onCollapse?: () => void\n className?: string\n children?: React.ReactNode\n brainIcon?: React.ReactNode\n chevronRightIcon?: React.ReactNode\n chevronDownIcon?: React.ReactNode\n }>\n taskSectionRenderer?: React.ComponentType<{\n title?: string\n type?: string\n parts?: any[]\n collapsed?: boolean\n onCollapse?: () => void\n className?: string\n children?: React.ReactNode\n taskIcon?: React.ReactNode\n chevronRightIcon?: React.ReactNode\n chevronDownIcon?: React.ReactNode\n }>\n // Individual icon props for direct icon usage\n brainIcon?: React.ReactNode\n chevronRightIcon?: React.ReactNode\n chevronDownIcon?: React.ReactNode\n searchIcon?: React.ReactNode\n folderIcon?: React.ReactNode\n settingsIcon?: React.ReactNode\n wrenchIcon?: React.ReactNode\n}\n\n// Headless content part data\nexport interface ContentPartData {\n type: string\n parts: any[]\n metadata: Record<string, any>\n componentType: 'thinking' | 'task' | 'unknown' | null\n title?: string\n iconName?: IconProps['name']\n thinkingData?: {\n duration?: number\n thought?: string\n }\n}\n\n// Headless hook for content part\nexport function useContentPart(part: any): ContentPartData {\n if (!part) {\n return {\n type: '',\n parts: [],\n metadata: {},\n componentType: null,\n }\n }\n\n const { type, parts = [], ...metadata } = part\n\n let componentType: ContentPartData['componentType'] = 'unknown'\n let title: string | undefined\n let iconName: IconProps['name'] | undefined\n let thinkingData: ContentPartData['thinkingData']\n\n switch (type) {\n case 'task-thinking-v1':\n componentType = 'thinking'\n title = 'Thought'\n const thinkingPart = parts.find((p: any) => p.type === 'thinking-end')\n thinkingData = {\n duration: thinkingPart?.duration,\n thought: thinkingPart?.thought,\n }\n break\n\n case 'task-search-web-v1':\n componentType = 'task'\n title = metadata.taskNameComplete || metadata.taskNameActive\n iconName = 'search'\n break\n\n case 'task-search-repo-v1':\n componentType = 'task'\n title = metadata.taskNameComplete || metadata.taskNameActive\n iconName = 'folder'\n break\n\n case 'task-diagnostics-v1':\n componentType = 'task'\n title = metadata.taskNameComplete || metadata.taskNameActive\n iconName = 'settings'\n break\n\n case 'task-read-file-v1':\n componentType = 'task'\n title =\n metadata.taskNameComplete || metadata.taskNameActive || 'Reading file'\n iconName = 'folder'\n break\n\n case 'task-coding-v1':\n componentType = 'task'\n title = metadata.taskNameComplete || metadata.taskNameActive || 'Coding'\n iconName = 'wrench'\n break\n\n case 'task-start-v1':\n componentType = null // Usually just indicates task start - can be hidden\n break\n\n case 'task-generate-design-inspiration-v1':\n componentType = 'task'\n title =\n metadata.taskNameComplete ||\n metadata.taskNameActive ||\n 'Generating Design Inspiration'\n iconName = 'wrench'\n break\n\n // Handle any other task-*-v1 patterns that might be added in the future\n default:\n // Check if it's a task type we haven't explicitly handled yet\n if (\n type &&\n typeof type === 'string' &&\n type.startsWith('task-') &&\n type.endsWith('-v1')\n ) {\n componentType = 'task'\n // Generate a readable title from the task type\n const taskName = type\n .replace('task-', '')\n .replace('-v1', '')\n .split('-')\n .map((word: string) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(' ')\n title = metadata.taskNameComplete || metadata.taskNameActive || taskName\n iconName = 'wrench' // Default icon for unknown task types\n } else {\n componentType = 'unknown'\n }\n break\n }\n\n return {\n type,\n parts,\n metadata,\n componentType,\n title,\n iconName,\n thinkingData,\n }\n}\n\n/**\n * Content part renderer that handles different types of v0 API content parts\n *\n * For headless usage, use the useContentPart hook instead.\n */\nexport function ContentPartRenderer({\n part,\n iconRenderer,\n thinkingSectionRenderer,\n taskSectionRenderer,\n brainIcon,\n chevronRightIcon,\n chevronDownIcon,\n searchIcon,\n folderIcon,\n settingsIcon,\n wrenchIcon,\n}: ContentPartRendererProps) {\n const contentData = useContentPart(part)\n\n if (!contentData.componentType) {\n return null\n }\n\n if (contentData.componentType === 'thinking') {\n const ThinkingComponent = thinkingSectionRenderer || ThinkingSection\n const [collapsed, setCollapsed] = useState(true)\n\n return React.createElement(ThinkingComponent, {\n title: contentData.title,\n duration: contentData.thinkingData?.duration,\n thought: contentData.thinkingData?.thought,\n collapsed,\n onCollapse: () => setCollapsed(!collapsed),\n brainIcon,\n chevronRightIcon,\n chevronDownIcon,\n })\n }\n\n if (contentData.componentType === 'task') {\n const TaskComponent = taskSectionRenderer || TaskSection\n const [collapsed, setCollapsed] = useState(true)\n\n // Map icon names to icon components\n let taskIcon: React.ReactNode\n switch (contentData.iconName) {\n case 'search':\n taskIcon = searchIcon\n break\n case 'folder':\n taskIcon = folderIcon\n break\n case 'settings':\n taskIcon = settingsIcon\n break\n case 'wrench':\n taskIcon = wrenchIcon\n break\n default:\n taskIcon = undefined\n break\n }\n\n return React.createElement(TaskComponent, {\n title: contentData.title,\n type: contentData.type,\n parts: contentData.parts,\n collapsed,\n onCollapse: () => setCollapsed(!collapsed),\n taskIcon,\n chevronRightIcon,\n chevronDownIcon,\n })\n }\n\n if (contentData.componentType === 'unknown') {\n return React.createElement(\n 'div',\n { 'data-unknown-part-type': contentData.type },\n `Unknown part type: ${contentData.type}`,\n )\n }\n\n return null\n}\n","import React from 'react'\n\nexport interface MathPartProps {\n content: string\n inline?: boolean\n className?: string\n children?: React.ReactNode\n displayMode?: boolean\n}\n\n// Headless math data\nexport interface MathData {\n content: string\n inline: boolean\n displayMode: boolean\n processedContent: string\n}\n\n// Headless hook for math data\nexport function useMath(\n props: Omit<MathPartProps, 'className' | 'children'>,\n): MathData {\n return {\n content: props.content,\n inline: props.inline ?? false,\n displayMode: props.displayMode ?? !props.inline,\n processedContent: props.content, // Could be enhanced with math processing\n }\n}\n\n/**\n * Generic math renderer component\n * Renders plain math content by default - consumers should provide their own math rendering\n *\n * For headless usage, use the useMath hook instead.\n */\nexport function MathPart({\n content,\n inline = false,\n className = '',\n children,\n displayMode,\n}: MathPartProps) {\n // If children provided, use that (allows complete customization)\n if (children) {\n return React.createElement(React.Fragment, {}, children)\n }\n\n const mathData = useMath({ content, inline, displayMode })\n\n // Simple fallback - just render plain math content\n // Uses React.createElement for maximum compatibility across environments\n return React.createElement(\n mathData.inline ? 'span' : 'div',\n {\n className,\n 'data-math-inline': mathData.inline,\n 'data-math-display': mathData.displayMode,\n },\n mathData.processedContent,\n )\n}\n","import React from 'react'\n\nexport interface CodeBlockProps {\n language: string\n code: string\n className?: string\n children?: React.ReactNode\n filename?: string\n}\n\n// Headless code block data\nexport interface CodeBlockData {\n language: string\n code: string\n filename?: string\n lines: string[]\n lineCount: number\n}\n\n// Headless hook for code block data\nexport function useCodeBlock(\n props: Omit<CodeBlockProps, 'className' | 'children'>,\n): CodeBlockData {\n const lines = props.code.split('\\n')\n\n return {\n language: props.language,\n code: props.code,\n filename: props.filename,\n lines,\n lineCount: lines.length,\n }\n}\n\n/**\n * Generic code block component\n * Renders plain code by default - consumers should provide their own styling and highlighting\n *\n * For headless usage, use the useCodeBlock hook instead.\n */\nexport function CodeBlock({\n language,\n code,\n className = '',\n children,\n filename,\n}: CodeBlockProps) {\n // If children provided, use that (allows complete customization)\n if (children) {\n return React.createElement(React.Fragment, {}, children)\n }\n\n const codeBlockData = useCodeBlock({ language, code, filename })\n\n // Simple fallback - just render plain code\n // Uses React.createElement for maximum compatibility across environments\n return React.createElement(\n 'pre',\n {\n className,\n 'data-language': codeBlockData.language,\n ...(filename && { 'data-filename': filename }),\n },\n React.createElement('code', {}, codeBlockData.code),\n )\n}\n"],"names":[],"mappings":";;AAAA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACO;AACP;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACA;;AC5KA;AACP;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;;ACxBA;AACP;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;;ACZA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AC/EO;AACP;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;;AC3BO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;;ACnCO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;;ACtDO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;;ACpCO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AC9EO;AACP;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;;ACxBO;AACP;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;;;"}
|
package/dist/index.js
CHANGED
|
@@ -269,6 +269,12 @@ function getTypeIcon(type, title) {
|
|
|
269
269
|
return 'folder';
|
|
270
270
|
case 'task-diagnostics-v1':
|
|
271
271
|
return 'settings';
|
|
272
|
+
case 'task-generate-design-inspiration-v1':
|
|
273
|
+
return 'wrench';
|
|
274
|
+
case 'task-read-file-v1':
|
|
275
|
+
return 'folder';
|
|
276
|
+
case 'task-coding-v1':
|
|
277
|
+
return 'wrench';
|
|
272
278
|
default:
|
|
273
279
|
return 'wrench';
|
|
274
280
|
}
|
|
@@ -522,8 +528,23 @@ function useContentPart(part) {
|
|
|
522
528
|
case 'task-start-v1':
|
|
523
529
|
componentType = null; // Usually just indicates task start - can be hidden
|
|
524
530
|
break;
|
|
531
|
+
case 'task-generate-design-inspiration-v1':
|
|
532
|
+
componentType = 'task';
|
|
533
|
+
title = metadata.taskNameComplete || metadata.taskNameActive || 'Generating Design Inspiration';
|
|
534
|
+
iconName = 'wrench';
|
|
535
|
+
break;
|
|
536
|
+
// Handle any other task-*-v1 patterns that might be added in the future
|
|
525
537
|
default:
|
|
526
|
-
|
|
538
|
+
// Check if it's a task type we haven't explicitly handled yet
|
|
539
|
+
if (type && typeof type === 'string' && type.startsWith('task-') && type.endsWith('-v1')) {
|
|
540
|
+
componentType = 'task';
|
|
541
|
+
// Generate a readable title from the task type
|
|
542
|
+
const taskName = type.replace('task-', '').replace('-v1', '').split('-').map((word)=>word.charAt(0).toUpperCase() + word.slice(1)).join(' ');
|
|
543
|
+
title = metadata.taskNameComplete || metadata.taskNameActive || taskName;
|
|
544
|
+
iconName = 'wrench'; // Default icon for unknown task types
|
|
545
|
+
} else {
|
|
546
|
+
componentType = 'unknown';
|
|
547
|
+
}
|
|
527
548
|
break;
|
|
528
549
|
}
|
|
529
550
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@v0-sdk/react",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Headless React components for rendering v0 Platform API content",
|
|
5
5
|
"homepage": "https://v0.dev/docs/api",
|
|
6
6
|
"repository": {
|
|
@@ -21,11 +21,6 @@
|
|
|
21
21
|
"files": [
|
|
22
22
|
"dist"
|
|
23
23
|
],
|
|
24
|
-
"scripts": {
|
|
25
|
-
"type-check": "tsc --noEmit",
|
|
26
|
-
"build": "bunchee",
|
|
27
|
-
"format": "prettier --write \"**/*.{ts,tsx,md}\""
|
|
28
|
-
},
|
|
29
24
|
"peerDependencies": {
|
|
30
25
|
"react": "^18.0.0 || ^19.0.0"
|
|
31
26
|
},
|
|
@@ -55,5 +50,12 @@
|
|
|
55
50
|
},
|
|
56
51
|
"dependencies": {
|
|
57
52
|
"jsondiffpatch": "^0.7.3"
|
|
53
|
+
},
|
|
54
|
+
"scripts": {
|
|
55
|
+
"type-check": "tsc --noEmit",
|
|
56
|
+
"build": "bunchee",
|
|
57
|
+
"clean": "rm -rf dist *.tsbuildinfo",
|
|
58
|
+
"format": "prettier --write \"**/*.{ts,tsx,md}\"",
|
|
59
|
+
"lint": "echo 'No linting configured for react package'"
|
|
58
60
|
}
|
|
59
|
-
}
|
|
61
|
+
}
|