@copilotkit/react-core 1.56.2 → 1.56.4-canary.1777529757

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 (63) hide show
  1. package/dist/{copilotkit-CSJw5BG8.cjs → copilotkit-BAkj3zUc.cjs} +359 -157
  2. package/dist/copilotkit-BAkj3zUc.cjs.map +1 -0
  3. package/dist/{copilotkit-Cj2ZIxVr.mjs → copilotkit-DAatqMh2.mjs} +360 -158
  4. package/dist/copilotkit-DAatqMh2.mjs.map +1 -0
  5. package/dist/{copilotkit-CCbxm6JM.d.mts → copilotkit-DFaI4j2r.d.mts} +64 -18
  6. package/dist/copilotkit-DFaI4j2r.d.mts.map +1 -0
  7. package/dist/{copilotkit-BtP7w7cT.d.cts → copilotkit-Dg4r4Gi_.d.cts} +64 -18
  8. package/dist/copilotkit-Dg4r4Gi_.d.cts.map +1 -0
  9. package/dist/index.cjs +1 -1
  10. package/dist/index.d.cts +2 -1
  11. package/dist/index.d.cts.map +1 -1
  12. package/dist/index.d.mts +2 -1
  13. package/dist/index.d.mts.map +1 -1
  14. package/dist/index.mjs +1 -1
  15. package/dist/index.umd.js +31 -44
  16. package/dist/index.umd.js.map +1 -1
  17. package/dist/v2/index.cjs +1 -1
  18. package/dist/v2/index.css +1 -1
  19. package/dist/v2/index.d.cts +2 -2
  20. package/dist/v2/index.d.mts +2 -2
  21. package/dist/v2/index.mjs +1 -1
  22. package/dist/v2/index.umd.js +361 -163
  23. package/dist/v2/index.umd.js.map +1 -1
  24. package/package.json +8 -8
  25. package/src/components/copilot-provider/__tests__/v1-explicit-threadid-bridge.test.tsx +107 -0
  26. package/src/components/copilot-provider/copilotkit.tsx +6 -1
  27. package/src/context/__tests__/threads-context.test.tsx +116 -3
  28. package/src/context/threads-context.tsx +18 -1
  29. package/src/v2/components/chat/CopilotChat.tsx +91 -4
  30. package/src/v2/components/chat/CopilotChatAttachmentQueue.tsx +7 -114
  31. package/src/v2/components/chat/CopilotChatAttachmentRenderer.tsx +26 -6
  32. package/src/v2/components/chat/CopilotChatInput.tsx +22 -0
  33. package/src/v2/components/chat/CopilotChatUserMessage.tsx +2 -2
  34. package/src/v2/components/chat/CopilotChatView.tsx +226 -48
  35. package/src/v2/components/chat/Lightbox.tsx +103 -0
  36. package/src/v2/components/chat/__tests__/CopilotChat.absentThreadConnect.test.tsx +66 -0
  37. package/src/v2/components/chat/__tests__/CopilotChat.suggestionsAlways.test.tsx +189 -0
  38. package/src/v2/components/chat/__tests__/CopilotChat.welcomeGate.test.tsx +186 -0
  39. package/src/v2/components/chat/__tests__/CopilotChatActivityRendering.e2e.test.tsx +438 -4
  40. package/src/v2/components/chat/__tests__/CopilotChatView.connectingGate.test.tsx +56 -0
  41. package/src/v2/components/chat/__tests__/CopilotChatView.inputOverlay.test.tsx +264 -0
  42. package/src/v2/components/chat/__tests__/CopilotChatView.pinToSend.test.tsx +94 -0
  43. package/src/v2/components/chat/__tests__/copilot-chat-throttle.test.tsx +0 -1
  44. package/src/v2/components/chat/__tests__/normalize-auto-scroll.test.ts +37 -0
  45. package/src/v2/components/chat/index.ts +2 -0
  46. package/src/v2/components/chat/last-user-message-context.ts +21 -0
  47. package/src/v2/components/chat/normalize-auto-scroll.ts +17 -0
  48. package/src/v2/components/license-warning-banner.tsx +20 -1
  49. package/src/v2/hooks/__tests__/use-agent-stability.test.tsx +6 -0
  50. package/src/v2/hooks/__tests__/use-agent-thread-isolation.test.tsx +6 -0
  51. package/src/v2/hooks/__tests__/use-agent-throttle.test.tsx +76 -50
  52. package/src/v2/hooks/__tests__/use-pin-to-send.test.tsx +219 -0
  53. package/src/v2/hooks/__tests__/use-threads.test.tsx +68 -0
  54. package/src/v2/hooks/use-agent.tsx +34 -77
  55. package/src/v2/hooks/use-pin-to-send.ts +94 -0
  56. package/src/v2/hooks/use-threads.tsx +55 -12
  57. package/src/v2/providers/CopilotChatConfigurationProvider.tsx +29 -1
  58. package/src/v2/providers/CopilotKitProvider.tsx +2 -11
  59. package/src/v2/providers/__tests__/CopilotChatConfigurationProvider.test.tsx +106 -0
  60. package/dist/copilotkit-BtP7w7cT.d.cts.map +0 -1
  61. package/dist/copilotkit-CCbxm6JM.d.mts.map +0 -1
  62. package/dist/copilotkit-CSJw5BG8.cjs.map +0 -1
  63. package/dist/copilotkit-Cj2ZIxVr.mjs.map +0 -1
@@ -1,10 +1,10 @@
1
1
  "use client";
2
2
 
