@copilotkit/react-core 1.56.1 → 1.56.2-canary.pin-to-send

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 (48) hide show
  1. package/dist/{copilotkit-Cj2ZIxVr.mjs → copilotkit-BBYbekCa.mjs} +234 -60
  2. package/dist/copilotkit-BBYbekCa.mjs.map +1 -0
  3. package/dist/{copilotkit-CSJw5BG8.cjs → copilotkit-D5JT2Pu3.cjs} +233 -59
  4. package/dist/copilotkit-D5JT2Pu3.cjs.map +1 -0
  5. package/dist/{copilotkit-CCbxm6JM.d.mts → copilotkit-DArT2Iuw.d.mts} +62 -18
  6. package/dist/copilotkit-DArT2Iuw.d.mts.map +1 -0
  7. package/dist/{copilotkit-BtP7w7cT.d.cts → copilotkit-KEc28l8G.d.cts} +62 -18
  8. package/dist/copilotkit-KEc28l8G.d.cts.map +1 -0
  9. package/dist/index.cjs +1 -1
  10. package/dist/index.d.cts +1 -1
  11. package/dist/index.d.mts +1 -1
  12. package/dist/index.mjs +1 -1
  13. package/dist/index.umd.js +16 -40
  14. package/dist/index.umd.js.map +1 -1
  15. package/dist/v2/index.cjs +1 -1
  16. package/dist/v2/index.css +1 -1
  17. package/dist/v2/index.d.cts +2 -2
  18. package/dist/v2/index.d.mts +2 -2
  19. package/dist/v2/index.mjs +1 -1
  20. package/dist/v2/index.umd.js +232 -62
  21. package/dist/v2/index.umd.js.map +1 -1
  22. package/package.json +6 -6
  23. package/src/v2/components/chat/CopilotChat.tsx +80 -4
  24. package/src/v2/components/chat/CopilotChatInput.tsx +22 -0
  25. package/src/v2/components/chat/CopilotChatView.tsx +206 -11
  26. package/src/v2/components/chat/__tests__/CopilotChat.absentThreadConnect.test.tsx +66 -0
  27. package/src/v2/components/chat/__tests__/CopilotChatActivityRendering.e2e.test.tsx +300 -2
  28. package/src/v2/components/chat/__tests__/CopilotChatView.connectingGate.test.tsx +56 -0
  29. package/src/v2/components/chat/__tests__/CopilotChatView.pinToSend.test.tsx +94 -0
  30. package/src/v2/components/chat/__tests__/copilot-chat-throttle.test.tsx +0 -1
  31. package/src/v2/components/chat/__tests__/normalize-auto-scroll.test.ts +37 -0
  32. package/src/v2/components/chat/index.ts +2 -0
  33. package/src/v2/components/chat/last-user-message-context.ts +21 -0
  34. package/src/v2/components/chat/normalize-auto-scroll.ts +17 -0
  35. package/src/v2/components/license-warning-banner.tsx +20 -1
  36. package/src/v2/hooks/__tests__/use-agent-stability.test.tsx +6 -0
  37. package/src/v2/hooks/__tests__/use-agent-thread-isolation.test.tsx +6 -0
  38. package/src/v2/hooks/__tests__/use-agent-throttle.test.tsx +76 -50
  39. package/src/v2/hooks/__tests__/use-pin-to-send.test.tsx +219 -0
  40. package/src/v2/hooks/__tests__/use-threads.test.tsx +68 -0
  41. package/src/v2/hooks/use-agent.tsx +34 -77
  42. package/src/v2/hooks/use-pin-to-send.ts +94 -0
  43. package/src/v2/hooks/use-threads.tsx +55 -12
  44. package/src/v2/providers/CopilotKitProvider.tsx +2 -11
  45. package/dist/copilotkit-BtP7w7cT.d.cts.map +0 -1
  46. package/dist/copilotkit-CCbxm6JM.d.mts.map +0 -1
  47. package/dist/copilotkit-CSJw5BG8.cjs.map +0 -1
  48. package/dist/copilotkit-Cj2ZIxVr.mjs.map +0 -1
@@ -599,7 +599,7 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
599
599
  //#region src/v2/components/chat/CopilotChatInput.tsx
600
600
  const SLASH_MENU_MAX_VISIBLE_ITEMS = 5;
