@copilotkit/react-core 0.0.0-main-20260403195251 → 0.0.0-main-20260407163127

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 (57) hide show
  1. package/CHANGELOG.md +10 -6
  2. package/dist/{copilotkit-CDZGGOIB.cjs → copilotkit-B3Eeystj.cjs} +907 -153
  3. package/dist/copilotkit-B3Eeystj.cjs.map +1 -0
  4. package/dist/{copilotkit-BYNyIuE7.d.ts → copilotkit-B_nbOmBS.d.cts} +96 -13
  5. package/dist/copilotkit-B_nbOmBS.d.cts.map +1 -0
  6. package/dist/{copilotkit-rBCfdqIo.d.mts → copilotkit-Cs-8EAok.d.mts} +96 -13
  7. package/dist/copilotkit-Cs-8EAok.d.mts.map +1 -0
  8. package/dist/{copilotkit-BK_zLoVm.mjs → copilotkit-D3sdzqTz.mjs} +893 -157
  9. package/dist/copilotkit-D3sdzqTz.mjs.map +1 -0
  10. package/dist/index.cjs +1 -1
  11. package/dist/index.d.cts +1 -1
  12. package/dist/index.d.mts +1 -1
  13. package/dist/index.mjs +1 -1
  14. package/dist/index.umd.js +4 -4
  15. package/dist/index.umd.js.map +1 -1
  16. package/dist/v2/index.cjs +4 -1
  17. package/dist/v2/index.css +1 -1
  18. package/dist/v2/index.d.cts +2 -2
  19. package/dist/v2/index.d.mts +2 -2
  20. package/dist/v2/index.mjs +2 -2
  21. package/dist/v2/index.umd.js +913 -155
  22. package/dist/v2/index.umd.js.map +1 -1
  23. package/package.json +18 -10
  24. package/src/v2/__tests__/utils/test-helpers.tsx +64 -0
  25. package/src/v2/components/chat/CopilotChat.tsx +151 -31
  26. package/src/v2/components/chat/CopilotChatAssistantMessage.tsx +17 -2
  27. package/src/v2/components/chat/CopilotChatAttachmentQueue.tsx +481 -0
  28. package/src/v2/components/chat/CopilotChatAttachmentRenderer.tsx +139 -0
  29. package/src/v2/components/chat/CopilotChatInput.tsx +4 -20
  30. package/src/v2/components/chat/CopilotChatMessageView.tsx +253 -149
  31. package/src/v2/components/chat/CopilotChatUserMessage.tsx +54 -0
  32. package/src/v2/components/chat/CopilotChatView.tsx +179 -66
  33. package/src/v2/components/chat/__tests__/CopilotChat.attachments.test.tsx +168 -0
  34. package/src/v2/components/chat/__tests__/CopilotChatActivityRendering.e2e.test.tsx +63 -2
  35. package/src/v2/components/chat/__tests__/CopilotChatInput.test.tsx +47 -1
  36. package/src/v2/components/chat/__tests__/CopilotChatPerf.e2e.test.tsx +268 -0
  37. package/src/v2/components/chat/__tests__/MCPAppsActivityRenderer.e2e.test.tsx +43 -2
  38. package/src/v2/components/chat/index.ts +9 -0
  39. package/src/v2/components/chat/scroll-element-context.ts +13 -0
  40. package/src/v2/hooks/__tests__/use-attachments.test.tsx +169 -0
  41. package/src/v2/hooks/__tests__/use-threads.test.tsx +54 -0
  42. package/src/v2/hooks/index.ts +5 -0
  43. package/src/v2/hooks/use-attachments.tsx +269 -0
  44. package/src/v2/hooks/use-render-activity-message.tsx +9 -2
  45. package/src/v2/hooks/use-threads.tsx +35 -15
  46. package/src/v2/providers/CopilotChatConfigurationProvider.tsx +1 -1
  47. package/src/v2/providers/CopilotKitProvider.tsx +14 -3
  48. package/src/v2/providers/__tests__/CopilotKitProvider.test.tsx +70 -0
  49. package/dist/copilotkit-BK_zLoVm.mjs.map +0 -1
  50. package/dist/copilotkit-BYNyIuE7.d.cts +0 -2208
  51. package/dist/copilotkit-BYNyIuE7.d.cts.map +0 -1
  52. package/dist/copilotkit-BYNyIuE7.d.ts.map +0 -1
  53. package/dist/copilotkit-CDZGGOIB.cjs.map +0 -1
  54. package/dist/copilotkit-rBCfdqIo.d.mts.map +0 -1
  55. package/dist/index.d.ts +0 -667
  56. package/dist/index.d.ts.map +0 -1
  57. package/dist/v2/index.d.ts +0 -6
@@ -44,9 +44,10 @@ let streamdown = require("streamdown");
44
44
  let zod = require("zod");
45
45
  let _lit_labs_react = require("@lit-labs/react");
46
46
  let _copilotkit_a2ui_renderer = require("@copilotkit/a2ui-renderer");
47
+ let _tanstack_react_virtual = require("@tanstack/react-virtual");
48
+ let react_dom = require("react-dom");
47
49
  let use_stick_to_bottom = require("use-stick-to-bottom");
48
50
  let ts_deepmerge = require("ts-deepmerge");
49
- let react_dom = require("react-dom");
50
51
  let react_markdown = require("react-markdown");
51
52
  react_markdown = __toESM(react_markdown);
52
53
 
@@ -56,7 +57,7 @@ const CopilotChatDefaultLabels = {
56
57
  chatInputToolbarStartTranscribeButtonLabel: "Transcribe",
57
58
  chatInputToolbarCancelTranscribeButtonLabel: "Cancel",
58
59
  chatInputToolbarFinishTranscribeButtonLabel: "Finish",
59
- chatInputToolbarAddButtonLabel: "Add photos or files",
60
+ chatInputToolbarAddButtonLabel: "Add attachments",
60
61
  chatInputToolbarToolsButtonLabel: "Tools",
61
62
  assistantMessageToolbarCopyCodeLabel: "Copy",
62
63
  assistantMessageToolbarCopyCodeCopiedLabel: "Copied",
@@ -578,7 +579,7 @@ function renderSlot(slot, DefaultComponent, props) {
578
579
  //#region src/v2/components/chat/CopilotChatInput.tsx
579
580
  const SLASH_MENU_MAX_VISIBLE_ITEMS = 5;
580
581
  const SLASH_MENU_ITEM_HEIGHT_PX = 40;
581
- function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning = false, onStartTranscribe, onCancelTranscribe, onFinishTranscribe, onFinishTranscribeWithAudio, onAddFile, onChange, value, toolsMenu, autoFocus = true, positioning = "static", keyboardHeight = 0, containerRef, showDisclaimer, textArea, sendButton, startTranscribeButton, cancelTranscribeButton, finishTranscribeButton, addMenuButton, audioRecorder, disclaimer, children, className, ...props }) {
582
+ function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning = false, onStartTranscribe, onCancelTranscribe, onFinishTranscribe, onFinishTranscribeWithAudio, onAddFile, onChange, value, toolsMenu, autoFocus = false, positioning = "static", keyboardHeight = 0, containerRef, showDisclaimer, textArea, sendButton, startTranscribeButton, cancelTranscribeButton, finishTranscribeButton, addMenuButton, audioRecorder, disclaimer, children, className, ...props }) {
582
583
  const isControlled = value !== void 0;
583
584
  const [internalValue, setInternalValue] = (0, react.useState)(() => value ?? "");
584
585
  (0, react.useEffect)(() => {
@@ -651,7 +652,7 @@ function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning =
651
652
  previousModalStateRef.current = config?.isModalOpen;
652
653
  return;
653
654
  }
654
- if (config?.isModalOpen && !previousModalStateRef.current) inputRef.current?.focus();
655
+ if (config?.isModalOpen && !previousModalStateRef.current) inputRef.current?.focus({ preventScroll: true });
655
656
  previousModalStateRef.current = config?.isModalOpen;
656
657
  }, [config?.isModalOpen, autoFocus]);
657
658
  (0, react.useEffect)(() => {
@@ -1175,7 +1176,7 @@ function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning =
1175
1176
  side: "bottom",
1176
1177
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1177
1178
  className: "cpk:flex cpk:items-center cpk:gap-1 cpk:text-xs cpk:font-medium",
1178
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "Add files and more" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
1179
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "Add attachments" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
1179
1180
  className: "cpk:rounded cpk:bg-[#4a4a4a] cpk:px-1 cpk:py-[1px] cpk:font-mono cpk:text-[11px] cpk:text-white cpk:dark:bg-[#e0e0e0] cpk:dark:text-black",
1180
1181
  children: "/"
1181
1182
  })]
@@ -1191,21 +1192,7 @@ function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning =
1191
1192
  const labels = useCopilotChatConfiguration()?.labels ?? CopilotChatDefaultLabels;
1192
1193
  (0, react.useImperativeHandle)(ref, () => internalTextareaRef.current);
1193
1194
  (0, react.useEffect)(() => {
1194
- const textarea = internalTextareaRef.current;
1195
- if (!textarea) return;
1196
- const handleFocus = () => {
1197
- setTimeout(() => {
1198
- textarea.scrollIntoView({
1199
- behavior: "smooth",
1200
- block: "nearest"
1201
- });
1202
- }, 300);
1203
- };
1204
- textarea.addEventListener("focus", handleFocus);
1205
- return () => textarea.removeEventListener("focus", handleFocus);
1206
- }, []);
1207
- (0, react.useEffect)(() => {
1208
- if (autoFocus) internalTextareaRef.current?.focus();
1195
+ if (autoFocus) internalTextareaRef.current?.focus({ preventScroll: true });
1209
1196
  }, [autoFocus]);
1210
1197
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
1211
1198
  ref: internalTextareaRef,
@@ -2149,7 +2136,7 @@ function useStableArrayProp(prop, warningMessage, isMeaningfulChange) {
2149
2136
  }, [value, warningMessage]);
2150
2137
  return value;
2151
2138
  }
