@qredence/fleet 0.5.11 → 0.6.1

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.
Files changed (132) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/web/client/assets/action-swap-roll-COzQ86wm.js +1 -0
  3. package/dist/web/client/assets/agent-activity-svETLf_5.js +1 -0
  4. package/dist/web/client/assets/agent-disclosure-DAMuuGcM.js +1 -0
  5. package/dist/web/client/assets/analytics-stub-B6zGMuSV.js +1 -0
  6. package/dist/web/client/assets/{artifacts-panel-CQmFEF_H.js → artifacts-panel-HVjqBMyx.js} +1 -1
  7. package/dist/web/client/assets/button-C5wFDY5_.js +1 -0
  8. package/dist/web/client/assets/card-DA3Vnqay.js +1 -0
  9. package/dist/web/client/assets/{chat-yh02zgA7.js → chat-BhYseKd6.js} +7 -7
  10. package/dist/web/client/assets/{chat-command-palette-CmHTS3rs.js → chat-command-palette-LILDO_vG.js} +1 -1
  11. package/dist/web/client/assets/{citations-C_tPOJE_.js → citations-BPv8HIpf.js} +2 -2
  12. package/dist/web/client/assets/command-PhicLCoO.js +1 -0
  13. package/dist/web/client/assets/dialog-pBGUjAQ7.js +1 -0
  14. package/dist/web/client/assets/fleet-message-queue-fbYYwMj0.js +1 -0
  15. package/dist/web/client/assets/{fleet-pi-tool-renderer-BEDIO3al.js → fleet-pi-tool-renderer-BRgNnmaJ.js} +28 -28
  16. package/dist/web/client/assets/fleet-reasoning-panel-CjA2dwUJ.js +1 -0
  17. package/dist/web/client/assets/{fork-picker-dialog-ByBOGVsa.js → fork-picker-dialog-B7NNpVmW.js} +1 -1
  18. package/dist/web/client/assets/globals-CuIiqOTg.css +2 -0
  19. package/dist/web/client/assets/horizontal-resize-Cp3iHMH7.js +1 -0
  20. package/dist/web/client/assets/index-CSGplyr_.js +12 -0
  21. package/dist/web/client/assets/{input-0wwjWjKq.js → input-PcdubeKn.js} +1 -1
  22. package/dist/web/client/assets/input-group-C2HJEZvl.js +1 -0
  23. package/dist/web/client/assets/ipython-tool-CyuMOxz1.js +3 -0
  24. package/dist/web/client/assets/{markdown-code-CuQcR3UO.js → markdown-code-xNyjdZ4g.js} +1 -1
  25. package/dist/web/client/assets/{markdown-DInPmRUG.js → markdown-lvexwooo.js} +2 -2
  26. package/dist/web/client/assets/{model-selector-list-DsKTuAgS.js → model-selector-list-CoXX358G.js} +1 -1
  27. package/dist/web/client/assets/{notify-DgMKgZZW.js → notify-DxL0VD7Z.js} +1 -1
  28. package/dist/web/client/assets/openui-renderer-TLNe5e_Q.js +378 -0
  29. package/dist/web/client/assets/pencil-DcUwsKLn.js +1 -0
  30. package/dist/web/client/assets/prompt-input-fwP8TsgQ.js +1 -0
  31. package/dist/web/client/assets/{prompt-suggestions-Cc--lSNR.js → prompt-suggestions-BlGXmWS7.js} +1 -1
  32. package/dist/web/client/assets/provider-catalog-DYMFdbKc.js +1 -0
  33. package/dist/web/client/assets/provider-label-B4q5mI65.js +1 -0
  34. package/dist/web/client/assets/{repl-panel-DsPX04Xr.js → repl-panel-C5om8MGU.js} +1 -1
  35. package/dist/web/client/assets/{right-panel-registry-DewpBWHM.js → right-panel-registry-BBlxLxnx.js} +2 -2
  36. package/dist/web/client/assets/routes-XGsAIqBJ.js +16 -0
  37. package/dist/web/client/assets/settings-dialog-BSBasR3o.js +2 -0
  38. package/dist/web/client/assets/{subagent-chat-panel-DkVUumrP.js → subagent-chat-panel-oeLDPDAK.js} +1 -1
  39. package/dist/web/client/assets/{subagent-transcript-D8wmQxXe.js → subagent-transcript-DaVHjNUH.js} +1 -1
  40. package/dist/web/client/assets/{subagents-panel-BMp2Ego0.js → subagents-panel-D_aVgSCi.js} +1 -1
  41. package/dist/web/client/assets/surfaces-BhAXz9So.js +1 -0
  42. package/dist/web/client/assets/{switch-9rTeg6J6.js → switch-WhXeJNNY.js} +1 -1
  43. package/dist/web/client/assets/{toast-Bt9p2OL4.js → toast-D3c9XeGe.js} +1 -1
  44. package/dist/web/client/assets/{tool-result-DfnPN0FQ.js → tool-result-Cki8WUNc.js} +3 -3
  45. package/dist/web/client/assets/virtualized-turn-list-CdMa-ydT.js +6 -0
  46. package/dist/web/client/assets/workspace-panel-77bDbprs.js +1 -0
  47. package/dist/web/client/bundle-budget.json +6 -6
  48. package/dist/web/launcher.mjs +10 -1
  49. package/dist/web/server/{_tanstack-start-manifest_v-po4woX6g-AUGCOEZ2.js → _tanstack-start-manifest_v-0L7fqnH4-KMVGUFE4.js} +25 -24
  50. package/dist/web/server/{agent-activity-CS2rbF_C-A3R26CYH.js → agent-activity-BuEE6iVw-CDZLK4D2.js} +4 -4
  51. package/dist/web/server/{artifacts-panel-8GGUCtOd-X5TNKTY2.js → artifacts-panel-D3ySO--V-XSL53CXL.js} +18 -18
  52. package/dist/web/server/{chat-command-palette-Dgl5tqWn-72LLEWUD.js → chat-command-palette-kAbXhWVg-TJWXH2WW.js} +9 -9
  53. package/dist/web/server/{chunk-O4OTWWPF.js → chunk-2DDWXTVG.js} +5 -5
  54. package/dist/web/server/{chunk-MIW4LJTV.js → chunk-2JHTYWD5.js} +15 -23
  55. package/dist/web/server/{chunk-7MDZVLIY.js → chunk-3TIMGWWI.js} +1 -2
  56. package/dist/web/server/{chunk-AGFRJQZ7.js → chunk-4WBFCB4O.js} +257 -257
  57. package/dist/web/server/{chunk-P6UMR32I.js → chunk-5UCAJBAH.js} +4 -4
  58. package/dist/web/server/{chunk-BXWAUTTH.js → chunk-D27LRYVT.js} +42 -42
  59. package/dist/web/server/{chunk-YXXRP3NS.js → chunk-DLUZFOG3.js} +5 -5
  60. package/dist/web/server/{chunk-K3BXQG3N.js → chunk-EBG3XFGQ.js} +60 -60
  61. package/dist/web/server/{chunk-2ATMSG7U.js → chunk-EJEPDVK6.js} +3 -3
  62. package/dist/web/server/{chunk-IJPCV6DH.js → chunk-FYDQK6UX.js} +1 -1
  63. package/dist/web/server/{chunk-WKV2Z4KO.js → chunk-HF7DG7PC.js} +74 -74
  64. package/dist/web/server/{chunk-WYL2DW5R.js → chunk-JTCCQZLN.js} +2 -2
  65. package/dist/web/server/{chunk-UAPNEWCA.js → chunk-KP2PNDGL.js} +6 -6
  66. package/dist/web/server/chunk-M5RSPNYN.js +22 -0
  67. package/dist/web/server/{chunk-7SR6WDM2.js → chunk-MNBGB2SP.js} +2 -2
  68. package/dist/web/server/{chunk-HSJMLO36.js → chunk-MNPHMZU7.js} +3 -3
  69. package/dist/web/server/{chunk-JQDLJ7KI.js → chunk-NGCQJ33Y.js} +1134 -1134
  70. package/dist/web/server/{chunk-7SD2ZSEA.js → chunk-NIDBN7BQ.js} +1 -1
  71. package/dist/web/server/{chunk-ZJFA4F5X.js → chunk-NIZBFVJF.js} +2 -2
  72. package/dist/web/server/{chunk-P7H2OPKF.js → chunk-NJ4QEZHK.js} +11 -4
  73. package/dist/web/server/{chunk-WM4PUT6M.js → chunk-NNKJTKOQ.js} +234 -234
  74. package/dist/web/server/{chunk-N6NOTURM.js → chunk-NWDI3OWB.js} +4 -4
  75. package/dist/web/server/{chunk-ABBWBBSS.js → chunk-O3HDELZ2.js} +74826 -74825
  76. package/dist/web/server/{chunk-GGT2ZKDB.js → chunk-PLYZ7P2Y.js} +11 -155
  77. package/dist/web/server/chunk-PTCKOR23.js +39 -0
  78. package/dist/web/server/{chunk-RNOJACWQ.js → chunk-QFRCJWQQ.js} +7 -7
  79. package/dist/web/server/{chunk-NXJ32VFR.js → chunk-QIULBRTU.js} +13 -13
  80. package/dist/web/server/{chunk-XKKXXG6N.js → chunk-RIP3PBWT.js} +3 -3
  81. package/dist/web/server/{chunk-YKVGQBZA.js → chunk-S5LFXXDY.js} +625 -622
  82. package/dist/web/server/{chunk-XLNCWZMZ.js → chunk-UCUFC553.js} +3 -1
  83. package/dist/web/server/{chunk-KOUEJ3NU.js → chunk-UL2725KQ.js} +3 -3
  84. package/dist/web/server/{chunk-HFQTI7AX.js → chunk-VPKX7QRU.js} +3 -3
  85. package/dist/web/server/{chunk-LZ4Q5KUJ.js → chunk-WNCAL4HN.js} +133 -133
  86. package/dist/web/server/{chunk-C5X6G26O.js → chunk-XIB6XNUX.js} +436 -436
  87. package/dist/web/server/{chunk-JUROEEWM.js → chunk-YGYOBX7G.js} +82 -4
  88. package/dist/web/server/{chunk-WILCIZYU.js → chunk-ZIFSJ3HQ.js} +626 -626
  89. package/dist/web/server/fleet-message-queue-D_w8lSeY-T3HB7HVQ.js +152 -0
  90. package/dist/web/server/{fleet-pi-tool-renderer-umKunT3K-O4GBH35V.js → fleet-pi-tool-renderer-qwc-qZFq-4U556N5Q.js} +12 -12
  91. package/dist/web/server/{fleet-reasoning-panel-BxC2COxi-GPLMQSTT.js → fleet-reasoning-panel-DG_vuvv0-NCZWEEIT.js} +26 -26
  92. package/dist/web/server/{fleet-subagent-list-CpSYzCIq-JDH2LHSS.js → fleet-subagent-list-BGb0PjhF-2X67FMUV.js} +1 -1
  93. package/dist/web/server/{fleet-tool-timeline-IB36_PB2-XJENTO6I.js → fleet-tool-timeline-D6yM4ZMz-ZSFVRRRI.js} +1 -1
  94. package/dist/web/server/{fork-picker-dialog-DlINz9UQ-UWIXFDJX.js → fork-picker-dialog-Ds6Tgtu3-DIBWN7MV.js} +5 -5
  95. package/dist/web/server/{model-selector-list-OYnzNbyu-HBGCNF2L.js → model-selector-list-DxeGZoD2-P6S6FSJH.js} +9 -9
  96. package/dist/web/server/{openui-renderer-Cj7kfqyL-EKJ6JTTQ.js → openui-renderer-MKFv6LlH-MDRSVFEY.js} +11 -11
  97. package/dist/web/server/{prompt-suggestions-D4AEEY7K-RZSP7PWV.js → prompt-suggestions-BOItmGnD-YQA7RHLG.js} +2 -2
  98. package/dist/web/server/{repl-panel-D5KsoJik-BQKO53JO.js → repl-panel-CuyF8wt5-CHDT2Q3N.js} +2 -2
  99. package/dist/web/server/{router-Ci1wxI7I-E2AYMNON.js → router-Dg72SESP-PCWKLFRG.js} +1239 -470
  100. package/dist/web/server/{routes-CRpm9-Sa-IYK6MUJO.js → routes-BGrVtPYF-SURPAJN6.js} +5781 -5588
  101. package/dist/web/server/server.js +2 -2
  102. package/dist/web/server/{settings-dialog-BvkgNflv-GPJ6COQO.js → settings-dialog-CK0FJlTg-HZRBAXDZ.js} +1577 -1264
  103. package/dist/web/server/{subagent-chat-panel-CMuvW9Sd-M5TEEGQW.js → subagent-chat-panel-gzamnTgK-PQ2TLLJF.js} +21 -21
  104. package/dist/web/server/{subagents-panel-DgfB6_Yy-4INEPE3Y.js → subagents-panel-Berjd7HJ-EI5ZKC6V.js} +20 -20
  105. package/dist/web/server/{workspace-panel-bpwj-Veu-Q2CEJMN4.js → workspace-panel-CBBa_3UO-B4PPFPMA.js} +46 -21
  106. package/package.json +1 -1
  107. package/dist/web/client/assets/action-swap-roll-BB8DFMuf.js +0 -1
  108. package/dist/web/client/assets/agent-activity-CwMDU7aP.js +0 -1
  109. package/dist/web/client/assets/agent-disclosure-B8SnhQXL.js +0 -1
  110. package/dist/web/client/assets/analytics-stub-Cas1He0w.js +0 -1
  111. package/dist/web/client/assets/button-BR2WWrFo.js +0 -1
  112. package/dist/web/client/assets/card-BKl76a3R.js +0 -1
  113. package/dist/web/client/assets/command-DPfiaYUL.js +0 -1
  114. package/dist/web/client/assets/dialog-BEZuZhzk.js +0 -1
  115. package/dist/web/client/assets/fleet-message-queue-DUz6Hfrf.js +0 -1
  116. package/dist/web/client/assets/fleet-reasoning-panel-CbkO7u-v.js +0 -1
  117. package/dist/web/client/assets/globals-B_I1Eq1u.css +0 -2
  118. package/dist/web/client/assets/horizontal-resize-qK6eRhIG.js +0 -1
  119. package/dist/web/client/assets/index-C1zOjrMK.js +0 -12
  120. package/dist/web/client/assets/input-group-BFDsCVS9.js +0 -1
  121. package/dist/web/client/assets/ipython-tool-ldJ9F1Cw.js +0 -3
  122. package/dist/web/client/assets/openui-renderer-J3dhPpL4.js +0 -378
  123. package/dist/web/client/assets/prompt-input-qPIXZ-Rb.js +0 -1
  124. package/dist/web/client/assets/provider-catalog-D8G66gwc.js +0 -1
  125. package/dist/web/client/assets/provider-label-Dpr7djG9.js +0 -1
  126. package/dist/web/client/assets/routes-vNkVToAF.js +0 -16
  127. package/dist/web/client/assets/settings-dialog-B-v4me3Y.js +0 -1
  128. package/dist/web/client/assets/surfaces-DI-U-_2a.js +0 -1
  129. package/dist/web/client/assets/virtualized-turn-list-DMJENRY0.js +0 -6
  130. package/dist/web/client/assets/workspace-panel-G6yMpKJ8.js +0 -1
  131. package/dist/web/server/chunk-QK4CHLVV.js +0 -17
  132. package/dist/web/server/fleet-message-queue-j0DCU5Xy-5HKBHMPV.js +0 -75