601
601
  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 }) {
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, bottomAnchored = false, textArea, sendButton, startTranscribeButton, cancelTranscribeButton, finishTranscribeButton, addMenuButton, audioRecorder, disclaimer, children, className, ...props }) {
603
603
  var _config$labels;
604
604
  const isControlled = value !== void 0;
605
605
  const [internalValue, setInternalValue] = (0, react.useState)(() => value !== null && value !== void 0 ? value : "");
@@ -1134,7 +1134,8 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
1134
1134
  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
1135
  style: {
1136
1136
  transform: keyboardHeight > 0 ? `translateY(-${keyboardHeight}px)` : void 0,
1137
- transition: "transform 0.2s ease-out"
1137
+ transition: "transform 0.2s ease-out",
1138
+ ...positioning === "absolute" || bottomAnchored ? { paddingBottom: "var(--copilotkit-license-banner-offset, 0px)" } : {}
1138
1139
  },
1139
1140
  ...props,
1140
1141
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -1361,6 +1362,8 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
1361
1362
 
1362
1363
  //#endregion
1363
1364
  //#region src/v2/components/license-warning-banner.tsx
1365
+ const LICENSE_BANNER_OFFSET_PX = 52;
1366
+ const LICENSE_BANNER_OFFSET_VAR = "--copilotkit-license-banner-offset";
1364
1367
  const BANNER_STYLES = {
1365
1368
  base: {
1366
1369
  position: "fixed",
@@ -1402,6 +1405,14 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
1402
1405
  }
1403
1406
  }
1404
1407
  function BannerShell({ severity, message, actionLabel, actionUrl, onDismiss }) {
1408
+ (0, react.useEffect)(() => {
1409
+ if (typeof document === "undefined") return;
1410
+ const root = document.documentElement;
1411
+ root.style.setProperty(LICENSE_BANNER_OFFSET_VAR, `${LICENSE_BANNER_OFFSET_PX}px`);
1412
+ return () => {
1413
+ root.style.removeProperty(LICENSE_BANNER_OFFSET_VAR);
1414
+ };
1415
+ }, []);
1405
1416
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1406
1417
  style: {
1407
1418
  ...BANNER_STYLES.base,
@@ -3386,7 +3397,6 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
3386
3397
  didMountRef.current = true;
3387
3398
  }, []);
3388
3399
  (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
3400
  copilotkit.setDefaultThrottleMs(defaultThrottleMs);
3391
3401
  }, [copilotkit, defaultThrottleMs]);
3392
3402
  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 +3618,6 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
3608
3618
  const providerThrottleMs = copilotkit.defaultThrottleMs;
3609
3619
  const chatConfig = useCopilotChatConfiguration();
3610
3620
  (_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
3621
  const [, forceUpdate] = (0, react.useReducer)((x) => x + 1, 0);
3622
3622
  const updateFlags = (0, react.useMemo)(() => updates !== null && updates !== void 0 ? updates : ALL_UPDATES, [JSON.stringify(updates)]);
3623
3623
  const provisionalAgentCache = (0, react.useRef)(/* @__PURE__ */ new Map());
@@ -3681,9 +3681,8 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
3681
3681
  ]);
3682
3682
  (0, react.useEffect)(() => {
3683
3683
  if (updateFlags.length === 0) return;
3684
- const handlers = {};
3685
- let timerId = null;
3686
3684
  let active = true;
3685
+ const handlers = {};
3687
3686
  let batchScheduled = false;
3688
3687
  const batchedForceUpdate = () => {
3689
3688
  if (!active) return;
@@ -3695,46 +3694,24 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
3695
3694
  });
3696
3695
  }
3697
3696
  };
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
- }
3697
+ if (updateFlags.includes(UseAgentUpdate.OnMessagesChanged)) handlers.onMessagesChanged = forceUpdate;
3722
3698
  if (updateFlags.includes(UseAgentUpdate.OnStateChanged)) handlers.onStateChanged = batchedForceUpdate;
3723
3699
  if (updateFlags.includes(UseAgentUpdate.OnRunStatusChanged)) {
3724
3700
  handlers.onRunInitialized = batchedForceUpdate;
3725
3701
  handlers.onRunFinalized = batchedForceUpdate;
3726
3702
  handlers.onRunFailed = batchedForceUpdate;
3703
+ handlers.onRunErrorEvent = batchedForceUpdate;
3727
3704
  }