2152
- const CopilotKitProvider = ({ children, runtimeUrl, headers = {}, credentials, publicApiKey, publicLicenseKey, licenseToken, properties = {}, agents__unsafe_dev_only: agents = {}, selfManagedAgents = {}, renderToolCalls, renderActivityMessages, renderCustomMessages, frontendTools, humanInTheLoop, showDevConsole = false, useSingleEndpoint = false, onError, a2ui }) => {
2139
+ const CopilotKitProvider = ({ children, runtimeUrl, headers = {}, credentials, publicApiKey, publicLicenseKey, licenseToken, properties = {}, agents__unsafe_dev_only: agents = {}, selfManagedAgents = {}, renderToolCalls, renderActivityMessages, renderCustomMessages, frontendTools, humanInTheLoop, showDevConsole = false, useSingleEndpoint, onError, a2ui }) => {
2153
2140
  const [shouldRenderInspector, setShouldRenderInspector] = (0, react.useState)(false);
2154
2141
  const [runtimeA2UIEnabled, setRuntimeA2UIEnabled] = (0, react.useState)(false);
2155
2142
  const [runtimeLicenseStatus, setRuntimeLicenseStatus] = (0, react.useState)(void 0);
@@ -2263,7 +2250,7 @@ const CopilotKitProvider = ({ children, runtimeUrl, headers = {}, credentials, p
2263
2250
  const copilotkitRef = (0, react.useRef)(null);
2264
2251
  if (copilotkitRef.current === null) copilotkitRef.current = new CopilotKitCoreReact({
2265
2252
  runtimeUrl: chatApiEndpoint,
2266
- runtimeTransport: useSingleEndpoint ? "single" : "rest",
2253
+ runtimeTransport: useSingleEndpoint === true ? "single" : useSingleEndpoint === false ? "rest" : "auto",
2267
2254
  headers: mergedHeaders,
2268
2255
  credentials,
2269
2256
  properties,
@@ -2335,7 +2322,7 @@ const CopilotKitProvider = ({ children, runtimeUrl, headers = {}, credentials, p
2335
2322
  }, [copilotkit]);
2336
2323
  (0, react.useEffect)(() => {
2337
2324
  copilotkit.setRuntimeUrl(chatApiEndpoint);
2338
- copilotkit.setRuntimeTransport(useSingleEndpoint ? "single" : "rest");
2325
+ copilotkit.setRuntimeTransport(useSingleEndpoint === true ? "single" : useSingleEndpoint === false ? "rest" : "auto");
2339
2326
  copilotkit.setHeaders(mergedHeaders);
2340
2327
  copilotkit.setCredentials(credentials);
2341
2328
  copilotkit.setProperties(properties);
@@ -2701,7 +2688,8 @@ function useRenderCustomMessages() {
2701
2688
  //#region src/v2/hooks/use-render-activity-message.tsx
2702
2689
  function useRenderActivityMessage() {
2703
2690
  const { copilotkit } = useCopilotKit();
2704
- const agentId = useCopilotChatConfiguration()?.agentId ?? _copilotkit_shared.DEFAULT_AGENT_ID;
2691
+ const config = useCopilotChatConfiguration();
2692
+ const agentId = config?.agentId ?? _copilotkit_shared.DEFAULT_AGENT_ID;
2705
2693
  const renderers = copilotkit.renderActivityMessages;
2706
2694
  const findRenderer = (0, react.useCallback)((activityType) => {
2707
2695
  if (!renderers.length) return null;
@@ -2717,7 +2705,8 @@ function useRenderActivityMessage() {
2717
2705
  return null;
2718
2706
  }
2719
2707
  const Component = renderer.render;
2720
- const agent = copilotkit.getAgent(agentId);
2708
+ const registryAgent = copilotkit.getAgent(agentId);
2709
+ const agent = getThreadClone(registryAgent, config?.threadId) ?? registryAgent;
2721
2710
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Component, {
2722
2711
  activityType: message.activityType,
2723
2712
  content: parseResult.data,
@@ -2726,6 +2715,7 @@ function useRenderActivityMessage() {
2726
2715
  }, message.id);
2727
2716
  }, [
2728
2717
  agentId,
2718
+ config?.threadId,
2729
2719
  copilotkit,
2730
2720
  findRenderer
2731
2721
  ]);
@@ -3611,11 +3601,19 @@ function useThreadStoreSelector(store, selector) {
3611
3601
  function useThreads$1({ agentId, includeArchived, limit }) {
3612
3602
  const { copilotkit } = useCopilotKit();
3613
3603
  const [store] = (0, react.useState)(() => (0, _copilotkit_core.ɵcreateThreadStore)({ fetch: globalThis.fetch }));
3614
- const threads = useThreadStoreSelector(store, _copilotkit_core.ɵselectThreads);
3604
+ const coreThreads = useThreadStoreSelector(store, _copilotkit_core.ɵselectThreads);
3605
+ const threads = (0, react.useMemo)(() => coreThreads.map(({ id, agentId, name, archived, createdAt, updatedAt }) => ({
3606
+ id,
3607
+ agentId,
3608
+ name,
3609
+ archived,
3610
+ createdAt,
3611
+ updatedAt
3612
+ })), [coreThreads]);
3615
3613
  const storeIsLoading = useThreadStoreSelector(store, _copilotkit_core.ɵselectThreadsIsLoading);
3616
3614
  const storeError = useThreadStoreSelector(store, _copilotkit_core.ɵselectThreadsError);
3617
- const hasNextPage = useThreadStoreSelector(store, _copilotkit_core.ɵselectHasNextPage);
3618
- const isFetchingNextPage = useThreadStoreSelector(store, _copilotkit_core.ɵselectIsFetchingNextPage);
3615
+ const hasMoreThreads = useThreadStoreSelector(store, _copilotkit_core.ɵselectHasNextPage);
3616
+ const isFetchingMoreThreads = useThreadStoreSelector(store, _copilotkit_core.ɵselectIsFetchingNextPage);
3619
3617
  const headersKey = (0, react.useMemo)(() => {
3620
3618
  return JSON.stringify(Object.entries(copilotkit.headers ?? {}).sort(([left], [right]) => left.localeCompare(right)));
3621
3619
  }, [copilotkit.headers]);
@@ -3658,15 +3656,173 @@ function useThreads$1({ agentId, includeArchived, limit }) {
3658
3656
  threads,
3659
3657
  isLoading,
3660
3658
  error,
3661
- hasNextPage,
3662
- isFetchingNextPage,
3663
- fetchNextPage: (0, react.useCallback)(() => store.fetchNextPage(), [store]),
3659
+ hasMoreThreads,
3660
+ isFetchingMoreThreads,
3661
+ fetchMoreThreads: (0, react.useCallback)(() => store.fetchNextPage(), [store]),
3664
3662
  renameThread,
3665
3663
  archiveThread,
3666
3664
  deleteThread
3667
3665
  };
3668
3666
  }
3669
3667
 
3668
+ //#endregion
3669
+ //#region src/v2/hooks/use-attachments.tsx
3670
+ /**
3671
+ * Hook that manages file attachment state — uploads, drag-and-drop, paste,
3672
+ * and lifecycle. All returned callbacks are referentially stable across
3673
+ * renders (via useCallback) to avoid destabilizing downstream memoization.
3674
+ */
3675
+ function useAttachments({ config }) {
3676
+ const enabled = config?.enabled ?? false;
3677
+ const [attachments, setAttachments] = (0, react.useState)([]);
3678
+ const [dragOver, setDragOver] = (0, react.useState)(false);
3679
+ const fileInputRef = (0, react.useRef)(null);
3680
+ const containerRef = (0, react.useRef)(null);
3681
+ const configRef = (0, react.useRef)(config);
3682
+ configRef.current = config;
3683
+ const attachmentsRef = (0, react.useRef)([]);
3684
+ attachmentsRef.current = attachments;
3685
+ const processFiles = (0, react.useCallback)(async (files) => {
3686
+ const cfg = configRef.current;
3687
+ const accept = cfg?.accept ?? "*/*";
3688
+ const maxSize = cfg?.maxSize ?? 20 * 1024 * 1024;
3689
+ const rejectedFiles = files.filter((file) => !(0, _copilotkit_shared.matchesAcceptFilter)(file, accept));
3690
+ for (const file of rejectedFiles) cfg?.onUploadFailed?.({
3691
+ reason: "invalid-type",
3692
+ file,
3693
+ message: `File "${file.name}" is not accepted. Supported types: ${accept}`
3694
+ });
3695
+ const validFiles = files.filter((file) => (0, _copilotkit_shared.matchesAcceptFilter)(file, accept));
3696
+ for (const file of validFiles) {
3697
+ if ((0, _copilotkit_shared.exceedsMaxSize)(file, maxSize)) {
3698
+ cfg?.onUploadFailed?.({
3699
+ reason: "file-too-large",
3700
+ file,
3701
+ message: `File "${file.name}" exceeds the maximum size of ${(0, _copilotkit_shared.formatFileSize)(maxSize)}`
3702
+ });
3703
+ continue;
3704
+ }
3705
+ const modality = (0, _copilotkit_shared.getModalityFromMimeType)(file.type);
3706
+ const placeholderId = (0, _copilotkit_shared.randomUUID)();
3707
+ const placeholder = {
3708
+ id: placeholderId,
3709
+ type: modality,
3710
+ source: {
3711
+ type: "data",
3712
+ value: "",
3713
+ mimeType: file.type
3714
+ },
3715
+ filename: file.name,
3716
+ size: file.size,
3717
+ status: "uploading"
3718
+ };
3719
+ setAttachments((prev) => [...prev, placeholder]);
3720
+ try {
3721
+ let source;
3722
+ let uploadMetadata;
3723
+ if (cfg?.onUpload) {
3724
+ const { metadata: meta, ...uploadSource } = await cfg.onUpload(file);
3725
+ source = uploadSource;
3726
+ uploadMetadata = meta;
3727
+ } else source = {
3728
+ type: "data",
3729
+ value: await (0, _copilotkit_shared.readFileAsBase64)(file),
3730
+ mimeType: file.type
3731
+ };
3732
+ let thumbnail;
3733
+ if (modality === "video") thumbnail = await (0, _copilotkit_shared.generateVideoThumbnail)(file);
3734
+ setAttachments((prev) => prev.map((att) => att.id === placeholderId ? {
3735
+ ...att,
3736
+ source,
3737
+ status: "ready",
3738
+ thumbnail,
3739
+ metadata: uploadMetadata
3740
+ } : att));
3741
+ } catch (error) {
3742
+ setAttachments((prev) => prev.filter((att) => att.id !== placeholderId));
3743
+ console.error(`[CopilotKit] Failed to upload "${file.name}":`, error);
3744
+ cfg?.onUploadFailed?.({
3745
+ reason: "upload-failed",
3746
+ file,
3747
+ message: error instanceof Error ? error.message : `Failed to upload "${file.name}"`
3748
+ });
3749
+ }
3750
+ }
3751
+ }, []);
3752
+ const handleFileUpload = (0, react.useCallback)(async (e) => {
3753
+ if (!e.target.files?.length) return;
3754
+ try {
3755
+ await processFiles(Array.from(e.target.files));
3756
+ } catch (error) {
3757
+ console.error("[CopilotKit] Upload error:", error);
3758
+ }
3759
+ }, [processFiles]);
3760
+ const handleDragOver = (0, react.useCallback)((e) => {
3761
+ if (!configRef.current?.enabled) return;
3762
+ e.preventDefault();
3763
+ e.stopPropagation();
3764
+ setDragOver(true);
3765
+ }, []);
3766
+ const handleDragLeave = (0, react.useCallback)((e) => {
3767
+ e.preventDefault();
3768
+ e.stopPropagation();
3769
+ setDragOver(false);
3770
+ }, []);
3771
+ const handleDrop = (0, react.useCallback)(async (e) => {
3772
+ e.preventDefault();
3773
+ e.stopPropagation();
3774
+ setDragOver(false);
3775
+ if (!configRef.current?.enabled) return;
3776
+ const files = Array.from(e.dataTransfer.files);
3777
+ if (files.length > 0) try {
3778
+ await processFiles(files);
3779
+ } catch (error) {
3780
+ console.error("[CopilotKit] Drop error:", error);
3781
+ }
3782
+ }, [processFiles]);
3783
+ (0, react.useEffect)(() => {
3784
+ if (!enabled) return;
3785
+ const handlePaste = async (e) => {
3786
+ const target = e.target;
3787
+ if (!target || !containerRef.current?.contains(target)) return;
3788
+ const accept = configRef.current?.accept ?? "*/*";
3789
+ const fileItems = Array.from(e.clipboardData?.items || []).filter((item) => item.kind === "file" && item.getAsFile() !== null && (0, _copilotkit_shared.matchesAcceptFilter)(item.getAsFile(), accept));
3790
+ if (fileItems.length === 0) return;
3791
+ e.preventDefault();
3792
+ const files = fileItems.map((item) => item.getAsFile()).filter((f) => f !== null);
3793
+ try {
3794
+ await processFiles(files);
3795
+ } catch (error) {
3796
+ console.error("[CopilotKit] Paste error:", error);
3797
+ }
3798
+ };
3799
+ document.addEventListener("paste", handlePaste);
3800
+ return () => document.removeEventListener("paste", handlePaste);
3801
+ }, [enabled, processFiles]);
3802
+ return {
3803
+ attachments,
3804
+ enabled,
3805
+ dragOver,
3806
+ fileInputRef,
3807
+ containerRef,
3808
+ processFiles,
3809
+ handleFileUpload,
3810
+ handleDragOver,
3811
+ handleDragLeave,
3812
+ handleDrop,
3813
+ removeAttachment: (0, react.useCallback)((id) => {
3814
+ setAttachments((prev) => prev.filter((a) => a.id !== id));
3815
+ }, []),
3816
+ consumeAttachments: (0, react.useCallback)(() => {
3817
+ const ready = attachmentsRef.current.filter((a) => a.status === "ready");
3818
+ if (ready.length === 0) return ready;
3819
+ setAttachments((prev) => prev.filter((a) => a.status !== "ready"));
3820
+ if (fileInputRef.current) fileInputRef.current.value = "";
3821
+ return ready;
3822
+ }, [])
3823
+ };
3824
+ }
3825
+
3670
3826
  //#endregion
3671
3827
  //#region src/v2/components/chat/CopilotChatToolCallsView.tsx
3672
3828
  function CopilotChatToolCallsView({ message, messages = [] }) {
@@ -3783,9 +3939,19 @@ function CopilotChatAssistantMessage({ message, messages, isRunning, onThumbsUp,
3783
3939
  _CopilotChatAssistantMessage.CopyButton = ({ className, title, onClick, ...props }) => {
3784
3940
  const labels = useCopilotChatConfiguration()?.labels ?? CopilotChatDefaultLabels;
3785
3941
  const [copied, setCopied] = (0, react.useState)(false);
3942
+ const timerRef = (0, react.useRef)(null);
3943
+ (0, react.useEffect)(() => {
3944
+ return () => {
3945
+ if (timerRef.current !== null) clearTimeout(timerRef.current);
3946
+ };
3947
+ }, []);
3786
3948
  const handleClick = (event) => {
3787
3949
  setCopied(true);
3788
- setTimeout(() => setCopied(false), 2e3);
3950
+ if (timerRef.current !== null) clearTimeout(timerRef.current);
3951
+ timerRef.current = setTimeout(() => {
3952
+ timerRef.current = null;
3953
+ setCopied(false);
3954
+ }, 2e3);
3789
3955
  if (onClick) onClick(event);
3790
3956
  };
3791
3957
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolbarButton, {
@@ -3843,6 +4009,79 @@ CopilotChatAssistantMessage.ReadAloudButton.displayName = "CopilotChatAssistantM
3843
4009
  CopilotChatAssistantMessage.RegenerateButton.displayName = "CopilotChatAssistantMessage.RegenerateButton";
3844
4010
  var CopilotChatAssistantMessage_default = CopilotChatAssistantMessage;
3845
4011
 
4012
+ //#endregion
4013
+ //#region src/v2/components/chat/CopilotChatAttachmentRenderer.tsx
4014
+ const ImageAttachment = (0, react.memo)(function ImageAttachment({ src, className }) {
4015
+ const [error, setError] = (0, react.useState)(false);
4016
+ if (error) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4017
+ className: cn("cpk:flex cpk:flex-col cpk:items-center cpk:justify-center cpk:rounded-lg cpk:bg-muted cpk:p-4 cpk:text-sm cpk:text-muted-foreground", className),
4018
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "Failed to load image" })
4019
+ });
4020
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
4021
+ src,
4022
+ alt: "Image attachment",
4023
+ className: cn("cpk:max-w-full cpk:h-auto cpk:rounded-lg", className),
4024
+ onError: () => setError(true)
4025
+ });
4026
+ });
4027
+ const AudioAttachment = (0, react.memo)(function AudioAttachment({ src, filename, className }) {
4028
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4029
+ className: cn("cpk:flex cpk:flex-col cpk:gap-1", className),
4030
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("audio", {
4031
+ src,
4032
+ controls: true,
4033
+ preload: "metadata",
4034
+ className: "cpk:max-w-[300px] cpk:w-full cpk:h-10"
4035
+ }), filename && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4036
+ className: "cpk:text-xs cpk:text-muted-foreground cpk:truncate cpk:max-w-[300px]",
4037
+ children: filename
4038
+ })]
4039
+ });
4040
+ });
4041
+ const VideoAttachment = (0, react.memo)(function VideoAttachment({ src, className }) {
4042
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("video", {
4043
+ src,
4044
+ controls: true,
4045
+ preload: "metadata",
4046
+ className: cn("cpk:max-w-[400px] cpk:w-full cpk:rounded-lg", className)
4047
+ });
4048
+ });
4049
+ const DocumentAttachment = (0, react.memo)(function DocumentAttachment({ source, filename, className }) {
4050
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4051
+ className: cn("cpk:inline-flex cpk:items-center cpk:gap-2 cpk:px-3 cpk:py-2 cpk:border cpk:border-border cpk:rounded-lg cpk:bg-muted", className),
4052
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4053
+ className: "cpk:text-xs cpk:font-bold cpk:uppercase",
4054
+ children: (0, _copilotkit_shared.getDocumentIcon)(source.mimeType ?? "")
4055
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4056
+ className: "cpk:text-sm cpk:text-muted-foreground cpk:truncate",
4057
+ children: filename || source.mimeType || "Unknown type"
4058
+ })]
4059
+ });
4060
+ });
4061
+ const CopilotChatAttachmentRenderer = ({ type, source, filename, className }) => {
4062
+ const src = (0, _copilotkit_shared.getSourceUrl)(source);
4063
+ switch (type) {
4064
+ case "image": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ImageAttachment, {
4065
+ src,
4066
+ className
4067
+ });
4068
+ case "audio": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AudioAttachment, {
4069
+ src,
4070
+ filename,
4071
+ className
4072
+ });
4073
+ case "video": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(VideoAttachment, {
4074
+ src,
4075
+ className
4076
+ });
4077
+ case "document": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DocumentAttachment, {
4078
+ source,
4079
+ filename,
4080
+ className
4081
+ });
4082
+ }
4083
+ };
4084
+
3846
4085
  //#endregion
