@copilotz/chat-ui 0.1.9 → 0.1.10
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 +188 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +193 -21
- package/dist/index.js.map +1 -1
- package/dist/styles.css +21 -0
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -119,7 +119,9 @@ var defaultChatConfig = {
|
|
|
119
119
|
daysAgo: "days ago",
|
|
120
120
|
inputHelpText: "Press Enter to send, Shift+Enter to add a new line.",
|
|
121
121
|
thinking: "Thinking...",
|
|
122
|
-
defaultThreadName: "Main Thread"
|
|
122
|
+
defaultThreadName: "Main Thread",
|
|
123
|
+
showMoreMessage: "Show more",
|
|
124
|
+
showLessMessage: "Show less"
|
|
123
125
|
},
|
|
124
126
|
features: {
|
|
125
127
|
enableThreads: true,
|
|
@@ -138,7 +140,12 @@ var defaultChatConfig = {
|
|
|
138
140
|
showTimestamps: false,
|
|
139
141
|
showAvatars: true,
|
|
140
142
|
compactMode: false,
|
|
141
|
-
showWordCount: false
|
|
143
|
+
showWordCount: false,
|
|
144
|
+
collapseLongMessages: false,
|
|
145
|
+
collapseLongMessagesForUserOnly: false,
|
|
146
|
+
longMessagePreviewChars: 4e3,
|
|
147
|
+
longMessageChunkChars: 12e3,
|
|
148
|
+
renderUserMarkdown: true
|
|
142
149
|
},
|
|
143
150
|
customComponent: {},
|
|
144
151
|
headerActions: null
|
|
@@ -293,7 +300,7 @@ var configUtils = {
|
|
|
293
300
|
};
|
|
294
301
|
|
|
295
302
|
// src/components/chat/Message.tsx
|
|
296
|
-
var import_react = require("react");
|
|
303
|
+
var import_react = __toESM(require("react"), 1);
|
|
297
304
|
var import_react_markdown = __toESM(require("react-markdown"), 1);
|
|
298
305
|
var import_remark_gfm = __toESM(require("remark-gfm"), 1);
|
|
299
306
|
var import_rehype_highlight = __toESM(require("rehype-highlight"), 1);
|
|
@@ -614,21 +621,89 @@ var markdownComponents = {
|
|
|
614
621
|
var remarkPluginsDefault = [import_remark_gfm.default];
|
|
615
622
|
var rehypePluginsDefault = [import_rehype_highlight.default];
|
|
616
623
|
var rehypePluginsEmpty = [];
|
|
624
|
+
var getPlainTextChunks = (content, chunkSize) => {
|
|
625
|
+
if (chunkSize <= 0 || content.length <= chunkSize) {
|
|
626
|
+
return [content];
|
|
627
|
+
}
|
|
628
|
+
const chunks = [];
|
|
629
|
+
let start = 0;
|
|
630
|
+
while (start < content.length) {
|
|
631
|
+
let end = Math.min(start + chunkSize, content.length);
|
|
632
|
+
if (end < content.length) {
|
|
633
|
+
const splitAt = content.lastIndexOf("\n", end);
|
|
634
|
+
if (splitAt > start + Math.floor(chunkSize / 2)) {
|
|
635
|
+
end = splitAt + 1;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
chunks.push(content.slice(start, end));
|
|
639
|
+
start = end;
|
|
640
|
+
}
|
|
641
|
+
return chunks;
|
|
642
|
+
};
|
|
643
|
+
var hasCodeBlocks = (content) => /(^|\n)(```|~~~)/.test(content);
|
|
644
|
+
var getCollapsedPreview = (content, previewChars, previewOverride) => {
|
|
645
|
+
if (previewOverride && previewOverride.trim().length > 0) {
|
|
646
|
+
const normalizedPreview = previewOverride.trimEnd();
|
|
647
|
+
return normalizedPreview.endsWith("...") ? normalizedPreview : `${normalizedPreview}...`;
|
|
648
|
+
}
|
|
649
|
+
if (content.length <= previewChars) {
|
|
650
|
+
return content;
|
|
651
|
+
}
|
|
652
|
+
return `${content.slice(0, previewChars).trimEnd()}...`;
|
|
653
|
+
};
|
|
654
|
+
var LongContentShell = (0, import_react.memo)(function LongContentShell2({ children, className, style }) {
|
|
655
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className, style, children });
|
|
656
|
+
});
|
|
657
|
+
var PlainTextContent = (0, import_react.memo)(function PlainTextContent2({
|
|
658
|
+
content,
|
|
659
|
+
className = "",
|
|
660
|
+
chunkSize = 12e3,
|
|
661
|
+
style
|
|
662
|
+
}) {
|
|
663
|
+
const chunks = (0, import_react.useMemo)(() => getPlainTextChunks(content, chunkSize), [content, chunkSize]);
|
|
664
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
665
|
+
LongContentShell,
|
|
666
|
+
{
|
|
667
|
+
className: `text-sm leading-6 whitespace-pre-wrap break-words ${className}`.trim(),
|
|
668
|
+
style,
|
|
669
|
+
children: chunks.map((chunk, index) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react.default.Fragment, { children: chunk }, index))
|
|
670
|
+
}
|
|
671
|
+
);
|
|
672
|
+
});
|
|
617
673
|
var StreamingText = (0, import_react.memo)(function StreamingText2({
|
|
618
674
|
content,
|
|
619
675
|
isStreaming = false,
|
|
620
676
|
thinkingLabel = "Thinking...",
|
|
621
|
-
className = ""
|
|
677
|
+
className = "",
|
|
678
|
+
renderMarkdown = true,
|
|
679
|
+
plainTextChunkChars = 12e3,
|
|
680
|
+
contentStyle
|
|
622
681
|
}) {
|
|
623
682
|
const hasContent = content.trim().length > 0;
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
683
|
+
const enableSyntaxHighlight = renderMarkdown && !isStreaming && hasCodeBlocks(content);
|
|
684
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
|
|
685
|
+
hasContent ? renderMarkdown ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
686
|
+
LongContentShell,
|
|
687
|
+
{
|
|
688
|
+
className: `prose prose-sm max-w-none dark:prose-invert break-words ${className}`.trim(),
|
|
689
|
+
style: contentStyle,
|
|
690
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
691
|
+
import_react_markdown.default,
|
|
692
|
+
{
|
|
693
|
+
remarkPlugins: remarkPluginsDefault,
|
|
694
|
+
rehypePlugins: enableSyntaxHighlight ? rehypePluginsDefault : rehypePluginsEmpty,
|
|
695
|
+
components: markdownComponents,
|
|
696
|
+
children: content
|
|
697
|
+
}
|
|
698
|
+
)
|
|
699
|
+
}
|
|
700
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
701
|
+
PlainTextContent,
|
|
627
702
|
{
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
703
|
+
content,
|
|
704
|
+
className,
|
|
705
|
+
chunkSize: plainTextChunkChars,
|
|
706
|
+
style: contentStyle
|
|
632
707
|
}
|
|
633
708
|
) : isStreaming ? (
|
|
634
709
|
// Show thinking indicator while waiting for first token
|
|
@@ -798,6 +873,15 @@ var arePropsEqual = (prevProps, nextProps) => {
|
|
|
798
873
|
if (prevProps.className !== nextProps.className) return false;
|
|
799
874
|
if (prevProps.toolUsedLabel !== nextProps.toolUsedLabel) return false;
|
|
800
875
|
if (prevProps.thinkingLabel !== nextProps.thinkingLabel) return false;
|
|
876
|
+
if (prevProps.showMoreLabel !== nextProps.showMoreLabel) return false;
|
|
877
|
+
if (prevProps.showLessLabel !== nextProps.showLessLabel) return false;
|
|
878
|
+
if (prevProps.collapseLongMessages !== nextProps.collapseLongMessages) return false;
|
|
879
|
+
if (prevProps.collapseLongMessagesForUserOnly !== nextProps.collapseLongMessagesForUserOnly) return false;
|
|
880
|
+
if (prevProps.longMessagePreviewChars !== nextProps.longMessagePreviewChars) return false;
|
|
881
|
+
if (prevProps.longMessageChunkChars !== nextProps.longMessageChunkChars) return false;
|
|
882
|
+
if (prevProps.renderUserMarkdown !== nextProps.renderUserMarkdown) return false;
|
|
883
|
+
if (prevProps.isExpanded !== nextProps.isExpanded) return false;
|
|
884
|
+
if (prevProps.onToggleExpanded !== nextProps.onToggleExpanded) return false;
|
|
801
885
|
if (prevProps.isGrouped !== nextProps.isGrouped) return false;
|
|
802
886
|
if (prevProps.assistantAvatar !== nextProps.assistantAvatar) return false;
|
|
803
887
|
return true;
|
|
@@ -820,6 +904,15 @@ var Message = (0, import_react.memo)(({
|
|
|
820
904
|
className = "",
|
|
821
905
|
toolUsedLabel,
|
|
822
906
|
thinkingLabel = "Thinking...",
|
|
907
|
+
showMoreLabel = "Show more",
|
|
908
|
+
showLessLabel = "Show less",
|
|
909
|
+
collapseLongMessages = false,
|
|
910
|
+
collapseLongMessagesForUserOnly = false,
|
|
911
|
+
longMessagePreviewChars = 4e3,
|
|
912
|
+
longMessageChunkChars = 12e3,
|
|
913
|
+
renderUserMarkdown = true,
|
|
914
|
+
isExpanded = false,
|
|
915
|
+
onToggleExpanded,
|
|
823
916
|
isGrouped = false
|
|
824
917
|
}) => {
|
|
825
918
|
const [isEditing, setIsEditing] = (0, import_react.useState)(false);
|
|
@@ -829,6 +922,18 @@ var Message = (0, import_react.memo)(({
|
|
|
829
922
|
const messageIsUser = isUser ?? message.role === "user";
|
|
830
923
|
const canEdit = enableEdit && messageIsUser;
|
|
831
924
|
const canRegenerate = enableRegenerate && !messageIsUser;
|
|
925
|
+
const normalizedPreviewChars = Math.max(longMessagePreviewChars, 1);
|
|
926
|
+
const normalizedChunkChars = Math.max(longMessageChunkChars, 1);
|
|
927
|
+
const previewOverride = typeof message.metadata?.previewContent === "string" ? message.metadata.previewContent : void 0;
|
|
928
|
+
const canCollapseMessage = collapseLongMessages && !message.isStreaming && message.content.length > normalizedPreviewChars && (!collapseLongMessagesForUserOnly || messageIsUser);
|
|
929
|
+
const isCollapsed = canCollapseMessage && !isExpanded;
|
|
930
|
+
const contentToRender = isCollapsed ? getCollapsedPreview(message.content, normalizedPreviewChars, previewOverride) : message.content;
|
|
931
|
+
const shouldRenderMarkdown = !isCollapsed && (!messageIsUser || renderUserMarkdown);
|
|
932
|
+
const shouldApplyLargeContentContainment = !isCollapsed && message.content.length > normalizedChunkChars;
|
|
933
|
+
const contentStyle = shouldApplyLargeContentContainment ? {
|
|
934
|
+
contentVisibility: "auto",
|
|
935
|
+
containIntrinsicSize: "1px 400px"
|
|
936
|
+
} : void 0;
|
|
832
937
|
const handleCopy = async () => {
|
|
833
938
|
try {
|
|
834
939
|
await navigator.clipboard.writeText(message.content);
|
|
@@ -857,6 +962,9 @@ var Message = (0, import_react.memo)(({
|
|
|
857
962
|
const handleRegenerate = () => {
|
|
858
963
|
onAction?.({ action: "regenerate", messageId: message.id });
|
|
859
964
|
};
|
|
965
|
+
const handleToggleExpanded = () => {
|
|
966
|
+
onToggleExpanded?.(message.id);
|
|
967
|
+
};
|
|
860
968
|
const formatTime = (timestamp) => {
|
|
861
969
|
return new Date(timestamp).toLocaleTimeString("pt-BR", {
|
|
862
970
|
hour: "2-digit",
|
|
@@ -907,11 +1015,26 @@ var Message = (0, import_react.memo)(({
|
|
|
907
1015
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
908
1016
|
StreamingText,
|
|
909
1017
|
{
|
|
910
|
-
content:
|
|
1018
|
+
content: contentToRender,
|
|
911
1019
|
isStreaming: message.isStreaming,
|
|
912
|
-
thinkingLabel
|
|
1020
|
+
thinkingLabel,
|
|
1021
|
+
renderMarkdown: shouldRenderMarkdown,
|
|
1022
|
+
plainTextChunkChars: normalizedChunkChars,
|
|
1023
|
+
contentStyle
|
|
913
1024
|
}
|
|
914
1025
|
),
|
|
1026
|
+
canCollapseMessage && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "mt-3", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1027
|
+
Button,
|
|
1028
|
+
{
|
|
1029
|
+
type: "button",
|
|
1030
|
+
variant: "ghost",
|
|
1031
|
+
size: "sm",
|
|
1032
|
+
className: "h-auto px-0 text-xs font-medium text-current hover:bg-transparent hover:opacity-80",
|
|
1033
|
+
"aria-expanded": !isCollapsed,
|
|
1034
|
+
onClick: handleToggleExpanded,
|
|
1035
|
+
children: isCollapsed ? showMoreLabel : showLessLabel
|
|
1036
|
+
}
|
|
1037
|
+
) }),
|
|
915
1038
|
message.attachments && message.attachments.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "mt-3 space-y-2", children: message.attachments.map((attachment, index) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(MediaRenderer, { attachment }, index)) })
|
|
916
1039
|
] }),
|
|
917
1040
|
!isEditing && (showActions || copied) && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: `absolute -top-2 flex gap-1 ${messageIsUser ? "-left-2" : "-right-2"}`, children: [
|
|
@@ -3734,6 +3857,7 @@ var ChatUI = ({
|
|
|
3734
3857
|
};
|
|
3735
3858
|
const [inputValue, setInputValue] = (0, import_react7.useState)("");
|
|
3736
3859
|
const [attachments, setAttachments] = (0, import_react7.useState)([]);
|
|
3860
|
+
const [expandedMessageIds, setExpandedMessageIds] = (0, import_react7.useState)({});
|
|
3737
3861
|
const [state, setState] = (0, import_react7.useState)({
|
|
3738
3862
|
isRecording: false,
|
|
3739
3863
|
selectedThreadId: currentThreadId,
|
|
@@ -3838,6 +3962,24 @@ var ChatUI = ({
|
|
|
3838
3962
|
}
|
|
3839
3963
|
});
|
|
3840
3964
|
}, [messages, state.isAtBottom, virtualizer]);
|
|
3965
|
+
(0, import_react7.useEffect)(() => {
|
|
3966
|
+
virtualizer.measure();
|
|
3967
|
+
}, [expandedMessageIds, virtualizer]);
|
|
3968
|
+
(0, import_react7.useEffect)(() => {
|
|
3969
|
+
const validMessageIds = new Set(messages.map((message) => message.id));
|
|
3970
|
+
setExpandedMessageIds((prev) => {
|
|
3971
|
+
const activeIds = Object.keys(prev);
|
|
3972
|
+
const staleIds = activeIds.filter((messageId) => !validMessageIds.has(messageId));
|
|
3973
|
+
if (staleIds.length === 0) {
|
|
3974
|
+
return prev;
|
|
3975
|
+
}
|
|
3976
|
+
const next = { ...prev };
|
|
3977
|
+
staleIds.forEach((messageId) => {
|
|
3978
|
+
delete next[messageId];
|
|
3979
|
+
});
|
|
3980
|
+
return next;
|
|
3981
|
+
});
|
|
3982
|
+
}, [messages]);
|
|
3841
3983
|
const handleScroll = (0, import_react7.useCallback)((e) => {
|
|
3842
3984
|
const { scrollTop, scrollHeight, clientHeight } = e.currentTarget;
|
|
3843
3985
|
const isAtBottom = scrollHeight - scrollTop - clientHeight < 50;
|
|
@@ -3875,6 +4017,19 @@ var ChatUI = ({
|
|
|
3875
4017
|
break;
|
|
3876
4018
|
}
|
|
3877
4019
|
}, [callbacks, createStateCallback]);
|
|
4020
|
+
const handleToggleMessageExpansion = (0, import_react7.useCallback)((messageId) => {
|
|
4021
|
+
setExpandedMessageIds((prev) => {
|
|
4022
|
+
if (prev[messageId]) {
|
|
4023
|
+
const next = { ...prev };
|
|
4024
|
+
delete next[messageId];
|
|
4025
|
+
return next;
|
|
4026
|
+
}
|
|
4027
|
+
return {
|
|
4028
|
+
...prev,
|
|
4029
|
+
[messageId]: true
|
|
4030
|
+
};
|
|
4031
|
+
});
|
|
4032
|
+
}, []);
|
|
3878
4033
|
const handleCreateThread = (0, import_react7.useCallback)((title) => {
|
|
3879
4034
|
callbacks.onCreateThread?.(title, createStateCallback());
|
|
3880
4035
|
}, [callbacks, createStateCallback]);
|
|
@@ -3979,7 +4134,15 @@ var ChatUI = ({
|
|
|
3979
4134
|
compactMode: config.ui.compactMode,
|
|
3980
4135
|
onAction: handleMessageAction,
|
|
3981
4136
|
toolUsedLabel: config.labels.toolUsed,
|
|
3982
|
-
thinkingLabel: config.labels.thinking
|
|
4137
|
+
thinkingLabel: config.labels.thinking,
|
|
4138
|
+
showMoreLabel: config.labels.showMoreMessage,
|
|
4139
|
+
showLessLabel: config.labels.showLessMessage,
|
|
4140
|
+
collapseLongMessages: config.ui.collapseLongMessages,
|
|
4141
|
+
collapseLongMessagesForUserOnly: config.ui.collapseLongMessagesForUserOnly,
|
|
4142
|
+
longMessagePreviewChars: config.ui.longMessagePreviewChars,
|
|
4143
|
+
longMessageChunkChars: config.ui.longMessageChunkChars,
|
|
4144
|
+
renderUserMarkdown: config.ui.renderUserMarkdown,
|
|
4145
|
+
onToggleExpanded: handleToggleMessageExpansion
|
|
3983
4146
|
}), [
|
|
3984
4147
|
user?.avatar,
|
|
3985
4148
|
user?.name,
|
|
@@ -3994,7 +4157,15 @@ var ChatUI = ({
|
|
|
3994
4157
|
config.features.enableToolCallsDisplay,
|
|
3995
4158
|
config.labels.toolUsed,
|
|
3996
4159
|
config.labels.thinking,
|
|
3997
|
-
|
|
4160
|
+
config.labels.showMoreMessage,
|
|
4161
|
+
config.labels.showLessMessage,
|
|
4162
|
+
config.ui.collapseLongMessages,
|
|
4163
|
+
config.ui.collapseLongMessagesForUserOnly,
|
|
4164
|
+
config.ui.longMessagePreviewChars,
|
|
4165
|
+
config.ui.longMessageChunkChars,
|
|
4166
|
+
config.ui.renderUserMarkdown,
|
|
4167
|
+
handleMessageAction,
|
|
4168
|
+
handleToggleMessageExpansion
|
|
3998
4169
|
]);
|
|
3999
4170
|
const shouldShowAgentSelector = Boolean(
|
|
4000
4171
|
config.agentSelector?.enabled && onSelectAgent && agentOptions.length > 0 && (!config.agentSelector?.hideIfSingle || agentOptions.length > 1)
|
|
@@ -4086,7 +4257,8 @@ var ChatUI = ({
|
|
|
4086
4257
|
{
|
|
4087
4258
|
message,
|
|
4088
4259
|
...messageProps,
|
|
4089
|
-
isGrouped
|
|
4260
|
+
isGrouped,
|
|
4261
|
+
isExpanded: Boolean(expandedMessageIds[message.id])
|
|
4090
4262
|
}
|
|
4091
4263
|
),
|
|
4092
4264
|
message.role === "assistant" && renderInlineSuggestions(message.id)
|