3728
- const subscription = agent.subscribe(handlers);
3705
+ const subscription = copilotkit.subscribeToAgentWithOptions(agent, handlers, { throttleMs });
3729
3706
  return () => {
3730
3707
  active = false;
3731
- if (timerId !== null) clearTimeout(timerId);
3732
3708
  subscription.unsubscribe();
3733
3709
  };
3734
3710
  }, [
3735
3711
  agent,
3736
3712
  forceUpdate,
3737
- effectiveThrottleMs,
3713
+ throttleMs,
3714
+ providerThrottleMs,
3738
3715
  updateFlags
3739
3716
  ]);
3740
3717
  (0, react.useEffect)(() => {
@@ -4705,13 +4682,14 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
4705
4682
  const { copilotkit } = useCopilotKit();
4706
4683
  const [store] = (0, react.useState)(() => (0, _copilotkit_core.ɵcreateThreadStore)({ fetch: globalThis.fetch }));
4707
4684
  const coreThreads = useThreadStoreSelector(store, _copilotkit_core.ɵselectThreads);
4708
- const threads = (0, react.useMemo)(() => coreThreads.map(({ id, agentId, name, archived, createdAt, updatedAt }) => ({
4685
+ const threads = (0, react.useMemo)(() => coreThreads.map(({ id, agentId, name, archived, createdAt, updatedAt, lastRunAt }) => ({
4709
4686
  id,
4710
4687
  agentId,
4711
4688
  name,
4712
4689
  archived,
4713
4690
  createdAt,
4714
- updatedAt
4691
+ updatedAt,
4692
+ ...lastRunAt !== void 0 ? { lastRunAt } : {}
4715
4693
  })), [coreThreads]);
4716
4694
  const storeIsLoading = useThreadStoreSelector(store, _copilotkit_core.ɵselectThreadsIsLoading);
4717
4695
  const storeError = useThreadStoreSelector(store, _copilotkit_core.ɵselectThreadsError);
@@ -4725,7 +4703,9 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
4725
4703
  if (copilotkit.runtimeUrl) return null;
4726
4704
  return /* @__PURE__ */ new Error("Runtime URL is not configured");
4727
4705
  }, [copilotkit.runtimeUrl]);
4728
- const isLoading = runtimeError ? false : storeIsLoading;
4706
+ const [hasDispatchedContext, setHasDispatchedContext] = (0, react.useState)(false);
4707
+ const preConnectLoading = !!copilotkit.runtimeUrl && !hasDispatchedContext;
4708
+ const isLoading = runtimeError ? false : preConnectLoading || storeIsLoading;
4729
4709
  const error = runtimeError !== null && runtimeError !== void 0 ? runtimeError : storeError;
4730
4710
  (0, react.useEffect)(() => {
4731
4711
  store.start();
@@ -4733,20 +4713,28 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
4733
4713
  store.stop();
4734
4714
  };
4735
4715
  }, [store]);
4716
+ const runtimeStatus = copilotkit.runtimeConnectionStatus;
4736
4717
  (0, react.useEffect)(() => {
4737
4718
  var _copilotkit$intellige;
4738
- const context = copilotkit.runtimeUrl ? {
4719
+ if (!copilotkit.runtimeUrl) {
4720
+ store.setContext(null);
4721
+ return;
4722
+ }
4723
+ if (runtimeStatus !== _copilotkit_core.CopilotKitCoreRuntimeConnectionStatus.Connected) return;
4724
+ const context = {
4739
4725
  runtimeUrl: copilotkit.runtimeUrl,
4740
4726
  headers: { ...copilotkit.headers },
4741
4727
  wsUrl: (_copilotkit$intellige = copilotkit.intelligence) === null || _copilotkit$intellige === void 0 ? void 0 : _copilotkit$intellige.wsUrl,
4742
4728
  agentId,
4743
4729
  includeArchived,
4744
4730
  limit
4745
- } : null;
4731
+ };
4746
4732
  store.setContext(context);
4733
+ setHasDispatchedContext(true);
4747
4734
  }, [
4748
4735
  store,
4749
4736
  copilotkit.runtimeUrl,
4737
+ runtimeStatus,
4750
4738
  headersKey,
4751
4739
  (_copilotkit$intellige2 = copilotkit.intelligence) === null || _copilotkit$intellige2 === void 0 ? void 0 : _copilotkit$intellige2.wsUrl,
4752
4740
  agentId,
@@ -6308,9 +6296,97 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6308
6296
  return keyboardState;
6309
6297
  }
6310
6298
 
6299
+ //#endregion
6300
+ //#region src/v2/components/chat/normalize-auto-scroll.ts
6301
+ const VALID = [
6302
+ "pin-to-bottom",
6303
+ "pin-to-send",
6304
+ "none"
6305
+ ];
6306
+ function normalizeAutoScroll(value) {
6307
+ if (value === void 0) return "pin-to-bottom";
6308
+ if (value === true) return "pin-to-bottom";
6309
+ if (value === false) return "none";
6310
+ if (VALID.includes(value)) return value;
6311
+ return "pin-to-bottom";
6312
+ }
6313
+
6314
+ //#endregion
6315
+ //#region src/v2/components/chat/last-user-message-context.ts
6316
+ const LastUserMessageContext = react.default.createContext({
6317
+ id: null,
6318
+ sendNonce: 0
6319
+ });
6320
+
6321
+ //#endregion
6322
+ //#region src/v2/hooks/use-pin-to-send.ts
6323
+ function usePinToSend({ scrollRef, contentRef, spacerRef, topOffset = 16 }) {
6324
+ const { id, sendNonce } = (0, react.useContext)(LastUserMessageContext);
6325
+ const lastNonceRef = (0, react.useRef)(-1);
6326
+ const currentSpacerHeightRef = (0, react.useRef)(0);
6327
+ (0, react.useEffect)(() => {
6328
+ if (sendNonce === lastNonceRef.current) return;
6329
+ lastNonceRef.current = sendNonce;
6330
+ if (!id) return;
6331
+ const scrollEl = scrollRef.current;
6332
+ const contentEl = contentRef.current;
6333
+ const spacerEl = spacerRef.current;
6334
+ if (!scrollEl || !contentEl || !spacerEl) return;
6335
+ const escaped = typeof CSS !== "undefined" && CSS.escape ? CSS.escape(id) : id.replace(/[!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g, "\\$&");
6336
+ const targetEl = contentEl.querySelector(`[data-message-id="${escaped}"]`);
6337
+ if (!targetEl) return;
6338
+ const viewportHeight = scrollEl.clientHeight;
6339
+ const userMessageHeight = targetEl.getBoundingClientRect().height;
6340
+ const paddingTop = parseFloat(getComputedStyle(targetEl).paddingTop) || 0;
6341
+ const bubbleHeight = Math.max(0, userMessageHeight - paddingTop);
6342
+ const spacerHeight = Math.max(0, viewportHeight - bubbleHeight - topOffset);
6343
+ spacerEl.style.height = `${spacerHeight}px`;
6344
+ currentSpacerHeightRef.current = spacerHeight;
6345
+ const raf = requestAnimationFrame(() => {
6346
+ const targetTop = computeOffsetTop(targetEl, scrollEl) + paddingTop - topOffset;
6347
+ scrollEl.scrollTo({
6348
+ top: Math.max(0, targetTop),
6349
+ behavior: "smooth"
6350
+ });
6351
+ });
6352
+ const ro = new ResizeObserver(() => {
6353
+ if (!contentEl || !spacerEl || !scrollEl) return;
6354
+ const consumedBelow = contentEl.getBoundingClientRect().height - computeOffsetTop(targetEl, contentEl) - userMessageHeight;
6355
+ const remaining = Math.max(0, spacerHeight - consumedBelow);
6356
+ if (remaining < currentSpacerHeightRef.current) {
6357
+ spacerEl.style.height = `${remaining}px`;
6358
+ currentSpacerHeightRef.current = remaining;
6359
+ }
6360
+ });
6361
+ ro.observe(contentEl);
6362
+ return () => {
6363
+ cancelAnimationFrame(raf);
6364
+ ro.disconnect();
6365
+ };
6366
+ }, [
6367
+ id,
6368
+ sendNonce,
6369
+ scrollRef,
6370
+ contentRef,
6371
+ spacerRef,
6372
+ topOffset
6373
+ ]);
6374
+ }
6375
+ function computeOffsetTop(el, stopAt) {
6376
+ const elRect = el.getBoundingClientRect();
6377
+ const stopRect = stopAt.getBoundingClientRect();
6378
+ return elRect.top - stopRect.top + stopAt.scrollTop;
6379
+ }
6380
+
6311
6381
  //#endregion
6312
6382
  //#region src/v2/components/chat/CopilotChatView.tsx
6313
6383
  const FEATHER_HEIGHT = 96;
6384
+ const PIN_TO_SEND_FEATHER_HEIGHT = 48;
6385
+ const PinToSendSoftFeather = ({ className, style, ...props }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6386
+ 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),
6387
+ style,
6388
+ ...props
6389
+ });
6314
6390
  function DropOverlay() {
6315
6391
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6316
6392
  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 +6399,7 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6323
6399
  })
6324
6400
  });
