@copilotkit/react-core 1.56.1 → 1.56.2-canary.test-welcome-screen

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/dist/{copilotkit-CSJw5BG8.cjs → copilotkit-By2G6-Zx.cjs} +250 -63
  2. package/dist/copilotkit-By2G6-Zx.cjs.map +1 -0
  3. package/dist/{copilotkit-CCbxm6JM.d.mts → copilotkit-DFaI4j2r.d.mts} +64 -18
  4. package/dist/copilotkit-DFaI4j2r.d.mts.map +1 -0
  5. package/dist/{copilotkit-BtP7w7cT.d.cts → copilotkit-Dg4r4Gi_.d.cts} +64 -18
  6. package/dist/copilotkit-Dg4r4Gi_.d.cts.map +1 -0
  7. package/dist/{copilotkit-Cj2ZIxVr.mjs → copilotkit-PzJlPKcU.mjs} +251 -64
  8. package/dist/copilotkit-PzJlPKcU.mjs.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 +249 -66
  23. package/dist/v2/index.umd.js.map +1 -1
  24. package/package.json +6 -6
  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/CopilotChatInput.tsx +22 -0
  31. package/src/v2/components/chat/CopilotChatView.tsx +206 -11
  32. package/src/v2/components/chat/__tests__/CopilotChat.absentThreadConnect.test.tsx +66 -0
  33. package/src/v2/components/chat/__tests__/CopilotChat.welcomeGate.test.tsx +186 -0
  34. package/src/v2/components/chat/__tests__/CopilotChatActivityRendering.e2e.test.tsx +300 -2
  35. package/src/v2/components/chat/__tests__/CopilotChatView.connectingGate.test.tsx +56 -0
  36. package/src/v2/components/chat/__tests__/CopilotChatView.pinToSend.test.tsx +94 -0
  37. package/src/v2/components/chat/__tests__/copilot-chat-throttle.test.tsx +0 -1
  38. package/src/v2/components/chat/__tests__/normalize-auto-scroll.test.ts +37 -0
  39. package/src/v2/components/chat/index.ts +2 -0
  40. package/src/v2/components/chat/last-user-message-context.ts +21 -0
  41. package/src/v2/components/chat/normalize-auto-scroll.ts +17 -0
  42. package/src/v2/components/license-warning-banner.tsx +20 -1
  43. package/src/v2/hooks/__tests__/use-agent-stability.test.tsx +6 -0
  44. package/src/v2/hooks/__tests__/use-agent-thread-isolation.test.tsx +6 -0
  45. package/src/v2/hooks/__tests__/use-agent-throttle.test.tsx +76 -50
  46. package/src/v2/hooks/__tests__/use-pin-to-send.test.tsx +219 -0
  47. package/src/v2/hooks/__tests__/use-threads.test.tsx +68 -0
  48. package/src/v2/hooks/use-agent.tsx +34 -77
  49. package/src/v2/hooks/use-pin-to-send.ts +94 -0
  50. package/src/v2/hooks/use-threads.tsx +55 -12
  51. package/src/v2/providers/CopilotChatConfigurationProvider.tsx +29 -1
  52. package/src/v2/providers/CopilotKitProvider.tsx +2 -11
  53. package/src/v2/providers/__tests__/CopilotChatConfigurationProvider.test.tsx +106 -0
  54. package/dist/copilotkit-BtP7w7cT.d.cts.map +0 -1
  55. package/dist/copilotkit-CCbxm6JM.d.mts.map +0 -1
  56. package/dist/copilotkit-CSJw5BG8.cjs.map +0 -1
  57. package/dist/copilotkit-Cj2ZIxVr.mjs.map +0 -1
@@ -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,
@@ -6308,9 +6299,97 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6308
6299
  return keyboardState;
6309
6300
  }
6310
6301
 