3
3
  (function(global, factory) {
4
- typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('./index.css'), require('@copilotkit/core'), require('@ag-ui/client'), require('react'), require('tailwind-merge'), require('lucide-react'), require('@copilotkit/shared'), require('react/jsx-runtime'), require('@radix-ui/react-slot'), require('class-variance-authority'), require('clsx'), require('@radix-ui/react-tooltip'), require('@radix-ui/react-dropdown-menu'), require('streamdown'), require('zod'), require('@lit-labs/react'), require('@copilotkit/a2ui-renderer'), require('zod-to-json-schema'), require('@tanstack/react-virtual'), require('react-dom'), require('use-stick-to-bottom')) :
5
- typeof define === 'function' && define.amd ? define(['exports', './index.css', '@copilotkit/core', '@ag-ui/client', 'react', 'tailwind-merge', 'lucide-react', '@copilotkit/shared', 'react/jsx-runtime', '@radix-ui/react-slot', 'class-variance-authority', 'clsx', '@radix-ui/react-tooltip', '@radix-ui/react-dropdown-menu', 'streamdown', 'zod', '@lit-labs/react', '@copilotkit/a2ui-renderer', 'zod-to-json-schema', '@tanstack/react-virtual', 'react-dom', 'use-stick-to-bottom'], factory) :
6
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory((global.CopilotKitReactCoreV2 = {}), global.src_v2_index_css,global.CopilotKitCore,global.AgUIClient,global.React,global.tailwindMerge,global.lucideReact,global.CopilotKitShared,global.ReactJsxRuntime,global.RadixReactSlot,global.classVarianceAuthority,global.clsx,global.RadixReactTooltip,global.RadixReactDropdownMenu,global.streamdown,global.Zod,global.LitLabsReact,global.CopilotKitA2UIRenderer,global.zod_to_json_schema,global._tanstack_react_virtual,global.ReactDOM,global.useStickToBottom));
7
- })(this, function(exports, src_v2_index_css, _copilotkit_core, _ag_ui_client, react, tailwind_merge, lucide_react, _copilotkit_shared, react_jsx_runtime, _radix_ui_react_slot, class_variance_authority, clsx, _radix_ui_react_tooltip, _radix_ui_react_dropdown_menu, streamdown, zod, _lit_labs_react, _copilotkit_a2ui_renderer, zod_to_json_schema, _tanstack_react_virtual, react_dom, use_stick_to_bottom) {
4
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('./index.css'), require('@copilotkit/core'), require('@ag-ui/client'), require('react'), require('tailwind-merge'), require('lucide-react'), require('@copilotkit/shared'), require('react/jsx-runtime'), require('@radix-ui/react-slot'), require('class-variance-authority'), require('clsx'), require('@radix-ui/react-tooltip'), require('@radix-ui/react-dropdown-menu'), require('streamdown'), require('zod'), require('@lit-labs/react'), require('@copilotkit/a2ui-renderer'), require('zod-to-json-schema'), require('react-dom'), require('@tanstack/react-virtual'), require('use-stick-to-bottom')) :
5
+ typeof define === 'function' && define.amd ? define(['exports', './index.css', '@copilotkit/core', '@ag-ui/client', 'react', 'tailwind-merge', 'lucide-react', '@copilotkit/shared', 'react/jsx-runtime', '@radix-ui/react-slot', 'class-variance-authority', 'clsx', '@radix-ui/react-tooltip', '@radix-ui/react-dropdown-menu', 'streamdown', 'zod', '@lit-labs/react', '@copilotkit/a2ui-renderer', 'zod-to-json-schema', 'react-dom', '@tanstack/react-virtual', 'use-stick-to-bottom'], factory) :
6
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory((global.CopilotKitReactCoreV2 = {}), global.src_v2_index_css,global.CopilotKitCore,global.AgUIClient,global.React,global.tailwindMerge,global.lucideReact,global.CopilotKitShared,global.ReactJsxRuntime,global.RadixReactSlot,global.classVarianceAuthority,global.clsx,global.RadixReactTooltip,global.RadixReactDropdownMenu,global.streamdown,global.Zod,global.LitLabsReact,global.CopilotKitA2UIRenderer,global.zod_to_json_schema,global.ReactDOM,global._tanstack_react_virtual,global.useStickToBottom));
7
+ })(this, function(exports, src_v2_index_css, _copilotkit_core, _ag_ui_client, react, tailwind_merge, lucide_react, _copilotkit_shared, react_jsx_runtime, _radix_ui_react_slot, class_variance_authority, clsx, _radix_ui_react_tooltip, _radix_ui_react_dropdown_menu, streamdown, zod, _lit_labs_react, _copilotkit_a2ui_renderer, zod_to_json_schema, react_dom, _tanstack_react_virtual, use_stick_to_bottom) {
8
8
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
9
9
  //#region \0rolldown/runtime.js
10
10
  var __create = Object.create;
@@ -161,7 +161,7 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
161
161
  welcomeMessageText: "How can I help you today?"
162
162
  };
163
163
  const CopilotChatConfiguration = (0, react.createContext)(null);
164
- const CopilotChatConfigurationProvider = ({ children, labels, agentId, threadId, isModalDefaultOpen }) => {
164
+ const CopilotChatConfigurationProvider = ({ children, labels, agentId, threadId, hasExplicitThreadId, isModalDefaultOpen }) => {
165
165
  var _ref, _parentConfig$isModal, _parentConfig$setModa;
166
166
  const parentConfig = (0, react.useContext)(CopilotChatConfiguration);
167
167
  const stableLabels = useShallowStableRef(labels);
@@ -176,6 +176,7 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
176
176
  if (parentConfig === null || parentConfig === void 0 ? void 0 : parentConfig.threadId) return parentConfig.threadId;
177
177
  return (0, _copilotkit_shared.randomUUID)();
178
178
  }, [threadId, parentConfig === null || parentConfig === void 0 ? void 0 : parentConfig.threadId]);
179
+ const resolvedHasExplicitThreadId = (hasExplicitThreadId !== void 0 ? hasExplicitThreadId : !!threadId) || !!(parentConfig === null || parentConfig === void 0 ? void 0 : parentConfig.hasExplicitThreadId);
179
180
  const [internalModalOpen, setInternalModalOpen] = (0, react.useState)(isModalDefaultOpen !== null && isModalDefaultOpen !== void 0 ? isModalDefaultOpen : true);
180
181
  const hasExplicitDefault = isModalDefaultOpen !== void 0;
181
182
  const setAndSync = (0, react.useCallback)((open) => {
@@ -198,12 +199,14 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
198
199
  labels: mergedLabels,
199
200
  agentId: resolvedAgentId,
200
201
  threadId: resolvedThreadId,
202
+ hasExplicitThreadId: resolvedHasExplicitThreadId,
201
203
  isModalOpen: resolvedIsModalOpen,
202
204
  setModalOpen: resolvedSetModalOpen
203
205
  }), [
204
206
  mergedLabels,
205
207
  resolvedAgentId,
206
208
  resolvedThreadId,
209
+ resolvedHasExplicitThreadId,
207
210
  resolvedIsModalOpen,
208
211
  resolvedSetModalOpen
209
212
  ]);
@@ -599,7 +602,7 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
599
602
  //#region src/v2/components/chat/CopilotChatInput.tsx
600
603
  const SLASH_MENU_MAX_VISIBLE_ITEMS = 5;
601
604
  const SLASH_MENU_ITEM_HEIGHT_PX = 40;
