@copilotz/chat-ui 0.1.8 → 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.d.cts CHANGED
@@ -117,6 +117,8 @@ interface ChatConfig {
117
117
  inputHelpText?: string;
118
118
  thinking?: string;
119
119
  defaultThreadName?: string;
120
+ showMoreMessage?: string;
121
+ showLessMessage?: string;
120
122
  };
121
123
  features?: {
122
124
  enableThreads?: boolean;
@@ -135,6 +137,11 @@ interface ChatConfig {
135
137
  showAvatars?: boolean;
136
138
  compactMode?: boolean;
137
139
  showWordCount?: boolean;
140
+ collapseLongMessages?: boolean;
141
+ collapseLongMessagesForUserOnly?: boolean;
142
+ longMessagePreviewChars?: number;
143
+ longMessageChunkChars?: number;
144
+ renderUserMarkdown?: boolean;
138
145
  };
139
146
  customComponent?: {
140
147
  label?: string;
@@ -359,6 +366,15 @@ interface MessageProps {
359
366
  className?: string;
360
367
  toolUsedLabel?: string;
361
368
  thinkingLabel?: string;
369
+ showMoreLabel?: string;
370
+ showLessLabel?: string;
371
+ collapseLongMessages?: boolean;
372
+ collapseLongMessagesForUserOnly?: boolean;
373
+ longMessagePreviewChars?: number;
374
+ longMessageChunkChars?: number;
375
+ renderUserMarkdown?: boolean;
376
+ isExpanded?: boolean;
377
+ onToggleExpanded?: (messageId: string) => void;
362
378
  /** When true, hides the avatar and name (for grouped consecutive messages from same sender) */
363
379
  isGrouped?: boolean;
364
380
  }
@@ -565,6 +581,11 @@ declare const configUtils: {
565
581
  showAvatars?: boolean;
566
582
  compactMode?: boolean;
567
583
  showWordCount?: boolean;
584
+ collapseLongMessages?: boolean;
585
+ collapseLongMessagesForUserOnly?: boolean;
586
+ longMessagePreviewChars?: number;
587
+ longMessageChunkChars?: number;
588
+ renderUserMarkdown?: boolean;
568
589
  };
569
590
  };
570
591
  };
package/dist/index.d.ts CHANGED
@@ -117,6 +117,8 @@ interface ChatConfig {
117
117
  inputHelpText?: string;
118
118
  thinking?: string;
119
119
  defaultThreadName?: string;
120
+ showMoreMessage?: string;
121
+ showLessMessage?: string;
120
122
  };
121
123
  features?: {
122
124
  enableThreads?: boolean;
@@ -135,6 +137,11 @@ interface ChatConfig {
135
137
  showAvatars?: boolean;
136
138
  compactMode?: boolean;
137
139
  showWordCount?: boolean;
140
+ collapseLongMessages?: boolean;
141
+ collapseLongMessagesForUserOnly?: boolean;
142
+ longMessagePreviewChars?: number;
143
+ longMessageChunkChars?: number;
144
+ renderUserMarkdown?: boolean;
138
145
  };
139
146
  customComponent?: {
140
147
  label?: string;
@@ -359,6 +366,15 @@ interface MessageProps {
359
366
  className?: string;
360
367
  toolUsedLabel?: string;
361
368
  thinkingLabel?: string;
369
+ showMoreLabel?: string;
370
+ showLessLabel?: string;
371
+ collapseLongMessages?: boolean;
372
+ collapseLongMessagesForUserOnly?: boolean;
373
+ longMessagePreviewChars?: number;
374
+ longMessageChunkChars?: number;
375
+ renderUserMarkdown?: boolean;
376
+ isExpanded?: boolean;
377
+ onToggleExpanded?: (messageId: string) => void;
362
378
  /** When true, hides the avatar and name (for grouped consecutive messages from same sender) */
363
379
  isGrouped?: boolean;
364
380
  }
@@ -565,6 +581,11 @@ declare const configUtils: {
565
581
  showAvatars?: boolean;
566
582
  compactMode?: boolean;
567
583
  showWordCount?: boolean;
584
+ collapseLongMessages?: boolean;
585
+ collapseLongMessagesForUserOnly?: boolean;
586
+ longMessagePreviewChars?: number;
587
+ longMessageChunkChars?: number;
588
+ renderUserMarkdown?: boolean;
568
589
  };
569
590
  };
570
591
  };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/components/chat/ChatUI.tsx