6302
+ //#endregion
6303
+ //#region src/v2/components/chat/normalize-auto-scroll.ts
6304
+ const VALID = [
6305
+ "pin-to-bottom",
6306
+ "pin-to-send",
6307
+ "none"
6308
+ ];
6309
+ function normalizeAutoScroll(value) {
6310
+ if (value === void 0) return "pin-to-bottom";
6311
+ if (value === true) return "pin-to-bottom";
6312
+ if (value === false) return "none";
6313
+ if (VALID.includes(value)) return value;
6314
+ return "pin-to-bottom";
6315
+ }
6316
+
6317
+ //#endregion
6318
+ //#region src/v2/components/chat/last-user-message-context.ts
6319
+ const LastUserMessageContext = react.default.createContext({
6320
+ id: null,
6321
+ sendNonce: 0
6322
+ });
6323
+
6324
+ //#endregion
6325
+ //#region src/v2/hooks/use-pin-to-send.ts
6326
+ function usePinToSend({ scrollRef, contentRef, spacerRef, topOffset = 16 }) {
6327
+ const { id, sendNonce } = (0, react.useContext)(LastUserMessageContext);
6328
+ const lastNonceRef = (0, react.useRef)(-1);
6329
+ const currentSpacerHeightRef = (0, react.useRef)(0);
6330
+ (0, react.useEffect)(() => {
6331
+ if (sendNonce === lastNonceRef.current) return;
6332
+ lastNonceRef.current = sendNonce;
6333
+ if (!id) return;
6334
+ const scrollEl = scrollRef.current;
6335
+ const contentEl = contentRef.current;
6336
+ const spacerEl = spacerRef.current;
6337
+ if (!scrollEl || !contentEl || !spacerEl) return;
6338
+ const escaped = typeof CSS !== "undefined" && CSS.escape ? CSS.escape(id) : id.replace(/[!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g, "\\$&");
6339
+ const targetEl = contentEl.querySelector(`[data-message-id="${escaped}"]`);
6340
+ if (!targetEl) return;
6341
+ const viewportHeight = scrollEl.clientHeight;
6342
+ const userMessageHeight = targetEl.getBoundingClientRect().height;
6343
+ const paddingTop = parseFloat(getComputedStyle(targetEl).paddingTop) || 0;
6344
+ const bubbleHeight = Math.max(0, userMessageHeight - paddingTop);
6345
+ const spacerHeight = Math.max(0, viewportHeight - bubbleHeight - topOffset);
6346
+ spacerEl.style.height = `${spacerHeight}px`;
6347
+ currentSpacerHeightRef.current = spacerHeight;
6348
+ const raf = requestAnimationFrame(() => {
6349
+ const targetTop = computeOffsetTop(targetEl, scrollEl) + paddingTop - topOffset;
6350
+ scrollEl.scrollTo({
6351
+ top: Math.max(0, targetTop),
6352
+ behavior: "smooth"
6353
+ });
6354
+ });
6355
+ const ro = new ResizeObserver(() => {
6356
+ if (!contentEl || !spacerEl || !scrollEl) return;
6357
+ const consumedBelow = contentEl.getBoundingClientRect().height - computeOffsetTop(targetEl, contentEl) - userMessageHeight;
6358
+ const remaining = Math.max(0, spacerHeight - consumedBelow);
6359
+ if (remaining < currentSpacerHeightRef.current) {
6360
+ spacerEl.style.height = `${remaining}px`;
6361
+ currentSpacerHeightRef.current = remaining;
6362
+ }
6363
+ });
6364
+ ro.observe(contentEl);
6365
+ return () => {
6366
+ cancelAnimationFrame(raf);
6367
+ ro.disconnect();
6368
+ };
6369
+ }, [
6370
+ id,
6371
+ sendNonce,
6372
+ scrollRef,
6373
+ contentRef,
6374
+ spacerRef,
6375
+ topOffset
6376
+ ]);
6377
+ }
6378
+ function computeOffsetTop(el, stopAt) {
6379
+ const elRect = el.getBoundingClientRect();
6380
+ const stopRect = stopAt.getBoundingClientRect();
6381
+ return elRect.top - stopRect.top + stopAt.scrollTop;
6382
+ }
6383
+
6311
6384
  //#endregion
6312
6385
  //#region src/v2/components/chat/CopilotChatView.tsx
6313
6386
  const FEATHER_HEIGHT = 96;
6387
+ const PIN_TO_SEND_FEATHER_HEIGHT = 48;
6388
+ const PinToSendSoftFeather = ({ className, style, ...props }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6389
+ className: cn("cpk:absolute cpk:bottom-0 cpk:left-0 cpk:right-4 cpk:h-12 cpk:pointer-events-none cpk:z-10 cpk:bg-gradient-to-t", "cpk:from-white cpk:to-transparent", "cpk:dark:from-[rgb(33,33,33)]", className),
6390
+ style,
6391
+ ...props
6392
+ });
6314
6393
  function DropOverlay() {
6315
6394
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6316
6395
  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,7 +6402,7 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6323
6402
  })