602
- 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 }) {
605
+ function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning = false, onStartTranscribe, onCancelTranscribe, onFinishTranscribe, onFinishTranscribeWithAudio, onAddFile, onChange, value, toolsMenu, autoFocus = false, positioning = "static", keyboardHeight = 0, containerRef, showDisclaimer, bottomAnchored = false, textArea, sendButton, startTranscribeButton, cancelTranscribeButton, finishTranscribeButton, addMenuButton, audioRecorder, disclaimer, children, className, ...props }) {
603
606
  var _config$labels;
604
607
  const isControlled = value !== void 0;
605
608
  const [internalValue, setInternalValue] = (0, react.useState)(() => value !== null && value !== void 0 ? value : "");
@@ -1134,7 +1137,8 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
1134
1137
  className: cn("cpk:pointer-events-none cpk:relative cpk:z-20", positioning === "absolute" && "cpk:absolute cpk:bottom-0 cpk:left-0 cpk:right-0", className),
1135
1138
  style: {
1136
1139
  transform: keyboardHeight > 0 ? `translateY(-${keyboardHeight}px)` : void 0,
1137
- transition: "transform 0.2s ease-out"
1140
+ transition: "transform 0.2s ease-out",
1141
+ ...positioning === "absolute" || bottomAnchored ? { paddingBottom: "var(--copilotkit-license-banner-offset, 0px)" } : {}
1138
1142
  },
1139
1143
  ...props,
1140
1144
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -1361,6 +1365,8 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
1361
1365
 
1362
1366
  //#endregion
1363
1367
  //#region src/v2/components/license-warning-banner.tsx
1368
+ const LICENSE_BANNER_OFFSET_PX = 52;
1369
+ const LICENSE_BANNER_OFFSET_VAR = "--copilotkit-license-banner-offset";
1364
1370
  const BANNER_STYLES = {
1365
1371
  base: {
1366
1372
  position: "fixed",
@@ -1402,6 +1408,14 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
1402
1408
  }
1403
1409
  }
1404
1410
  function BannerShell({ severity, message, actionLabel, actionUrl, onDismiss }) {
1411
+ (0, react.useEffect)(() => {
1412
+ if (typeof document === "undefined") return;
1413
+ const root = document.documentElement;
1414
+ root.style.setProperty(LICENSE_BANNER_OFFSET_VAR, `${LICENSE_BANNER_OFFSET_PX}px`);
1415
+ return () => {
1416
+ root.style.removeProperty(LICENSE_BANNER_OFFSET_VAR);
1417
+ };
1418
+ }, []);
1405
1419
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1406
1420
  style: {
1407
1421
  ...BANNER_STYLES.base,
@@ -3386,7 +3400,6 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
3386
3400
  didMountRef.current = true;
3387
3401
  }, []);
3388
3402
  (0, react.useEffect)(() => {
3389
- if (defaultThrottleMs !== void 0 && (!Number.isFinite(defaultThrottleMs) || defaultThrottleMs < 0)) console.error(`CopilotKitProvider: defaultThrottleMs must be a non-negative finite number, got ${defaultThrottleMs}. useAgent hooks without an explicit throttleMs will fall back to unthrottled.`);
3390
3403
  copilotkit.setDefaultThrottleMs(defaultThrottleMs);
3391
3404
  }, [copilotkit, defaultThrottleMs]);
3392
3405
  const designSkill = (_openGenerativeUI$des = openGenerativeUI === null || openGenerativeUI === void 0 ? void 0 : openGenerativeUI.designSkill) !== null && _openGenerativeUI$des !== void 0 ? _openGenerativeUI$des : DEFAULT_DESIGN_SKILL;
@@ -3608,16 +3621,6 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
3608
3621
  const providerThrottleMs = copilotkit.defaultThrottleMs;
3609
3622
  const chatConfig = useCopilotChatConfiguration();
3610
3623
  (_threadId = threadId) !== null && _threadId !== void 0 || (threadId = chatConfig === null || chatConfig === void 0 ? void 0 : chatConfig.threadId);
3611
- const effectiveThrottleMs = (0, react.useMemo)(() => {
3612
- var _ref;
3613
- const resolved = (_ref = throttleMs !== null && throttleMs !== void 0 ? throttleMs : providerThrottleMs) !== null && _ref !== void 0 ? _ref : 0;
3614
- if (!Number.isFinite(resolved) || resolved < 0) {
3615
- const source = throttleMs !== void 0 ? "hook-level throttleMs" : "provider-level defaultThrottleMs";
3616
- console.error(`useAgent: ${source} must be a non-negative finite number, got ${resolved}. Falling back to unthrottled.`);
3617
- return 0;
3618
- }
3619
- return resolved;
3620
- }, [throttleMs, providerThrottleMs]);
3621
3624
  const [, forceUpdate] = (0, react.useReducer)((x) => x + 1, 0);
3622
3625
  const updateFlags = (0, react.useMemo)(() => updates !== null && updates !== void 0 ? updates : ALL_UPDATES, [JSON.stringify(updates)]);
3623
3626
  const provisionalAgentCache = (0, react.useRef)(/* @__PURE__ */ new Map());
@@ -3681,9 +3684,8 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
3681
3684
  ]);
3682
3685
  (0, react.useEffect)(() => {
3683
3686
  if (updateFlags.length === 0) return;
3684
- const handlers = {};
3685
- let timerId = null;
3686
3687
  let active = true;
3688
+ const handlers = {};
3687
3689
  let batchScheduled = false;
3688
3690
  const batchedForceUpdate = () => {
3689
3691
  if (!active) return;
@@ -3695,46 +3697,24 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
3695
3697
  });
3696
3698
  }
3697
3699
  };
3698
- if (updateFlags.includes(UseAgentUpdate.OnMessagesChanged)) {
3699
- const ms = effectiveThrottleMs;
3700
- if (ms > 0) {
3701
- let throttleActive = false;
3702
- let pending = false;
3703
- const throttledNotify = () => {
3704
- if (!active) return;
3705
- if (!throttleActive) {
3706
- throttleActive = true;
3707
- pending = false;
3708
- forceUpdate();
3709
- timerId = setTimeout(function trailingEdge() {
3710
- timerId = null;
3711
- if (active && pending) {
3712
- pending = false;
3713
- forceUpdate();
3714
- timerId = setTimeout(trailingEdge, ms);
3715
- } else throttleActive = false;
3716
- }, ms);
3717
- } else pending = true;
3718
- };
3719
- handlers.onMessagesChanged = throttledNotify;
3720
- } else handlers.onMessagesChanged = forceUpdate;
3721
- }
3700
+ if (updateFlags.includes(UseAgentUpdate.OnMessagesChanged)) handlers.onMessagesChanged = forceUpdate;
3722
3701
  if (updateFlags.includes(UseAgentUpdate.OnStateChanged)) handlers.onStateChanged = batchedForceUpdate;
3723
3702
  if (updateFlags.includes(UseAgentUpdate.OnRunStatusChanged)) {
3724
3703
  handlers.onRunInitialized = batchedForceUpdate;
3725
3704
  handlers.onRunFinalized = batchedForceUpdate;
3726
3705
  handlers.onRunFailed = batchedForceUpdate;
3706
+ handlers.onRunErrorEvent = batchedForceUpdate;
3727
3707
  }
3728
- const subscription = agent.subscribe(handlers);
3708
+ const subscription = copilotkit.subscribeToAgentWithOptions(agent, handlers, { throttleMs });
3729
3709
  return () => {
3730
3710
  active = false;
3731
- if (timerId !== null) clearTimeout(timerId);
3732
3711
  subscription.unsubscribe();
3733
3712
  };
3734
3713
  }, [
3735
3714
  agent,
3736
3715
  forceUpdate,
3737
- effectiveThrottleMs,
3716
+ throttleMs,
3717
+ providerThrottleMs,
3738
3718
  updateFlags
3739
3719
  ]);
