@copilotz/chat-ui 0.1.10 → 0.1.12

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,
@@ -333,6 +334,24 @@ var formatDate = (timestamp, labels) => {
333
334
  });
334
335
  }
335
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
+ };
336
355
 
337
356
  // src/components/ui/button.tsx
338
357
  var import_jsx_runtime = require("react/jsx-runtime");
@@ -713,26 +732,22 @@ var StreamingText = (0, import_react.memo)(function StreamingText2({
713
732
  ] });
714
733
  });
715
734
  var MediaRenderer = (0, import_react.memo)(function MediaRenderer2({ attachment }) {
716
- const [isPlaying, setIsPlaying] = (0, import_react.useState)(false);
717
- const audioRef = (0, import_react.useRef)(null);
718
- const videoRef = (0, import_react.useRef)(null);
719
- const togglePlayback = () => {
720
- if (attachment.kind === "audio" && audioRef.current) {
721
- if (isPlaying) {
722
- audioRef.current.pause();
723
- } else {
724
- audioRef.current.play();
725
- }
726
- setIsPlaying(!isPlaying);
727
- } else if (attachment.kind === "video" && videoRef.current) {
728
- if (isPlaying) {
729
- videoRef.current.pause();
730
- } else {
731
- videoRef.current.play();
732
- }
733
- 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;
734
740
  }
735
- };
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]);
736
751
  const formatDuration = (ms) => {
737
752
  if (!ms) return "";
738
753
  const seconds = Math.floor(ms / 1e3);
@@ -757,13 +772,10 @@ var MediaRenderer = (0, import_react.memo)(function MediaRenderer2({ attachment
757
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)(
758
773
  "audio",
759
774
  {
760
- ref: audioRef,
761
- src: attachment.dataUrl,
762
- onPlay: () => setIsPlaying(true),
763
- onPause: () => setIsPlaying(false),
764
- onEnded: () => setIsPlaying(false),
765
775
  className: "w-full mt-2",
766
- controls: true
776
+ preload: "metadata",
777
+ controls: true,
778
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("source", { src: audioPlaybackSrc, type: attachment.mimeType })
767
779
  }
768
780
  ) });
769
781
  case "video":
@@ -771,14 +783,10 @@ var MediaRenderer = (0, import_react.memo)(function MediaRenderer2({ attachment
771
783
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
772
784
  "video",
773
785
  {
774
- ref: videoRef,
775
786
  src: attachment.dataUrl,
776
787
  poster: attachment.poster,
777
788
  controls: true,
778
- className: "w-full h-auto",
779
- onPlay: () => setIsPlaying(true),
780
- onPause: () => setIsPlaying(false),
781
- onEnded: () => setIsPlaying(false)
789
+ className: "w-full h-auto"
782
790
  }
783
791
  ),
784
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 })
@@ -934,6 +942,7 @@ var Message = (0, import_react.memo)(({
934
942
  contentVisibility: "auto",
935
943
  containIntrinsicSize: "1px 400px"
936
944
  } : void 0;
945
+ const horizontalOffsetClass = showAvatar ? messageIsUser ? compactMode ? "mr-9" : "mr-11" : compactMode ? "ml-9" : "ml-11" : "";
937
946
  const handleCopy = async () => {
938
947
  try {
939
948
  await navigator.clipboard.writeText(message.content);
@@ -989,7 +998,7 @@ var Message = (0, import_react.memo)(({
989
998
  message.isEdited && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(Badge, { variant: "outline", className: "text-xs", children: "editado" })
990
999
  ] })
991
1000
  ] }),
992
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("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__ */ (0, import_jsx_runtime7.jsxs)("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: [
1001
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: `flex-1 min-w-0 ${messageIsUser ? "text-right" : "text-left"} ${horizontalOffsetClass}`, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("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: [
993
1002
  isEditing ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "space-y-2", children: [
994
1003
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
995
1004
  Textarea,
@@ -2855,7 +2864,23 @@ var FileUploadItem = (0, import_react5.memo)(function FileUploadItem2({ file, pr
2855
2864
  });
2856
2865
  var AttachmentPreview = (0, import_react5.memo)(function AttachmentPreview2({ attachment, onRemove }) {
2857
2866
  const [isPlaying, setIsPlaying] = (0, import_react5.useState)(false);
2867
+ const [audioPlaybackSrc, setAudioPlaybackSrc] = (0, import_react5.useState)(attachment.dataUrl);
2858
2868
  const audioRef = (0, import_react5.useRef)(null);
2869
+ (0, import_react5.useEffect)(() => {
2870
+ if (attachment.kind !== "audio" || !attachment.dataUrl.startsWith("data:")) {
2871
+ setAudioPlaybackSrc(attachment.dataUrl);
2872
+ return;
2873
+ }
2874
+ const objectUrl = createObjectUrlFromDataUrl(attachment.dataUrl);
2875
+ if (!objectUrl) {
2876
+ setAudioPlaybackSrc(attachment.dataUrl);
2877
+ return;
2878
+ }
2879
+ setAudioPlaybackSrc(objectUrl);
2880
+ return () => {
2881
+ URL.revokeObjectURL(objectUrl);
2882
+ };
2883
+ }, [attachment.kind, attachment.dataUrl]);
2859
2884
  const handlePlayPause = () => {
2860
2885
  if (audioRef.current) {
2861
2886
  if (isPlaying) {
@@ -2934,10 +2959,11 @@ var AttachmentPreview = (0, import_react5.memo)(function AttachmentPreview2({ at
2934
2959
  "audio",
2935
2960
  {
2936
2961
  ref: audioRef,
2937
- src: attachment.dataUrl,
2938
2962
  onPlay: () => setIsPlaying(true),
2939
2963
  onPause: () => setIsPlaying(false),
2940
- onEnded: () => setIsPlaying(false)
2964
+ onEnded: () => setIsPlaying(false),
2965
+ preload: "metadata",
2966
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("source", { src: audioPlaybackSrc, type: attachment.mimeType })
2941
2967
  }
2942
2968
  ),
2943
2969
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
@@ -4087,7 +4113,8 @@ var ChatUI = ({
4087
4113
  const renderInlineSuggestions = (messageId) => {
4088
4114
  const items = messageSuggestions?.[messageId];
4089
4115
  if (!items || items.length === 0) return null;
4090
- return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: "flex flex-wrap gap-2 mt-2 ml-11", children: items.map((suggestion, index) => /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
4116
+ const inlineSuggestionOffsetClass = config.ui.showAvatars ? config.ui.compactMode ? "ml-9" : "ml-11" : "";
4117
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { className: `flex flex-wrap gap-2 mt-2 ${inlineSuggestionOffsetClass}`, children: items.map((suggestion, index) => /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
4091
4118
  "button",
4092
4119
  {
4093
4120
  type: "button",
@@ -4649,6 +4676,7 @@ var chatUtils = {
4649
4676
  chatUtils,
4650
4677
  cn,
4651
4678
  configUtils,
4679
+ createObjectUrlFromDataUrl,
4652
4680
  defaultChatConfig,
4653
4681
  featureFlags,
4654
4682
  formatDate,