6324
6403
  });
6325
6404
  }
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 }) {
6405
+ 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 }) {
6327
6406
  const inputContainerRef = (0, react.useRef)(null);
6328
6407
  const [inputContainerHeight, setInputContainerHeight] = (0, react.useState)(0);
6329
6408
  const [isResizing, setIsResizing] = (0, react.useState)(false);
@@ -6375,9 +6454,10 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6375
6454
  keyboardHeight: isKeyboardOpen ? keyboardHeight : 0,
6376
6455
  containerRef: inputContainerRef,
6377
6456
  showDisclaimer: true,
6457
+ bottomAnchored: true,
6378
6458
  ...disclaimer !== void 0 ? { disclaimer } : {}
6379
6459
  });
6380
- const hasSuggestions = Array.isArray(suggestions) && suggestions.length > 0;
6460
+ const hasSuggestions = !isConnecting && !isRunning && Array.isArray(suggestions) && suggestions.length > 0;
6381
6461
  const BoundSuggestionView = hasSuggestions ? renderSlot(suggestionView, CopilotChatSuggestionView, {
6382
6462
  suggestions,
6383
6463
  loadingIndexes: suggestionLoadingIndexes,
@@ -6399,7 +6479,7 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6399
6479
  })
6400
6480
  })
6401
6481
  });
6402
- if (messages.length === 0 && !(welcomeScreen === false)) {
6482
+ if (messages.length === 0 && !(welcomeScreen === false) && !isConnecting && !hasExplicitThreadId) {
6403
6483
  const BoundInputForWelcome = renderSlot(input, CopilotChatInput_default, {
6404
6484
  onSubmitMessage,
6405
6485
  onStop,
@@ -6507,9 +6587,60 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6507
6587
  ] })
6508
6588
  });
6509
6589
  };