3740
3720
  (0, react.useEffect)(() => {
@@ -4705,13 +4685,14 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
4705
4685
  const { copilotkit } = useCopilotKit();
4706
4686
  const [store] = (0, react.useState)(() => (0, _copilotkit_core.ɵcreateThreadStore)({ fetch: globalThis.fetch }));
4707
4687
  const coreThreads = useThreadStoreSelector(store, _copilotkit_core.ɵselectThreads);
4708
- const threads = (0, react.useMemo)(() => coreThreads.map(({ id, agentId, name, archived, createdAt, updatedAt }) => ({
4688
+ const threads = (0, react.useMemo)(() => coreThreads.map(({ id, agentId, name, archived, createdAt, updatedAt, lastRunAt }) => ({
4709
4689
  id,
4710
4690
  agentId,
4711
4691
  name,
4712
4692
  archived,
4713
4693
  createdAt,
4714
- updatedAt
4694
+ updatedAt,
4695
+ ...lastRunAt !== void 0 ? { lastRunAt } : {}
4715
4696
  })), [coreThreads]);
4716
4697
  const storeIsLoading = useThreadStoreSelector(store, _copilotkit_core.ɵselectThreadsIsLoading);
4717
4698
  const storeError = useThreadStoreSelector(store, _copilotkit_core.ɵselectThreadsError);
@@ -4725,7 +4706,9 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
4725
4706
  if (copilotkit.runtimeUrl) return null;
4726
4707
  return /* @__PURE__ */ new Error("Runtime URL is not configured");
4727
4708
  }, [copilotkit.runtimeUrl]);
4728
- const isLoading = runtimeError ? false : storeIsLoading;
4709
+ const [hasDispatchedContext, setHasDispatchedContext] = (0, react.useState)(false);
4710
+ const preConnectLoading = !!copilotkit.runtimeUrl && !hasDispatchedContext;
4711
+ const isLoading = runtimeError ? false : preConnectLoading || storeIsLoading;
4729
4712
  const error = runtimeError !== null && runtimeError !== void 0 ? runtimeError : storeError;
4730
4713
  (0, react.useEffect)(() => {
4731
4714
  store.start();
@@ -4733,20 +4716,28 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
4733
4716
  store.stop();
4734
4717
  };
4735
4718
  }, [store]);
4719
+ const runtimeStatus = copilotkit.runtimeConnectionStatus;
4736
4720
  (0, react.useEffect)(() => {
4737
4721
  var _copilotkit$intellige;
4738
- const context = copilotkit.runtimeUrl ? {
4722
+ if (!copilotkit.runtimeUrl) {
4723
+ store.setContext(null);
4724
+ return;
4725
+ }
4726
+ if (runtimeStatus !== _copilotkit_core.CopilotKitCoreRuntimeConnectionStatus.Connected) return;
4727
+ const context = {
4739
4728
  runtimeUrl: copilotkit.runtimeUrl,
4740
4729
  headers: { ...copilotkit.headers },
4741
4730
  wsUrl: (_copilotkit$intellige = copilotkit.intelligence) === null || _copilotkit$intellige === void 0 ? void 0 : _copilotkit$intellige.wsUrl,
4742
4731
  agentId,
4743
4732
  includeArchived,
4744
4733
  limit
4745
- } : null;
4734
+ };
4746
4735
  store.setContext(context);
4736
+ setHasDispatchedContext(true);
4747
4737
  }, [
4748
4738
  store,
4749
4739
  copilotkit.runtimeUrl,
4740
+ runtimeStatus,
4750
4741
  headersKey,
4751
4742
  (_copilotkit$intellige2 = copilotkit.intelligence) === null || _copilotkit$intellige2 === void 0 ? void 0 : _copilotkit$intellige2.wsUrl,
4752
4743
  agentId,
@@ -5136,20 +5127,101 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
5136
5127
  CopilotChatAssistantMessage.RegenerateButton.displayName = "CopilotChatAssistantMessage.RegenerateButton";
5137
5128
  var CopilotChatAssistantMessage_default = CopilotChatAssistantMessage;
5138
5129
 
5130
+ //#endregion
5131
+ //#region src/v2/components/chat/Lightbox.tsx
5132
+ function Lightbox({ onClose, children }) {
5133
+ (0, react.useEffect)(() => {
5134
+ const handleKey = (e) => {
5135
+ if (e.key === "Escape") onClose();
5136
+ };
5137
+ document.addEventListener("keydown", handleKey);
5138
+ return () => document.removeEventListener("keydown", handleKey);
5139
+ }, [onClose]);
5140
+ if (typeof document === "undefined") return null;
5141
+ return (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5142
+ 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",
5143
+ onClick: onClose,
5144
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5145
+ onClick: onClose,
5146
+ 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",
5147
+ "aria-label": "Close preview",
5148
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.X, { className: "cpk:w-5 cpk:h-5" })
5149
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5150
+ onClick: (e) => e.stopPropagation(),
5151
+ children
5152
+ })]
5153
+ }), document.body);
5154
+ }
5155
+ /**
5156
+ * Hook that manages lightbox open/close and uses the View Transition API to
5157
+ * morph the thumbnail into fullscreen content.
5158
+ *
5159
+ * The trick: `view-transition-name` must live on exactly ONE element at a time.
5160
+ * - Old state (thumbnail visible): name is on the thumbnail.
5161
+ * - New state (lightbox visible): name moves to the lightbox content.
5162
+ * `flushSync` ensures React commits the DOM change synchronously inside the
5163
+ * `startViewTransition` callback so the API can snapshot old → new correctly.
5164
+ */
5165
+ function useLightbox() {
5166
+ const thumbnailRef = (0, react.useRef)(null);
5167
+ const [open, setOpen] = (0, react.useState)(false);
5168
+ const vtName = (0, react.useId)();
5169
+ return {
5170
+ thumbnailRef,
5171
+ vtName,
5172
+ open,
5173
+ openLightbox: (0, react.useCallback)(() => {
5174
+ const thumb = thumbnailRef.current;
5175
+ const doc = document;
5176
+ if (doc.startViewTransition && thumb) {
5177
+ thumb.style.viewTransitionName = vtName;
5178
+ doc.startViewTransition(() => {
5179
+ thumb.style.viewTransitionName = "";
5180
+ (0, react_dom.flushSync)(() => setOpen(true));
5181
+ });
5182
+ } else setOpen(true);
5183
+ }, [vtName]),
5184
+ closeLightbox: (0, react.useCallback)(() => {
5185
+ const thumb = thumbnailRef.current;
5186
+ const doc = document;
5187
+ if (doc.startViewTransition && thumb) doc.startViewTransition(() => {
5188
+ (0, react_dom.flushSync)(() => setOpen(false));
5189
+ thumb.style.viewTransitionName = vtName;
5190
+ }).finished.then(() => {
5191
+ thumb.style.viewTransitionName = "";
5192
+ }).catch(() => {
5193
+ thumb.style.viewTransitionName = "";
5194
+ });
5195
+ else setOpen(false);
5196
+ }, [vtName])
5197
+ };
5198
+ }
5199
+
5139
5200
  //#endregion
5140
5201
  //#region src/v2/components/chat/CopilotChatAttachmentRenderer.tsx
5141
5202
  const ImageAttachment = (0, react.memo)(function ImageAttachment({ src, className }) {
5142
5203
  const [error, setError] = (0, react.useState)(false);
5204
+ const { thumbnailRef, vtName, open, openLightbox, closeLightbox } = useLightbox();
5143
5205
  if (error) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5144
5206
  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),
5145
5207
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "Failed to load image" })
5146
5208
  });
5147
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
5209
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
5210
+ ref: thumbnailRef,
5148
5211
  src,
5149
5212
  alt: "Image attachment",
5150
- className: cn("cpk:max-w-full cpk:h-auto cpk:rounded-lg", className),
5213
+ className: cn("cpk:max-w-[80px] cpk:max-h-[80px] cpk:w-auto cpk:h-auto cpk:rounded-xl cpk:object-cover cpk:cursor-pointer cpk:bg-muted", className),
5214
+ onClick: openLightbox,
5151
5215
  onError: () => setError(true)
5152
- });
5216
+ }), open && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Lightbox, {
5217
+ onClose: closeLightbox,
5218
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
5219
+ style: { viewTransitionName: vtName },
5220
+ src,
5221
+ alt: "Image attachment",
5222
+ className: "cpk:max-w-[90vw] cpk:max-h-[90vh] cpk:object-contain cpk:rounded-lg"
5223
+ })
5224
+ })] });
5153
5225
  });
5154
5226
  const AudioAttachment = (0, react.memo)(function AudioAttachment({ src, filename, className }) {
5155
5227
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -5275,15 +5347,15 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
5275
5347
  "data-message-id": message.id,
5276
5348
  ...props,
5277
5349
  children: [
5278
- BoundMessageRenderer,
5279
5350
  mediaParts.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5280
- className: "cpk:flex cpk:flex-col cpk:items-end cpk:gap-2 cpk:mt-2",
5351
+ className: "cpk:flex cpk:flex-row cpk:flex-wrap cpk:justify-end cpk:gap-2 cpk:mb-2",
5281
5352
  children: mediaParts.map((part, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopilotChatAttachmentRenderer, {
5282
5353
  type: part.type,
5283
5354
  source: part.source,
5284
5355
  filename: getFilename(part)
5285
5356
  }, index))
5286
5357
  }),
5358
+ BoundMessageRenderer,
5287
5359
  BoundToolbar
5288
5360
  ]
