@v0-sdk/react 0.1.1 → 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/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,421 @@ 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
+ return;
784
+ }
785
+ // Handle locked streams gracefully
786
+ if (stream.locked) {
787
+ console.warn('Stream is locked, cannot process');
788
+ return;
789
+ }
790
+ this.processedStreams.add(stream);
791
+ this.reset();
792
+ this.setStreaming(true);
793
+ try {
794
+ await this.readStream(stream, options);
795
+ } catch (err) {
796
+ const errorMessage = err instanceof Error ? err.message : 'Unknown streaming error';
797
+ this.setError(errorMessage);
798
+ options.onError?.(errorMessage);
799
+ } finally{
800
+ this.setStreaming(false);
801
+ }
802
+ };
803
+ this.reset = ()=>{
804
+ this.content = [];
805
+ this.isStreaming = false;
806
+ this.error = undefined;
807
+ this.isComplete = false;
808
+ this.notifySubscribers();
809
+ };
810
+ this.setStreaming = (streaming)=>{
811
+ this.isStreaming = streaming;
812
+ this.notifySubscribers();
813
+ };
814
+ this.setError = (error)=>{
815
+ this.error = error;
816
+ this.notifySubscribers();
817
+ };
818
+ this.setComplete = (complete)=>{
819
+ this.isComplete = complete;
820
+ this.notifySubscribers();
821
+ };
822
+ this.updateContent = (newContent)=>{
823
+ this.content = [
824
+ ...newContent
825
+ ];
826
+ this.notifySubscribers();
827
+ };
828
+ this.readStream = async (stream, options)=>{
829
+ const reader = stream.getReader();
830
+ const decoder = new TextDecoder();
831
+ let buffer = '';
832
+ let currentContent = [];
833
+ try {
834
+ while(true){
835
+ const { done, value } = await reader.read();
836
+ if (done) {
837
+ break;
763
838
  }
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);
839
+ const chunk = decoder.decode(value, {
840
+ stream: true
841
+ });
842
+ buffer += chunk;
843
+ const lines = buffer.split('\n');
844
+ buffer = lines.pop() || '';
845
+ for (const line of lines){
846
+ if (line.trim() === '') {
847
+ continue;
848
+ }
849
+ // Handle SSE format (data: ...)
850
+ let jsonData;
851
+ if (line.startsWith('data: ')) {
852
+ jsonData = line.slice(6); // Remove "data: " prefix
853
+ if (jsonData === '[DONE]') {
854
+ this.setComplete(true);
855
+ options.onComplete?.(currentContent);
856
+ return;
857
+ }
858
+ } else {
859
+ // Handle raw JSON lines (fallback)
860
+ jsonData = line;
861
+ }
862
+ try {
863
+ // Parse the JSON data
864
+ const parsedData = JSON.parse(jsonData);
865
+ // Handle v0 streaming format
866
+ if (parsedData.type === 'connected') {
867
+ continue;
868
+ } else if (parsedData.type === 'done') {
869
+ this.setComplete(true);
870
+ options.onComplete?.(currentContent);
871
+ return;
872
+ } else if (parsedData.object === 'chat' && parsedData.id) {
873
+ // Handle the initial chat data message
874
+ options.onChatData?.(parsedData);
875
+ continue;
876
+ } else if (parsedData.delta) {
877
+ // Apply the delta using jsondiffpatch
878
+ const patchedContent = patch(currentContent, parsedData.delta);
879
+ currentContent = Array.isArray(patchedContent) ? patchedContent : [];
880
+ this.updateContent(currentContent);
881
+ options.onChunk?.(currentContent);
882
+ }
883
+ } catch (e) {
884
+ console.warn('Failed to parse streaming data:', line, e);
885
+ }
787
886
  }
788
887
  }
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
- }
888
+ this.setComplete(true);
889
+ options.onComplete?.(currentContent);
890
+ } finally{
891
+ reader.releaseLock();
892
+ }
893
+ };
798
894
  }
799
- return null;
800
895
  }
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);
896
+ /**
897
+ * Hook for handling streaming message content from v0 API using useSyncExternalStore
898
+ */ function useStreamingMessage(stream, options = {}) {
899
+ // Create a stable stream manager instance
900
+ const managerRef = React.useRef(null);
901
+ if (!managerRef.current) {
902
+ managerRef.current = new StreamStateManager();
903
+ }
904
+ const manager = managerRef.current;
905
+ // Subscribe to state changes using useSyncExternalStore
906
+ const state = React.useSyncExternalStore(manager.subscribe, manager.getState, manager.getState);
907
+ // Process stream when it changes
908
+ const lastStreamRef = React.useRef(null);
909
+ if (stream !== lastStreamRef.current) {
910
+ lastStreamRef.current = stream;
911
+ if (stream) {
912
+ manager.processStream(stream, options);
913
+ }
914
+ }
915
+ return state;
806
916
  }