6325
6401
  }
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 }) {
6402
+ 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
6403
  const inputContainerRef = (0, react.useRef)(null);
6328
6404
  const [inputContainerHeight, setInputContainerHeight] = (0, react.useState)(0);
6329
6405
  const [isResizing, setIsResizing] = (0, react.useState)(false);
@@ -6375,9 +6451,10 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6375
6451
  keyboardHeight: isKeyboardOpen ? keyboardHeight : 0,
6376
6452
  containerRef: inputContainerRef,
6377
6453
  showDisclaimer: true,
6454
+ bottomAnchored: true,
6378
6455
  ...disclaimer !== void 0 ? { disclaimer } : {}
6379
6456
  });
6380
- const hasSuggestions = Array.isArray(suggestions) && suggestions.length > 0;
6457
+ const hasSuggestions = !isConnecting && !isRunning && Array.isArray(suggestions) && suggestions.length > 0;
6381
6458
  const BoundSuggestionView = hasSuggestions ? renderSlot(suggestionView, CopilotChatSuggestionView, {
6382
6459
  suggestions,
6383
6460
  loadingIndexes: suggestionLoadingIndexes,
@@ -6399,7 +6476,7 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6399
6476
  })
6400
6477
  })
6401
6478
  });
6402
- if (messages.length === 0 && !(welcomeScreen === false)) {
6479
+ if (messages.length === 0 && !(welcomeScreen === false) && !isConnecting && !hasExplicitThreadId) {
6403
6480
  const BoundInputForWelcome = renderSlot(input, CopilotChatInput_default, {
6404
6481
  onSubmitMessage,
6405
6482
  onStop,
@@ -6507,9 +6584,60 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6507
6584
  ] })
6508
6585
  });