5289
5361
  });
@@ -5966,6 +6038,7 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
5966
6038
  const CopilotChatAttachmentQueue = ({ attachments, onRemoveAttachment, className }) => {
5967
6039
  if (attachments.length === 0) return null;
5968
6040
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6041
+ "data-testid": "copilot-attachment-queue",
5969
6042
  className: cn("cpk:flex cpk:flex-wrap cpk:gap-2 cpk:p-2", className),
5970
6043
  children: attachments.map((attachment) => {
5971
6044
  const isMedia = attachment.type === "image" || attachment.type === "video";
@@ -6000,73 +6073,6 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6000
6073
  case "document": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DocumentPreview, { attachment });
6001
6074
  }
6002
6075
  }
6003
- function Lightbox({ onClose, children }) {
6004
- (0, react.useEffect)(() => {
6005
- const handleKey = (e) => {
6006
- if (e.key === "Escape") onClose();
6007
- };
6008
- document.addEventListener("keydown", handleKey);
6009
- return () => document.removeEventListener("keydown", handleKey);
6010
- }, [onClose]);
6011
- if (typeof document === "undefined") return null;
6012
- return (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6013
- 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",
6014
- onClick: onClose,
6015
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6016
- onClick: onClose,
6017
- 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",
6018
- "aria-label": "Close preview",
6019
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.X, { className: "cpk:w-5 cpk:h-5" })
6020
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6021
- onClick: (e) => e.stopPropagation(),
6022
- children
6023
- })]
6024
- }), document.body);
6025
- }
6026
- /**
6027
- * Hook that manages lightbox open/close and uses the View Transition API to
6028
- * morph the thumbnail into fullscreen content.
6029
- *
6030
- * The trick: `view-transition-name` must live on exactly ONE element at a time.
6031
- * - Old state (thumbnail visible): name is on the thumbnail.
6032
- * - New state (lightbox visible): name moves to the lightbox content.
6033
- * `flushSync` ensures React commits the DOM change synchronously inside the
6034
- * `startViewTransition` callback so the API can snapshot old → new correctly.
6035
- */
6036
- function useLightbox() {
6037
- const thumbnailRef = (0, react.useRef)(null);
6038
- const [open, setOpen] = (0, react.useState)(false);
6039
- const vtName = (0, react.useId)();
6040
- return {
6041
- thumbnailRef,
6042
- vtName,
6043
- open,
6044
- openLightbox: (0, react.useCallback)(() => {
6045
- const thumb = thumbnailRef.current;
6046
- const doc = document;
6047
- if (doc.startViewTransition && thumb) {
6048
- thumb.style.viewTransitionName = vtName;
6049
- doc.startViewTransition(() => {
6050
- thumb.style.viewTransitionName = "";
6051
- (0, react_dom.flushSync)(() => setOpen(true));
6052
- });
6053
- } else setOpen(true);
6054
- }, []),
6055
- closeLightbox: (0, react.useCallback)(() => {
6056
- const thumb = thumbnailRef.current;
6057
- const doc = document;
6058
- if (doc.startViewTransition && thumb) doc.startViewTransition(() => {
6059
- (0, react_dom.flushSync)(() => setOpen(false));
6060
- thumb.style.viewTransitionName = vtName;
6061
- }).finished.then(() => {
6062
- thumb.style.viewTransitionName = "";
6063
- }).catch(() => {
6064
- thumb.style.viewTransitionName = "";
6065
- });
6066
- else setOpen(false);
6067
- }, [])
6068
- };
6069
- }
6070
6076
  function ImagePreview({ attachment }) {
6071
6077
  const src = (0, _copilotkit_shared.getSourceUrl)(attachment.source);
6072
6078
  const { thumbnailRef, vtName, open, openLightbox, closeLightbox } = useLightbox();
@@ -6308,9 +6314,91 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6308
6314
  return keyboardState;
6309
6315
  }
6310
6316
 
6317
+ //#endregion
6318
+ //#region src/v2/components/chat/normalize-auto-scroll.ts
6319
+ const VALID = [
6320
+ "pin-to-bottom",
6321
+ "pin-to-send",
6322
+ "none"
6323
+ ];
6324
+ function normalizeAutoScroll(value) {
6325
+ if (value === void 0) return "pin-to-bottom";
6326
+ if (value === true) return "pin-to-bottom";
6327
+ if (value === false) return "none";
6328
+ if (VALID.includes(value)) return value;
6329
+ return "pin-to-bottom";
6330
+ }
6331
+
6332
+ //#endregion
6333
+ //#region src/v2/components/chat/last-user-message-context.ts
6334
+ const LastUserMessageContext = react.default.createContext({
6335
+ id: null,
6336
+ sendNonce: 0
6337
+ });
6338
+
6339
+ //#endregion
6340
+ //#region src/v2/hooks/use-pin-to-send.ts
6341
+ function usePinToSend({ scrollRef, contentRef, spacerRef, topOffset = 16 }) {
6342
+ const { id, sendNonce } = (0, react.useContext)(LastUserMessageContext);
6343
+ const lastNonceRef = (0, react.useRef)(-1);
6344
+ const currentSpacerHeightRef = (0, react.useRef)(0);
6345
+ (0, react.useEffect)(() => {
6346
+ if (sendNonce === lastNonceRef.current) return;
6347
+ lastNonceRef.current = sendNonce;
6348
+ if (!id) return;
6349
+ const scrollEl = scrollRef.current;
6350
+ const contentEl = contentRef.current;
6351
+ const spacerEl = spacerRef.current;
6352
+ if (!scrollEl || !contentEl || !spacerEl) return;
6353
+ const escaped = typeof CSS !== "undefined" && CSS.escape ? CSS.escape(id) : id.replace(/[!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g, "\\$&");
6354
+ const targetEl = contentEl.querySelector(`[data-message-id="${escaped}"]`);
6355
+ if (!targetEl) return;
6356
+ const viewportHeight = scrollEl.clientHeight;
6357
+ const userMessageHeight = targetEl.getBoundingClientRect().height;
6358
+ const paddingTop = parseFloat(getComputedStyle(targetEl).paddingTop) || 0;
6359
+ const bubbleHeight = Math.max(0, userMessageHeight - paddingTop);
6360
+ const spacerHeight = Math.max(0, viewportHeight - bubbleHeight - topOffset);
6361
+ spacerEl.style.height = `${spacerHeight}px`;
6362
+ currentSpacerHeightRef.current = spacerHeight;
6363
+ const raf = requestAnimationFrame(() => {
6364
+ const targetTop = computeOffsetTop(targetEl, scrollEl) + paddingTop - topOffset;
6365
+ scrollEl.scrollTo({
6366
+ top: Math.max(0, targetTop),
6367
+ behavior: "smooth"
6368
+ });
6369
+ });
6370
+ const ro = new ResizeObserver(() => {
6371
+ if (!contentEl || !spacerEl || !scrollEl) return;
6372
+ const consumedBelow = contentEl.getBoundingClientRect().height - computeOffsetTop(targetEl, contentEl) - userMessageHeight;
6373
+ const remaining = Math.max(0, spacerHeight - consumedBelow);
6374
+ if (remaining < currentSpacerHeightRef.current) {
6375
+ spacerEl.style.height = `${remaining}px`;
6376
+ currentSpacerHeightRef.current = remaining;
6377
+ }
6378
+ });
6379
+ ro.observe(contentEl);
6380
+ return () => {
6381
+ cancelAnimationFrame(raf);
6382
+ ro.disconnect();
6383
+ };
6384
+ }, [
6385
+ id,
6386
+ sendNonce,
6387
+ scrollRef,
6388
+ contentRef,
6389
+ spacerRef,
6390
+ topOffset
6391
+ ]);
6392
+ }
6393
+ function computeOffsetTop(el, stopAt) {
6394
+ const elRect = el.getBoundingClientRect();
6395
+ const stopRect = stopAt.getBoundingClientRect();
6396
+ return elRect.top - stopRect.top + stopAt.scrollTop;
6397
+ }
6398
+
6311
6399
  //#endregion
6312
6400
  //#region src/v2/components/chat/CopilotChatView.tsx
6313
- const FEATHER_HEIGHT = 96;
6401
+ const SCROLL_BUTTON_OFFSET = 16;
6314
6402
  function DropOverlay() {
6315
6403
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6316
6404
  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"),
@@ -6323,15 +6411,18 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6323
6411
  })