917
+
807
918
  /**
808
- * Main component for rendering v0 Platform API message content
919
+ * Component for rendering streaming message content from v0 API
809
920
  *
810
921
  * @example
811
922
  * ```tsx
812
- * import { Message } from '@v0-sdk/react'
923
+ * import { v0 } from 'v0-sdk'
924
+ * import { StreamingMessage } from '@v0-sdk/react'
925
+ *
926
+ * function ChatDemo() {
927
+ * const [stream, setStream] = useState<ReadableStream<Uint8Array> | null>(null)
813
928
  *
814
- * function MyComponent({ apiResponse }) {
815
- * const content = JSON.parse(apiResponse.content)
929
+ * const handleSubmit = async () => {
930
+ * const response = await v0.chats.create({
931
+ * message: 'Create a button component',
932
+ * responseMode: 'experimental_stream'
933
+ * })
934
+ * setStream(response)
935
+ * }
816
936
  *
817
937
  * 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
- * />
938
+ * <div>
939
+ * <button onClick={handleSubmit}>Send Message</button>
940
+ * {stream && (
941
+ * <StreamingMessage
942
+ * stream={stream}
943
+ * messageId="demo-message"
944
+ * role="assistant"
945
+ * onComplete={(content) => handleCompletion(content)}
946
+ * onChatData={(chatData) => handleChatData(chatData)}
947
+ * />
948
+ * )}
949
+ * </div>
830
950
  * )
831
951
  * }
832
952
  * ```
833
- */ const Message = /*#__PURE__*/ React__default.default.memo(MessageImpl);
953
+ */ function StreamingMessage({ stream, showLoadingIndicator = true, loadingComponent, errorComponent, onChunk, onComplete, onError, onChatData, ...messageProps }) {
954
+ const { content, isStreaming, error, isComplete } = useStreamingMessage(stream, {
955
+ onChunk,
956
+ onComplete,
957
+ onError,
958
+ onChatData
959
+ });
960
+ // Handle error state
961
+ if (error) {
962
+ if (errorComponent) {
963
+ return /*#__PURE__*/ jsxRuntime.jsx(jsxRuntime.Fragment, {
964
+ children: errorComponent(error)
965
+ });
966
+ }
967
+ return /*#__PURE__*/ jsxRuntime.jsxs("div", {
968
+ className: "text-red-500 p-4 border border-red-200 rounded",
969
+ children: [
970
+ "Error: ",
971
+ error
972
+ ]
973
+ });
974
+ }
975
+ // Handle loading state
976
+ if (showLoadingIndicator && isStreaming && content.length === 0) {
977
+ if (loadingComponent) {
978
+ return /*#__PURE__*/ jsxRuntime.jsx(jsxRuntime.Fragment, {
979
+ children: loadingComponent
980
+ });
981
+ }
982
+ return /*#__PURE__*/ jsxRuntime.jsxs("div", {
983
+ className: "flex items-center space-x-2 text-gray-500",
984
+ children: [
985
+ /*#__PURE__*/ jsxRuntime.jsx("div", {
986
+ className: "animate-spin h-4 w-4 border-2 border-gray-300 border-t-gray-600 rounded-full"
987
+ }),
988
+ /*#__PURE__*/ jsxRuntime.jsx("span", {
989
+ children: "Loading..."
990
+ })
991
+ ]
992
+ });
993
+ }
994
+ // Render the message content
995
+ return /*#__PURE__*/ jsxRuntime.jsx(Message, {
996
+ ...messageProps,
997
+ content: content,
998
+ streaming: isStreaming,
999
+ isLastMessage: true
1000
+ });
1001
+ }
1002
+
1003
+ /**
1004
+ * Generic math renderer component
1005
+ * Renders plain math content by default - consumers should provide their own math rendering
1006
+ */ function MathPart({ content, inline = false, className = '', children }) {
1007
+ // If children provided, use that (allows complete customization)
1008
+ if (children) {
1009
+ return /*#__PURE__*/ jsxRuntime.jsx(jsxRuntime.Fragment, {
1010
+ children: children
1011
+ });
1012
+ }
1013
+ // Simple fallback - just render plain math content
1014
+ const Element = inline ? 'span' : 'div';
1015
+ return /*#__PURE__*/ jsxRuntime.jsx(Element, {
1016
+ className: className,
1017
+ "data-math-inline": inline,
1018
+ children: content
1019
+ });
1020
+ }
834
1021
 
835
1022
  exports.AssistantMessageContentPart = ContentPartRenderer;
836
1023
  exports.CodeBlock = CodeBlock;
@@ -843,6 +1030,8 @@ exports.MathRenderer = MathPart;
843
1030
  exports.Message = Message;
844
1031
  exports.MessageContent = Message;
845
1032
  exports.MessageRenderer = Message;
1033
+ exports.StreamingMessage = StreamingMessage;
846
1034
  exports.TaskSection = TaskSection;
847
1035
  exports.ThinkingSection = ThinkingSection;
848
1036
  exports.V0MessageRenderer = Message;
1037
+ exports.useStreamingMessage = useStreamingMessage;