@qredence/fleet 0.5.1 → 0.5.5

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 (78) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/bin/fleet-prime.mjs +6 -0
  3. package/dist/web/client/assets/action-swap-roll-DX5o_33Q.js +1 -0
  4. package/dist/web/client/assets/artifacts-panel-C3yewUBP.js +1 -0
  5. package/dist/web/client/assets/{chat-CsepBQwy.js → chat-W-yOMyG_.js} +1 -1
  6. package/dist/web/client/assets/chat-command-palette-D0xDK1j4.js +1 -0
  7. package/dist/web/client/assets/command-CQzrtjyj.js +2 -0
  8. package/dist/web/client/assets/{fleet-pi-tool-renderer-BD_ZAjlG.js → fleet-pi-tool-renderer-B0Y7ou0U.js} +16 -16
  9. package/dist/web/client/assets/generative-text-renderer-SS9c1kNX.js +6 -0
  10. package/dist/web/client/assets/globals-D_Hy9bT1.css +2 -0
  11. package/dist/web/client/assets/{index-DweoFRMs.js → index-C9FloFgj.js} +2 -2
  12. package/dist/web/client/assets/{ipython-tool-BfeIa0nK.js → ipython-tool-BrMePqD-.js} +1 -1
  13. package/dist/web/client/assets/{openui-renderer-DlUo6ehT.js → openui-renderer-CCfQqUfE.js} +5 -5
  14. package/dist/web/client/assets/{prime-agent-artifacts-CHGb5V_j.js → prime-agent-artifacts-C-g1VPiW.js} +1 -1
  15. package/dist/web/client/assets/range-65lubOFp.js +2 -0
  16. package/dist/web/client/assets/repl-panel-CCh4ly7J.js +1 -0
  17. package/dist/web/client/assets/routes-DSTyx_J0.js +16 -0
  18. package/dist/web/client/assets/session-insights-panel-CQdT90Op.js +1 -0
  19. package/dist/web/client/assets/subagent-chat-panel-D-qw4uWb.js +1 -0
  20. package/dist/web/client/assets/subagent-transcript-C7oRwEEP.js +1 -0
  21. package/dist/web/client/assets/subagents-panel-aSMWZP9p.js +1 -0
  22. package/dist/web/client/assets/{tool-result-CBzwwUE9.js → tool-result-C2NvnN32.js} +3 -3
  23. package/dist/web/server/{_tanstack-start-manifest_v-bDUlyKY0-KFW35JRO.js → _tanstack-start-manifest_v-DZc7dwm7-6ZQLGXB7.js} +13 -13
  24. package/dist/web/server/{artifacts-panel-D9w1DHAN-FGRDCYU2.js → artifacts-panel-Bt7YUzm4-NCBMXJ25.js} +15 -15
  25. package/dist/web/server/{chat-command-palette-BaenPkCN-BHSOPGU4.js → chat-command-palette-Co1L3rUY-RQLNME4T.js} +33 -12
  26. package/dist/web/server/{chunk-KPNGZR2R.js → chunk-2V6B62OF.js} +204 -204
  27. package/dist/web/server/{chunk-44IVTHDV.js → chunk-3HUI75QM.js} +60 -60
  28. package/dist/web/server/{chunk-5AVQZ4AZ.js → chunk-4UBOIY2S.js} +2 -1
  29. package/dist/web/server/{chunk-Y6LJ4WBI.js → chunk-56HSKUBD.js} +699 -691
  30. package/dist/web/server/{chunk-RPROZ2JC.js → chunk-5RQ6QAZX.js} +133 -133
  31. package/dist/web/server/{chunk-PWK2HJIV.js → chunk-6D524OCJ.js} +175 -175
  32. package/dist/web/server/{chunk-FBOO3G67.js → chunk-AECEK3K7.js} +6 -6
  33. package/dist/web/server/{chunk-VHCAD46L.js → chunk-AWFOBHH4.js} +4 -4
  34. package/dist/web/server/{chunk-FVWDJNXR.js → chunk-BEMDNODD.js} +1 -1
  35. package/dist/web/server/{chunk-X2TUFLTP.js → chunk-BLVCDQ7E.js} +438 -438
  36. package/dist/web/server/{chunk-EIVNRYP3.js → chunk-FZRBAPUM.js} +2 -2
  37. package/dist/web/server/chunk-KOIPYBJF.js +3773 -0
  38. package/dist/web/server/{chunk-XYYNYFFD.js → chunk-LSIHVSUT.js} +2 -2
  39. package/dist/web/server/{chunk-H64JLONZ.js → chunk-PDRD2X5G.js} +2 -2
  40. package/dist/web/server/{chunk-FE4KN6T2.js → chunk-RX62LUDV.js} +4 -4
  41. package/dist/web/server/chunk-SR65ARVS.js +192 -0
  42. package/dist/web/server/{chunk-WK7CQS2N.js → chunk-SZ65K4CO.js} +48 -48
  43. package/dist/web/server/{chunk-DO3TVNGA.js → chunk-TO3YPLNZ.js} +6 -6
  44. package/dist/web/server/{chunk-3IKSPTYI.js → chunk-UKINNXJY.js} +2 -2
  45. package/dist/web/server/{chunk-M2FGGPNA.js → chunk-VGDP4YDU.js} +70 -45
  46. package/dist/web/server/{chunk-PCJ5EKFE.js → chunk-VRLTWOVJ.js} +11 -6
  47. package/dist/web/server/{chunk-HBZ6YKRB.js → chunk-WN77JNYY.js} +2 -2
  48. package/dist/web/server/{chunk-2U7PWSJX.js → chunk-WZNZQNI4.js} +231 -3633
  49. package/dist/web/server/{chunk-B2F6MLVZ.js → chunk-XFVL5ITL.js} +2246 -2228
  50. package/dist/web/server/{chunk-3GSFXWL5.js → chunk-XLCTCC4W.js} +3 -3
  51. package/dist/web/server/{chunk-AZZHUVJR.js → chunk-Y4MHIA7B.js} +18 -26
  52. package/dist/web/server/{fleet-pi-tool-renderer-lJvZWmBJ-4M5YI6VS.js → fleet-pi-tool-renderer-CE45vM6T-2UQN6RJM.js} +11 -9
  53. package/dist/web/server/{fork-picker-dialog-BZzfxdQd-Y725VJ7G.js → fork-picker-dialog-DCzuGelH-TN2Q5OXI.js} +5 -5
  54. package/dist/web/server/{openui-renderer-Cg4RSE9k-7BTJIYZJ.js → openui-renderer-c36_GYes-HX46N4M5.js} +11 -11
  55. package/dist/web/server/{repl-panel-BEQrC2ef-V3YEZ7EO.js → repl-panel-D9E8nkQy-L45ZNG2V.js} +4 -3
  56. package/dist/web/server/{router-CEahIUpn-SRG7XKIH.js → router-_ZpegqiQ-QIE44PIB.js} +1737 -1370
  57. package/dist/web/server/{routes-BznemByz-F4DJAYIM.js → routes-Ct81-8Tq-LHRMOS7R.js} +2882 -2330
  58. package/dist/web/server/server.js +3 -3
  59. package/dist/web/server/{session-insights-panel-JfeTHE0d-3HZB3ZWI.js → session-insights-panel-BHZyKDTo-LRMAMA3O.js} +4 -2
  60. package/dist/web/server/{settings-dialog-DgeBMORv-7SRSENPM.js → settings-dialog-B4uUS6f--AZ4I4ZOT.js} +142 -142
  61. package/dist/web/server/{start-CLAD7RcD-56S4BWMM.js → start-BYVnok_H-IA6TWGA3.js} +1 -1
  62. package/dist/web/server/subagent-chat-panel-C97S3WID-DLOAD5TG.js +63 -0
  63. package/dist/web/server/subagents-panel-iHpvMIZy-LI6XK66M.js +181 -0
  64. package/dist/web/server/{workspace-panel-y54P79_B-VPIYUXF6.js → workspace-panel-CHPoiXEo-YB4WS3MK.js} +2 -2
  65. package/package.json +5 -5
  66. package/dist/web/client/assets/artifacts-panel-CX62LQ13.js +0 -1
  67. package/dist/web/client/assets/chat-command-palette-C4sSf8Kg.js +0 -1
  68. package/dist/web/client/assets/collapsible-DmyYcezk.js +0 -1
  69. package/dist/web/client/assets/command-CB6emP8m.js +0 -2
  70. package/dist/web/client/assets/conversation-turns-ByFoWPXe.js +0 -6
  71. package/dist/web/client/assets/globals-CqqQdk2S.css +0 -2
  72. package/dist/web/client/assets/range-vSJ7Ux_5.js +0 -2
  73. package/dist/web/client/assets/repl-panel-DiGS6uCw.js +0 -1
  74. package/dist/web/client/assets/routes-CxlNCi_v.js +0 -16
  75. package/dist/web/client/assets/session-insights-panel-DwmwiikS.js +0 -1
  76. package/dist/web/client/assets/subagents-panel-DGnsK6Nx.js +0 -1
  77. package/dist/web/server/chunk-ZEVZFLIY.js +0 -334
  78. package/dist/web/server/subagents-panel-CzZe3co7-2VZ5JTWF.js +0 -332
@@ -2,15 +2,15 @@ import {
2
2
  LazyMarkdown,
3
3
  Terminal,
4
4
  useHoverCapable
5
- } from "./chunk-44IVTHDV.js";
5
+ } from "./chunk-3HUI75QM.js";
6
6
  import {
7
7
  notify
8
- } from "./chunk-EIVNRYP3.js";
8
+ } from "./chunk-FZRBAPUM.js";
9
9
  import {
10
10
  CircleX,
11
11
  Copy,
12
12
  Wrench
13
- } from "./chunk-PWK2HJIV.js";
13
+ } from "./chunk-6D524OCJ.js";
14
14
  import {
15
15
  AnimatePresence,
16
16
  EASE_OUT,
@@ -36,7 +36,7 @@ import {
36
36
  } from "./chunk-NLIUTTKL.js";
37
37
  import {
38
38
  X
39
- } from "./chunk-X2TUFLTP.js";
39
+ } from "./chunk-BLVCDQ7E.js";
40
40
  import {
41
41
  CircleAlert,
42
42
  FileText
@@ -48,7 +48,7 @@ import {
48
48
  cn
49
49
  } from "./chunk-DJMHPJBP.js";
50
50
 
51
- // web/app/dist/server/assets/conversation-turns-wRm6Ard7.js
51
+ // web/app/dist/server/assets/generative-text-renderer-Cn-31rC8.js
52
52
  import { Suspense, cloneElement, createContext, lazy, memo, useCallback, useContext, useEffect, useEffectEvent, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
53
53
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
54
54
  import { createPortal } from "react-dom";
@@ -151,6 +151,13 @@ var Image = createLucideIcon("image", [
151
151
  key: "1xmnt7"
152
152
  }]
153
153
  ]);
