@v0-sdk/react 0.1.0 → 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/LICENSE +13 -0
- package/README.md +305 -61
- package/dist/index.cjs +446 -244
- package/dist/index.d.ts +67 -21
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +429 -248
- package/package.json +5 -2
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
//
|
|
553
|
-
function
|
|
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
|
-
|
|
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
|
|
609
|
+
// Markdown data (type 0) - this is the main content
|
|
603
610
|
if (type === 0) {
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
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
|
-
//
|
|
616
|
+
// Metadata (type 1) - extract context but don't render
|
|
616
617
|
if (type === 1) {
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
return
|
|
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
|
-
//
|
|
634
|
-
return
|
|
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
|
-
|
|
648
|
-
|
|
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:
|
|
649
|
+
children: element
|
|
651
650
|
}, key);
|
|
652
651
|
}
|
|
653
|
-
if (Array.isArray(
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
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
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
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
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
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
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
return
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
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
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
const
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
}
|
|
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
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
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
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
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
|
-
*
|
|
932
|
+
* Component for rendering streaming message content from v0 API
|
|
809
933
|
*
|
|
810
934
|
* @example
|
|
811
935
|
* ```tsx
|
|
812
|
-
* import {
|
|
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
|
-
*
|
|
815
|
-
*
|
|
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
|
-
* <
|
|
819
|
-
*
|
|
820
|
-
*
|
|
821
|
-
*
|
|
822
|
-
*
|
|
823
|
-
*
|
|
824
|
-
*
|
|
825
|
-
*
|
|
826
|
-
*
|
|
827
|
-
*
|
|
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
|
-
*/
|
|
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;
|