@yourgpt/copilot-sdk 2.1.5-alpha.5 → 2.1.5-alpha.7

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 (37) hide show
  1. package/dist/{ThreadManager-DK46fVl3.d.cts → ThreadManager-B12N-zpq.d.cts} +3 -3
  2. package/dist/{ThreadManager-D7KwT2FJ.d.ts → ThreadManager-BxGOeykx.d.ts} +3 -3
  3. package/dist/{chunk-KGYDGK3U.cjs → chunk-7GWEW2DU.cjs} +24 -8
  4. package/dist/chunk-7GWEW2DU.cjs.map +1 -0
  5. package/dist/{chunk-YLZCTR4O.js → chunk-J5D3AZF6.js} +24 -8
  6. package/dist/chunk-J5D3AZF6.js.map +1 -0
  7. package/dist/{chunk-5UGWLGFS.cjs → chunk-M66XAHSW.cjs} +84 -46
  8. package/dist/chunk-M66XAHSW.cjs.map +1 -0
  9. package/dist/{chunk-LHLVTGIP.cjs → chunk-NPBOKT63.cjs} +297 -96
  10. package/dist/chunk-NPBOKT63.cjs.map +1 -0
  11. package/dist/{chunk-DH6EO6NW.js → chunk-UZHGMDOK.js} +293 -92
  12. package/dist/chunk-UZHGMDOK.js.map +1 -0
  13. package/dist/{chunk-ZAOTYA5L.js → chunk-YHW6JZEF.js} +48 -11
  14. package/dist/chunk-YHW6JZEF.js.map +1 -0
  15. package/dist/core/index.cjs +72 -72
  16. package/dist/core/index.d.cts +2 -2
  17. package/dist/core/index.d.ts +2 -2
  18. package/dist/core/index.js +1 -1
  19. package/dist/experimental/index.cjs +3 -3
  20. package/dist/experimental/index.js +2 -2
  21. package/dist/react/index.cjs +70 -66
  22. package/dist/react/index.d.cts +207 -5
  23. package/dist/react/index.d.ts +207 -5
  24. package/dist/react/index.js +3 -3
  25. package/dist/ui/index.cjs +1178 -228
  26. package/dist/ui/index.cjs.map +1 -1
  27. package/dist/ui/index.d.cts +119 -7
  28. package/dist/ui/index.d.ts +119 -7
  29. package/dist/ui/index.js +1173 -227
  30. package/dist/ui/index.js.map +1 -1
  31. package/package.json +1 -1
  32. package/dist/chunk-5UGWLGFS.cjs.map +0 -1
  33. package/dist/chunk-DH6EO6NW.js.map +0 -1
  34. package/dist/chunk-KGYDGK3U.cjs.map +0 -1
  35. package/dist/chunk-LHLVTGIP.cjs.map +0 -1
  36. package/dist/chunk-YLZCTR4O.js.map +0 -1
  37. package/dist/chunk-ZAOTYA5L.js.map +0 -1
package/dist/ui/index.js CHANGED
@@ -1,9 +1,9 @@
1
- import { useThreadManager } from '../chunk-ZAOTYA5L.js';
1
+ import { useThreadManager } from '../chunk-YHW6JZEF.js';
2
2
  import { DEFAULT_MCP_UI_SANDBOX, parseMCPUIMessage } from '../chunk-G4SF2PNQ.js';
3
3
  import { cn, Loader, TextShimmerLoader } from '../chunk-TXQ37MAO.js';
4
4
  export { Loader, cn } from '../chunk-TXQ37MAO.js';
5
- import { useCopilot } from '../chunk-DH6EO6NW.js';
6
- import { createServerAdapter } from '../chunk-YLZCTR4O.js';
5
+ import { useCopilot } from '../chunk-UZHGMDOK.js';
6
+ import { createLocalStorageAdapter, createServerAdapter } from '../chunk-J5D3AZF6.js';
7
7
  import '../chunk-EWVQWTNV.js';
8
8
  import '../chunk-VNLLW3ZI.js';
9
9
  import '../chunk-533K2Z7C.js';
@@ -19,7 +19,7 @@ import '../chunk-MNDGIW47.js';
19
19
  import '../chunk-VD74IPKB.js';
20
20
  import '../chunk-DGUM43GV.js';
21
21
  import * as React19 from 'react';
22
- import React19__default, { memo, createContext, useState, useRef, useId, useCallback, useMemo, useLayoutEffect, useContext, useEffect } from 'react';
22
+ import React19__default, { memo, createContext, useState, useRef, useId, useCallback, useMemo, useLayoutEffect, useContext, useReducer, useEffect } from 'react';
23
23
  import { Streamdown } from 'streamdown';
24
24
  import { code } from '@streamdown/code';
25
25
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
@@ -31,6 +31,7 @@ import * as AvatarPrimitive from '@radix-ui/react-avatar';
31
31
  import * as HoverCardPrimitive from '@radix-ui/react-hover-card';
32
32
  import { Globe, ChevronUp, ChevronDown, ExternalLink } from 'lucide-react';
33
33
  import { Popover as Popover$1 } from '@base-ui/react/popover';
34
+ import * as ReactDOM from 'react-dom';
34
35
 
