@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 CHANGED
@@ -63,7 +63,7 @@ function ChatDemo() {
63
63
  stream={stream}
64
64
  messageId="demo-message"
65
65
  role="assistant"
66
- onComplete={(content) => console.log('Stream complete:', content)}
66
+ onComplete={(content) => handleCompletion(content)}
67
67
  />
68
68
  )}
69
69
  </div>
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) => console.log('Stream complete:', content)}
959
- * onChatData={(chatData) => console.log('Chat created:', chatData.id)}
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) => console.log('Stream complete:', content)}
249
- * onChatData={(chatData) => console.log('Chat created:', chatData.id)}
248
+ * onComplete={(content) => handleCompletion(content)}
249
+ * onChatData={(chatData) => handleChatData(chatData)}
250
250
  * />
251
251
  * )}
252
252
  * </div>
@@ -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) => console.log('Stream complete:', content)}
934
- * onChatData={(chatData) => console.log('Chat created:', chatData.id)}
920
+ * onComplete={(content) => handleCompletion(content)}
921
+ * onChatData={(chatData) => handleChatData(chatData)}
935
922
  * />
936
923
  * )}
937
924
  * </div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@v0-sdk/react",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "React components for rendering v0 Platform API content",
5
5
  "homepage": "https://v0.dev/docs/api",
6
6
  "repository": {