3847
4086
  //#region src/v2/components/chat/CopilotChatUserMessage.tsx
3848
4087
  function flattenUserMessageContent(content) {
@@ -3853,8 +4092,17 @@ function flattenUserMessageContent(content) {
3853
4092
  return "";
3854
4093
  }).filter((text) => text.length > 0).join("\n");
3855
4094
  }
4095
+ function getMediaParts(content) {
4096
+ if (!content || typeof content === "string") return [];
4097
+ return content.filter((part) => part.type === "image" || part.type === "audio" || part.type === "video" || part.type === "document");
4098
+ }
4099
+ function getFilename(part) {
4100
+ const meta = part.metadata;
4101
+ if (meta != null && typeof meta === "object" && "filename" in meta && typeof meta.filename === "string") return meta.filename;
4102
+ }
3856
4103
  function CopilotChatUserMessage({ message, onEditMessage, branchIndex, numberOfBranches, onSwitchToBranch, additionalToolbarItems, messageRenderer, toolbar, copyButton, editButton, branchNavigation, children, className, ...props }) {
3857
4104
  const flattenedContent = (0, react.useMemo)(() => flattenUserMessageContent(message.content), [message.content]);
4105
+ const mediaParts = (0, react.useMemo)(() => getMediaParts(message.content), [message.content]);
3858
4106
  const BoundMessageRenderer = renderSlot(messageRenderer, CopilotChatUserMessage.MessageRenderer, { content: flattenedContent });
3859
4107
  const BoundCopyButton = renderSlot(copyButton, CopilotChatUserMessage.CopyButton, { onClick: async () => {
3860
4108
  if (flattenedContent) try {
@@ -3901,7 +4149,18 @@ function CopilotChatUserMessage({ message, onEditMessage, branchIndex, numberOfB
3901
4149
  className: (0, tailwind_merge.twMerge)("copilotKitMessage copilotKitUserMessage cpk:flex cpk:flex-col cpk:items-end cpk:group cpk:pt-10", className),
3902
4150
  "data-message-id": message.id,
3903
4151
  ...props,
3904
- children: [BoundMessageRenderer, BoundToolbar]
4152
+ children: [
4153
+ BoundMessageRenderer,
4154
+ mediaParts.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4155
+ className: "cpk:flex cpk:flex-col cpk:items-end cpk:gap-2 cpk:mt-2",
4156
+ children: mediaParts.map((part, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopilotChatAttachmentRenderer, {
4157
+ type: part.type,
4158
+ source: part.source,
4159
+ filename: getFilename(part)
4160
+ }, index))
4161
+ }),
4162
+ BoundToolbar
4163
+ ]
3905
4164
  });
3906
4165
  }
3907
4166
  (function(_CopilotChatUserMessage) {
@@ -4235,9 +4494,43 @@ const CopilotChatSuggestionView = react.default.forwardRef(function CopilotChatS
4235
4494
  });
4236
4495
  CopilotChatSuggestionView.displayName = "CopilotChatSuggestionView";
4237
4496
 
4497
+ //#endregion
4498
+ //#region src/v2/components/chat/scroll-element-context.ts
4499
+ /**
4500
+ * Provides the scroll container element to child components that need it for
4501
+ * virtualization. Set by CopilotChatView.ScrollView; consumed by
4502
+ * CopilotChatMessageView to feed useVirtualizer's getScrollElement.
4503
+ *
4504
+ * Carries the element itself (not a ref) so that context consumers re-render
4505
+ * reactively when the scroll container is first mounted.
4506
+ */
4507
+ const ScrollElementContext = react.default.createContext(null);
4508
+
4238
4509
  //#endregion
4239
4510
  //#region src/v2/components/chat/CopilotChatMessageView.tsx
4240
4511
  /**
4512
+ * Resolves a slot value into a { Component, slotProps } pair, handling the three
4513
+ * slot forms: a component type, a className string, or a partial-props object.
4514
+ */
4515
+ function resolveSlotComponent(slot, DefaultComponent) {
4516
+ if (isReactComponentType(slot)) return {
4517
+ Component: slot,
4518
+ slotProps: void 0
4519
+ };
4520
+ if (typeof slot === "string") return {
4521
+ Component: DefaultComponent,
4522
+ slotProps: { className: slot }
4523
+ };
4524
+ if (slot && typeof slot === "object") return {
4525
+ Component: DefaultComponent,
4526
+ slotProps: slot
4527
+ };
4528
+ return {
4529
+ Component: DefaultComponent,
4530
+ slotProps: void 0
4531
+ };
4532
+ }
4533
+ /**
4241
4534
  * Memoized wrapper for assistant messages to prevent re-renders when other messages change.
4242
4535
  */
4243
4536
  const MemoizedAssistantMessage = react.default.memo(function MemoizedAssistantMessage({ message, messages, isRunning, AssistantMessageComponent, slotProps }) {
@@ -4256,7 +4549,6 @@ const MemoizedAssistantMessage = react.default.memo(function MemoizedAssistantMe
4256
4549
  if (prevToolCalls && nextToolCalls) for (let i = 0; i < prevToolCalls.length; i++) {
4257
4550
  const prevTc = prevToolCalls[i];
4258
4551
  const nextTc = nextToolCalls[i];
4259
- if (!prevTc || !nextTc) return false;
4260
4552
  if (prevTc.id !== nextTc.id) return false;
4261
4553
  if (prevTc.function.arguments !== nextTc.function.arguments) return false;
4262
4554
  }
@@ -4335,6 +4627,7 @@ const MemoizedCustomMessage = react.default.memo(function MemoizedCustomMessage(
4335
4627
  if (JSON.stringify(prevProps.stateSnapshot) !== JSON.stringify(nextProps.stateSnapshot)) return false;
4336
4628
  return true;
4337
4629
  });
4630
+ const VIRTUALIZE_THRESHOLD = 50;
4338
4631
  function CopilotChatMessageView({ messages = [], assistantMessage, userMessage, reasoningMessage, cursor, isRunning = false, children, className, ...props }) {
4339
4632
  const renderCustomMessage = useRenderCustomMessages();
4340
4633
  const { renderActivityMessage } = useRenderActivityMessage();
@@ -4368,9 +4661,34 @@ function CopilotChatMessageView({ messages = [], assistantMessage, userMessage,
4368
4661
  if (!resolvedRunId) return void 0;
4369
4662
  return copilotkit.getStateByRun(config.agentId, config.threadId, resolvedRunId);
4370
4663
  };
4371
- const deduplicatedMessages = [...new Map(messages.map((m) => [m.id, m])).values()];
4664
+ const deduplicatedMessages = (0, react.useMemo)(() => [...new Map(messages.map((m) => [m.id, m])).values()], [messages]);
4372
4665
  if (process.env.NODE_ENV === "development" && deduplicatedMessages.length < messages.length) console.warn(`CopilotChatMessageView: Deduplicated ${messages.length - deduplicatedMessages.length} message(s) with duplicate IDs.`);
4373
- const messageElements = deduplicatedMessages.flatMap((message) => {
4666
+ const { Component: AssistantComponent, slotProps: assistantSlotProps } = (0, react.useMemo)(() => resolveSlotComponent(assistantMessage, CopilotChatAssistantMessage_default), [assistantMessage]);
4667
+ const { Component: UserComponent, slotProps: userSlotProps } = (0, react.useMemo)(() => resolveSlotComponent(userMessage, CopilotChatUserMessage_default), [userMessage]);
4668
+ const { Component: ReasoningComponent, slotProps: reasoningSlotProps } = (0, react.useMemo)(() => resolveSlotComponent(reasoningMessage, CopilotChatReasoningMessage_default), [reasoningMessage]);
4669
+ const scrollElementFromCtx = (0, react.useContext)(ScrollElementContext);
4670
+ const scrollElement = scrollElementFromCtx && scrollElementFromCtx.clientHeight > 0 ? scrollElementFromCtx : null;
4671
+ (0, react.useEffect)(() => {
4672
+ if (process.env.NODE_ENV !== "production" && scrollElementFromCtx && scrollElementFromCtx.clientHeight === 0) console.warn("[CopilotKit] Chat scroll container has clientHeight=0 — virtualization disabled. Ensure the chat is rendered in a visible container with a non-zero height.");
4673
+ }, [scrollElementFromCtx]);
4674
+ const shouldVirtualize = !!scrollElement && !children && deduplicatedMessages.length > VIRTUALIZE_THRESHOLD;
4675
+ const virtualizer = (0, _tanstack_react_virtual.useVirtualizer)({
4676
+ count: shouldVirtualize ? deduplicatedMessages.length : 0,
4677
+ getScrollElement: () => scrollElement,
4678
+ estimateSize: () => 100,
4679
+ overscan: 5,
4680
+ measureElement: (el) => el?.getBoundingClientRect().height ?? 0,
4681
+ initialRect: {
4682
+ width: 0,
4683
+ height: 600
4684
+ }
4685
+ });
4686
+ const firstMessageId = deduplicatedMessages[0]?.id;
4687
+ (0, react.useLayoutEffect)(() => {
4688
+ if (!shouldVirtualize || !deduplicatedMessages.length) return;
4689
+ virtualizer.scrollToIndex(deduplicatedMessages.length - 1, { align: "end" });
4690
+ }, [shouldVirtualize, firstMessageId]);
4691
+ const renderMessageBlock = (message) => {
4374
4692
  const elements = [];
4375
4693
  const stateSnapshot = getStateSnapshotForMessage(message.id);
4376
4694
  if (renderCustomMessage) elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedCustomMessage, {
@@ -4379,58 +4697,38 @@ function CopilotChatMessageView({ messages = [], assistantMessage, userMessage,
4379
4697
  renderCustomMessage,
4380
4698
  stateSnapshot
4381
4699
  }, `${message.id}-custom-before`));
4382
- if (message.role === "assistant") {
4383
- let AssistantComponent = CopilotChatAssistantMessage_default;
4384
- let assistantSlotProps;
4385
- if (isReactComponentType(assistantMessage)) AssistantComponent = assistantMessage;
4386
- else if (typeof assistantMessage === "string") assistantSlotProps = { className: assistantMessage };
4387
- else if (assistantMessage && typeof assistantMessage === "object") assistantSlotProps = assistantMessage;
4388
- elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedAssistantMessage, {
4389
- message,
4390
- messages,
4391
- isRunning,
4392
- AssistantMessageComponent: AssistantComponent,
4393
- slotProps: assistantSlotProps
4394
- }, message.id));
4395
- } else if (message.role === "user") {
4396
- let UserComponent = CopilotChatUserMessage_default;
4397
- let userSlotProps;
4398
- if (isReactComponentType(userMessage)) UserComponent = userMessage;
4399
- else if (typeof userMessage === "string") userSlotProps = { className: userMessage };
4400
- else if (userMessage && typeof userMessage === "object") userSlotProps = userMessage;
4401
- elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedUserMessage, {
4402
- message,
4403
- UserMessageComponent: UserComponent,
4404
- slotProps: userSlotProps
4405
- }, message.id));
4406
- } else if (message.role === "activity") {
4407
- const activityMsg = message;
4408
- elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedActivityMessage, {
4409
- message: activityMsg,
4410
- renderActivityMessage
4411
- }, message.id));
4412
- } else if (message.role === "reasoning") {
4413
- let ReasoningComponent = CopilotChatReasoningMessage_default;
4414
- let reasoningSlotProps;
4415
- if (isReactComponentType(reasoningMessage)) ReasoningComponent = reasoningMessage;
4416
- else if (typeof reasoningMessage === "string") reasoningSlotProps = { className: reasoningMessage };
4417
- else if (reasoningMessage && typeof reasoningMessage === "object") reasoningSlotProps = reasoningMessage;
4418
- elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedReasoningMessage, {
4419
- message,
4420
- messages,
4421
- isRunning,
4422
- ReasoningMessageComponent: ReasoningComponent,
4423
- slotProps: reasoningSlotProps
4424
- }, message.id));
4425
- }
4700
+ if (message.role === "assistant") elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedAssistantMessage, {
4701
+ message,
4702
+ messages,
4703
+ isRunning,
4704
+ AssistantMessageComponent: AssistantComponent,
4705
+ slotProps: assistantSlotProps
4706
+ }, message.id));
4707
+ else if (message.role === "user") elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedUserMessage, {
4708
+ message,
4709
+ UserMessageComponent: UserComponent,
4710
+ slotProps: userSlotProps
4711
+ }, message.id));
4712
+ else if (message.role === "activity") elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedActivityMessage, {
4713
+ message,
4714
+ renderActivityMessage
4715
+ }, message.id));
4716
+ else if (message.role === "reasoning") elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedReasoningMessage, {
4717
+ message,
4718
+ messages,
4719
+ isRunning,
4720
+ ReasoningMessageComponent: ReasoningComponent,
4721
+ slotProps: reasoningSlotProps
4722
+ }, message.id));
4426
4723
  if (renderCustomMessage) elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedCustomMessage, {
4427
4724
  message,
4428
4725
  position: "after",
4429
4726
  renderCustomMessage,
4430
4727
  stateSnapshot
4431
4728
  }, `${message.id}-custom-after`));