6324
6412
  });
6325
6413
  }
6326
- 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 }) {
6327
- const inputContainerRef = (0, react.useRef)(null);
6414
+ 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, isConnecting = false, hasExplicitThreadId = false, disclaimer, children, className, ...props }) {
6415
+ const [inputContainerEl, setInputContainerEl] = (0, react.useState)(null);
6328
6416
  const [inputContainerHeight, setInputContainerHeight] = (0, react.useState)(0);
6329
6417
  const [isResizing, setIsResizing] = (0, react.useState)(false);
6330
6418
  const resizeTimeoutRef = (0, react.useRef)(null);
6331
6419
  const { isKeyboardOpen, keyboardHeight, availableHeight } = useKeyboardHeight();
6332
6420
  (0, react.useEffect)(() => {
6333
- const element = inputContainerRef.current;
6334
- if (!element) return;
6421
+ const element = inputContainerEl;
6422
+ if (!element) {
6423
+ setInputContainerHeight(0);
6424
+ return;
6425
+ }
6335
6426
  const resizeObserver = new ResizeObserver((entries) => {
6336
6427
  for (const entry of entries) {
6337
6428
  const newHeight = entry.contentRect.height;
@@ -6354,7 +6445,7 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6354
6445
  resizeObserver.disconnect();
6355
6446
  if (resizeTimeoutRef.current) clearTimeout(resizeTimeoutRef.current);
6356
6447
  };
6357
- }, []);
6448
+ }, [inputContainerEl]);
6358
6449
  const BoundMessageView = renderSlot(messageView, CopilotChatMessageView, {
6359
6450
  messages,
6360
6451
  isRunning
@@ -6373,11 +6464,11 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6373
6464
  onAddFile,
6374
6465
  positioning: "static",
6375
6466
  keyboardHeight: isKeyboardOpen ? keyboardHeight : 0,
6376
- containerRef: inputContainerRef,
6377
6467
  showDisclaimer: true,
6468
+ bottomAnchored: true,
6378
6469
  ...disclaimer !== void 0 ? { disclaimer } : {}
6379
6470
  });
6380
- const hasSuggestions = Array.isArray(suggestions) && suggestions.length > 0;
6471
+ const hasSuggestions = !isConnecting && Array.isArray(suggestions) && suggestions.length > 0;
6381
6472
  const BoundSuggestionView = hasSuggestions ? renderSlot(suggestionView, CopilotChatSuggestionView, {
6382
6473
  suggestions,
6383
6474
  loadingIndexes: suggestionLoadingIndexes,
@@ -6389,7 +6480,8 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6389
6480
  inputContainerHeight,
6390
6481
  isResizing,
6391
6482
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6392
- style: { paddingBottom: `${hasSuggestions ? 4 : 32}px` },
6483
+ "data-testid": "copilot-scroll-content",
6484
+ style: { paddingBottom: `${inputContainerHeight + (hasSuggestions ? 4 : 32)}px` },
6393
6485
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6394
6486
  className: "cpk:max-w-3xl cpk:mx-auto",
6395
6487
  children: [BoundMessageView, hasSuggestions ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -6399,7 +6491,7 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6399
6491
  })
6400
6492
  })
6401
6493
  });
6402
- if (messages.length === 0 && !(welcomeScreen === false)) {
6494
+ if (messages.length === 0 && !(welcomeScreen === false) && !isConnecting && !hasExplicitThreadId) {
6403
6495
  const BoundInputForWelcome = renderSlot(input, CopilotChatInput_default, {
6404
6496
  onSubmitMessage,
6405
6497
  onStop,
@@ -6463,15 +6555,19 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6463
6555
  children: [
6464
6556
  dragOver && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DropOverlay, {}),
6465
6557
  BoundScrollView,
6466
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6467
- className: "cpk:max-w-3xl cpk:mx-auto cpk:w-full",
6468
- children: attachments && attachments.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopilotChatAttachmentQueue, {
6469
- attachments,
6470
- onRemoveAttachment: (id) => onRemoveAttachment === null || onRemoveAttachment === void 0 ? void 0 : onRemoveAttachment(id),
6471
- className: "cpk:px-4"
6472
- })
6473
- }),
6474
- BoundInput
6558
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6559
+ ref: setInputContainerEl,
6560
+ "data-testid": "copilot-input-overlay",
6561
+ className: "cpk:absolute cpk:bottom-0 cpk:left-0 cpk:right-0 cpk:z-20 cpk:pointer-events-none",
6562
+ children: [attachments && attachments.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6563
+ className: "cpk:max-w-3xl cpk:mx-auto cpk:w-full cpk:pointer-events-auto",
6564
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopilotChatAttachmentQueue, {
6565
+ attachments,
6566
+ onRemoveAttachment: (id) => onRemoveAttachment === null || onRemoveAttachment === void 0 ? void 0 : onRemoveAttachment(id),
6567
+ className: "cpk:px-4"
6568
+ })
6569
+ }), BoundInput]
6570
+ })
6475
6571
  ]
6476
6572
  });
6477
6573
  }
@@ -6501,15 +6597,66 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6501
6597
  BoundFeather,
6502
6598
  !isAtBottom && !isResizing && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6503
6599
  className: "cpk:absolute cpk:inset-x-0 cpk:flex cpk:justify-center cpk:z-30 cpk:pointer-events-none",
6504
- style: { bottom: `${inputContainerHeight + FEATHER_HEIGHT + 16}px` },
6600
+ style: { bottom: `${inputContainerHeight + SCROLL_BUTTON_OFFSET}px` },
6505
6601
  children: renderSlot(scrollToBottomButton, CopilotChatView.ScrollToBottomButton, { onClick: () => scrollToBottom() })
6506
6602
  })
6507
6603
  ] })
6508
6604
  });
6509
6605
  };
