@v0-sdk/react 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -2,30 +2,31 @@ Object.defineProperty(exports, '__esModule', { value: true });
2
2
 
3
3
  var jsxRuntime = require('react/jsx-runtime');
4
4
  var React = require('react');
5
+ var jsondiffpatch = require('jsondiffpatch');
5
6
 
6
7
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
7
8
 
8
- var React__default = /*#__PURE__*/_interopDefault(React);
9
-
10
- /**
11
- * Generic math renderer component
12
- * Renders plain math content by default - consumers should provide their own math rendering
13
- */ function MathPart({ content, inline = false, className = '', children }) {
14
- // If children provided, use that (allows complete customization)
15
- if (children) {
16
- return /*#__PURE__*/ jsxRuntime.jsx(jsxRuntime.Fragment, {
17
- children: children
9
+ function _interopNamespace(e) {
10
+ if (e && e.__esModule) return e;
11
+ var n = Object.create(null);
12
+ if (e) {
13
+ Object.keys(e).forEach(function (k) {
14
+ if (k !== 'default') {
15
+ var d = Object.getOwnPropertyDescriptor(e, k);
16
+ Object.defineProperty(n, k, d.get ? d : {
17
+ enumerable: true,
18
+ get: function () { return e[k]; }
18
19
  });
19
- }
20
- // Simple fallback - just render plain math content
21
- const Element = inline ? 'span' : 'div';
22
- return /*#__PURE__*/ jsxRuntime.jsx(Element, {
23
- className: className,
24
- "data-math-inline": inline,
25
- children: content
20
+ }
26
21
  });
22
+ }
23
+ n.default = e;
24
+ return n;
27
25
  }
28
26
 
27
+ var React__default = /*#__PURE__*/_interopDefault(React);
28
+ var jsondiffpatch__namespace = /*#__PURE__*/_interopNamespace(jsondiffpatch);
29
+
29
30
  /**
30
31
  * Generic code block component
31
32
  * Renders plain code by default - consumers should provide their own styling and highlighting
@@ -530,6 +531,36 @@ function ContentPartRenderer({ part, iconRenderer, thinkingSectionRenderer, task
530
531
  chevronDownIcon: chevronDownIcon
531
532
  });
532
533
  }
534
+ case 'task-read-file-v1':
535
+ {
536
+ const TaskComponent = taskSectionRenderer || TaskSection;
537
+ const [collapsed, setCollapsed] = React.useState(true);
538
+ return /*#__PURE__*/ jsxRuntime.jsx(TaskComponent, {
539
+ title: metadata.taskNameComplete || metadata.taskNameActive || 'Reading file',
540
+ type: type,
541
+ parts: parts,
542
+ collapsed: collapsed,
543
+ onCollapse: ()=>setCollapsed(!collapsed),
544
+ taskIcon: folderIcon,
545
+ chevronRightIcon: chevronRightIcon,
546
+ chevronDownIcon: chevronDownIcon
547
+ });
548
+ }
549
+ case 'task-coding-v1':
550
+ {
551
+ const TaskComponent = taskSectionRenderer || TaskSection;
552
+ const [collapsed, setCollapsed] = React.useState(true);
553
+ return /*#__PURE__*/ jsxRuntime.jsx(TaskComponent, {
554
+ title: metadata.taskNameComplete || metadata.taskNameActive || 'Coding',
555
+ type: type,
556
+ parts: parts,
557
+ collapsed: collapsed,
558
+ onCollapse: ()=>setCollapsed(!collapsed),
559
+ taskIcon: wrenchIcon,
560
+ chevronRightIcon: chevronRightIcon,
561
+ chevronDownIcon: chevronDownIcon
562
+ });
563
+ }
533
564
  case 'task-start-v1':
534
565
  // Usually just indicates task start - can be hidden or show as status
535
566
  return null;
@@ -549,33 +580,8 @@ function cn(...classes) {
549
580
  return classes.filter(Boolean).join(' ');
550
581
  }
551
582
 
552
- // Helper function to render HTML elements with component or className config
553
- function renderHtmlElement(tagName, key, props, children, className, componentOrConfig, components) {
554
- if (typeof componentOrConfig === 'function') {
555
- const Component = componentOrConfig;
556
- return /*#__PURE__*/ jsxRuntime.jsx(Component, {
557
- ...props,
558
- className: className,
559
- children: renderChildren(children, key, components)
560
- }, key);
561
- } else if (componentOrConfig && typeof componentOrConfig === 'object') {
562
- const mergedClassName = cn(className, componentOrConfig.className);
563
- return /*#__PURE__*/ React__default.default.createElement(tagName, {
564
- key,
565
- ...props,
566
- className: mergedClassName
567
- }, renderChildren(children, key, components));
568
- } else {
569
- return /*#__PURE__*/ React__default.default.createElement(tagName, {
570
- key,
571
- ...props,
572
- className
573
- }, renderChildren(children, key, components));
574
- }
575
- }
576
- /**
577
- * Core renderer component for v0 Platform API message content
578
- */ function MessageImpl({ content, messageId = 'unknown', role: _role = 'assistant', streaming: _streaming = false, isLastMessage: _isLastMessage = false, className, components, renderers }) {
583
+ // Simplified renderer that matches v0's exact approach
584
+ function MessageImpl({ content, messageId = 'unknown', role: _role = 'assistant', streaming: _streaming = false, isLastMessage: _isLastMessage = false, className, components, renderers }) {
579
585
  if (!Array.isArray(content)) {
580
586
  console.warn('MessageContent: content must be an array (MessageBinaryFormat)');
581
587
  return null;
@@ -597,240 +603,434 @@ function renderHtmlElement(tagName, key, props, children, className, componentOr
597
603
  Icon: renderers.Icon
598
604
  }
599
605
  };
600
- const elements = content.map(([type, ...data], index)=>{
606
+ // Process content exactly like v0's Renderer component
607
+ const elements = content.map(([type, data], index)=>{
601
608
  const key = `${messageId}-${index}`;
602
- // Markdown/text content (type 0)
609
+ // Markdown data (type 0) - this is the main content
603
610
  if (type === 0) {
604
- const markdownData = data[0];
605
- if (!Array.isArray(markdownData)) {
606
- return null;
607
- }
608
- return /*#__PURE__*/ jsxRuntime.jsx("div", {
609
- children: markdownData.map((item, mdIndex)=>{
610
- const mdKey = `${key}-md-${mdIndex}`;
611
- return renderMarkdownElement(item, mdKey, mergedComponents);
612
- })
611
+ return /*#__PURE__*/ jsxRuntime.jsx(Elements, {
612
+ data: data,
613
+ components: mergedComponents
613
614
  }, key);
614
615
  }
615
- // Code block (type 1)
616
+ // Metadata (type 1) - extract context but don't render
616
617
  if (type === 1) {
617
- const [language, code] = data;
618
- const CodeBlockComponent = mergedComponents?.CodeBlock || CodeBlock;
619
- return /*#__PURE__*/ jsxRuntime.jsx(CodeBlockComponent, {
620
- language: language || 'text',
621
- code: code || ''
622
- }, key);
623
- }
624
- // Math (type 2 for inline, type 3 for block)
625
- if (type === 2 || type === 3) {
626
- const mathContent = data[0] || '';
627
- const MathPartComponent = mergedComponents?.MathPart || MathPart;
628
- return /*#__PURE__*/ jsxRuntime.jsx(MathPartComponent, {
629
- content: mathContent,
630
- inline: type === 2
631
- }, key);
618
+ // In the future, we could extract sources/context here like v0 does
619
+ // For now, just return null like v0's renderer
620
+ return null;
632
621
  }
633
- // Unknown type - render as text for debugging
634
- return /*#__PURE__*/ jsxRuntime.jsxs("div", {
635
- children: [
636
- "[Unknown content type: ",
637
- type,
638
- "]"
639
- ]
640
- }, key);
622
+ // Other types - v0 doesn't handle these in the main renderer
623
+ return null;
641
624
  });
642
625
  return /*#__PURE__*/ jsxRuntime.jsx("div", {
643
626
  className: className,
644
627
  children: elements
645
628
  });
646
629
  }
647
- function renderMarkdownElement(item, key, components) {
648
- if (typeof item === 'string') {
630
+ // This component handles the markdown data array (equivalent to v0's Elements component)
631
+ function Elements({ data, components }) {
632
+ // Handle case where data might not be an array due to streaming/patching
633
+ if (!Array.isArray(data)) {
634
+ return null;
635
+ }
636
+ const renderedElements = data.map((item, index)=>{
637
+ const key = `element-${index}`;
638
+ return renderElement(item, key, components);
639
+ }).filter(Boolean) // Filter out null/undefined elements
640
+ ;
641
+ return /*#__PURE__*/ jsxRuntime.jsx(jsxRuntime.Fragment, {
642
+ children: renderedElements
643
+ });
644
+ }
645
+ // Render individual elements (equivalent to v0's element rendering logic)
646
+ function renderElement(element, key, components) {
647
+ if (typeof element === 'string') {
649
648
  return /*#__PURE__*/ jsxRuntime.jsx("span", {
650
- children: item
649
+ children: element
651
650
  }, key);
652
651
  }
653
- if (Array.isArray(item)) {
654
- const [tagName, props, ...children] = item;
655
- // Handle special v0 Platform API elements
656
- if (tagName === 'AssistantMessageContentPart') {
657
- return /*#__PURE__*/ jsxRuntime.jsx(ContentPartRenderer, {
658
- part: props.part,
659
- iconRenderer: components?.Icon,
660
- thinkingSectionRenderer: components?.ThinkingSection,
661
- taskSectionRenderer: components?.TaskSection
662
- }, key);
652
+ if (!Array.isArray(element)) {
653
+ return null;
654
+ }
655
+ const [tagName, props, ...children] = element;
656
+ if (!tagName) {
657
+ return null;
658
+ }
659
+ // Handle special v0 Platform API elements
660
+ if (tagName === 'AssistantMessageContentPart') {
661
+ return /*#__PURE__*/ jsxRuntime.jsx(ContentPartRenderer, {
662
+ part: props.part,
663
+ iconRenderer: components?.Icon,
664
+ thinkingSectionRenderer: components?.ThinkingSection,
665
+ taskSectionRenderer: components?.TaskSection
666
+ }, key);
667
+ }
668
+ if (tagName === 'Codeblock') {
669
+ const CustomCodeProjectPart = components?.CodeProjectPart;
670
+ const CodeProjectComponent = CustomCodeProjectPart || CodeProjectPart;
671
+ return /*#__PURE__*/ jsxRuntime.jsx(CodeProjectComponent, {
672
+ language: props.lang,
673
+ code: children[0],
674
+ iconRenderer: components?.Icon
675
+ }, key);
676
+ }
677
+ if (tagName === 'text') {
678
+ return /*#__PURE__*/ jsxRuntime.jsx("span", {
679
+ children: children[0] || ''
680
+ }, key);
681
+ }
682
+ // Render children
683
+ const renderedChildren = children.map((child, childIndex)=>{
684
+ const childKey = `${key}-child-${childIndex}`;
685
+ return renderElement(child, childKey, components);
686
+ }).filter(Boolean);
687
+ // Handle standard HTML elements
688
+ const className = props?.className;
689
+ const componentOrConfig = components?.[tagName];
690
+ if (typeof componentOrConfig === 'function') {
691
+ const Component = componentOrConfig;
692
+ return /*#__PURE__*/ jsxRuntime.jsx(Component, {
693
+ ...props,
694
+ className: className,
695
+ children: renderedChildren
696
+ }, key);
697
+ } else if (componentOrConfig && typeof componentOrConfig === 'object') {
698
+ const mergedClassName = cn(className, componentOrConfig.className);
699
+ return /*#__PURE__*/ React__default.default.createElement(tagName, {
700
+ key,
701
+ ...props,
702
+ className: mergedClassName
703
+ }, renderedChildren);
704
+ } else {
705
+ // Default HTML element rendering
706
+ const elementProps = {
707
+ key,
708
+ ...props
709
+ };
710
+ if (className) {
711
+ elementProps.className = className;
663
712
  }
664
- if (tagName === 'Codeblock') {
665
- const CustomCodeProjectPart = components?.CodeProjectPart;
666
- const CodeProjectComponent = CustomCodeProjectPart || CodeProjectPart;
667
- return /*#__PURE__*/ jsxRuntime.jsx(CodeProjectComponent, {
668
- language: props.lang,
669
- code: children[0],
670
- iconRenderer: components?.Icon
671
- }, key);
713
+ // Special handling for links
714
+ if (tagName === 'a') {
715
+ elementProps.target = '_blank';
716
+ elementProps.rel = 'noopener noreferrer';
672
717
  }
673
- if (tagName === 'text') {
674
- return /*#__PURE__*/ jsxRuntime.jsx("span", {
675
- children: children[0]
676
- }, key);
718
+ return /*#__PURE__*/ React__default.default.createElement(tagName, elementProps, renderedChildren);
719
+ }
720
+ }
721
+ /**
722
+ * Main component for rendering v0 Platform API message content
723
+ */ const Message = /*#__PURE__*/ React__default.default.memo(MessageImpl);
724
+
725
+ const jdf = jsondiffpatch__namespace.create({});
726
+ // Exact copy of the patch function from v0/chat/lib/diffpatch.ts
727
+ function patch(original, delta) {
728
+ const newObj = jdf.clone(original);
729
+ // Check for our customized delta
730
+ if (Array.isArray(delta) && delta[1] === 9 && delta[2] === 9) {
731
+ // Get the path to the modified element
732
+ const indexes = delta[0].slice(0, -1);
733
+ // Get the string to be appended
734
+ const value = delta[0].slice(-1);
735
+ let obj = newObj;
736
+ for (const index of indexes){
737
+ if (typeof obj[index] === 'string') {
738
+ obj[index] += value;
739
+ return newObj;
740
+ }
741
+ obj = obj[index];
677
742
  }
678
- // Handle standard markdown elements
679
- const className = props?.className;
680
- switch(tagName){
681
- case 'p':
682
- {
683
- const componentOrConfig = components?.p;
684
- if (typeof componentOrConfig === 'function') {
685
- const Component = componentOrConfig;
686
- return /*#__PURE__*/ jsxRuntime.jsx(Component, {
687
- ...props,
688
- className: className,
689
- children: renderChildren(children, key, components)
690
- }, key);
691
- } else if (componentOrConfig && typeof componentOrConfig === 'object') {
692
- const mergedClassName = cn(className, componentOrConfig.className);
693
- return /*#__PURE__*/ jsxRuntime.jsx("p", {
694
- ...props,
695
- className: mergedClassName,
696
- children: renderChildren(children, key, components)
697
- }, key);
698
- } else {
699
- return /*#__PURE__*/ jsxRuntime.jsx("p", {
700
- ...props,
701
- className: className,
702
- children: renderChildren(children, key, components)
703
- }, key);
704
- }
705
- }
706
- case 'h1':
707
- return renderHtmlElement('h1', key, props, children, className, components?.h1, components);
708
- case 'h2':
709
- return renderHtmlElement('h2', key, props, children, className, components?.h2, components);
710
- case 'h3':
711
- return renderHtmlElement('h3', key, props, children, className, components?.h3, components);
712
- case 'h4':
713
- return renderHtmlElement('h4', key, props, children, className, components?.h4, components);
714
- case 'h5':
715
- return renderHtmlElement('h5', key, props, children, className, components?.h5, components);
716
- case 'h6':
717
- return renderHtmlElement('h6', key, props, children, className, components?.h6, components);
718
- case 'ul':
719
- return renderHtmlElement('ul', key, props, children, className, components?.ul, components);
720
- case 'ol':
721
- return renderHtmlElement('ol', key, props, children, className, components?.ol, components);
722
- case 'li':
723
- return renderHtmlElement('li', key, props, children, className, components?.li, components);
724
- case 'blockquote':
725
- return renderHtmlElement('blockquote', key, props, children, className, components?.blockquote, components);
726
- case 'code':
727
- return renderHtmlElement('code', key, props, children, className, components?.code, components);
728
- case 'pre':
729
- return renderHtmlElement('pre', key, props, children, className, components?.pre, components);
730
- case 'strong':
731
- return renderHtmlElement('strong', key, props, children, className, components?.strong, components);
732
- case 'em':
733
- return renderHtmlElement('em', key, props, children, className, components?.em, components);
734
- case 'a':
735
- {
736
- const componentOrConfig = components?.a;
737
- if (typeof componentOrConfig === 'function') {
738
- const Component = componentOrConfig;
739
- return /*#__PURE__*/ jsxRuntime.jsx(Component, {
740
- ...props,
741
- className: className,
742
- target: "_blank",
743
- rel: "noopener noreferrer",
744
- children: renderChildren(children, key, components)
745
- }, key);
746
- } else if (componentOrConfig && typeof componentOrConfig === 'object') {
747
- const mergedClassName = cn(className, componentOrConfig.className);
748
- return /*#__PURE__*/ jsxRuntime.jsx("a", {
749
- ...props,
750
- className: mergedClassName,
751
- target: "_blank",
752
- rel: "noopener noreferrer",
753
- children: renderChildren(children, key, components)
754
- }, key);
755
- } else {
756
- return /*#__PURE__*/ jsxRuntime.jsx("a", {
757
- ...props,
758
- className: className,
759
- target: "_blank",
760
- rel: "noopener noreferrer",
761
- children: renderChildren(children, key, components)
762
- }, key);
743
+ }
744
+ // If not custom delta, apply standard jsondiffpatch-ing
745
+ jdf.patch(newObj, delta);
746
+ return newObj;
747
+ }
748
+ // Stream state manager - isolated from React lifecycle
749
+ class StreamStateManager {
750
+ constructor(){
751
+ this.content = [];
752
+ this.isStreaming = false;
753
+ this.isComplete = false;
754
+ this.callbacks = new Set();
755
+ this.processedStreams = new WeakSet();
756
+ this.cachedState = null;
757
+ this.subscribe = (callback)=>{
758
+ this.callbacks.add(callback);
759
+ return ()=>{
760
+ this.callbacks.delete(callback);
761
+ };
762
+ };
763
+ this.notifySubscribers = ()=>{
764
+ // Invalidate cached state when state changes
765
+ this.cachedState = null;
766
+ this.callbacks.forEach((callback)=>callback());
767
+ };
768
+ this.getState = ()=>{
769
+ // Return cached state to prevent infinite re-renders
770
+ if (this.cachedState === null) {
771
+ this.cachedState = {
772
+ content: this.content,
773
+ isStreaming: this.isStreaming,
774
+ error: this.error,
775
+ isComplete: this.isComplete
776
+ };
777
+ }
778
+ return this.cachedState;
779
+ };
780
+ this.processStream = async (stream, options = {})=>{
781
+ // Prevent processing the same stream multiple times
782
+ if (this.processedStreams.has(stream)) {
783
+ console.log('Stream already processed, skipping');
784
+ return;
785
+ }
786
+ // Handle locked streams gracefully
787
+ if (stream.locked) {
788
+ console.warn('Stream is locked, cannot process');
789
+ return;
790
+ }
791
+ this.processedStreams.add(stream);
792
+ this.reset();
793
+ this.setStreaming(true);
794
+ try {
795
+ await this.readStream(stream, options);
796
+ } catch (err) {
797
+ const errorMessage = err instanceof Error ? err.message : 'Unknown streaming error';
798
+ this.setError(errorMessage);
799
+ options.onError?.(errorMessage);
800
+ } finally{
801
+ this.setStreaming(false);
802
+ }
803
+ };
804
+ this.reset = ()=>{
805
+ this.content = [];
806
+ this.isStreaming = false;
807
+ this.error = undefined;
808
+ this.isComplete = false;
809
+ this.notifySubscribers();
810
+ };
811
+ this.setStreaming = (streaming)=>{
812
+ this.isStreaming = streaming;
813
+ this.notifySubscribers();
814
+ };
815
+ this.setError = (error)=>{
816
+ this.error = error;
817
+ this.notifySubscribers();
818
+ };
819
+ this.setComplete = (complete)=>{
820
+ this.isComplete = complete;
821
+ this.notifySubscribers();
822
+ };
823
+ this.updateContent = (newContent)=>{
824
+ this.content = [
825
+ ...newContent
826
+ ];
827
+ this.notifySubscribers();
828
+ };
829
+ this.readStream = async (stream, options)=>{
830
+ const reader = stream.getReader();
831
+ const decoder = new TextDecoder();
832
+ let buffer = '';
833
+ let currentContent = [];
834
+ try {
835
+ while(true){
836
+ const { done, value } = await reader.read();
837
+ if (done) {
838
+ console.log('Stream reading completed');
839
+ break;
763
840
  }
764
- }
765
- case 'br':
766
- return /*#__PURE__*/ jsxRuntime.jsx("br", {}, key);
767
- case 'hr':
768
- {
769
- const componentOrConfig = components?.hr;
770
- if (typeof componentOrConfig === 'function') {
771
- const Component = componentOrConfig;
772
- return /*#__PURE__*/ jsxRuntime.jsx(Component, {
773
- ...props,
774
- className: className
775
- }, key);
776
- } else if (componentOrConfig && typeof componentOrConfig === 'object') {
777
- const mergedClassName = cn(className, componentOrConfig.className);
778
- return /*#__PURE__*/ jsxRuntime.jsx("hr", {
779
- ...props,
780
- className: mergedClassName
781
- }, key);
782
- } else {
783
- return /*#__PURE__*/ jsxRuntime.jsx("hr", {
784
- ...props,
785
- className: className
786
- }, key);
841
+ const chunk = decoder.decode(value, {
842
+ stream: true
843
+ });
844
+ console.log('Received raw chunk:', chunk);
845
+ buffer += chunk;
846
+ const lines = buffer.split('\n');
847
+ buffer = lines.pop() || '';
848
+ for (const line of lines){
849
+ if (line.trim() === '') {
850
+ continue;
851
+ }
852
+ console.log('Processing line:', line);
853
+ // Handle SSE format (data: ...)
854
+ let jsonData;
855
+ if (line.startsWith('data: ')) {
856
+ jsonData = line.slice(6); // Remove "data: " prefix
857
+ if (jsonData === '[DONE]') {
858
+ console.log('Stream marked as done via SSE');
859
+ this.setComplete(true);
860
+ options.onComplete?.(currentContent);
861
+ return;
862
+ }
863
+ } else {
864
+ // Handle raw JSON lines (fallback)
865
+ jsonData = line;
866
+ }
867
+ try {
868
+ // Parse the JSON data
869
+ const parsedData = JSON.parse(jsonData);
870
+ console.log('Parsed data:', JSON.stringify(parsedData, null, 2));
871
+ // Handle v0 streaming format
872
+ if (parsedData.type === 'connected') {
873
+ console.log('Stream connected');
874
+ continue;
875
+ } else if (parsedData.type === 'done') {
876
+ console.log('Stream marked as done');
877
+ this.setComplete(true);
878
+ options.onComplete?.(currentContent);
879
+ return;
880
+ } else if (parsedData.object === 'chat' && parsedData.id) {
881
+ // Handle the initial chat data message
882
+ console.log('Received chat data:', parsedData.id);
883
+ options.onChatData?.(parsedData);
884
+ continue;
885
+ } else if (parsedData.delta) {
886
+ // 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
+ const patchedContent = patch(currentContent, parsedData.delta);
890
+ currentContent = Array.isArray(patchedContent) ? patchedContent : [];
891
+ console.log('Patched content result:', JSON.stringify(currentContent, null, 2));
892
+ this.updateContent(currentContent);
893
+ options.onChunk?.(currentContent);
894
+ }
895
+ } catch (e) {
896
+ console.warn('Failed to parse streaming data:', line, e);
897
+ }
787
898
  }
788
899
  }
789
- case 'div':
790
- return renderHtmlElement('div', key, props, children, className, components?.div, components);
791
- case 'span':
792
- return renderHtmlElement('span', key, props, children, className, components?.span, components);
793
- default:
794
- return /*#__PURE__*/ jsxRuntime.jsx("span", {
795
- children: renderChildren(children, key, components)
796
- }, key);
797
- }
900
+ this.setComplete(true);
901
+ options.onComplete?.(currentContent);
902
+ } finally{
903
+ reader.releaseLock();
904
+ }
905
+ };
798
906
  }
799
- return null;
800
907
  }
801
- function renderChildren(children, parentKey, components) {
802
- return children.map((child, index)=>{
803
- const key = `${parentKey}-child-${index}`;
804
- return renderMarkdownElement(child, key, components);
805
- }).filter(Boolean);
908
+ /**
909
+ * Hook for handling streaming message content from v0 API using useSyncExternalStore
910
+ */ function useStreamingMessage(stream, options = {}) {
911
+ // Create a stable stream manager instance
912
+ const managerRef = React.useRef(null);
913
+ if (!managerRef.current) {
914
+ managerRef.current = new StreamStateManager();
915
+ }
916
+ const manager = managerRef.current;
917
+ // Subscribe to state changes using useSyncExternalStore
918
+ const state = React.useSyncExternalStore(manager.subscribe, manager.getState, manager.getState);
919
+ // Process stream when it changes
920
+ const lastStreamRef = React.useRef(null);
921
+ if (stream !== lastStreamRef.current) {
922
+ lastStreamRef.current = stream;
923
+ if (stream) {
924
+ console.log('New stream detected, starting processing');
925
+ manager.processStream(stream, options);
926
+ }
927
+ }
928
+ return state;
806
929
  }
930
+
807
931
  /**
808
- * Main component for rendering v0 Platform API message content
932
+ * Component for rendering streaming message content from v0 API
809
933
  *
810
934
  * @example
811
935
  * ```tsx
812
- * import { Message } from '@v0-sdk/react'
936
+ * import { v0 } from 'v0-sdk'
937
+ * import { StreamingMessage } from '@v0-sdk/react'
938
+ *
939
+ * function ChatDemo() {
940
+ * const [stream, setStream] = useState<ReadableStream<Uint8Array> | null>(null)
813
941
  *
814
- * function MyComponent({ apiResponse }) {
815
- * const content = JSON.parse(apiResponse.content)
942
+ * const handleSubmit = async () => {
943
+ * const response = await v0.chats.create({
944
+ * message: 'Create a button component',
945
+ * responseMode: 'experimental_stream'
946
+ * })
947
+ * setStream(response)
948
+ * }
816
949
  *
817
950
  * return (
818
- * <Message
819
- * content={content}
820
- * messageId={apiResponse.id}
821
- * role={apiResponse.role}
822
- * className="space-y-4"
823
- * components={{
824
- * p: ({ children, ...props }) => <p className="mb-4" {...props}>{children}</p>,
825
- * h1: ({ children, ...props }) => <h1 className="mb-4 text-2xl font-bold" {...props}>{children}</h1>,
826
- * CodeBlock: MyCustomCodeBlock,
827
- * MathPart: MyCustomMathRenderer,
828
- * }}
829
- * />
951
+ * <div>
952
+ * <button onClick={handleSubmit}>Send Message</button>
953
+ * {stream && (
954
+ * <StreamingMessage
955
+ * stream={stream}
956
+ * messageId="demo-message"
957
+ * role="assistant"
958
+ * onComplete={(content) => console.log('Stream complete:', content)}
959
+ * onChatData={(chatData) => console.log('Chat created:', chatData.id)}
960
+ * />
961
+ * )}
962
+ * </div>
830
963
  * )
831
964
  * }
832
965
  * ```
833
- */ const Message = /*#__PURE__*/ React__default.default.memo(MessageImpl);
966
+ */ function StreamingMessage({ stream, showLoadingIndicator = true, loadingComponent, errorComponent, onChunk, onComplete, onError, onChatData, ...messageProps }) {
967
+ const { content, isStreaming, error, isComplete } = useStreamingMessage(stream, {
968
+ onChunk,
969
+ onComplete,
970
+ onError,
971
+ onChatData
972
+ });
973
+ // Handle error state
974
+ if (error) {
975
+ if (errorComponent) {
976
+ return /*#__PURE__*/ jsxRuntime.jsx(jsxRuntime.Fragment, {
977
+ children: errorComponent(error)
978
+ });
979
+ }
980
+ return /*#__PURE__*/ jsxRuntime.jsxs("div", {
981
+ className: "text-red-500 p-4 border border-red-200 rounded",
982
+ children: [
983
+ "Error: ",
984
+ error
985
+ ]
986
+ });
987
+ }
988
+ // Handle loading state
989
+ if (showLoadingIndicator && isStreaming && content.length === 0) {
990
+ if (loadingComponent) {
991
+ return /*#__PURE__*/ jsxRuntime.jsx(jsxRuntime.Fragment, {
992
+ children: loadingComponent
993
+ });
994
+ }
995
+ return /*#__PURE__*/ jsxRuntime.jsxs("div", {
996
+ className: "flex items-center space-x-2 text-gray-500",
997
+ children: [
998
+ /*#__PURE__*/ jsxRuntime.jsx("div", {
999
+ className: "animate-spin h-4 w-4 border-2 border-gray-300 border-t-gray-600 rounded-full"
1000
+ }),
1001
+ /*#__PURE__*/ jsxRuntime.jsx("span", {
1002
+ children: "Loading..."
1003
+ })
1004
+ ]
1005
+ });
1006
+ }
1007
+ // Render the message content
1008
+ return /*#__PURE__*/ jsxRuntime.jsx(Message, {
1009
+ ...messageProps,
1010
+ content: content,
1011
+ streaming: isStreaming,
1012
+ isLastMessage: true
1013
+ });
1014
+ }
1015
+
1016
+ /**
1017
+ * Generic math renderer component
1018
+ * Renders plain math content by default - consumers should provide their own math rendering
1019
+ */ function MathPart({ content, inline = false, className = '', children }) {
1020
+ // If children provided, use that (allows complete customization)
1021
+ if (children) {
1022
+ return /*#__PURE__*/ jsxRuntime.jsx(jsxRuntime.Fragment, {
1023
+ children: children
1024
+ });
1025
+ }
1026
+ // Simple fallback - just render plain math content
1027
+ const Element = inline ? 'span' : 'div';
1028
+ return /*#__PURE__*/ jsxRuntime.jsx(Element, {
1029
+ className: className,
1030
+ "data-math-inline": inline,
1031
+ children: content
1032
+ });
1033
+ }
834
1034
 
835
1035
  exports.AssistantMessageContentPart = ContentPartRenderer;
836
1036
  exports.CodeBlock = CodeBlock;
@@ -843,6 +1043,8 @@ exports.MathRenderer = MathPart;
843
1043
  exports.Message = Message;
844
1044
  exports.MessageContent = Message;
845
1045
  exports.MessageRenderer = Message;
1046
+ exports.StreamingMessage = StreamingMessage;
846
1047
  exports.TaskSection = TaskSection;
847
1048
  exports.ThinkingSection = ThinkingSection;
848
1049
  exports.V0MessageRenderer = Message;
1050
+ exports.useStreamingMessage = useStreamingMessage;