@@ -3,18 +3,18 @@ import {
3
3
  } from "./chunk-FJV6EQDW.js";
4
4
  import {
5
5
  notify
6
- } from "./chunk-WYL2DW5R.js";
6
+ } from "./chunk-JTCCQZLN.js";
7
7
  import {
8
8
  LazyMarkdown,
9
9
  useHoverCapable
10
- } from "./chunk-N6NOTURM.js";
10
+ } from "./chunk-NWDI3OWB.js";
11
11
  import {
12
12
  Copy
13
- } from "./chunk-2ATMSG7U.js";
13
+ } from "./chunk-EJEPDVK6.js";
14
14
  import {
15
15
  AnimatePresence,
16
16
  useReducedMotion
17
- } from "./chunk-ZJFA4F5X.js";
17
+ } from "./chunk-NIZBFVJF.js";
18
18
  import {
19
19
  ChevronRight
20
20
  } from "./chunk-3C4CFCIS.js";
@@ -22,7 +22,7 @@ import {
22
22
  EASE_OUT,
23
23
  SPRING_LAYOUT,
24
24
  m
25
- } from "./chunk-K3BXQG3N.js";
25
+ } from "./chunk-EBG3XFGQ.js";
26
26
  import {
27
27
  segmentOpenUIContent
28
28
  } from "./chunk-NFRJDPB3.js";
@@ -42,7 +42,7 @@ import {
42
42
  cn
43
43
  } from "./chunk-DJMHPJBP.js";
44
44
 
45
- // web/app/dist/server/assets/virtualized-turn-list-CVAfBX0T.js
45
+ // web/app/dist/server/assets/virtualized-turn-list-DNo8CDwR.js
46
46
  import { Fragment, Suspense, cloneElement, createContext, lazy, memo, useCallback, useContext, useEffect, useEffectEvent, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
47
47
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
48
48
  import { createPortal } from "react-dom";
@@ -86,6 +86,29 @@ var FileCode = createLucideIcon("file-code", [
86
86
  key: "yinavb"
87
87
  }]
88
88
  ]);
89
+ function groupMessages(messages) {
90
+ const turns = [];
91
+ let current;
92
+ for (const message of messages) {
93
+ if (message.role === "user") {
94
+ if (current) turns.push(current);
95
+ current = {
96
+ user: message,
97
+ assistants: []
98
+ };
99
+ continue;
100
+ }
101
+ if (message.role !== "assistant") continue;
102
+ if (!current || message.source === "local") {
103
+ if (current) turns.push(current);
104
+ current = { assistants: [message] };
105
+ continue;
106
+ }
107
+ current.assistants.push(message);
108
+ }
109
+ if (current) turns.push(current);
110
+ return turns;
111
+ }
89
112
  var MessageSideContext = createContext(void 0);
90
113
  var MessageBubbleContext = createContext({
91
114
  animateIn: true,
@@ -747,258 +770,124 @@ function MessageContent({ className, ...props }) {
747
770
  ...props
748
771
  });
749
772
  }
