@melony/react 0.1.22 → 0.1.24
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 +210 -69
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -6
- package/dist/index.d.ts +13 -6
- package/dist/index.js +211 -70
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -584,11 +584,22 @@ function Composer({
|
|
|
584
584
|
className,
|
|
585
585
|
options = [],
|
|
586
586
|
autoFocus = false,
|
|
587
|
-
defaultSelectedIds = []
|
|
587
|
+
defaultSelectedIds = [],
|
|
588
|
+
fileAttachments,
|
|
589
|
+
// Legacy props for backward compatibility
|
|
590
|
+
accept: legacyAccept,
|
|
591
|
+
maxFiles: legacyMaxFiles,
|
|
592
|
+
maxFileSize: legacyMaxFileSize
|
|
588
593
|
}) {
|
|
594
|
+
const enabled = fileAttachments?.enabled !== false;
|
|
595
|
+
const accept = fileAttachments?.accept ?? legacyAccept;
|
|
596
|
+
const maxFiles = fileAttachments?.maxFiles ?? legacyMaxFiles ?? 10;
|
|
597
|
+
const maxFileSize = fileAttachments?.maxFileSize ?? legacyMaxFileSize ?? 10 * 1024 * 1024;
|
|
589
598
|
const [selectedOptions, setSelectedOptions] = React10__namespace.default.useState(
|
|
590
599
|
() => new Set(defaultSelectedIds)
|
|
591
600
|
);
|
|
601
|
+
const [attachedFiles, setAttachedFiles] = React10__namespace.default.useState([]);
|
|
602
|
+
const fileInputRef = React10__namespace.default.useRef(null);
|
|
592
603
|
const toggleOption = (id, groupOptions, type = "multiple") => {
|
|
593
604
|
const next = new Set(selectedOptions);
|
|
594
605
|
if (type === "single") {
|
|
@@ -608,7 +619,34 @@ function Composer({
|
|
|
608
619
|
}
|
|
609
620
|
setSelectedOptions(next);
|
|
610
621
|
};
|
|
611
|
-
const
|
|
622
|
+
const handleFileSelect = (e) => {
|
|
623
|
+
const files = Array.from(e.target.files || []);
|
|
624
|
+
const validFiles = files.filter((file) => {
|
|
625
|
+
if (file.size > maxFileSize) {
|
|
626
|
+
console.warn(`File ${file.name} exceeds maximum size of ${maxFileSize} bytes`);
|
|
627
|
+
return false;
|
|
628
|
+
}
|
|
629
|
+
return true;
|
|
630
|
+
});
|
|
631
|
+
const remainingSlots = maxFiles - attachedFiles.length;
|
|
632
|
+
const filesToAdd = validFiles.slice(0, remainingSlots);
|
|
633
|
+
if (filesToAdd.length < validFiles.length) {
|
|
634
|
+
console.warn(`Only ${filesToAdd.length} files can be added (max: ${maxFiles})`);
|
|
635
|
+
}
|
|
636
|
+
setAttachedFiles((prev) => [...prev, ...filesToAdd]);
|
|
637
|
+
if (fileInputRef.current) {
|
|
638
|
+
fileInputRef.current.value = "";
|
|
639
|
+
}
|
|
640
|
+
};
|
|
641
|
+
const handleRemoveFile = (index) => {
|
|
642
|
+
setAttachedFiles((prev) => prev.filter((_, i) => i !== index));
|
|
643
|
+
};
|
|
644
|
+
const formatFileSize = (bytes) => {
|
|
645
|
+
if (bytes < 1024) return bytes + " B";
|
|
646
|
+
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KB";
|
|
647
|
+
return (bytes / (1024 * 1024)).toFixed(1) + " MB";
|
|
648
|
+
};
|
|
649
|
+
const handleInternalSubmit = async () => {
|
|
612
650
|
const state = {};
|
|
613
651
|
options.forEach((group) => {
|
|
614
652
|
const selectedInGroup = group.options.filter(
|
|
@@ -625,85 +663,178 @@ function Composer({
|
|
|
625
663
|
}
|
|
626
664
|
}
|
|
627
665
|
});
|
|
666
|
+
if (attachedFiles.length > 0) {
|
|
667
|
+
const filePromises = attachedFiles.map((file) => {
|
|
668
|
+
return new Promise((resolve, reject) => {
|
|
669
|
+
const reader = new FileReader();
|
|
670
|
+
reader.onload = () => {
|
|
671
|
+
try {
|
|
672
|
+
const base64 = reader.result;
|
|
673
|
+
if (!base64) {
|
|
674
|
+
reject(new Error("FileReader returned empty result"));
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
const base64Data = base64.includes(",") ? base64.split(",")[1] : base64;
|
|
678
|
+
resolve({
|
|
679
|
+
name: file.name,
|
|
680
|
+
type: file.type,
|
|
681
|
+
size: file.size,
|
|
682
|
+
data: base64Data
|
|
683
|
+
});
|
|
684
|
+
} catch (error) {
|
|
685
|
+
reject(error);
|
|
686
|
+
}
|
|
687
|
+
};
|
|
688
|
+
reader.onerror = (error) => {
|
|
689
|
+
reject(new Error(`Failed to read file ${file.name}: ${error}`));
|
|
690
|
+
};
|
|
691
|
+
reader.onabort = () => {
|
|
692
|
+
reject(new Error(`File read aborted for ${file.name}`));
|
|
693
|
+
};
|
|
694
|
+
reader.readAsDataURL(file);
|
|
695
|
+
});
|
|
696
|
+
});
|
|
697
|
+
try {
|
|
698
|
+
const convertedFiles = await Promise.all(filePromises);
|
|
699
|
+
if (convertedFiles.length > 0) {
|
|
700
|
+
state.files = convertedFiles;
|
|
701
|
+
}
|
|
702
|
+
} catch (error) {
|
|
703
|
+
console.error("Failed to convert files to base64:", error);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
628
706
|
onSubmit(state);
|
|
707
|
+
setAttachedFiles([]);
|
|
629
708
|
};
|
|
630
709
|
const handleKeyDown = (e) => {
|
|
631
710
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
632
711
|
e.preventDefault();
|
|
633
|
-
handleInternalSubmit();
|
|
712
|
+
handleInternalSubmit().catch(console.error);
|
|
634
713
|
}
|
|
635
714
|
};
|
|
636
|
-
return /* @__PURE__ */ jsxRuntime.
|
|
637
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
638
|
-
|
|
715
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("relative flex flex-col w-full", className), children: [
|
|
716
|
+
enabled && attachedFiles.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-2 flex flex-wrap gap-2", children: attachedFiles.map((file, index) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
717
|
+
"div",
|
|
639
718
|
{
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
className: "min-h-[44px] max-h-[200px] border-none bg-transparent focus-visible:ring-0 focus-visible:ring-offset-0 px-3 py-2 text-[15px] resize-none",
|
|
645
|
-
autoFocus
|
|
646
|
-
}
|
|
647
|
-
),
|
|
648
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between items-center px-1", children: [
|
|
649
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-1", children: options.map((group) => {
|
|
650
|
-
const selectedInGroup = group.options.filter(
|
|
651
|
-
(o) => selectedOptions.has(o.id)
|
|
652
|
-
);
|
|
653
|
-
const label = selectedInGroup.length === 0 ? group.label : selectedInGroup.length === 1 ? selectedInGroup[0].label : `${group.label} (${selectedInGroup.length})`;
|
|
654
|
-
const isSingle = group.type === "single";
|
|
655
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(DropdownMenu, { children: [
|
|
719
|
+
className: "flex items-center gap-2 px-3 py-1.5 bg-muted rounded-lg text-sm",
|
|
720
|
+
children: [
|
|
721
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate max-w-[200px]", title: file.name, children: file.name }),
|
|
722
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground text-xs", children: formatFileSize(file.size) }),
|
|
656
723
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
657
|
-
|
|
724
|
+
"button",
|
|
658
725
|
{
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
className: cn(
|
|
665
|
-
selectedInGroup.length > 0 ? "text-foreground bg-muted/50" : "text-muted-foreground"
|
|
666
|
-
),
|
|
667
|
-
children: [
|
|
668
|
-
label,
|
|
669
|
-
/* @__PURE__ */ jsxRuntime.jsx(ICONS.IconChevronDown, { className: "h-3 w-3 opacity-50" })
|
|
670
|
-
]
|
|
671
|
-
}
|
|
672
|
-
)
|
|
726
|
+
type: "button",
|
|
727
|
+
onClick: () => handleRemoveFile(index),
|
|
728
|
+
className: "ml-1 hover:bg-muted-foreground/20 rounded p-0.5 transition-colors",
|
|
729
|
+
"aria-label": "Remove file",
|
|
730
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(ICONS.IconX, { className: "h-3.5 w-3.5" })
|
|
673
731
|
}
|
|
674
|
-
)
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
{
|
|
681
|
-
checked: selectedOptions.has(option.id),
|
|
682
|
-
onCheckedChange: () => toggleOption(
|
|
683
|
-
option.id,
|
|
684
|
-
group.options,
|
|
685
|
-
isSingle ? "single" : "multiple"
|
|
686
|
-
),
|
|
687
|
-
onSelect: (e) => e.preventDefault(),
|
|
688
|
-
children: option.label
|
|
689
|
-
},
|
|
690
|
-
option.id
|
|
691
|
-
))
|
|
692
|
-
] }) })
|
|
693
|
-
] }, group.id);
|
|
694
|
-
}) }),
|
|
732
|
+
)
|
|
733
|
+
]
|
|
734
|
+
},
|
|
735
|
+
index
|
|
736
|
+
)) }),
|
|
737
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex flex-col w-full border-input border-[1.5px] rounded-3xl bg-background shadow-sm focus-within:border-ring transition-all p-2", children: [
|
|
695
738
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
696
|
-
|
|
739
|
+
Textarea,
|
|
697
740
|
{
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
741
|
+
value,
|
|
742
|
+
onChange: (e) => onChange(e.target.value),
|
|
743
|
+
onKeyDown: handleKeyDown,
|
|
744
|
+
placeholder,
|
|
745
|
+
className: "min-h-[44px] max-h-[200px] border-none bg-transparent focus-visible:ring-0 focus-visible:ring-offset-0 px-3 py-2 text-[15px] resize-none",
|
|
746
|
+
autoFocus
|
|
703
747
|
}
|
|
704
|
-
)
|
|
748
|
+
),
|
|
749
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between items-center px-1", children: [
|
|
750
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
|
|
751
|
+
enabled && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
752
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
753
|
+
"input",
|
|
754
|
+
{
|
|
755
|
+
ref: fileInputRef,
|
|
756
|
+
type: "file",
|
|
757
|
+
multiple: true,
|
|
758
|
+
accept,
|
|
759
|
+
onChange: handleFileSelect,
|
|
760
|
+
className: "hidden",
|
|
761
|
+
disabled: isLoading || attachedFiles.length >= maxFiles
|
|
762
|
+
}
|
|
763
|
+
),
|
|
764
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
765
|
+
Button,
|
|
766
|
+
{
|
|
767
|
+
type: "button",
|
|
768
|
+
variant: "ghost",
|
|
769
|
+
size: "sm",
|
|
770
|
+
onClick: () => fileInputRef.current?.click(),
|
|
771
|
+
disabled: isLoading || attachedFiles.length >= maxFiles,
|
|
772
|
+
className: "text-muted-foreground",
|
|
773
|
+
title: attachedFiles.length >= maxFiles ? `Maximum ${maxFiles} files allowed` : "Attach file",
|
|
774
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(ICONS.IconPaperclip, { className: "h-4 w-4" })
|
|
775
|
+
}
|
|
776
|
+
)
|
|
777
|
+
] }),
|
|
778
|
+
options.map((group) => {
|
|
779
|
+
const selectedInGroup = group.options.filter(
|
|
780
|
+
(o) => selectedOptions.has(o.id)
|
|
781
|
+
);
|
|
782
|
+
const label = selectedInGroup.length === 0 ? group.label : selectedInGroup.length === 1 ? selectedInGroup[0].label : `${group.label} (${selectedInGroup.length})`;
|
|
783
|
+
const isSingle = group.type === "single";
|
|
784
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(DropdownMenu, { children: [
|
|
785
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
786
|
+
DropdownMenuTrigger,
|
|
787
|
+
{
|
|
788
|
+
render: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
789
|
+
Button,
|
|
790
|
+
{
|
|
791
|
+
variant: "ghost",
|
|
792
|
+
size: "sm",
|
|
793
|
+
className: cn(
|
|
794
|
+
selectedInGroup.length > 0 ? "text-foreground bg-muted/50" : "text-muted-foreground"
|
|
795
|
+
),
|
|
796
|
+
children: [
|
|
797
|
+
label,
|
|
798
|
+
/* @__PURE__ */ jsxRuntime.jsx(ICONS.IconChevronDown, { className: "h-3 w-3 opacity-50" })
|
|
799
|
+
]
|
|
800
|
+
}
|
|
801
|
+
)
|
|
802
|
+
}
|
|
803
|
+
),
|
|
804
|
+
/* @__PURE__ */ jsxRuntime.jsx(DropdownMenuContent, { align: "start", className: "w-56", children: /* @__PURE__ */ jsxRuntime.jsxs(DropdownMenuGroup, { children: [
|
|
805
|
+
/* @__PURE__ */ jsxRuntime.jsx(DropdownMenuLabel, { children: group.label }),
|
|
806
|
+
/* @__PURE__ */ jsxRuntime.jsx(DropdownMenuSeparator, {}),
|
|
807
|
+
group.options.map((option) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
808
|
+
DropdownMenuCheckboxItem,
|
|
809
|
+
{
|
|
810
|
+
checked: selectedOptions.has(option.id),
|
|
811
|
+
onCheckedChange: () => toggleOption(
|
|
812
|
+
option.id,
|
|
813
|
+
group.options,
|
|
814
|
+
isSingle ? "single" : "multiple"
|
|
815
|
+
),
|
|
816
|
+
onSelect: (e) => e.preventDefault(),
|
|
817
|
+
children: option.label
|
|
818
|
+
},
|
|
819
|
+
option.id
|
|
820
|
+
))
|
|
821
|
+
] }) })
|
|
822
|
+
] }, group.id);
|
|
823
|
+
})
|
|
824
|
+
] }),
|
|
825
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
826
|
+
Button,
|
|
827
|
+
{
|
|
828
|
+
type: "submit",
|
|
829
|
+
disabled: !value.trim() && attachedFiles.length === 0 && !isLoading || isLoading,
|
|
830
|
+
size: "icon-lg",
|
|
831
|
+
onClick: () => handleInternalSubmit().catch(console.error),
|
|
832
|
+
children: isLoading ? /* @__PURE__ */ jsxRuntime.jsx(ICONS.IconLoader2, { className: "h-5 w-5 animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(ICONS.IconArrowUp, { className: "h-5 w-5" })
|
|
833
|
+
}
|
|
834
|
+
)
|
|
835
|
+
] })
|
|
705
836
|
] })
|
|
706
|
-
] })
|
|
837
|
+
] });
|
|
707
838
|
}
|
|
708
839
|
function Card({
|
|
709
840
|
className,
|
|
@@ -2362,6 +2493,14 @@ function Thread({
|
|
|
2362
2493
|
});
|
|
2363
2494
|
const starterPrompts = localStarterPrompts ?? config?.starterPrompts;
|
|
2364
2495
|
const options = localOptions ?? config?.options;
|
|
2496
|
+
const allDefaultSelectedIds = React10.useMemo(() => {
|
|
2497
|
+
const defaultSelectedIdsFromOptions = options?.flatMap(
|
|
2498
|
+
(group) => group.defaultSelectedIds ?? []
|
|
2499
|
+
) ?? [];
|
|
2500
|
+
return [
|
|
2501
|
+
.../* @__PURE__ */ new Set([...defaultSelectedIdsFromOptions, ...defaultSelectedIds ?? []])
|
|
2502
|
+
];
|
|
2503
|
+
}, [options, defaultSelectedIds]);
|
|
2365
2504
|
const [input, setInput] = React10.useState("");
|
|
2366
2505
|
const messagesEndRef = React10.useRef(null);
|
|
2367
2506
|
React10.useEffect(() => {
|
|
@@ -2369,13 +2508,14 @@ function Thread({
|
|
|
2369
2508
|
}, [messages]);
|
|
2370
2509
|
const handleSubmit = async (state, overrideInput) => {
|
|
2371
2510
|
const text = (overrideInput ?? input).trim();
|
|
2372
|
-
|
|
2511
|
+
const hasFiles = state?.files && Array.isArray(state.files) && state.files.length > 0;
|
|
2512
|
+
if (!text && !hasFiles || isLoading) return;
|
|
2373
2513
|
if (!overrideInput) setInput("");
|
|
2374
2514
|
await sendEvent(
|
|
2375
2515
|
{
|
|
2376
2516
|
type: "text",
|
|
2377
2517
|
role: "user",
|
|
2378
|
-
data: { content: text }
|
|
2518
|
+
data: { content: text || "" }
|
|
2379
2519
|
},
|
|
2380
2520
|
{ state: { ...state, threadId: activeThreadId ?? void 0 } }
|
|
2381
2521
|
);
|
|
@@ -2433,7 +2573,8 @@ function Thread({
|
|
|
2433
2573
|
isLoading,
|
|
2434
2574
|
options,
|
|
2435
2575
|
autoFocus,
|
|
2436
|
-
defaultSelectedIds
|
|
2576
|
+
defaultSelectedIds: allDefaultSelectedIds,
|
|
2577
|
+
fileAttachments: config?.fileAttachments
|
|
2437
2578
|
}
|
|
2438
2579
|
) }) })
|
|
2439
2580
|
]
|