6509
6586
  };
6510
- _CopilotChatView.ScrollView = ({ children, autoScroll = true, scrollToBottomButton, feather, inputContainerHeight = 0, isResizing = false, className, ...props }) => {
6587
+ const PinToSendScrollContainer = ({ children, scrollRef, contentRef, scrollToBottom, scrollToBottomButton, feather, inputContainerHeight, isResizing, nonAutoScrollEl, nonAutoScrollRefCallback, showScrollButton, className, ...props }) => {
6588
+ const spacerRef = (0, react.useRef)(null);
6589
+ usePinToSend({
6590
+ scrollRef,
6591
+ contentRef,
6592
+ spacerRef,
6593
+ topOffset: 16
6594
+ });
6595
+ const BoundFeather = renderSlot(feather, PinToSendSoftFeather, {});
6596
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ScrollElementContext.Provider, {
6597
+ value: nonAutoScrollEl,
6598
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6599
+ className: cn("cpk:h-full cpk:max-h-full cpk:flex cpk:flex-col cpk:min-h-0 cpk:relative", className),
6600
+ children: [
6601
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6602
+ ref: nonAutoScrollRefCallback,
6603
+ className: "cpk:flex-1 cpk:min-h-0 cpk:overflow-y-auto cpk:overflow-x-hidden",
6604
+ ...props,
6605
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6606
+ ref: contentRef,
6607
+ className: "cpk:px-4 cpk:sm:px-0 cpk:[div[data-sidebar-chat]_&]:px-8 cpk:[div[data-popup-chat]_&]:px-6",
6608
+ children
6609
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6610
+ ref: spacerRef,
6611
+ "data-pin-to-send-spacer": true,
6612
+ "aria-hidden": "true",
6613
+ style: {
6614
+ height: 0,
6615
+ flex: "0 0 auto"
6616
+ }
6617
+ })]
6618
+ }),
6619
+ BoundFeather,
6620
+ showScrollButton && !isResizing && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6621
+ className: "cpk:absolute cpk:inset-x-0 cpk:flex cpk:justify-center cpk:z-30 cpk:pointer-events-none",
6622
+ style: { bottom: `${inputContainerHeight + PIN_TO_SEND_FEATHER_HEIGHT + 16}px` },
6623
+ children: renderSlot(scrollToBottomButton, CopilotChatView.ScrollToBottomButton, { onClick: () => scrollToBottom() })
6624
+ })
6625
+ ]
6626
+ })
6627
+ });
6628
+ };
6629
+ _CopilotChatView.ScrollView = ({ children, autoScroll = "pin-to-bottom", scrollToBottomButton, feather, inputContainerHeight = 0, isResizing = false, className, ...props }) => {
6630
+ const mode = normalizeAutoScroll(autoScroll);
6511
6631
  const [hasMounted, setHasMounted] = (0, react.useState)(false);
6512
- const { scrollRef, contentRef, scrollToBottom } = (0, use_stick_to_bottom.useStickToBottom)();
6632
+ const scrollRef = (0, react.useRef)(null);
6633
+ const contentRef = (0, react.useRef)(null);
6634
+ const scrollToBottom = (0, react.useCallback)(() => {
6635
+ const el = scrollRef.current;
6636
+ if (el) el.scrollTo({
6637
+ top: el.scrollHeight,
6638
+ behavior: "smooth"
6639
+ });
6640
+ }, []);
6513
6641
  const [showScrollButton, setShowScrollButton] = (0, react.useState)(false);
6514
6642
  const [nonAutoScrollEl, setNonAutoScrollEl] = (0, react.useState)(null);
6515
6643
  const nonAutoScrollRefCallback = (0, react.useCallback)((el) => {
@@ -6520,7 +6648,7 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6520
6648
  setHasMounted(true);
6521
6649
  }, []);
6522
6650
  (0, react.useEffect)(() => {
6523
- if (autoScroll) return;
6651
+ if (mode === "pin-to-bottom") return;
6524
6652
  const scrollElement = scrollRef.current;
6525
6653
  if (!scrollElement) return;
6526
6654
  const checkScroll = () => {
@@ -6534,7 +6662,7 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6534
6662
  scrollElement.removeEventListener("scroll", checkScroll);
6535
6663
  resizeObserver.disconnect();
6536
6664
  };
6537
- }, [scrollRef, autoScroll]);
6665
+ }, [scrollRef, mode]);
6538
6666
  if (!hasMounted) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6539