750
- function ImageLightbox({ open, onClose, images, initialIndex = 0 }) {
751
- const [currentIndex, setCurrentIndex] = useState(initialIndex);
752
- const hasMultipleImages = images.length > 1;
753
- useEffect(() => {
754
- if (open) setCurrentIndex(initialIndex);
755
- }, [open, initialIndex]);
756
- const goToPrevious = useCallback((event) => {
757
- event?.stopPropagation();
758
- setCurrentIndex((prev) => prev > 0 ? prev - 1 : images.length - 1);
759
- }, [images.length]);
760
- const goToNext = useCallback((event) => {
761
- event?.stopPropagation();
762
- setCurrentIndex((prev) => prev < images.length - 1 ? prev + 1 : 0);
763
- }, [images.length]);
764
- const onLightboxKeyDown = useEffectEvent((event) => {
765
- switch (event.key) {
766
- case "ArrowLeft":
767
- event.preventDefault();
768
- if (hasMultipleImages) goToPrevious();
769
- break;
770
- case "ArrowRight":
771
- event.preventDefault();
772
- if (hasMultipleImages) goToNext();
773
- }
773
+ var ErrorMessage = memo(function ErrorMessage2({ title = "Something went wrong", message, className }) {
774
+ return /* @__PURE__ */ jsx("div", {
775
+ className: cn("flex justify-start", className),
776
+ children: /* @__PURE__ */ jsxs("div", {
777
+ className: "rounded-[8px] border border-red-500/30 bg-red-500/10 px-4 py-2.5 text-sm text-an-foreground",
778
+ children: [/* @__PURE__ */ jsx("div", {
779
+ className: "font-medium text-an-foreground",
780
+ children: title
781
+ }), /* @__PURE__ */ jsx("div", {
782
+ className: "mt-0.5 text-an-foreground-muted",
783
+ children: message
784
+ })]
785
+ })
774
786
  });
787
+ });
788
+ var timeFormatter = new Intl.DateTimeFormat("en-US", {
789
+ hour: "numeric",
790
+ minute: "2-digit",
791
+ hour12: true
792
+ });
793
+ var dateFormatter = new Intl.DateTimeFormat("en-US", {
794
+ month: "short",
795
+ day: "numeric"
796
+ });
797
+ function formatTimestamp(date) {
798
+ const now = /* @__PURE__ */ new Date();
799
+ if (date.getFullYear() === now.getFullYear() && date.getMonth() === now.getMonth() && date.getDate() === now.getDate()) return timeFormatter.format(date);
800
+ return dateFormatter.format(date);
801
+ }
802
+ function CopyButton({ text, onCopied }) {
803
+ const [copied, setCopied] = useState(false);
804
+ const copiedTimerRef = useRef(null);
775
805
  useEffect(() => {
776
- if (!open) return;
777
- const handler = (event) => onLightboxKeyDown(event);
778
- window.addEventListener("keydown", handler, true);
779
- return () => window.removeEventListener("keydown", handler, true);
780
- }, [open]);
781
- useEffect(() => {
782
- if (!open) return;
783
- const previousOverflow = document.body.style.overflow;
784
- document.body.style.overflow = "hidden";
785
806
  return () => {
786
- document.body.style.overflow = previousOverflow;
807
+ if (copiedTimerRef.current) window.clearTimeout(copiedTimerRef.current);
787
808
  };
788
- }, [open]);
789
- const dialogRef = useRef(null);
790
- const onLightDismiss = useEffectEvent((event) => {
791
- if (dialogRef.current && event.target === dialogRef.current) onClose();
809
+ }, []);
810
+ const handleCopy = async () => {
811
+ try {
812
+ await navigator.clipboard.writeText(text);
813
+ setCopied(true);
814
+ if (copiedTimerRef.current) window.clearTimeout(copiedTimerRef.current);
815
+ copiedTimerRef.current = window.setTimeout(() => {
816
+ setCopied(false);
817
+ copiedTimerRef.current = null;
818
+ }, 2e3);
819
+ notify.success("Copied to clipboard");
820
+ onCopied?.();
821
+ } catch {
822
+ notify.error("Failed to copy to clipboard");
823
+ }
824
+ };
825
+ return /* @__PURE__ */ jsx("button", {
826
+ type: "button",
827
+ tabIndex: -1,
828
+ "aria-label": copied ? "Copied" : "Copy message",
829
+ onClick: handleCopy,
830
+ onPointerDown: (event) => {
831
+ event.stopPropagation();
832
+ },
833
+ onMouseDown: (event) => event.stopPropagation(),
834
+ className: cn("flex size-6 items-center justify-center rounded-md transition-[background-color,opacity,transform] duration-150 ease-out active:scale-[0.97]", "bg-transparent opacity-50 hover:bg-an-foreground/10 hover:opacity-100"),
835
+ children: /* @__PURE__ */ jsxs("div", {
836
+ className: "relative size-3.5",
837
+ children: [/* @__PURE__ */ jsx(Copy, { className: cn("absolute inset-0 h-3.5 w-3.5 text-an-foreground-muted transition-[opacity,transform] duration-150 ease-out", copied ? "scale-50 opacity-0" : "scale-100 opacity-100") }), /* @__PURE__ */ jsx(Check, { className: cn("absolute inset-0 h-3.5 w-3.5 text-an-foreground-muted transition-[opacity,transform] duration-150 ease-out", copied ? "scale-100 opacity-100" : "scale-50 opacity-0") })]
838
+ })
792
839
  });
793
- useEffect(() => {
794
- if (!open) return;
795
- const dialog = dialogRef.current;
796
- if (!dialog) return;
797
- if (!dialog.open) dialog.showModal();
798
- const handler = (event) => onLightDismiss(event);
799
- dialog.addEventListener("click", handler);
800
- return () => {
801
- dialog.removeEventListener("click", handler);
802
- if (dialog.open) dialog.close();
803
- };
804
- }, [open]);
805
- if (typeof document === "undefined") return null;
806
- if (!open) return null;
807
- const currentImage = images[currentIndex] ?? images[0];
808
- if (!currentImage?.url) return null;
809
- return createPortal(/* @__PURE__ */ jsxs("dialog", {
810
- ref: dialogRef,
811
- "aria-label": "Image viewer",
812
- onClose: () => onClose(),
813
- className: "fixed inset-0 z-50 m-0 flex h-full max-h-none w-full max-w-none items-center justify-center border-0 bg-black/90 p-0 backdrop:bg-transparent",
840
+ }
841
+ function MessageToolbar({ text, timestamp, heightClass, hoverClass, isVisible, alignClass, onCopied }) {
842
+ return /* @__PURE__ */ jsxs("div", {
843
+ className: cn("pointer-events-none flex items-center gap-1 pt-1 text-xs text-an-foreground-muted/70 opacity-0 transition-opacity duration-100", heightClass, alignClass, hoverClass, isVisible && "pointer-events-auto opacity-100"),
844
+ onMouseDown: (event) => event.stopPropagation(),
845
+ onPointerDown: (event) => event.stopPropagation(),
846
+ children: [timestamp && /* @__PURE__ */ jsx("span", { children: timestamp }), text && /* @__PURE__ */ jsx(CopyButton, {
847
+ text,
848
+ onCopied
849
+ })]
850
+ });
851
+ }
852
+ function formatPayload(value) {
853
+ if (typeof value === "string") return value;
854
+ if (value === void 0) return "";
855
+ try {
856
+ return JSON.stringify(value, null, 2) ?? "";
857
+ } catch {
858
+ return String(value);
859
+ }
860
+ }
861
+ function PayloadPart({ part }) {
862
+ const payload = formatPayload(part.payload);
863
+ return /* @__PURE__ */ jsxs("section", {
864
+ "data-payload-kind": part.kind,
865
+ className: "w-full min-w-0 overflow-hidden rounded-xl border border-border/60 bg-muted/40 text-sm",
814
866
  children: [
815
- /* @__PURE__ */ jsx("button", {
816
- type: "button",
817
- onClick: onClose,
818
- "aria-label": "Close fullscreen (Esc)",
819
- className: "absolute top-4 right-4 z-10 inline-flex size-9 items-center justify-center rounded-full bg-black/50 text-white transition-colors hover:bg-black/70",
820
- children: /* @__PURE__ */ jsx(X, { className: "size-5" })
821
- }),
822
- hasMultipleImages && /* @__PURE__ */ jsx("button", {
823
- type: "button",
824
- onClick: goToPrevious,
825
- "aria-label": "Previous image (\u2190)",
826
- className: "absolute top-1/2 left-4 z-10 inline-flex size-10 -translate-y-1/2 items-center justify-center rounded-full bg-black/50 text-white transition-colors hover:bg-black/70",
827
- children: /* @__PURE__ */ jsx(ChevronLeft, { className: "size-6" })
828
- }),
829
- /* @__PURE__ */ jsx("img", {
830
- src: currentImage.url,
831
- alt: currentImage.filename ?? "Image preview",
832
- className: "max-h-[85vh] max-w-[90vw] object-contain select-none",
833
- onClick: (event) => event.stopPropagation(),
834
- draggable: false
835
- }),
836
- hasMultipleImages && /* @__PURE__ */ jsx("button", {
837
- type: "button",
838
- onClick: goToNext,
839
- "aria-label": "Next image (\u2192)",
840
- className: "absolute top-1/2 right-4 z-10 inline-flex size-10 -translate-y-1/2 items-center justify-center rounded-full bg-black/50 text-white transition-colors hover:bg-black/70",
841
- children: /* @__PURE__ */ jsx(ChevronRight, { className: "size-6" })
867
+ /* @__PURE__ */ jsx("div", {
868
+ className: "border-b border-border/50 px-3 py-2 text-xs font-medium text-muted-foreground",
869
+ children: part.title
842
870
  }),
843
- hasMultipleImages && /* @__PURE__ */ jsxs("div", {
844
- className: "absolute bottom-6 left-1/2 flex -translate-x-1/2 flex-col items-center gap-3",
845
- children: [/* @__PURE__ */ jsx("div", {
846
- className: "flex gap-2",
847
- children: images.map((image, idx) => /* @__PURE__ */ jsx("button", {
848
- type: "button",
849
- onClick: (event) => {
850
- event.stopPropagation();
851
- setCurrentIndex(idx);
852
- },
853
- "aria-label": `Go to image ${idx + 1}`,
854
- className: cn("size-2 rounded-full transition-all", idx === currentIndex ? "scale-125 bg-white" : "bg-white/40 hover:bg-white/60")
855
- }, image.url))
856
- }), /* @__PURE__ */ jsxs("span", {
857
- className: "text-sm text-white/70",
858
- children: [
859
- currentIndex + 1,
860
- " / ",
861
- images.length
862
- ]
871
+ part.text ? /* @__PURE__ */ jsx("pre", {
872
+ className: "m-0 whitespace-pre-wrap break-words p-3 font-mono text-xs leading-5 text-foreground/80",
873
+ children: part.text
874
+ }) : null,
875
+ payload ? /* @__PURE__ */ jsxs("details", {
876
+ open: true,
877
+ className: "border-t border-border/50 px-3 py-2",
878
+ children: [/* @__PURE__ */ jsx("summary", {
879
+ className: "cursor-pointer text-xs font-medium text-muted-foreground",
880
+ children: "Payload"
881
+ }), /* @__PURE__ */ jsx("pre", {
882
+ className: "m-0 mt-2 max-w-full whitespace-pre-wrap break-words font-mono text-xs leading-5 text-foreground/70",
883
+ children: payload
863
884
  })]
864
- })
885
+ }) : null
865
886
  ]
866
- }), document.body);
887
+ });
867
888
  }
868
- function formatFileSize(bytes) {
869
- if (bytes < 1024) return `${bytes} B`;
870
- if (bytes < 1048576) return `${(bytes / 1024).toFixed(1)} KB`;
871
- return `${(bytes / 1048576).toFixed(1)} MB`;
872
- }
873
- function getFileIconName(filename, isImage) {
874
- if (isImage) return "image";
875
- const ext = filename.split(".").pop()?.toLowerCase();
876
- if ([
877
- "js",
878
- "ts",
879
- "jsx",
880
- "tsx",
881
- "py",
882
- "rb",
883
- "go",
884
- "rs",
885
- "java",
886
- "kt",
887
- "swift",
888
- "c",
889
- "cpp",
890
- "h",
891
- "hpp",
892
- "cs",
893
- "php"
894
- ].includes(ext || "")) return "code";
895
- if ([
896
- "json",
897
- "yaml",
898
- "yml",
899
- "xml"
900
- ].includes(ext || "")) return "data";
901
- return "text";
902
- }
903
- function renderFileIcon(iconName) {
904
- switch (iconName) {
905
- case "image":
906
- return /* @__PURE__ */ jsx(Image, { className: "size-4 text-muted-foreground" });
907
- case "code":
908
- return /* @__PURE__ */ jsx(FileCode, { className: "size-4 text-muted-foreground" });
909
- case "data":
910
- return /* @__PURE__ */ jsx(FileBraces, { className: "size-4 text-muted-foreground" });
911
- default:
912
- return /* @__PURE__ */ jsx(FileText, { className: "size-4 text-muted-foreground" });
913
- }
914
- }
915
- function FileAttachment({ id, filename, size, isImage, url, onRemove, className, display = "chip", enableImagePreview = true }) {
916
- const [isHovered, setIsHovered] = useState(false);
917
- const [isLightboxOpen, setIsLightboxOpen] = useState(false);
918
- const iconName = getFileIconName(filename, isImage);
919
- const isImageOnly = display === "image-only" && isImage && !!url;
920
- const canPreview = Boolean(enableImagePreview && isImage && url);
921
- const openLightbox = (event) => {
922
- event?.stopPropagation();
923
- setIsLightboxOpen(true);
924
- };
925
- return /* @__PURE__ */ jsxs("div", {
926
- className: cn("relative rounded-[calc(var(--an-input-border-radius)-var(--an-context-padding))] bg-muted/50", isImageOnly ? "flex size-10 items-center justify-center" : "flex max-w-[200px] min-w-[120px] items-center gap-2 py-1 pr-2 pl-1", className),
927
- onMouseEnter: () => setIsHovered(true),
928
- onMouseLeave: () => setIsHovered(false),
929
- children: [
930
- isImageOnly ? /* @__PURE__ */ jsx("div", {
931
- className: cn("size-8 shrink-0 overflow-hidden rounded-[calc(var(--an-input-border-radius)-var(--an-context-padding)-2px)]", canPreview && "cursor-pointer"),
932
- onClick: canPreview ? openLightbox : void 0,
933
- role: canPreview ? "button" : void 0,
934
- tabIndex: canPreview ? 0 : void 0,
935
- "aria-label": canPreview ? `Preview ${filename}` : void 0,
936
- onKeyDown: canPreview ? (event) => {
937
- if (event.key === "Enter" || event.key === " ") {
938
- event.preventDefault();
939
- openLightbox();
940
- }
941
- } : void 0,
942
- children: /* @__PURE__ */ jsx("img", {
943
- src: url,
944
- alt: filename,
945
- className: "h-full w-full object-cover"
946
- })
947
- }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [isImage && url ? /* @__PURE__ */ jsx("div", {
948
- className: cn("w-8 shrink-0 self-stretch overflow-hidden rounded-[calc(var(--an-input-border-radius)-var(--an-context-padding)-2px)]", canPreview && "cursor-pointer"),
949
- onClick: canPreview ? openLightbox : void 0,
950
- role: canPreview ? "button" : void 0,
951
- tabIndex: canPreview ? 0 : void 0,
952
- "aria-label": canPreview ? `Preview ${filename}` : void 0,
953
- onKeyDown: canPreview ? (event) => {
954
- if (event.key === "Enter" || event.key === " ") {
955
- event.preventDefault();
956
- openLightbox();
957
- }
958
- } : void 0,
959
- children: /* @__PURE__ */ jsx("img", {
960
- src: url,
961
- alt: filename,
962
- className: "aspect-square h-full w-full object-cover"
963
- })
964
- }) : /* @__PURE__ */ jsx("div", {
965
- className: "flex w-8 shrink-0 items-center justify-center self-stretch rounded-[calc(var(--an-input-border-radius)-var(--an-context-padding)-2px)] bg-muted",
966
- children: renderFileIcon(iconName)
967
- }), /* @__PURE__ */ jsxs("div", {
968
- className: "flex min-w-0 flex-col",
969
- children: [/* @__PURE__ */ jsx("span", {
970
- className: "truncate text-sm font-medium text-foreground",
971
- title: filename,
972
- children: filename
973
- }), size !== void 0 && /* @__PURE__ */ jsx("span", {
974
- className: "text-[0.625rem] text-muted-foreground",
975
- children: formatFileSize(size)
976
- })]
977
- })] }),
978
- onRemove && /* @__PURE__ */ jsx("button", {
979
- onClick: (e) => {
980
- e.stopPropagation();
981
- onRemove();
982
- },
983
- "aria-label": `Remove ${filename}`,
984
- className: `absolute -top-1.5 -right-1.5 z-10 flex size-4 items-center justify-center rounded-full border border-border bg-background text-muted-foreground transition-[opacity,transform] duration-150 ease-out hover:text-foreground active:scale-[0.97] ${isHovered ? "opacity-100" : "opacity-0"}`,
985
- type: "button",
986
- children: /* @__PURE__ */ jsx(X, { className: "size-3" })
987
- }),
988
- canPreview && url && /* @__PURE__ */ jsx(ImageLightbox, {
989
- open: isLightboxOpen,
990
- onClose: () => setIsLightboxOpen(false),
991
- images: [{
992
- id,
993
- url,
994
- filename
995
- }]
996
- })
997
- ]
998
- });
999
- }
1000
- function isRecord$2(value) {
1001
- return typeof value === "object" && value !== null;
889
+ function isRecord$2(value) {
890
+ return typeof value === "object" && value !== null;
1002
891
  }
1003
892
  function isTextPart(part) {
1004
893
  return isRecord$2(part) && part.type === "text" && typeof part.text === "string";
@@ -1022,170 +911,19 @@ function getTextFromParts(parts, joiner) {
1022
911
  function isRecord$1(value) {
1023
912
  return typeof value === "object" && value !== null;
1024
913
  }
1025
- function getImageUrlFromPart(part) {
1026
- if (!isRecord$1(part)) return null;
1027
- const type = part.type;
1028
- if (typeof type !== "string") return null;
1029
- if (type === "image") {
1030
- const imagePart = part;
1031
- return imagePart.url ?? imagePart.image ?? null;
1032
- }
1033
- if (type === "data-image") return part.data?.url ?? null;
1034
- if (type === "file") {
1035
- const filePart = part;
1036
- if (filePart.mimeType?.startsWith("image/")) {
1037
- if (filePart.url) return filePart.url;
1038
- if (filePart.data) return `data:${filePart.mimeType};base64,${filePart.data}`;
1039
- }
1040
- }
1041
- return null;
1042
- }
1043
- function getFileFromPart(part) {
1044
- if (!isRecord$1(part)) return null;
1045
- if (part.type !== "file") return null;
1046
- const filePart = part;
1047
- const filename = filePart.filename || filePart.name || filePart.fileName || "Attachment";
1048
- if (filePart.mimeType?.startsWith("image/") ?? false) return null;
1049
- return {
1050
- filename,
1051
- size: filePart.size
1052
- };
1053
- }
1054
- var UserMessage = memo(function UserMessage2({ message, className, enableImagePreview = true }) {
1055
- const [lightboxIndex, setLightboxIndex] = useState(null);
1056
- const text = (message.parts?.filter(isTextPart) ?? []).map((p) => p.text).join("");
1057
- const images = [];
1058
- const files = [];
1059
- for (const part of message.parts ?? []) {
1060
- const imageUrl = getImageUrlFromPart(part);
1061
- if (imageUrl) images.push(imageUrl);
1062
- const file = getFileFromPart(part);
1063
- if (file) files.push(file);
1064
- }
1065
- if (isRecord$1(message) && Array.isArray(message.experimental_attachments)) {
1066
- for (const att of message.experimental_attachments) if (att.contentType?.startsWith("image/") && att.url) images.push(att.url);
1067
- }
1068
- if (!text && images.length === 0 && files.length === 0) return null;
1069
- const lightboxImages = images.map((url, i) => ({
1070
- id: `${message.id}-img-${i}`,
1071
- url,
1072
- filename: `image-${i + 1}`
1073
- }));
1074
- return /* @__PURE__ */ jsxs("div", {
1075
- className: cn("flex flex-col items-end gap-1", className),
1076
- children: [
1077
- images.length > 0 && images.map((url, i) => /* @__PURE__ */ jsx("div", {
1078
- className: cn("max-w-[200px] rounded-an-message bg-an-foreground/4 p-1.5", enableImagePreview && "cursor-pointer"),
1079
- onClick: enableImagePreview ? () => setLightboxIndex(i) : void 0,
1080
- role: enableImagePreview ? "button" : void 0,
1081
- tabIndex: enableImagePreview ? 0 : void 0,
1082
- onKeyDown: enableImagePreview ? (event) => {
1083
- if (event.key === "Enter" || event.key === " ") {
1084
- event.preventDefault();
1085
- setLightboxIndex(i);
1086
- }
1087
- } : void 0,
1088
- children: /* @__PURE__ */ jsx("img", {
1089
- src: url,
1090
- alt: "attachment",
1091
- className: "block max-h-[120px] max-w-[184px] rounded-an-message-inner object-cover"
1092
- })
1093
- }, url)),
1094
- enableImagePreview && lightboxImages.length > 0 && /* @__PURE__ */ jsx(ImageLightbox, {
1095
- open: lightboxIndex !== null,
1096
- onClose: () => setLightboxIndex(null),
1097
- images: lightboxImages,
1098
- initialIndex: lightboxIndex ?? 0
1099
- }),
1100
- files.length > 0 && /* @__PURE__ */ jsx("div", {
1101
- className: "flex flex-col items-end gap-2",
1102
- children: files.map((file, i) => /* @__PURE__ */ jsx(FileAttachment, {
1103
- id: `${file.filename}-${i}`,
1104
- filename: file.filename,
1105
- size: file.size
1106
- }, `${file.filename}-${file.size}`))
1107
- }),
1108
- text && /* @__PURE__ */ jsx("div", {
1109
- className: "ms-[70px] max-w-[calc(95%-40px)]",
1110
- children: /* @__PURE__ */ jsx("div", {
1111
- className: "rounded-an-message bg-an-user-message-bg px-3.5 py-1.5 text-sm text-an-user-message-text transition-colors",
1112
- children: /* @__PURE__ */ jsx("p", {
1113
- className: "leading-5 wrap-break-word whitespace-pre-wrap",
1114
- children: text
1115
- })
1116
- })
1117
- })
1118
- ]
1119
- });
1120
- });
1121
- var ErrorMessage = memo(function ErrorMessage2({ title = "Something went wrong", message, className }) {
1122
- return /* @__PURE__ */ jsx("div", {
1123
- className: cn("flex justify-start", className),
1124
- children: /* @__PURE__ */ jsxs("div", {
1125
- className: "rounded-[8px] border border-red-500/30 bg-red-500/10 px-4 py-2.5 text-sm text-an-foreground",
1126
- children: [/* @__PURE__ */ jsx("div", {
1127
- className: "font-medium text-an-foreground",
1128
- children: title
1129
- }), /* @__PURE__ */ jsx("div", {
1130
- className: "mt-0.5 text-an-foreground-muted",
1131
- children: message
1132
- })]
1133
- })
1134
- });
1135
- });
1136
- function formatPayload(value) {
1137
- if (typeof value === "string") return value;
1138
- if (value === void 0) return "";
914
+ function parseStructuredJson(value) {
915
+ if (typeof value !== "string") return value;
916
+ const trimmed = value.trim();
917
+ if (!trimmed) return value;
1139
918
  try {
1140
- return JSON.stringify(value, null, 2) ?? "";
919
+ const parsed = JSON.parse(trimmed);
920
+ return isRecord$1(parsed) || Array.isArray(parsed) ? parsed : value;
1141
921
  } catch {
1142
- return String(value);
1143
- }
1144
- }
1145
- function PayloadPart({ part }) {
1146
- const payload = formatPayload(part.payload);
1147
- return /* @__PURE__ */ jsxs("section", {
1148
- "data-payload-kind": part.kind,
1149
- className: "w-full min-w-0 overflow-hidden rounded-xl border border-border/60 bg-muted/40 text-sm",
1150
- children: [
1151
- /* @__PURE__ */ jsx("div", {
1152
- className: "border-b border-border/50 px-3 py-2 text-xs font-medium text-muted-foreground",
1153
- children: part.title
1154
- }),
1155
- part.text ? /* @__PURE__ */ jsx("pre", {
1156
- className: "m-0 whitespace-pre-wrap break-words p-3 font-mono text-xs leading-5 text-foreground/80",
1157
- children: part.text
1158
- }) : null,
1159
- payload ? /* @__PURE__ */ jsxs("details", {
1160
- open: true,
1161
- className: "border-t border-border/50 px-3 py-2",
1162
- children: [/* @__PURE__ */ jsx("summary", {
1163
- className: "cursor-pointer text-xs font-medium text-muted-foreground",
1164
- children: "Payload"
1165
- }), /* @__PURE__ */ jsx("pre", {
1166
- className: "m-0 mt-2 max-w-full whitespace-pre-wrap break-words font-mono text-xs leading-5 text-foreground/70",
1167
- children: payload
1168
- })]
1169
- }) : null
1170
- ]
1171
- });
1172
- }
1173
- function isRecord(value) {
1174
- return typeof value === "object" && value !== null;
1175
- }
1176
- function parseStructuredJson(value) {
1177
- if (typeof value !== "string") return value;
1178
- const trimmed = value.trim();
1179
- if (!trimmed) return value;
1180
- try {
1181
- const parsed = JSON.parse(trimmed);
1182
- return isRecord(parsed) || Array.isArray(parsed) ? parsed : value;
1183
- } catch {
1184
- return value;
922
+ return value;
1185
923
  }
1186
924
  }
1187
925
  function normalizeToolPart(part) {
1188
- if (!isRecord(part)) return part;
926
+ if (!isRecord$1(part)) return part;
1189
927
  if (typeof part.type !== "string" || !part.type.startsWith("tool-")) return part;
1190
928
  const normalizedInput = parseStructuredJson(part.input);
1191
929
  const normalizedOutput = parseStructuredJson(part.output);
@@ -1209,70 +947,6 @@ function normalizeAssistantToolParts(parts) {
1209
947
  });
1210
948
  return changed ? normalizedParts : parts;
1211
949
  }
1212
- var timeFormatter = new Intl.DateTimeFormat("en-US", {
1213
- hour: "numeric",
1214
- minute: "2-digit",
1215
- hour12: true
1216
- });
1217
- var dateFormatter = new Intl.DateTimeFormat("en-US", {
1218
- month: "short",
1219
- day: "numeric"
1220
- });
1221
- function formatTimestamp(date) {
1222
- const now = /* @__PURE__ */ new Date();
1223
- if (date.getFullYear() === now.getFullYear() && date.getMonth() === now.getMonth() && date.getDate() === now.getDate()) return timeFormatter.format(date);
1224
- return dateFormatter.format(date);
1225
- }
1226
- function CopyButton({ text, onCopied }) {
1227
- const [copied, setCopied] = useState(false);
1228
- const copiedTimerRef = useRef(null);
1229
- useEffect(() => {
1230
- return () => {
1231
- if (copiedTimerRef.current) window.clearTimeout(copiedTimerRef.current);
1232
- };
1233
- }, []);
1234
- const handleCopy = async () => {
1235
- try {
1236
- await navigator.clipboard.writeText(text);
1237
- setCopied(true);
1238
- if (copiedTimerRef.current) window.clearTimeout(copiedTimerRef.current);
1239
- copiedTimerRef.current = window.setTimeout(() => {
1240
- setCopied(false);
1241
- copiedTimerRef.current = null;
1242
- }, 2e3);
1243
- notify.success("Copied to clipboard");
1244
- onCopied?.();
1245
- } catch {
1246
- notify.error("Failed to copy to clipboard");
1247
- }
1248
- };
1249
- return /* @__PURE__ */ jsx("button", {
1250
- type: "button",
1251
- tabIndex: -1,
1252
- "aria-label": copied ? "Copied" : "Copy message",
1253
- onClick: handleCopy,
1254
- onPointerDown: (event) => {
1255
- event.stopPropagation();
1256
- },
1257
- onMouseDown: (event) => event.stopPropagation(),
1258
- className: cn("flex size-6 items-center justify-center rounded-md transition-[background-color,opacity,transform] duration-150 ease-out active:scale-[0.97]", "bg-transparent opacity-50 hover:bg-an-foreground/10 hover:opacity-100"),
1259
- children: /* @__PURE__ */ jsxs("div", {
1260
- className: "relative size-3.5",
1261
- children: [/* @__PURE__ */ jsx(Copy, { className: cn("absolute inset-0 h-3.5 w-3.5 text-an-foreground-muted transition-[opacity,transform] duration-150 ease-out", copied ? "scale-50 opacity-0" : "scale-100 opacity-100") }), /* @__PURE__ */ jsx(Check, { className: cn("absolute inset-0 h-3.5 w-3.5 text-an-foreground-muted transition-[opacity,transform] duration-150 ease-out", copied ? "scale-100 opacity-100" : "scale-50 opacity-0") })]
1262
- })
1263
- });
1264
- }
1265
- function MessageToolbar({ text, timestamp, heightClass, hoverClass, isVisible, alignClass, onCopied }) {
1266
- return /* @__PURE__ */ jsxs("div", {
1267
- className: cn("pointer-events-none flex items-center gap-1 pt-1 text-xs text-an-foreground-muted/70 opacity-0 transition-opacity duration-100", heightClass, alignClass, hoverClass, isVisible && "pointer-events-auto opacity-100"),
1268
- onMouseDown: (event) => event.stopPropagation(),
1269
- onPointerDown: (event) => event.stopPropagation(),
1270
- children: [timestamp && /* @__PURE__ */ jsx("span", { children: timestamp }), text && /* @__PURE__ */ jsx(CopyButton, {
1271
- text,
1272
- onCopied
1273
- })]
1274
- });
1275
- }
1276
950
  memo(function UserTurn(props) {
1277
951
  const { message, UserMessageComponent, userMessageClassName, onCopied, display } = props;
1278
952
  const { enableImagePreview, showCopyToolbar, isMounted, isCopyVisible } = display;
@@ -1328,166 +1002,492 @@ memo(function AssistantTurn(props) {
1328
1002
  onOpenArtifact
1329
1003
  }, msg.id);
1330
1004
  })
1331
- }), (showToolbar || isCopyVisible) && /* @__PURE__ */ jsx(MessageToolbar, {
1332
- text: toolbarText,
1333
- heightClass: "h-[48px] flex items-start w-full",
1334
- hoverClass: "group-hover/assistant-turn:opacity-100 group-hover/assistant-turn:pointer-events-auto",
1335
- isVisible: isCopyVisible,
1336
- alignClass: "justify-start",
1337
- onCopied: () => onCopied(copyKey)
1338
- })]
1005
+ }), (showToolbar || isCopyVisible) && /* @__PURE__ */ jsx(MessageToolbar, {
1006
+ text: toolbarText,
1007
+ heightClass: "h-[48px] flex items-start w-full",
1008
+ hoverClass: "group-hover/assistant-turn:opacity-100 group-hover/assistant-turn:pointer-events-auto",
1009
+ isVisible: isCopyVisible,
1010
+ alignClass: "justify-start",
1011
+ onCopied: () => onCopied(copyKey)
1012
+ })]
1013
+ });
1014
+ });
1015
+ var TOOL_CALL_KEY_PREFIX = "tool-call:";
1016
+ var UNKEYED_TOOL_KEY_PREFIX = "unkeyed-tool:";
1017
+ function getAssistantToolElementKey(messageId, part, occurrence) {
1018
+ if (part.toolCallId) return `${TOOL_CALL_KEY_PREFIX}${part.toolCallId}`;
1019
+ return `${UNKEYED_TOOL_KEY_PREFIX}${messageId}:${part.type}:${occurrence}`;
1020
+ }
1021
+ function buildAssistantElements(parts, opts) {
1022
+ const { messageId, isLast, isStreaming, suppressQuestionTool, suppressTextWhenPlanWrite = false, ToolRendererComponent, TextRendererComponent, toolRenderers, onOpenUIAction, onOpenUIArtifactReady, onOpenArtifact } = opts;
1023
+ const elems = [];
1024
+ const textChunks = [];
1025
+ const hasPlanWritePresentation = suppressTextWhenPlanWrite && parts.some((part) => isV5ToolPart(part) && part.type === "tool-PlanWrite");
1026
+ const taskPartIds = new Set(parts.filter((p) => isV5ToolPart(p) && (p.type === "tool-Task" || p.type === "tool-Agent") && typeof p.toolCallId === "string").map((p) => p.toolCallId));
1027
+ const nestedToolsMap = /* @__PURE__ */ new Map();
1028
+ const nestedToolIds = /* @__PURE__ */ new Set();
1029
+ const unkeyedToolOccurrences = /* @__PURE__ */ new Map();
1030
+ for (const part of parts) {
1031
+ if (!isV5ToolPart(part)) continue;
1032
+ if (part.type === "tool-TaskOutput") continue;
1033
+ if (!part.toolCallId || !part.toolCallId.includes(":")) continue;
1034
+ const parentId = part.toolCallId.split(":")[0];
1035
+ if (!taskPartIds.has(parentId)) continue;
1036
+ if (!nestedToolsMap.has(parentId)) nestedToolsMap.set(parentId, []);
1037
+ nestedToolsMap.get(parentId).push(part);
1038
+ nestedToolIds.add(part.toolCallId);
1039
+ }
1040
+ let i = 0;
1041
+ while (i < parts.length) {
1042
+ const part = parts[i];
1043
+ if (isV5ToolPart(part) && part.type === "tool-TaskOutput") {
1044
+ i++;
1045
+ continue;
1046
+ }
1047
+ if (isTextPart(part)) {
1048
+ const text2 = part.text;
1049
+ if (text2 && !hasPlanWritePresentation) textChunks.push(text2);
1050
+ i++;
1051
+ continue;
1052
+ }
1053
+ if (isErrorPart(part)) {
1054
+ elems.push(/* @__PURE__ */ jsx(ErrorMessage, {
1055
+ title: part.title,
1056
+ message: part.message
1057
+ }, `${messageId}-error-${part.title}-${part.message}`));
1058
+ i++;
1059
+ continue;
1060
+ }
1061
+ if (isPayloadPart(part)) {
1062
+ elems.push(/* @__PURE__ */ jsx(PayloadPart, { part }, `${messageId}-payload-${i}`));
1063
+ i++;
1064
+ continue;
1065
+ }
1066
+ if (isV5ToolPart(part)) {
1067
+ if (suppressQuestionTool && part.type === "tool-Question") {
1068
+ i++;
1069
+ continue;
1070
+ }
1071
+ if (part.toolCallId && nestedToolIds.has(part.toolCallId)) {
1072
+ i++;
1073
+ continue;
1074
+ }
1075
+ const chatStreamingStatus = isLast && isStreaming ? "streaming" : void 0;
1076
+ const toolCallId = part.toolCallId;
1077
+ const nestedTools = (part.type === "tool-Task" || part.type === "tool-Agent") && toolCallId ? nestedToolsMap.get(toolCallId) || [] : void 0;
1078
+ const occurrence = part.toolCallId ? 0 : unkeyedToolOccurrences.get(part.type) ?? 0;
1079
+ if (!part.toolCallId) unkeyedToolOccurrences.set(part.type, occurrence + 1);
1080
+ elems.push(/* @__PURE__ */ jsx(ToolRendererComponent, {
1081
+ part,
1082
+ nestedTools,
1083
+ chatStatus: chatStreamingStatus,
1084
+ toolRenderers
1085
+ }, getAssistantToolElementKey(messageId, part, occurrence)));
1086
+ i++;
1087
+ continue;
1088
+ }
1089
+ i++;
1090
+ }
1091
+ const text = textChunks.join("\n\n");
1092
+ if (text) elems.push(/* @__PURE__ */ jsx("div", {
1093
+ className: "group/assistant-text text-[0.875rem]",
1094
+ children: /* @__PURE__ */ jsx(TextRendererComponent, {
1095
+ content: text,
1096
+ isStreaming,
1097
+ messageId,
1098
+ onOpenUIAction,
1099
+ onOpenUIArtifactReady,
1100
+ onOpenArtifact,
1101
+ className: "leading-relaxed [&_p]:leading-relaxed"
1102
+ })
1103
+ }, `${messageId}-text-final`));
1104
+ return elems;
1105
+ }
1106
+ function AssistantParts({ msg, isLast, isStreaming, suppressQuestionTool, ToolRendererComponent, TextRendererComponent, toolRenderers, onOpenUIAction, onOpenUIArtifactReady, onOpenArtifact }) {
1107
+ const parts = useMemo(() => normalizeAssistantToolParts(msg.parts ?? []), [msg.parts]);
1108
+ const elements = useMemo(() => buildAssistantElements(parts, {
1109
+ messageId: msg.id,
1110
+ isLast,
1111
+ isStreaming,
1112
+ suppressQuestionTool,
1113
+ ToolRendererComponent,
1114
+ TextRendererComponent,
1115
+ toolRenderers,
1116
+ onOpenUIAction,
1117
+ onOpenUIArtifactReady,
1118
+ onOpenArtifact
1119
+ }), [
1120
+ parts,
1121
+ msg.id,
1122
+ isLast,
1123
+ isStreaming,
1124
+ suppressQuestionTool,
1125
+ ToolRendererComponent,
1126
+ TextRendererComponent,
1127
+ toolRenderers,
1128
+ onOpenUIAction,
1129
+ onOpenUIArtifactReady,
1130
+ onOpenArtifact
1131
+ ]);
1132
+ if (elements.length > 1) return /* @__PURE__ */ jsx("div", {
1133
+ className: "group/assistant-turn flex flex-col gap-3",
1134
+ children: elements
1135
+ });
1136
+ return /* @__PURE__ */ jsx("div", {
1137
+ className: "group/assistant-turn",
1138
+ children: elements
1139
+ });
1140
+ }
1141
+ function ImageLightbox({ open, onClose, images, initialIndex = 0 }) {
1142
+ const [currentIndex, setCurrentIndex] = useState(initialIndex);
1143
+ const hasMultipleImages = images.length > 1;
1144
+ useEffect(() => {
1145
+ if (open) setCurrentIndex(initialIndex);
1146
+ }, [open, initialIndex]);
1147
+ const goToPrevious = useCallback((event) => {
1148
+ event?.stopPropagation();
1149
+ setCurrentIndex((prev) => prev > 0 ? prev - 1 : images.length - 1);
1150
+ }, [images.length]);
1151
+ const goToNext = useCallback((event) => {
1152
+ event?.stopPropagation();
1153
+ setCurrentIndex((prev) => prev < images.length - 1 ? prev + 1 : 0);
1154
+ }, [images.length]);
1155
+ const onLightboxKeyDown = useEffectEvent((event) => {
1156
+ switch (event.key) {
1157
+ case "ArrowLeft":
1158
+ event.preventDefault();
1159
+ if (hasMultipleImages) goToPrevious();
1160
+ break;
1161
+ case "ArrowRight":
1162
+ event.preventDefault();
1163
+ if (hasMultipleImages) goToNext();
1164
+ }
1165
+ });
1166
+ useEffect(() => {
1167
+ if (!open) return;
1168
+ const handler = (event) => onLightboxKeyDown(event);
1169
+ window.addEventListener("keydown", handler, true);
1170
+ return () => window.removeEventListener("keydown", handler, true);
1171
+ }, [open]);
1172
+ useEffect(() => {
1173
+ if (!open) return;
1174
+ const previousOverflow = document.body.style.overflow;
1175
+ document.body.style.overflow = "hidden";
1176
+ return () => {
1177
+ document.body.style.overflow = previousOverflow;
1178
+ };
1179
+ }, [open]);
1180
+ const dialogRef = useRef(null);
1181
+ const onLightDismiss = useEffectEvent((event) => {
1182
+ if (dialogRef.current && event.target === dialogRef.current) onClose();
1183
+ });
1184
+ useEffect(() => {
1185
+ if (!open) return;
1186
+ const dialog = dialogRef.current;
1187
+ if (!dialog) return;
1188
+ if (!dialog.open) dialog.showModal();
1189
+ const handler = (event) => onLightDismiss(event);
1190
+ dialog.addEventListener("click", handler);
1191
+ return () => {
1192
+ dialog.removeEventListener("click", handler);
1193
+ if (dialog.open) dialog.close();
1194
+ };
1195
+ }, [open]);
1196
+ if (typeof document === "undefined") return null;
1197
+ if (!open) return null;
1198
+ const currentImage = images[currentIndex] ?? images[0];
1199
+ if (!currentImage?.url) return null;
1200
+ return createPortal(/* @__PURE__ */ jsxs("dialog", {
1201
+ ref: dialogRef,
1202
+ "aria-label": "Image viewer",
1203
+ onClose: () => onClose(),
1204
+ className: "fixed inset-0 z-50 m-0 flex h-full max-h-none w-full max-w-none items-center justify-center border-0 bg-black/90 p-0 backdrop:bg-transparent",
1205
+ children: [
1206
+ /* @__PURE__ */ jsx("button", {
1207
+ type: "button",
1208
+ onClick: onClose,
1209
+ "aria-label": "Close fullscreen (Esc)",
1210
+ className: "absolute top-4 right-4 z-10 inline-flex size-9 items-center justify-center rounded-full bg-black/50 text-white transition-colors hover:bg-black/70",
1211
+ children: /* @__PURE__ */ jsx(X, { className: "size-5" })
1212
+ }),
1213
+ hasMultipleImages && /* @__PURE__ */ jsx("button", {
1214
+ type: "button",
1215
+ onClick: goToPrevious,
1216
+ "aria-label": "Previous image (\u2190)",
1217
+ className: "absolute top-1/2 left-4 z-10 inline-flex size-10 -translate-y-1/2 items-center justify-center rounded-full bg-black/50 text-white transition-colors hover:bg-black/70",
1218
+ children: /* @__PURE__ */ jsx(ChevronLeft, { className: "size-6" })
1219
+ }),
1220
+ /* @__PURE__ */ jsx("img", {
1221
+ src: currentImage.url,
1222
+ alt: currentImage.filename ?? "Image preview",
1223
+ className: "max-h-[85vh] max-w-[90vw] object-contain select-none",
1224
+ onClick: (event) => event.stopPropagation(),
1225
+ draggable: false
1226
+ }),
1227
+ hasMultipleImages && /* @__PURE__ */ jsx("button", {
1228
+ type: "button",
1229
+ onClick: goToNext,
1230
+ "aria-label": "Next image (\u2192)",
1231
+ className: "absolute top-1/2 right-4 z-10 inline-flex size-10 -translate-y-1/2 items-center justify-center rounded-full bg-black/50 text-white transition-colors hover:bg-black/70",
1232
+ children: /* @__PURE__ */ jsx(ChevronRight, { className: "size-6" })
1233
+ }),
1234
+ hasMultipleImages && /* @__PURE__ */ jsxs("div", {
1235
+ className: "absolute bottom-6 left-1/2 flex -translate-x-1/2 flex-col items-center gap-3",
1236
+ children: [/* @__PURE__ */ jsx("div", {
1237
+ className: "flex gap-2",
1238
+ children: images.map((image, idx) => /* @__PURE__ */ jsx("button", {
1239
+ type: "button",
1240
+ onClick: (event) => {
1241
+ event.stopPropagation();
1242
+ setCurrentIndex(idx);
1243
+ },
1244
+ "aria-label": `Go to image ${idx + 1}`,
1245
+ className: cn("size-2 rounded-full transition-all", idx === currentIndex ? "scale-125 bg-white" : "bg-white/40 hover:bg-white/60")
1246
+ }, image.url))
1247
+ }), /* @__PURE__ */ jsxs("span", {
1248
+ className: "text-sm text-white/70",
1249
+ children: [
1250
+ currentIndex + 1,
1251
+ " / ",
1252
+ images.length
1253
+ ]
1254
+ })]
1255
+ })
1256
+ ]
1257
+ }), document.body);
1258
+ }
1259
+ function formatFileSize(bytes) {
1260
+ if (bytes < 1024) return `${bytes} B`;
1261
+ if (bytes < 1048576) return `${(bytes / 1024).toFixed(1)} KB`;
1262
+ return `${(bytes / 1048576).toFixed(1)} MB`;
1263
+ }
1264
+ function getFileIconName(filename, isImage) {
1265
+ if (isImage) return "image";
1266
+ const ext = filename.split(".").pop()?.toLowerCase();
1267
+ if ([
1268
+ "js",
1269
+ "ts",
1270
+ "jsx",
1271
+ "tsx",
1272
+ "py",
1273
+ "rb",
1274
+ "go",
1275
+ "rs",
1276
+ "java",
1277
+ "kt",
1278
+ "swift",
1279
+ "c",
1280
+ "cpp",
1281
+ "h",
1282
+ "hpp",
1283
+ "cs",
1284
+ "php"
1285
+ ].includes(ext || "")) return "code";
1286
+ if ([
1287
+ "json",
1288
+ "yaml",
1289
+ "yml",
1290
+ "xml"
1291
+ ].includes(ext || "")) return "data";
1292
+ return "text";
1293
+ }
1294
+ function renderFileIcon(iconName) {
1295
+ switch (iconName) {
1296
+ case "image":
1297
+ return /* @__PURE__ */ jsx(Image, { className: "size-4 text-muted-foreground" });
1298
+ case "code":
1299
+ return /* @__PURE__ */ jsx(FileCode, { className: "size-4 text-muted-foreground" });
1300
+ case "data":
1301
+ return /* @__PURE__ */ jsx(FileBraces, { className: "size-4 text-muted-foreground" });
1302
+ default:
1303
+ return /* @__PURE__ */ jsx(FileText, { className: "size-4 text-muted-foreground" });
1304
+ }
1305
+ }
1306
+ function FileAttachment({ id, filename, size, isImage, url, onRemove, className, display = "chip", enableImagePreview = true }) {
1307
+ const [isHovered, setIsHovered] = useState(false);
1308
+ const [isLightboxOpen, setIsLightboxOpen] = useState(false);
1309
+ const iconName = getFileIconName(filename, isImage);
1310
+ const isImageOnly = display === "image-only" && isImage && !!url;
1311
+ const canPreview = Boolean(enableImagePreview && isImage && url);
1312
+ const openLightbox = (event) => {
1313
+ event?.stopPropagation();
1314
+ setIsLightboxOpen(true);
1315
+ };
1316
+ return /* @__PURE__ */ jsxs("div", {
1317
+ className: cn("relative rounded-[calc(var(--an-input-border-radius)-var(--an-context-padding))] bg-muted/50", isImageOnly ? "flex size-10 items-center justify-center" : "flex max-w-[200px] min-w-[120px] items-center gap-2 py-1 pr-2 pl-1", className),
1318
+ onMouseEnter: () => setIsHovered(true),
1319
+ onMouseLeave: () => setIsHovered(false),
1320
+ children: [
1321
+ isImageOnly ? /* @__PURE__ */ jsx("div", {
1322
+ className: cn("size-8 shrink-0 overflow-hidden rounded-[calc(var(--an-input-border-radius)-var(--an-context-padding)-2px)]", canPreview && "cursor-pointer"),
1323
+ onClick: canPreview ? openLightbox : void 0,
1324
+ role: canPreview ? "button" : void 0,
1325
+ tabIndex: canPreview ? 0 : void 0,
1326
+ "aria-label": canPreview ? `Preview ${filename}` : void 0,
1327
+ onKeyDown: canPreview ? (event) => {
1328
+ if (event.key === "Enter" || event.key === " ") {
1329
+ event.preventDefault();
1330
+ openLightbox();
1331
+ }
1332
+ } : void 0,
1333
+ children: /* @__PURE__ */ jsx("img", {
1334
+ src: url,
1335
+ alt: filename,
1336
+ className: "h-full w-full object-cover"
1337
+ })
1338
+ }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [isImage && url ? /* @__PURE__ */ jsx("div", {
1339
+ className: cn("w-8 shrink-0 self-stretch overflow-hidden rounded-[calc(var(--an-input-border-radius)-var(--an-context-padding)-2px)]", canPreview && "cursor-pointer"),
1340
+ onClick: canPreview ? openLightbox : void 0,
1341
+ role: canPreview ? "button" : void 0,
1342
+ tabIndex: canPreview ? 0 : void 0,
1343
+ "aria-label": canPreview ? `Preview ${filename}` : void 0,
1344
+ onKeyDown: canPreview ? (event) => {
1345
+ if (event.key === "Enter" || event.key === " ") {
1346
+ event.preventDefault();
1347
+ openLightbox();
1348
+ }
1349
+ } : void 0,
1350
+ children: /* @__PURE__ */ jsx("img", {
1351
+ src: url,
1352
+ alt: filename,
1353
+ className: "aspect-square h-full w-full object-cover"
1354
+ })
1355
+ }) : /* @__PURE__ */ jsx("div", {
1356
+ className: "flex w-8 shrink-0 items-center justify-center self-stretch rounded-[calc(var(--an-input-border-radius)-var(--an-context-padding)-2px)] bg-muted",
1357
+ children: renderFileIcon(iconName)
1358
+ }), /* @__PURE__ */ jsxs("div", {
1359
+ className: "flex min-w-0 flex-col",
1360
+ children: [/* @__PURE__ */ jsx("span", {
1361
+ className: "truncate text-sm font-medium text-foreground",
1362
+ title: filename,
1363
+ children: filename
1364
+ }), size !== void 0 && /* @__PURE__ */ jsx("span", {
1365
+ className: "text-[0.625rem] text-muted-foreground",
1366
+ children: formatFileSize(size)
1367
+ })]
1368
+ })] }),
1369
+ onRemove && /* @__PURE__ */ jsx("button", {
1370
+ onClick: (e) => {
1371
+ e.stopPropagation();
1372
+ onRemove();
1373
+ },
1374
+ "aria-label": `Remove ${filename}`,
1375
+ className: `absolute -top-1.5 -right-1.5 z-10 flex size-4 items-center justify-center rounded-full border border-border bg-background text-muted-foreground transition-[opacity,transform] duration-150 ease-out hover:text-foreground active:scale-[0.97] ${isHovered ? "opacity-100" : "opacity-0"}`,
1376
+ type: "button",
1377
+ children: /* @__PURE__ */ jsx(X, { className: "size-3" })
1378
+ }),
1379
+ canPreview && url && /* @__PURE__ */ jsx(ImageLightbox, {
1380
+ open: isLightboxOpen,
1381
+ onClose: () => setIsLightboxOpen(false),
1382
+ images: [{
1383
+ id,
1384
+ url,
1385
+ filename
1386
+ }]
1387
+ })
1388
+ ]
1339
1389
  });