4432
- return elements;
4433
- }).filter(Boolean);
4729
+ return elements.filter(Boolean);
4730
+ };
4731
+ const messageElements = shouldVirtualize ? [] : deduplicatedMessages.flatMap(renderMessageBlock);
4434
4732
  if (children) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4435
4733
  "data-copilotkit": true,
4436
4734
  style: { display: "contents" },
@@ -4449,7 +4747,27 @@ function CopilotChatMessageView({ messages = [], assistantMessage, userMessage,
4449
4747
  className: (0, tailwind_merge.twMerge)("copilotKitMessages cpk:flex cpk:flex-col", className),
4450
4748
  ...props,
4451
4749
  children: [
4452
- messageElements,
4750
+ shouldVirtualize ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4751
+ style: {
4752
+ height: virtualizer.getTotalSize(),
4753
+ position: "relative"
4754
+ },
4755
+ children: virtualizer.getVirtualItems().map((virtualItem) => {
4756
+ const message = deduplicatedMessages[virtualItem.index];
4757
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4758
+ "data-index": virtualItem.index,
4759
+ ref: virtualizer.measureElement,
4760
+ style: {
4761
+ position: "absolute",
4762
+ top: 0,
4763
+ left: 0,
4764
+ width: "100%",
4765
+ transform: `translateY(${virtualItem.start}px)`
4766
+ },
4767
+ children: renderMessageBlock(message)
4768
+ }, message.id);
4769
+ })
4770
+ }) : messageElements,
4453
4771
  interruptElement,