6510
- _CopilotChatView.ScrollView = ({ children, autoScroll = true, scrollToBottomButton, feather, inputContainerHeight = 0, isResizing = false, className, ...props }) => {
6590
+ const PinToSendScrollContainer = ({ children, scrollRef, contentRef, scrollToBottom, scrollToBottomButton, feather, inputContainerHeight, isResizing, nonAutoScrollEl, nonAutoScrollRefCallback, showScrollButton, className, ...props }) => {
6591
+ const spacerRef = (0, react.useRef)(null);
6592
+ usePinToSend({
6593
+ scrollRef,
6594
+ contentRef,
6595
+ spacerRef,
6596
+ topOffset: 16
6597
+ });
6598
+ const BoundFeather = renderSlot(feather, PinToSendSoftFeather, {});
6599
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ScrollElementContext.Provider, {
6600
+ value: nonAutoScrollEl,
6601
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6602
+ className: cn("cpk:h-full cpk:max-h-full cpk:flex cpk:flex-col cpk:min-h-0 cpk:relative", className),
6603
+ children: [
6604
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6605
+ ref: nonAutoScrollRefCallback,
6606
+ className: "cpk:flex-1 cpk:min-h-0 cpk:overflow-y-auto cpk:overflow-x-hidden",
6607
+ ...props,
6608
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6609
+ ref: contentRef,
6610
+ className: "cpk:px-4 cpk:sm:px-0 cpk:[div[data-sidebar-chat]_&]:px-8 cpk:[div[data-popup-chat]_&]:px-6",
6611
+ children
6612
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6613
+ ref: spacerRef,
6614
+ "data-pin-to-send-spacer": true,
6615
+ "aria-hidden": "true",
6616
+ style: {
6617
+ height: 0,
6618
+ flex: "0 0 auto"
6619
+ }
6620
+ })]
6621
+ }),
6622
+ BoundFeather,
6623
+ showScrollButton && !isResizing && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6624
+ className: "cpk:absolute cpk:inset-x-0 cpk:flex cpk:justify-center cpk:z-30 cpk:pointer-events-none",
6625
+ style: { bottom: `${inputContainerHeight + PIN_TO_SEND_FEATHER_HEIGHT + 16}px` },
6626
+ children: renderSlot(scrollToBottomButton, CopilotChatView.ScrollToBottomButton, { onClick: () => scrollToBottom() })
6627
+ })
6628
+ ]
6629
+ })
6630
+ });
6631
+ };
6632
+ _CopilotChatView.ScrollView = ({ children, autoScroll = "pin-to-bottom", scrollToBottomButton, feather, inputContainerHeight = 0, isResizing = false, className, ...props }) => {
6633
+ const mode = normalizeAutoScroll(autoScroll);
6511
6634
  const [hasMounted, setHasMounted] = (0, react.useState)(false);
6512
- const { scrollRef, contentRef, scrollToBottom } = (0, use_stick_to_bottom.useStickToBottom)();
6635
+ const scrollRef = (0, react.useRef)(null);
6636
+ const contentRef = (0, react.useRef)(null);
6637
+ const scrollToBottom = (0, react.useCallback)(() => {
6638
+ const el = scrollRef.current;
6639
+ if (el) el.scrollTo({
6640
+ top: el.scrollHeight,
6641
+ behavior: "smooth"
6642
+ });
6643
+ }, []);
6513
6644
  const [showScrollButton, setShowScrollButton] = (0, react.useState)(false);
6514
6645
  const [nonAutoScrollEl, setNonAutoScrollEl] = (0, react.useState)(null);
6515
6646
  const nonAutoScrollRefCallback = (0, react.useCallback)((el) => {
@@ -6520,7 +6651,7 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6520
6651
  setHasMounted(true);
6521
6652
  }, []);
6522
6653
  (0, react.useEffect)(() => {
6523
- if (autoScroll) return;
6654
+ if (mode === "pin-to-bottom") return;
6524
6655
  const scrollElement = scrollRef.current;
6525
6656
  if (!scrollElement) return;
6526
6657
  const checkScroll = () => {
@@ -6534,7 +6665,7 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6534
6665
  scrollElement.removeEventListener("scroll", checkScroll);
6535
6666
  resizeObserver.disconnect();
6536
6667
  };
6537
- }, [scrollRef, autoScroll]);
6668
+ }, [scrollRef, mode]);
6538
6669
  if (!hasMounted) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6539
6670
  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
6671
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -6542,7 +6673,7 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6542
6673
  children
6543
6674
  })
6544
6675
  });
6545
- if (!autoScroll) {
6676
+ if (mode === "none") {
6546
6677
  const BoundFeather = renderSlot(feather, CopilotChatView.Feather, {});
6547
6678
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ScrollElementContext.Provider, {
6548
6679
  value: nonAutoScrollEl,
@@ -6566,6 +6697,21 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6566
6697
  })
6567
6698
  });
6568
6699
  }
