@copilotz/chat-ui 0.1.9 → 0.1.11
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 +246 -48
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +23 -1
- package/dist/index.d.ts +23 -1
- package/dist/index.js +284 -87
- package/dist/index.js.map +1 -1
- package/dist/styles.css +21 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/components/chat/ChatUI.tsx
|
|
2
|
-
import { useState as useState8, useEffect as
|
|
2
|
+
import { useState as useState8, useEffect as useEffect10, useRef as useRef6, useCallback as useCallback4, useMemo as useMemo4 } from "react";
|
|
3
3
|
import { useVirtualizer } from "@tanstack/react-virtual";
|
|
4
4
|
|
|
5
5
|
// src/config/chatConfig.ts
|
|
@@ -64,7 +64,9 @@ var defaultChatConfig = {
|
|
|
64
64
|
daysAgo: "days ago",
|
|
65
65
|
inputHelpText: "Press Enter to send, Shift+Enter to add a new line.",
|
|
66
66
|
thinking: "Thinking...",
|
|
67
|
-
defaultThreadName: "Main Thread"
|
|
67
|
+
defaultThreadName: "Main Thread",
|
|
68
|
+
showMoreMessage: "Show more",
|
|
69
|
+
showLessMessage: "Show less"
|
|
68
70
|
},
|
|
69
71
|
features: {
|
|
70
72
|
enableThreads: true,
|
|
@@ -83,7 +85,12 @@ var defaultChatConfig = {
|
|
|
83
85
|
showTimestamps: false,
|
|
84
86
|
showAvatars: true,
|
|
85
87
|
compactMode: false,
|
|
86
|
-
showWordCount: false
|
|
88
|
+
showWordCount: false,
|
|
89
|
+
collapseLongMessages: false,
|
|
90
|
+
collapseLongMessagesForUserOnly: false,
|
|
91
|
+
longMessagePreviewChars: 4e3,
|
|
92
|
+
longMessageChunkChars: 12e3,
|
|
93
|
+
renderUserMarkdown: true
|
|
87
94
|
},
|
|
88
95
|
customComponent: {},
|
|
89
96
|
headerActions: null
|
|
@@ -238,7 +245,7 @@ var configUtils = {
|
|
|
238
245
|
};
|
|
239
246
|
|
|
240
247
|
// src/components/chat/Message.tsx
|
|
241
|
-
import { useState,
|
|
248
|
+
import React, { useState, useMemo, useEffect, memo } from "react";
|
|
242
249
|
import ReactMarkdown from "react-markdown";
|
|
243
250
|
import remarkGfm from "remark-gfm";
|
|
244
251
|
import rehypeHighlight from "rehype-highlight";
|
|
@@ -271,6 +278,24 @@ var formatDate = (timestamp, labels) => {
|
|
|
271
278
|
});
|
|
272
279
|
}
|
|
273
280
|
};
|
|
281
|
+
var createObjectUrlFromDataUrl = (dataUrl) => {
|
|
282
|
+
const match = dataUrl.match(/^data:(.+?);base64,(.+)$/s);
|
|
283
|
+
if (!match) {
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
try {
|
|
287
|
+
const [, mimeType, base64] = match;
|
|
288
|
+
const binary = atob(base64);
|
|
289
|
+
const bytes = new Uint8Array(binary.length);
|
|
290
|
+
for (let i = 0; i < binary.length; i += 1) {
|
|
291
|
+
bytes[i] = binary.charCodeAt(i);
|
|
292
|
+
}
|
|
293
|
+
const blob = new Blob([bytes], { type: mimeType || "application/octet-stream" });
|
|
294
|
+
return URL.createObjectURL(blob);
|
|
295
|
+
} catch {
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
};
|
|
274
299
|
|
|
275
300
|
// src/components/ui/button.tsx
|
|
276
301
|
import { jsx } from "react/jsx-runtime";
|
|
@@ -569,21 +594,89 @@ var markdownComponents = {
|
|
|
569
594
|
var remarkPluginsDefault = [remarkGfm];
|
|
570
595
|
var rehypePluginsDefault = [rehypeHighlight];
|
|
571
596
|
var rehypePluginsEmpty = [];
|
|
597
|
+
var getPlainTextChunks = (content, chunkSize) => {
|
|
598
|
+
if (chunkSize <= 0 || content.length <= chunkSize) {
|
|
599
|
+
return [content];
|
|
600
|
+
}
|
|
601
|
+
const chunks = [];
|
|
602
|
+
let start = 0;
|
|
603
|
+
while (start < content.length) {
|
|
604
|
+
let end = Math.min(start + chunkSize, content.length);
|
|
605
|
+
if (end < content.length) {
|
|
606
|
+
const splitAt = content.lastIndexOf("\n", end);
|
|
607
|
+
if (splitAt > start + Math.floor(chunkSize / 2)) {
|
|
608
|
+
end = splitAt + 1;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
chunks.push(content.slice(start, end));
|
|
612
|
+
start = end;
|
|
613
|
+
}
|
|
614
|
+
return chunks;
|
|
615
|
+
};
|
|
616
|
+
var hasCodeBlocks = (content) => /(^|\n)(```|~~~)/.test(content);
|
|
617
|
+
var getCollapsedPreview = (content, previewChars, previewOverride) => {
|
|
618
|
+
if (previewOverride && previewOverride.trim().length > 0) {
|
|
619
|
+
const normalizedPreview = previewOverride.trimEnd();
|
|
620
|
+
return normalizedPreview.endsWith("...") ? normalizedPreview : `${normalizedPreview}...`;
|
|
621
|
+
}
|
|
622
|
+
if (content.length <= previewChars) {
|
|
623
|
+
return content;
|
|
624
|
+
}
|
|
625
|
+
return `${content.slice(0, previewChars).trimEnd()}...`;
|
|
626
|
+
};
|
|
627
|
+
var LongContentShell = memo(function LongContentShell2({ children, className, style }) {
|
|
628
|
+
return /* @__PURE__ */ jsx7("div", { className, style, children });
|
|
629
|
+
});
|
|
630
|
+
var PlainTextContent = memo(function PlainTextContent2({
|
|
631
|
+
content,
|
|
632
|
+
className = "",
|
|
633
|
+
chunkSize = 12e3,
|
|
634
|
+
style
|
|
635
|
+
}) {
|
|
636
|
+
const chunks = useMemo(() => getPlainTextChunks(content, chunkSize), [content, chunkSize]);
|
|
637
|
+
return /* @__PURE__ */ jsx7(
|
|
638
|
+
LongContentShell,
|
|
639
|
+
{
|
|
640
|
+
className: `text-sm leading-6 whitespace-pre-wrap break-words ${className}`.trim(),
|
|
641
|
+
style,
|
|
642
|
+
children: chunks.map((chunk, index) => /* @__PURE__ */ jsx7(React.Fragment, { children: chunk }, index))
|
|
643
|
+
}
|
|
644
|
+
);
|
|
645
|
+
});
|
|
572
646
|
var StreamingText = memo(function StreamingText2({
|
|
573
647
|
content,
|
|
574
648
|
isStreaming = false,
|
|
575
649
|
thinkingLabel = "Thinking...",
|
|
576
|
-
className = ""
|
|
650
|
+
className = "",
|
|
651
|
+
renderMarkdown = true,
|
|
652
|
+
plainTextChunkChars = 12e3,
|
|
653
|
+
contentStyle
|
|
577
654
|
}) {
|
|
578
655
|
const hasContent = content.trim().length > 0;
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
656
|
+
const enableSyntaxHighlight = renderMarkdown && !isStreaming && hasCodeBlocks(content);
|
|
657
|
+
return /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
658
|
+
hasContent ? renderMarkdown ? /* @__PURE__ */ jsx7(
|
|
659
|
+
LongContentShell,
|
|
582
660
|
{
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
661
|
+
className: `prose prose-sm max-w-none dark:prose-invert break-words ${className}`.trim(),
|
|
662
|
+
style: contentStyle,
|
|
663
|
+
children: /* @__PURE__ */ jsx7(
|
|
664
|
+
ReactMarkdown,
|
|
665
|
+
{
|
|
666
|
+
remarkPlugins: remarkPluginsDefault,
|
|
667
|
+
rehypePlugins: enableSyntaxHighlight ? rehypePluginsDefault : rehypePluginsEmpty,
|
|
668
|
+
components: markdownComponents,
|
|
669
|
+
children: content
|
|
670
|
+
}
|
|
671
|
+
)
|
|
672
|
+
}
|
|
673
|
+
) : /* @__PURE__ */ jsx7(
|
|
674
|
+
PlainTextContent,
|
|
675
|
+
{
|
|
676
|
+
content,
|
|
677
|
+
className,
|
|
678
|
+
chunkSize: plainTextChunkChars,
|
|
679
|
+
style: contentStyle
|
|
587
680
|
}
|
|
588
681
|
) : isStreaming ? (
|
|
589
682
|
// Show thinking indicator while waiting for first token
|
|
@@ -593,26 +686,22 @@ var StreamingText = memo(function StreamingText2({
|
|
|
593
686
|
] });
|
|
594
687
|
});
|
|
595
688
|
var MediaRenderer = memo(function MediaRenderer2({ attachment }) {
|
|
596
|
-
const [
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
if (isPlaying) {
|
|
602
|
-
audioRef.current.pause();
|
|
603
|
-
} else {
|
|
604
|
-
audioRef.current.play();
|
|
605
|
-
}
|
|
606
|
-
setIsPlaying(!isPlaying);
|
|
607
|
-
} else if (attachment.kind === "video" && videoRef.current) {
|
|
608
|
-
if (isPlaying) {
|
|
609
|
-
videoRef.current.pause();
|
|
610
|
-
} else {
|
|
611
|
-
videoRef.current.play();
|
|
612
|
-
}
|
|
613
|
-
setIsPlaying(!isPlaying);
|
|
689
|
+
const [audioPlaybackSrc, setAudioPlaybackSrc] = useState(attachment.dataUrl);
|
|
690
|
+
useEffect(() => {
|
|
691
|
+
if (attachment.kind !== "audio" || !attachment.dataUrl.startsWith("data:")) {
|
|
692
|
+
setAudioPlaybackSrc(attachment.dataUrl);
|
|
693
|
+
return;
|
|
614
694
|
}
|
|
615
|
-
|
|
695
|
+
const objectUrl = createObjectUrlFromDataUrl(attachment.dataUrl);
|
|
696
|
+
if (!objectUrl) {
|
|
697
|
+
setAudioPlaybackSrc(attachment.dataUrl);
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
setAudioPlaybackSrc(objectUrl);
|
|
701
|
+
return () => {
|
|
702
|
+
URL.revokeObjectURL(objectUrl);
|
|
703
|
+
};
|
|
704
|
+
}, [attachment.kind, attachment.dataUrl]);
|
|
616
705
|
const formatDuration = (ms) => {
|
|
617
706
|
if (!ms) return "";
|
|
618
707
|
const seconds = Math.floor(ms / 1e3);
|
|
@@ -637,13 +726,10 @@ var MediaRenderer = memo(function MediaRenderer2({ attachment }) {
|
|
|
637
726
|
return /* @__PURE__ */ jsx7("div", { className: "flex w-full max-w-md py-0 min-w-64 items-center gap-3", children: /* @__PURE__ */ jsx7(
|
|
638
727
|
"audio",
|
|
639
728
|
{
|
|
640
|
-
ref: audioRef,
|
|
641
|
-
src: attachment.dataUrl,
|
|
642
|
-
onPlay: () => setIsPlaying(true),
|
|
643
|
-
onPause: () => setIsPlaying(false),
|
|
644
|
-
onEnded: () => setIsPlaying(false),
|
|
645
729
|
className: "w-full mt-2",
|
|
646
|
-
|
|
730
|
+
preload: "metadata",
|
|
731
|
+
controls: true,
|
|
732
|
+
children: /* @__PURE__ */ jsx7("source", { src: audioPlaybackSrc, type: attachment.mimeType })
|
|
647
733
|
}
|
|
648
734
|
) });
|
|
649
735
|
case "video":
|
|
@@ -651,14 +737,10 @@ var MediaRenderer = memo(function MediaRenderer2({ attachment }) {
|
|
|
651
737
|
/* @__PURE__ */ jsx7(
|
|
652
738
|
"video",
|
|
653
739
|
{
|
|
654
|
-
ref: videoRef,
|
|
655
740
|
src: attachment.dataUrl,
|
|
656
741
|
poster: attachment.poster,
|
|
657
742
|
controls: true,
|
|
658
|
-
className: "w-full h-auto"
|
|
659
|
-
onPlay: () => setIsPlaying(true),
|
|
660
|
-
onPause: () => setIsPlaying(false),
|
|
661
|
-
onEnded: () => setIsPlaying(false)
|
|
743
|
+
className: "w-full h-auto"
|
|
662
744
|
}
|
|
663
745
|
),
|
|
664
746
|
attachment.fileName && /* @__PURE__ */ jsx7("div", { className: "absolute bottom-0 left-0 right-0 bg-black/50 text-white text-xs p-2", children: attachment.fileName })
|
|
@@ -753,6 +835,15 @@ var arePropsEqual = (prevProps, nextProps) => {
|
|
|
753
835
|
if (prevProps.className !== nextProps.className) return false;
|
|
754
836
|
if (prevProps.toolUsedLabel !== nextProps.toolUsedLabel) return false;
|
|
755
837
|
if (prevProps.thinkingLabel !== nextProps.thinkingLabel) return false;
|
|
838
|
+
if (prevProps.showMoreLabel !== nextProps.showMoreLabel) return false;
|
|
839
|
+
if (prevProps.showLessLabel !== nextProps.showLessLabel) return false;
|
|
840
|
+
if (prevProps.collapseLongMessages !== nextProps.collapseLongMessages) return false;
|
|
841
|
+
if (prevProps.collapseLongMessagesForUserOnly !== nextProps.collapseLongMessagesForUserOnly) return false;
|
|
842
|
+
if (prevProps.longMessagePreviewChars !== nextProps.longMessagePreviewChars) return false;
|
|
843
|
+
if (prevProps.longMessageChunkChars !== nextProps.longMessageChunkChars) return false;
|
|
844
|
+
if (prevProps.renderUserMarkdown !== nextProps.renderUserMarkdown) return false;
|
|
845
|
+
if (prevProps.isExpanded !== nextProps.isExpanded) return false;
|
|
846
|
+
if (prevProps.onToggleExpanded !== nextProps.onToggleExpanded) return false;
|
|
756
847
|
if (prevProps.isGrouped !== nextProps.isGrouped) return false;
|
|
757
848
|
if (prevProps.assistantAvatar !== nextProps.assistantAvatar) return false;
|
|
758
849
|
return true;
|
|
@@ -775,6 +866,15 @@ var Message = memo(({
|
|
|
775
866
|
className = "",
|
|
776
867
|
toolUsedLabel,
|
|
777
868
|
thinkingLabel = "Thinking...",
|
|
869
|
+
showMoreLabel = "Show more",
|
|
870
|
+
showLessLabel = "Show less",
|
|
871
|
+
collapseLongMessages = false,
|
|
872
|
+
collapseLongMessagesForUserOnly = false,
|
|
873
|
+
longMessagePreviewChars = 4e3,
|
|
874
|
+
longMessageChunkChars = 12e3,
|
|
875
|
+
renderUserMarkdown = true,
|
|
876
|
+
isExpanded = false,
|
|
877
|
+
onToggleExpanded,
|
|
778
878
|
isGrouped = false
|
|
779
879
|
}) => {
|
|
780
880
|
const [isEditing, setIsEditing] = useState(false);
|
|
@@ -784,6 +884,18 @@ var Message = memo(({
|
|
|
784
884
|
const messageIsUser = isUser ?? message.role === "user";
|
|
785
885
|
const canEdit = enableEdit && messageIsUser;
|
|
786
886
|
const canRegenerate = enableRegenerate && !messageIsUser;
|
|
887
|
+
const normalizedPreviewChars = Math.max(longMessagePreviewChars, 1);
|
|
888
|
+
const normalizedChunkChars = Math.max(longMessageChunkChars, 1);
|
|
889
|
+
const previewOverride = typeof message.metadata?.previewContent === "string" ? message.metadata.previewContent : void 0;
|
|
890
|
+
const canCollapseMessage = collapseLongMessages && !message.isStreaming && message.content.length > normalizedPreviewChars && (!collapseLongMessagesForUserOnly || messageIsUser);
|
|
891
|
+
const isCollapsed = canCollapseMessage && !isExpanded;
|
|
892
|
+
const contentToRender = isCollapsed ? getCollapsedPreview(message.content, normalizedPreviewChars, previewOverride) : message.content;
|
|
893
|
+
const shouldRenderMarkdown = !isCollapsed && (!messageIsUser || renderUserMarkdown);
|
|
894
|
+
const shouldApplyLargeContentContainment = !isCollapsed && message.content.length > normalizedChunkChars;
|
|
895
|
+
const contentStyle = shouldApplyLargeContentContainment ? {
|
|
896
|
+
contentVisibility: "auto",
|
|
897
|
+
containIntrinsicSize: "1px 400px"
|
|
898
|
+
} : void 0;
|
|
787
899
|
const handleCopy = async () => {
|
|
788
900
|
try {
|
|
789
901
|
await navigator.clipboard.writeText(message.content);
|
|
@@ -812,6 +924,9 @@ var Message = memo(({
|
|
|
812
924
|
const handleRegenerate = () => {
|
|
813
925
|
onAction?.({ action: "regenerate", messageId: message.id });
|
|
814
926
|
};
|
|
927
|
+
const handleToggleExpanded = () => {
|
|
928
|
+
onToggleExpanded?.(message.id);
|
|
929
|
+
};
|
|
815
930
|
const formatTime = (timestamp) => {
|
|
816
931
|
return new Date(timestamp).toLocaleTimeString("pt-BR", {
|
|
817
932
|
hour: "2-digit",
|
|
@@ -862,11 +977,26 @@ var Message = memo(({
|
|
|
862
977
|
/* @__PURE__ */ jsx7(
|
|
863
978
|
StreamingText,
|
|
864
979
|
{
|
|
865
|
-
content:
|
|
980
|
+
content: contentToRender,
|
|
866
981
|
isStreaming: message.isStreaming,
|
|
867
|
-
thinkingLabel
|
|
982
|
+
thinkingLabel,
|
|
983
|
+
renderMarkdown: shouldRenderMarkdown,
|
|
984
|
+
plainTextChunkChars: normalizedChunkChars,
|
|
985
|
+
contentStyle
|
|
868
986
|
}
|
|
869
987
|
),
|
|
988
|
+
canCollapseMessage && /* @__PURE__ */ jsx7("div", { className: "mt-3", children: /* @__PURE__ */ jsx7(
|
|
989
|
+
Button,
|
|
990
|
+
{
|
|
991
|
+
type: "button",
|
|
992
|
+
variant: "ghost",
|
|
993
|
+
size: "sm",
|
|
994
|
+
className: "h-auto px-0 text-xs font-medium text-current hover:bg-transparent hover:opacity-80",
|
|
995
|
+
"aria-expanded": !isCollapsed,
|
|
996
|
+
onClick: handleToggleExpanded,
|
|
997
|
+
children: isCollapsed ? showMoreLabel : showLessLabel
|
|
998
|
+
}
|
|
999
|
+
) }),
|
|
870
1000
|
message.attachments && message.attachments.length > 0 && /* @__PURE__ */ jsx7("div", { className: "mt-3 space-y-2", children: message.attachments.map((attachment, index) => /* @__PURE__ */ jsx7(MediaRenderer, { attachment }, index)) })
|
|
871
1001
|
] }),
|
|
872
1002
|
!isEditing && (showActions || copied) && /* @__PURE__ */ jsxs2("div", { className: `absolute -top-2 flex gap-1 ${messageIsUser ? "-left-2" : "-right-2"}`, children: [
|
|
@@ -917,7 +1047,7 @@ var Message = memo(({
|
|
|
917
1047
|
}, arePropsEqual);
|
|
918
1048
|
|
|
919
1049
|
// src/components/chat/Sidebar.tsx
|
|
920
|
-
import { useState as useState4, useRef as
|
|
1050
|
+
import { useState as useState4, useRef as useRef4, useEffect as useEffect7 } from "react";
|
|
921
1051
|
|
|
922
1052
|
// src/components/ui/input.tsx
|
|
923
1053
|
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
@@ -2166,9 +2296,9 @@ var Sidebar2 = ({
|
|
|
2166
2296
|
const [deleteThreadId, setDeleteThreadId] = useState4(null);
|
|
2167
2297
|
const [editingThreadId, setEditingThreadId] = useState4(null);
|
|
2168
2298
|
const [editTitle, setEditTitle] = useState4("");
|
|
2169
|
-
const inputRef =
|
|
2299
|
+
const inputRef = useRef4(null);
|
|
2170
2300
|
const { setOpen } = useSidebar();
|
|
2171
|
-
|
|
2301
|
+
useEffect7(() => {
|
|
2172
2302
|
if (editingThreadId && inputRef.current) {
|
|
2173
2303
|
inputRef.current.focus();
|
|
2174
2304
|
inputRef.current.select();
|
|
@@ -2585,10 +2715,10 @@ var ChatHeader = ({
|
|
|
2585
2715
|
};
|
|
2586
2716
|
|
|
2587
2717
|
// src/components/chat/ChatInput.tsx
|
|
2588
|
-
import { useState as useState6, useRef as
|
|
2718
|
+
import { useState as useState6, useRef as useRef5, useCallback as useCallback3, useEffect as useEffect9, memo as memo2 } from "react";
|
|
2589
2719
|
|
|
2590
2720
|
// src/components/chat/UserContext.tsx
|
|
2591
|
-
import { createContext as createContext2, useCallback as useCallback2, useContext as useContext2, useEffect as
|
|
2721
|
+
import { createContext as createContext2, useCallback as useCallback2, useContext as useContext2, useEffect as useEffect8, useMemo as useMemo3, useState as useState5 } from "react";
|
|
2592
2722
|
import { jsx as jsx19 } from "react/jsx-runtime";
|
|
2593
2723
|
var Ctx = createContext2(void 0);
|
|
2594
2724
|
var ChatUserContextProvider = ({ children, initial }) => {
|
|
@@ -2596,7 +2726,7 @@ var ChatUserContextProvider = ({ children, initial }) => {
|
|
|
2596
2726
|
updatedAt: Date.now(),
|
|
2597
2727
|
...initial ?? {}
|
|
2598
2728
|
}));
|
|
2599
|
-
|
|
2729
|
+
useEffect8(() => {
|
|
2600
2730
|
if (!initial) return;
|
|
2601
2731
|
setCtx((prev) => ({
|
|
2602
2732
|
...prev,
|
|
@@ -2610,7 +2740,7 @@ var ChatUserContextProvider = ({ children, initial }) => {
|
|
|
2610
2740
|
return { ...prev, ...partial, updatedAt: Date.now() };
|
|
2611
2741
|
});
|
|
2612
2742
|
}, []);
|
|
2613
|
-
const value =
|
|
2743
|
+
const value = useMemo3(() => ({
|
|
2614
2744
|
context: ctx,
|
|
2615
2745
|
setContext: setPartial,
|
|
2616
2746
|
resetContext: () => setCtx({ updatedAt: Date.now() })
|
|
@@ -2662,8 +2792,8 @@ import {
|
|
|
2662
2792
|
FileText,
|
|
2663
2793
|
X as X2,
|
|
2664
2794
|
Square,
|
|
2665
|
-
Play
|
|
2666
|
-
Pause
|
|
2795
|
+
Play,
|
|
2796
|
+
Pause,
|
|
2667
2797
|
Loader2
|
|
2668
2798
|
} from "lucide-react";
|
|
2669
2799
|
import { Fragment as Fragment4, jsx as jsx21, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
@@ -2728,7 +2858,23 @@ var FileUploadItem = memo2(function FileUploadItem2({ file, progress, onCancel }
|
|
|
2728
2858
|
});
|
|
2729
2859
|
var AttachmentPreview = memo2(function AttachmentPreview2({ attachment, onRemove }) {
|
|
2730
2860
|
const [isPlaying, setIsPlaying] = useState6(false);
|
|
2731
|
-
const
|
|
2861
|
+
const [audioPlaybackSrc, setAudioPlaybackSrc] = useState6(attachment.dataUrl);
|
|
2862
|
+
const audioRef = useRef5(null);
|
|
2863
|
+
useEffect9(() => {
|
|
2864
|
+
if (attachment.kind !== "audio" || !attachment.dataUrl.startsWith("data:")) {
|
|
2865
|
+
setAudioPlaybackSrc(attachment.dataUrl);
|
|
2866
|
+
return;
|
|
2867
|
+
}
|
|
2868
|
+
const objectUrl = createObjectUrlFromDataUrl(attachment.dataUrl);
|
|
2869
|
+
if (!objectUrl) {
|
|
2870
|
+
setAudioPlaybackSrc(attachment.dataUrl);
|
|
2871
|
+
return;
|
|
2872
|
+
}
|
|
2873
|
+
setAudioPlaybackSrc(objectUrl);
|
|
2874
|
+
return () => {
|
|
2875
|
+
URL.revokeObjectURL(objectUrl);
|
|
2876
|
+
};
|
|
2877
|
+
}, [attachment.kind, attachment.dataUrl]);
|
|
2732
2878
|
const handlePlayPause = () => {
|
|
2733
2879
|
if (audioRef.current) {
|
|
2734
2880
|
if (isPlaying) {
|
|
@@ -2796,7 +2942,7 @@ var AttachmentPreview = memo2(function AttachmentPreview2({ attachment, onRemove
|
|
|
2796
2942
|
size: "icon",
|
|
2797
2943
|
className: "h-8 w-8",
|
|
2798
2944
|
onClick: handlePlayPause,
|
|
2799
|
-
children: isPlaying ? /* @__PURE__ */ jsx21(
|
|
2945
|
+
children: isPlaying ? /* @__PURE__ */ jsx21(Pause, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx21(Play, { className: "h-3 w-3" })
|
|
2800
2946
|
}
|
|
2801
2947
|
),
|
|
2802
2948
|
/* @__PURE__ */ jsxs11("div", { className: "flex-1", children: [
|
|
@@ -2807,10 +2953,11 @@ var AttachmentPreview = memo2(function AttachmentPreview2({ attachment, onRemove
|
|
|
2807
2953
|
"audio",
|
|
2808
2954
|
{
|
|
2809
2955
|
ref: audioRef,
|
|
2810
|
-
src: attachment.dataUrl,
|
|
2811
2956
|
onPlay: () => setIsPlaying(true),
|
|
2812
2957
|
onPause: () => setIsPlaying(false),
|
|
2813
|
-
onEnded: () => setIsPlaying(false)
|
|
2958
|
+
onEnded: () => setIsPlaying(false),
|
|
2959
|
+
preload: "metadata",
|
|
2960
|
+
children: /* @__PURE__ */ jsx21("source", { src: audioPlaybackSrc, type: attachment.mimeType })
|
|
2814
2961
|
}
|
|
2815
2962
|
),
|
|
2816
2963
|
/* @__PURE__ */ jsx21(
|
|
@@ -2905,13 +3052,13 @@ var ChatInput = memo2(function ChatInput2({
|
|
|
2905
3052
|
const { setContext } = useChatUserContext();
|
|
2906
3053
|
const [recordingDuration, setRecordingDuration] = useState6(0);
|
|
2907
3054
|
const [uploadProgress, setUploadProgress] = useState6(/* @__PURE__ */ new Map());
|
|
2908
|
-
const textareaRef =
|
|
2909
|
-
const fileInputRef =
|
|
2910
|
-
const mediaRecorderRef =
|
|
2911
|
-
const recordingStartTime =
|
|
2912
|
-
const recordingInterval =
|
|
2913
|
-
const mediaStreamRef =
|
|
2914
|
-
|
|
3055
|
+
const textareaRef = useRef5(null);
|
|
3056
|
+
const fileInputRef = useRef5(null);
|
|
3057
|
+
const mediaRecorderRef = useRef5(null);
|
|
3058
|
+
const recordingStartTime = useRef5(0);
|
|
3059
|
+
const recordingInterval = useRef5(null);
|
|
3060
|
+
const mediaStreamRef = useRef5(null);
|
|
3061
|
+
useEffect9(() => {
|
|
2915
3062
|
return () => {
|
|
2916
3063
|
if (mediaStreamRef.current) {
|
|
2917
3064
|
mediaStreamRef.current.getTracks().forEach((track) => track.stop());
|
|
@@ -3735,7 +3882,7 @@ var ChatUI = ({
|
|
|
3735
3882
|
initialInput,
|
|
3736
3883
|
onInitialInputConsumed
|
|
3737
3884
|
}) => {
|
|
3738
|
-
const config =
|
|
3885
|
+
const config = useMemo4(
|
|
3739
3886
|
() => mergeConfig(defaultChatConfig, userConfig),
|
|
3740
3887
|
[userConfig]
|
|
3741
3888
|
);
|
|
@@ -3756,6 +3903,7 @@ var ChatUI = ({
|
|
|
3756
3903
|
};
|
|
3757
3904
|
const [inputValue, setInputValue] = useState8("");
|
|
3758
3905
|
const [attachments, setAttachments] = useState8([]);
|
|
3906
|
+
const [expandedMessageIds, setExpandedMessageIds] = useState8({});
|
|
3759
3907
|
const [state, setState] = useState8({
|
|
3760
3908
|
isRecording: false,
|
|
3761
3909
|
selectedThreadId: currentThreadId,
|
|
@@ -3768,30 +3916,30 @@ var ChatUI = ({
|
|
|
3768
3916
|
isSidebarCollapsed: false
|
|
3769
3917
|
// No longer used for main sidebar
|
|
3770
3918
|
});
|
|
3771
|
-
|
|
3919
|
+
useEffect10(() => {
|
|
3772
3920
|
if (currentThreadId !== state.selectedThreadId) {
|
|
3773
3921
|
setState((prev) => ({ ...prev, selectedThreadId: currentThreadId }));
|
|
3774
3922
|
}
|
|
3775
3923
|
}, [currentThreadId]);
|
|
3776
|
-
const initialInputApplied =
|
|
3777
|
-
const initialInputConsumedRef =
|
|
3778
|
-
|
|
3924
|
+
const initialInputApplied = useRef6(false);
|
|
3925
|
+
const initialInputConsumedRef = useRef6(false);
|
|
3926
|
+
useEffect10(() => {
|
|
3779
3927
|
if (initialInput && !initialInputApplied.current) {
|
|
3780
3928
|
setInputValue(initialInput);
|
|
3781
3929
|
initialInputApplied.current = true;
|
|
3782
3930
|
}
|
|
3783
3931
|
}, [initialInput]);
|
|
3784
|
-
const scrollAreaRef =
|
|
3785
|
-
const stateRef =
|
|
3786
|
-
const inputValueRef =
|
|
3787
|
-
const attachmentsRef =
|
|
3788
|
-
|
|
3932
|
+
const scrollAreaRef = useRef6(null);
|
|
3933
|
+
const stateRef = useRef6(state);
|
|
3934
|
+
const inputValueRef = useRef6(inputValue);
|
|
3935
|
+
const attachmentsRef = useRef6(attachments);
|
|
3936
|
+
useEffect10(() => {
|
|
3789
3937
|
stateRef.current = state;
|
|
3790
3938
|
}, [state]);
|
|
3791
|
-
|
|
3939
|
+
useEffect10(() => {
|
|
3792
3940
|
inputValueRef.current = inputValue;
|
|
3793
3941
|
}, [inputValue]);
|
|
3794
|
-
|
|
3942
|
+
useEffect10(() => {
|
|
3795
3943
|
attachmentsRef.current = attachments;
|
|
3796
3944
|
}, [attachments]);
|
|
3797
3945
|
const [isCustomMounted, setIsCustomMounted] = useState8(false);
|
|
@@ -3814,7 +3962,7 @@ var ChatUI = ({
|
|
|
3814
3962
|
[]
|
|
3815
3963
|
// No dependencies - uses refs for latest state
|
|
3816
3964
|
);
|
|
3817
|
-
|
|
3965
|
+
useEffect10(() => {
|
|
3818
3966
|
const checkMobile = () => {
|
|
3819
3967
|
setIsMobile(globalThis.innerWidth < 1024);
|
|
3820
3968
|
};
|
|
@@ -3822,7 +3970,7 @@ var ChatUI = ({
|
|
|
3822
3970
|
globalThis.addEventListener("resize", checkMobile);
|
|
3823
3971
|
return () => globalThis.removeEventListener("resize", checkMobile);
|
|
3824
3972
|
}, []);
|
|
3825
|
-
|
|
3973
|
+
useEffect10(() => {
|
|
3826
3974
|
if (!isMobile || !config.customComponent?.component) return;
|
|
3827
3975
|
if (state.showSidebar) {
|
|
3828
3976
|
setIsCustomMounted(true);
|
|
@@ -3833,8 +3981,8 @@ var ChatUI = ({
|
|
|
3833
3981
|
return () => clearTimeout(t);
|
|
3834
3982
|
}
|
|
3835
3983
|
}, [state.showSidebar, isMobile, config.customComponent]);
|
|
3836
|
-
const prevMessageCountRef =
|
|
3837
|
-
|
|
3984
|
+
const prevMessageCountRef = useRef6(0);
|
|
3985
|
+
useEffect10(() => {
|
|
3838
3986
|
if (messages.length === 0) {
|
|
3839
3987
|
prevMessageCountRef.current = 0;
|
|
3840
3988
|
return;
|
|
@@ -3860,6 +4008,24 @@ var ChatUI = ({
|
|
|
3860
4008
|
}
|
|
3861
4009
|
});
|
|
3862
4010
|
}, [messages, state.isAtBottom, virtualizer]);
|
|
4011
|
+
useEffect10(() => {
|
|
4012
|
+
virtualizer.measure();
|
|
4013
|
+
}, [expandedMessageIds, virtualizer]);
|
|
4014
|
+
useEffect10(() => {
|
|
4015
|
+
const validMessageIds = new Set(messages.map((message) => message.id));
|
|
4016
|
+
setExpandedMessageIds((prev) => {
|
|
4017
|
+
const activeIds = Object.keys(prev);
|
|
4018
|
+
const staleIds = activeIds.filter((messageId) => !validMessageIds.has(messageId));
|
|
4019
|
+
if (staleIds.length === 0) {
|
|
4020
|
+
return prev;
|
|
4021
|
+
}
|
|
4022
|
+
const next = { ...prev };
|
|
4023
|
+
staleIds.forEach((messageId) => {
|
|
4024
|
+
delete next[messageId];
|
|
4025
|
+
});
|
|
4026
|
+
return next;
|
|
4027
|
+
});
|
|
4028
|
+
}, [messages]);
|
|
3863
4029
|
const handleScroll = useCallback4((e) => {
|
|
3864
4030
|
const { scrollTop, scrollHeight, clientHeight } = e.currentTarget;
|
|
3865
4031
|
const isAtBottom = scrollHeight - scrollTop - clientHeight < 50;
|
|
@@ -3897,6 +4063,19 @@ var ChatUI = ({
|
|
|
3897
4063
|
break;
|
|
3898
4064
|
}
|
|
3899
4065
|
}, [callbacks, createStateCallback]);
|
|
4066
|
+
const handleToggleMessageExpansion = useCallback4((messageId) => {
|
|
4067
|
+
setExpandedMessageIds((prev) => {
|
|
4068
|
+
if (prev[messageId]) {
|
|
4069
|
+
const next = { ...prev };
|
|
4070
|
+
delete next[messageId];
|
|
4071
|
+
return next;
|
|
4072
|
+
}
|
|
4073
|
+
return {
|
|
4074
|
+
...prev,
|
|
4075
|
+
[messageId]: true
|
|
4076
|
+
};
|
|
4077
|
+
});
|
|
4078
|
+
}, []);
|
|
3900
4079
|
const handleCreateThread = useCallback4((title) => {
|
|
3901
4080
|
callbacks.onCreateThread?.(title, createStateCallback());
|
|
3902
4081
|
}, [callbacks, createStateCallback]);
|
|
@@ -3987,7 +4166,7 @@ var ChatUI = ({
|
|
|
3987
4166
|
`message-skeleton-${index}`
|
|
3988
4167
|
);
|
|
3989
4168
|
}) });
|
|
3990
|
-
const messageProps =
|
|
4169
|
+
const messageProps = useMemo4(() => ({
|
|
3991
4170
|
userAvatar: user?.avatar,
|
|
3992
4171
|
userName: user?.name,
|
|
3993
4172
|
assistantAvatar: assistant?.avatar,
|
|
@@ -4001,7 +4180,15 @@ var ChatUI = ({
|
|
|
4001
4180
|
compactMode: config.ui.compactMode,
|
|
4002
4181
|
onAction: handleMessageAction,
|
|
4003
4182
|
toolUsedLabel: config.labels.toolUsed,
|
|
4004
|
-
thinkingLabel: config.labels.thinking
|
|
4183
|
+
thinkingLabel: config.labels.thinking,
|
|
4184
|
+
showMoreLabel: config.labels.showMoreMessage,
|
|
4185
|
+
showLessLabel: config.labels.showLessMessage,
|
|
4186
|
+
collapseLongMessages: config.ui.collapseLongMessages,
|
|
4187
|
+
collapseLongMessagesForUserOnly: config.ui.collapseLongMessagesForUserOnly,
|
|
4188
|
+
longMessagePreviewChars: config.ui.longMessagePreviewChars,
|
|
4189
|
+
longMessageChunkChars: config.ui.longMessageChunkChars,
|
|
4190
|
+
renderUserMarkdown: config.ui.renderUserMarkdown,
|
|
4191
|
+
onToggleExpanded: handleToggleMessageExpansion
|
|
4005
4192
|
}), [
|
|
4006
4193
|
user?.avatar,
|
|
4007
4194
|
user?.name,
|
|
@@ -4016,7 +4203,15 @@ var ChatUI = ({
|
|
|
4016
4203
|
config.features.enableToolCallsDisplay,
|
|
4017
4204
|
config.labels.toolUsed,
|
|
4018
4205
|
config.labels.thinking,
|
|
4019
|
-
|
|
4206
|
+
config.labels.showMoreMessage,
|
|
4207
|
+
config.labels.showLessMessage,
|
|
4208
|
+
config.ui.collapseLongMessages,
|
|
4209
|
+
config.ui.collapseLongMessagesForUserOnly,
|
|
4210
|
+
config.ui.longMessagePreviewChars,
|
|
4211
|
+
config.ui.longMessageChunkChars,
|
|
4212
|
+
config.ui.renderUserMarkdown,
|
|
4213
|
+
handleMessageAction,
|
|
4214
|
+
handleToggleMessageExpansion
|
|
4020
4215
|
]);
|
|
4021
4216
|
const shouldShowAgentSelector = Boolean(
|
|
4022
4217
|
config.agentSelector?.enabled && onSelectAgent && agentOptions.length > 0 && (!config.agentSelector?.hideIfSingle || agentOptions.length > 1)
|
|
@@ -4108,7 +4303,8 @@ var ChatUI = ({
|
|
|
4108
4303
|
{
|
|
4109
4304
|
message,
|
|
4110
4305
|
...messageProps,
|
|
4111
|
-
isGrouped
|
|
4306
|
+
isGrouped,
|
|
4307
|
+
isExpanded: Boolean(expandedMessageIds[message.id])
|
|
4112
4308
|
}
|
|
4113
4309
|
),
|
|
4114
4310
|
message.role === "assistant" && renderInlineSuggestions(message.id)
|
|
@@ -4197,7 +4393,7 @@ var ChatUI = ({
|
|
|
4197
4393
|
};
|
|
4198
4394
|
|
|
4199
4395
|
// src/components/chat/ThreadManager.tsx
|
|
4200
|
-
import { useState as useState9, useRef as
|
|
4396
|
+
import { useState as useState9, useRef as useRef7, useEffect as useEffect11 } from "react";
|
|
4201
4397
|
import {
|
|
4202
4398
|
Plus as Plus4,
|
|
4203
4399
|
MessageSquare as MessageSquare2,
|
|
@@ -4216,8 +4412,8 @@ import { Fragment as Fragment6, jsx as jsx25, jsxs as jsxs15 } from "react/jsx-r
|
|
|
4216
4412
|
var ThreadItem = ({ thread, isActive, config, onSelect, onRename, onDelete, onArchive }) => {
|
|
4217
4413
|
const [isEditing, setIsEditing] = useState9(false);
|
|
4218
4414
|
const [editTitle, setEditTitle] = useState9(thread.title);
|
|
4219
|
-
const inputRef =
|
|
4220
|
-
|
|
4415
|
+
const inputRef = useRef7(null);
|
|
4416
|
+
useEffect11(() => {
|
|
4221
4417
|
if (isEditing && inputRef.current) {
|
|
4222
4418
|
inputRef.current.focus();
|
|
4223
4419
|
inputRef.current.select();
|
|
@@ -4511,6 +4707,7 @@ export {
|
|
|
4511
4707
|
chatUtils,
|
|
4512
4708
|
cn,
|
|
4513
4709
|
configUtils,
|
|
4710
|
+
createObjectUrlFromDataUrl,
|
|
4514
4711
|
defaultChatConfig,
|
|
4515
4712
|
featureFlags,
|
|
4516
4713
|
formatDate,
|