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