6700
+ if (mode === "pin-to-send") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PinToSendScrollContainer, {
6701
+ scrollRef,
6702
+ contentRef,
6703
+ scrollToBottom,
6704
+ scrollToBottomButton,
6705
+ feather,
6706
+ inputContainerHeight,
6707
+ isResizing,
6708
+ nonAutoScrollEl,
6709
+ nonAutoScrollRefCallback,
6710
+ showScrollButton,
6711
+ className,
6712
+ ...props,
6713
+ children
6714
+ });
6569
6715
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(use_stick_to_bottom.StickToBottom, {
6570
6716
  className: cn("cpk:flex-1 cpk:max-h-full cpk:flex cpk:flex-col cpk:min-h-0", className),
6571
6717
  resize: "smooth",
@@ -6762,10 +6908,9 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6762
6908
  var _ref, _attachmentsConfig$ac;
6763
6909
  const existingConfig = useCopilotChatConfiguration();
6764
6910
  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]);
6911
+ const providedThreadId = threadId !== null && threadId !== void 0 ? threadId : existingConfig === null || existingConfig === void 0 ? void 0 : existingConfig.threadId;
6912
+ const resolvedThreadId = (0, react.useMemo)(() => providedThreadId !== null && providedThreadId !== void 0 ? providedThreadId : (0, _copilotkit_shared.randomUUID)(), [providedThreadId]);
6913
+ const hasExplicitThreadId = !!threadId || !!(existingConfig === null || existingConfig === void 0 ? void 0 : existingConfig.hasExplicitThreadId);
6769
6914
  const { agent } = useAgent({
6770
6915
  agentId: resolvedAgentId,
6771
6916
  threadId: resolvedThreadId,
@@ -6807,7 +6952,10 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6807
6952
  const isTranscriptionEnabled = copilotkit.audioFileTranscriptionEnabled;
6808
6953
  const isMediaRecorderSupported = typeof window !== "undefined" && typeof MediaRecorder !== "undefined";
6809
6954
  const { messageView: providedMessageView, suggestionView: providedSuggestionView, onStop: providedStopHandler, ...restProps } = props;
6955
+ const [lastConnectedThreadId, setLastConnectedThreadId] = (0, react.useState)(null);
6956
+ const isConnecting = hasExplicitThreadId && lastConnectedThreadId !== resolvedThreadId;
6810
6957
  (0, react.useEffect)(() => {
6958
+ if (!hasExplicitThreadId) return;
6811
6959
  let detached = false;
6812
6960
  const connectAbortController = new AbortController();
6813
6961
  if (agent instanceof _ag_ui_client.HttpAgent) agent.abortController = connectAbortController;
@@ -6817,6 +6965,10 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6817
6965
  } catch (error) {
6818
6966
  if (detached) return;
6819
6967
  console.error("CopilotChat: connectAgent failed", error);
6968
+ } finally {
6969
+ if (!detached) (typeof requestAnimationFrame === "function" ? requestAnimationFrame : (cb) => setTimeout(cb, 16))(() => {
6970
+ if (!detached) setLastConnectedThreadId(resolvedThreadId);
6971
+ });
6820
6972
  }
6821
6973
  };
6822
6974
  connect(agent);
@@ -6828,7 +6980,8 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6828
6980
  }, [
6829
6981
  resolvedThreadId,
6830
6982
  agent,
6831
- resolvedAgentId
6983
+ resolvedAgentId,
6984
+ hasExplicitThreadId
6832
6985
  ]);
6833
6986
  const onSubmitInput = (0, react.useCallback)(async (value) => {
6834
6987
  if (selectedAttachments.some((a) => a.status === "uploading")) {
@@ -6985,6 +7138,22 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6985
7138
  }).join(";") : "";
6986
7139
  return `${m.id}:${m.role}:${contentKey}:${toolCallsKey}`;
6987
7140
  }).join(",")]);