6667
  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
6668
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -6542,7 +6670,7 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6542
6670
  children
6543
6671
  })
6544
6672
  });
6545
- if (!autoScroll) {
6673
+ if (mode === "none") {
6546
6674
  const BoundFeather = renderSlot(feather, CopilotChatView.Feather, {});
6547
6675
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ScrollElementContext.Provider, {
6548
6676
  value: nonAutoScrollEl,
@@ -6566,6 +6694,21 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6566
6694
  })
6567
6695
  });
6568
6696
  }
6697
+ if (mode === "pin-to-send") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PinToSendScrollContainer, {
6698
+ scrollRef,
6699
+ contentRef,
6700
+ scrollToBottom,
6701
+ scrollToBottomButton,
6702
+ feather,
6703
+ inputContainerHeight,
6704
+ isResizing,
6705
+ nonAutoScrollEl,
6706
+ nonAutoScrollRefCallback,
6707
+ showScrollButton,
6708
+ className,
6709
+ ...props,
6710
+ children
6711
+ });
6569
6712
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(use_stick_to_bottom.StickToBottom, {
6570
6713
  className: cn("cpk:flex-1 cpk:max-h-full cpk:flex cpk:flex-col cpk:min-h-0", className),
6571
6714
  resize: "smooth",
@@ -6762,10 +6905,8 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6762
6905
  var _ref, _attachmentsConfig$ac;
6763
6906
  const existingConfig = useCopilotChatConfiguration();
6764
6907
  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]);
