@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 CHANGED
@@ -43,6 +43,7 @@ __export(index_exports, {
43
43
  chatUtils: () => chatUtils,
44
44
  cn: () => cn,
45
45
  configUtils: () => configUtils,
46
+ createObjectUrlFromDataUrl: () => createObjectUrlFromDataUrl,
46
47
  defaultChatConfig: () => defaultChatConfig,
47
48
  featureFlags: () => featureFlags,
48
49
  formatDate: () => formatDate,
@@ -119,7 +120,9 @@ var defaultChatConfig = {
119
120
  daysAgo: "days ago",
120
121
  inputHelpText: "Press Enter to send, Shift+Enter to add a new line.",
121
122
  thinking: "Thinking...",
122
- defaultThreadName: "Main Thread"
123
+ defaultThreadName: "Main Thread",
124
+ showMoreMessage: "Show more",
125
+ showLessMessage: "Show less"
123
126
  },
124
127
  features: {
125
128
  enableThreads: true,
@@ -138,7 +141,12 @@ var defaultChatConfig = {
138
141
  showTimestamps: false,
139
142
  showAvatars: true,
140
143
  compactMode: false,
141
- showWordCount: false
144
+ showWordCount: false,
145
+ collapseLongMessages: false,
146
+ collapseLongMessagesForUserOnly: false,
147
+ longMessagePreviewChars: 4e3,
148
+ longMessageChunkChars: 12e3,
149
+ renderUserMarkdown: true
142
150
  },
143
151
  customComponent: {},
144
152
  headerActions: null
@@ -293,7 +301,7 @@ var configUtils = {
293
301
  };
294
302
 
295
303
  // src/components/chat/Message.tsx
296
- var import_react = require("react");
304
+ var import_react = __toESM(require("react"), 1);
297
305
  var import_react_markdown = __toESM(require("react-markdown"), 1);
298
306
  var import_remark_gfm = __toESM(require("remark-gfm"), 1);
299
307
  var import_rehype_highlight = __toESM(require("rehype-highlight"), 1);
@@ -326,6 +334,24 @@ var formatDate = (timestamp, labels) => {
326
334
  });
327
335
  }
328
336
  };
337
+ var createObjectUrlFromDataUrl = (dataUrl) => {
338
+ const match = dataUrl.match(/^data:(.+?);base64,(.+)$/s);
339
+ if (!match) {
340
+ return null;
341
+ }
342
+ try {
343
+ const [, mimeType, base64] = match;
344
+ const binary = atob(base64);
345
+ const bytes = new Uint8Array(binary.length);
346
+ for (let i = 0; i < binary.length; i += 1) {
347
+ bytes[i] = binary.charCodeAt(i);
348
+ }
349
+ const blob = new Blob([bytes], { type: mimeType || "application/octet-stream" });
350
+ return URL.createObjectURL(blob);
351
+ } catch {
352
+ return null;
353
+ }
354
+ };
329
355
 
330
356
  // src/components/ui/button.tsx
331
357
  var import_jsx_runtime = require("react/jsx-runtime");
