@v0-sdk/react 0.2.0 → 0.2.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 +1 -1
- package/dist/index.cjs +2 -15
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -15
- package/package.json +1 -1
package/README.md
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -780,7 +780,6 @@ class StreamStateManager {
|
|
|
780
780
|
this.processStream = async (stream, options = {})=>{
|
|
781
781
|
// Prevent processing the same stream multiple times
|
|
782
782
|
if (this.processedStreams.has(stream)) {
|
|
783
|
-
console.log('Stream already processed, skipping');
|
|
784
783
|
return;
|
|
785
784
|
}
|
|
786
785
|
// Handle locked streams gracefully
|
|
@@ -835,13 +834,11 @@ class StreamStateManager {
|
|
|
835
834
|
while(true){
|
|
836
835
|
const { done, value } = await reader.read();
|
|
837
836
|
if (done) {
|
|
838
|
-
console.log('Stream reading completed');
|
|
839
837
|
break;
|
|
840
838
|
}
|
|
841
839
|
const chunk = decoder.decode(value, {
|
|
842
840
|
stream: true
|
|
843
841
|
});
|
|
844
|
-
console.log('Received raw chunk:', chunk);
|
|
845
842
|
buffer += chunk;
|
|
846
843
|
const lines = buffer.split('\n');
|
|
847
844
|
buffer = lines.pop() || '';
|
|
@@ -849,13 +846,11 @@ class StreamStateManager {
|
|
|
849
846
|
if (line.trim() === '') {
|
|
850
847
|
continue;
|
|
851
848
|
}
|
|
852
|
-
console.log('Processing line:', line);
|
|
853
849
|
// Handle SSE format (data: ...)
|
|
854
850
|
let jsonData;
|
|
855
851
|
if (line.startsWith('data: ')) {
|
|
856
852
|
jsonData = line.slice(6); // Remove "data: " prefix
|
|
857
853
|
if (jsonData === '[DONE]') {
|
|
858
|
-
console.log('Stream marked as done via SSE');
|
|
859
854
|
this.setComplete(true);
|
|
860
855
|
options.onComplete?.(currentContent);
|
|
861
856
|
return;
|
|
@@ -867,28 +862,21 @@ class StreamStateManager {
|
|
|
867
862
|
try {
|
|
868
863
|
// Parse the JSON data
|
|
869
864
|
const parsedData = JSON.parse(jsonData);
|
|
870
|
-
console.log('Parsed data:', JSON.stringify(parsedData, null, 2));
|
|
871
865
|
// Handle v0 streaming format
|
|
872
866
|
if (parsedData.type === 'connected') {
|
|
873
|
-
console.log('Stream connected');
|
|
874
867
|
continue;
|
|
875
868
|
} else if (parsedData.type === 'done') {
|
|
876
|
-
console.log('Stream marked as done');
|
|
877
869
|
this.setComplete(true);
|
|
878
870
|
options.onComplete?.(currentContent);
|
|
879
871
|
return;
|
|
880
872
|
} else if (parsedData.object === 'chat' && parsedData.id) {
|
|
881
873
|
// Handle the initial chat data message
|
|
882
|
-
console.log('Received chat data:', parsedData.id);
|
|
883
874
|
options.onChatData?.(parsedData);
|
|
884
875
|
continue;
|
|
885
876
|
} else if (parsedData.delta) {
|
|
886
877
|
// Apply the delta using jsondiffpatch
|
|
887
|
-
console.log('Applying delta to content:', JSON.stringify(currentContent, null, 2));
|
|
888
|
-
console.log('Delta:', JSON.stringify(parsedData.delta, null, 2));
|
|
889
878
|
const patchedContent = patch(currentContent, parsedData.delta);
|
|
890
879
|
currentContent = Array.isArray(patchedContent) ? patchedContent : [];
|
|
891
|
-
console.log('Patched content result:', JSON.stringify(currentContent, null, 2));
|
|
892
880
|
this.updateContent(currentContent);
|
|
893
881
|
options.onChunk?.(currentContent);
|
|
894
882
|
}
|
|
@@ -921,7 +909,6 @@ class StreamStateManager {
|
|
|
921
909
|
if (stream !== lastStreamRef.current) {
|
|
922
910
|
lastStreamRef.current = stream;
|
|
923
911
|
if (stream) {
|
|
924
|
-
console.log('New stream detected, starting processing');
|
|
925
912
|
manager.processStream(stream, options);
|
|
926
913
|
}
|
|
927
914
|
}
|
|
@@ -955,8 +942,8 @@ class StreamStateManager {
|
|
|
955
942
|
* stream={stream}
|
|
956
943
|
* messageId="demo-message"
|
|
957
944
|
* role="assistant"
|
|
958
|
-
* onComplete={(content) =>
|
|
959
|
-
* onChatData={(chatData) =>
|
|
945
|
+
* onComplete={(content) => handleCompletion(content)}
|
|
946
|
+
* onChatData={(chatData) => handleChatData(chatData)}
|
|
960
947
|
* />
|
|
961
948
|
* )}
|
|
962
949
|
* </div>
|
package/dist/index.d.ts
CHANGED
|
@@ -245,8 +245,8 @@ interface StreamingMessageProps extends Omit<MessageProps, 'content' | 'streamin
|
|
|
245
245
|
* stream={stream}
|
|
246
246
|
* messageId="demo-message"
|
|
247
247
|
* role="assistant"
|
|
248
|
-
* onComplete={(content) =>
|
|
249
|
-
* onChatData={(chatData) =>
|
|
248
|
+
* onComplete={(content) => handleCompletion(content)}
|
|
249
|
+
* onChatData={(chatData) => handleChatData(chatData)}
|
|
250
250
|
* />
|
|
251
251
|
* )}
|
|
252
252
|
* </div>
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sources":["../src/types.ts","../src/components/message.tsx","../src/hooks/use-streaming-message.tsx","../src/components/streaming-message.tsx","../src/components/icon.tsx","../src/components/thinking-section.tsx","../src/components/task-section.tsx","../src/components/code-project-part.tsx","../src/components/content-part-renderer.tsx","../src/components/math-part.tsx","../src/components/code-block.tsx"],"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// Simplified renderer that matches v0's exact approach\nfunction MessageImpl({\n content,\n messageId = 'unknown',\n role: _role = 'assistant',\n streaming: _streaming = false,\n isLastMessage: _isLastMessage = false,\n className,\n components,\n renderers, // deprecated\n}: MessageProps) {\n if (!Array.isArray(content)) {\n console.warn(\n 'MessageContent: content must be an array (MessageBinaryFormat)',\n )\n return null\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.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 <Elements key={key} data={data} components={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\n return <div className={className}>{elements}</div>\n}\n\n// This component handles the markdown data array (equivalent to v0's Elements component)\nfunction Elements({\n data,\n components,\n}: {\n data: any\n components?: MessageProps['components']\n}) {\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 renderedElements = data\n .map((item, index) => {\n const key = `element-${index}`\n return renderElement(item, key, components)\n })\n .filter(Boolean) // Filter out null/undefined elements\n\n return <>{renderedElements}</>\n}\n\n// Render individual elements (equivalent to v0's element rendering logic)\nfunction renderElement(\n element: any,\n key: string,\n components?: MessageProps['components'],\n): React.ReactNode {\n if (typeof element === 'string') {\n return <span key={key}>{element}</span>\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 <ContentPartRenderer\n key={key}\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 const CustomCodeProjectPart = components?.CodeProjectPart\n const CodeProjectComponent = CustomCodeProjectPart || CodeProjectPart\n return (\n <CodeProjectComponent\n key={key}\n language={props.lang}\n code={children[0]}\n iconRenderer={components?.Icon}\n />\n )\n }\n\n if (tagName === 'text') {\n return <span key={key}>{children[0] || ''}</span>\n }\n\n // Render children\n const renderedChildren = children\n .map((child, childIndex) => {\n const childKey = `${key}-child-${childIndex}`\n return renderElement(child, childKey, components)\n })\n .filter(Boolean)\n\n // Handle standard HTML elements\n const className = props?.className\n const componentOrConfig = components?.[tagName as keyof typeof components]\n\n if (typeof componentOrConfig === 'function') {\n const Component = componentOrConfig\n return (\n <Component key={key} {...props} className={className}>\n {renderedChildren}\n </Component>\n )\n } else if (componentOrConfig && typeof componentOrConfig === 'object') {\n const mergedClassName = cn(className, componentOrConfig.className)\n return React.createElement(\n tagName,\n { key, ...props, className: mergedClassName },\n renderedChildren,\n )\n } else {\n // Default HTML element rendering\n const elementProps: Record<string, any> = { key, ...props }\n if (className) {\n elementProps.className = 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\n/**\n * Main component for rendering v0 Platform API message content\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 console.log('Stream already processed, skipping')\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 console.log('Stream reading completed')\n break\n }\n\n const chunk = decoder.decode(value, { stream: true })\n console.log('Received raw chunk:', chunk)\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 console.log('Processing line:', line)\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 console.log('Stream marked as done via SSE')\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 console.log('Parsed data:', JSON.stringify(parsedData, null, 2))\n\n // Handle v0 streaming format\n if (parsedData.type === 'connected') {\n console.log('Stream connected')\n continue\n } else if (parsedData.type === 'done') {\n console.log('Stream marked as done')\n this.setComplete(true)\n options.onComplete?.(currentContent)\n return\n } else if (parsedData.object === 'chat' && parsedData.id) {\n // Handle the initial chat data message\n console.log('Received chat data:', parsedData.id)\n options.onChatData?.(parsedData)\n continue\n } else if (parsedData.delta) {\n // Apply the delta using jsondiffpatch\n console.log(\n 'Applying delta to content:',\n JSON.stringify(currentContent, null, 2),\n )\n console.log('Delta:', JSON.stringify(parsedData.delta, null, 2))\n const patchedContent = patch(currentContent, parsedData.delta)\n currentContent = Array.isArray(patchedContent)\n ? (patchedContent as MessageBinaryFormat)\n : []\n console.log(\n 'Patched content result:',\n JSON.stringify(currentContent, null, 2),\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 console.log('New stream detected, starting processing')\n manager.processStream(stream, options)\n }\n }\n\n return state\n}\n","import React from 'react'\nimport { Message } from './message'\nimport {\n useStreamingMessage,\n UseStreamingMessageOptions,\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/**\n * Component for rendering streaming message content from v0 API\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) => console.log('Stream complete:', content)}\n * onChatData={(chatData) => console.log('Chat created:', chatData.id)}\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 ...messageProps\n}: StreamingMessageProps) {\n const { content, isStreaming, error, isComplete } = useStreamingMessage(\n stream,\n {\n onChunk,\n onComplete,\n onError,\n onChatData,\n },\n )\n\n // Handle error state\n if (error) {\n if (errorComponent) {\n return <>{errorComponent(error)}</>\n }\n return (\n <div className=\"text-red-500 p-4 border border-red-200 rounded\">\n Error: {error}\n </div>\n )\n }\n\n // Handle loading state\n if (showLoadingIndicator && isStreaming && content.length === 0) {\n if (loadingComponent) {\n return <>{loadingComponent}</>\n }\n return (\n <div className=\"flex items-center space-x-2 text-gray-500\">\n <div className=\"animate-spin h-4 w-4 border-2 border-gray-300 border-t-gray-600 rounded-full\"></div>\n <span>Loading...</span>\n </div>\n )\n }\n\n // Render the message content\n return (\n <Message\n {...messageProps}\n content={content}\n streaming={isStreaming}\n isLastMessage={true}\n />\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/**\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 */\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 <CustomIcon {...props} />\n }\n\n // Fallback implementation - consumers should override this\n return (\n <span\n className={props.className}\n data-icon={props.name}\n suppressHydrationWarning\n aria-label={props.name.replace('-', ' ')}\n >\n {getIconFallback(props.name)}\n </span>\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 (\n <IconContext.Provider value={component}>{children}</IconContext.Provider>\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/**\n * Generic thinking section component\n * Renders a collapsible section with basic structure - consumers provide styling\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 [internalCollapsed, setInternalCollapsed] = useState(initialCollapsed)\n const collapsed = onCollapse ? initialCollapsed : internalCollapsed\n const handleCollapse =\n onCollapse || (() => setInternalCollapsed(!internalCollapsed))\n\n // If children provided, use that (allows complete customization)\n if (children) {\n return <>{children}</>\n }\n\n return (\n <div className={className} data-component=\"thinking-section\">\n <button onClick={handleCollapse} data-expanded={!collapsed} data-button>\n <div data-icon-container>\n {collapsed ? (\n <>\n {brainIcon ||\n (iconRenderer ? (\n React.createElement(iconRenderer, { name: 'brain' })\n ) : (\n <Icon name=\"brain\" />\n ))}\n {chevronRightIcon ||\n (iconRenderer ? (\n React.createElement(iconRenderer, { name: 'chevron-right' })\n ) : (\n <Icon name=\"chevron-right\" />\n ))}\n </>\n ) : (\n chevronDownIcon ||\n (iconRenderer ? (\n React.createElement(iconRenderer, { name: 'chevron-down' })\n ) : (\n <Icon name=\"chevron-down\" />\n ))\n )}\n </div>\n <span data-title>\n {title || 'Thinking'}\n {duration && ` for ${Math.round(duration)}s`}\n </span>\n </button>\n {!collapsed && thought && (\n <div data-content>\n <div data-thought-container>\n {thought.split('\\n\\n').map((paragraph, index) => (\n <div key={index} data-paragraph>\n {paragraph}\n </div>\n ))}\n </div>\n </div>\n )}\n </div>\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\nfunction getTypeIcon(\n type?: string,\n title?: string,\n iconRenderer?: React.ComponentType<IconProps>,\n) {\n // Check title content for specific cases\n if (title?.includes('No issues found')) {\n return iconRenderer ? (\n React.createElement(iconRenderer, { name: 'wrench' })\n ) : (\n <Icon name=\"wrench\" />\n )\n }\n if (title?.includes('Analyzed codebase')) {\n return iconRenderer ? (\n React.createElement(iconRenderer, { name: 'search' })\n ) : (\n <Icon name=\"search\" />\n )\n }\n\n // Fallback to type-based icons\n switch (type) {\n case 'task-search-web-v1':\n return iconRenderer ? (\n React.createElement(iconRenderer, { name: 'search' })\n ) : (\n <Icon name=\"search\" />\n )\n case 'task-search-repo-v1':\n return iconRenderer ? (\n React.createElement(iconRenderer, { name: 'folder' })\n ) : (\n <Icon name=\"folder\" />\n )\n case 'task-diagnostics-v1':\n return iconRenderer ? (\n React.createElement(iconRenderer, { name: 'settings' })\n ) : (\n <Icon name=\"settings\" />\n )\n default:\n return iconRenderer ? (\n React.createElement(iconRenderer, { name: 'wrench' })\n ) : (\n <Icon name=\"wrench\" />\n )\n }\n}\n\nfunction renderTaskPart(\n part: any,\n index: number,\n iconRenderer?: React.ComponentType<IconProps>,\n) {\n if (part.type === 'search-web') {\n if (part.status === 'searching') {\n return <div key={index}>{`Searching \"${part.query}\"`}</div>\n }\n if (part.status === 'analyzing') {\n return <div key={index}>{`Analyzing ${part.count} results...`}</div>\n }\n if (part.status === 'complete' && part.answer) {\n return (\n <div key={index}>\n <p>{part.answer}</p>\n {part.sources && part.sources.length > 0 && (\n <div>\n {part.sources.map((source: any, sourceIndex: number) => (\n <a\n key={sourceIndex}\n href={source.url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n {source.title}\n </a>\n ))}\n </div>\n )}\n </div>\n )\n }\n }\n\n if (part.type === 'search-repo') {\n if (part.status === 'searching') {\n return <div key={index}>{`Searching \"${part.query}\"`}</div>\n }\n if (part.status === 'reading' && part.files) {\n return (\n <div key={index}>\n <span>Reading files</span>\n {part.files.map((file: string, fileIndex: number) => (\n <span key={fileIndex}>\n {iconRenderer ? (\n React.createElement(iconRenderer, { name: 'file-text' })\n ) : (\n <Icon name=\"file-text\" />\n )}{' '}\n {file}\n </span>\n ))}\n </div>\n )\n }\n }\n\n if (part.type === 'diagnostics') {\n if (part.status === 'checking') {\n return <div key={index}>Checking for issues...</div>\n }\n if (part.status === 'complete' && part.issues === 0) {\n return <div key={index}>✅ No issues found</div>\n }\n }\n\n return <div key={index}>{JSON.stringify(part)}</div>\n}\n\n/**\n * Generic task section component\n * Renders a collapsible task section with basic structure - consumers provide styling\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 [internalCollapsed, setInternalCollapsed] = useState(initialCollapsed)\n const collapsed = onCollapse ? initialCollapsed : internalCollapsed\n const handleCollapse =\n onCollapse || (() => setInternalCollapsed(!internalCollapsed))\n\n // If children provided, use that (allows complete customization)\n if (children) {\n return <>{children}</>\n }\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 // Add more meaningful part types as needed\n return false\n })\n\n // If there's only one meaningful part, show just the content without the collapsible wrapper\n if (meaningfulParts.length === 1) {\n return (\n <div className={className} data-component=\"task-section-inline\">\n <div data-part>\n {renderTaskPart(meaningfulParts[0], 0, iconRenderer)}\n </div>\n </div>\n )\n }\n\n return (\n <div className={className} data-component=\"task-section\">\n <button onClick={handleCollapse} data-expanded={!collapsed} data-button>\n <div data-icon-container>\n <div data-task-icon>\n {taskIcon || getTypeIcon(type, title, iconRenderer)}\n </div>\n {collapsed\n ? chevronRightIcon ||\n (iconRenderer ? (\n React.createElement(iconRenderer, { name: 'chevron-right' })\n ) : (\n <Icon name=\"chevron-right\" />\n ))\n : chevronDownIcon ||\n (iconRenderer ? (\n React.createElement(iconRenderer, { name: 'chevron-down' })\n ) : (\n <Icon name=\"chevron-down\" />\n ))}\n </div>\n <span data-title>{title || 'Task'}</span>\n </button>\n {!collapsed && (\n <div data-content>\n <div data-parts-container>\n {parts.map((part, index) => (\n <div key={index} data-part>\n {renderTaskPart(part, index, iconRenderer)}\n </div>\n ))}\n </div>\n </div>\n )}\n </div>\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/**\n * Generic code project block component\n * Renders a collapsible code project with basic structure - consumers provide styling\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 [collapsed, setCollapsed] = useState(initialCollapsed)\n\n // If children provided, use that (allows complete customization)\n if (children) {\n return <>{children}</>\n }\n\n return (\n <div className={className} data-component=\"code-project-block\">\n <button\n onClick={() => setCollapsed(!collapsed)}\n data-expanded={!collapsed}\n >\n <div data-header>\n {iconRenderer ? (\n React.createElement(iconRenderer, { name: 'folder' })\n ) : (\n <Icon name=\"folder\" />\n )}\n <span data-title>{title || 'Code Project'}</span>\n </div>\n <span data-version>v1</span>\n </button>\n {!collapsed && (\n <div data-content>\n <div data-file-list>\n <div data-file data-active>\n {iconRenderer ? (\n React.createElement(iconRenderer, { name: 'file-text' })\n ) : (\n <Icon name=\"file-text\" />\n )}\n <span data-filename>{filename}</span>\n <span data-filepath>app/page.tsx</span>\n </div>\n {/* Additional files could be added here */}\n <div data-file>\n {iconRenderer ? (\n React.createElement(iconRenderer, { name: 'file-text' })\n ) : (\n <Icon name=\"file-text\" />\n )}\n <span data-filename>layout.tsx</span>\n <span data-filepath>app/layout.tsx</span>\n </div>\n <div data-file>\n {iconRenderer ? (\n React.createElement(iconRenderer, { name: 'file-text' })\n ) : (\n <Icon name=\"file-text\" />\n )}\n <span data-filename>globals.css</span>\n <span data-filepath>app/globals.css</span>\n </div>\n </div>\n {code && <CodeBlock language={language} code={code} />}\n </div>\n )}\n </div>\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 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 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\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 if (!part) return null\n\n const { type, parts = [], ...metadata } = part\n\n switch (type) {\n case 'task-thinking-v1': {\n const thinkingPart = parts.find((p: any) => p.type === 'thinking-end')\n const ThinkingComponent = thinkingSectionRenderer || ThinkingSection\n const [collapsed, setCollapsed] = useState(true)\n\n return (\n <ThinkingComponent\n title=\"Thought\"\n duration={thinkingPart?.duration}\n thought={thinkingPart?.thought}\n collapsed={collapsed}\n onCollapse={() => setCollapsed(!collapsed)}\n brainIcon={brainIcon}\n chevronRightIcon={chevronRightIcon}\n chevronDownIcon={chevronDownIcon}\n />\n )\n }\n\n case 'task-search-web-v1': {\n const TaskComponent = taskSectionRenderer || TaskSection\n const [collapsed, setCollapsed] = useState(true)\n\n return (\n <TaskComponent\n title={metadata.taskNameComplete || metadata.taskNameActive}\n type={type}\n parts={parts}\n collapsed={collapsed}\n onCollapse={() => setCollapsed(!collapsed)}\n taskIcon={searchIcon}\n chevronRightIcon={chevronRightIcon}\n chevronDownIcon={chevronDownIcon}\n />\n )\n }\n\n case 'task-search-repo-v1': {\n const TaskComponent = taskSectionRenderer || TaskSection\n const [collapsed, setCollapsed] = useState(true)\n\n return (\n <TaskComponent\n title={metadata.taskNameComplete || metadata.taskNameActive}\n type={type}\n parts={parts}\n collapsed={collapsed}\n onCollapse={() => setCollapsed(!collapsed)}\n taskIcon={folderIcon}\n chevronRightIcon={chevronRightIcon}\n chevronDownIcon={chevronDownIcon}\n />\n )\n }\n\n case 'task-diagnostics-v1': {\n const TaskComponent = taskSectionRenderer || TaskSection\n const [collapsed, setCollapsed] = useState(true)\n\n return (\n <TaskComponent\n title={metadata.taskNameComplete || metadata.taskNameActive}\n type={type}\n parts={parts}\n collapsed={collapsed}\n onCollapse={() => setCollapsed(!collapsed)}\n taskIcon={settingsIcon}\n chevronRightIcon={chevronRightIcon}\n chevronDownIcon={chevronDownIcon}\n />\n )\n }\n\n case 'task-read-file-v1': {\n const TaskComponent = taskSectionRenderer || TaskSection\n const [collapsed, setCollapsed] = useState(true)\n\n return (\n <TaskComponent\n title={\n metadata.taskNameComplete ||\n metadata.taskNameActive ||\n 'Reading file'\n }\n type={type}\n parts={parts}\n collapsed={collapsed}\n onCollapse={() => setCollapsed(!collapsed)}\n taskIcon={folderIcon}\n chevronRightIcon={chevronRightIcon}\n chevronDownIcon={chevronDownIcon}\n />\n )\n }\n\n case 'task-coding-v1': {\n const TaskComponent = taskSectionRenderer || TaskSection\n const [collapsed, setCollapsed] = useState(true)\n\n return (\n <TaskComponent\n title={\n metadata.taskNameComplete || metadata.taskNameActive || 'Coding'\n }\n type={type}\n parts={parts}\n collapsed={collapsed}\n onCollapse={() => setCollapsed(!collapsed)}\n taskIcon={wrenchIcon}\n chevronRightIcon={chevronRightIcon}\n chevronDownIcon={chevronDownIcon}\n />\n )\n }\n\n case 'task-start-v1':\n // Usually just indicates task start - can be hidden or show as status\n return null\n\n default:\n return <div data-unknown-part-type={type}>Unknown part type: {type}</div>\n }\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/**\n * Generic math renderer component\n * Renders plain math content by default - consumers should provide their own math rendering\n */\nexport function MathPart({\n content,\n inline = false,\n className = '',\n children,\n}: MathPartProps) {\n // If children provided, use that (allows complete customization)\n if (children) {\n return <>{children}</>\n }\n\n // Simple fallback - just render plain math content\n const Element = inline ? 'span' : 'div'\n\n return (\n <Element className={className} data-math-inline={inline}>\n {content}\n </Element>\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/**\n * Generic code block component\n * Renders plain code by default - consumers should provide their own styling and highlighting\n */\nexport function CodeBlock({\n language,\n code,\n className = '',\n children,\n}: CodeBlockProps) {\n // If children provided, use that (allows complete customization)\n if (children) {\n return <>{children}</>\n }\n\n // Simple fallback - just render plain code\n return (\n <pre className={className} data-language={language}>\n <code>{code}</code>\n </pre>\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;;AC5KP;AACA;AACA;AACA;AACO;;ACLA;AACP;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;;ACbA;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;AACO;;ACxDA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;;ACRA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;;ACjBA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;;ACjBA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;;ACdA;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;AACO;;AClCA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;;ACXA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;;;"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sources":["../src/types.ts","../src/components/message.tsx","../src/hooks/use-streaming-message.tsx","../src/components/streaming-message.tsx","../src/components/icon.tsx","../src/components/thinking-section.tsx","../src/components/task-section.tsx","../src/components/code-project-part.tsx","../src/components/content-part-renderer.tsx","../src/components/math-part.tsx","../src/components/code-block.tsx"],"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// Simplified renderer that matches v0's exact approach\nfunction MessageImpl({\n content,\n messageId = 'unknown',\n role: _role = 'assistant',\n streaming: _streaming = false,\n isLastMessage: _isLastMessage = false,\n className,\n components,\n renderers, // deprecated\n}: MessageProps) {\n if (!Array.isArray(content)) {\n console.warn(\n 'MessageContent: content must be an array (MessageBinaryFormat)',\n )\n return null\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.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 <Elements key={key} data={data} components={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\n return <div className={className}>{elements}</div>\n}\n\n// This component handles the markdown data array (equivalent to v0's Elements component)\nfunction Elements({\n data,\n components,\n}: {\n data: any\n components?: MessageProps['components']\n}) {\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 renderedElements = data\n .map((item, index) => {\n const key = `element-${index}`\n return renderElement(item, key, components)\n })\n .filter(Boolean) // Filter out null/undefined elements\n\n return <>{renderedElements}</>\n}\n\n// Render individual elements (equivalent to v0's element rendering logic)\nfunction renderElement(\n element: any,\n key: string,\n components?: MessageProps['components'],\n): React.ReactNode {\n if (typeof element === 'string') {\n return <span key={key}>{element}</span>\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 <ContentPartRenderer\n key={key}\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 const CustomCodeProjectPart = components?.CodeProjectPart\n const CodeProjectComponent = CustomCodeProjectPart || CodeProjectPart\n return (\n <CodeProjectComponent\n key={key}\n language={props.lang}\n code={children[0]}\n iconRenderer={components?.Icon}\n />\n )\n }\n\n if (tagName === 'text') {\n return <span key={key}>{children[0] || ''}</span>\n }\n\n // Render children\n const renderedChildren = children\n .map((child, childIndex) => {\n const childKey = `${key}-child-${childIndex}`\n return renderElement(child, childKey, components)\n })\n .filter(Boolean)\n\n // Handle standard HTML elements\n const className = props?.className\n const componentOrConfig = components?.[tagName as keyof typeof components]\n\n if (typeof componentOrConfig === 'function') {\n const Component = componentOrConfig\n return (\n <Component key={key} {...props} className={className}>\n {renderedChildren}\n </Component>\n )\n } else if (componentOrConfig && typeof componentOrConfig === 'object') {\n const mergedClassName = cn(className, componentOrConfig.className)\n return React.createElement(\n tagName,\n { key, ...props, className: mergedClassName },\n renderedChildren,\n )\n } else {\n // Default HTML element rendering\n const elementProps: Record<string, any> = { key, ...props }\n if (className) {\n elementProps.className = 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\n/**\n * Main component for rendering v0 Platform API message content\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 (parsedData.object === 'chat' && parsedData.id) {\n // Handle the initial chat data message\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 } from './message'\nimport {\n useStreamingMessage,\n UseStreamingMessageOptions,\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/**\n * Component for rendering streaming message content from v0 API\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 ...messageProps\n}: StreamingMessageProps) {\n const { content, isStreaming, error, isComplete } = useStreamingMessage(\n stream,\n {\n onChunk,\n onComplete,\n onError,\n onChatData,\n },\n )\n\n // Handle error state\n if (error) {\n if (errorComponent) {\n return <>{errorComponent(error)}</>\n }\n return (\n <div className=\"text-red-500 p-4 border border-red-200 rounded\">\n Error: {error}\n </div>\n )\n }\n\n // Handle loading state\n if (showLoadingIndicator && isStreaming && content.length === 0) {\n if (loadingComponent) {\n return <>{loadingComponent}</>\n }\n return (\n <div className=\"flex items-center space-x-2 text-gray-500\">\n <div className=\"animate-spin h-4 w-4 border-2 border-gray-300 border-t-gray-600 rounded-full\"></div>\n <span>Loading...</span>\n </div>\n )\n }\n\n // Render the message content\n return (\n <Message\n {...messageProps}\n content={content}\n streaming={isStreaming}\n isLastMessage={true}\n />\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/**\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 */\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 <CustomIcon {...props} />\n }\n\n // Fallback implementation - consumers should override this\n return (\n <span\n className={props.className}\n data-icon={props.name}\n suppressHydrationWarning\n aria-label={props.name.replace('-', ' ')}\n >\n {getIconFallback(props.name)}\n </span>\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 (\n <IconContext.Provider value={component}>{children}</IconContext.Provider>\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/**\n * Generic thinking section component\n * Renders a collapsible section with basic structure - consumers provide styling\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 [internalCollapsed, setInternalCollapsed] = useState(initialCollapsed)\n const collapsed = onCollapse ? initialCollapsed : internalCollapsed\n const handleCollapse =\n onCollapse || (() => setInternalCollapsed(!internalCollapsed))\n\n // If children provided, use that (allows complete customization)\n if (children) {\n return <>{children}</>\n }\n\n return (\n <div className={className} data-component=\"thinking-section\">\n <button onClick={handleCollapse} data-expanded={!collapsed} data-button>\n <div data-icon-container>\n {collapsed ? (\n <>\n {brainIcon ||\n (iconRenderer ? (\n React.createElement(iconRenderer, { name: 'brain' })\n ) : (\n <Icon name=\"brain\" />\n ))}\n {chevronRightIcon ||\n (iconRenderer ? (\n React.createElement(iconRenderer, { name: 'chevron-right' })\n ) : (\n <Icon name=\"chevron-right\" />\n ))}\n </>\n ) : (\n chevronDownIcon ||\n (iconRenderer ? (\n React.createElement(iconRenderer, { name: 'chevron-down' })\n ) : (\n <Icon name=\"chevron-down\" />\n ))\n )}\n </div>\n <span data-title>\n {title || 'Thinking'}\n {duration && ` for ${Math.round(duration)}s`}\n </span>\n </button>\n {!collapsed && thought && (\n <div data-content>\n <div data-thought-container>\n {thought.split('\\n\\n').map((paragraph, index) => (\n <div key={index} data-paragraph>\n {paragraph}\n </div>\n ))}\n </div>\n </div>\n )}\n </div>\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\nfunction getTypeIcon(\n type?: string,\n title?: string,\n iconRenderer?: React.ComponentType<IconProps>,\n) {\n // Check title content for specific cases\n if (title?.includes('No issues found')) {\n return iconRenderer ? (\n React.createElement(iconRenderer, { name: 'wrench' })\n ) : (\n <Icon name=\"wrench\" />\n )\n }\n if (title?.includes('Analyzed codebase')) {\n return iconRenderer ? (\n React.createElement(iconRenderer, { name: 'search' })\n ) : (\n <Icon name=\"search\" />\n )\n }\n\n // Fallback to type-based icons\n switch (type) {\n case 'task-search-web-v1':\n return iconRenderer ? (\n React.createElement(iconRenderer, { name: 'search' })\n ) : (\n <Icon name=\"search\" />\n )\n case 'task-search-repo-v1':\n return iconRenderer ? (\n React.createElement(iconRenderer, { name: 'folder' })\n ) : (\n <Icon name=\"folder\" />\n )\n case 'task-diagnostics-v1':\n return iconRenderer ? (\n React.createElement(iconRenderer, { name: 'settings' })\n ) : (\n <Icon name=\"settings\" />\n )\n default:\n return iconRenderer ? (\n React.createElement(iconRenderer, { name: 'wrench' })\n ) : (\n <Icon name=\"wrench\" />\n )\n }\n}\n\nfunction renderTaskPart(\n part: any,\n index: number,\n iconRenderer?: React.ComponentType<IconProps>,\n) {\n if (part.type === 'search-web') {\n if (part.status === 'searching') {\n return <div key={index}>{`Searching \"${part.query}\"`}</div>\n }\n if (part.status === 'analyzing') {\n return <div key={index}>{`Analyzing ${part.count} results...`}</div>\n }\n if (part.status === 'complete' && part.answer) {\n return (\n <div key={index}>\n <p>{part.answer}</p>\n {part.sources && part.sources.length > 0 && (\n <div>\n {part.sources.map((source: any, sourceIndex: number) => (\n <a\n key={sourceIndex}\n href={source.url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n {source.title}\n </a>\n ))}\n </div>\n )}\n </div>\n )\n }\n }\n\n if (part.type === 'search-repo') {\n if (part.status === 'searching') {\n return <div key={index}>{`Searching \"${part.query}\"`}</div>\n }\n if (part.status === 'reading' && part.files) {\n return (\n <div key={index}>\n <span>Reading files</span>\n {part.files.map((file: string, fileIndex: number) => (\n <span key={fileIndex}>\n {iconRenderer ? (\n React.createElement(iconRenderer, { name: 'file-text' })\n ) : (\n <Icon name=\"file-text\" />\n )}{' '}\n {file}\n </span>\n ))}\n </div>\n )\n }\n }\n\n if (part.type === 'diagnostics') {\n if (part.status === 'checking') {\n return <div key={index}>Checking for issues...</div>\n }\n if (part.status === 'complete' && part.issues === 0) {\n return <div key={index}>✅ No issues found</div>\n }\n }\n\n return <div key={index}>{JSON.stringify(part)}</div>\n}\n\n/**\n * Generic task section component\n * Renders a collapsible task section with basic structure - consumers provide styling\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 [internalCollapsed, setInternalCollapsed] = useState(initialCollapsed)\n const collapsed = onCollapse ? initialCollapsed : internalCollapsed\n const handleCollapse =\n onCollapse || (() => setInternalCollapsed(!internalCollapsed))\n\n // If children provided, use that (allows complete customization)\n if (children) {\n return <>{children}</>\n }\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 // Add more meaningful part types as needed\n return false\n })\n\n // If there's only one meaningful part, show just the content without the collapsible wrapper\n if (meaningfulParts.length === 1) {\n return (\n <div className={className} data-component=\"task-section-inline\">\n <div data-part>\n {renderTaskPart(meaningfulParts[0], 0, iconRenderer)}\n </div>\n </div>\n )\n }\n\n return (\n <div className={className} data-component=\"task-section\">\n <button onClick={handleCollapse} data-expanded={!collapsed} data-button>\n <div data-icon-container>\n <div data-task-icon>\n {taskIcon || getTypeIcon(type, title, iconRenderer)}\n </div>\n {collapsed\n ? chevronRightIcon ||\n (iconRenderer ? (\n React.createElement(iconRenderer, { name: 'chevron-right' })\n ) : (\n <Icon name=\"chevron-right\" />\n ))\n : chevronDownIcon ||\n (iconRenderer ? (\n React.createElement(iconRenderer, { name: 'chevron-down' })\n ) : (\n <Icon name=\"chevron-down\" />\n ))}\n </div>\n <span data-title>{title || 'Task'}</span>\n </button>\n {!collapsed && (\n <div data-content>\n <div data-parts-container>\n {parts.map((part, index) => (\n <div key={index} data-part>\n {renderTaskPart(part, index, iconRenderer)}\n </div>\n ))}\n </div>\n </div>\n )}\n </div>\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/**\n * Generic code project block component\n * Renders a collapsible code project with basic structure - consumers provide styling\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 [collapsed, setCollapsed] = useState(initialCollapsed)\n\n // If children provided, use that (allows complete customization)\n if (children) {\n return <>{children}</>\n }\n\n return (\n <div className={className} data-component=\"code-project-block\">\n <button\n onClick={() => setCollapsed(!collapsed)}\n data-expanded={!collapsed}\n >\n <div data-header>\n {iconRenderer ? (\n React.createElement(iconRenderer, { name: 'folder' })\n ) : (\n <Icon name=\"folder\" />\n )}\n <span data-title>{title || 'Code Project'}</span>\n </div>\n <span data-version>v1</span>\n </button>\n {!collapsed && (\n <div data-content>\n <div data-file-list>\n <div data-file data-active>\n {iconRenderer ? (\n React.createElement(iconRenderer, { name: 'file-text' })\n ) : (\n <Icon name=\"file-text\" />\n )}\n <span data-filename>{filename}</span>\n <span data-filepath>app/page.tsx</span>\n </div>\n {/* Additional files could be added here */}\n <div data-file>\n {iconRenderer ? (\n React.createElement(iconRenderer, { name: 'file-text' })\n ) : (\n <Icon name=\"file-text\" />\n )}\n <span data-filename>layout.tsx</span>\n <span data-filepath>app/layout.tsx</span>\n </div>\n <div data-file>\n {iconRenderer ? (\n React.createElement(iconRenderer, { name: 'file-text' })\n ) : (\n <Icon name=\"file-text\" />\n )}\n <span data-filename>globals.css</span>\n <span data-filepath>app/globals.css</span>\n </div>\n </div>\n {code && <CodeBlock language={language} code={code} />}\n </div>\n )}\n </div>\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 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 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\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 if (!part) return null\n\n const { type, parts = [], ...metadata } = part\n\n switch (type) {\n case 'task-thinking-v1': {\n const thinkingPart = parts.find((p: any) => p.type === 'thinking-end')\n const ThinkingComponent = thinkingSectionRenderer || ThinkingSection\n const [collapsed, setCollapsed] = useState(true)\n\n return (\n <ThinkingComponent\n title=\"Thought\"\n duration={thinkingPart?.duration}\n thought={thinkingPart?.thought}\n collapsed={collapsed}\n onCollapse={() => setCollapsed(!collapsed)}\n brainIcon={brainIcon}\n chevronRightIcon={chevronRightIcon}\n chevronDownIcon={chevronDownIcon}\n />\n )\n }\n\n case 'task-search-web-v1': {\n const TaskComponent = taskSectionRenderer || TaskSection\n const [collapsed, setCollapsed] = useState(true)\n\n return (\n <TaskComponent\n title={metadata.taskNameComplete || metadata.taskNameActive}\n type={type}\n parts={parts}\n collapsed={collapsed}\n onCollapse={() => setCollapsed(!collapsed)}\n taskIcon={searchIcon}\n chevronRightIcon={chevronRightIcon}\n chevronDownIcon={chevronDownIcon}\n />\n )\n }\n\n case 'task-search-repo-v1': {\n const TaskComponent = taskSectionRenderer || TaskSection\n const [collapsed, setCollapsed] = useState(true)\n\n return (\n <TaskComponent\n title={metadata.taskNameComplete || metadata.taskNameActive}\n type={type}\n parts={parts}\n collapsed={collapsed}\n onCollapse={() => setCollapsed(!collapsed)}\n taskIcon={folderIcon}\n chevronRightIcon={chevronRightIcon}\n chevronDownIcon={chevronDownIcon}\n />\n )\n }\n\n case 'task-diagnostics-v1': {\n const TaskComponent = taskSectionRenderer || TaskSection\n const [collapsed, setCollapsed] = useState(true)\n\n return (\n <TaskComponent\n title={metadata.taskNameComplete || metadata.taskNameActive}\n type={type}\n parts={parts}\n collapsed={collapsed}\n onCollapse={() => setCollapsed(!collapsed)}\n taskIcon={settingsIcon}\n chevronRightIcon={chevronRightIcon}\n chevronDownIcon={chevronDownIcon}\n />\n )\n }\n\n case 'task-read-file-v1': {\n const TaskComponent = taskSectionRenderer || TaskSection\n const [collapsed, setCollapsed] = useState(true)\n\n return (\n <TaskComponent\n title={\n metadata.taskNameComplete ||\n metadata.taskNameActive ||\n 'Reading file'\n }\n type={type}\n parts={parts}\n collapsed={collapsed}\n onCollapse={() => setCollapsed(!collapsed)}\n taskIcon={folderIcon}\n chevronRightIcon={chevronRightIcon}\n chevronDownIcon={chevronDownIcon}\n />\n )\n }\n\n case 'task-coding-v1': {\n const TaskComponent = taskSectionRenderer || TaskSection\n const [collapsed, setCollapsed] = useState(true)\n\n return (\n <TaskComponent\n title={\n metadata.taskNameComplete || metadata.taskNameActive || 'Coding'\n }\n type={type}\n parts={parts}\n collapsed={collapsed}\n onCollapse={() => setCollapsed(!collapsed)}\n taskIcon={wrenchIcon}\n chevronRightIcon={chevronRightIcon}\n chevronDownIcon={chevronDownIcon}\n />\n )\n }\n\n case 'task-start-v1':\n // Usually just indicates task start - can be hidden or show as status\n return null\n\n default:\n return <div data-unknown-part-type={type}>Unknown part type: {type}</div>\n }\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/**\n * Generic math renderer component\n * Renders plain math content by default - consumers should provide their own math rendering\n */\nexport function MathPart({\n content,\n inline = false,\n className = '',\n children,\n}: MathPartProps) {\n // If children provided, use that (allows complete customization)\n if (children) {\n return <>{children}</>\n }\n\n // Simple fallback - just render plain math content\n const Element = inline ? 'span' : 'div'\n\n return (\n <Element className={className} data-math-inline={inline}>\n {content}\n </Element>\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/**\n * Generic code block component\n * Renders plain code by default - consumers should provide their own styling and highlighting\n */\nexport function CodeBlock({\n language,\n code,\n className = '',\n children,\n}: CodeBlockProps) {\n // If children provided, use that (allows complete customization)\n if (children) {\n return <>{children}</>\n }\n\n // Simple fallback - just render plain code\n return (\n <pre className={className} data-language={language}>\n <code>{code}</code>\n </pre>\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;;AC5KP;AACA;AACA;AACA;AACO;;ACLA;AACP;AACA;AACA;AACA;AACA;AACO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;;ACbA;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;AACO;;ACxDA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;;ACRA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;;ACjBA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;;ACjBA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;;ACdA;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;AACO;;AClCA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;;ACXA;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO;;;"}
|
package/dist/index.js
CHANGED
|
@@ -755,7 +755,6 @@ class StreamStateManager {
|
|
|
755
755
|
this.processStream = async (stream, options = {})=>{
|
|
756
756
|
// Prevent processing the same stream multiple times
|
|
757
757
|
if (this.processedStreams.has(stream)) {
|
|
758
|
-
console.log('Stream already processed, skipping');
|
|
759
758
|
return;
|
|
760
759
|
}
|
|
761
760
|
// Handle locked streams gracefully
|
|
@@ -810,13 +809,11 @@ class StreamStateManager {
|
|
|
810
809
|
while(true){
|
|
811
810
|
const { done, value } = await reader.read();
|
|
812
811
|
if (done) {
|
|
813
|
-
console.log('Stream reading completed');
|
|
814
812
|
break;
|
|
815
813
|
}
|
|
816
814
|
const chunk = decoder.decode(value, {
|
|
817
815
|
stream: true
|
|
818
816
|
});
|
|
819
|
-
console.log('Received raw chunk:', chunk);
|
|
820
817
|
buffer += chunk;
|
|
821
818
|
const lines = buffer.split('\n');
|
|
822
819
|
buffer = lines.pop() || '';
|
|
@@ -824,13 +821,11 @@ class StreamStateManager {
|
|
|
824
821
|
if (line.trim() === '') {
|
|
825
822
|
continue;
|
|
826
823
|
}
|
|
827
|
-
console.log('Processing line:', line);
|
|
828
824
|
// Handle SSE format (data: ...)
|
|
829
825
|
let jsonData;
|
|
830
826
|
if (line.startsWith('data: ')) {
|
|
831
827
|
jsonData = line.slice(6); // Remove "data: " prefix
|
|
832
828
|
if (jsonData === '[DONE]') {
|
|
833
|
-
console.log('Stream marked as done via SSE');
|
|
834
829
|
this.setComplete(true);
|
|
835
830
|
options.onComplete?.(currentContent);
|
|
836
831
|
return;
|
|
@@ -842,28 +837,21 @@ class StreamStateManager {
|
|
|
842
837
|
try {
|
|
843
838
|
// Parse the JSON data
|
|
844
839
|
const parsedData = JSON.parse(jsonData);
|
|
845
|
-
console.log('Parsed data:', JSON.stringify(parsedData, null, 2));
|
|
846
840
|
// Handle v0 streaming format
|
|
847
841
|
if (parsedData.type === 'connected') {
|
|
848
|
-
console.log('Stream connected');
|
|
849
842
|
continue;
|
|
850
843
|
} else if (parsedData.type === 'done') {
|
|
851
|
-
console.log('Stream marked as done');
|
|
852
844
|
this.setComplete(true);
|
|
853
845
|
options.onComplete?.(currentContent);
|
|
854
846
|
return;
|
|
855
847
|
} else if (parsedData.object === 'chat' && parsedData.id) {
|
|
856
848
|
// Handle the initial chat data message
|
|
857
|
-
console.log('Received chat data:', parsedData.id);
|
|
858
849
|
options.onChatData?.(parsedData);
|
|
859
850
|
continue;
|
|
860
851
|
} else if (parsedData.delta) {
|
|
861
852
|
// Apply the delta using jsondiffpatch
|
|
862
|
-
console.log('Applying delta to content:', JSON.stringify(currentContent, null, 2));
|
|
863
|
-
console.log('Delta:', JSON.stringify(parsedData.delta, null, 2));
|
|
864
853
|
const patchedContent = patch(currentContent, parsedData.delta);
|
|
865
854
|
currentContent = Array.isArray(patchedContent) ? patchedContent : [];
|
|
866
|
-
console.log('Patched content result:', JSON.stringify(currentContent, null, 2));
|
|
867
855
|
this.updateContent(currentContent);
|
|
868
856
|
options.onChunk?.(currentContent);
|
|
869
857
|
}
|
|
@@ -896,7 +884,6 @@ class StreamStateManager {
|
|
|
896
884
|
if (stream !== lastStreamRef.current) {
|
|
897
885
|
lastStreamRef.current = stream;
|
|
898
886
|
if (stream) {
|
|
899
|
-
console.log('New stream detected, starting processing');
|
|
900
887
|
manager.processStream(stream, options);
|
|
901
888
|
}
|
|
902
889
|
}
|
|
@@ -930,8 +917,8 @@ class StreamStateManager {
|
|
|
930
917
|
* stream={stream}
|
|
931
918
|
* messageId="demo-message"
|
|
932
919
|
* role="assistant"
|
|
933
|
-
* onComplete={(content) =>
|
|
934
|
-
* onChatData={(chatData) =>
|
|
920
|
+
* onComplete={(content) => handleCompletion(content)}
|
|
921
|
+
* onChatData={(chatData) => handleChatData(chatData)}
|
|
935
922
|
* />
|
|
936
923
|
* )}
|
|
937
924
|
* </div>
|