4454
4772
  showCursor && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4455
4773
  className: "cpk:mt-2",
@@ -4466,6 +4784,312 @@ CopilotChatMessageView.Cursor = function Cursor({ className, ...props }) {
4466
4784
  });
4467
4785
  };
4468
4786
 
4787
+ //#endregion
4788
+ //#region src/v2/components/chat/CopilotChatAttachmentQueue.tsx
4789
+ const CopilotChatAttachmentQueue = ({ attachments, onRemoveAttachment, className }) => {
4790
+ if (attachments.length === 0) return null;
4791
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4792
+ className: cn("cpk:flex cpk:flex-wrap cpk:gap-2 cpk:p-2", className),
4793
+ children: attachments.map((attachment) => {
4794
+ const isMedia = attachment.type === "image" || attachment.type === "video";
4795
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4796
+ className: cn("cpk:relative cpk:inline-flex cpk:rounded-lg cpk:overflow-hidden cpk:border cpk:border-border", isMedia ? "cpk:w-[72px] cpk:h-[72px]" : attachment.type === "audio" ? "cpk:min-w-[200px] cpk:max-w-[280px] cpk:flex-col cpk:p-1 cpk:pr-8" : "cpk:p-2 cpk:px-3 cpk:pr-8 cpk:max-w-[240px]"),
4797
+ children: [
4798
+ attachment.status === "uploading" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UploadingOverlay, {}),
4799
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AttachmentPreview, { attachment }),
4800
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4801
+ onClick: () => onRemoveAttachment(attachment.id),
4802
+ className: cn("cpk:absolute cpk:bg-black/60 cpk:text-white cpk:border-none cpk:rounded-full cpk:w-5 cpk:h-5 cpk:flex cpk:items-center cpk:justify-center cpk:cursor-pointer cpk:text-[10px] cpk:z-20", isMedia ? "cpk:top-1 cpk:right-1" : "cpk:top-1.5 cpk:right-1.5"),
4803
+ "aria-label": "Remove attachment",
4804
+ children: "✕"
4805
+ })
4806
+ ]
4807
+ }, attachment.id);
4808
+ })
4809
+ });
4810
+ };
4811
+ function UploadingOverlay() {
4812
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4813
+ className: "cpk:absolute cpk:inset-0 cpk:flex cpk:items-center cpk:justify-center cpk:bg-black/40 cpk:z-10",
4814
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "cpk:w-5 cpk:h-5 cpk:border-2 cpk:border-white cpk:border-t-transparent cpk:rounded-full cpk:animate-spin" })
4815
+ });
4816
+ }
4817
+ function AttachmentPreview({ attachment }) {
4818
+ if (attachment.status === "uploading") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "cpk:w-full cpk:h-full" });
4819
+ switch (attachment.type) {
4820
+ case "image": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ImagePreview, { attachment });
4821
+ case "audio": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AudioPreview, { attachment });
4822
+ case "video": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(VideoPreview, { attachment });
4823
+ case "document": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DocumentPreview, { attachment });
4824
+ }
4825
+ }
4826
+ function Lightbox({ onClose, children }) {
4827
+ (0, react.useEffect)(() => {
4828
+ const handleKey = (e) => {
4829
+ if (e.key === "Escape") onClose();
4830
+ };
4831
+ document.addEventListener("keydown", handleKey);
4832
+ return () => document.removeEventListener("keydown", handleKey);
4833
+ }, [onClose]);
4834
+ if (typeof document === "undefined") return null;
4835
+ return (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4836
+ className: "cpk:fixed cpk:inset-0 cpk:z-[9999] cpk:flex cpk:items-center cpk:justify-center cpk:bg-black/80 cpk:animate-fade-in",
4837
+ onClick: onClose,
4838
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4839
+ onClick: onClose,
4840
+ className: "cpk:absolute cpk:top-4 cpk:right-4 cpk:text-white cpk:bg-white/10 cpk:hover:bg-white/20 cpk:rounded-full cpk:w-10 cpk:h-10 cpk:flex cpk:items-center cpk:justify-center cpk:cursor-pointer cpk:border-none cpk:z-10",
4841
+ "aria-label": "Close preview",
4842
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.X, { className: "cpk:w-5 cpk:h-5" })
4843
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4844
+ onClick: (e) => e.stopPropagation(),
4845
+ children
4846
+ })]
4847
+ }), document.body);
4848
+ }
4849
+ /**
4850
+ * Hook that manages lightbox open/close and uses the View Transition API to
4851
+ * morph the thumbnail into fullscreen content.
4852
+ *
4853
+ * The trick: `view-transition-name` must live on exactly ONE element at a time.
4854
+ * - Old state (thumbnail visible): name is on the thumbnail.
4855
+ * - New state (lightbox visible): name moves to the lightbox content.
4856
+ * `flushSync` ensures React commits the DOM change synchronously inside the
4857
+ * `startViewTransition` callback so the API can snapshot old → new correctly.
4858
+ */
4859
+ function useLightbox() {
4860
+ const thumbnailRef = (0, react.useRef)(null);
4861
+ const [open, setOpen] = (0, react.useState)(false);
4862
+ const vtName = (0, react.useId)();
4863
+ return {
4864
+ thumbnailRef,
4865
+ vtName,
4866
+ open,
4867
+ openLightbox: (0, react.useCallback)(() => {
4868
+ const thumb = thumbnailRef.current;
4869
+ const doc = document;
4870
+ if (doc.startViewTransition && thumb) {
4871
+ thumb.style.viewTransitionName = vtName;
4872
+ doc.startViewTransition(() => {
4873
+ thumb.style.viewTransitionName = "";
4874
+ (0, react_dom.flushSync)(() => setOpen(true));
4875
+ });
4876
+ } else setOpen(true);
4877
+ }, []),
4878
+ closeLightbox: (0, react.useCallback)(() => {
4879
+ const thumb = thumbnailRef.current;
4880
+ const doc = document;
4881
+ if (doc.startViewTransition && thumb) doc.startViewTransition(() => {
4882
+ (0, react_dom.flushSync)(() => setOpen(false));
4883
+ thumb.style.viewTransitionName = vtName;
4884
+ }).finished.then(() => {
4885
+ thumb.style.viewTransitionName = "";
4886
+ }).catch(() => {
4887
+ thumb.style.viewTransitionName = "";
4888
+ });
4889
+ else setOpen(false);
4890
+ }, [])
4891
+ };
4892
+ }
4893
+ function ImagePreview({ attachment }) {
4894
+ const src = (0, _copilotkit_shared.getSourceUrl)(attachment.source);
4895
+ const { thumbnailRef, vtName, open, openLightbox, closeLightbox } = useLightbox();
4896
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
4897
+ ref: thumbnailRef,
4898
+ src,
4899
+ alt: attachment.filename || "Image attachment",
4900
+ className: "cpk:w-full cpk:h-full cpk:object-cover cpk:cursor-pointer",
4901
+ onClick: openLightbox
4902
+ }), open && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Lightbox, {
4903
+ onClose: closeLightbox,
4904
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
4905
+ style: { viewTransitionName: vtName },
4906
+ src,
4907
+ alt: attachment.filename || "Image attachment",
4908
+ className: "cpk:max-w-[90vw] cpk:max-h-[90vh] cpk:object-contain cpk:rounded-lg"
4909
+ })
4910
+ })] });
4911
+ }
4912
+ function AudioPreview({ attachment }) {
4913
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4914
+ className: "cpk:flex cpk:flex-col cpk:gap-1 cpk:w-full",
4915
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("audio", {
4916
+ src: (0, _copilotkit_shared.getSourceUrl)(attachment.source),
4917
+ controls: true,
4918
+ preload: "metadata",
4919
+ className: "cpk:w-full cpk:h-8"
4920
+ }), attachment.filename && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4921
+ className: "cpk:text-xs cpk:font-medium cpk:overflow-hidden cpk:text-ellipsis cpk:whitespace-nowrap",
4922
+ children: attachment.filename
4923
+ })]
4924
+ });
4925
+ }
4926
+ function VideoPreview({ attachment }) {
4927
+ const src = (0, _copilotkit_shared.getSourceUrl)(attachment.source);
4928
+ const { thumbnailRef, vtName, open, openLightbox, closeLightbox } = useLightbox();
4929
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
4930
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4931
+ ref: thumbnailRef,
4932
+ className: "cpk:w-full cpk:h-full",
4933
+ children: attachment.thumbnail ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
4934
+ src: attachment.thumbnail,
4935
+ alt: attachment.filename || "Video thumbnail",
4936
+ className: "cpk:w-full cpk:h-full cpk:object-cover"
4937
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("video", {
4938
+ src,
4939
+ preload: "metadata",
4940
+ muted: true,
4941
+ className: "cpk:w-full cpk:h-full cpk:object-cover"
4942
+ })
4943
+ }),
4944
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4945
+ onClick: openLightbox,
4946
+ className: "cpk:absolute cpk:inset-0 cpk:flex cpk:items-center cpk:justify-center cpk:z-10 cpk:cursor-pointer cpk:bg-black/20 cpk:border-none cpk:p-0",
4947
+ "aria-label": "Play video",
4948
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4949
+ className: "cpk:w-8 cpk:h-8 cpk:rounded-full cpk:bg-black/60 cpk:flex cpk:items-center cpk:justify-center",
4950
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Play, { className: "cpk:w-4 cpk:h-4 cpk:text-white cpk:ml-0.5" })
4951
+ })
4952
+ }),
4953
+ open && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Lightbox, {
4954
+ onClose: closeLightbox,
4955
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("video", {
4956
+ style: { viewTransitionName: vtName },
4957
+ src,
4958
+ controls: true,
4959
+ autoPlay: true,
4960
+ className: "cpk:max-w-[90vw] cpk:max-h-[90vh] cpk:rounded-lg"
4961
+ })
4962
+ })
4963
+ ] });
4964
+ }
4965
+ function isPdf(mimeType) {
4966
+ return !!mimeType && mimeType.includes("pdf");
4967
+ }
4968
+ function isText(mimeType) {
4969
+ return !!mimeType && mimeType.startsWith("text/");
4970
+ }
4971
+ function canPreviewInBrowser(mimeType) {
4972
+ return isPdf(mimeType) || isText(mimeType);
4973
+ }
4974
+ /**
4975
+ * Convert a base64-encoded data source to a blob: URL that browsers will
4976
+ * render inside an iframe (data: URLs are blocked for PDFs in most browsers).
4977
+ */
4978
+ function useBlobUrl(attachment) {
4979
+ const [url, setUrl] = (0, react.useState)(null);
4980
+ (0, react.useEffect)(() => {
4981
+ if (attachment.source.type !== "data") return;
4982
+ try {
4983
+ const binary = atob(attachment.source.value);
4984
+ const bytes = new Uint8Array(binary.length);
4985
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
4986
+ const blob = new Blob([bytes], { type: attachment.source.mimeType || "application/octet-stream" });
4987
+ const blobUrl = URL.createObjectURL(blob);
4988
+ setUrl(blobUrl);
4989
+ return () => URL.revokeObjectURL(blobUrl);
4990
+ } catch (error) {
4991
+ console.error("[CopilotKit] Failed to decode attachment data:", error);
4992
+ setUrl(null);
4993
+ }
4994
+ }, [
4995
+ attachment.source.type,
4996
+ attachment.source.value,
4997
+ attachment.source.mimeType
4998
+ ]);
4999
+ if (attachment.source.type === "url") return attachment.source.value;
5000
+ return url;
5001
+ }
5002
+ function DocumentLightboxContent({ attachment, vtName }) {
5003
+ const mimeType = attachment.source.mimeType;
5004
+ const blobUrl = useBlobUrl(attachment);
5005
+ if (isPdf(mimeType)) {
5006
+ if (!blobUrl) return null;
5007
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("iframe", {
5008
+ style: { viewTransitionName: vtName },
5009
+ src: blobUrl,
5010
+ title: attachment.filename || "PDF preview",
5011
+ className: "cpk:w-[90vw] cpk:h-[90vh] cpk:max-w-[1000px] cpk:rounded-lg cpk:bg-white"
5012
+ });
5013
+ }
5014
+ if (isText(mimeType)) {
5015
+ const textContent = attachment.source.type === "data" ? (() => {
5016
+ try {
5017
+ return atob(attachment.source.value);
5018
+ } catch {
5019
+ return attachment.source.value;
5020
+ }
5021
+ })() : null;
5022
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5023
+ style: { viewTransitionName: vtName },
5024
+ className: "cpk:w-[90vw] cpk:max-w-[800px] cpk:max-h-[90vh] cpk:overflow-auto cpk:rounded-lg cpk:bg-white cpk:dark:bg-gray-900 cpk:p-6",
5025
+ children: [attachment.filename && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5026
+ className: "cpk:text-sm cpk:font-medium cpk:text-gray-500 cpk:dark:text-gray-400 cpk:mb-4 cpk:pb-2 cpk:border-b cpk:border-gray-200 cpk:dark:border-gray-700",
5027
+ children: attachment.filename
5028
+ }), textContent ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
5029
+ className: "cpk:text-sm cpk:whitespace-pre-wrap cpk:break-words cpk:text-gray-800 cpk:dark:text-gray-200 cpk:font-mono cpk:m-0",
5030
+ children: textContent
5031
+ }) : blobUrl ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("iframe", {
5032
+ src: blobUrl,
5033
+ title: attachment.filename || "Text preview",
5034
+ className: "cpk:w-full cpk:h-[80vh] cpk:border-none"
5035
+ }) : null]
5036
+ });
5037
+ }
5038
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5039
+ style: { viewTransitionName: vtName },
5040
+ className: "cpk:flex cpk:flex-col cpk:items-center cpk:gap-4 cpk:p-8 cpk:rounded-lg cpk:bg-white cpk:dark:bg-gray-900",
5041
+ children: [
5042
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5043
+ className: "cpk:w-16 cpk:h-16 cpk:rounded-xl cpk:bg-primary cpk:text-primary-foreground cpk:flex cpk:items-center cpk:justify-center cpk:text-xl cpk:font-bold",
5044
+ children: (0, _copilotkit_shared.getDocumentIcon)(mimeType ?? "")
5045
+ }),
5046
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5047
+ className: "cpk:text-center",
5048
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5049
+ className: "cpk:text-base cpk:font-medium cpk:text-gray-800 cpk:dark:text-gray-200",
5050
+ children: attachment.filename || "Document"
5051
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5052
+ className: "cpk:text-sm cpk:text-gray-500 cpk:dark:text-gray-400 cpk:mt-1",
5053
+ children: [mimeType || "Unknown type", attachment.size != null && ` · ${(0, _copilotkit_shared.formatFileSize)(attachment.size)}`]
5054
+ })]
5055
+ }),
5056
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5057
+ className: "cpk:text-xs cpk:text-gray-400 cpk:dark:text-gray-500",
5058
+ children: "No preview available for this file type"
5059
+ })
5060
+ ]
5061
+ });
5062
+ }
5063
+ function DocumentPreview({ attachment }) {
5064
+ const { thumbnailRef, vtName, open, openLightbox, closeLightbox } = useLightbox();
5065
+ const mimeType = attachment.source.mimeType;
5066
+ const previewable = canPreviewInBrowser(mimeType);
5067
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5068
+ ref: thumbnailRef,
5069
+ className: cn("cpk:flex cpk:items-center cpk:gap-2", previewable && "cpk:cursor-pointer"),
5070
+ onClick: previewable ? openLightbox : void 0,
5071
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5072
+ className: "cpk:w-8 cpk:h-8 cpk:rounded-md cpk:bg-primary cpk:text-primary-foreground cpk:flex cpk:items-center cpk:justify-center cpk:text-[10px] cpk:font-semibold cpk:shrink-0",
5073
+ children: (0, _copilotkit_shared.getDocumentIcon)(mimeType ?? "")
5074
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5075
+ className: "cpk:flex cpk:flex-col cpk:min-w-0",
5076
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5077
+ className: "cpk:text-xs cpk:font-medium cpk:break-all cpk:leading-tight",
5078
+ children: attachment.filename || "Document"
5079
+ }), attachment.size != null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5080
+ className: "cpk:text-[11px] cpk:text-muted-foreground",
5081
+ children: (0, _copilotkit_shared.formatFileSize)(attachment.size)
5082
+ })]
5083
+ })]
5084
+ }), open && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Lightbox, {
5085
+ onClose: closeLightbox,
5086
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DocumentLightboxContent, {
5087
+ attachment,
5088
+ vtName
5089
+ })
5090
+ })] });
5091
+ }
5092
+
4469
5093
  //#endregion