7141
+ const lastUserMessageId = (0, react.useMemo)(() => {
7142
+ for (let i = messages.length - 1; i >= 0; i--) if (messages[i].role === "user") return messages[i].id;
7143
+ return null;
7144
+ }, [messages]);
7145
+ const [sendNonce, setSendNonce] = (0, react.useState)(0);
7146
+ const prevLastUserMessageIdRef = (0, react.useRef)(lastUserMessageId);
7147
+ (0, react.useEffect)(() => {
7148
+ if (lastUserMessageId && lastUserMessageId !== prevLastUserMessageIdRef.current) {
7149
+ setSendNonce((n) => n + 1);
7150
+ prevLastUserMessageIdRef.current = lastUserMessageId;
7151
+ }
7152
+ }, [lastUserMessageId]);
7153
+ const lastUserMessageState = (0, react.useMemo)(() => ({
7154
+ id: lastUserMessageId,
7155
+ sendNonce
7156
+ }), [lastUserMessageId, sendNonce]);
6988
7157
  const RenderedChatView = renderSlot(chatView, CopilotChatView, {
6989
7158
  ...mergedProps,
6990
7159
  messages,
@@ -7003,11 +7172,14 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
7003
7172
  dragOver,
7004
7173
  onDragOver: handleDragOver,
7005
7174
  onDragLeave: handleDragLeave,
7006
- onDrop: handleDrop
7175
+ onDrop: handleDrop,
7176
+ isConnecting,
7177
+ hasExplicitThreadId
7007
7178
  });
7008
7179
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopilotChatConfigurationProvider, {
7009
7180
  agentId: resolvedAgentId,
7010
7181
  threadId: resolvedThreadId,
7182
+ hasExplicitThreadId,
7011
7183
  labels,
7012
7184
  isModalDefaultOpen,
7013
7185
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -7038,7 +7210,10 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
7038
7210
  },
7039
7211
  children: transcriptionError
7040
7212
  }),
7041
- RenderedChatView
7213
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LastUserMessageContext.Provider, {
7214
+ value: lastUserMessageState,
7215
+ children: RenderedChatView
7216
+ })
7042
7217
  ]
7043
7218
  })
7044
7219
  });
@@ -8663,12 +8838,19 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
8663
8838
  //#region src/context/threads-context.tsx
8664
8839
  const ThreadsContext = (0, react.createContext)(void 0);
8665
8840
  function ThreadsProvider({ children, threadId: explicitThreadId }) {
8666
- const [internalThreadId, setThreadId] = (0, react.useState)(() => (0, _copilotkit_shared.randomUUID)());
8841
+ const [internalThreadId, setInternalThreadId] = (0, react.useState)(() => (0, _copilotkit_shared.randomUUID)());
8842
+ const [internalIsExplicit, setInternalIsExplicit] = (0, react.useState)(false);
8667
8843
  const threadId = explicitThreadId !== null && explicitThreadId !== void 0 ? explicitThreadId : internalThreadId;
8844
+ const isThreadIdExplicit = explicitThreadId != null || internalIsExplicit;
8845
+ const setThreadId = (0, react.useCallback)((value) => {
8846
+ setInternalThreadId(value);
8847
+ setInternalIsExplicit(true);
8848
+ }, []);
8668
8849
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ThreadsContext.Provider, {
8669
8850
  value: {
8670
8851
  threadId,
8671
- setThreadId
8852
+ setThreadId,
8853
+ isThreadIdExplicit
8672
8854
  },
8673
8855
  children
8674
8856
  });
@@ -9388,7 +9570,7 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
9388
9570
  if (props.agent) setAgentSession({ agentName: props.agent });
9389
9571
  else setAgentSession(null);
9390
9572
  }, [props.agent]);
9391
- const { threadId, setThreadId: setInternalThreadId } = useThreads$1();
9573
+ const { threadId, setThreadId: setInternalThreadId, isThreadIdExplicit } = useThreads$1();
9392
9574
  const setThreadId = (0, react.useCallback)((value) => {
9393
9575
  if (props.threadId) throw new Error("Cannot call setThreadId() when threadId is provided via props.");
9394
9576
  setInternalThreadId(value);
@@ -9594,6 +9776,7 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
9594
9776
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopilotChatConfigurationProvider, {
9595
9777
  agentId: (_props$agent2 = props.agent) !== null && _props$agent2 !== void 0 ? _props$agent2 : "default",
9596
9778
  threadId,
9779
+ hasExplicitThreadId: isThreadIdExplicit,
9597
9780
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(CopilotContext.Provider, {
9598
9781
  value: copilotContextValue,
9599
9782
  children: [