@@ -614,21 +640,89 @@ var markdownComponents = {
614
640
  var remarkPluginsDefault = [import_remark_gfm.default];
615
641
  var rehypePluginsDefault = [import_rehype_highlight.default];
616
642
  var rehypePluginsEmpty = [];
643
+ var getPlainTextChunks = (content, chunkSize) => {
644
+ if (chunkSize <= 0 || content.length <= chunkSize) {
645
+ return [content];
646
+ }
647
+ const chunks = [];
648
+ let start = 0;
649
+ while (start < content.length) {
650
+ let end = Math.min(start + chunkSize, content.length);
651
+ if (end < content.length) {
652
+ const splitAt = content.lastIndexOf("\n", end);
653
+ if (splitAt > start + Math.floor(chunkSize / 2)) {
654
+ end = splitAt + 1;
655
+ }
656
+ }
657
+ chunks.push(content.slice(start, end));
658
+ start = end;
659
+ }
660
+ return chunks;
661
+ };
662
+ var hasCodeBlocks = (content) => /(^|\n)(```|~~~)/.test(content);
663
+ var getCollapsedPreview = (content, previewChars, previewOverride) => {
664
+ if (previewOverride && previewOverride.trim().length > 0) {
665
+ const normalizedPreview = previewOverride.trimEnd();
666
+ return normalizedPreview.endsWith("...") ? normalizedPreview : `${normalizedPreview}...`;
667
+ }
668
+ if (content.length <= previewChars) {
669
+ return content;
670
+ }
671
+ return `${content.slice(0, previewChars).trimEnd()}...`;
672
+ };
673
+ var LongContentShell = (0, import_react.memo)(function LongContentShell2({ children, className, style }) {
674
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className, style, children });
675
+ });
676
+ var PlainTextContent = (0, import_react.memo)(function PlainTextContent2({
677
+ content,
678
+ className = "",
679
+ chunkSize = 12e3,
680
+ style
681
+ }) {
682
+ const chunks = (0, import_react.useMemo)(() => getPlainTextChunks(content, chunkSize), [content, chunkSize]);
683
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
684
+ LongContentShell,
685
+ {
686
+ className: `text-sm leading-6 whitespace-pre-wrap break-words ${className}`.trim(),
687
+ style,
688
+ children: chunks.map((chunk, index) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react.default.Fragment, { children: chunk }, index))
689
+ }
690
+ );
691
+ });
617
692
  var StreamingText = (0, import_react.memo)(function StreamingText2({
618
693
  content,
619
694
  isStreaming = false,
620
695
  thinkingLabel = "Thinking...",
621
- className = ""
696
+ className = "",
697
+ renderMarkdown = true,
698
+ plainTextChunkChars = 12e3,
699
+ contentStyle
622
700
  }) {
623
701
  const hasContent = content.trim().length > 0;
624
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: `prose prose-sm max-w-none dark:prose-invert break-words ${className}`, children: [
625
- hasContent ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
626
- import_react_markdown.default,
702
+ const enableSyntaxHighlight = renderMarkdown && !isStreaming && hasCodeBlocks(content);
703
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
704
+ hasContent ? renderMarkdown ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
705
+ LongContentShell,
706
+ {
707
+ className: `prose prose-sm max-w-none dark:prose-invert break-words ${className}`.trim(),
708
+ style: contentStyle,
709
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
710
+ import_react_markdown.default,
711
+ {
712
+ remarkPlugins: remarkPluginsDefault,
713
+ rehypePlugins: enableSyntaxHighlight ? rehypePluginsDefault : rehypePluginsEmpty,
714
+ components: markdownComponents,
715
+ children: content
716
+ }
717
+ )
718
+ }
719
+ ) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
720
+ PlainTextContent,
627
721
  {
628
- remarkPlugins: remarkPluginsDefault,
629
- rehypePlugins: isStreaming ? rehypePluginsEmpty : rehypePluginsDefault,
630
- components: markdownComponents,
631
- children: content
722
+ content,
723
+ className,
724
+ chunkSize: plainTextChunkChars,
725
+ style: contentStyle
632
726
  }
633
727
  ) : isStreaming ? (
634
728
  // Show thinking indicator while waiting for first token
@@ -638,26 +732,22 @@ var StreamingText = (0, import_react.memo)(function StreamingText2({
638
732
  ] });
639
733
  });
640
734
  var MediaRenderer = (0, import_react.memo)(function MediaRenderer2({ attachment }) {
641
- const [isPlaying, setIsPlaying] = (0, import_react.useState)(false);
642
- const audioRef = (0, import_react.useRef)(null);
643
- const videoRef = (0, import_react.useRef)(null);
644
- const togglePlayback = () => {
645
- if (attachment.kind === "audio" && audioRef.current) {
646
- if (isPlaying) {
647
- audioRef.current.pause();
648
- } else {
649
- audioRef.current.play();
650
- }
651
- setIsPlaying(!isPlaying);
652
- } else if (attachment.kind === "video" && videoRef.current) {
653
- if (isPlaying) {
654
- videoRef.current.pause();
655
- } else {
656
- videoRef.current.play();
657
- }
658
- setIsPlaying(!isPlaying);
735
+ const [audioPlaybackSrc, setAudioPlaybackSrc] = (0, import_react.useState)(attachment.dataUrl);
736
+ (0, import_react.useEffect)(() => {
737
+ if (attachment.kind !== "audio" || !attachment.dataUrl.startsWith("data:")) {
738
+ setAudioPlaybackSrc(attachment.dataUrl);
739
+ return;
659
740
  }
660
- };
741
+ const objectUrl = createObjectUrlFromDataUrl(attachment.dataUrl);
742
+ if (!objectUrl) {
743
+ setAudioPlaybackSrc(attachment.dataUrl);
744
+ return;
745
+ }
746
+ setAudioPlaybackSrc(objectUrl);
747
+ return () => {
748
+ URL.revokeObjectURL(objectUrl);
749
+ };
750
+ }, [attachment.kind, attachment.dataUrl]);
661
751
  const formatDuration = (ms) => {
662
752
  if (!ms) return "";
663
753
  const seconds = Math.floor(ms / 1e3);
@@ -682,13 +772,10 @@ var MediaRenderer = (0, import_react.memo)(function MediaRenderer2({ attachment
682
772
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "flex w-full max-w-md py-0 min-w-64 items-center gap-3", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
683
773
  "audio",
684
774
  {
685
- ref: audioRef,
686
- src: attachment.dataUrl,
687
- onPlay: () => setIsPlaying(true),
688
- onPause: () => setIsPlaying(false),
689
- onEnded: () => setIsPlaying(false),
690
775
  className: "w-full mt-2",
691
- controls: true
776
+ preload: "metadata",
777
+ controls: true,
778
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("source", { src: audioPlaybackSrc, type: attachment.mimeType })
692
779
  }
693
780
  ) });
694
781
  case "video":
@@ -696,14 +783,10 @@ var MediaRenderer = (0, import_react.memo)(function MediaRenderer2({ attachment
696
783
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
697
784
  "video",
698
785
  {
699
- ref: videoRef,
700
786
  src: attachment.dataUrl,
701
787
  poster: attachment.poster,
702
788
  controls: true,
703
- className: "w-full h-auto",
704
- onPlay: () => setIsPlaying(true),
705
- onPause: () => setIsPlaying(false),
706
- onEnded: () => setIsPlaying(false)
789
+ className: "w-full h-auto"
707
790
  }
708
791
  ),
709
792
  attachment.fileName && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "absolute bottom-0 left-0 right-0 bg-black/50 text-white text-xs p-2", children: attachment.fileName })
@@ -798,6 +881,15 @@ var arePropsEqual = (prevProps, nextProps) => {
798
881
  if (prevProps.className !== nextProps.className) return false;
799
882
  if (prevProps.toolUsedLabel !== nextProps.toolUsedLabel) return false;
800
883
  if (prevProps.thinkingLabel !== nextProps.thinkingLabel) return false;
884
+ if (prevProps.showMoreLabel !== nextProps.showMoreLabel) return false;
885
+ if (prevProps.showLessLabel !== nextProps.showLessLabel) return false;
886
+ if (prevProps.collapseLongMessages !== nextProps.collapseLongMessages) return false;
887
+ if (prevProps.collapseLongMessagesForUserOnly !== nextProps.collapseLongMessagesForUserOnly) return false;
888
+ if (prevProps.longMessagePreviewChars !== nextProps.longMessagePreviewChars) return false;
889
+ if (prevProps.longMessageChunkChars !== nextProps.longMessageChunkChars) return false;
890
+ if (prevProps.renderUserMarkdown !== nextProps.renderUserMarkdown) return false;
891
+ if (prevProps.isExpanded !== nextProps.isExpanded) return false;
892
+ if (prevProps.onToggleExpanded !== nextProps.onToggleExpanded) return false;
801
893
  if (prevProps.isGrouped !== nextProps.isGrouped) return false;
802
894
  if (prevProps.assistantAvatar !== nextProps.assistantAvatar) return false;
803
895
  return true;
@@ -820,6 +912,15 @@ var Message = (0, import_react.memo)(({
820
912
  className = "",
821
913
  toolUsedLabel,
822
914
  thinkingLabel = "Thinking...",
915
+ showMoreLabel = "Show more",
916
+ showLessLabel = "Show less",
917
+ collapseLongMessages = false,
918
+ collapseLongMessagesForUserOnly = false,
919
+ longMessagePreviewChars = 4e3,
920
+ longMessageChunkChars = 12e3,
921
+ renderUserMarkdown = true,
922
+ isExpanded = false,
923
+ onToggleExpanded,
823
924
  isGrouped = false
824
925
  }) => {
825
926
  const [isEditing, setIsEditing] = (0, import_react.useState)(false);
@@ -829,6 +930,18 @@ var Message = (0, import_react.memo)(({
829
930
  const messageIsUser = isUser ?? message.role === "user";
830
931
  const canEdit = enableEdit && messageIsUser;
831
932
  const canRegenerate = enableRegenerate && !messageIsUser;
933
+ const normalizedPreviewChars = Math.max(longMessagePreviewChars, 1);
934
+ const normalizedChunkChars = Math.max(longMessageChunkChars, 1);
935
+ const previewOverride = typeof message.metadata?.previewContent === "string" ? message.metadata.previewContent : void 0;
936
+ const canCollapseMessage = collapseLongMessages && !message.isStreaming && message.content.length > normalizedPreviewChars && (!collapseLongMessagesForUserOnly || messageIsUser);
937
+ const isCollapsed = canCollapseMessage && !isExpanded;
938
+ const contentToRender = isCollapsed ? getCollapsedPreview(message.content, normalizedPreviewChars, previewOverride) : message.content;
939
+ const shouldRenderMarkdown = !isCollapsed && (!messageIsUser || renderUserMarkdown);
940
+ const shouldApplyLargeContentContainment = !isCollapsed && message.content.length > normalizedChunkChars;
941
+ const contentStyle = shouldApplyLargeContentContainment ? {
942
+ contentVisibility: "auto",
943
+ containIntrinsicSize: "1px 400px"
944
+ } : void 0;
832
945
  const handleCopy = async () => {
833
946
  try {
834
947
  await navigator.clipboard.writeText(message.content);
@@ -857,6 +970,9 @@ var Message = (0, import_react.memo)(({
857
970
  const handleRegenerate = () => {
858
971
  onAction?.({ action: "regenerate", messageId: message.id });
859
972
  };
973
+ const handleToggleExpanded = () => {
974
+ onToggleExpanded?.(message.id);
975
+ };
860
976
  const formatTime = (timestamp) => {
861
977
  return new Date(timestamp).toLocaleTimeString("pt-BR", {
862
978
  hour: "2-digit",
@@ -907,11 +1023,26 @@ var Message = (0, import_react.memo)(({
907
1023
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
908
1024
  StreamingText,
909
1025
  {
910
- content: message.content,
1026
+ content: contentToRender,
911
1027
  isStreaming: message.isStreaming,
912
- thinkingLabel
1028
+ thinkingLabel,
1029
+ renderMarkdown: shouldRenderMarkdown,
1030
+ plainTextChunkChars: normalizedChunkChars,
1031
+ contentStyle
913
1032
  }
914
1033
  ),
1034
+ canCollapseMessage && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "mt-3", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1035
+ Button,
1036
+ {
1037
+ type: "button",
1038
+ variant: "ghost",
1039
+ size: "sm",
1040
+ className: "h-auto px-0 text-xs font-medium text-current hover:bg-transparent hover:opacity-80",
1041
+ "aria-expanded": !isCollapsed,
1042
+ onClick: handleToggleExpanded,
1043
+ children: isCollapsed ? showMoreLabel : showLessLabel
1044
+ }
1045
+ ) }),
915
1046
  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
1047
  ] }),
917
1048
  !isEditing && (showActions || copied) && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: `absolute -top-2 flex gap-1 ${messageIsUser ? "-left-2" : "-right-2"}`, children: [
@@ -2732,7 +2863,23 @@ var FileUploadItem = (0, import_react5.memo)(function FileUploadItem2({ file, pr
2732
2863
  });
2733
2864
  var AttachmentPreview = (0, import_react5.memo)(function AttachmentPreview2({ attachment, onRemove }) {
2734
2865
  const [isPlaying, setIsPlaying] = (0, import_react5.useState)(false);
2866
+ const [audioPlaybackSrc, setAudioPlaybackSrc] = (0, import_react5.useState)(attachment.dataUrl);
2735
2867
  const audioRef = (0, import_react5.useRef)(null);
2868
+ (0, import_react5.useEffect)(() => {
2869
+ if (attachment.kind !== "audio" || !attachment.dataUrl.startsWith("data:")) {
2870
+ setAudioPlaybackSrc(attachment.dataUrl);
2871
+ return;
2872
+ }
2873
+ const objectUrl = createObjectUrlFromDataUrl(attachment.dataUrl);
2874
+ if (!objectUrl) {
2875
+ setAudioPlaybackSrc(attachment.dataUrl);
2876
+ return;
2877
+ }
2878
+ setAudioPlaybackSrc(objectUrl);
2879
+ return () => {
2880
+ URL.revokeObjectURL(objectUrl);
2881
+ };
2882
+ }, [attachment.kind, attachment.dataUrl]);
2736
2883
  const handlePlayPause = () => {
2737
2884
  if (audioRef.current) {
2738
2885
  if (isPlaying) {
@@ -2811,10 +2958,11 @@ var AttachmentPreview = (0, import_react5.memo)(function AttachmentPreview2({ at
2811
2958
  "audio",
2812
2959
  {
2813
2960
  ref: audioRef,
2814
- src: attachment.dataUrl,
2815
2961
  onPlay: () => setIsPlaying(true),
2816
2962
  onPause: () => setIsPlaying(false),
2817
- onEnded: () => setIsPlaying(false)
2963
+ onEnded: () => setIsPlaying(false),
2964
+ preload: "metadata",
2965
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("source", { src: audioPlaybackSrc, type: attachment.mimeType })
2818
2966
  }
2819
2967
  ),
2820
2968
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
@@ -3734,6 +3882,7 @@ var ChatUI = ({
3734
3882
  };
3735
3883
  const [inputValue, setInputValue] = (0, import_react7.useState)("");
3736
3884
  const [attachments, setAttachments] = (0, import_react7.useState)([]);
3885
+ const [expandedMessageIds, setExpandedMessageIds] = (0, import_react7.useState)({});
3737
3886
  const [state, setState] = (0, import_react7.useState)({
3738
3887
  isRecording: false,
3739
3888
  selectedThreadId: currentThreadId,
@@ -3838,6 +3987,24 @@ var ChatUI = ({
3838
3987
  }
3839
3988
  });
3840
3989
  }, [messages, state.isAtBottom, virtualizer]);
3990
+ (0, import_react7.useEffect)(() => {
3991
+ virtualizer.measure();
3992
+ }, [expandedMessageIds, virtualizer]);
3993
+ (0, import_react7.useEffect)(() => {
3994
+ const validMessageIds = new Set(messages.map((message) => message.id));
3995
+ setExpandedMessageIds((prev) => {
3996
+ const activeIds = Object.keys(prev);
3997
+ const staleIds = activeIds.filter((messageId) => !validMessageIds.has(messageId));
3998
+ if (staleIds.length === 0) {
3999
+ return prev;
4000
+ }
4001
+ const next = { ...prev };
4002
+ staleIds.forEach((messageId) => {
4003
+ delete next[messageId];
4004
+ });
4005
+ return next;
4006
+ });
4007
+ }, [messages]);
3841
4008
  const handleScroll = (0, import_react7.useCallback)((e) => {
3842
4009
  const { scrollTop, scrollHeight, clientHeight } = e.currentTarget;
3843
4010
  const isAtBottom = scrollHeight - scrollTop - clientHeight < 50;
@@ -3875,6 +4042,19 @@ var ChatUI = ({
3875
4042
  break;
3876
4043
  }
3877
4044
  }, [callbacks, createStateCallback]);
4045
+ const handleToggleMessageExpansion = (0, import_react7.useCallback)((messageId) => {
4046
+ setExpandedMessageIds((prev) => {
4047
+ if (prev[messageId]) {
4048
+ const next = { ...prev };
4049
+ delete next[messageId];
4050
+ return next;
4051
+ }
4052
+ return {
4053
+ ...prev,
4054
+ [messageId]: true
4055
+ };
4056
+ });
4057
+ }, []);
3878
4058
  const handleCreateThread = (0, import_react7.useCallback)((title) => {
3879
4059
  callbacks.onCreateThread?.(title, createStateCallback());
3880
4060
  }, [callbacks, createStateCallback]);
@@ -3979,7 +4159,15 @@ var ChatUI = ({
3979
4159
  compactMode: config.ui.compactMode,
3980
4160
  onAction: handleMessageAction,
3981
4161
  toolUsedLabel: config.labels.toolUsed,
3982
- thinkingLabel: config.labels.thinking
4162
+ thinkingLabel: config.labels.thinking,
4163
+ showMoreLabel: config.labels.showMoreMessage,
4164
+ showLessLabel: config.labels.showLessMessage,
4165
+ collapseLongMessages: config.ui.collapseLongMessages,
4166
+ collapseLongMessagesForUserOnly: config.ui.collapseLongMessagesForUserOnly,
4167
+ longMessagePreviewChars: config.ui.longMessagePreviewChars,
4168
+ longMessageChunkChars: config.ui.longMessageChunkChars,
4169
+ renderUserMarkdown: config.ui.renderUserMarkdown,
4170
+ onToggleExpanded: handleToggleMessageExpansion
3983
4171
  }), [
3984
4172
  user?.avatar,
3985
4173
  user?.name,
@@ -3994,7 +4182,15 @@ var ChatUI = ({
3994
4182
  config.features.enableToolCallsDisplay,
3995
4183
  config.labels.toolUsed,
3996
4184
  config.labels.thinking,
3997
- handleMessageAction
4185
+ config.labels.showMoreMessage,
4186
+ config.labels.showLessMessage,
4187
+ config.ui.collapseLongMessages,
4188
+ config.ui.collapseLongMessagesForUserOnly,
4189
+ config.ui.longMessagePreviewChars,
4190
+ config.ui.longMessageChunkChars,
4191
+ config.ui.renderUserMarkdown,
4192
+ handleMessageAction,
4193
+ handleToggleMessageExpansion
3998
4194
  ]);
3999
4195
  const shouldShowAgentSelector = Boolean(
4000
4196
  config.agentSelector?.enabled && onSelectAgent && agentOptions.length > 0 && (!config.agentSelector?.hideIfSingle || agentOptions.length > 1)
@@ -4086,7 +4282,8 @@ var ChatUI = ({
4086
4282
  {
4087
4283
  message,
4088
4284
  ...messageProps,
4089
- isGrouped
4285
+ isGrouped,
4286
+ isExpanded: Boolean(expandedMessageIds[message.id])
4090
4287
  }
4091
4288
  ),
4092
4289
  message.role === "assistant" && renderInlineSuggestions(message.id)
@@ -4477,6 +4674,7 @@ var chatUtils = {
4477
4674
  chatUtils,
4478
4675
  cn,
4479
4676
  configUtils,
4677
+ createObjectUrlFromDataUrl,
4480
4678
  defaultChatConfig,
4481
4679
  featureFlags,
4482
4680
  formatDate,