4470
5094
  //#region src/v2/hooks/use-keyboard-height.tsx
4471
5095
  /**
@@ -4510,7 +5134,19 @@ function useKeyboardHeight() {
4510
5134
  //#endregion
4511
5135
  //#region src/v2/components/chat/CopilotChatView.tsx
4512
5136
  const FEATHER_HEIGHT = 96;
4513
- function CopilotChatView({ messageView, input, scrollView, suggestionView, welcomeScreen, messages = [], autoScroll = true, isRunning = false, suggestions, suggestionLoadingIndexes, onSelectSuggestion, onSubmitMessage, onStop, inputMode, inputValue, onInputChange, onStartTranscribe, onCancelTranscribe, onFinishTranscribe, onFinishTranscribeWithAudio, disclaimer, children, className, ...props }) {
5137
+ function DropOverlay() {
5138
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5139
+ className: cn("cpk:absolute cpk:inset-0 cpk:z-50 cpk:pointer-events-none", "cpk:flex cpk:items-center cpk:justify-center", "cpk:bg-primary/5 cpk:backdrop-blur-[2px]", "cpk:border-2 cpk:border-dashed cpk:border-primary/40 cpk:rounded-lg cpk:m-2"),
5140
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5141
+ className: "cpk:flex cpk:flex-col cpk:items-center cpk:gap-2 cpk:text-primary/70",
5142
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Upload, { className: "cpk:w-8 cpk:h-8" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5143
+ className: "cpk:text-sm cpk:font-medium",
5144
+ children: "Drop files here"
5145
+ })]
5146
+ })
5147
+ });
5148
+ }
5149
+ function CopilotChatView({ messageView, input, scrollView, suggestionView, welcomeScreen, messages = [], autoScroll = true, isRunning = false, suggestions, suggestionLoadingIndexes, onSelectSuggestion, onSubmitMessage, onStop, inputMode, inputValue, onInputChange, onStartTranscribe, onCancelTranscribe, onFinishTranscribe, onFinishTranscribeWithAudio, attachments, onRemoveAttachment, onAddFile, dragOver, onDragOver, onDragLeave, onDrop, disclaimer, children, className, ...props }) {
4514
5150
  const inputContainerRef = (0, react.useRef)(null);
4515
5151
  const [inputContainerHeight, setInputContainerHeight] = (0, react.useState)(0);
4516
5152
  const [isResizing, setIsResizing] = (0, react.useState)(false);
@@ -4557,6 +5193,7 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
4557
5193
  onCancelTranscribe,
4558
5194
  onFinishTranscribe,
4559
5195
  onFinishTranscribeWithAudio,
5196
+ onAddFile,
4560
5197
  positioning: "static",
4561
5198
  keyboardHeight: isKeyboardOpen ? keyboardHeight : 0,
4562
5199
  containerRef: inputContainerRef,
@@ -4597,21 +5234,34 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
4597
5234
  onCancelTranscribe,
4598
5235
  onFinishTranscribe,
4599
5236
  onFinishTranscribeWithAudio,
5237
+ onAddFile,
4600
5238
  positioning: "static",
4601
5239
  showDisclaimer: true,
4602
5240
  ...disclaimer !== void 0 ? { disclaimer } : {}
4603
5241
  });
4604
- const BoundWelcomeScreen = renderSlot(welcomeScreen === true ? void 0 : welcomeScreen, CopilotChatView.WelcomeScreen, {
4605
- input: BoundInputForWelcome,
5242
+ const welcomeScreenSlot = welcomeScreen === true ? void 0 : welcomeScreen;
5243
+ const inputWithAttachments = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5244
+ className: "cpk:w-full",
5245
+ children: [attachments && attachments.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopilotChatAttachmentQueue, {
5246
+ attachments,
5247
+ onRemoveAttachment: (id) => onRemoveAttachment?.(id),
5248
+ className: "cpk:mb-2"
5249
+ }), BoundInputForWelcome]
5250
+ });
5251
+ const BoundWelcomeScreen = renderSlot(welcomeScreenSlot, CopilotChatView.WelcomeScreen, {
5252
+ input: inputWithAttachments,
4606
5253
  suggestionView: BoundSuggestionView ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, {})
4607
5254
  });
4608
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5255
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4609
5256
  "data-copilotkit": true,
4610
5257
  "data-testid": "copilot-chat",
4611
5258
  "data-copilot-running": isRunning ? "true" : "false",
4612
- className: (0, tailwind_merge.twMerge)("copilotKitChat cpk:relative cpk:h-full cpk:flex cpk:flex-col", className),
5259
+ onDragOver,
5260
+ onDragLeave,
5261
+ onDrop,
5262
+ className: cn("copilotKitChat cpk:relative cpk:h-full cpk:flex cpk:flex-col", className),
4613
5263
  ...props,
4614
- children: BoundWelcomeScreen
5264
+ children: [dragOver && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DropOverlay, {}), BoundWelcomeScreen]
4615
5265
  });
4616
5266
  }
4617
5267
  if (children) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -4628,39 +5278,66 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
4628
5278
  "data-copilotkit": true,
4629
5279
  "data-testid": "copilot-chat",
4630
5280
  "data-copilot-running": isRunning ? "true" : "false",
4631
- className: (0, tailwind_merge.twMerge)("copilotKitChat cpk:relative cpk:h-full cpk:flex cpk:flex-col", className),
5281
+ onDragOver,
5282
+ onDragLeave,
5283
+ onDrop,
5284
+ className: cn("copilotKitChat cpk:relative cpk:h-full cpk:flex cpk:flex-col", className),
4632
5285
  ...props,
4633
- children: [BoundScrollView, BoundInput]
5286
+ children: [
5287
+ dragOver && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DropOverlay, {}),
5288
+ BoundScrollView,
5289
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5290
+ className: "cpk:max-w-3xl cpk:mx-auto cpk:w-full",
5291
+ children: attachments && attachments.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopilotChatAttachmentQueue, {
5292
+ attachments,
5293
+ onRemoveAttachment: (id) => onRemoveAttachment?.(id),
5294
+ className: "cpk:px-4"
5295
+ })
5296
+ }),
5297
+ BoundInput
5298
+ ]
4634
5299
  });
4635
5300
  }
4636
5301
  (function(_CopilotChatView) {
4637
5302
  const ScrollContent = ({ children, scrollToBottomButton, feather, inputContainerHeight, isResizing }) => {
4638
- const { isAtBottom, scrollToBottom } = (0, use_stick_to_bottom.useStickToBottomContext)();
5303
+ const { isAtBottom, scrollToBottom, scrollRef } = (0, use_stick_to_bottom.useStickToBottomContext)();
5304
+ const [scrollEl, setScrollEl] = (0, react.useState)(null);
5305
+ (0, react.useLayoutEffect)(() => {
5306
+ setScrollEl(scrollRef.current ?? null);
5307
+ }, []);
4639
5308
  const BoundFeather = renderSlot(feather, CopilotChatView.Feather, {});
4640
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
4641
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(use_stick_to_bottom.StickToBottom.Content, {
4642
- className: "cpk:overflow-y-auto cpk:overflow-x-hidden",
4643
- style: {
4644
- flex: "1 1 0%",
4645
- minHeight: 0
4646
- },
4647
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4648
- className: "cpk:px-4 cpk:sm:px-0 cpk:[div[data-sidebar-chat]_&]:px-8 cpk:[div[data-popup-chat]_&]:px-6",
4649
- children
5309
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ScrollElementContext.Provider, {
5310
+ value: scrollEl,
5311
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
5312
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(use_stick_to_bottom.StickToBottom.Content, {
5313
+ className: "cpk:overflow-y-auto cpk:overflow-x-hidden",
5314
+ style: {
5315
+ flex: "1 1 0%",
5316
+ minHeight: 0
5317
+ },
5318
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5319
+ className: "cpk:px-4 cpk:sm:px-0 cpk:[div[data-sidebar-chat]_&]:px-8 cpk:[div[data-popup-chat]_&]:px-6",
5320
+ children
5321
+ })
5322
+ }),
5323
+ BoundFeather,
5324
+ !isAtBottom && !isResizing && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5325
+ className: "cpk:absolute cpk:inset-x-0 cpk:flex cpk:justify-center cpk:z-30 cpk:pointer-events-none",
5326
+ style: { bottom: `${inputContainerHeight + FEATHER_HEIGHT + 16}px` },
5327
+ children: renderSlot(scrollToBottomButton, CopilotChatView.ScrollToBottomButton, { onClick: () => scrollToBottom() })
4650
5328
  })
4651
- }),
4652
- BoundFeather,
4653
- !isAtBottom && !isResizing && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4654
- className: "cpk:absolute cpk:inset-x-0 cpk:flex cpk:justify-center cpk:z-30 cpk:pointer-events-none",
4655
- style: { bottom: `${inputContainerHeight + FEATHER_HEIGHT + 16}px` },
4656
- children: renderSlot(scrollToBottomButton, CopilotChatView.ScrollToBottomButton, { onClick: () => scrollToBottom() })
4657
- })
4658
- ] });
5329
+ ] })
5330
+ });
4659
5331
  };
4660
5332
  _CopilotChatView.ScrollView = ({ children, autoScroll = true, scrollToBottomButton, feather, inputContainerHeight = 0, isResizing = false, className, ...props }) => {
4661
5333
  const [hasMounted, setHasMounted] = (0, react.useState)(false);
4662
5334
  const { scrollRef, contentRef, scrollToBottom } = (0, use_stick_to_bottom.useStickToBottom)();
4663
5335
  const [showScrollButton, setShowScrollButton] = (0, react.useState)(false);
5336
+ const [nonAutoScrollEl, setNonAutoScrollEl] = (0, react.useState)(null);
5337
+ const nonAutoScrollRefCallback = (0, react.useCallback)((el) => {
5338
+ scrollRef.current = el;
5339
+ setNonAutoScrollEl(el);
5340
+ }, []);
4664
5341
  (0, react.useEffect)(() => {
4665
5342
  setHasMounted(true);
4666
5343
  }, []);
@@ -4689,23 +5366,26 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
4689
5366
  });
4690
5367
  if (!autoScroll) {
4691
5368
  const BoundFeather = renderSlot(feather, CopilotChatView.Feather, {});
4692
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4693
- ref: scrollRef,
4694
- className: cn("cpk:h-full cpk:max-h-full cpk:flex cpk:flex-col cpk:min-h-0 cpk:overflow-y-auto cpk:overflow-x-hidden cpk:relative", className),
4695
- ...props,
4696
- children: [
4697
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4698
- ref: contentRef,
4699
- className: "cpk:px-4 cpk:sm:px-0 cpk:[div[data-sidebar-chat]_&]:px-8 cpk:[div[data-popup-chat]_&]:px-6",
4700
- children
4701
- }),
4702
- BoundFeather,
4703
- showScrollButton && !isResizing && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4704
- className: "cpk:absolute cpk:inset-x-0 cpk:flex cpk:justify-center cpk:z-30 cpk:pointer-events-none",
4705
- style: { bottom: `${inputContainerHeight + FEATHER_HEIGHT + 16}px` },
4706
- children: renderSlot(scrollToBottomButton, CopilotChatView.ScrollToBottomButton, { onClick: () => scrollToBottom() })
4707
- })
4708
- ]
5369
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ScrollElementContext.Provider, {
5370
+ value: nonAutoScrollEl,
5371
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5372
+ ref: nonAutoScrollRefCallback,
5373
+ className: cn("cpk:h-full cpk:max-h-full cpk:flex cpk:flex-col cpk:min-h-0 cpk:overflow-y-auto cpk:overflow-x-hidden cpk:relative", className),
5374
+ ...props,
5375
+ children: [
5376
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5377
+ ref: contentRef,
5378
+ className: "cpk:px-4 cpk:sm:px-0 cpk:[div[data-sidebar-chat]_&]:px-8 cpk:[div[data-popup-chat]_&]:px-6",
5379
+ children
5380
+ }),
5381
+ BoundFeather,
5382
+ showScrollButton && !isResizing && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5383
+ className: "cpk:absolute cpk:inset-x-0 cpk:flex cpk:justify-center cpk:z-30 cpk:pointer-events-none",
5384
+ style: { bottom: `${inputContainerHeight + FEATHER_HEIGHT + 16}px` },
5385
+ children: renderSlot(scrollToBottomButton, CopilotChatView.ScrollToBottomButton, { onClick: () => scrollToBottom() })
5386
+ })
5387
+ ]
5388
+ })
4709
5389
  });
4710
5390
  }
4711
5391
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(use_stick_to_bottom.StickToBottom, {
@@ -4897,7 +5577,7 @@ async function transcribeAudio(core, audioBlob, filename = "recording.webm") {
4897
5577
 
4898
5578
  //#endregion
4899
5579
  //#region src/v2/components/chat/CopilotChat.tsx
4900
- function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen, onError, ...props }) {
5580
+ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen, attachments: attachmentsConfig, onError, ...props }) {
4901
5581
  const existingConfig = useCopilotChatConfiguration();
4902
5582
  const resolvedAgentId = agentId ?? existingConfig?.agentId ?? _copilotkit_shared.DEFAULT_AGENT_ID;
4903
5583
  const resolvedThreadId = (0, react.useMemo)(() => threadId ?? existingConfig?.threadId ?? (0, _copilotkit_shared.randomUUID)(), [threadId, existingConfig?.threadId]);
@@ -4933,6 +5613,7 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
4933
5613
  const [inputValue, setInputValue] = (0, react.useState)("");
4934
5614
  const [transcriptionError, setTranscriptionError] = (0, react.useState)(null);
4935
5615
  const [isTranscribing, setIsTranscribing] = (0, react.useState)(false);
5616
+ const { attachments: selectedAttachments, enabled: attachmentsEnabled, dragOver, fileInputRef, containerRef: chatContainerRef, handleFileUpload, handleDragOver, handleDragLeave, handleDrop, removeAttachment, consumeAttachments } = useAttachments({ config: attachmentsConfig });
4936
5617
  const isTranscriptionEnabled = copilotkit.audioFileTranscriptionEnabled;
4937
5618
  const isMediaRecorderSupported = typeof window !== "undefined" && typeof MediaRecorder !== "undefined";
4938
5619
  const { messageView: providedMessageView, suggestionView: providedSuggestionView, onStop: providedStopHandler, ...restProps } = props;
@@ -4960,7 +5641,31 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
4960
5641
  resolvedAgentId
4961
5642
  ]);
4962
5643
  const onSubmitInput = (0, react.useCallback)(async (value) => {
4963
- agent.addMessage({
5644
+ if (selectedAttachments.some((a) => a.status === "uploading")) {
5645
+ console.error("[CopilotKit] Cannot send while attachments are uploading");
5646
+ return;
5647
+ }
5648
+ const readyAttachments = consumeAttachments();
5649
+ if (readyAttachments.length > 0) {
5650
+ const contentParts = [];
5651
+ if (value.trim()) contentParts.push({
5652
+ type: "text",
5653
+ text: value
5654
+ });
5655
+ for (const att of readyAttachments) contentParts.push({
5656
+ type: att.type,
5657
+ source: att.source,
5658
+ metadata: {
5659
+ ...att.filename ? { filename: att.filename } : {},
5660
+ ...att.metadata
5661
+ }
5662
+ });
5663
+ agent.addMessage({
5664
+ id: (0, _copilotkit_shared.randomUUID)(),
5665
+ role: "user",
5666
+ content: contentParts
5667
+ });
5668
+ } else agent.addMessage({
4964
5669
  id: (0, _copilotkit_shared.randomUUID)(),
4965
5670
  role: "user",
4966
5671
  content: value
@@ -4971,7 +5676,11 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
4971
5676
  } catch (error) {
4972
5677
  console.error("CopilotChat: runAgent failed", error);
4973
5678
  }
4974
- }, [agent]);
5679
+ }, [
5680
+ agent,
5681
+ selectedAttachments,
5682
+ consumeAttachments
5683
+ ]);
4975
5684
  const handleSelectSuggestion = (0, react.useCallback)(async (suggestion) => {
4976
5685
  agent.addMessage({
4977
5686
  id: (0, _copilotkit_shared.randomUUID)(),
@@ -5072,7 +5781,11 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
5072
5781
  const showTranscription = isTranscriptionEnabled && isMediaRecorderSupported;
5073
5782
  const effectiveMode = isTranscribing ? "processing" : transcribeMode;
5074
5783
  const RenderedChatView = renderSlot(chatView, CopilotChatView, (0, ts_deepmerge.merge)(mergedProps, {
5075
- messages: (0, react.useMemo)(() => [...agent.messages], [JSON.stringify(agent.messages)]),
5784
+ messages: (0, react.useMemo)(() => [...agent.messages], [agent.messages.map((m) => {
5785
+ const contentKey = typeof m.content === "string" ? m.content.length : Array.isArray(m.content) ? m.content.length : 0;
5786
+ const toolCallsKey = "toolCalls" in m && Array.isArray(m.toolCalls) ? m.toolCalls.map((tc) => `${tc.id}:${tc.function?.arguments?.length ?? 0}`).join(";") : "";
5787
+ return `${m.id}:${m.role}:${contentKey}:${toolCallsKey}`;
5788
+ }).join(",")]),
5076
5789
  onSubmitMessage: onSubmitInput,
5077
5790
  onStop: effectiveStopHandler,
5078
5791
  inputMode: effectiveMode,
@@ -5081,32 +5794,55 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
5081
5794
  onStartTranscribe: showTranscription ? handleStartTranscribe : void 0,
5082
5795
  onCancelTranscribe: showTranscription ? handleCancelTranscribe : void 0,
5083
5796
  onFinishTranscribe: showTranscription ? handleFinishTranscribe : void 0,
5084
- onFinishTranscribeWithAudio: showTranscription ? handleFinishTranscribeWithAudio : void 0
5797
+ onFinishTranscribeWithAudio: showTranscription ? handleFinishTranscribeWithAudio : void 0,
5798
+ attachments: selectedAttachments,
5799
+ onRemoveAttachment: removeAttachment,
5800
+ onAddFile: attachmentsEnabled ? () => {
5801
+ setTimeout(() => {
5802
+ fileInputRef.current?.click();
5803
+ }, 100);
5804
+ } : void 0,
5805
+ dragOver,
5806
+ onDragOver: handleDragOver,
5807
+ onDragLeave: handleDragLeave,
5808
+ onDrop: handleDrop
5085
5809
  }));
5086
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(CopilotChatConfigurationProvider, {
5810
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopilotChatConfigurationProvider, {
5087
5811
  agentId: resolvedAgentId,
5088
5812
  threadId: resolvedThreadId,
5089
5813
  labels,
5090
5814
  isModalDefaultOpen,
5091
- children: [
5092
- !isChatLicensed && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InlineFeatureWarning, { featureName: "Chat" }),
5093
- transcriptionError && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5094
- style: {
5095
- position: "absolute",
5096
- bottom: "100px",
5097
- left: "50%",
5098
- transform: "translateX(-50%)",
5099
- backgroundColor: "#ef4444",
5100
- color: "white",
5101
- padding: "8px 16px",
5102
- borderRadius: "8px",
5103
- fontSize: "14px",
5104
- zIndex: 50
5105
- },
5106
- children: transcriptionError
5107
- }),
5108
- RenderedChatView
5109
- ]
5815
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5816
+ ref: chatContainerRef,
5817
+ style: { display: "contents" },
5818
+ children: [
5819
+ attachmentsEnabled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
5820
+ type: "file",
5821
+ multiple: true,
5822
+ ref: fileInputRef,
5823
+ onChange: handleFileUpload,
5824
+ accept: attachmentsConfig?.accept ?? "*/*",
5825
+ style: { display: "none" }
5826
+ }),
5827
+ !isChatLicensed && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InlineFeatureWarning, { featureName: "Chat" }),
5828
+ transcriptionError && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5829
+ style: {
5830
+ position: "absolute",
5831
+ bottom: "100px",
5832
+ left: "50%",
5833
+ transform: "translateX(-50%)",
5834
+ backgroundColor: "#ef4444",
5835
+ color: "white",
5836
+ padding: "8px 16px",
5837
+ borderRadius: "8px",
5838
+ fontSize: "14px",
5839
+ zIndex: 50
5840
+ },
5841
+ children: transcriptionError
5842
+ }),
5843
+ RenderedChatView
5844
+ ]
5845
+ })
5110
5846
  });