2
- import { useState as useState8, useEffect as useEffect9, useRef as useRef7, useCallback as useCallback4, useMemo as useMemo3 } from "react";
2
+ import { useState as useState8, useEffect as useEffect9, useRef as useRef7, 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, useRef, memo } from "react";
248
+ import React, { useState, useRef, useMemo, memo } from "react";
242
249
  import ReactMarkdown from "react-markdown";
243
250
  import remarkGfm from "remark-gfm";
244
251
  import rehypeHighlight from "rehype-highlight";
@@ -569,21 +576,89 @@ var markdownComponents = {
569
576
  var remarkPluginsDefault = [remarkGfm];
570
577
  var rehypePluginsDefault = [rehypeHighlight];
571
578
  var rehypePluginsEmpty = [];
579
+ var getPlainTextChunks = (content, chunkSize) => {
580
+ if (chunkSize <= 0 || content.length <= chunkSize) {
581
+ return [content];
582
+ }
583
+ const chunks = [];
584
+ let start = 0;
585
+ while (start < content.length) {
586
+ let end = Math.min(start + chunkSize, content.length);
587
+ if (end < content.length) {
588
+ const splitAt = content.lastIndexOf("\n", end);
589
+ if (splitAt > start + Math.floor(chunkSize / 2)) {
590
+ end = splitAt + 1;
591
+ }
592
+ }
593
+ chunks.push(content.slice(start, end));
594
+ start = end;
595
+ }
596
+ return chunks;
597
+ };
598
+ var hasCodeBlocks = (content) => /(^|\n)(```|~~~)/.test(content);
599
+ var getCollapsedPreview = (content, previewChars, previewOverride) => {
600
+ if (previewOverride && previewOverride.trim().length > 0) {
601
+ const normalizedPreview = previewOverride.trimEnd();
602
+ return normalizedPreview.endsWith("...") ? normalizedPreview : `${normalizedPreview}...`;
603
+ }
604
+ if (content.length <= previewChars) {
605
+ return content;
606
+ }
607
+ return `${content.slice(0, previewChars).trimEnd()}...`;
608
+ };
609
+ var LongContentShell = memo(function LongContentShell2({ children, className, style }) {
610
+ return /* @__PURE__ */ jsx7("div", { className, style, children });
611
+ });
612
+ var PlainTextContent = memo(function PlainTextContent2({
613
+ content,
614
+ className = "",
615
+ chunkSize = 12e3,
616
+ style
617
+ }) {
618
+ const chunks = useMemo(() => getPlainTextChunks(content, chunkSize), [content, chunkSize]);
619
+ return /* @__PURE__ */ jsx7(
620
+ LongContentShell,
621
+ {
622
+ className: `text-sm leading-6 whitespace-pre-wrap break-words ${className}`.trim(),
623
+ style,
624
+ children: chunks.map((chunk, index) => /* @__PURE__ */ jsx7(React.Fragment, { children: chunk }, index))
625
+ }
626
+ );
627
+ });
572
628
  var StreamingText = memo(function StreamingText2({
573
629
  content,
574
630
  isStreaming = false,
575
631
  thinkingLabel = "Thinking...",
576
- className = ""
632
+ className = "",
633
+ renderMarkdown = true,
634
+ plainTextChunkChars = 12e3,
635
+ contentStyle
577
636
  }) {
578
637
  const hasContent = content.trim().length > 0;
579
- return /* @__PURE__ */ jsxs2("div", { className: `prose prose-sm max-w-none dark:prose-invert break-words ${className}`, children: [
580
- hasContent ? /* @__PURE__ */ jsx7(
581
- ReactMarkdown,
638
+ const enableSyntaxHighlight = renderMarkdown && !isStreaming && hasCodeBlocks(content);
639
+ return /* @__PURE__ */ jsxs2(Fragment, { children: [
640
+ hasContent ? renderMarkdown ? /* @__PURE__ */ jsx7(
641
+ LongContentShell,
642
+ {
643
+ className: `prose prose-sm max-w-none dark:prose-invert break-words ${className}`.trim(),
644
+ style: contentStyle,
645
+ children: /* @__PURE__ */ jsx7(
646
+ ReactMarkdown,
647
+ {
648
+ remarkPlugins: remarkPluginsDefault,
649
+ rehypePlugins: enableSyntaxHighlight ? rehypePluginsDefault : rehypePluginsEmpty,
650
+ components: markdownComponents,
651
+ children: content
652
+ }
653
+ )
654
+ }
655
+ ) : /* @__PURE__ */ jsx7(
656
+ PlainTextContent,
582
657
  {
583
- remarkPlugins: remarkPluginsDefault,
584
- rehypePlugins: isStreaming ? rehypePluginsEmpty : rehypePluginsDefault,
585
- components: markdownComponents,
586
- children: content
658
+ content,
659
+ className,
660
+ chunkSize: plainTextChunkChars,
661
+ style: contentStyle
587
662
  }
588
663
  ) : isStreaming ? (
589
664
  // Show thinking indicator while waiting for first token
@@ -753,6 +828,15 @@ var arePropsEqual = (prevProps, nextProps) => {
753
828
  if (prevProps.className !== nextProps.className) return false;
754
829
  if (prevProps.toolUsedLabel !== nextProps.toolUsedLabel) return false;
755
830
  if (prevProps.thinkingLabel !== nextProps.thinkingLabel) return false;
831
+ if (prevProps.showMoreLabel !== nextProps.showMoreLabel) return false;
832
+ if (prevProps.showLessLabel !== nextProps.showLessLabel) return false;
833
+ if (prevProps.collapseLongMessages !== nextProps.collapseLongMessages) return false;
834
+ if (prevProps.collapseLongMessagesForUserOnly !== nextProps.collapseLongMessagesForUserOnly) return false;
835
+ if (prevProps.longMessagePreviewChars !== nextProps.longMessagePreviewChars) return false;
836
+ if (prevProps.longMessageChunkChars !== nextProps.longMessageChunkChars) return false;
837
+ if (prevProps.renderUserMarkdown !== nextProps.renderUserMarkdown) return false;
838
+ if (prevProps.isExpanded !== nextProps.isExpanded) return false;
839
+ if (prevProps.onToggleExpanded !== nextProps.onToggleExpanded) return false;
756
840
  if (prevProps.isGrouped !== nextProps.isGrouped) return false;
757
841
  if (prevProps.assistantAvatar !== nextProps.assistantAvatar) return false;
758
842
  return true;
@@ -775,6 +859,15 @@ var Message = memo(({
775
859
  className = "",
776
860
  toolUsedLabel,
777
861
  thinkingLabel = "Thinking...",
862
+ showMoreLabel = "Show more",
863
+ showLessLabel = "Show less",
864
+ collapseLongMessages = false,
865
+ collapseLongMessagesForUserOnly = false,
866
+ longMessagePreviewChars = 4e3,
867
+ longMessageChunkChars = 12e3,
868
+ renderUserMarkdown = true,
869
+ isExpanded = false,
870
+ onToggleExpanded,
778
871
  isGrouped = false
779
872
  }) => {
780
873
  const [isEditing, setIsEditing] = useState(false);
@@ -784,6 +877,18 @@ var Message = memo(({
784
877
  const messageIsUser = isUser ?? message.role === "user";
785
878
  const canEdit = enableEdit && messageIsUser;
786
879
  const canRegenerate = enableRegenerate && !messageIsUser;
880
+ const normalizedPreviewChars = Math.max(longMessagePreviewChars, 1);
881
+ const normalizedChunkChars = Math.max(longMessageChunkChars, 1);
882
+ const previewOverride = typeof message.metadata?.previewContent === "string" ? message.metadata.previewContent : void 0;
883
+ const canCollapseMessage = collapseLongMessages && !message.isStreaming && message.content.length > normalizedPreviewChars && (!collapseLongMessagesForUserOnly || messageIsUser);
884
+ const isCollapsed = canCollapseMessage && !isExpanded;
885
+ const contentToRender = isCollapsed ? getCollapsedPreview(message.content, normalizedPreviewChars, previewOverride) : message.content;
886
+ const shouldRenderMarkdown = !isCollapsed && (!messageIsUser || renderUserMarkdown);
887
+ const shouldApplyLargeContentContainment = !isCollapsed && message.content.length > normalizedChunkChars;
888
+ const contentStyle = shouldApplyLargeContentContainment ? {
889
+ contentVisibility: "auto",
890
+ containIntrinsicSize: "1px 400px"
891
+ } : void 0;
787
892
  const handleCopy = async () => {
788
893
  try {
789
894
  await navigator.clipboard.writeText(message.content);
@@ -812,6 +917,9 @@ var Message = memo(({
812
917
  const handleRegenerate = () => {
813
918
  onAction?.({ action: "regenerate", messageId: message.id });
814
919
  };
920
+ const handleToggleExpanded = () => {
921
+ onToggleExpanded?.(message.id);
922
+ };
815
923
  const formatTime = (timestamp) => {
816
924
  return new Date(timestamp).toLocaleTimeString("pt-BR", {
817
925
  hour: "2-digit",
@@ -836,7 +944,7 @@ var Message = memo(({
836
944
  message.isEdited && /* @__PURE__ */ jsx7(Badge, { variant: "outline", className: "text-xs", children: "editado" })
837
945
  ] })
838
946
  ] }),
839
- /* @__PURE__ */ jsx7("div", { className: `flex-1 min-w-0 ${messageIsUser ? "text-right" : "text-left"} ${isGrouped && showAvatar && !messageIsUser ? compactMode ? "ml-9" : "ml-11" : ""} ${isGrouped && showAvatar && messageIsUser ? compactMode ? "mr-9" : "mr-11" : ""}`, children: /* @__PURE__ */ jsxs2("div", { className: `relative inline-flex flex-col overflow-hidden ${messageIsUser ? "rounded-lg p-3 bg-primary text-primary-foreground ml-auto max-w-[85%]" : "max-w-full"}`, children: [
947
+ /* @__PURE__ */ jsx7("div", { className: `flex-1 min-w-0 ${messageIsUser ? "text-right" : "text-left"} ${isGrouped && showAvatar && !messageIsUser ? compactMode ? "ml-9" : "ml-11" : ""} ${isGrouped && showAvatar && messageIsUser ? compactMode ? "mr-9" : "mr-11" : ""}`, children: /* @__PURE__ */ jsxs2("div", { className: `relative inline-flex flex-col overflow-hidden text-left ${messageIsUser ? "rounded-lg p-3 bg-primary text-primary-foreground ml-auto max-w-[85%]" : "max-w-full"}`, children: [
840
948
  isEditing ? /* @__PURE__ */ jsxs2("div", { className: "space-y-2", children: [
841
949
  /* @__PURE__ */ jsx7(
842
950
  Textarea,
@@ -862,12 +970,26 @@ var Message = memo(({
862
970
  /* @__PURE__ */ jsx7(
863
971
  StreamingText,
864
972
  {
865
- content: message.content,
973
+ content: contentToRender,
866
974
  isStreaming: message.isStreaming,
867
975
  thinkingLabel,
868
- className: messageIsUser ? "[&_*]:text-right" : ""
976
+ renderMarkdown: shouldRenderMarkdown,
977
+ plainTextChunkChars: normalizedChunkChars,
978
+ contentStyle
869
979
  }
870
980
  ),
981
+ canCollapseMessage && /* @__PURE__ */ jsx7("div", { className: "mt-3", children: /* @__PURE__ */ jsx7(
982
+ Button,
983
+ {
984
+ type: "button",
985
+ variant: "ghost",
986
+ size: "sm",
987
+ className: "h-auto px-0 text-xs font-medium text-current hover:bg-transparent hover:opacity-80",
988
+ "aria-expanded": !isCollapsed,
989
+ onClick: handleToggleExpanded,
990
+ children: isCollapsed ? showMoreLabel : showLessLabel
991
+ }
992
+ ) }),
871
993
  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)) })
872
994
  ] }),
873
995
  !isEditing && (showActions || copied) && /* @__PURE__ */ jsxs2("div", { className: `absolute -top-2 flex gap-1 ${messageIsUser ? "-left-2" : "-right-2"}`, children: [
@@ -2589,7 +2711,7 @@ var ChatHeader = ({
2589
2711
  import { useState as useState6, useRef as useRef6, useCallback as useCallback3, useEffect as useEffect8, memo as memo2 } from "react";
2590
2712
 
2591
2713
  // src/components/chat/UserContext.tsx
2592
- import { createContext as createContext2, useCallback as useCallback2, useContext as useContext2, useEffect as useEffect7, useMemo as useMemo2, useState as useState5 } from "react";
2714
+ import { createContext as createContext2, useCallback as useCallback2, useContext as useContext2, useEffect as useEffect7, useMemo as useMemo3, useState as useState5 } from "react";
2593
2715
  import { jsx as jsx19 } from "react/jsx-runtime";
2594
2716
  var Ctx = createContext2(void 0);
2595
2717
  var ChatUserContextProvider = ({ children, initial }) => {
@@ -2611,7 +2733,7 @@ var ChatUserContextProvider = ({ children, initial }) => {
2611
2733
  return { ...prev, ...partial, updatedAt: Date.now() };
2612
2734
  });
2613
2735
  }, []);
2614
- const value = useMemo2(() => ({
2736
+ const value = useMemo3(() => ({
2615
2737
  context: ctx,
2616
2738
  setContext: setPartial,
2617
2739
  resetContext: () => setCtx({ updatedAt: Date.now() })
@@ -3736,7 +3858,7 @@ var ChatUI = ({
3736
3858
  initialInput,
3737
3859
  onInitialInputConsumed
3738
3860
  }) => {
3739
- const config = useMemo3(
3861
+ const config = useMemo4(
3740
3862
  () => mergeConfig(defaultChatConfig, userConfig),
3741
3863
  [userConfig]
3742
3864
  );
@@ -3757,6 +3879,7 @@ var ChatUI = ({
3757
3879
  };
3758
3880
  const [inputValue, setInputValue] = useState8("");
3759
3881
  const [attachments, setAttachments] = useState8([]);
3882
+ const [expandedMessageIds, setExpandedMessageIds] = useState8({});
3760
3883
  const [state, setState] = useState8({
3761
3884
  isRecording: false,
3762
3885
  selectedThreadId: currentThreadId,
@@ -3834,8 +3957,23 @@ var ChatUI = ({
3834
3957
  return () => clearTimeout(t);
3835
3958
  }
3836
3959
  }, [state.showSidebar, isMobile, config.customComponent]);
3960
+ const prevMessageCountRef = useRef7(0);
3837
3961
  useEffect9(() => {
3838
- if (!state.isAtBottom || messages.length === 0) return;
3962
+ if (messages.length === 0) {
3963
+ prevMessageCountRef.current = 0;
3964
+ return;
3965
+ }
3966
+ const wasEmpty = prevMessageCountRef.current === 0;
3967
+ prevMessageCountRef.current = messages.length;
3968
+ if (wasEmpty) {
3969
+ requestAnimationFrame(() => {
3970
+ requestAnimationFrame(() => {
3971
+ virtualizer.scrollToIndex(messages.length - 1, { align: "end" });
3972
+ });
3973
+ });
3974
+ return;
3975
+ }
3976
+ if (!state.isAtBottom) return;
3839
3977
  requestAnimationFrame(() => {
3840
3978
  const viewport = scrollAreaRef.current;
3841
3979
  if (!viewport) return;
@@ -3845,7 +3983,25 @@ var ChatUI = ({
3845
3983
  viewport.scrollTop = viewport.scrollHeight;
3846
3984
  }
3847
3985
  });
3848
- }, [messages, state.isAtBottom]);
3986
+ }, [messages, state.isAtBottom, virtualizer]);
3987
+ useEffect9(() => {
3988
+ virtualizer.measure();
3989
+ }, [expandedMessageIds, virtualizer]);
3990
+ useEffect9(() => {
3991
+ const validMessageIds = new Set(messages.map((message) => message.id));
3992
+ setExpandedMessageIds((prev) => {
3993
+ const activeIds = Object.keys(prev);
3994
+ const staleIds = activeIds.filter((messageId) => !validMessageIds.has(messageId));
3995
+ if (staleIds.length === 0) {
3996
+ return prev;
3997
+ }
3998
+ const next = { ...prev };
3999
+ staleIds.forEach((messageId) => {
4000
+ delete next[messageId];
4001
+ });
4002
+ return next;
4003
+ });
4004
+ }, [messages]);
3849
4005
  const handleScroll = useCallback4((e) => {
3850
4006
  const { scrollTop, scrollHeight, clientHeight } = e.currentTarget;
3851
4007
  const isAtBottom = scrollHeight - scrollTop - clientHeight < 50;
@@ -3883,6 +4039,19 @@ var ChatUI = ({
3883
4039
  break;
3884
4040
  }
3885
4041
  }, [callbacks, createStateCallback]);
4042
+ const handleToggleMessageExpansion = useCallback4((messageId) => {
4043
+ setExpandedMessageIds((prev) => {
4044
+ if (prev[messageId]) {
4045
+ const next = { ...prev };
4046
+ delete next[messageId];
4047
+ return next;
4048
+ }
4049
+ return {
4050
+ ...prev,
4051
+ [messageId]: true
4052
+ };
4053
+ });
4054
+ }, []);
3886
4055
  const handleCreateThread = useCallback4((title) => {
3887
4056
  callbacks.onCreateThread?.(title, createStateCallback());
3888
4057
  }, [callbacks, createStateCallback]);
@@ -3973,7 +4142,7 @@ var ChatUI = ({
3973
4142
  `message-skeleton-${index}`
3974
4143
  );
3975
4144
  }) });
3976
- const messageProps = useMemo3(() => ({
4145
+ const messageProps = useMemo4(() => ({
3977
4146
  userAvatar: user?.avatar,
3978
4147
  userName: user?.name,
3979
4148
  assistantAvatar: assistant?.avatar,
@@ -3987,7 +4156,15 @@ var ChatUI = ({
3987
4156
  compactMode: config.ui.compactMode,
3988
4157
  onAction: handleMessageAction,
3989
4158
  toolUsedLabel: config.labels.toolUsed,
3990
- thinkingLabel: config.labels.thinking
4159
+ thinkingLabel: config.labels.thinking,
4160
+ showMoreLabel: config.labels.showMoreMessage,
4161
+ showLessLabel: config.labels.showLessMessage,
4162
+ collapseLongMessages: config.ui.collapseLongMessages,
4163
+ collapseLongMessagesForUserOnly: config.ui.collapseLongMessagesForUserOnly,
4164
+ longMessagePreviewChars: config.ui.longMessagePreviewChars,
4165
+ longMessageChunkChars: config.ui.longMessageChunkChars,
4166
+ renderUserMarkdown: config.ui.renderUserMarkdown,
4167
+ onToggleExpanded: handleToggleMessageExpansion
3991
4168
  }), [
3992
4169
  user?.avatar,
3993
4170
  user?.name,
@@ -4002,7 +4179,15 @@ var ChatUI = ({
4002
4179
  config.features.enableToolCallsDisplay,
4003
4180
  config.labels.toolUsed,
4004
4181
  config.labels.thinking,
4005
- handleMessageAction
4182
+ config.labels.showMoreMessage,
4183
+ config.labels.showLessMessage,
4184
+ config.ui.collapseLongMessages,
4185
+ config.ui.collapseLongMessagesForUserOnly,
4186
+ config.ui.longMessagePreviewChars,
4187
+ config.ui.longMessageChunkChars,
4188
+ config.ui.renderUserMarkdown,
4189
+ handleMessageAction,
4190
+ handleToggleMessageExpansion
4006
4191
  ]);
4007
4192
  const shouldShowAgentSelector = Boolean(
4008
4193
  config.agentSelector?.enabled && onSelectAgent && agentOptions.length > 0 && (!config.agentSelector?.hideIfSingle || agentOptions.length > 1)
@@ -4094,7 +4279,8 @@ var ChatUI = ({
4094
4279
  {
4095
4280
  message,
4096
4281
  ...messageProps,
4097
- isGrouped
4282
+ isGrouped,
4283
+ isExpanded: Boolean(expandedMessageIds[message.id])
4098
4284
  }
4099
4285
  ),
4100
4286
  message.role === "assistant" && renderInlineSuggestions(message.id)