1340
- });
1341
- var TOOL_CALL_KEY_PREFIX = "tool-call:";
1342
- var UNKEYED_TOOL_KEY_PREFIX = "unkeyed-tool:";
1343
- function getAssistantToolElementKey(messageId, part, occurrence) {
1344
- if (part.toolCallId) return `${TOOL_CALL_KEY_PREFIX}${part.toolCallId}`;
1345
- return `${UNKEYED_TOOL_KEY_PREFIX}${messageId}:${part.type}:${occurrence}`;
1346
1390
  }
1347
- function buildAssistantElements(parts, opts) {
1348
- const { messageId, isLast, isStreaming, suppressQuestionTool, suppressTextWhenPlanWrite = false, ToolRendererComponent, TextRendererComponent, toolRenderers, onOpenUIAction, onOpenUIArtifactReady, onOpenArtifact } = opts;
1349
- const elems = [];
1350
- const textChunks = [];
1351
- const hasPlanWritePresentation = suppressTextWhenPlanWrite && parts.some((part) => isV5ToolPart(part) && part.type === "tool-PlanWrite");
1352
- const taskPartIds = new Set(parts.filter((p) => isV5ToolPart(p) && (p.type === "tool-Task" || p.type === "tool-Agent") && typeof p.toolCallId === "string").map((p) => p.toolCallId));
1353
- const nestedToolsMap = /* @__PURE__ */ new Map();
1354
- const nestedToolIds = /* @__PURE__ */ new Set();
1355
- const unkeyedToolOccurrences = /* @__PURE__ */ new Map();
1356
- for (const part of parts) {
1357
- if (!isV5ToolPart(part)) continue;
1358
- if (part.type === "tool-TaskOutput") continue;
1359
- if (!part.toolCallId || !part.toolCallId.includes(":")) continue;
1360
- const parentId = part.toolCallId.split(":")[0];
1361
- if (!taskPartIds.has(parentId)) continue;
1362
- if (!nestedToolsMap.has(parentId)) nestedToolsMap.set(parentId, []);
1363
- nestedToolsMap.get(parentId).push(part);
1364
- nestedToolIds.add(part.toolCallId);
1391
+ function isRecord(value) {
1392
+ return typeof value === "object" && value !== null;
1393
+ }
1394
+ function getImageUrlFromPart(part) {
1395
+ if (!isRecord(part)) return null;
1396
+ const type = part.type;
1397
+ if (typeof type !== "string") return null;
1398
+ if (type === "image") {
1399
+ const imagePart = part;
1400
+ return imagePart.url ?? imagePart.image ?? null;
1365
1401
  }
1366
- let i = 0;
1367
- while (i < parts.length) {
1368
- const part = parts[i];
1369
- if (isV5ToolPart(part) && part.type === "tool-TaskOutput") {
1370
- i++;
1371
- continue;
1372
- }
1373
- if (isTextPart(part)) {
1374
- const text2 = part.text;
1375
- if (text2 && !hasPlanWritePresentation) textChunks.push(text2);
1376
- i++;
1377
- continue;
1378
- }
1379
- if (isErrorPart(part)) {
1380
- elems.push(/* @__PURE__ */ jsx(ErrorMessage, {
1381
- title: part.title,
1382
- message: part.message
1383
- }, `${messageId}-error-${part.title}-${part.message}`));
1384
- i++;
1385
- continue;
1386
- }
1387
- if (isPayloadPart(part)) {
1388
- elems.push(/* @__PURE__ */ jsx(PayloadPart, { part }, `${messageId}-payload-${i}`));
1389
- i++;
1390
- continue;
1391
- }
1392
- if (isV5ToolPart(part)) {
1393
- if (suppressQuestionTool && part.type === "tool-Question") {
1394
- i++;
1395
- continue;
1396
- }
1397
- if (part.toolCallId && nestedToolIds.has(part.toolCallId)) {
1398
- i++;
1399
- continue;
1400
- }
1401
- const chatStreamingStatus = isLast && isStreaming ? "streaming" : void 0;
1402
- const toolCallId = part.toolCallId;
1403
- const nestedTools = (part.type === "tool-Task" || part.type === "tool-Agent") && toolCallId ? nestedToolsMap.get(toolCallId) || [] : void 0;
1404
- const occurrence = part.toolCallId ? 0 : unkeyedToolOccurrences.get(part.type) ?? 0;
1405
- if (!part.toolCallId) unkeyedToolOccurrences.set(part.type, occurrence + 1);
1406
- elems.push(/* @__PURE__ */ jsx(ToolRendererComponent, {
1407
- part,
1408
- nestedTools,
1409
- chatStatus: chatStreamingStatus,
1410
- toolRenderers
1411
- }, getAssistantToolElementKey(messageId, part, occurrence)));
1412
- i++;
1413
- continue;
1402
+ if (type === "data-image") return part.data?.url ?? null;
1403
+ if (type === "file") {
1404
+ const filePart = part;
1405
+ if (filePart.mimeType?.startsWith("image/")) {
1406
+ if (filePart.url) return filePart.url;
1407
+ if (filePart.data) return `data:${filePart.mimeType};base64,${filePart.data}`;
1414
1408
  }
1415
- i++;
1416
1409
  }
1417
- const text = textChunks.join("\n\n");
1418
- if (text) elems.push(/* @__PURE__ */ jsx("div", {
1419
- className: "group/assistant-text text-[0.875rem]",
1420
- children: /* @__PURE__ */ jsx(TextRendererComponent, {
1421
- content: text,
1422
- isStreaming,
1423
- messageId,
1424
- onOpenUIAction,
1425
- onOpenUIArtifactReady,
1426
- onOpenArtifact,
1427
- className: "leading-relaxed [&_p]:leading-relaxed"
1428
- })
1429
- }, `${messageId}-text-final`));
1430
- return elems;
1410
+ return null;
1431
1411
  }
1432
- function AssistantParts({ msg, isLast, isStreaming, suppressQuestionTool, ToolRendererComponent, TextRendererComponent, toolRenderers, onOpenUIAction, onOpenUIArtifactReady, onOpenArtifact }) {
1433
- const parts = useMemo(() => normalizeAssistantToolParts(msg.parts ?? []), [msg.parts]);
1434
- const elements = useMemo(() => buildAssistantElements(parts, {
1435
- messageId: msg.id,
1436
- isLast,
1437
- isStreaming,
1438
- suppressQuestionTool,
1439
- ToolRendererComponent,
1440
- TextRendererComponent,
1441
- toolRenderers,
1442
- onOpenUIAction,
1443
- onOpenUIArtifactReady,
1444
- onOpenArtifact
1445
- }), [
1446
- parts,
1447
- msg.id,
1448
- isLast,
1449
- isStreaming,
1450
- suppressQuestionTool,
1451
- ToolRendererComponent,
1452
- TextRendererComponent,
1453
- toolRenderers,
1454
- onOpenUIAction,
1455
- onOpenUIArtifactReady,
1456
- onOpenArtifact
1457
- ]);
1458
- if (elements.length > 1) return /* @__PURE__ */ jsx("div", {
1459
- className: "group/assistant-turn flex flex-col gap-3",
1460
- children: elements
1461
- });
1462
- return /* @__PURE__ */ jsx("div", {
1463
- className: "group/assistant-turn",
1464
- children: elements
1465
- });
1412
+ function getFileFromPart(part) {
1413
+ if (!isRecord(part)) return null;
1414
+ if (part.type !== "file") return null;
1415
+ const filePart = part;
1416
+ const filename = filePart.filename || filePart.name || filePart.fileName || "Attachment";
1417
+ if (filePart.mimeType?.startsWith("image/") ?? false) return null;
1418
+ return {
1419
+ filename,
1420
+ size: filePart.size
1421
+ };
1466
1422
  }
1467
- function groupMessages(messages) {
1468
- const turns = [];
1469
- let current;
1470
- for (const message of messages) {
1471
- if (message.role === "user") {
1472
- if (current) turns.push(current);
1473
- current = {
1474
- user: message,
1475
- assistants: []
1476
- };
1477
- continue;
1478
- }
1479
- if (message.role !== "assistant") continue;
1480
- if (!current || message.source === "local") {
1481
- if (current) turns.push(current);
1482
- current = { assistants: [message] };
1483
- continue;
1484
- }
1485
- current.assistants.push(message);
1423
+ var UserMessage = memo(function UserMessage2({ message, className, enableImagePreview = true }) {
1424
+ const [lightboxIndex, setLightboxIndex] = useState(null);
1425
+ const text = (message.parts?.filter(isTextPart) ?? []).map((p) => p.text).join("");
1426
+ const images = [];
1427
+ const files = [];
1428
+ for (const part of message.parts ?? []) {
1429
+ const imageUrl = getImageUrlFromPart(part);
1430
+ if (imageUrl) images.push(imageUrl);
1431
+ const file = getFileFromPart(part);
1432
+ if (file) files.push(file);
1486
1433
  }
1487
- if (current) turns.push(current);
1488
- return turns;
1489
- }
1490
- var LazyGenerativeTextRenderer = lazy(() => import("./openui-renderer-Cj7kfqyL-EKJ6JTTQ.js").then((n) => n.n).then(({ GenerativeTextRenderer }) => ({ default: GenerativeTextRenderer })));
1434
+ if (isRecord(message) && Array.isArray(message.experimental_attachments)) {
1435
+ for (const att of message.experimental_attachments) if (att.contentType?.startsWith("image/") && att.url) images.push(att.url);
1436
+ }
1437
+ if (!text && images.length === 0 && files.length === 0) return null;
1438
+ const lightboxImages = images.map((url, i) => ({
1439
+ id: `${message.id}-img-${i}`,
1440
+ url,
1441
+ filename: `image-${i + 1}`
1442
+ }));
1443
+ return /* @__PURE__ */ jsxs("div", {
1444
+ className: cn("flex flex-col items-end gap-1", className),
1445
+ children: [
1446
+ images.length > 0 && images.map((url, i) => /* @__PURE__ */ jsx("div", {
1447
+ className: cn("max-w-[200px] rounded-an-message bg-an-foreground/4 p-1.5", enableImagePreview && "cursor-pointer"),
1448
+ onClick: enableImagePreview ? () => setLightboxIndex(i) : void 0,
1449
+ role: enableImagePreview ? "button" : void 0,
1450
+ tabIndex: enableImagePreview ? 0 : void 0,
1451
+ onKeyDown: enableImagePreview ? (event) => {
1452
+ if (event.key === "Enter" || event.key === " ") {
1453
+ event.preventDefault();
1454
+ setLightboxIndex(i);
1455
+ }
1456
+ } : void 0,
1457
+ children: /* @__PURE__ */ jsx("img", {
1458
+ src: url,
1459
+ alt: "attachment",
1460
+ className: "block max-h-[120px] max-w-[184px] rounded-an-message-inner object-cover"
1461
+ })
1462
+ }, url)),
1463
+ enableImagePreview && lightboxImages.length > 0 && /* @__PURE__ */ jsx(ImageLightbox, {
1464
+ open: lightboxIndex !== null,
1465
+ onClose: () => setLightboxIndex(null),
1466
+ images: lightboxImages,
1467
+ initialIndex: lightboxIndex ?? 0
1468
+ }),
1469
+ files.length > 0 && /* @__PURE__ */ jsx("div", {
1470
+ className: "flex flex-col items-end gap-2",
1471
+ children: files.map((file, i) => /* @__PURE__ */ jsx(FileAttachment, {
1472
+ id: `${file.filename}-${i}`,
1473
+ filename: file.filename,
1474
+ size: file.size
1475
+ }, `${file.filename}-${file.size}`))
1476
+ }),
1477
+ text && /* @__PURE__ */ jsx("div", {
1478
+ className: "ms-[70px] max-w-[calc(95%-40px)]",
1479
+ children: /* @__PURE__ */ jsx("div", {
1480
+ className: "rounded-an-message bg-an-user-message-bg px-3.5 py-1.5 text-sm text-an-user-message-text transition-colors",
1481
+ children: /* @__PURE__ */ jsx("p", {
1482
+ className: "leading-5 wrap-break-word whitespace-pre-wrap",
1483
+ children: text
1484
+ })
1485
+ })
1486
+ })
1487
+ ]
1488
+ });
1489
+ });
1490
+ var LazyGenerativeTextRenderer = lazy(() => import("./openui-renderer-MKFv6LlH-MDRSVFEY.js").then((n) => n.n).then(({ GenerativeTextRenderer }) => ({ default: GenerativeTextRenderer })));
1491
1491
  function PlainTextFallback({ className, content }) {
1492
1492
  return /* @__PURE__ */ jsx("div", {
1493
1493
  className: className ? `${className} whitespace-pre-wrap` : "whitespace-pre-wrap",
@@ -1693,16 +1693,16 @@ function VirtualizedTurnList({ estimateSize, getItemKey, itemGap = 0, items, ove
1693
1693
 
1694
1694
  export {
1695
1695
  ChevronLeft,
1696
+ groupMessages,
1696
1697
  MessageBubble,
1697
1698
  MessageBubbleContent,
1698
1699
  MessageScroller,
1699
1700
  Message,
1700
1701
  MessageContent,
1701
- FileAttachment,
1702
- UserMessage,
1703
1702
  normalizeAssistantToolParts,
1704
1703
  buildAssistantElements,
1705
- groupMessages,
1704
+ FileAttachment,
1705
+ UserMessage,
1706
1706
  FleetGenerativeTextRenderer,
1707
1707
  VirtualizedTurnList
1708
1708
  };