6510
- _CopilotChatView.ScrollView = ({ children, autoScroll = true, scrollToBottomButton, feather, inputContainerHeight = 0, isResizing = false, className, ...props }) => {
6606
+ const PinToSendScrollContainer = ({ children, scrollRef, contentRef, scrollToBottom, scrollToBottomButton, feather, inputContainerHeight, isResizing, nonAutoScrollEl, nonAutoScrollRefCallback, showScrollButton, className, ...props }) => {
6607
+ const spacerRef = (0, react.useRef)(null);
6608
+ usePinToSend({
6609
+ scrollRef,
6610
+ contentRef,
6611
+ spacerRef,
6612
+ topOffset: 16
6613
+ });
6614
+ const BoundFeather = renderSlot(feather, CopilotChatView.Feather, {});
6615
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ScrollElementContext.Provider, {
6616
+ value: nonAutoScrollEl,
6617
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6618
+ className: cn("cpk:h-full cpk:max-h-full cpk:flex cpk:flex-col cpk:min-h-0 cpk:relative", className),
6619
+ children: [
6620
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6621
+ ref: nonAutoScrollRefCallback,
6622
+ className: "cpk:flex-1 cpk:min-h-0 cpk:overflow-y-auto cpk:overflow-x-hidden",
6623
+ ...props,
6624
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6625
+ ref: contentRef,
6626
+ className: "cpk:px-4 cpk:sm:px-0 cpk:[div[data-sidebar-chat]_&]:px-8 cpk:[div[data-popup-chat]_&]:px-6",
6627
+ children
6628
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6629
+ ref: spacerRef,
6630
+ "data-pin-to-send-spacer": true,
6631
+ "aria-hidden": "true",
6632
+ style: {
6633
+ height: 0,
6634
+ flex: "0 0 auto"
6635
+ }
6636
+ })]
6637
+ }),
6638
+ BoundFeather,
6639
+ showScrollButton && !isResizing && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6640
+ className: "cpk:absolute cpk:inset-x-0 cpk:flex cpk:justify-center cpk:z-30 cpk:pointer-events-none",
6641
+ style: { bottom: `${inputContainerHeight + SCROLL_BUTTON_OFFSET}px` },
6642
+ children: renderSlot(scrollToBottomButton, CopilotChatView.ScrollToBottomButton, { onClick: () => scrollToBottom() })
6643
+ })
6644
+ ]
6645
+ })
6646
+ });
6647
+ };
6648
+ _CopilotChatView.ScrollView = ({ children, autoScroll = "pin-to-bottom", scrollToBottomButton, feather, inputContainerHeight = 0, isResizing = false, className, ...props }) => {
6649
+ const mode = normalizeAutoScroll(autoScroll);
6511
6650
  const [hasMounted, setHasMounted] = (0, react.useState)(false);
6512
- const { scrollRef, contentRef, scrollToBottom } = (0, use_stick_to_bottom.useStickToBottom)();
6651
+ const scrollRef = (0, react.useRef)(null);
6652
+ const contentRef = (0, react.useRef)(null);
6653
+ const scrollToBottom = (0, react.useCallback)(() => {
6654
+ const el = scrollRef.current;
6655
+ if (el) el.scrollTo({
6656
+ top: el.scrollHeight,
6657
+ behavior: "smooth"
6658
+ });
6659
+ }, []);
6513
6660
  const [showScrollButton, setShowScrollButton] = (0, react.useState)(false);
6514
6661
  const [nonAutoScrollEl, setNonAutoScrollEl] = (0, react.useState)(null);
6515
6662
  const nonAutoScrollRefCallback = (0, react.useCallback)((el) => {
@@ -6520,7 +6667,7 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6520
6667
  setHasMounted(true);
6521
6668
  }, []);
6522
6669
  (0, react.useEffect)(() => {
6523
- if (autoScroll) return;
6670
+ if (mode === "pin-to-bottom") return;
6524
6671
  const scrollElement = scrollRef.current;
6525
6672
  if (!scrollElement) return;
6526
6673
  const checkScroll = () => {
@@ -6534,7 +6681,7 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6534
6681
  scrollElement.removeEventListener("scroll", checkScroll);
6535
6682
  resizeObserver.disconnect();
6536
6683
  };
6537
- }, [scrollRef, autoScroll]);
6684
+ }, [scrollRef, mode]);
6538
6685
  if (!hasMounted) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6539
6686
  className: "cpk:h-full cpk:max-h-full cpk:flex cpk:flex-col cpk:min-h-0 cpk:overflow-y-auto cpk:overflow-x-hidden",
6540
6687
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -6542,7 +6689,7 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6542
6689
  children
6543
6690
  })
6544
6691
  });
6545
- if (!autoScroll) {
6692
+ if (mode === "none") {
6546
6693
  const BoundFeather = renderSlot(feather, CopilotChatView.Feather, {});
6547
6694
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ScrollElementContext.Provider, {
6548
6695
  value: nonAutoScrollEl,
@@ -6559,13 +6706,28 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6559
6706
  BoundFeather,
6560
6707
  showScrollButton && !isResizing && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6561
6708
  className: "cpk:absolute cpk:inset-x-0 cpk:flex cpk:justify-center cpk:z-30 cpk:pointer-events-none",
6562
- style: { bottom: `${inputContainerHeight + FEATHER_HEIGHT + 16}px` },
6709
+ style: { bottom: `${inputContainerHeight + SCROLL_BUTTON_OFFSET}px` },
6563
6710
  children: renderSlot(scrollToBottomButton, CopilotChatView.ScrollToBottomButton, { onClick: () => scrollToBottom() })
6564
6711
  })
6565
6712
  ]
6566
6713
  })
6567
6714
  });
6568
6715
  }
6716
+ if (mode === "pin-to-send") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PinToSendScrollContainer, {
6717
+ scrollRef,
6718
+ contentRef,
6719
+ scrollToBottom,
6720
+ scrollToBottomButton,
6721
+ feather,
6722
+ inputContainerHeight,
6723
+ isResizing,
6724
+ nonAutoScrollEl,
6725
+ nonAutoScrollRefCallback,
6726
+ showScrollButton,
6727
+ className,
6728
+ ...props,
6729
+ children
6730
+ });
6569
6731
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(use_stick_to_bottom.StickToBottom, {
6570
6732
  className: cn("cpk:flex-1 cpk:max-h-full cpk:flex cpk:flex-col cpk:min-h-0", className),
6571
6733
  resize: "smooth",
@@ -6588,9 +6750,8 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6588
6750
  ...props,
6589
6751
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronDown, { className: "cpk:w-4 cpk:h-4 cpk:text-gray-600 cpk:dark:text-white" })
6590
6752
  });
6591
- _CopilotChatView.Feather = ({ className, style, ...props }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6592
- className: cn("cpk:absolute cpk:bottom-0 cpk:left-0 cpk:right-4 cpk:h-24 cpk:pointer-events-none cpk:z-10 cpk:bg-gradient-to-t", "cpk:from-white cpk:via-white cpk:to-transparent", "cpk:dark:from-[rgb(33,33,33)] cpk:dark:via-[rgb(33,33,33)]", className),
6593
- style,
6753
+ _CopilotChatView.Feather = ({ className, ...props }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6754
+ className,
6594
6755
  ...props
6595
6756
  });