35
36
  var createHeading = (Tag) => {
36
37
  const HeadingComponent = ({
@@ -3453,10 +3454,14 @@ function ThreadPicker({
3453
3454
  newButtonClassName
3454
3455
  }) {
3455
3456
  const [isOpen, setIsOpen] = React19.useState(false);
3457
+ const [displayedThreads, setDisplayedThreads] = React19.useState(threads);
3458
+ React19.useEffect(() => {
3459
+ setDisplayedThreads(threads);
3460
+ }, [threads]);
3456
3461
  const selectedThread = React19.useMemo(() => {
3457
3462
  if (!value) return null;
3458
- return threads.find((t) => t.id === value) ?? null;
3459
- }, [value, threads]);
3463
+ return displayedThreads.find((t) => t.id === value) ?? null;
3464
+ }, [value, displayedThreads]);
3460
3465
  const handleSelect = (threadId) => {
3461
3466
  onSelect?.(threadId);
3462
3467
  setIsOpen(false);
@@ -3465,6 +3470,10 @@ function ThreadPicker({
3465
3470
  onNewThread?.();
3466
3471
  setIsOpen(false);
3467
3472
  };
3473
+ const handleDelete = (threadId) => {
3474
+ setDisplayedThreads((prev) => prev.filter((t) => t.id !== threadId));
3475
+ onDeleteThread?.(threadId);
3476
+ };
3468
3477
  return /* @__PURE__ */ jsxs(Popover, { open: isOpen, onOpenChange: setIsOpen, children: [
3469
3478
  /* @__PURE__ */ jsxs(
3470
3479
  PopoverTrigger,
@@ -3517,7 +3526,7 @@ function ThreadPicker({
3517
3526
  ]
3518
3527
  }
3519
3528
  ),
3520
- threads.length > 0 ? threads.map((thread) => /* @__PURE__ */ jsxs(
3529
+ displayedThreads.length > 0 ? displayedThreads.map((thread) => /* @__PURE__ */ jsxs(
3521
3530
  "div",
3522
3531
  {
3523
3532
  className: cn(
@@ -3553,7 +3562,7 @@ function ThreadPicker({
3553
3562
  type: "button",
3554
3563
  onClick: (e) => {
3555
3564
  e.stopPropagation();
3556
- onDeleteThread(thread.id);
3565
+ handleDelete(thread.id);
3557
3566
  },
3558
3567
  className: "flex-shrink-0 p-1 rounded opacity-0 group-hover:opacity-100 text-muted-foreground hover:text-destructive hover:bg-destructive/10 transition-all focus:opacity-100 focus:outline-none",
3559
3568
  "aria-label": "Delete thread",
@@ -4555,17 +4564,37 @@ function DefaultMessage({
4555
4564
  )
4556
4565
  ] })
4557
4566
  ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
4558
- message.content && /* @__PURE__ */ jsxs("div", { className: "relative", children: [
4559
- /* @__PURE__ */ jsx(
4560
- MessageContent,
4567
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
4568
+ /* @__PURE__ */ jsxs(
4569
+ "div",
4561
4570
  {
4562
4571
  className: cn(
4563
- "csdk-message-user rounded-lg px-4 py-2 bg-primary text-primary-foreground",
4572
+ "csdk-message-user rounded-2xl overflow-hidden bg-primary text-primary-foreground",
4573
+ hasAttachments && "p-[3px]",
4574
+ hasAttachments && !message.content && "max-w-[260px]",
4575
+ hasAttachments && message.content && "max-w-[280px]",
4576
+ !hasAttachments && "",
4564
4577
  userMessageClassName
4565
4578
  ),
4566
- markdown: true,
4567
- size,
4568
- children: message.content
4579
+ children: [
4580
+ hasAttachments && /* @__PURE__ */ jsx(
4581
+ MessageMedia,
4582
+ {
4583
+ attachments: message.attachments,
4584
+ hasText: !!message.content,
4585
+ align: "end"
4586
+ }
4587
+ ),
4588
+ message.content && /* @__PURE__ */ jsx(
4589
+ MessageContent,
4590
+ {
4591
+ className: cn("px-4 py-2"),
4592
+ markdown: true,
4593
+ size,
4594
+ children: message.content
4595
+ }
4596
+ )
4597
+ ]
4569
4598
  }
4570
4599
  ),
4571
4600
  showEditBtn && /* @__PURE__ */ jsx(
@@ -4579,7 +4608,7 @@ function DefaultMessage({
4579
4608
  "size-6 flex items-center justify-center rounded-full",
4580
4609
  "text-muted-foreground bg-background border border-border shadow-sm",
4581
4610
  "opacity-0 group-hover/user-msg:opacity-100 transition-opacity",
4582
- "hover:text-foreground hover:bg-muted"
4611
+ "hover:text-foreground hover:bg-muted cursor-pointer"
4583
4612
  ),
4584
4613
  children: /* @__PURE__ */ jsxs(
4585
4614
  "svg",
@@ -4601,7 +4630,6 @@ function DefaultMessage({
4601
4630
  }
4602
4631
  )
4603
4632
  ] }),
4604
- hasAttachments && /* @__PURE__ */ jsx("div", { className: "mt-2 flex flex-wrap gap-2 justify-end", children: message.attachments.map((attachment, index) => /* @__PURE__ */ jsx(AttachmentPreview, { attachment }, index)) }),
4605
4633
  showBranchNav && /* @__PURE__ */ jsx(
4606
4634
  BranchNavigator,
4607
4635
  {
@@ -4873,7 +4901,14 @@ function DefaultMessage({
4873
4901
  tool.id
4874
4902
  );
4875
4903
  }) }),
4876
- message.attachments && message.attachments.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-2 flex flex-wrap gap-2", children: message.attachments.map((attachment, index) => /* @__PURE__ */ jsx(AttachmentPreview, { attachment }, index)) }),
4904
+ message.attachments && message.attachments.length > 0 && /* @__PURE__ */ jsx("div", { className: "csdk-assistant-attachments mt-2", children: /* @__PURE__ */ jsx(
4905
+ MessageMedia,
4906
+ {
4907
+ attachments: message.attachments,
4908
+ hasText: !!message.content,
4909
+ align: "start"
4910
+ }
4911
+ ) }),
4877
4912
  shouldShowSources && /* @__PURE__ */ jsx(
4878
4913
  SourceGroup,
4879
4914
  {
@@ -4900,83 +4935,379 @@ function DefaultMessage({
4900
4935
  ] })
4901
4936
  ] });
4902
4937
  }
4903
- function AttachmentPreview({ attachment }) {
4904
- const [expanded, setExpanded] = React19.useState(false);
4905
- if (attachment.type !== "image") {
4906
- return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 rounded-lg border bg-muted/50 px-3 py-2 text-sm", children: [
4907
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: attachment.type }),
4908
- /* @__PURE__ */ jsx("span", { children: attachment.filename || "Attachment" })
4909
- ] });
4910
- }
4911
- let src;
4912
- if (attachment.url) {
4913
- src = attachment.url;
4914
- } else if (attachment.data) {
4915
- src = attachment.data.startsWith("data:") ? attachment.data : `data:${attachment.mimeType};base64,${attachment.data}`;
4916
- } else {
4917
- return null;
4938
+ function getAttachmentSrc(attachment) {
4939
+ if (attachment.url) return attachment.url;
4940
+ if (attachment.data) {
4941
+ return attachment.data.startsWith("data:") ? attachment.data : `data:${attachment.mimeType};base64,${attachment.data}`;
4918
4942
  }
4943
+ return null;
4944
+ }
4945
+ function ImageLightbox({
4946
+ src,
4947
+ alt,
4948
+ onClose
4949
+ }) {
4950
+ const [closing, setClosing] = React19.useState(false);
4951
+ const backdropRef = React19.useRef(null);
4952
+ const handleClose = React19.useCallback(() => {
4953
+ setClosing(true);
4954
+ setTimeout(onClose, 180);
4955
+ }, [onClose]);
4956
+ React19.useEffect(() => {
4957
+ const handler = (e) => {
4958
+ if (e.key === "Escape") handleClose();
4959
+ };
4960
+ document.addEventListener("keydown", handler);
4961
+ return () => document.removeEventListener("keydown", handler);
4962
+ }, [handleClose]);
4963
+ React19.useEffect(() => {
4964
+ const prev = document.body.style.overflow;
4965
+ document.body.style.overflow = "hidden";
4966
+ return () => {
4967
+ document.body.style.overflow = prev;
4968
+ };
4969
+ }, []);
4970
+ const portal = /* @__PURE__ */ jsxs(
4971
+ "div",
4972
+ {
4973
+ ref: backdropRef,
4974
+ className: "csdk-lightbox csdk-lightbox-backdrop fixed inset-0 z-[9999] flex items-center justify-center cursor-zoom-out",
4975
+ onClick: handleClose,
4976
+ style: {
4977
+ animation: closing ? "csdk-lightbox-backdrop-out 180ms ease-in forwards" : "csdk-lightbox-backdrop-in 200ms ease-out forwards"
4978
+ },
4979
+ children: [
4980
+ /* @__PURE__ */ jsx(
4981
+ "div",
4982
+ {
4983
+ className: "csdk-lightbox-scrim absolute inset-0",
4984
+ style: {
4985
+ backgroundColor: "rgba(0, 0, 0, 0.88)",
4986
+ backdropFilter: "blur(12px)",
4987
+ WebkitBackdropFilter: "blur(12px)",
4988
+ animation: closing ? "csdk-lightbox-fade-out 180ms ease-in forwards" : "csdk-lightbox-fade-in 200ms ease-out forwards"
4989
+ }
4990
+ }
4991
+ ),
4992
+ /* @__PURE__ */ jsxs(
4993
+ "div",
4994
+ {
4995
+ className: "csdk-lightbox-content relative z-10 max-w-[90vw] max-h-[90vh]",
4996
+ style: {
4997
+ animation: closing ? "csdk-lightbox-img-out 180ms ease-in forwards" : "csdk-lightbox-img-in 220ms cubic-bezier(0.22, 1, 0.36, 1) forwards"
4998
+ },
4999
+ onClick: (e) => e.stopPropagation(),
5000
+ children: [
5001
+ /* @__PURE__ */ jsx(
5002
+ "img",
5003
+ {
5004
+ src,
5005
+ alt,
5006
+ className: "csdk-lightbox-image max-w-full max-h-[90vh] object-contain rounded-xl",
5007
+ style: { boxShadow: "0 25px 50px -12px rgba(0,0,0,0.5)" },
5008
+ draggable: false
5009
+ }
5010
+ ),
5011
+ /* @__PURE__ */ jsx(
5012
+ "button",
5013
+ {
5014
+ type: "button",
5015
+ className: "csdk-lightbox-close absolute -top-3 -right-3 size-8 flex items-center justify-center rounded-full shadow-lg transition-[background,transform] duration-150 cursor-pointer active:scale-95",
5016
+ style: { backgroundColor: "rgba(255,255,255,0.9)" },
5017
+ onMouseEnter: (e) => {
5018
+ e.currentTarget.style.backgroundColor = "rgba(255,255,255,1)";
5019
+ },
5020
+ onMouseLeave: (e) => {
5021
+ e.currentTarget.style.backgroundColor = "rgba(255,255,255,0.9)";
5022
+ },
5023
+ onClick: handleClose,
5024
+ children: /* @__PURE__ */ jsxs(
5025
+ "svg",
5026
+ {
5027
+ className: "size-4",
5028
+ style: { color: "#333" },
5029
+ fill: "none",
5030
+ viewBox: "0 0 24 24",
5031
+ stroke: "currentColor",
5032
+ strokeWidth: 2.5,
5033
+ strokeLinecap: "round",
5034
+ strokeLinejoin: "round",
5035
+ children: [
5036
+ /* @__PURE__ */ jsx("path", { d: "M18 6 6 18" }),
5037
+ /* @__PURE__ */ jsx("path", { d: "m6 6 12 12" })
5038
+ ]
5039
+ }
5040
+ )
5041
+ }
5042
+ )
5043
+ ]
5044
+ }
5045
+ ),
5046
+ /* @__PURE__ */ jsx("style", { children: `
5047
+ @keyframes csdk-lightbox-fade-in { from { opacity: 0; } to { opacity: 1; } }
5048
+ @keyframes csdk-lightbox-fade-out { from { opacity: 1; } to { opacity: 0; } }
5049
+ @keyframes csdk-lightbox-img-in { from { opacity: 0; transform: scale(0.92); } to { opacity: 1; transform: scale(1); } }
5050
+ @keyframes csdk-lightbox-img-out { from { opacity: 1; transform: scale(1); } to { opacity: 0; transform: scale(0.95); } }
5051
+ @keyframes csdk-lightbox-backdrop-in { from { opacity: 0; } to { opacity: 1; } }
5052
+ @keyframes csdk-lightbox-backdrop-out { from { opacity: 1; } to { opacity: 0; } }
5053
+ ` })
5054
+ ]
5055
+ }
5056
+ );
5057
+ return typeof document !== "undefined" ? ReactDOM.createPortal(portal, document.body) : null;
5058
+ }
5059
+ function ImageThumb({
5060
+ src,
5061
+ alt,
5062
+ className
5063
+ }) {
5064
+ const [expanded, setExpanded] = React19.useState(false);
4919
5065
  return /* @__PURE__ */ jsxs(Fragment, { children: [
4920
5066
  /* @__PURE__ */ jsx(
4921
5067
  "button",
4922
5068
  {
4923
5069
  type: "button",
4924
5070
  onClick: () => setExpanded(true),
4925
- className: "relative rounded-lg overflow-hidden border bg-muted/50 hover:opacity-90 transition-opacity",
5071
+ className: cn(
5072
+ "csdk-attachment-image relative overflow-hidden cursor-zoom-in",
5073
+ "transition-[opacity,transform] duration-150 hover:opacity-90 active:scale-[0.98]",
5074
+ className
5075
+ ),
5076
+ style: { backgroundColor: "#000" },
4926
5077
  children: /* @__PURE__ */ jsx(
4927
5078
  "img",
4928
5079
  {
4929
5080
  src,
4930
- alt: attachment.filename || "Image",
4931
- className: "max-w-[200px] max-h-[150px] object-cover"
5081
+ alt,
5082
+ className: "w-full h-full object-cover",
5083
+ loading: "lazy",
5084
+ draggable: false
4932
5085
  }
4933
5086
  )
4934
5087
  }
4935
5088
  ),
4936
- expanded && /* @__PURE__ */ jsx(
5089
+ expanded && /* @__PURE__ */ jsx(ImageLightbox, { src, alt, onClose: () => setExpanded(false) })
5090
+ ] });
5091
+ }
5092
+ function FileCard({ attachment }) {
5093
+ const isPdfFile = isPdf(attachment);
5094
+ const isAudio = attachment.type === "audio";
5095
+ const isVideo = attachment.type === "video";
5096
+ const accent = isPdfFile ? { color: "#ef4444", bg: "rgba(239,68,68,0.12)" } : isAudio ? { color: "#10b981", bg: "rgba(16,185,129,0.12)" } : isVideo ? { color: "#8b5cf6", bg: "rgba(139,92,246,0.12)" } : { color: "#3b82f6", bg: "rgba(59,130,246,0.12)" };
5097
+ const label = isPdfFile ? "PDF" : attachment.mimeType?.split("/")[1]?.toUpperCase() || attachment.type?.toUpperCase() || "FILE";
5098
+ const filename = attachment.filename || "Attachment";
5099
+ const href = attachment.url || (attachment.data?.startsWith("data:") ? attachment.data : null);
5100
+ const cssClass = isPdfFile ? "csdk-attachment-pdf" : "csdk-attachment-file";
5101
+ return /* @__PURE__ */ jsxs(
5102
+ "a",
5103
+ {
5104
+ href: href ?? void 0,
5105
+ target: "_blank",
5106
+ rel: "noopener noreferrer",
5107
+ download: isPdfFile ? void 0 : attachment.filename,
5108
+ className: cn(
5109
+ cssClass,
5110
+ "flex items-center gap-2 rounded-lg min-w-0 w-full",
5111
+ "px-2 py-1.5 cursor-pointer transition-opacity duration-150 hover:opacity-80",
5112
+ "no-underline"
5113
+ ),
5114
+ style: { backgroundColor: "rgba(255,255,255,0.92)", color: "#1a1a1a" },
5115
+ onClick: (e) => {
5116
+ if (!href) e.preventDefault();
5117
+ },
5118
+ children: [
5119
+ /* @__PURE__ */ jsx(
5120
+ "div",
5121
+ {
5122
+ className: "size-8 rounded-md flex items-center justify-center shrink-0",
5123
+ style: { backgroundColor: accent.bg },
5124
+ children: /* @__PURE__ */ jsx(
5125
+ "svg",
5126
+ {
5127
+ className: "size-4",
5128
+ viewBox: "0 0 24 24",
5129
+ fill: "none",
5130
+ stroke: accent.color,
5131
+ strokeWidth: 1.8,
5132
+ strokeLinecap: "round",
5133
+ strokeLinejoin: "round",
5134
+ children: isPdfFile || !isAudio && !isVideo ? /* @__PURE__ */ jsxs(Fragment, { children: [
5135
+ /* @__PURE__ */ jsx("path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }),
5136
+ /* @__PURE__ */ jsx("path", { d: "M14 2v4a2 2 0 0 0 2 2h4" })
5137
+ ] }) : isAudio ? /* @__PURE__ */ jsxs(Fragment, { children: [
5138
+ /* @__PURE__ */ jsx("path", { d: "M9 18V5l12-2v13" }),
5139
+ /* @__PURE__ */ jsx("circle", { cx: "6", cy: "18", r: "3" }),
5140
+ /* @__PURE__ */ jsx("circle", { cx: "18", cy: "16", r: "3" })
5141
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
5142
+ /* @__PURE__ */ jsx("path", { d: "m16 13 5.223 3.482a.5.5 0 0 0 .777-.416V7.934a.5.5 0 0 0-.777-.416L16 11" }),
5143
+ /* @__PURE__ */ jsx("rect", { width: "14", height: "12", x: "2", y: "6", rx: "2" })
5144
+ ] })
5145
+ }
5146
+ )
5147
+ }
5148
+ ),
5149
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
5150
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] font-medium truncate leading-tight", children: filename }),
5151
+ /* @__PURE__ */ jsx(
5152
+ "p",
5153
+ {
5154
+ className: "text-[9px] font-semibold uppercase tracking-wider leading-tight mt-0.5",
5155
+ style: { color: accent.color },
5156
+ children: label
5157
+ }
5158
+ )
5159
+ ] }),
5160
+ href && /* @__PURE__ */ jsx(
5161
+ "div",
5162
+ {
5163
+ className: "size-6 rounded-md flex items-center justify-center shrink-0",
5164
+ style: { backgroundColor: "rgba(0,0,0,0.05)" },
5165
+ children: /* @__PURE__ */ jsxs(
5166
+ "svg",
5167
+ {
5168
+ className: "size-3",
5169
+ viewBox: "0 0 24 24",
5170
+ fill: "none",
5171
+ stroke: "currentColor",
5172
+ strokeWidth: 2.5,
5173
+ strokeLinecap: "round",
5174
+ strokeLinejoin: "round",
5175
+ style: { opacity: 0.35 },
5176
+ children: [
5177
+ /* @__PURE__ */ jsx("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
5178
+ /* @__PURE__ */ jsx("polyline", { points: "7 10 12 15 17 10" }),
5179
+ /* @__PURE__ */ jsx("line", { x1: "12", x2: "12", y1: "15", y2: "3" })
5180
+ ]
5181
+ }
5182
+ )
5183
+ }
5184
+ )
5185
+ ]
5186
+ }
5187
+ );
5188
+ }
5189
+ function ImageGrid({
5190
+ images,
5191
+ bubbleRadius
5192
+ }) {
5193
+ const srcs = images.map((img) => getAttachmentSrc(img)).filter(Boolean);
5194
+ if (srcs.length === 0) return null;
5195
+ const innerRadius = bubbleRadius ? `calc(${bubbleRadius} - 2px)` : "0.875rem";
5196
+ if (srcs.length === 1) {
5197
+ return /* @__PURE__ */ jsx(
5198
+ "div",
5199
+ {
5200
+ className: "csdk-attachment-grid",
5201
+ style: { borderRadius: innerRadius, overflow: "hidden" },
5202
+ children: /* @__PURE__ */ jsx(
5203
+ ImageThumb,
5204
+ {
5205
+ src: srcs[0],
5206
+ alt: images[0].filename || "Image",
5207
+ className: "w-full"
5208
+ }
5209
+ )
5210
+ }
5211
+ );
5212
+ }
5213
+ if (srcs.length === 2) {
5214
+ return /* @__PURE__ */ jsx(
4937
5215
  "div",
4938
5216
  {
4939
- className: "csdk-image-backdrop fixed inset-0 z-50 flex items-center justify-center bg-black/80",
4940
- onClick: () => setExpanded(false),
4941
- children: /* @__PURE__ */ jsxs("div", { className: "relative max-w-[90vw] max-h-[90vh]", children: [
5217
+ className: "csdk-attachment-grid grid grid-cols-2 gap-[2px]",
5218
+ style: { borderRadius: innerRadius, overflow: "hidden" },
5219
+ children: srcs.map((src, i) => /* @__PURE__ */ jsx(
5220
+ ImageThumb,
5221
+ {
5222
+ src,
5223
+ alt: images[i].filename || "Image",
5224
+ className: "aspect-square"
5225
+ },
5226
+ i
5227
+ ))
5228
+ }
5229
+ );
5230
+ }
5231
+ if (srcs.length === 3) {
5232
+ return /* @__PURE__ */ jsxs(
5233
+ "div",
5234
+ {
5235
+ className: "csdk-attachment-grid grid grid-cols-2 gap-[2px]",
5236
+ style: { borderRadius: innerRadius, overflow: "hidden" },
5237
+ children: [
4942
5238
  /* @__PURE__ */ jsx(
4943
- "img",
5239
+ ImageThumb,
4944
5240
  {
4945
- src,
4946
- alt: attachment.filename || "Image (expanded)",
4947
- className: "max-w-full max-h-full object-contain rounded-lg"
5241
+ src: srcs[0],
5242
+ alt: images[0].filename || "Image",
5243
+ className: "col-span-2 max-h-[180px] min-h-[100px]"
4948
5244
  }
4949
5245
  ),
4950
5246
  /* @__PURE__ */ jsx(
4951
- "button",
5247
+ ImageThumb,
4952
5248
  {
4953
- type: "button",
4954
- className: "csdk-image-close absolute top-2 right-2 bg-white/90 rounded-full p-2 hover:bg-white transition-colors",
4955
- onClick: (e) => {
4956
- e.stopPropagation();
4957
- setExpanded(false);
4958
- },
4959
- children: /* @__PURE__ */ jsx(
4960
- "svg",
4961
- {
4962
- className: "w-4 h-4",
4963
- fill: "none",
4964
- viewBox: "0 0 24 24",
4965
- stroke: "currentColor",
4966
- strokeWidth: 2,
4967
- children: /* @__PURE__ */ jsx(
4968
- "path",
4969
- {
4970
- strokeLinecap: "round",
4971
- strokeLinejoin: "round",
4972
- d: "M6 18L18 6M6 6l12 12"
4973
- }
4974
- )
4975
- }
4976
- )
5249
+ src: srcs[1],
5250
+ alt: images[1].filename || "Image",
5251
+ className: "aspect-square"
5252
+ }
5253
+ ),
5254
+ /* @__PURE__ */ jsx(
5255
+ ImageThumb,
5256
+ {
5257
+ src: srcs[2],
5258
+ alt: images[2].filename || "Image",
5259
+ className: "aspect-square"
4977
5260
  }
4978
5261
  )
4979
- ] })
5262
+ ]
5263
+ }
5264
+ );
5265
+ }
5266
+ const showOverlay = srcs.length > 4;
5267
+ const gridSrcs = srcs.slice(0, 4);
5268
+ return /* @__PURE__ */ jsx(
5269
+ "div",
5270
+ {
5271
+ className: "csdk-attachment-grid grid grid-cols-2 gap-[2px]",
5272
+ style: { borderRadius: innerRadius, overflow: "hidden" },
5273
+ children: gridSrcs.map((src, i) => /* @__PURE__ */ jsxs("div", { className: "relative aspect-square", children: [
5274
+ /* @__PURE__ */ jsx(
5275
+ ImageThumb,
5276
+ {
5277
+ src,
5278
+ alt: images[i].filename || "Image",
5279
+ className: "w-full h-full"
5280
+ }
5281
+ ),
5282
+ i === 3 && showOverlay && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-black/50 flex items-center justify-center pointer-events-none", children: /* @__PURE__ */ jsxs("span", { className: "text-white text-lg font-semibold", children: [
5283
+ "+",
5284
+ srcs.length - 4
5285
+ ] }) })
5286
+ ] }, i))
5287
+ }
5288
+ );
5289
+ }
5290
+ function isPdf(a) {
5291
+ return a.mimeType === "application/pdf" || a.filename?.toLowerCase().endsWith(".pdf") === true;
5292
+ }
5293
+ function MessageMedia({
5294
+ attachments,
5295
+ hasText,
5296
+ align = "end"
5297
+ }) {
5298
+ const images = attachments.filter((a) => a.type === "image");
5299
+ const pdfs = attachments.filter((a) => isPdf(a));
5300
+ const files = attachments.filter((a) => a.type !== "image" && !isPdf(a));
5301
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
5302
+ images.length > 0 && /* @__PURE__ */ jsx("div", { className: cn("csdk-attachment-images", hasText ? "mb-0" : ""), children: /* @__PURE__ */ jsx(ImageGrid, { images, bubbleRadius: "0.5rem" }) }),
5303
+ (pdfs.length > 0 || files.length > 0) && /* @__PURE__ */ jsx(
5304
+ "div",
5305
+ {
5306
+ className: cn(
5307
+ "csdk-attachment-files flex flex-col gap-1",
5308
+ hasText || images.length > 0 ? "px-1.5 pb-1.5 pt-1" : "p-1.5"
5309
+ ),
5310
+ children: [...pdfs, ...files].map((file, i) => /* @__PURE__ */ jsx(FileCard, { attachment: file }, i))
4980
5311
  }
4981
5312
  )
4982
5313
  ] });
@@ -5571,7 +5902,8 @@ function ChatComponent({
5571
5902
  allowedFileTypes = DEFAULT_ALLOWED_TYPES2,
5572
5903
  attachmentsEnabled = true,
5573
5904
  attachmentsDisabledTooltip = "Attachments not supported by this model",
5574
- processAttachment: processAttachmentProp,
5905
+ upload: uploadProp,
5906
+ processAttachment: deprecatedProcessAttachment,
5575
5907
  // Suggestions
5576
5908
  suggestions = [],
5577
5909
  onSuggestionClick,
@@ -5670,8 +6002,34 @@ function ChatComponent({
5670
6002
  ]);
5671
6003
  try {
5672
6004
  let attachment;
5673
- if (processAttachmentProp) {
5674
- attachment = await processAttachmentProp(file);
6005
+ const uploader = uploadProp ?? deprecatedProcessAttachment;
6006
+ if (typeof uploader === "function") {
6007
+ attachment = await uploader(file);
6008
+ } else if (uploader) {
6009
+ const config = typeof uploader === "string" ? { url: uploader } : uploader;
6010
+ const extraHeaders = typeof config.headers === "function" ? config.headers() : config.headers;
6011
+ const extraBody = typeof config.body === "function" ? config.body() : config.body;
6012
+ const data = await fileToBase642(file);
6013
+ const res = await fetch(config.url, {
6014
+ method: "POST",
6015
+ headers: { "Content-Type": "application/json", ...extraHeaders },
6016
+ body: JSON.stringify({
6017
+ data,
6018
+ mimeType: file.type,
6019
+ filename: file.name,
6020
+ ...extraBody
6021
+ })
6022
+ });
6023
+ if (!res.ok) throw new Error(`Upload failed: ${res.status}`);
6024
+ const result = await res.json();
6025
+ const url = result.url ?? result.data?.url;
6026
+ if (!url) throw new Error("Upload returned no URL");
6027
+ attachment = {
6028
+ type: getAttachmentType2(file.type),
6029
+ url,
6030
+ mimeType: file.type,
6031
+ filename: file.name
6032
+ };
5675
6033
  } else {
5676
6034
  const data = await fileToBase642(file);
5677
6035
  attachment = {
@@ -5699,7 +6057,13 @@ function ChatComponent({
5699
6057
  }
5700
6058
  }
5701
6059
  },
5702
- [attachmentsEnabled, maxFileSize, isFileTypeAllowed, processAttachmentProp]
6060
+ [
6061
+ attachmentsEnabled,
6062
+ maxFileSize,
6063
+ isFileTypeAllowed,
6064
+ uploadProp,
6065
+ deprecatedProcessAttachment
6066
+ ]
5703
6067
  );
5704
6068
  const handleInputChange = useCallback(
5705
6069
  (e) => {
@@ -5843,7 +6207,7 @@ function ChatComponent({
5843
6207
  onDragLeave: handleDragLeave,
5844
6208
  onDrop: handleDrop,
5845
6209
  children: [
5846
- isDragging && /* @__PURE__ */ jsx("div", { className: "csdk-dropzone-overlay absolute inset-0 z-50 bg-primary/10 border-2 border-dashed border-primary flex items-center justify-center", children: /* @__PURE__ */ jsx("div", { className: "text-primary font-medium text-lg", children: "Drop files here" }) }),
6210
+ isDragging && !renderInput && /* @__PURE__ */ jsx("div", { className: "csdk-dropzone-overlay absolute inset-0 z-50 bg-primary/10 border-2 border-dashed border-primary flex items-center justify-center", children: /* @__PURE__ */ jsx("div", { className: "text-primary font-medium text-lg", children: "Drop files here" }) }),
5847
6211
  showHeader && (renderHeader ? renderHeader() : /* @__PURE__ */ jsx(
5848
6212
  ChatHeader,
5849
6213
  {
@@ -5877,7 +6241,7 @@ function ChatComponent({
5877
6241
  attachmentsDisabledTooltip,
5878
6242
  maxFileSize,
5879
6243
  allowedFileTypes,
5880
- processAttachment: processAttachmentProp
6244
+ processAttachment: typeof uploadProp === "function" ? uploadProp : deprecatedProcessAttachment
5881
6245
  }
5882
6246
  )
5883
6247
  ) : null,
@@ -6316,6 +6680,106 @@ function ToolExecutionMessage({
6316
6680
  ] })
6317
6681
  ] });
6318
6682
  }
6683
+ var INITIAL_STATE = {
6684
+ phase: "idle",
6685
+ threadId: null,
6686
+ lastSnapshot: "",
6687
+ initialized: false
6688
+ };
6689
+ function threadReducer(state, action) {
6690
+ switch (action.type) {
6691
+ case "FIRST_RESPONSE_COMPLETE":
6692
+ if (state.phase !== "idle") return state;
6693
+ return { ...state, phase: "awaiting_server_id", initialized: true };
6694
+ case "SERVER_ID_RECEIVED":
6695
+ if (state.phase !== "awaiting_server_id") return state;
6696
+ return { ...state, phase: "creating" };
6697
+ case "CREATE_WITH_LOCAL_ID":
6698
+ if (state.phase !== "awaiting_server_id") return state;
6699
+ return { ...state, phase: "creating" };
6700
+ case "THREAD_CREATED":
6701
+ return {
6702
+ ...state,
6703
+ phase: "active",
6704
+ threadId: action.threadId,
6705
+ lastSnapshot: action.snapshot,
6706
+ initialized: true
6707
+ };
6708
+ case "MESSAGES_SAVED":
6709
+ if (state.phase !== "active") return state;
6710
+ return { ...state, lastSnapshot: action.snapshot };
6711
+ case "START_SWITCH":
6712
+ if (state.phase !== "active" && state.phase !== "idle") return state;
6713
+ return { ...state, phase: "switching" };
6714
+ case "SWITCH_COMPLETE":
6715
+ return {
6716
+ ...state,
6717
+ phase: "active",
6718
+ threadId: action.threadId,
6719
+ lastSnapshot: action.snapshot
6720
+ };
6721
+ case "NEW_THREAD":
6722
+ return {
6723
+ ...INITIAL_STATE,
6724
+ initialized: true
6725
+ };
6726
+ case "RESTORE_START":
6727
+ if (state.initialized) return state;
6728
+ if (state.phase === "creating" || state.phase === "awaiting_server_id")
6729
+ return state;
6730
+ return { ...state, phase: "restoring" };
6731
+ case "RESTORE_COMPLETE":
6732
+ return {
6733
+ ...state,
6734
+ phase: "active",
6735
+ threadId: action.threadId,
6736
+ lastSnapshot: action.snapshot,
6737
+ initialized: true
6738
+ };
6739
+ case "SKIP_RESTORE":
6740
+ return { ...state, initialized: true };
6741
+ default:
6742
+ return state;
6743
+ }
6744
+ }
6745
+ function getMessageSnapshot(msgs) {
6746
+ return msgs.map((m) => {
6747
+ const preview = (m.content ?? "").slice(0, 20);
6748
+ return `${m.id}:${preview}:${m.content?.length ?? 0}`;
6749
+ }).join("|");
6750
+ }
6751
+ function convertToCore(msgs) {
6752
+ return msgs.map((m) => ({
6753
+ id: m.id,
6754
+ role: m.role,
6755
+ content: m.content,
6756
+ created_at: m.createdAt,
6757
+ tool_calls: m.toolCalls,
6758
+ tool_call_id: m.toolCallId,
6759
+ parent_id: m.parentId,
6760
+ children_ids: m.childrenIds,
6761
+ metadata: {
6762
+ ...m.metadata,
6763
+ attachments: m.attachments,
6764
+ thinking: m.thinking
6765
+ }
6766
+ }));
6767
+ }
6768
+ function coreToUI(m) {
6769
+ return {
6770
+ id: m.id,
6771
+ role: m.role,
6772
+ content: m.content ?? "",
6773
+ createdAt: m.created_at ?? /* @__PURE__ */ new Date(),
6774
+ toolCalls: m.tool_calls,
6775
+ toolCallId: m.tool_call_id,
6776
+ parentId: m.parent_id,
6777
+ childrenIds: m.children_ids,
6778
+ attachments: m.metadata?.attachments,
6779
+ thinking: m.metadata?.thinking,
6780
+ metadata: m.metadata
6781
+ };
6782
+ }
6319
6783
  function useInternalThreadManager(config = {}) {
6320
6784
  const {
6321
6785
  adapter,
@@ -6324,20 +6788,20 @@ function useInternalThreadManager(config = {}) {
6324
6788
  onThreadChange,
6325
6789
  enabled = true
6326
6790
  } = config;
6327
- const threadManagerConfig = {
6791
+ const [state, dispatch] = useReducer(threadReducer, INITIAL_STATE);
6792
+ const isLoadingRef = useRef(false);
6793
+ const threadManager = useThreadManager({
6328
6794
  adapter,
6329
6795
  saveDebounce,
6330
6796
  autoRestoreLastThread
6331
- };
6332
- const threadManager = useThreadManager(threadManagerConfig);
6797
+ });
6333
6798
  const {
6334
6799
  currentThread,
6335
6800
  currentThreadId,
6336
6801
  createThread,
6337
6802
  switchThread,
6338
6803
  updateCurrentThread,
6339
- clearCurrentThread,
6340
- refreshThreads
6804
+ clearCurrentThread
6341
6805
  } = threadManager;
6342
6806
  const {
6343
6807
  messages,
@@ -6345,183 +6809,148 @@ function useInternalThreadManager(config = {}) {
6345
6809
  status,
6346
6810
  isLoading,
6347
6811
  getAllMessages,
6348
- switchBranch
6812
+ switchBranch,
6813
+ threadId: sdkThreadId,
6814
+ setActiveThread
6349
6815
  } = useCopilot();
6350
- const isLoadingMessagesRef = useRef(false);
6351
- const savingToThreadRef = useRef(null);
6352
- const lastSavedSnapshotRef = useRef("");
6353
- const hasInitializedRef = useRef(false);
6354
- const getMessageSnapshot = useCallback((msgs) => {
6355
- return msgs.map((m) => {
6356
- const contentPreview = (m.content ?? "").slice(0, 20);
6357
- return `${m.id}:${contentPreview}:${m.content?.length ?? 0}`;
6358
- }).join("|");
6359
- }, []);
6360
- const convertToCore = useCallback((msgs) => {
6361
- return msgs.map((m) => ({
6362
- id: m.id,
6363
- role: m.role,
6364
- content: m.content,
6365
- created_at: m.createdAt,
6366
- tool_calls: m.toolCalls,
6367
- tool_call_id: m.toolCallId,
6368
- parent_id: m.parentId,
6369
- children_ids: m.childrenIds,
6370
- // Preserve full metadata including citations, toolExecutions, etc.
6371
- metadata: {
6372
- ...m.metadata,
6373
- attachments: m.attachments,
6374
- thinking: m.thinking
6375
- }
6376
- }));
6377
- }, []);
6378
- const handleSwitchThread = useCallback(
6379
- async (threadId) => {
6380
- isLoadingMessagesRef.current = true;
6381
- const thread = await switchThread(threadId);
6382
- if (thread?.messages) {
6383
- const uiMessages = thread.messages.map((m) => ({
6384
- id: m.id,
6385
- role: m.role,
6386
- content: m.content ?? "",
6387
- createdAt: m.created_at ?? /* @__PURE__ */ new Date(),
6388
- toolCalls: m.tool_calls,
6389
- toolCallId: m.tool_call_id,
6390
- parentId: m.parent_id,
6391
- childrenIds: m.children_ids,
6392
- attachments: m.metadata?.attachments,
6393
- thinking: m.metadata?.thinking,
6394
- // Preserve full metadata including citations, toolCallsHidden, toolExecutions, etc.
6395
- metadata: m.metadata
6396
- }));
6397
- lastSavedSnapshotRef.current = getMessageSnapshot(uiMessages);
6398
- savingToThreadRef.current = threadId;
6399
- setMessages(uiMessages);
6400
- if (thread.activeLeafId) {
6401
- switchBranch(thread.activeLeafId);
6402
- }
6403
- } else {
6404
- lastSavedSnapshotRef.current = "";
6405
- savingToThreadRef.current = threadId;
6406
- setMessages([]);
6407
- }
6408
- onThreadChange?.(threadId);
6409
- requestAnimationFrame(() => {
6410
- isLoadingMessagesRef.current = false;
6411
- });
6412
- },
6413
- [
6414
- switchThread,
6415
- setMessages,
6416
- switchBranch,
6417
- getMessageSnapshot,
6418
- onThreadChange
6419
- ]
6420
- );
6421
- const handleNewThread = useCallback(async () => {
6422
- isLoadingMessagesRef.current = true;
6423
- clearCurrentThread();
6424
- lastSavedSnapshotRef.current = "";
6425
- savingToThreadRef.current = null;
6426
- setMessages([]);
6427
- onThreadChange?.(null);
6428
- requestAnimationFrame(() => {
6429
- isLoadingMessagesRef.current = false;
6430
- });
6431
- }, [clearCurrentThread, setMessages, onThreadChange]);
6432
6816
  useEffect(() => {
6433
- if (!enabled) return;
6434
- if (hasInitializedRef.current || !currentThread) {
6435
- return;
6436
- }
6437
- hasInitializedRef.current = true;
6438
- isLoadingMessagesRef.current = true;
6817
+ if (!enabled || state.initialized || !currentThread) return;
6818
+ dispatch({ type: "RESTORE_START" });
6819
+ isLoadingRef.current = true;
6439
6820
  if (currentThread.messages && currentThread.messages.length > 0) {
6440
- const uiMessages = currentThread.messages.map((m) => ({
6441
- id: m.id,
6442
- role: m.role,
6443
- content: m.content ?? "",
6444
- createdAt: m.created_at ?? /* @__PURE__ */ new Date(),
6445
- toolCalls: m.tool_calls,
6446
- toolCallId: m.tool_call_id,
6447
- parentId: m.parent_id,
6448
- childrenIds: m.children_ids,
6449
- attachments: m.metadata?.attachments,
6450
- thinking: m.metadata?.thinking,
6451
- // Preserve full metadata including citations, toolExecutions, etc.
6452
- metadata: m.metadata
6453
- }));
6454
- lastSavedSnapshotRef.current = getMessageSnapshot(uiMessages);
6455
- savingToThreadRef.current = currentThread.id;
6821
+ const uiMessages = currentThread.messages.map(coreToUI);
6822
+ const snapshot = getMessageSnapshot(uiMessages);
6456
6823
  setMessages(uiMessages);
6457
- if (currentThread.activeLeafId) {
6458
- switchBranch(currentThread.activeLeafId);
6459
- }
6824
+ if (currentThread.activeLeafId) switchBranch(currentThread.activeLeafId);
6825
+ onThreadChange?.(currentThread.id);
6826
+ dispatch({
6827
+ type: "RESTORE_COMPLETE",
6828
+ threadId: currentThread.id,
6829
+ snapshot
6830
+ });
6460
6831
  } else {
6461
- lastSavedSnapshotRef.current = "";
6462
- savingToThreadRef.current = currentThread.id;
6832
+ onThreadChange?.(currentThread.id);
6833
+ dispatch({
6834
+ type: "RESTORE_COMPLETE",
6835
+ threadId: currentThread.id,
6836
+ snapshot: ""
6837
+ });
6463
6838
  }
6464
- onThreadChange?.(currentThread.id);
6465
6839
  requestAnimationFrame(() => {
6466
- isLoadingMessagesRef.current = false;
6840
+ isLoadingRef.current = false;
6467
6841
  });
6468
6842
  }, [
6469
6843
  enabled,
6470
- adapter,
6471
6844
  currentThread,
6845
+ state.initialized,
6472
6846
  setMessages,
6473
6847
  switchBranch,
6474
- getMessageSnapshot,
6475
6848
  onThreadChange
6476
6849
  ]);
6477
6850
  useEffect(() => {
6478
- if (!enabled) return;
6479
- if (isLoadingMessagesRef.current) {
6480
- return;
6481
- }
6482
- if (status === "streaming" || status === "submitted") {
6851
+ if (!enabled) {
6852
+ dispatch({ type: "SKIP_RESTORE" });
6483
6853
  return;
6484
6854
  }
6485
- if (messages.length === 0) {
6486
- return;
6855
+ if (!autoRestoreLastThread && !state.initialized) {
6856
+ dispatch({ type: "SKIP_RESTORE" });
6487
6857
  }
6488
- const currentSnapshot = getMessageSnapshot(messages);
6489
- if (currentSnapshot === lastSavedSnapshotRef.current) {
6490
- return;
6858
+ }, [enabled, autoRestoreLastThread, state.initialized]);
6859
+ useEffect(() => {
6860
+ if (!enabled) return;
6861
+ if (state.phase !== "idle") return;
6862
+ if (isLoadingRef.current) return;
6863
+ if (status === "streaming" || status === "submitted") return;
6864
+ if (messages.length === 0) return;
6865
+ if (currentThreadId) return;
6866
+ dispatch({ type: "FIRST_RESPONSE_COMPLETE" });
6867
+ }, [enabled, state.phase, status, messages.length, currentThreadId]);
6868
+ useEffect(() => {
6869
+ if (state.phase !== "awaiting_server_id") return;
6870
+ if (sdkThreadId) {
6871
+ dispatch({ type: "SERVER_ID_RECEIVED", threadId: sdkThreadId });
6872
+ } else {
6873
+ dispatch({ type: "CREATE_WITH_LOCAL_ID" });
6491
6874
  }
6875
+ }, [state.phase, sdkThreadId]);
6876
+ useEffect(() => {
6877
+ if (state.phase !== "creating") return;
6878
+ const allUIMessages = getAllMessages();
6879
+ const coreMessages = convertToCore(
6880
+ allUIMessages.length > 0 ? allUIMessages : messages
6881
+ );
6882
+ const activeLeafId = messages[messages.length - 1]?.id;
6883
+ const snapshot = getMessageSnapshot(messages);
6884
+ createThread({
6885
+ id: sdkThreadId ?? void 0,
6886
+ messages: coreMessages,
6887
+ activeLeafId
6888
+ }).then((thread) => {
6889
+ dispatch({ type: "THREAD_CREATED", threadId: thread.id, snapshot });
6890
+ onThreadChange?.(thread.id);
6891
+ });
6892
+ }, [state.phase]);
6893
+ useEffect(() => {
6894
+ if (!enabled) return;
6895
+ if (state.phase !== "active") return;
6896
+ if (isLoadingRef.current) return;
6897
+ if (status === "streaming" || status === "submitted") return;
6898
+ if (messages.length === 0) return;
6899
+ const snapshot = getMessageSnapshot(messages);
6900
+ if (snapshot === state.lastSnapshot) return;
6901
+ if (state.threadId && state.threadId !== currentThreadId) return;
6492
6902
  const allUIMessages = getAllMessages();
6493
6903
  const coreMessages = convertToCore(
6494
6904
  allUIMessages.length > 0 ? allUIMessages : messages
6495
6905
  );
6496
6906
  const activeLeafId = messages[messages.length - 1]?.id;
6497
- if (!currentThreadId && !savingToThreadRef.current) {
6498
- savingToThreadRef.current = "creating";
6499
- createThread({ messages: coreMessages, activeLeafId }).then((thread) => {
6500
- lastSavedSnapshotRef.current = currentSnapshot;
6501
- savingToThreadRef.current = thread.id;
6502
- onThreadChange?.(thread.id);
6503
- });
6504
- return;
6505
- }
6506
- if (savingToThreadRef.current && savingToThreadRef.current !== currentThreadId) {
6507
- return;
6508
- }
6509
6907
  updateCurrentThread({ messages: coreMessages, activeLeafId });
6510
- lastSavedSnapshotRef.current = currentSnapshot;
6908
+ dispatch({ type: "MESSAGES_SAVED", snapshot });
6511
6909
  }, [
6512
6910
  enabled,
6513
- adapter,
6911
+ state.phase,
6912
+ state.lastSnapshot,
6913
+ state.threadId,
6514
6914
  messages,
6515
- currentThreadId,
6516
6915
  status,
6916
+ currentThreadId,
6517
6917
  updateCurrentThread,
6518
- createThread,
6519
- refreshThreads,
6520
- getMessageSnapshot,
6521
- convertToCore,
6522
- getAllMessages,
6523
- onThreadChange
6918
+ getAllMessages
6524
6919
  ]);
6920
+ const handleSwitchThread = useCallback(
6921
+ async (threadId) => {
6922
+ dispatch({ type: "START_SWITCH" });
6923
+ isLoadingRef.current = true;
6924
+ const thread = await switchThread(threadId);
6925
+ if (thread?.messages) {
6926
+ const uiMessages = thread.messages.map(coreToUI);
6927
+ const snapshot = getMessageSnapshot(uiMessages);
6928
+ setMessages(uiMessages);
6929
+ if (thread.activeLeafId) switchBranch(thread.activeLeafId);
6930
+ onThreadChange?.(threadId);
6931
+ dispatch({ type: "SWITCH_COMPLETE", threadId, snapshot });
6932
+ } else {
6933
+ setMessages([]);
6934
+ onThreadChange?.(threadId);
6935
+ dispatch({ type: "SWITCH_COMPLETE", threadId, snapshot: "" });
6936
+ }
6937
+ requestAnimationFrame(() => {
6938
+ isLoadingRef.current = false;
6939
+ });
6940
+ },
6941
+ [switchThread, setMessages, switchBranch, onThreadChange]
6942
+ );
6943
+ const handleNewThread = useCallback(async () => {
6944
+ isLoadingRef.current = true;
6945
+ clearCurrentThread();
6946
+ setMessages([]);
6947
+ setActiveThread(null);
6948
+ onThreadChange?.(null);
6949
+ dispatch({ type: "NEW_THREAD" });
6950
+ requestAnimationFrame(() => {
6951
+ isLoadingRef.current = false;
6952
+ });
6953
+ }, [clearCurrentThread, setMessages, setActiveThread, onThreadChange]);
6525
6954
  const isBusy = isLoading || status === "streaming" || status === "submitted";
6526
6955
  return {
6527
6956
  threadManager,
@@ -6587,7 +7016,15 @@ function CopilotChatBase(props) {
6587
7016
  allowEdit = false,
6588
7017
  ...chatProps
6589
7018
  } = props;
7019
+ const localStorageKey = typeof persistence === "object" && "type" in persistence && persistence.type === "local" ? persistence.localStorageKey : void 0;
7020
+ const customAdapter = useMemo(
7021
+ () => localStorageKey ? createLocalStorageAdapter({ storageKey: localStorageKey }) : void 0,
7022
+ [localStorageKey]
7023
+ );
6590
7024
  const persistenceConfig = parsePersistenceConfig(persistence, onThreadChange);
7025
+ if (persistenceConfig && customAdapter) {
7026
+ persistenceConfig.adapter = customAdapter;
7027
+ }
6591
7028
  const threadManagerResult = useInternalThreadManager(
6592
7029
  persistenceConfig ?? { autoRestoreLastThread: false, enabled: false }
6593
7030
  );
@@ -6890,6 +7327,515 @@ function PoweredBy({ className, showLogo = true }) {
6890
7327
  }
6891
7328
  );
6892
7329
  }
7330
+ var DEFAULT_MAX_FILES = 5;
7331
+ var DEFAULT_MAX_FILE_SIZE3 = 10 * 1024 * 1024;
7332
+ var DEFAULT_ALLOWED_TYPES3 = [
7333
+ "image/*",
7334
+ "application/pdf",
7335
+ "text/csv",
7336
+ "text/plain",
7337
+ "text/markdown",
7338
+ "application/json",
7339
+ ".csv",
7340
+ ".txt",
7341
+ ".md",
7342
+ ".json"
7343
+ ];
7344
+ var TEXT_MIME_TYPES = /* @__PURE__ */ new Set([
7345
+ "text/csv",
7346
+ "text/plain",
7347
+ "text/markdown",
7348
+ "text/x-markdown",
7349
+ "application/json",
7350
+ "application/csv"
7351
+ ]);
7352
+ function isTextFile(file) {
7353
+ if (TEXT_MIME_TYPES.has(file.type)) return true;
7354
+ const ext = file.name.toLowerCase().split(".").pop();
7355
+ return ext === "csv" || ext === "txt" || ext === "md" || ext === "json";
7356
+ }
7357
+ function readFileAsText(file) {
7358
+ return new Promise((resolve, reject) => {
7359
+ const reader = new FileReader();
7360
+ reader.onload = () => resolve(reader.result);
7361
+ reader.onerror = () => reject(new Error("Failed to read file"));
7362
+ reader.readAsText(file);
7363
+ });
7364
+ }
7365
+ function generateId() {
7366
+ return `att_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;
7367
+ }
7368
+ function getAttachmentType3(mimeType) {
7369
+ if (mimeType.startsWith("image/")) return "image";
7370
+ if (mimeType.startsWith("audio/")) return "audio";
7371
+ if (mimeType.startsWith("video/")) return "video";
7372
+ return "file";
7373
+ }
7374
+ function isTypeAllowed(file, allowedTypes) {
7375
+ for (const type of allowedTypes) {
7376
+ if (type === file.type) return true;
7377
+ if (type.endsWith("/*") && file.type.startsWith(type.slice(0, -1)))
7378
+ return true;
7379
+ if (type.startsWith(".") && file.name.toLowerCase().endsWith(type))
7380
+ return true;
7381
+ }
7382
+ return false;
7383
+ }
7384
+ function fileToBase643(file) {
7385
+ return new Promise((resolve, reject) => {
7386
+ const reader = new FileReader();
7387
+ reader.onload = () => resolve(reader.result);
7388
+ reader.onerror = () => reject(new Error("Failed to read file"));
7389
+ reader.readAsDataURL(file);
7390
+ });
7391
+ }
7392
+ function createPreview(file) {
7393
+ if (file.type.startsWith("image/")) {
7394
+ return URL.createObjectURL(file);
7395
+ }
7396
+ return void 0;
7397
+ }
7398
+ function useAttachments(config = {}) {
7399
+ const {
7400
+ upload,
7401
+ maxFiles = DEFAULT_MAX_FILES,
7402
+ maxFileSize = DEFAULT_MAX_FILE_SIZE3,
7403
+ allowedFileTypes = DEFAULT_ALLOWED_TYPES3
7404
+ } = config;
7405
+ const [attachments, setAttachments] = useState([]);
7406
+ const [isDragging, setIsDragging] = useState(false);
7407
+ const abortControllers = useRef(/* @__PURE__ */ new Map());
7408
+ const fileInputRef = useRef(null);
7409
+ const dragCounter = useRef(0);
7410
+ const uploadFile = useCallback(
7411
+ async (id, file) => {
7412
+ const updateProgress = (progress) => {
7413
+ setAttachments(
7414
+ (prev) => prev.map((a) => a.id === id ? { ...a, progress } : a)
7415
+ );
7416
+ };
7417
+ const markReady = (attachment) => {
7418
+ setAttachments(
7419
+ (prev) => prev.map(
7420
+ (a) => a.id === id ? { ...a, status: "ready", progress: 100, attachment } : a
7421
+ )
7422
+ );
7423
+ };
7424
+ const markError = (error) => {
7425
+ setAttachments(
7426
+ (prev) => prev.map(
7427
+ (a) => a.id === id ? { ...a, status: "error", error } : a
7428
+ )
7429
+ );
7430
+ };
7431
+ try {
7432
+ const controller = new AbortController();
7433
+ abortControllers.current.set(id, controller);
7434
+ let result;
7435
+ if (isTextFile(file)) {
7436
+ updateProgress(50);
7437
+ const textContent = await readFileAsText(file);
7438
+ result = {
7439
+ type: "file",
7440
+ data: textContent,
7441
+ mimeType: file.type || "text/plain",
7442
+ filename: file.name
7443
+ };
7444
+ markReady(result);
7445
+ return;
7446
+ }
7447
+ if (typeof upload === "function") {
7448
+ updateProgress(50);
7449
+ result = await upload(file);
7450
+ } else if (upload) {
7451
+ const uploadConfig = typeof upload === "string" ? { url: upload } : upload;
7452
+ const extraHeaders = typeof uploadConfig.headers === "function" ? uploadConfig.headers() : uploadConfig.headers;
7453
+ const extraBody = typeof uploadConfig.body === "function" ? uploadConfig.body() : uploadConfig.body;
7454
+ const base64 = await fileToBase643(file);
7455
+ updateProgress(30);
7456
+ const body = JSON.stringify({
7457
+ data: base64,
7458
+ mimeType: file.type,
7459
+ filename: file.name,
7460
+ ...extraBody
7461
+ });
7462
+ const res = await fetch(uploadConfig.url, {
7463
+ method: "POST",
7464
+ headers: { "Content-Type": "application/json", ...extraHeaders },
7465
+ body,
7466
+ signal: controller.signal
7467
+ });
7468
+ updateProgress(90);
7469
+ if (!res.ok) throw new Error(`Upload failed: ${res.status}`);
7470
+ const json = await res.json();
7471
+ const url = json.url ?? json.data?.url;
7472
+ if (!url) throw new Error("Upload returned no URL");
7473
+ result = {
7474
+ type: getAttachmentType3(file.type),
7475
+ url,
7476
+ mimeType: file.type,
7477
+ filename: file.name
7478
+ };
7479
+ } else {
7480
+ updateProgress(50);
7481
+ const data = await fileToBase643(file);
7482
+ result = {
7483
+ type: getAttachmentType3(file.type),
7484
+ data,
7485
+ mimeType: file.type,
7486
+ filename: file.name
7487
+ };
7488
+ }
7489
+ markReady(result);
7490
+ } catch (err) {
7491
+ if (err?.name === "AbortError") return;
7492
+ markError(err?.message ?? "Upload failed");
7493
+ } finally {
7494
+ abortControllers.current.delete(id);
7495
+ }
7496
+ },
7497
+ [upload]
7498
+ );
7499
+ const addFiles = useCallback(
7500
+ (files) => {
7501
+ const fileArray = Array.from(files);
7502
+ const remaining = maxFiles - attachments.length;
7503
+ if (remaining <= 0) return;
7504
+ const toAdd = fileArray.slice(0, remaining);
7505
+ const newAttachments = [];
7506
+ for (const file of toAdd) {
7507
+ if (!isTypeAllowed(file, allowedFileTypes)) continue;
7508
+ if (file.size > maxFileSize) continue;
7509
+ const id = generateId();
7510
+ const preview = createPreview(file);
7511
+ newAttachments.push({
7512
+ id,
7513
+ file,
7514
+ preview,
7515
+ status: "uploading",
7516
+ progress: 0
7517
+ });
7518
+ }
7519
+ if (newAttachments.length === 0) return;
7520
+ setAttachments((prev) => [...prev, ...newAttachments]);
7521
+ for (const att of newAttachments) {
7522
+ uploadFile(att.id, att.file);
7523
+ }
7524
+ },
7525
+ [attachments.length, maxFiles, maxFileSize, allowedFileTypes, uploadFile]
7526
+ );
7527
+ const removeAttachment = useCallback((id) => {
7528
+ setAttachments((prev) => {
7529
+ const att = prev.find((a) => a.id === id);
7530
+ if (att?.preview) URL.revokeObjectURL(att.preview);
7531
+ return prev.filter((a) => a.id !== id);
7532
+ });
7533
+ const controller = abortControllers.current.get(id);
7534
+ if (controller) {
7535
+ controller.abort();
7536
+ abortControllers.current.delete(id);
7537
+ }
7538
+ }, []);
7539
+ const cancelUpload = useCallback(
7540
+ (id) => {
7541
+ removeAttachment(id);
7542
+ },
7543
+ [removeAttachment]
7544
+ );
7545
+ const retryUpload = useCallback(
7546
+ (id) => {
7547
+ setAttachments(
7548
+ (prev) => prev.map(
7549
+ (a) => a.id === id ? {
7550
+ ...a,
7551
+ status: "uploading",
7552
+ progress: 0,
7553
+ error: void 0
7554
+ } : a
7555
+ )
7556
+ );
7557
+ const att = attachments.find((a) => a.id === id);
7558
+ if (att) uploadFile(id, att.file);
7559
+ },
7560
+ [attachments, uploadFile]
7561
+ );
7562
+ const clearAll = useCallback(() => {
7563
+ for (const att of attachments) {
7564
+ if (att.preview) URL.revokeObjectURL(att.preview);
7565
+ }
7566
+ for (const controller of abortControllers.current.values()) {
7567
+ controller.abort();
7568
+ }
7569
+ abortControllers.current.clear();
7570
+ setAttachments([]);
7571
+ }, [attachments]);
7572
+ const getReadyAttachments = useCallback(() => {
7573
+ return attachments.filter((a) => a.status === "ready" && a.attachment).map((a) => a.attachment);
7574
+ }, [attachments]);
7575
+ const hasAttachments = attachments.length > 0;
7576
+ const isUploading = attachments.some((a) => a.status === "uploading");
7577
+ const canSend = hasAttachments && attachments.some((a) => a.status === "ready") && !isUploading;
7578
+ const dragHandlers = useMemo(
7579
+ () => ({
7580
+ onDragEnter: (e) => {
7581
+ e.preventDefault();
7582
+ e.stopPropagation();
7583
+ dragCounter.current++;
7584
+ if (e.dataTransfer.types.includes("Files")) {
7585
+ setIsDragging(true);
7586
+ }
7587
+ },
7588
+ onDragOver: (e) => {
7589
+ e.preventDefault();
7590
+ e.stopPropagation();
7591
+ },
7592
+ onDragLeave: (e) => {
7593
+ e.preventDefault();
7594
+ e.stopPropagation();
7595
+ dragCounter.current--;
7596
+ if (dragCounter.current === 0) {
7597
+ setIsDragging(false);
7598
+ }
7599
+ },
7600
+ onDrop: (e) => {
7601
+ e.preventDefault();
7602
+ e.stopPropagation();
7603
+ dragCounter.current = 0;
7604
+ setIsDragging(false);
7605
+ if (e.dataTransfer.files?.length) {
7606
+ addFiles(e.dataTransfer.files);
7607
+ }
7608
+ }
7609
+ }),
7610
+ [addFiles]
7611
+ );
7612
+ const openFilePicker = useCallback(() => {
7613
+ fileInputRef.current?.click();
7614
+ }, []);
7615
+ const onFileInputChange = useCallback(
7616
+ (e) => {
7617
+ if (e.target.files?.length) {
7618
+ addFiles(e.target.files);
7619
+ }
7620
+ if (fileInputRef.current) fileInputRef.current.value = "";
7621
+ },
7622
+ [addFiles]
7623
+ );
7624
+ return {
7625
+ attachments,
7626
+ isDragging,
7627
+ addFiles,
7628
+ removeAttachment,
7629
+ cancelUpload,
7630
+ retryUpload,
7631
+ clearAll,
7632
+ getReadyAttachments,
7633
+ hasAttachments,
7634
+ isUploading,
7635
+ canSend,
7636
+ dragHandlers,
7637
+ openFilePicker,
7638
+ fileInputRef,
7639
+ onFileInputChange
7640
+ };
7641
+ }
7642
+ var svgProps = {
7643
+ xmlns: "http://www.w3.org/2000/svg",
7644
+ viewBox: "0 0 24 24",
7645
+ fill: "none",
7646
+ stroke: "currentColor",
7647
+ strokeWidth: 2,
7648
+ strokeLinecap: "round",
7649
+ strokeLinejoin: "round"
7650
+ };
7651
+ function ImageIcon({ className }) {
7652
+ return /* @__PURE__ */ jsxs("svg", { ...svgProps, className, children: [
7653
+ /* @__PURE__ */ jsx("rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2" }),
7654
+ /* @__PURE__ */ jsx("circle", { cx: "9", cy: "9", r: "2" }),
7655
+ /* @__PURE__ */ jsx("path", { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" })
7656
+ ] });
7657
+ }
7658
+ function FileTextIcon({ className }) {
7659
+ return /* @__PURE__ */ jsxs("svg", { ...svgProps, className, children: [
7660
+ /* @__PURE__ */ jsx("path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }),
7661
+ /* @__PURE__ */ jsx("path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }),
7662
+ /* @__PURE__ */ jsx("path", { d: "M10 9H8" }),
7663
+ /* @__PURE__ */ jsx("path", { d: "M16 13H8" }),
7664
+ /* @__PURE__ */ jsx("path", { d: "M16 17H8" })
7665
+ ] });
7666
+ }
7667
+ function MusicIcon({ className }) {
7668
+ return /* @__PURE__ */ jsxs("svg", { ...svgProps, className, children: [
7669
+ /* @__PURE__ */ jsx("path", { d: "M9 18V5l12-2v13" }),
7670
+ /* @__PURE__ */ jsx("circle", { cx: "6", cy: "18", r: "3" }),
7671
+ /* @__PURE__ */ jsx("circle", { cx: "18", cy: "16", r: "3" })
7672
+ ] });
7673
+ }
7674
+ function VideoIcon({ className }) {
7675
+ return /* @__PURE__ */ jsxs("svg", { ...svgProps, className, children: [
7676
+ /* @__PURE__ */ jsx("path", { d: "m16 13 5.223 3.482a.5.5 0 0 0 .777-.416V7.934a.5.5 0 0 0-.777-.416L16 11" }),
7677
+ /* @__PURE__ */ jsx("rect", { width: "14", height: "12", x: "2", y: "6", rx: "2" })
7678
+ ] });
7679
+ }
7680
+ function XIcon3({ className }) {
7681
+ return /* @__PURE__ */ jsxs("svg", { ...svgProps, className, children: [
7682
+ /* @__PURE__ */ jsx("path", { d: "M18 6 6 18" }),
7683
+ /* @__PURE__ */ jsx("path", { d: "m6 6 12 12" })
7684
+ ] });
7685
+ }
7686
+ function RefreshCwIcon({ className }) {
7687
+ return /* @__PURE__ */ jsxs("svg", { ...svgProps, className, children: [
7688
+ /* @__PURE__ */ jsx("path", { d: "M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8" }),
7689
+ /* @__PURE__ */ jsx("path", { d: "M21 3v5h-5" }),
7690
+ /* @__PURE__ */ jsx("path", { d: "M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16" }),
7691
+ /* @__PURE__ */ jsx("path", { d: "M8 16H3v5" })
7692
+ ] });
7693
+ }
7694
+ function Loader2Icon({ className }) {
7695
+ return /* @__PURE__ */ jsx("svg", { ...svgProps, className, children: /* @__PURE__ */ jsx("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" }) });
7696
+ }
7697
+ function CheckIcon4({ className }) {
7698
+ return /* @__PURE__ */ jsx("svg", { ...svgProps, className, children: /* @__PURE__ */ jsx("path", { d: "M20 6 9 17l-5-5" }) });
7699
+ }
7700
+ function FileTypeIcon({ mimeType }) {
7701
+ const cls = "size-3.5";
7702
+ if (mimeType.startsWith("image/")) return /* @__PURE__ */ jsx(ImageIcon, { className: cls });
7703
+ if (mimeType.startsWith("audio/")) return /* @__PURE__ */ jsx(MusicIcon, { className: cls });
7704
+ if (mimeType.startsWith("video/")) return /* @__PURE__ */ jsx(VideoIcon, { className: cls });
7705
+ return /* @__PURE__ */ jsx(FileTextIcon, { className: cls });
7706
+ }
7707
+ function StatusBadge2({ status }) {
7708
+ if (status === "uploading") {
7709
+ return /* @__PURE__ */ jsx(Loader2Icon, { className: "size-3 animate-spin text-blue-400" });
7710
+ }
7711
+ if (status === "ready") {
7712
+ return /* @__PURE__ */ jsx(CheckIcon4, { className: "size-3 text-emerald-400" });
7713
+ }
7714
+ return null;
7715
+ }
7716
+ function AttachmentCard({
7717
+ attachment,
7718
+ onRemove,
7719
+ onRetry
7720
+ }) {
7721
+ const { id, file, preview, status, progress, error } = attachment;
7722
+ const isError = status === "error";
7723
+ return /* @__PURE__ */ jsxs(
7724
+ "div",
7725
+ {
7726
+ className: `
7727
+ csdk-attachment-card group relative flex items-center gap-1.5
7728
+ rounded-lg border px-2 py-1.5 min-w-0 max-w-[160px]
7729
+ transition-colors duration-150
7730
+ ${isError ? "border-red-500/30 bg-red-500/5" : "border-border/60 bg-muted/40 hover:bg-muted/60"}
7731
+ `,
7732
+ children: [
7733
+ /* @__PURE__ */ jsx("div", { className: "size-7 rounded shrink-0 overflow-hidden bg-muted/60 flex items-center justify-center", children: preview ? /* @__PURE__ */ jsx(
7734
+ "img",
7735
+ {
7736
+ src: preview,
7737
+ alt: file.name,
7738
+ className: "size-full object-cover"
7739
+ }
7740
+ ) : /* @__PURE__ */ jsx(FileTypeIcon, { mimeType: file.type }) }),
7741
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
7742
+ /* @__PURE__ */ jsx("p", { className: "text-[10px] font-medium truncate leading-tight text-foreground/80", children: file.name }),
7743
+ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1 mt-0.5", children: isError ? /* @__PURE__ */ jsxs(
7744
+ "button",
7745
+ {
7746
+ onClick: () => onRetry(id),
7747
+ className: "flex items-center gap-0.5 text-[9px] text-red-400 hover:text-red-300 cursor-pointer",
7748
+ children: [
7749
+ /* @__PURE__ */ jsx(RefreshCwIcon, { className: "size-2.5" }),
7750
+ "Retry"
7751
+ ]
7752
+ }
7753
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
7754
+ /* @__PURE__ */ jsx(StatusBadge2, { status }),
7755
+ /* @__PURE__ */ jsx("span", { className: "text-[9px] text-muted-foreground", children: status === "uploading" ? `${progress}%` : formatBytes(file.size) })
7756
+ ] }) })
7757
+ ] }),
7758
+ status === "uploading" && /* @__PURE__ */ jsx("div", { className: "absolute bottom-0 left-0 right-0 h-[2px] rounded-b-lg overflow-hidden bg-muted/40", children: /* @__PURE__ */ jsx(
7759
+ "div",
7760
+ {
7761
+ className: "h-full bg-blue-500 transition-[width] duration-300 ease-out",
7762
+ style: { width: `${progress}%` }
7763
+ }
7764
+ ) }),
7765
+ /* @__PURE__ */ jsx(
7766
+ "button",
7767
+ {
7768
+ onClick: () => onRemove(id),
7769
+ className: "\n size-4 rounded-full shrink-0 flex items-center justify-center\n text-muted-foreground/50 hover:text-foreground hover:bg-muted\n transition-colors cursor-pointer\n ",
7770
+ "aria-label": "Remove attachment",
7771
+ children: /* @__PURE__ */ jsx(XIcon3, { className: "size-2.5" })
7772
+ }
7773
+ )
7774
+ ]
7775
+ }
7776
+ );
7777
+ }
7778
+ function formatBytes(bytes) {
7779
+ if (bytes < 1024) return `${bytes}B`;
7780
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)}KB`;
7781
+ return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
7782
+ }
7783
+ function AttachmentStrip({
7784
+ attachments,
7785
+ onRemove,
7786
+ onRetry,
7787
+ className = ""
7788
+ }) {
7789
+ if (attachments.length === 0) return null;
7790
+ return /* @__PURE__ */ jsx(
7791
+ "div",
7792
+ {
7793
+ className: `csdk-attachment-strip flex gap-1.5 overflow-x-auto px-1 py-1.5 scrollbar-none ${className}`,
7794
+ children: attachments.map((att) => /* @__PURE__ */ jsx(
7795
+ AttachmentCard,
7796
+ {
7797
+ attachment: att,
7798
+ onRemove,
7799
+ onRetry
7800
+ },
7801
+ att.id
7802
+ ))
7803
+ }
7804
+ );
7805
+ }
7806
+ var svgProps2 = {
7807
+ xmlns: "http://www.w3.org/2000/svg",
7808
+ viewBox: "0 0 24 24",
7809
+ fill: "none",
7810
+ stroke: "currentColor",
7811
+ strokeWidth: 2,
7812
+ strokeLinecap: "round",
7813
+ strokeLinejoin: "round"
7814
+ };
7815
+ function PaperclipIcon({ className }) {
7816
+ return /* @__PURE__ */ jsx("svg", { ...svgProps2, className, children: /* @__PURE__ */ jsx("path", { d: "m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 18 8.84l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48" }) });
7817
+ }
7818
+ function DropZoneOverlay({
7819
+ isDragging,
7820
+ className = ""
7821
+ }) {
7822
+ if (!isDragging) return null;
7823
+ return /* @__PURE__ */ jsx(
7824
+ "div",
7825
+ {
7826
+ className: `
7827
+ csdk-drop-zone absolute inset-0 z-50 flex items-center justify-center
7828
+ bg-background/80 backdrop-blur-sm border-2 border-dashed border-primary/40
7829
+ rounded-xl pointer-events-none
7830
+ ${className}
7831
+ `,
7832
+ children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-2 text-primary/70", children: [
7833
+ /* @__PURE__ */ jsx("div", { className: "size-10 rounded-xl bg-primary/10 flex items-center justify-center", children: /* @__PURE__ */ jsx(PaperclipIcon, { className: "size-5" }) }),
7834
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: "Drop files to attach" })
7835
+ ] })
7836
+ }
7837
+ );
7838
+ }
6893
7839
 
6894
7840
  // src/ui/index.ts
6895
7841
  var ChatPrimitives = {
@@ -6923,6 +7869,6 @@ var ChatPrimitives = {
6923
7869
  Loader
6924
7870
  };
6925
7871
 
6926
- export { AlertTriangleIcon, BotIcon, BranchNavigator, Button, CapabilityBadge, CapabilityList, Chat, ChatContainerContent, ChatContainerRoot, ChatContainerScrollAnchor, ChatPrimitives, ChatWelcome, CheckIcon, ChevronDownIcon2 as ChevronDownIcon, ChevronLeftIcon, ChevronUpIcon, CitationBadge, CitationSuperscript, CloseIcon, CodeBlock, CompactPermissionConfirmation, Confirmation, ConfirmationActions, ConfirmationApproved, ConfirmationMessage, ConfirmationPending, ConfirmationRejected, ConnectedChat, CopilotChat, CopilotUIProvider, CopyIcon, DEFAULT_PERMISSION_OPTIONS, DevLogger, FeedbackBar, FollowUpQuestions, InlineToolSteps, MCPUIFrame, MCPUIFrameList, Markdown, MessageAvatar, MessageContent, Message as MessagePrimitive, MessageWithCitations, ModelSelector, PermissionConfirmation, PoweredBy, PromptInput, PromptInputAction, PromptInputActions, PromptInputTextarea, Reasoning, ReasoningContent, ReasoningTrigger, RefreshIcon, ScrollButton, SearchAnswer, SearchResults, SearchResultsWithAnswer, SendIcon, SimpleConfirmation, SimpleModelSelector, SimpleReasoning, SimpleSource, Source, SourceContent, SourceGroup, SourcePill, SourceTrigger, SourcesBar, SourcesCollapsible, SourcesList, StopIcon, ThreadCard, ThreadList, ThreadPicker, ThumbsDownIcon2 as ThumbsDownIcon, ThumbsUpIcon2 as ThumbsUpIcon, ToolExecutionMessage, ToolStep, ToolSteps, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, UserIcon, XIcon2 as XIcon, annotationsToCitations, parseFollowUps, resultsToCitations, useChatContainer, useCopilotChatContext, useCopilotUI };
7872
+ export { AlertTriangleIcon, AttachmentStrip, BotIcon, BranchNavigator, Button, CapabilityBadge, CapabilityList, Chat, ChatContainerContent, ChatContainerRoot, ChatContainerScrollAnchor, ChatPrimitives, ChatWelcome, CheckIcon, ChevronDownIcon2 as ChevronDownIcon, ChevronLeftIcon, ChevronUpIcon, CitationBadge, CitationSuperscript, CloseIcon, CodeBlock, CompactPermissionConfirmation, Confirmation, ConfirmationActions, ConfirmationApproved, ConfirmationMessage, ConfirmationPending, ConfirmationRejected, ConnectedChat, CopilotChat, CopilotUIProvider, CopyIcon, DEFAULT_PERMISSION_OPTIONS, DevLogger, DropZoneOverlay, FeedbackBar, FollowUpQuestions, InlineToolSteps, MCPUIFrame, MCPUIFrameList, Markdown, MessageAvatar, MessageContent, Message as MessagePrimitive, MessageWithCitations, ModelSelector, PermissionConfirmation, PoweredBy, PromptInput, PromptInputAction, PromptInputActions, PromptInputTextarea, Reasoning, ReasoningContent, ReasoningTrigger, RefreshIcon, ScrollButton, SearchAnswer, SearchResults, SearchResultsWithAnswer, SendIcon, SimpleConfirmation, SimpleModelSelector, SimpleReasoning, SimpleSource, Source, SourceContent, SourceGroup, SourcePill, SourceTrigger, SourcesBar, SourcesCollapsible, SourcesList, StopIcon, ThreadCard, ThreadList, ThreadPicker, ThumbsDownIcon2 as ThumbsDownIcon, ThumbsUpIcon2 as ThumbsUpIcon, ToolExecutionMessage, ToolStep, ToolSteps, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, UserIcon, XIcon2 as XIcon, annotationsToCitations, parseFollowUps, resultsToCitations, useAttachments, useChatContainer, useCopilotChatContext, useCopilotUI };
6927
7873
  //# sourceMappingURL=index.js.map
6928
7874
  //# sourceMappingURL=index.js.map