154
+ var RotateCw = createLucideIcon("rotate-cw", [["path", {
155
+ d: "M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8",
156
+ key: "1p45f6"
157
+ }], ["path", {
158
+ d: "M21 3v5h-5",
159
+ key: "1q7to0"
160
+ }]]);
154
161
  var MessageSideContext = createContext(void 0);
155
162
  var MessageBubbleContext = createContext({
156
163
  animateIn: true,
@@ -812,175 +819,270 @@ function MessageContent({ className, ...props }) {
812
819
  ...props
813
820
  });
814
821
  }
815
- var ErrorMessage = memo(function ErrorMessage2({ title = "Something went wrong", message, className }) {
816
- return /* @__PURE__ */ jsx("div", {
817
- className: cn("flex justify-start", className),
818
- children: /* @__PURE__ */ jsxs("div", {
819
- className: "rounded-[8px] border border-red-500/30 bg-red-500/10 px-4 py-2.5 text-sm text-an-foreground",
820
- children: [/* @__PURE__ */ jsx("div", {
821
- className: "font-medium text-an-foreground",
822
- children: title
823
- }), /* @__PURE__ */ jsx("div", {
824
- className: "mt-0.5 text-an-foreground-muted",
825
- children: message
826
- })]
827
- })
822
+ function ImageLightbox({ open, onClose, images, initialIndex = 0 }) {
823
+ const [currentIndex, setCurrentIndex] = useState(initialIndex);
824
+ const hasMultipleImages = images.length > 1;
825
+ useEffect(() => {
826
+ if (open) setCurrentIndex(initialIndex);
827
+ }, [open, initialIndex]);
828
+ const goToPrevious = useCallback((event) => {
829
+ event?.stopPropagation();
830
+ setCurrentIndex((prev) => prev > 0 ? prev - 1 : images.length - 1);
831
+ }, [images.length]);
832
+ const goToNext = useCallback((event) => {
833
+ event?.stopPropagation();
834
+ setCurrentIndex((prev) => prev < images.length - 1 ? prev + 1 : 0);
835
+ }, [images.length]);
836
+ const onLightboxKeyDown = useEffectEvent((event) => {
837
+ switch (event.key) {
838
+ case "ArrowLeft":
839
+ event.preventDefault();
840
+ if (hasMultipleImages) goToPrevious();
841
+ break;
842
+ case "ArrowRight":
843
+ event.preventDefault();
844
+ if (hasMultipleImages) goToNext();
845
+ }
828
846
  });
829
- });
830
- function formatPayload(value) {
831
- if (typeof value === "string") return value;
832
- if (value === void 0) return "";
833
- try {
834
- return JSON.stringify(value, null, 2) ?? "";
835
- } catch {
836
- return String(value);
837
- }
838
- }
839
- function PayloadPart({ part }) {
840
- const payload = formatPayload(part.payload);
841
- return /* @__PURE__ */ jsxs("section", {
842
- "data-payload-kind": part.kind,
843
- className: "w-full min-w-0 overflow-hidden rounded-xl border border-border/60 bg-muted/40 text-sm",
847
+ useEffect(() => {
848
+ if (!open) return;
849
+ const handler = (event) => onLightboxKeyDown(event);
850
+ window.addEventListener("keydown", handler, true);
851
+ return () => window.removeEventListener("keydown", handler, true);
852
+ }, [open]);
853
+ useEffect(() => {
854
+ if (!open) return;
855
+ const previousOverflow = document.body.style.overflow;
856
+ document.body.style.overflow = "hidden";
857
+ return () => {
858
+ document.body.style.overflow = previousOverflow;
859
+ };
860
+ }, [open]);
861
+ const dialogRef = useRef(null);
862
+ const onLightDismiss = useEffectEvent((event) => {
863
+ if (dialogRef.current && event.target === dialogRef.current) onClose();
864
+ });
865
+ useEffect(() => {
866
+ if (!open) return;
867
+ const dialog = dialogRef.current;
868
+ if (!dialog) return;
869
+ if (!dialog.open) dialog.showModal();
870
+ const handler = (event) => onLightDismiss(event);
871
+ dialog.addEventListener("click", handler);
872
+ return () => {
873
+ dialog.removeEventListener("click", handler);
874
+ if (dialog.open) dialog.close();
875
+ };
876
+ }, [open]);
877
+ if (typeof document === "undefined") return null;
878
+ if (!open) return null;
879
+ const currentImage = images[currentIndex] ?? images[0];
880
+ if (!currentImage?.url) return null;
881
+ return createPortal(/* @__PURE__ */ jsxs("dialog", {
882
+ ref: dialogRef,
883
+ "aria-label": "Image viewer",
884
+ onClose: () => onClose(),
885
+ 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",
844
886
  children: [
845
- /* @__PURE__ */ jsx("div", {
846
- className: "border-b border-border/50 px-3 py-2 text-xs font-medium text-muted-foreground",
847
- children: part.title
887
+ /* @__PURE__ */ jsx("button", {
888
+ type: "button",
889
+ onClick: onClose,
890
+ "aria-label": "Close fullscreen (Esc)",
891
+ 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",
892
+ children: /* @__PURE__ */ jsx(X, { className: "size-5" })
848
893
  }),
849
- part.text ? /* @__PURE__ */ jsx("pre", {
850
- className: "m-0 whitespace-pre-wrap break-words px-3 py-3 font-mono text-xs leading-5 text-foreground/80",
851
- children: part.text
852
- }) : null,
853
- payload ? /* @__PURE__ */ jsxs("details", {
854
- open: true,
855
- className: "border-t border-border/50 px-3 py-2",
856
- children: [/* @__PURE__ */ jsx("summary", {
857
- className: "cursor-pointer text-xs font-medium text-muted-foreground",
858
- children: "Payload"
859
- }), /* @__PURE__ */ jsx("pre", {
860
- className: "m-0 mt-2 max-w-full whitespace-pre-wrap break-words font-mono text-xs leading-5 text-foreground/70",
861
- children: payload
894
+ hasMultipleImages && /* @__PURE__ */ jsx("button", {
895
+ type: "button",
896
+ onClick: goToPrevious,
897
+ "aria-label": "Previous image (\u2190)",
898
+ 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",
899
+ children: /* @__PURE__ */ jsx(ChevronLeft, { className: "size-6" })
900
+ }),
901
+ /* @__PURE__ */ jsx("img", {
902
+ src: currentImage.url,
903
+ alt: currentImage.filename ?? "Image preview",
904
+ className: "max-h-[85vh] max-w-[90vw] object-contain select-none",
905
+ onClick: (event) => event.stopPropagation(),
906
+ draggable: false
907
+ }),
908
+ hasMultipleImages && /* @__PURE__ */ jsx("button", {
909
+ type: "button",
910
+ onClick: goToNext,
911
+ "aria-label": "Next image (\u2192)",
912
+ 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",
913
+ children: /* @__PURE__ */ jsx(ChevronRight, { className: "size-6" })
914
+ }),
915
+ hasMultipleImages && /* @__PURE__ */ jsxs("div", {
916
+ className: "absolute bottom-6 left-1/2 flex -translate-x-1/2 flex-col items-center gap-3",
917
+ children: [/* @__PURE__ */ jsx("div", {
918
+ className: "flex gap-2",
919
+ children: images.map((image, idx) => /* @__PURE__ */ jsx("button", {
920
+ type: "button",
921
+ onClick: (event) => {
922
+ event.stopPropagation();
923
+ setCurrentIndex(idx);
924
+ },
925
+ "aria-label": `Go to image ${idx + 1}`,
926
+ className: cn("size-2 rounded-full transition-all", idx === currentIndex ? "scale-125 bg-white" : "bg-white/40 hover:bg-white/60")
927
+ }, image.url))
928
+ }), /* @__PURE__ */ jsxs("span", {
929
+ className: "text-sm text-white/70",
930
+ children: [
931
+ currentIndex + 1,
932
+ " / ",
933
+ images.length
934
+ ]
862
935
  })]
863
- }) : null
936
+ })
864
937
  ]
865
- });
866
- }
867
- function isRecord$2(value) {
868
- return typeof value === "object" && value !== null;
869
- }
870
- function parseStructuredJson(value) {
871
- if (typeof value !== "string") return value;
872
- const trimmed = value.trim();
873
- if (!trimmed) return value;
874
- try {
875
- const parsed = JSON.parse(trimmed);
876
- return isRecord$2(parsed) || Array.isArray(parsed) ? parsed : value;
877
- } catch {
878
- return value;
879
- }
938
+ }), document.body);
880
939
  }
881
- function normalizeToolPart(part) {
882
- if (!isRecord$2(part)) return part;
883
- if (typeof part.type !== "string" || !part.type.startsWith("tool-")) return part;
884
- const normalizedInput = parseStructuredJson(part.input);
885
- const normalizedOutput = parseStructuredJson(part.output);
886
- const normalizedResult = parseStructuredJson(part.result);
887
- const inputChanged = normalizedInput !== part.input;
888
- const outputChanged = normalizedOutput !== part.output;
889
- const resultChanged = normalizedResult !== part.result;
890
- if (!inputChanged && !outputChanged && !resultChanged) return part;
891
- const normalizedPart = { ...part };
892
- if (inputChanged) normalizedPart.input = normalizedInput;
893
- if (outputChanged) normalizedPart.output = normalizedOutput;
894
- if (resultChanged) normalizedPart.result = normalizedResult;
895
- return normalizedPart;
940
+ function formatFileSize(bytes) {
941
+ if (bytes < 1024) return `${bytes} B`;
942
+ if (bytes < 1048576) return `${(bytes / 1024).toFixed(1)} KB`;
943
+ return `${(bytes / 1048576).toFixed(1)} MB`;
896
944
  }
897
- function normalizeAssistantToolParts(parts) {
898
- let changed = false;
899
- const normalizedParts = parts.map((part) => {
900
- const normalizedPart = normalizeToolPart(part);
901
- if (normalizedPart !== part) changed = true;
902
- return normalizedPart;
903
- });
904
- return changed ? normalizedParts : parts;
945
+ function getFileIconName(filename, isImage) {
946
+ if (isImage) return "image";
947
+ const ext = filename.split(".").pop()?.toLowerCase();
948
+ if ([
949
+ "js",
950
+ "ts",
951
+ "jsx",
952
+ "tsx",
953
+ "py",
954
+ "rb",
955
+ "go",
956
+ "rs",
957
+ "java",
958
+ "kt",
959
+ "swift",
960
+ "c",
961
+ "cpp",
962
+ "h",
963
+ "hpp",
964
+ "cs",
965
+ "php"
966
+ ].includes(ext || "")) return "code";
967
+ if ([
968
+ "json",
969
+ "yaml",
970
+ "yml",
971
+ "xml"
972
+ ].includes(ext || "")) return "data";
973
+ return "text";
905
974
  }
906
- var timeFormatter = new Intl.DateTimeFormat("en-US", {
907
- hour: "numeric",
908
- minute: "2-digit",
909
- hour12: true
910
- });
911
- var dateFormatter = new Intl.DateTimeFormat("en-US", {
912
- month: "short",
913
- day: "numeric"
914
- });
915
- function formatTimestamp(date) {
916
- const now = /* @__PURE__ */ new Date();
917
- if (date.getFullYear() === now.getFullYear() && date.getMonth() === now.getMonth() && date.getDate() === now.getDate()) return timeFormatter.format(date);
918
- return dateFormatter.format(date);
975
+ function renderFileIcon(iconName) {
976
+ switch (iconName) {
977
+ case "image":
978
+ return /* @__PURE__ */ jsx(Image, { className: "size-4 text-muted-foreground" });
979
+ case "code":
980
+ return /* @__PURE__ */ jsx(FileCode, { className: "size-4 text-muted-foreground" });
981
+ case "data":
982
+ return /* @__PURE__ */ jsx(FileBraces, { className: "size-4 text-muted-foreground" });
983
+ default:
984
+ return /* @__PURE__ */ jsx(FileText, { className: "size-4 text-muted-foreground" });
985
+ }
919
986
  }
920
- function CopyButton({ text, onCopied }) {
921
- const [copied, setCopied] = useState(false);
922
- const copiedTimerRef = useRef(null);
923
- useEffect(() => {
924
- return () => {
925
- if (copiedTimerRef.current) window.clearTimeout(copiedTimerRef.current);
926
- };
927
- }, []);
928
- const handleCopy = async () => {
929
- try {
930
- await navigator.clipboard.writeText(text);
931
- setCopied(true);
932
- if (copiedTimerRef.current) window.clearTimeout(copiedTimerRef.current);
933
- copiedTimerRef.current = window.setTimeout(() => {
934
- setCopied(false);
935
- copiedTimerRef.current = null;
936
- }, 2e3);
937
- notify.success("Copied to clipboard");
938
- onCopied?.();
939
- } catch {
940
- notify.error("Failed to copy to clipboard");
941
- }
987
+ function FileAttachment({ id, filename, size, isImage, url, onRemove, className, display = "chip", enableImagePreview = true }) {
988
+ const [isHovered, setIsHovered] = useState(false);
989
+ const [isLightboxOpen, setIsLightboxOpen] = useState(false);
990
+ const iconName = getFileIconName(filename, isImage);
991
+ const isImageOnly = display === "image-only" && isImage && !!url;
992
+ const canPreview = Boolean(enableImagePreview && isImage && url);
993
+ const openLightbox = (event) => {
994
+ event?.stopPropagation();
995
+ setIsLightboxOpen(true);
942
996
  };
943
- return /* @__PURE__ */ jsx("button", {
944
- type: "button",
945
- tabIndex: -1,
946
- "aria-label": copied ? "Copied" : "Copy message",
947
- onClick: handleCopy,
948
- onPointerDown: (event) => {
949
- event.stopPropagation();
950
- },
951
- onMouseDown: (event) => event.stopPropagation(),
952
- 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"),
953
- children: /* @__PURE__ */ jsxs("div", {
954
- className: "relative h-3.5 w-3.5",
955
- 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") })]
956
- })
957
- });
958
- }
959
- function MessageToolbar({ text, timestamp, heightClass, hoverClass, isVisible, alignClass, onCopied }) {
960
997
  return /* @__PURE__ */ jsxs("div", {
961
- 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"),
962
- onMouseDown: (event) => event.stopPropagation(),
963
- onPointerDown: (event) => event.stopPropagation(),
964
- children: [timestamp && /* @__PURE__ */ jsx("span", { children: timestamp }), text && /* @__PURE__ */ jsx(CopyButton, {
965
- text,
966
- onCopied
967
- })]
998
+ 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),
999
+ onMouseEnter: () => setIsHovered(true),
1000
+ onMouseLeave: () => setIsHovered(false),
1001
+ children: [
1002
+ isImageOnly ? /* @__PURE__ */ jsx("div", {
1003
+ className: cn("size-8 shrink-0 overflow-hidden rounded-[calc(var(--an-input-border-radius)-var(--an-context-padding)-2px)]", canPreview && "cursor-pointer"),
1004
+ onClick: canPreview ? openLightbox : void 0,
1005
+ role: canPreview ? "button" : void 0,
1006
+ tabIndex: canPreview ? 0 : void 0,
1007
+ "aria-label": canPreview ? `Preview ${filename}` : void 0,
1008
+ onKeyDown: canPreview ? (event) => {
1009
+ if (event.key === "Enter" || event.key === " ") {
1010
+ event.preventDefault();
1011
+ openLightbox();
1012
+ }
1013
+ } : void 0,
1014
+ children: /* @__PURE__ */ jsx("img", {
1015
+ src: url,
1016
+ alt: filename,
1017
+ className: "h-full w-full object-cover"
1018
+ })
1019
+ }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [isImage && url ? /* @__PURE__ */ jsx("div", {
1020
+ 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"),
1021
+ onClick: canPreview ? openLightbox : void 0,
1022
+ role: canPreview ? "button" : void 0,
1023
+ tabIndex: canPreview ? 0 : void 0,
1024
+ "aria-label": canPreview ? `Preview ${filename}` : void 0,
1025
+ onKeyDown: canPreview ? (event) => {
1026
+ if (event.key === "Enter" || event.key === " ") {
1027
+ event.preventDefault();
1028
+ openLightbox();
1029
+ }
1030
+ } : void 0,
1031
+ children: /* @__PURE__ */ jsx("img", {
1032
+ src: url,
1033
+ alt: filename,
1034
+ className: "aspect-square h-full w-full object-cover"
1035
+ })
1036
+ }) : /* @__PURE__ */ jsx("div", {
1037
+ 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",
1038
+ children: renderFileIcon(iconName)
1039
+ }), /* @__PURE__ */ jsxs("div", {
1040
+ className: "flex min-w-0 flex-col",
1041
+ children: [/* @__PURE__ */ jsx("span", {
1042
+ className: "truncate text-sm font-medium text-foreground",
1043
+ title: filename,
1044
+ children: filename
1045
+ }), size !== void 0 && /* @__PURE__ */ jsx("span", {
1046
+ className: "text-[10px] text-muted-foreground",
1047
+ children: formatFileSize(size)
1048
+ })]
1049
+ })] }),
1050
+ onRemove && /* @__PURE__ */ jsx("button", {
1051
+ onClick: (e) => {
1052
+ e.stopPropagation();
1053
+ onRemove();
1054
+ },
1055
+ "aria-label": `Remove ${filename}`,
1056
+ 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"}`,
1057
+ type: "button",
1058
+ children: /* @__PURE__ */ jsx(X, { className: "size-3" })
1059
+ }),
1060
+ canPreview && url && /* @__PURE__ */ jsx(ImageLightbox, {
1061
+ open: isLightboxOpen,
1062
+ onClose: () => setIsLightboxOpen(false),
1063
+ images: [{
1064
+ id,
1065
+ url,
1066
+ filename
1067
+ }]
1068
+ })
1069
+ ]
968
1070
  });
969
1071
  }
970
- function isRecord$1(value) {
1072
+ function isRecord$2(value) {
971
1073
  return typeof value === "object" && value !== null;
972
1074
  }
973
1075
  function isTextPart(part) {
974
- return isRecord$1(part) && part.type === "text" && typeof part.text === "string";
1076
+ return isRecord$2(part) && part.type === "text" && typeof part.text === "string";
975
1077
  }
976
1078
  function isErrorPart(part) {
977
- return isRecord$1(part) && part.type === "error" && typeof part.message === "string";
1079
+ return isRecord$2(part) && part.type === "error" && typeof part.message === "string";
978
1080
  }
979
1081
  function isPayloadPart(part) {
980
- return isRecord$1(part) && part.type === "payload" && typeof part.kind === "string" && typeof part.title === "string";
1082
+ return isRecord$2(part) && part.type === "payload" && typeof part.kind === "string" && typeof part.title === "string";
981
1083
  }
982
1084
  function isV5ToolPart(part) {
983
- if (!isRecord$1(part)) return false;
1085
+ if (!isRecord$2(part)) return false;
984
1086
  const partType = part.type;
985
1087
  return partType === "dynamic-tool" || typeof partType === "string" && partType.startsWith("tool-");
986
1088
  }
@@ -989,564 +1091,449 @@ function getTextFromParts(parts, joiner) {
989
1091
  for (const part of parts) if (isTextPart(part)) texts.push(part.text);
990
1092
  return texts.join(joiner);
991
1093
  }
992
- memo(function UserTurn(props) {
993
- const { message, UserMessageComponent, userMessageClassName, onCopied, display } = props;
994
- const { enableImagePreview, showCopyToolbar, isMounted, isCopyVisible } = display;
995
- const text = getTextFromParts(message.parts ?? [], "");
996
- const hasParts = (message.parts ?? []).length > 0;
997
- if (!text && !hasParts) return null;
998
- const userCreatedAt = message?.createdAt;
999
- const userCopyKey = `user-${message.id}`;
1000
- const userTimestamp = isMounted && userCreatedAt ? formatTimestamp(new Date(userCreatedAt)) : void 0;
1001
- return /* @__PURE__ */ jsxs("div", {
1002
- className: "group/user-message",
1003
- children: [/* @__PURE__ */ jsx(UserMessageComponent, {
1004
- message,
1005
- className: userMessageClassName,
1006
- enableImagePreview
1007
- }), (showCopyToolbar && Boolean(text) || Boolean(userTimestamp)) && /* @__PURE__ */ jsx(MessageToolbar, {
1008
- text: showCopyToolbar ? text : "",
1009
- timestamp: userTimestamp,
1010
- heightClass: "h-[28px]",
1011
- hoverClass: "group-hover/user-message:opacity-100 group-hover/user-message:pointer-events-auto",
1012
- isVisible: isCopyVisible,
1013
- alignClass: "justify-end",
1014
- onCopied: () => onCopied(userCopyKey)
1015
- })]
1016
- });
1017
- });
1018
- memo(function AssistantTurn(props) {
1019
- const { assistantMsgs, turnKey, ToolRendererComponent, TextRendererComponent, toolRenderers, onOpenUIAction, onOpenUIArtifactReady, onOpenArtifact, onCopied, stream, copy, display } = props;
1020
- const { isLast: isLastTurn, isStreaming } = stream;
1021
- const { enabled: showCopyToolbar, isVisible: isCopyVisible } = copy;
1022
- const { suppressQuestionTool } = display;
1023
- const assistantText = getTextFromParts(assistantMsgs.flatMap((msg) => msg.parts ?? []), "\n\n");
1024
- const isTurnStreaming = isStreaming && isLastTurn;
1025
- const showToolbar = showCopyToolbar && Boolean(assistantText.trim()) && !isTurnStreaming;
1026
- const copyKey = `assistant-${turnKey}-all`;
1027
- const toolbarText = showCopyToolbar ? assistantText : "";
1028
- return /* @__PURE__ */ jsxs("div", {
1029
- className: "group/assistant-turn",
1030
- children: [/* @__PURE__ */ jsx("div", {
1031
- className: "flex flex-col gap-3",
1032
- children: assistantMsgs.map((msg, i) => {
1033
- const isLastMsg = isLastTurn && i === assistantMsgs.length - 1;
1034
- return /* @__PURE__ */ jsx(AssistantParts, {
1035
- msg,
1036
- isLast: isLastMsg,
1037
- isStreaming,
1038
- suppressQuestionTool,
1039
- ToolRendererComponent,
1040
- TextRendererComponent,
1041
- toolRenderers,
1042
- onOpenUIAction,
1043
- onOpenUIArtifactReady,
1044
- onOpenArtifact
1045
- }, msg.id);
1046
- })
1047
- }), (showToolbar || isCopyVisible) && /* @__PURE__ */ jsx(MessageToolbar, {
1048
- text: toolbarText,
1049
- heightClass: "h-[48px] flex items-start w-full",
1050
- hoverClass: "group-hover/assistant-turn:opacity-100 group-hover/assistant-turn:pointer-events-auto",
1051
- isVisible: isCopyVisible,
1052
- alignClass: "justify-start",
1053
- onCopied: () => onCopied(copyKey)
1054
- })]
1055
- });
1056
- });
1057
- var TOOL_CALL_KEY_PREFIX = "tool-call:";
1058
- var UNKEYED_TOOL_KEY_PREFIX = "unkeyed-tool:";
1059
- function getAssistantToolElementKey(messageId, part, occurrence) {
1060
- if (part.toolCallId) return `${TOOL_CALL_KEY_PREFIX}${part.toolCallId}`;
1061
- return `${UNKEYED_TOOL_KEY_PREFIX}${messageId}:${part.type}:${occurrence}`;
1094
+ function isRecord$1(value) {
1095
+ return typeof value === "object" && value !== null;
1062
1096
  }
1063
- function buildAssistantElements(parts, opts) {
1064
- const { messageId, isLast, isStreaming, suppressQuestionTool, suppressTextWhenPlanWrite = false, ToolRendererComponent, TextRendererComponent, toolRenderers, onOpenUIAction, onOpenUIArtifactReady, onOpenArtifact } = opts;
1065
- const elems = [];
1066
- const textChunks = [];
1067
- const hasPlanWritePresentation = suppressTextWhenPlanWrite && parts.some((part) => isV5ToolPart(part) && part.type === "tool-PlanWrite");
1068
- 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));
1069
- const nestedToolsMap = /* @__PURE__ */ new Map();
1070
- const nestedToolIds = /* @__PURE__ */ new Set();
1071
- const unkeyedToolOccurrences = /* @__PURE__ */ new Map();
1072
- for (const part of parts) {
1073
- if (!isV5ToolPart(part)) continue;
1074
- if (part.type === "tool-TaskOutput") continue;
1075
- if (!part.toolCallId || !part.toolCallId.includes(":")) continue;
1076
- const parentId = part.toolCallId.split(":")[0];
1077
- if (!taskPartIds.has(parentId)) continue;
1078
- if (!nestedToolsMap.has(parentId)) nestedToolsMap.set(parentId, []);
1079
- nestedToolsMap.get(parentId).push(part);
1080
- nestedToolIds.add(part.toolCallId);
1097
+ function getImageUrlFromPart(part) {
1098
+ if (!isRecord$1(part)) return null;
1099
+ const type = part.type;
1100
+ if (typeof type !== "string") return null;
1101
+ if (type === "image") {
1102
+ const imagePart = part;
1103
+ return imagePart.url ?? imagePart.image ?? null;
1081
1104
  }
1082
- let i = 0;
1083
- while (i < parts.length) {
1084
- const part = parts[i];
1085
- if (isV5ToolPart(part) && part.type === "tool-TaskOutput") {
1086
- i++;
1087
- continue;
1088
- }
1089
- if (isTextPart(part)) {
1090
- const text2 = part.text;
1091
- if (text2 && !hasPlanWritePresentation) textChunks.push(text2);
1092
- i++;
1093
- continue;
1094
- }
1095
- if (isErrorPart(part)) {
1096
- elems.push(/* @__PURE__ */ jsx(ErrorMessage, {
1097
- title: part.title,
1098
- message: part.message
1099
- }, `${messageId}-error-${part.title}-${part.message}`));
1100
- i++;
1101
- continue;
1102
- }
1103
- if (isPayloadPart(part)) {
1104
- elems.push(/* @__PURE__ */ jsx(PayloadPart, { part }, `${messageId}-payload-${i}`));
1105
- i++;
1106
- continue;
1105
+ if (type === "data-image") return part.data?.url ?? null;
1106
+ if (type === "file") {
1107
+ const filePart = part;
1108
+ if (filePart.mimeType?.startsWith("image/")) {
1109
+ if (filePart.url) return filePart.url;
1110
+ if (filePart.data) return `data:${filePart.mimeType};base64,${filePart.data}`;
1107
1111
  }
1108
- if (isV5ToolPart(part)) {
1109
- if (suppressQuestionTool && part.type === "tool-Question") {
1110
- i++;
1111
- continue;
1112
- }
1113
- if (part.toolCallId && nestedToolIds.has(part.toolCallId)) {
1114
- i++;
1115
- continue;
1116
- }
1117
- const chatStreamingStatus = isLast && isStreaming ? "streaming" : void 0;
1118
- const toolCallId = part.toolCallId;
1119
- const nestedTools = (part.type === "tool-Task" || part.type === "tool-Agent") && toolCallId ? nestedToolsMap.get(toolCallId) || [] : void 0;
1120
- const occurrence = part.toolCallId ? 0 : unkeyedToolOccurrences.get(part.type) ?? 0;
1121
- if (!part.toolCallId) unkeyedToolOccurrences.set(part.type, occurrence + 1);
1122
- elems.push(/* @__PURE__ */ jsx(ToolRendererComponent, {
1123
- part,
1124
- nestedTools,
1125
- chatStatus: chatStreamingStatus,
1126
- toolRenderers
1127
- }, getAssistantToolElementKey(messageId, part, occurrence)));
1128
- i++;
1129
- continue;
1130
- }
1131
- i++;
1132
1112
  }
1133
- const text = textChunks.join("\n\n");
1134
- if (text) elems.push(/* @__PURE__ */ jsx("div", {
1135
- className: "group/assistant-text text-[14px]",
1136
- children: /* @__PURE__ */ jsx(TextRendererComponent, {
1137
- content: text,
1138
- isStreaming,
1139
- messageId,
1140
- onOpenUIAction,
1141
- onOpenUIArtifactReady,
1142
- onOpenArtifact,
1143
- className: "leading-relaxed [&_p]:leading-relaxed"
1144
- })
1145
- }, `${messageId}-text-final`));
1146
- return elems;
1113
+ return null;
1147
1114
  }
1148
- function AssistantParts({ msg, isLast, isStreaming, suppressQuestionTool, ToolRendererComponent, TextRendererComponent, toolRenderers, onOpenUIAction, onOpenUIArtifactReady, onOpenArtifact }) {
1149
- const parts = useMemo(() => normalizeAssistantToolParts(msg.parts ?? []), [msg.parts]);
1150
- const elements = useMemo(() => buildAssistantElements(parts, {
1151
- messageId: msg.id,
1152
- isLast,
1153
- isStreaming,
1154
- suppressQuestionTool,
1155
- ToolRendererComponent,
1156
- TextRendererComponent,
1157
- toolRenderers,
1158
- onOpenUIAction,
1159
- onOpenUIArtifactReady,
1160
- onOpenArtifact
1161
- }), [
1162
- parts,
1163
- msg.id,
1164
- isLast,
1165
- isStreaming,
1166
- suppressQuestionTool,
1167
- ToolRendererComponent,
1168
- TextRendererComponent,
1169
- toolRenderers,
1170
- onOpenUIAction,
1171
- onOpenUIArtifactReady,
1172
- onOpenArtifact
1173
- ]);
1174
- if (elements.length > 1) return /* @__PURE__ */ jsx("div", {
1175
- className: "group/assistant-turn flex flex-col gap-3",
1176
- children: elements
1177
- });
1178
- return /* @__PURE__ */ jsx("div", {
1179
- className: "group/assistant-turn",
1180
- children: elements
1181
- });
1115
+ function getFileFromPart(part) {
1116
+ if (!isRecord$1(part)) return null;
1117
+ if (part.type !== "file") return null;
1118
+ const filePart = part;
1119
+ const filename = filePart.filename || filePart.name || filePart.fileName || "Attachment";
1120
+ if (filePart.mimeType?.startsWith("image/") ?? false) return null;
1121
+ return {
1122
+ filename,
1123
+ size: filePart.size
1124
+ };
1182
1125
  }
1183
- function ImageLightbox({ open, onClose, images, initialIndex = 0 }) {
1184
- const [currentIndex, setCurrentIndex] = useState(initialIndex);
1185
- const hasMultipleImages = images.length > 1;
1186
- useEffect(() => {
1187
- if (open) setCurrentIndex(initialIndex);
1188
- }, [open, initialIndex]);
1189
- const goToPrevious = useCallback((event) => {
1190
- event?.stopPropagation();
1191
- setCurrentIndex((prev) => prev > 0 ? prev - 1 : images.length - 1);
1192
- }, [images.length]);
1193
- const goToNext = useCallback((event) => {
1194
- event?.stopPropagation();
1195
- setCurrentIndex((prev) => prev < images.length - 1 ? prev + 1 : 0);
1196
- }, [images.length]);
1197
- const onLightboxKeyDown = useEffectEvent((event) => {
1198
- switch (event.key) {
1199
- case "ArrowLeft":
1200
- event.preventDefault();
1201
- if (hasMultipleImages) goToPrevious();
1202
- break;
1203
- case "ArrowRight":
1204
- event.preventDefault();
1205
- if (hasMultipleImages) goToNext();
1206
- }
1207
- });
1208
- useEffect(() => {
1209
- if (!open) return;
1210
- const handler = (event) => onLightboxKeyDown(event);
1211
- window.addEventListener("keydown", handler, true);
1212
- return () => window.removeEventListener("keydown", handler, true);
1213
- }, [open]);
1214
- useEffect(() => {
1215
- if (!open) return;
1216
- const previousOverflow = document.body.style.overflow;
1217
- document.body.style.overflow = "hidden";
1218
- return () => {
1219
- document.body.style.overflow = previousOverflow;
1220
- };
1221
- }, [open]);
1222
- const dialogRef = useRef(null);
1223
- const onLightDismiss = useEffectEvent((event) => {
1224
- if (dialogRef.current && event.target === dialogRef.current) onClose();
1225
- });
1226
- useEffect(() => {
1227
- if (!open) return;
1228
- const dialog = dialogRef.current;
1229
- if (!dialog) return;
1230
- if (!dialog.open) dialog.showModal();
1231
- const handler = (event) => onLightDismiss(event);
1232
- dialog.addEventListener("click", handler);
1233
- return () => {
1234
- dialog.removeEventListener("click", handler);
1235
- if (dialog.open) dialog.close();
1236
- };
1237
- }, [open]);
1238
- if (typeof document === "undefined") return null;
1239
- if (!open) return null;
1240
- const currentImage = images[currentIndex] ?? images[0];
1241
- if (!currentImage?.url) return null;
1242
- return createPortal(/* @__PURE__ */ jsxs("dialog", {
1243
- ref: dialogRef,
1244
- "aria-label": "Image viewer",
1245
- onClose: () => onClose(),
1246
- 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",
1126
+ var UserMessage = memo(function UserMessage2({ message, className, enableImagePreview = true }) {
1127
+ const [lightboxIndex, setLightboxIndex] = useState(null);
1128
+ const text = (message.parts?.filter(isTextPart) ?? []).map((p) => p.text).join("");
1129
+ const images = [];
1130
+ const files = [];
1131
+ for (const part of message.parts ?? []) {
1132
+ const imageUrl = getImageUrlFromPart(part);
1133
+ if (imageUrl) images.push(imageUrl);
1134
+ const file = getFileFromPart(part);
1135
+ if (file) files.push(file);
1136
+ }
1137
+ if (isRecord$1(message) && Array.isArray(message.experimental_attachments)) {
1138
+ for (const att of message.experimental_attachments) if (att.contentType?.startsWith("image/") && att.url) images.push(att.url);
1139
+ }
1140
+ if (!text && images.length === 0 && files.length === 0) return null;
1141
+ const lightboxImages = images.map((url, i) => ({
1142
+ id: `${message.id}-img-${i}`,
1143
+ url,
1144
+ filename: `image-${i + 1}`
1145
+ }));
1146
+ return /* @__PURE__ */ jsxs("div", {
1147
+ className: cn("flex flex-col items-end gap-1", className),
1247
1148
  children: [
1248
- /* @__PURE__ */ jsx("button", {
1249
- type: "button",
1250
- onClick: onClose,
1251
- "aria-label": "Close fullscreen (Esc)",
1252
- 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",
1253
- children: /* @__PURE__ */ jsx(X, { className: "size-5" })
1254
- }),
1255
- hasMultipleImages && /* @__PURE__ */ jsx("button", {
1256
- type: "button",
1257
- onClick: goToPrevious,
1258
- "aria-label": "Previous image (\u2190)",
1259
- 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",
1260
- children: /* @__PURE__ */ jsx(ChevronLeft, { className: "size-6" })
1261
- }),
1262
- /* @__PURE__ */ jsx("img", {
1263
- src: currentImage.url,
1264
- alt: currentImage.filename ?? "Image preview",
1265
- className: "max-h-[85vh] max-w-[90vw] object-contain select-none",
1266
- onClick: (event) => event.stopPropagation(),
1267
- draggable: false
1149
+ images.length > 0 && images.map((url, i) => /* @__PURE__ */ jsx("div", {
1150
+ className: cn("max-w-[200px] rounded-an-message bg-an-foreground/4 p-1.5", enableImagePreview && "cursor-pointer"),
1151
+ onClick: enableImagePreview ? () => setLightboxIndex(i) : void 0,
1152
+ role: enableImagePreview ? "button" : void 0,
1153
+ tabIndex: enableImagePreview ? 0 : void 0,
1154
+ onKeyDown: enableImagePreview ? (event) => {
1155
+ if (event.key === "Enter" || event.key === " ") {
1156
+ event.preventDefault();
1157
+ setLightboxIndex(i);
1158
+ }
1159
+ } : void 0,
1160
+ children: /* @__PURE__ */ jsx("img", {
1161
+ src: url,
1162
+ alt: "attachment",
1163
+ className: "block max-h-[120px] max-w-[184px] rounded-an-message-inner object-cover"
1164
+ })
1165
+ }, url)),
1166
+ enableImagePreview && lightboxImages.length > 0 && /* @__PURE__ */ jsx(ImageLightbox, {
1167
+ open: lightboxIndex !== null,
1168
+ onClose: () => setLightboxIndex(null),
1169
+ images: lightboxImages,
1170
+ initialIndex: lightboxIndex ?? 0
1268
1171
  }),
1269
- hasMultipleImages && /* @__PURE__ */ jsx("button", {
1270
- type: "button",
1271
- onClick: goToNext,
1272
- "aria-label": "Next image (\u2192)",
1273
- 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",
1274
- children: /* @__PURE__ */ jsx(ChevronRight, { className: "size-6" })
1172
+ files.length > 0 && /* @__PURE__ */ jsx("div", {
1173
+ className: "flex flex-col items-end gap-2",
1174
+ children: files.map((file, i) => /* @__PURE__ */ jsx(FileAttachment, {
1175
+ id: `${file.filename}-${i}`,
1176
+ filename: file.filename,
1177
+ size: file.size
1178
+ }, `${file.filename}-${file.size}`))
1275
1179
  }),
1276
- hasMultipleImages && /* @__PURE__ */ jsxs("div", {
1277
- className: "absolute bottom-6 left-1/2 flex -translate-x-1/2 flex-col items-center gap-3",
1278
- children: [/* @__PURE__ */ jsx("div", {
1279
- className: "flex gap-2",
1280
- children: images.map((image, idx) => /* @__PURE__ */ jsx("button", {
1281
- type: "button",
1282
- onClick: (event) => {
1283
- event.stopPropagation();
1284
- setCurrentIndex(idx);
1285
- },
1286
- "aria-label": `Go to image ${idx + 1}`,
1287
- className: cn("size-2 rounded-full transition-all", idx === currentIndex ? "scale-125 bg-white" : "bg-white/40 hover:bg-white/60")
1288
- }, image.url))
1289
- }), /* @__PURE__ */ jsxs("span", {
1290
- className: "text-sm text-white/70",
1291
- children: [
1292
- currentIndex + 1,
1293
- " / ",
1294
- images.length
1295
- ]
1296
- })]
1180
+ text && /* @__PURE__ */ jsx("div", {
1181
+ className: "ms-[70px] max-w-[calc(95%-40px)]",
1182
+ children: /* @__PURE__ */ jsx("div", {
1183
+ className: "rounded-an-message bg-an-user-message-bg px-3.5 py-1.5 text-sm text-an-user-message-text transition-colors",
1184
+ children: /* @__PURE__ */ jsx("p", {
1185
+ className: "leading-5 wrap-break-word whitespace-pre-wrap",
1186
+ children: text
1187
+ })
1188
+ })
1297
1189
  })
1298
1190
  ]
1299
- }), document.body);
1300
- }
1301
- function formatFileSize(bytes) {
1302
- if (bytes < 1024) return `${bytes} B`;
1303
- if (bytes < 1048576) return `${(bytes / 1024).toFixed(1)} KB`;
1304
- return `${(bytes / 1048576).toFixed(1)} MB`;
1305
- }
1306
- function getFileIconName(filename, isImage) {
1307
- if (isImage) return "image";
1308
- const ext = filename.split(".").pop()?.toLowerCase();
1309
- if ([
1310
- "js",
1311
- "ts",
1312
- "jsx",
1313
- "tsx",
1314
- "py",
1315
- "rb",
1316
- "go",
1317
- "rs",
1318
- "java",
1319
- "kt",
1320
- "swift",
1321
- "c",
1322
- "cpp",
1323
- "h",
1324
- "hpp",
1325
- "cs",
1326
- "php"
1327
- ].includes(ext || "")) return "code";
1328
- if ([
1329
- "json",
1330
- "yaml",
1331
- "yml",
1332
- "xml"
1333
- ].includes(ext || "")) return "data";
1334
- return "text";
1191
+ });
1192
+ });
1193
+ var ErrorMessage = memo(function ErrorMessage2({ title = "Something went wrong", message, className }) {
1194
+ return /* @__PURE__ */ jsx("div", {
1195
+ className: cn("flex justify-start", className),
1196
+ children: /* @__PURE__ */ jsxs("div", {
1197
+ className: "rounded-[8px] border border-red-500/30 bg-red-500/10 px-4 py-2.5 text-sm text-an-foreground",
1198
+ children: [/* @__PURE__ */ jsx("div", {
1199
+ className: "font-medium text-an-foreground",
1200
+ children: title
1201
+ }), /* @__PURE__ */ jsx("div", {
1202
+ className: "mt-0.5 text-an-foreground-muted",
1203
+ children: message
1204
+ })]
1205
+ })
1206
+ });
1207
+ });
1208
+ function formatPayload(value) {
1209
+ if (typeof value === "string") return value;
1210
+ if (value === void 0) return "";
1211
+ try {
1212
+ return JSON.stringify(value, null, 2) ?? "";
1213
+ } catch {
1214
+ return String(value);
1215
+ }
1335
1216
  }
1336
- function renderFileIcon(iconName) {
1337
- switch (iconName) {
1338
- case "image":
1339
- return /* @__PURE__ */ jsx(Image, { className: "size-4 text-muted-foreground" });
1340
- case "code":
1341
- return /* @__PURE__ */ jsx(FileCode, { className: "size-4 text-muted-foreground" });
1342
- case "data":
1343
- return /* @__PURE__ */ jsx(FileBraces, { className: "size-4 text-muted-foreground" });
1344
- default:
1345
- return /* @__PURE__ */ jsx(FileText, { className: "size-4 text-muted-foreground" });
1217
+ function PayloadPart({ part }) {
1218
+ const payload = formatPayload(part.payload);
1219
+ return /* @__PURE__ */ jsxs("section", {
1220
+ "data-payload-kind": part.kind,
1221
+ className: "w-full min-w-0 overflow-hidden rounded-xl border border-border/60 bg-muted/40 text-sm",
1222
+ children: [
1223
+ /* @__PURE__ */ jsx("div", {
1224
+ className: "border-b border-border/50 px-3 py-2 text-xs font-medium text-muted-foreground",
1225
+ children: part.title
1226
+ }),
1227
+ part.text ? /* @__PURE__ */ jsx("pre", {
1228
+ className: "m-0 whitespace-pre-wrap break-words px-3 py-3 font-mono text-xs leading-5 text-foreground/80",
1229
+ children: part.text
1230
+ }) : null,
1231
+ payload ? /* @__PURE__ */ jsxs("details", {
1232
+ open: true,
1233
+ className: "border-t border-border/50 px-3 py-2",
1234
+ children: [/* @__PURE__ */ jsx("summary", {
1235
+ className: "cursor-pointer text-xs font-medium text-muted-foreground",
1236
+ children: "Payload"
1237
+ }), /* @__PURE__ */ jsx("pre", {
1238
+ className: "m-0 mt-2 max-w-full whitespace-pre-wrap break-words font-mono text-xs leading-5 text-foreground/70",
1239
+ children: payload
1240
+ })]
1241
+ }) : null
1242
+ ]
1243
+ });
1244
+ }
1245
+ function isRecord(value) {
1246
+ return typeof value === "object" && value !== null;
1247
+ }
1248
+ function parseStructuredJson(value) {
1249
+ if (typeof value !== "string") return value;
1250
+ const trimmed = value.trim();
1251
+ if (!trimmed) return value;
1252
+ try {
1253
+ const parsed = JSON.parse(trimmed);
1254
+ return isRecord(parsed) || Array.isArray(parsed) ? parsed : value;
1255
+ } catch {
1256
+ return value;
1346
1257
  }
1347
1258
  }
1348
- function FileAttachment({ id, filename, size, isImage, url, onRemove, className, display = "chip", enableImagePreview = true }) {
1349
- const [isHovered, setIsHovered] = useState(false);
1350
- const [isLightboxOpen, setIsLightboxOpen] = useState(false);
1351
- const iconName = getFileIconName(filename, isImage);
1352
- const isImageOnly = display === "image-only" && isImage && !!url;
1353
- const canPreview = Boolean(enableImagePreview && isImage && url);
1354
- const openLightbox = (event) => {
1355
- event?.stopPropagation();
1356
- setIsLightboxOpen(true);
1259
+ function normalizeToolPart(part) {
1260
+ if (!isRecord(part)) return part;
1261
+ if (typeof part.type !== "string" || !part.type.startsWith("tool-")) return part;
1262
+ const normalizedInput = parseStructuredJson(part.input);
1263
+ const normalizedOutput = parseStructuredJson(part.output);
1264
+ const normalizedResult = parseStructuredJson(part.result);
1265
+ const inputChanged = normalizedInput !== part.input;
1266
+ const outputChanged = normalizedOutput !== part.output;
1267
+ const resultChanged = normalizedResult !== part.result;
1268
+ if (!inputChanged && !outputChanged && !resultChanged) return part;
1269
+ const normalizedPart = { ...part };
1270
+ if (inputChanged) normalizedPart.input = normalizedInput;
1271
+ if (outputChanged) normalizedPart.output = normalizedOutput;
1272
+ if (resultChanged) normalizedPart.result = normalizedResult;
1273
+ return normalizedPart;
1274
+ }
1275
+ function normalizeAssistantToolParts(parts) {
1276
+ let changed = false;
1277
+ const normalizedParts = parts.map((part) => {
1278
+ const normalizedPart = normalizeToolPart(part);
1279
+ if (normalizedPart !== part) changed = true;
1280
+ return normalizedPart;
1281
+ });
1282
+ return changed ? normalizedParts : parts;
1283
+ }
1284
+ var timeFormatter = new Intl.DateTimeFormat("en-US", {
1285
+ hour: "numeric",
1286
+ minute: "2-digit",
1287
+ hour12: true
1288
+ });
1289
+ var dateFormatter = new Intl.DateTimeFormat("en-US", {
1290
+ month: "short",
1291
+ day: "numeric"
1292
+ });
1293
+ function formatTimestamp(date) {
1294
+ const now = /* @__PURE__ */ new Date();
1295
+ if (date.getFullYear() === now.getFullYear() && date.getMonth() === now.getMonth() && date.getDate() === now.getDate()) return timeFormatter.format(date);
1296
+ return dateFormatter.format(date);
1297
+ }
1298
+ function CopyButton({ text, onCopied }) {
1299
+ const [copied, setCopied] = useState(false);
1300
+ const copiedTimerRef = useRef(null);
1301
+ useEffect(() => {
1302
+ return () => {
1303
+ if (copiedTimerRef.current) window.clearTimeout(copiedTimerRef.current);
1304
+ };
1305
+ }, []);
1306
+ const handleCopy = async () => {
1307
+ try {
1308
+ await navigator.clipboard.writeText(text);
1309
+ setCopied(true);
1310
+ if (copiedTimerRef.current) window.clearTimeout(copiedTimerRef.current);
1311
+ copiedTimerRef.current = window.setTimeout(() => {
1312
+ setCopied(false);
1313
+ copiedTimerRef.current = null;
1314
+ }, 2e3);
1315
+ notify.success("Copied to clipboard");
1316
+ onCopied?.();
1317
+ } catch {
1318
+ notify.error("Failed to copy to clipboard");
1319
+ }
1357
1320
  };
1321
+ return /* @__PURE__ */ jsx("button", {
1322
+ type: "button",
1323
+ tabIndex: -1,
1324
+ "aria-label": copied ? "Copied" : "Copy message",
1325
+ onClick: handleCopy,
1326
+ onPointerDown: (event) => {
1327
+ event.stopPropagation();
1328
+ },
1329
+ onMouseDown: (event) => event.stopPropagation(),
1330
+ 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"),
1331
+ children: /* @__PURE__ */ jsxs("div", {
1332
+ className: "relative h-3.5 w-3.5",
1333
+ 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") })]
1334
+ })
1335
+ });
1336
+ }
1337
+ function MessageToolbar({ text, timestamp, heightClass, hoverClass, isVisible, alignClass, onCopied }) {
1358
1338
  return /* @__PURE__ */ jsxs("div", {
1359
- 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),
1360
- onMouseEnter: () => setIsHovered(true),
1361
- onMouseLeave: () => setIsHovered(false),
1362
- children: [
1363
- isImageOnly ? /* @__PURE__ */ jsx("div", {
1364
- className: cn("size-8 shrink-0 overflow-hidden rounded-[calc(var(--an-input-border-radius)-var(--an-context-padding)-2px)]", canPreview && "cursor-pointer"),
1365
- onClick: canPreview ? openLightbox : void 0,
1366
- role: canPreview ? "button" : void 0,
1367
- tabIndex: canPreview ? 0 : void 0,
1368
- "aria-label": canPreview ? `Preview ${filename}` : void 0,
1369
- onKeyDown: canPreview ? (event) => {
1370
- if (event.key === "Enter" || event.key === " ") {
1371
- event.preventDefault();
1372
- openLightbox();
1373
- }
1374
- } : void 0,
1375
- children: /* @__PURE__ */ jsx("img", {
1376
- src: url,
1377
- alt: filename,
1378
- className: "h-full w-full object-cover"
1379
- })
1380
- }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [isImage && url ? /* @__PURE__ */ jsx("div", {
1381
- 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"),
1382
- onClick: canPreview ? openLightbox : void 0,
1383
- role: canPreview ? "button" : void 0,
1384
- tabIndex: canPreview ? 0 : void 0,
1385
- "aria-label": canPreview ? `Preview ${filename}` : void 0,
1386
- onKeyDown: canPreview ? (event) => {
1387
- if (event.key === "Enter" || event.key === " ") {
1388
- event.preventDefault();
1389
- openLightbox();
1390
- }
1391
- } : void 0,
1392
- children: /* @__PURE__ */ jsx("img", {
1393
- src: url,
1394
- alt: filename,
1395
- className: "aspect-square h-full w-full object-cover"
1396
- })
1397
- }) : /* @__PURE__ */ jsx("div", {
1398
- 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",
1399
- children: renderFileIcon(iconName)
1400
- }), /* @__PURE__ */ jsxs("div", {
1401
- className: "flex min-w-0 flex-col",
1402
- children: [/* @__PURE__ */ jsx("span", {
1403
- className: "truncate text-sm font-medium text-foreground",
1404
- title: filename,
1405
- children: filename
1406
- }), size !== void 0 && /* @__PURE__ */ jsx("span", {
1407
- className: "text-[10px] text-muted-foreground",
1408
- children: formatFileSize(size)
1409
- })]
1410
- })] }),
1411
- onRemove && /* @__PURE__ */ jsx("button", {
1412
- onClick: (e) => {
1413
- e.stopPropagation();
1414
- onRemove();
1415
- },
1416
- "aria-label": `Remove ${filename}`,
1417
- 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"}`,
1418
- type: "button",
1419
- children: /* @__PURE__ */ jsx(X, { className: "size-3" })
1420
- }),
1421
- canPreview && url && /* @__PURE__ */ jsx(ImageLightbox, {
1422
- open: isLightboxOpen,
1423
- onClose: () => setIsLightboxOpen(false),
1424
- images: [{
1425
- id,
1426
- url,
1427
- filename
1428
- }]
1339
+ 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"),
1340
+ onMouseDown: (event) => event.stopPropagation(),
1341
+ onPointerDown: (event) => event.stopPropagation(),
1342
+ children: [timestamp && /* @__PURE__ */ jsx("span", { children: timestamp }), text && /* @__PURE__ */ jsx(CopyButton, {
1343
+ text,
1344
+ onCopied
1345
+ })]
1346
+ });
1347
+ }
1348
+ memo(function UserTurn(props) {
1349
+ const { message, UserMessageComponent, userMessageClassName, onCopied, display } = props;
1350
+ const { enableImagePreview, showCopyToolbar, isMounted, isCopyVisible } = display;
1351
+ const text = getTextFromParts(message.parts ?? [], "");
1352
+ const hasParts = (message.parts ?? []).length > 0;
1353
+ if (!text && !hasParts) return null;
1354
+ const userCreatedAt = message?.createdAt;
1355
+ const userCopyKey = `user-${message.id}`;
1356
+ const userTimestamp = isMounted && userCreatedAt ? formatTimestamp(new Date(userCreatedAt)) : void 0;
1357
+ return /* @__PURE__ */ jsxs("div", {
1358
+ className: "group/user-message",
1359
+ children: [/* @__PURE__ */ jsx(UserMessageComponent, {
1360
+ message,
1361
+ className: userMessageClassName,
1362
+ enableImagePreview
1363
+ }), (showCopyToolbar && Boolean(text) || Boolean(userTimestamp)) && /* @__PURE__ */ jsx(MessageToolbar, {
1364
+ text: showCopyToolbar ? text : "",
1365
+ timestamp: userTimestamp,
1366
+ heightClass: "h-[28px]",
1367
+ hoverClass: "group-hover/user-message:opacity-100 group-hover/user-message:pointer-events-auto",
1368
+ isVisible: isCopyVisible,
1369
+ alignClass: "justify-end",
1370
+ onCopied: () => onCopied(userCopyKey)
1371
+ })]
1372
+ });
1373
+ });
1374
+ memo(function AssistantTurn(props) {
1375
+ const { assistantMsgs, turnKey, ToolRendererComponent, TextRendererComponent, toolRenderers, onOpenUIAction, onOpenUIArtifactReady, onOpenArtifact, onCopied, stream, copy, display } = props;
1376
+ const { isLast: isLastTurn, isStreaming } = stream;
1377
+ const { enabled: showCopyToolbar, isVisible: isCopyVisible } = copy;
1378
+ const { suppressQuestionTool } = display;
1379
+ const assistantText = getTextFromParts(assistantMsgs.flatMap((msg) => msg.parts ?? []), "\n\n");
1380
+ const isTurnStreaming = isStreaming && isLastTurn;
1381
+ const showToolbar = showCopyToolbar && Boolean(assistantText.trim()) && !isTurnStreaming;
1382
+ const copyKey = `assistant-${turnKey}-all`;
1383
+ const toolbarText = showCopyToolbar ? assistantText : "";
1384
+ return /* @__PURE__ */ jsxs("div", {
1385
+ className: "group/assistant-turn",
1386
+ children: [/* @__PURE__ */ jsx("div", {
1387
+ className: "flex flex-col gap-3",
1388
+ children: assistantMsgs.map((msg, i) => {
1389
+ const isLastMsg = isLastTurn && i === assistantMsgs.length - 1;
1390
+ return /* @__PURE__ */ jsx(AssistantParts, {
1391
+ msg,
1392
+ isLast: isLastMsg,
1393
+ isStreaming,
1394
+ suppressQuestionTool,
1395
+ ToolRendererComponent,
1396
+ TextRendererComponent,
1397
+ toolRenderers,
1398
+ onOpenUIAction,
1399
+ onOpenUIArtifactReady,
1400
+ onOpenArtifact
1401
+ }, msg.id);
1429
1402
  })
1430
- ]
1403
+ }), (showToolbar || isCopyVisible) && /* @__PURE__ */ jsx(MessageToolbar, {
1404
+ text: toolbarText,
1405
+ heightClass: "h-[48px] flex items-start w-full",
1406
+ hoverClass: "group-hover/assistant-turn:opacity-100 group-hover/assistant-turn:pointer-events-auto",
1407
+ isVisible: isCopyVisible,
1408
+ alignClass: "justify-start",
1409
+ onCopied: () => onCopied(copyKey)
1410
+ })]
1431
1411
  });
1412
+ });
1413
+ var TOOL_CALL_KEY_PREFIX = "tool-call:";
1414
+ var UNKEYED_TOOL_KEY_PREFIX = "unkeyed-tool:";
1415
+ function getAssistantToolElementKey(messageId, part, occurrence) {
1416
+ if (part.toolCallId) return `${TOOL_CALL_KEY_PREFIX}${part.toolCallId}`;
1417
+ return `${UNKEYED_TOOL_KEY_PREFIX}${messageId}:${part.type}:${occurrence}`;
1432
1418
  }
1433
- function isRecord(value) {
1434
- return typeof value === "object" && value !== null;
1435
- }
1436
- function getImageUrlFromPart(part) {
1437
- if (!isRecord(part)) return null;
1438
- const type = part.type;
1439
- if (typeof type !== "string") return null;
1440
- if (type === "image") {
1441
- const imagePart = part;
1442
- return imagePart.url ?? imagePart.image ?? null;
1419
+ function buildAssistantElements(parts, opts) {
1420
+ const { messageId, isLast, isStreaming, suppressQuestionTool, suppressTextWhenPlanWrite = false, ToolRendererComponent, TextRendererComponent, toolRenderers, onOpenUIAction, onOpenUIArtifactReady, onOpenArtifact } = opts;
1421
+ const elems = [];
1422
+ const textChunks = [];
1423
+ const hasPlanWritePresentation = suppressTextWhenPlanWrite && parts.some((part) => isV5ToolPart(part) && part.type === "tool-PlanWrite");
1424
+ 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));
1425
+ const nestedToolsMap = /* @__PURE__ */ new Map();
1426
+ const nestedToolIds = /* @__PURE__ */ new Set();
1427
+ const unkeyedToolOccurrences = /* @__PURE__ */ new Map();
1428
+ for (const part of parts) {
1429
+ if (!isV5ToolPart(part)) continue;
1430
+ if (part.type === "tool-TaskOutput") continue;
1431
+ if (!part.toolCallId || !part.toolCallId.includes(":")) continue;
1432
+ const parentId = part.toolCallId.split(":")[0];
1433
+ if (!taskPartIds.has(parentId)) continue;
1434
+ if (!nestedToolsMap.has(parentId)) nestedToolsMap.set(parentId, []);
1435
+ nestedToolsMap.get(parentId).push(part);
1436
+ nestedToolIds.add(part.toolCallId);
1443
1437
  }
1444
- if (type === "data-image") return part.data?.url ?? null;
1445
- if (type === "file") {
1446
- const filePart = part;
1447
- if (filePart.mimeType?.startsWith("image/")) {
1448
- if (filePart.url) return filePart.url;
1449
- if (filePart.data) return `data:${filePart.mimeType};base64,${filePart.data}`;
1438
+ let i = 0;
1439
+ while (i < parts.length) {
1440
+ const part = parts[i];
1441
+ if (isV5ToolPart(part) && part.type === "tool-TaskOutput") {
1442
+ i++;
1443
+ continue;
1444
+ }
1445
+ if (isTextPart(part)) {
1446
+ const text2 = part.text;
1447
+ if (text2 && !hasPlanWritePresentation) textChunks.push(text2);
1448
+ i++;
1449
+ continue;
1450
+ }
1451
+ if (isErrorPart(part)) {
1452
+ elems.push(/* @__PURE__ */ jsx(ErrorMessage, {
1453
+ title: part.title,
1454
+ message: part.message
1455
+ }, `${messageId}-error-${part.title}-${part.message}`));
1456
+ i++;
1457
+ continue;
1458
+ }
1459
+ if (isPayloadPart(part)) {
1460
+ elems.push(/* @__PURE__ */ jsx(PayloadPart, { part }, `${messageId}-payload-${i}`));
1461
+ i++;
1462
+ continue;
1463
+ }
1464
+ if (isV5ToolPart(part)) {
1465
+ if (suppressQuestionTool && part.type === "tool-Question") {
1466
+ i++;
1467
+ continue;
1468
+ }
1469
+ if (part.toolCallId && nestedToolIds.has(part.toolCallId)) {
1470
+ i++;
1471
+ continue;
1472
+ }
1473
+ const chatStreamingStatus = isLast && isStreaming ? "streaming" : void 0;
1474
+ const toolCallId = part.toolCallId;
1475
+ const nestedTools = (part.type === "tool-Task" || part.type === "tool-Agent") && toolCallId ? nestedToolsMap.get(toolCallId) || [] : void 0;
1476
+ const occurrence = part.toolCallId ? 0 : unkeyedToolOccurrences.get(part.type) ?? 0;
1477
+ if (!part.toolCallId) unkeyedToolOccurrences.set(part.type, occurrence + 1);
1478
+ elems.push(/* @__PURE__ */ jsx(ToolRendererComponent, {
1479
+ part,
1480
+ nestedTools,
1481
+ chatStatus: chatStreamingStatus,
1482
+ toolRenderers
1483
+ }, getAssistantToolElementKey(messageId, part, occurrence)));
1484
+ i++;
1485
+ continue;
1450
1486
  }
1487
+ i++;
1451
1488
  }
1452
- return null;
1453
- }
1454
- function getFileFromPart(part) {
1455
- if (!isRecord(part)) return null;
1456
- if (part.type !== "file") return null;
1457
- const filePart = part;
1458
- const filename = filePart.filename || filePart.name || filePart.fileName || "Attachment";
1459
- if (filePart.mimeType?.startsWith("image/") ?? false) return null;
1460
- return {
1461
- filename,
1462
- size: filePart.size
1463
- };
1489
+ const text = textChunks.join("\n\n");
1490
+ if (text) elems.push(/* @__PURE__ */ jsx("div", {
1491
+ className: "group/assistant-text text-[14px]",
1492
+ children: /* @__PURE__ */ jsx(TextRendererComponent, {
1493
+ content: text,
1494
+ isStreaming,
1495
+ messageId,
1496
+ onOpenUIAction,
1497
+ onOpenUIArtifactReady,
1498
+ onOpenArtifact,
1499
+ className: "leading-relaxed [&_p]:leading-relaxed"
1500
+ })
1501
+ }, `${messageId}-text-final`));
1502
+ return elems;
1464
1503
  }
1465
- var UserMessage = memo(function UserMessage2({ message, className, enableImagePreview = true }) {
1466
- const [lightboxIndex, setLightboxIndex] = useState(null);
1467
- const text = (message.parts?.filter(isTextPart) ?? []).map((p) => p.text).join("");
1468
- const images = [];
1469
- const files = [];
1470
- for (const part of message.parts ?? []) {
1471
- const imageUrl = getImageUrlFromPart(part);
1472
- if (imageUrl) images.push(imageUrl);
1473
- const file = getFileFromPart(part);
1474
- if (file) files.push(file);
1475
- }
1476
- if (isRecord(message) && Array.isArray(message.experimental_attachments)) {
1477
- for (const att of message.experimental_attachments) if (att.contentType?.startsWith("image/") && att.url) images.push(att.url);
1478
- }
1479
- if (!text && images.length === 0 && files.length === 0) return null;
1480
- const lightboxImages = images.map((url, i) => ({
1481
- id: `${message.id}-img-${i}`,
1482
- url,
1483
- filename: `image-${i + 1}`
1484
- }));
1485
- return /* @__PURE__ */ jsxs("div", {
1486
- className: cn("flex flex-col items-end gap-1", className),
1487
- children: [
1488
- images.length > 0 && images.map((url, i) => /* @__PURE__ */ jsx("div", {
1489
- className: cn("max-w-[200px] rounded-an-message bg-an-foreground/4 p-1.5", enableImagePreview && "cursor-pointer"),
1490
- onClick: enableImagePreview ? () => setLightboxIndex(i) : void 0,
1491
- role: enableImagePreview ? "button" : void 0,
1492
- tabIndex: enableImagePreview ? 0 : void 0,
1493
- onKeyDown: enableImagePreview ? (event) => {
1494
- if (event.key === "Enter" || event.key === " ") {
1495
- event.preventDefault();
1496
- setLightboxIndex(i);
1497
- }
1498
- } : void 0,
1499
- children: /* @__PURE__ */ jsx("img", {
1500
- src: url,
1501
- alt: "attachment",
1502
- className: "block max-h-[120px] max-w-[184px] rounded-an-message-inner object-cover"
1503
- })
1504
- }, url)),
1505
- enableImagePreview && lightboxImages.length > 0 && /* @__PURE__ */ jsx(ImageLightbox, {
1506
- open: lightboxIndex !== null,
1507
- onClose: () => setLightboxIndex(null),
1508
- images: lightboxImages,
1509
- initialIndex: lightboxIndex ?? 0
1510
- }),
1511
- files.length > 0 && /* @__PURE__ */ jsx("div", {
1512
- className: "flex flex-col items-end gap-2",
1513
- children: files.map((file, i) => /* @__PURE__ */ jsx(FileAttachment, {
1514
- id: `${file.filename}-${i}`,
1515
- filename: file.filename,
1516
- size: file.size
1517
- }, `${file.filename}-${file.size}`))
1518
- }),
1519
- text && /* @__PURE__ */ jsx("div", {
1520
- className: "ms-[70px] max-w-[calc(95%-40px)]",
1521
- children: /* @__PURE__ */ jsx("div", {
1522
- className: "rounded-an-message bg-an-user-message-bg px-3.5 py-1.5 text-sm text-an-user-message-text transition-colors",
1523
- children: /* @__PURE__ */ jsx("p", {
1524
- className: "leading-5 wrap-break-word whitespace-pre-wrap",
1525
- children: text
1526
- })
1527
- })
1528
- })
1529
- ]
1504
+ function AssistantParts({ msg, isLast, isStreaming, suppressQuestionTool, ToolRendererComponent, TextRendererComponent, toolRenderers, onOpenUIAction, onOpenUIArtifactReady, onOpenArtifact }) {
1505
+ const parts = useMemo(() => normalizeAssistantToolParts(msg.parts ?? []), [msg.parts]);
1506
+ const elements = useMemo(() => buildAssistantElements(parts, {
1507
+ messageId: msg.id,
1508
+ isLast,
1509
+ isStreaming,
1510
+ suppressQuestionTool,
1511
+ ToolRendererComponent,
1512
+ TextRendererComponent,
1513
+ toolRenderers,
1514
+ onOpenUIAction,
1515
+ onOpenUIArtifactReady,
1516
+ onOpenArtifact
1517
+ }), [
1518
+ parts,
1519
+ msg.id,
1520
+ isLast,
1521
+ isStreaming,
1522
+ suppressQuestionTool,
1523
+ ToolRendererComponent,
1524
+ TextRendererComponent,
1525
+ toolRenderers,
1526
+ onOpenUIAction,
1527
+ onOpenUIArtifactReady,
1528
+ onOpenArtifact
1529
+ ]);
1530
+ if (elements.length > 1) return /* @__PURE__ */ jsx("div", {
1531
+ className: "group/assistant-turn flex flex-col gap-3",
1532
+ children: elements
1530
1533
  });
1531
- });
1532
- var LazyGenerativeTextRenderer = lazy(() => import("./openui-renderer-Cg4RSE9k-7BTJIYZJ.js").then((n) => n.n).then(({ GenerativeTextRenderer }) => ({ default: GenerativeTextRenderer })));
1533
- function PlainTextFallback({ className, content }) {
1534
1534
  return /* @__PURE__ */ jsx("div", {
1535
- className: className ? `${className} whitespace-pre-wrap` : "whitespace-pre-wrap",
1536
- children: content
1537
- });
1538
- }
1539
- function FleetGenerativeTextRenderer(props) {
1540
- if (!useMemo(() => segmentOpenUIContent(props.content).some((segment) => segment.type === "openui"), [props.content])) return /* @__PURE__ */ jsx(LazyMarkdown, {
1541
- className: props.className,
1542
- content: props.content
1543
- });
1544
- return /* @__PURE__ */ jsx(Suspense, {
1545
- fallback: /* @__PURE__ */ jsx(PlainTextFallback, {
1546
- className: props.className,
1547
- content: props.content
1548
- }),
1549
- children: /* @__PURE__ */ jsx(LazyGenerativeTextRenderer, { ...props })
1535
+ className: "group/assistant-turn",
1536
+ children: elements
1550
1537
  });
1551
1538
  }
1552
1539
  function orderedRlmChildren(children, tree) {
@@ -1566,13 +1553,14 @@ function orderedRlmChildren(children, tree) {
1566
1553
  }
1567
1554
  function rlmStatusIcon(status2) {
1568
1555
  if (status2 === "done") return /* @__PURE__ */ jsx(CircleCheck, { className: "size-3.5 text-emerald-600 dark:text-emerald-400" });
1569
- if (status2 === "error") return /* @__PURE__ */ jsx(CircleAlert, { className: "size-3.5 text-destructive" });
1556
+ if (status2 === "error" || status2 === "failed") return /* @__PURE__ */ jsx(CircleAlert, { className: "size-3.5 text-destructive" });
1570
1557
  if (status2 === "cancelled") return /* @__PURE__ */ jsx(CircleX, { className: "size-3.5 text-muted-foreground" });
1558
+ if (status2 === "recovering") return /* @__PURE__ */ jsx(RotateCw, { className: "size-3.5 animate-spin text-amber-500 dark:text-amber-400" });
1571
1559
  return /* @__PURE__ */ jsx(LoaderCircle, { className: "size-3.5 animate-spin text-blue-600 dark:text-blue-400" });
1572
1560
  }
1573
1561
  function FleetSubagentList({ children, tree, className }) {
1574
1562
  const agents = useMemo(() => orderedRlmChildren(children, tree), [children, tree]);
1575
- const active = agents.some((child) => child.status === "queued" || child.status === "running");
1563
+ const active = agents.some((child) => child.status === "queued" || child.status === "running" || child.status === "recovering");
1576
1564
  const [open, setOpen] = useState(active);
1577
1565
  if (agents.length === 0) return null;
1578
1566
  return /* @__PURE__ */ jsxs("section", {
@@ -1769,6 +1757,26 @@ function groupMessages(messages) {
1769
1757
  if (current) turns.push(current);
1770
1758
  return turns;
1771
1759
  }
1760
+ var LazyGenerativeTextRenderer = lazy(() => import("./openui-renderer-c36_GYes-HX46N4M5.js").then((n) => n.n).then(({ GenerativeTextRenderer }) => ({ default: GenerativeTextRenderer })));
1761
+ function PlainTextFallback({ className, content }) {
1762
+ return /* @__PURE__ */ jsx("div", {
1763
+ className: className ? `${className} whitespace-pre-wrap` : "whitespace-pre-wrap",
1764
+ children: content
1765
+ });
1766
+ }
1767
+ function FleetGenerativeTextRenderer(props) {
1768
+ if (!useMemo(() => segmentOpenUIContent(props.content).some((segment) => segment.type === "openui"), [props.content])) return /* @__PURE__ */ jsx(LazyMarkdown, {
1769
+ className: props.className,
1770
+ content: props.content
1771
+ });
1772
+ return /* @__PURE__ */ jsx(Suspense, {
1773
+ fallback: /* @__PURE__ */ jsx(PlainTextFallback, {
1774
+ className: props.className,
1775
+ content: props.content
1776
+ }),
1777
+ children: /* @__PURE__ */ jsx(LazyGenerativeTextRenderer, { ...props })
1778
+ });
1779
+ }
1772
1780
 
1773
1781
  export {
1774
1782
  ChevronLeft,
@@ -1778,16 +1786,16 @@ export {
1778
1786
  MessageScroller,
1779
1787
  Message,
1780
1788
  MessageContent,
1781
- normalizeAssistantToolParts,
1782
- buildAssistantElements,
1783
1789
  FileAttachment,
1784
1790
  UserMessage,
1785
- FleetGenerativeTextRenderer,
1791
+ normalizeAssistantToolParts,
1792
+ buildAssistantElements,
1786
1793
  orderedRlmChildren,
1787
1794
  rlmStatusIcon,
1788
1795
  FleetSubagentList,
1789
1796
  FleetToolTimeline,
1790
- groupMessages
1797
+ groupMessages,
1798
+ FleetGenerativeTextRenderer
1791
1799
  };
1792
1800
  /**
1793
1801
  * @license lucide-react v1.33.0 - ISC