6908
+ const providedThreadId = threadId !== null && threadId !== void 0 ? threadId : existingConfig === null || existingConfig === void 0 ? void 0 : existingConfig.threadId;
6909
+ const resolvedThreadId = (0, react.useMemo)(() => providedThreadId !== null && providedThreadId !== void 0 ? providedThreadId : (0, _copilotkit_shared.randomUUID)(), [providedThreadId]);
6769
6910
  const { agent } = useAgent({
6770
6911
  agentId: resolvedAgentId,
6771
6912
  threadId: resolvedThreadId,
@@ -6807,7 +6948,10 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6807
6948
  const isTranscriptionEnabled = copilotkit.audioFileTranscriptionEnabled;
6808
6949
  const isMediaRecorderSupported = typeof window !== "undefined" && typeof MediaRecorder !== "undefined";
6809
6950
  const { messageView: providedMessageView, suggestionView: providedSuggestionView, onStop: providedStopHandler, ...restProps } = props;
6951
+ const [lastConnectedThreadId, setLastConnectedThreadId] = (0, react.useState)(null);
6952
+ const isConnecting = !!providedThreadId && lastConnectedThreadId !== resolvedThreadId;
6810
6953
  (0, react.useEffect)(() => {
6954
+ if (!providedThreadId) return;
6811
6955
  let detached = false;
6812
6956
  const connectAbortController = new AbortController();
6813
6957
  if (agent instanceof _ag_ui_client.HttpAgent) agent.abortController = connectAbortController;
@@ -6817,6 +6961,10 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6817
6961
  } catch (error) {
6818
6962
  if (detached) return;
6819
6963
  console.error("CopilotChat: connectAgent failed", error);
6964
+ } finally {
6965
+ if (!detached) (typeof requestAnimationFrame === "function" ? requestAnimationFrame : (cb) => setTimeout(cb, 16))(() => {
6966
+ if (!detached) setLastConnectedThreadId(resolvedThreadId);
6967
+ });
6820
6968
  }
6821
6969
  };
6822
6970
  connect(agent);
@@ -6828,7 +6976,8 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6828
6976
  }, [
6829
6977
  resolvedThreadId,
6830
6978
  agent,
6831
- resolvedAgentId
6979
+ resolvedAgentId,
6980
+ providedThreadId
6832
6981
  ]);
6833
6982
  const onSubmitInput = (0, react.useCallback)(async (value) => {
6834
6983
  if (selectedAttachments.some((a) => a.status === "uploading")) {
@@ -6985,6 +7134,22 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
6985
7134
  }).join(";") : "";
6986
7135
  return `${m.id}:${m.role}:${contentKey}:${toolCallsKey}`;
6987
7136
  }).join(",")]);
7137
+ const lastUserMessageId = (0, react.useMemo)(() => {
7138
+ for (let i = messages.length - 1; i >= 0; i--) if (messages[i].role === "user") return messages[i].id;
7139
+ return null;
7140
+ }, [messages]);
7141
+ const [sendNonce, setSendNonce] = (0, react.useState)(0);
7142
+ const prevLastUserMessageIdRef = (0, react.useRef)(lastUserMessageId);
7143
+ (0, react.useEffect)(() => {
7144
+ if (lastUserMessageId && lastUserMessageId !== prevLastUserMessageIdRef.current) {
7145
+ setSendNonce((n) => n + 1);
7146
+ prevLastUserMessageIdRef.current = lastUserMessageId;
7147
+ }
7148
+ }, [lastUserMessageId]);
7149
+ const lastUserMessageState = (0, react.useMemo)(() => ({
7150
+ id: lastUserMessageId,
7151
+ sendNonce
7152
+ }), [lastUserMessageId, sendNonce]);
6988
7153
  const RenderedChatView = renderSlot(chatView, CopilotChatView, {
6989
7154
  ...mergedProps,
6990
7155
  messages,
@@ -7003,7 +7168,9 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
7003
7168
  dragOver,
7004
7169
  onDragOver: handleDragOver,
7005
7170
  onDragLeave: handleDragLeave,
7006
- onDrop: handleDrop
7171
+ onDrop: handleDrop,
7172
+ isConnecting,
7173
+ hasExplicitThreadId: !!providedThreadId
7007
7174
  });
7008
7175
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopilotChatConfigurationProvider, {
7009
7176
  agentId: resolvedAgentId,
@@ -7038,7 +7205,10 @@ window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-
7038
7205
  },
7039
7206
  children: transcriptionError
7040
7207
  }),
7041
- RenderedChatView
7208
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LastUserMessageContext.Provider, {
7209
+ value: lastUserMessageState,
7210
+ children: RenderedChatView
7211
+ })
7042
7212
  ]
7043
7213
  })
7044
7214
  });