5111
5847
  }
5112
5848
  (function(_CopilotChat) {
@@ -7723,6 +8459,18 @@ Object.defineProperty(exports, 'CopilotChatAssistantMessage_default', {
7723
8459
  return CopilotChatAssistantMessage_default;
7724
8460
  }
7725
8461
  });
8462
+ Object.defineProperty(exports, 'CopilotChatAttachmentQueue', {
8463
+ enumerable: true,
8464
+ get: function () {
8465
+ return CopilotChatAttachmentQueue;
8466
+ }
8467
+ });
8468
+ Object.defineProperty(exports, 'CopilotChatAttachmentRenderer', {
8469
+ enumerable: true,
8470
+ get: function () {
8471
+ return CopilotChatAttachmentRenderer;
8472
+ }
8473
+ });
7726
8474
  Object.defineProperty(exports, 'CopilotChatAudioRecorder', {
7727
8475
  enumerable: true,
7728
8476
  get: function () {
@@ -7957,6 +8705,12 @@ Object.defineProperty(exports, 'useAsyncCallback', {
7957
8705
  return useAsyncCallback;
7958
8706
  }
7959
8707
  });
8708
+ Object.defineProperty(exports, 'useAttachments', {
8709
+ enumerable: true,
8710
+ get: function () {
8711
+ return useAttachments;
8712
+ }
8713
+ });
7960
8714
  Object.defineProperty(exports, 'useCoAgentStateRenders', {
7961
8715
  enumerable: true,
7962
8716
  get: function () {
@@ -8071,4 +8825,4 @@ Object.defineProperty(exports, 'useToast', {
8071
8825
  return useToast;
8072
8826
  }
8073
8827
  });
8074
- //# sourceMappingURL=copilotkit-CDZGGOIB.cjs.map
8828
+ //# sourceMappingURL=copilotkit-B3Eeystj.cjs.map