6596
6757
  _CopilotChatView.WelcomeMessage = ({ className, ...props }) => {
@@ -6762,10 +6923,9 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6762
6923
  var _ref, _attachmentsConfig$ac;
6763
6924
  const existingConfig = useCopilotChatConfiguration();
6764
6925
  const resolvedAgentId = (_ref = agentId !== null && agentId !== void 0 ? agentId : existingConfig === null || existingConfig === void 0 ? void 0 : existingConfig.agentId) !== null && _ref !== void 0 ? _ref : _copilotkit_shared.DEFAULT_AGENT_ID;
6765
- const resolvedThreadId = (0, react.useMemo)(() => {
6766
- var _ref2;
6767
- return (_ref2 = threadId !== null && threadId !== void 0 ? threadId : existingConfig === null || existingConfig === void 0 ? void 0 : existingConfig.threadId) !== null && _ref2 !== void 0 ? _ref2 : (0, _copilotkit_shared.randomUUID)();
6768
- }, [threadId, existingConfig === null || existingConfig === void 0 ? void 0 : existingConfig.threadId]);
6926
+ const providedThreadId = threadId !== null && threadId !== void 0 ? threadId : existingConfig === null || existingConfig === void 0 ? void 0 : existingConfig.threadId;
6927
+ const resolvedThreadId = (0, react.useMemo)(() => providedThreadId !== null && providedThreadId !== void 0 ? providedThreadId : (0, _copilotkit_shared.randomUUID)(), [providedThreadId]);
6928
+ const hasExplicitThreadId = !!threadId || !!(existingConfig === null || existingConfig === void 0 ? void 0 : existingConfig.hasExplicitThreadId);
6769
6929
  const { agent } = useAgent({
6770
6930
  agentId: resolvedAgentId,
6771
6931
  threadId: resolvedThreadId,
@@ -6807,7 +6967,10 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6807
6967
  const isTranscriptionEnabled = copilotkit.audioFileTranscriptionEnabled;
6808
6968
  const isMediaRecorderSupported = typeof window !== "undefined" && typeof MediaRecorder !== "undefined";
6809
6969
  const { messageView: providedMessageView, suggestionView: providedSuggestionView, onStop: providedStopHandler, ...restProps } = props;
6970
+ const [lastConnectedThreadId, setLastConnectedThreadId] = (0, react.useState)(null);
6971
+ const isConnecting = hasExplicitThreadId && lastConnectedThreadId !== resolvedThreadId;
6810
6972
  (0, react.useEffect)(() => {
6973
+ if (!hasExplicitThreadId) return;
6811
6974
  let detached = false;
6812
6975
  const connectAbortController = new AbortController();
6813
6976
  if (agent instanceof _ag_ui_client.HttpAgent) agent.abortController = connectAbortController;
@@ -6817,6 +6980,10 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6817
6980
  } catch (error) {
6818
6981
  if (detached) return;
6819
6982
  console.error("CopilotChat: connectAgent failed", error);
6983
+ } finally {
6984
+ if (!detached) (typeof requestAnimationFrame === "function" ? requestAnimationFrame : (cb) => setTimeout(cb, 16))(() => {
6985
+ if (!detached) setLastConnectedThreadId(resolvedThreadId);
6986
+ });
6820
6987
  }
6821
6988
  };
6822
6989
  connect(agent);
@@ -6828,7 +6995,8 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6828
6995
  }, [
6829
6996
  resolvedThreadId,
6830
6997
  agent,
6831
- resolvedAgentId
6998
+ resolvedAgentId,
6999
+ hasExplicitThreadId
6832
7000
  ]);
6833
7001
  const onSubmitInput = (0, react.useCallback)(async (value) => {
6834
7002
  if (selectedAttachments.some((a) => a.status === "uploading")) {
@@ -6985,6 +7153,22 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6985
7153
  }).join(";") : "";
6986
7154
  return `${m.id}:${m.role}:${contentKey}:${toolCallsKey}`;
6987
7155
  }).join(",")]);
7156
+ const lastUserMessageId = (0, react.useMemo)(() => {
7157
+ for (let i = messages.length - 1; i >= 0; i--) if (messages[i].role === "user") return messages[i].id;
7158
+ return null;
7159
+ }, [messages]);
7160
+ const [sendNonce, setSendNonce] = (0, react.useState)(0);
7161
+ const prevLastUserMessageIdRef = (0, react.useRef)(lastUserMessageId);
7162
+ (0, react.useEffect)(() => {
7163
+ if (lastUserMessageId && lastUserMessageId !== prevLastUserMessageIdRef.current) {
7164
+ setSendNonce((n) => n + 1);
7165
+ prevLastUserMessageIdRef.current = lastUserMessageId;
7166
+ }
7167
+ }, [lastUserMessageId]);
7168
+ const lastUserMessageState = (0, react.useMemo)(() => ({
7169
+ id: lastUserMessageId,
7170
+ sendNonce
7171
+ }), [lastUserMessageId, sendNonce]);
6988
7172
  const RenderedChatView = renderSlot(chatView, CopilotChatView, {
6989
7173
  ...mergedProps,
6990
7174
  messages,
@@ -7003,11 +7187,14 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
7003
7187
  dragOver,
7004
7188
  onDragOver: handleDragOver,
7005
7189
  onDragLeave: handleDragLeave,
7006
- onDrop: handleDrop
7190
+ onDrop: handleDrop,
7191
+ isConnecting,
7192
+ hasExplicitThreadId
7007
7193
  });
7008
7194
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopilotChatConfigurationProvider, {
7009
7195
  agentId: resolvedAgentId,
7010
7196
  threadId: resolvedThreadId,
7197
+ hasExplicitThreadId,
7011
7198
  labels,
7012
7199
  isModalDefaultOpen,
7013
7200
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -7038,7 +7225,10 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
7038
7225
  },
7039
7226
  children: transcriptionError
7040
7227
  }),
7041
- RenderedChatView
7228
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LastUserMessageContext.Provider, {
7229
+ value: lastUserMessageState,
7230
+ children: RenderedChatView
7231
+ })
7042
7232
  ]
7043
7233
  })
7044
7234
  });
@@ -8663,12 +8853,19 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
8663
8853
  //#region src/context/threads-context.tsx
8664
8854
  const ThreadsContext = (0, react.createContext)(void 0);
8665
8855
  function ThreadsProvider({ children, threadId: explicitThreadId }) {
8666
- const [internalThreadId, setThreadId] = (0, react.useState)(() => (0, _copilotkit_shared.randomUUID)());
8856
+ const [internalThreadId, setInternalThreadId] = (0, react.useState)(() => (0, _copilotkit_shared.randomUUID)());
8857
+ const [internalIsExplicit, setInternalIsExplicit] = (0, react.useState)(false);
8667
8858
  const threadId = explicitThreadId !== null && explicitThreadId !== void 0 ? explicitThreadId : internalThreadId;
8859
+ const isThreadIdExplicit = explicitThreadId != null || internalIsExplicit;
8860
+ const setThreadId = (0, react.useCallback)((value) => {
8861
+ setInternalThreadId(value);
8862
+ setInternalIsExplicit(true);
8863
+ }, []);
8668
8864
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ThreadsContext.Provider, {
8669
8865
  value: {
8670
8866
  threadId,
8671
- setThreadId
8867
+ setThreadId,
8868
+ isThreadIdExplicit
8672
8869
  },
8673
8870
  children
8674
8871
  });
@@ -9388,7 +9585,7 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
9388
9585
  if (props.agent) setAgentSession({ agentName: props.agent });
9389
9586
  else setAgentSession(null);
9390
9587
  }, [props.agent]);
9391
- const { threadId, setThreadId: setInternalThreadId } = useThreads$1();
9588
+ const { threadId, setThreadId: setInternalThreadId, isThreadIdExplicit } = useThreads$1();
9392
9589
  const setThreadId = (0, react.useCallback)((value) => {
9393
9590
  if (props.threadId) throw new Error("Cannot call setThreadId() when threadId is provided via props.");
9394
9591
  setInternalThreadId(value);
@@ -9594,6 +9791,7 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
9594
9791
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopilotChatConfigurationProvider, {
9595
9792
  agentId: (_props$agent2 = props.agent) !== null && _props$agent2 !== void 0 ? _props$agent2 : "default",
9596
9793
  threadId,
9794
+ hasExplicitThreadId: isThreadIdExplicit,
9597
9795
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(CopilotContext.Provider, {
9598
9796
  value: copilotContextValue,
9599
9797
  children: [