@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
@@ -610,7 +610,7 @@ CopilotChatAudioRecorder.displayName = "CopilotChatAudioRecorder";
610
610
  //#region src/v2/components/chat/CopilotChatInput.tsx
611
611
  const SLASH_MENU_MAX_VISIBLE_ITEMS = 5;
612
612
  const SLASH_MENU_ITEM_HEIGHT_PX = 40;
613
- 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 }) {
613
+ 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 }) {
614
614
  const isControlled = value !== void 0;
615
615
  const [internalValue, setInternalValue] = (0, react.useState)(() => value ?? "");
616
616
  (0, react.useEffect)(() => {
@@ -1134,7 +1134,8 @@ function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning =
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", {
@@ -1349,6 +1350,8 @@ CopilotKitInspector.displayName = "CopilotKitInspector";
1349
1350
 
1350
1351
  //#endregion
1351
1352
  //#region src/v2/components/license-warning-banner.tsx
1353
+ const LICENSE_BANNER_OFFSET_PX = 52;
1354
+ const LICENSE_BANNER_OFFSET_VAR = "--copilotkit-license-banner-offset";
1352
1355
  const BANNER_STYLES = {
1353
1356
  base: {
1354
1357
  position: "fixed",
@@ -1390,6 +1393,14 @@ function getSeverityStyle(severity) {
1390
1393
  }
1391
1394
  }
1392
1395
  function BannerShell({ severity, message, actionLabel, actionUrl, onDismiss }) {
1396
+ (0, react.useEffect)(() => {
1397
+ if (typeof document === "undefined") return;
1398
+ const root = document.documentElement;
1399
+ root.style.setProperty(LICENSE_BANNER_OFFSET_VAR, `${LICENSE_BANNER_OFFSET_PX}px`);
1400
+ return () => {
1401
+ root.style.removeProperty(LICENSE_BANNER_OFFSET_VAR);
1402
+ };
1403
+ }, []);
1393
1404
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1394
1405
  style: {
1395
1406
  ...BANNER_STYLES.base,
@@ -3342,7 +3353,6 @@ const CopilotKitProvider = ({ children, runtimeUrl, headers: headersProp = {}, c
3342
3353
  didMountRef.current = true;
3343
3354
  }, []);
3344
3355
  (0, react.useEffect)(() => {
3345
- 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.`);
3346
3356
  copilotkit.setDefaultThrottleMs(defaultThrottleMs);
3347
3357
  }, [copilotkit, defaultThrottleMs]);
3348
3358
  const designSkill = openGenerativeUI?.designSkill ?? DEFAULT_DESIGN_SKILL;
@@ -3559,15 +3569,6 @@ function useAgent({ agentId, threadId, updates, throttleMs } = {}) {
3559
3569
  const providerThrottleMs = copilotkit.defaultThrottleMs;
3560
3570
  const chatConfig = useCopilotChatConfiguration();
3561
3571
  threadId ??= chatConfig?.threadId;
3562
- const effectiveThrottleMs = (0, react.useMemo)(() => {
3563
- const resolved = throttleMs ?? providerThrottleMs ?? 0;
3564
- if (!Number.isFinite(resolved) || resolved < 0) {
3565
- const source = throttleMs !== void 0 ? "hook-level throttleMs" : "provider-level defaultThrottleMs";
3566
- console.error(`useAgent: ${source} must be a non-negative finite number, got ${resolved}. Falling back to unthrottled.`);
3567
- return 0;
3568
- }
3569
- return resolved;
3570
- }, [throttleMs, providerThrottleMs]);
3571
3572
  const [, forceUpdate] = (0, react.useReducer)((x) => x + 1, 0);
3572
3573
  const updateFlags = (0, react.useMemo)(() => updates ?? ALL_UPDATES, [JSON.stringify(updates)]);
3573
3574
  const provisionalAgentCache = (0, react.useRef)(/* @__PURE__ */ new Map());
@@ -3630,9 +3631,8 @@ function useAgent({ agentId, threadId, updates, throttleMs } = {}) {
3630
3631
  ]);
3631
3632
  (0, react.useEffect)(() => {
3632
3633
  if (updateFlags.length === 0) return;
3633
- const handlers = {};
3634
- let timerId = null;
3635
3634
  let active = true;
3635
+ const handlers = {};
3636
3636
  let batchScheduled = false;
3637
3637
  const batchedForceUpdate = () => {
3638
3638
  if (!active) return;
@@ -3644,46 +3644,24 @@ function useAgent({ agentId, threadId, updates, throttleMs } = {}) {
3644
3644
  });
3645
3645
  }
3646
3646
  };
3647
- if (updateFlags.includes(UseAgentUpdate.OnMessagesChanged)) {
3648
- const ms = effectiveThrottleMs;
3649
- if (ms > 0) {
3650
- let throttleActive = false;
3651
- let pending = false;
3652
- const throttledNotify = () => {
3653
- if (!active) return;
3654
- if (!throttleActive) {
3655
- throttleActive = true;
3656
- pending = false;
3657
- forceUpdate();
3658
- timerId = setTimeout(function trailingEdge() {
3659
- timerId = null;
3660
- if (active && pending) {
3661
- pending = false;
3662
- forceUpdate();
3663
- timerId = setTimeout(trailingEdge, ms);
3664
- } else throttleActive = false;
3665
- }, ms);
3666
- } else pending = true;
3667
- };
3668
- handlers.onMessagesChanged = throttledNotify;
3669
- } else handlers.onMessagesChanged = forceUpdate;
3670
- }
3647
+ if (updateFlags.includes(UseAgentUpdate.OnMessagesChanged)) handlers.onMessagesChanged = forceUpdate;
3671
3648
  if (updateFlags.includes(UseAgentUpdate.OnStateChanged)) handlers.onStateChanged = batchedForceUpdate;
3672
3649
  if (updateFlags.includes(UseAgentUpdate.OnRunStatusChanged)) {
3673
3650
  handlers.onRunInitialized = batchedForceUpdate;
3674
3651
  handlers.onRunFinalized = batchedForceUpdate;
3675
3652
  handlers.onRunFailed = batchedForceUpdate;
3653
+ handlers.onRunErrorEvent = batchedForceUpdate;
3676
3654
  }
3677
- const subscription = agent.subscribe(handlers);
3655
+ const subscription = copilotkit.subscribeToAgentWithOptions(agent, handlers, { throttleMs });
3678
3656
  return () => {
3679
3657
  active = false;
3680
- if (timerId !== null) clearTimeout(timerId);
3681
3658
  subscription.unsubscribe();
3682
3659
  };
3683
3660
  }, [
3684
3661
  agent,
3685
3662
  forceUpdate,
3686
- effectiveThrottleMs,
3663
+ throttleMs,
3664
+ providerThrottleMs,
3687
3665
  updateFlags
3688
3666
  ]);
3689
3667
  (0, react.useEffect)(() => {
@@ -4637,13 +4615,14 @@ function useThreads$1({ agentId, includeArchived, limit }) {
4637
4615
  const { copilotkit } = useCopilotKit();
4638
4616
  const [store] = (0, react.useState)(() => (0, _copilotkit_core.ɵcreateThreadStore)({ fetch: globalThis.fetch }));
4639
4617
  const coreThreads = useThreadStoreSelector(store, _copilotkit_core.ɵselectThreads);
4640
- const threads = (0, react.useMemo)(() => coreThreads.map(({ id, agentId, name, archived, createdAt, updatedAt }) => ({
4618
+ const threads = (0, react.useMemo)(() => coreThreads.map(({ id, agentId, name, archived, createdAt, updatedAt, lastRunAt }) => ({
4641
4619
  id,
4642
4620
  agentId,
4643
4621
  name,
4644
4622
  archived,
4645
4623
  createdAt,
4646
- updatedAt
4624
+ updatedAt,
4625
+ ...lastRunAt !== void 0 ? { lastRunAt } : {}
4647
4626
  })), [coreThreads]);
4648
4627
  const storeIsLoading = useThreadStoreSelector(store, _copilotkit_core.ɵselectThreadsIsLoading);
4649
4628
  const storeError = useThreadStoreSelector(store, _copilotkit_core.ɵselectThreadsError);
@@ -4656,7 +4635,9 @@ function useThreads$1({ agentId, includeArchived, limit }) {
4656
4635
  if (copilotkit.runtimeUrl) return null;
4657
4636
  return /* @__PURE__ */ new Error("Runtime URL is not configured");
4658
4637
  }, [copilotkit.runtimeUrl]);
4659
- const isLoading = runtimeError ? false : storeIsLoading;
4638
+ const [hasDispatchedContext, setHasDispatchedContext] = (0, react.useState)(false);
4639
+ const preConnectLoading = !!copilotkit.runtimeUrl && !hasDispatchedContext;
4640
+ const isLoading = runtimeError ? false : preConnectLoading || storeIsLoading;
4660
4641
  const error = runtimeError ?? storeError;
4661
4642
  (0, react.useEffect)(() => {
4662
4643
  store.start();
@@ -4664,19 +4645,27 @@ function useThreads$1({ agentId, includeArchived, limit }) {
4664
4645
  store.stop();
4665
4646
  };
4666
4647
  }, [store]);
4648
+ const runtimeStatus = copilotkit.runtimeConnectionStatus;
4667
4649
  (0, react.useEffect)(() => {
4668
- const context = copilotkit.runtimeUrl ? {
4650
+ if (!copilotkit.runtimeUrl) {
4651
+ store.setContext(null);
4652
+ return;
4653
+ }
4654
+ if (runtimeStatus !== _copilotkit_core.CopilotKitCoreRuntimeConnectionStatus.Connected) return;
4655
+ const context = {
4669
4656
  runtimeUrl: copilotkit.runtimeUrl,
4670
4657
  headers: { ...copilotkit.headers },
4671
4658
  wsUrl: copilotkit.intelligence?.wsUrl,
4672
4659
  agentId,
4673
4660
  includeArchived,
4674
4661
  limit
4675
- } : null;
4662
+ };
4676
4663
  store.setContext(context);
4664
+ setHasDispatchedContext(true);
4677
4665
  }, [
4678
4666
  store,
4679
4667
  copilotkit.runtimeUrl,
4668
+ runtimeStatus,
4680
4669
  headersKey,
4681
4670
  copilotkit.intelligence?.wsUrl,
4682
4671
  agentId,
@@ -6201,9 +6190,97 @@ function useKeyboardHeight() {
6201
6190
  return keyboardState;
6202
6191
  }
6203
6192
 
6193
+ //#endregion
6194
+ //#region src/v2/components/chat/normalize-auto-scroll.ts
6195
+ const VALID = [
6196
+ "pin-to-bottom",
6197
+ "pin-to-send",
6198
+ "none"
6199
+ ];
6200
+ function normalizeAutoScroll(value) {
6201
+ if (value === void 0) return "pin-to-bottom";
6202
+ if (value === true) return "pin-to-bottom";
6203
+ if (value === false) return "none";
6204
+ if (VALID.includes(value)) return value;
6205
+ return "pin-to-bottom";
6206
+ }
6207
+
6208
+ //#endregion
6209
+ //#region src/v2/components/chat/last-user-message-context.ts
6210
+ const LastUserMessageContext = react.default.createContext({
6211
+ id: null,
6212
+ sendNonce: 0
6213
+ });
6214
+
6215
+ //#endregion
6216
+ //#region src/v2/hooks/use-pin-to-send.ts
6217
+ function usePinToSend({ scrollRef, contentRef, spacerRef, topOffset = 16 }) {
6218
+ const { id, sendNonce } = (0, react.useContext)(LastUserMessageContext);
6219
+ const lastNonceRef = (0, react.useRef)(-1);
6220
+ const currentSpacerHeightRef = (0, react.useRef)(0);
6221
+ (0, react.useEffect)(() => {
6222
+ if (sendNonce === lastNonceRef.current) return;
6223
+ lastNonceRef.current = sendNonce;
6224
+ if (!id) return;
6225
+ const scrollEl = scrollRef.current;
6226
+ const contentEl = contentRef.current;
6227
+ const spacerEl = spacerRef.current;
6228
+ if (!scrollEl || !contentEl || !spacerEl) return;
6229
+ const escaped = typeof CSS !== "undefined" && CSS.escape ? CSS.escape(id) : id.replace(/[!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g, "\\$&");
6230
+ const targetEl = contentEl.querySelector(`[data-message-id="${escaped}"]`);
6231
+ if (!targetEl) return;
6232
+ const viewportHeight = scrollEl.clientHeight;
6233
+ const userMessageHeight = targetEl.getBoundingClientRect().height;
6234
+ const paddingTop = parseFloat(getComputedStyle(targetEl).paddingTop) || 0;
6235
+ const bubbleHeight = Math.max(0, userMessageHeight - paddingTop);
6236
+ const spacerHeight = Math.max(0, viewportHeight - bubbleHeight - topOffset);
6237
+ spacerEl.style.height = `${spacerHeight}px`;
6238
+ currentSpacerHeightRef.current = spacerHeight;
6239
+ const raf = requestAnimationFrame(() => {
6240
+ const targetTop = computeOffsetTop(targetEl, scrollEl) + paddingTop - topOffset;
6241
+ scrollEl.scrollTo({
6242
+ top: Math.max(0, targetTop),
6243
+ behavior: "smooth"
6244
+ });
6245
+ });
6246
+ const ro = new ResizeObserver(() => {
6247
+ if (!contentEl || !spacerEl || !scrollEl) return;
6248
+ const consumedBelow = contentEl.getBoundingClientRect().height - computeOffsetTop(targetEl, contentEl) - userMessageHeight;
6249
+ const remaining = Math.max(0, spacerHeight - consumedBelow);
6250
+ if (remaining < currentSpacerHeightRef.current) {
6251
+ spacerEl.style.height = `${remaining}px`;
6252
+ currentSpacerHeightRef.current = remaining;
6253
+ }
6254
+ });
6255
+ ro.observe(contentEl);
6256
+ return () => {
6257
+ cancelAnimationFrame(raf);
6258
+ ro.disconnect();
6259
+ };
6260
+ }, [
6261
+ id,
6262
+ sendNonce,
6263
+ scrollRef,
6264
+ contentRef,
6265
+ spacerRef,
6266
+ topOffset
6267
+ ]);
6268
+ }
6269
+ function computeOffsetTop(el, stopAt) {
6270
+ const elRect = el.getBoundingClientRect();
6271
+ const stopRect = stopAt.getBoundingClientRect();
6272
+ return elRect.top - stopRect.top + stopAt.scrollTop;
6273
+ }
6274
+
6204
6275
  //#endregion
6205
6276
  //#region src/v2/components/chat/CopilotChatView.tsx
6206
6277
  const FEATHER_HEIGHT = 96;
6278
+ const PIN_TO_SEND_FEATHER_HEIGHT = 48;
6279
+ const PinToSendSoftFeather = ({ className, style, ...props }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6280
+ 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),
6281
+ style,
6282
+ ...props
6283
+ });
6207
6284
  function DropOverlay() {
6208
6285
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6209
6286
  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"),
@@ -6216,7 +6293,7 @@ function DropOverlay() {
6216
6293
  })
6217
6294
  });
6218
6295
  }
6219
- 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 }) {
6296
+ 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 }) {
6220
6297
  const inputContainerRef = (0, react.useRef)(null);
6221
6298
  const [inputContainerHeight, setInputContainerHeight] = (0, react.useState)(0);
6222
6299
  const [isResizing, setIsResizing] = (0, react.useState)(false);
@@ -6268,9 +6345,10 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
6268
6345
  keyboardHeight: isKeyboardOpen ? keyboardHeight : 0,
6269
6346
  containerRef: inputContainerRef,
6270
6347
  showDisclaimer: true,
6348
+ bottomAnchored: true,
6271
6349
  ...disclaimer !== void 0 ? { disclaimer } : {}
6272
6350
  });
6273
- const hasSuggestions = Array.isArray(suggestions) && suggestions.length > 0;
6351
+ const hasSuggestions = !isConnecting && !isRunning && Array.isArray(suggestions) && suggestions.length > 0;
6274
6352
  const BoundSuggestionView = hasSuggestions ? renderSlot(suggestionView, CopilotChatSuggestionView, {
6275
6353
  suggestions,
6276
6354
  loadingIndexes: suggestionLoadingIndexes,
@@ -6292,7 +6370,7 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
6292
6370
  })
6293
6371
  })
6294
6372
  });
6295
- if (messages.length === 0 && !(welcomeScreen === false)) {
6373
+ if (messages.length === 0 && !(welcomeScreen === false) && !isConnecting && !hasExplicitThreadId) {
6296
6374
  const BoundInputForWelcome = renderSlot(input, CopilotChatInput_default, {
6297
6375
  onSubmitMessage,
6298
6376
  onStop,
@@ -6399,9 +6477,60 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
6399
6477
  ] })
6400
6478
  });
6401
6479
  };
6402
- _CopilotChatView.ScrollView = ({ children, autoScroll = true, scrollToBottomButton, feather, inputContainerHeight = 0, isResizing = false, className, ...props }) => {
6480
+ const PinToSendScrollContainer = ({ children, scrollRef, contentRef, scrollToBottom, scrollToBottomButton, feather, inputContainerHeight, isResizing, nonAutoScrollEl, nonAutoScrollRefCallback, showScrollButton, className, ...props }) => {
6481
+ const spacerRef = (0, react.useRef)(null);
6482
+ usePinToSend({
6483
+ scrollRef,
6484
+ contentRef,
6485
+ spacerRef,
6486
+ topOffset: 16
6487
+ });
6488
+ const BoundFeather = renderSlot(feather, PinToSendSoftFeather, {});
6489
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ScrollElementContext.Provider, {
6490
+ value: nonAutoScrollEl,
6491
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6492
+ className: cn("cpk:h-full cpk:max-h-full cpk:flex cpk:flex-col cpk:min-h-0 cpk:relative", className),
6493
+ children: [
6494
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6495
+ ref: nonAutoScrollRefCallback,
6496
+ className: "cpk:flex-1 cpk:min-h-0 cpk:overflow-y-auto cpk:overflow-x-hidden",
6497
+ ...props,
6498
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6499
+ ref: contentRef,
6500
+ className: "cpk:px-4 cpk:sm:px-0 cpk:[div[data-sidebar-chat]_&]:px-8 cpk:[div[data-popup-chat]_&]:px-6",
6501
+ children
6502
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6503
+ ref: spacerRef,
6504
+ "data-pin-to-send-spacer": true,
6505
+ "aria-hidden": "true",
6506
+ style: {
6507
+ height: 0,
6508
+ flex: "0 0 auto"
6509
+ }
6510
+ })]
6511
+ }),
6512
+ BoundFeather,
6513
+ showScrollButton && !isResizing && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6514
+ className: "cpk:absolute cpk:inset-x-0 cpk:flex cpk:justify-center cpk:z-30 cpk:pointer-events-none",
6515
+ style: { bottom: `${inputContainerHeight + PIN_TO_SEND_FEATHER_HEIGHT + 16}px` },
6516
+ children: renderSlot(scrollToBottomButton, CopilotChatView.ScrollToBottomButton, { onClick: () => scrollToBottom() })
6517
+ })
6518
+ ]
6519
+ })
6520
+ });
6521
+ };
6522
+ _CopilotChatView.ScrollView = ({ children, autoScroll = "pin-to-bottom", scrollToBottomButton, feather, inputContainerHeight = 0, isResizing = false, className, ...props }) => {
6523
+ const mode = normalizeAutoScroll(autoScroll);
6403
6524
  const [hasMounted, setHasMounted] = (0, react.useState)(false);
6404
- const { scrollRef, contentRef, scrollToBottom } = (0, use_stick_to_bottom.useStickToBottom)();
6525
+ const scrollRef = (0, react.useRef)(null);
6526
+ const contentRef = (0, react.useRef)(null);
6527
+ const scrollToBottom = (0, react.useCallback)(() => {
6528
+ const el = scrollRef.current;
6529
+ if (el) el.scrollTo({
6530
+ top: el.scrollHeight,
6531
+ behavior: "smooth"
6532
+ });
6533
+ }, []);
6405
6534
  const [showScrollButton, setShowScrollButton] = (0, react.useState)(false);
6406
6535
  const [nonAutoScrollEl, setNonAutoScrollEl] = (0, react.useState)(null);
6407
6536
  const nonAutoScrollRefCallback = (0, react.useCallback)((el) => {
@@ -6412,7 +6541,7 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
6412
6541
  setHasMounted(true);
6413
6542
  }, []);
6414
6543
  (0, react.useEffect)(() => {
6415
- if (autoScroll) return;
6544
+ if (mode === "pin-to-bottom") return;
6416
6545
  const scrollElement = scrollRef.current;
6417
6546
  if (!scrollElement) return;
6418
6547
  const checkScroll = () => {
@@ -6426,7 +6555,7 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
6426
6555
  scrollElement.removeEventListener("scroll", checkScroll);
6427
6556
  resizeObserver.disconnect();
6428
6557
  };
6429
- }, [scrollRef, autoScroll]);
6558
+ }, [scrollRef, mode]);
6430
6559
  if (!hasMounted) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6431
6560
  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",
6432
6561
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -6434,7 +6563,7 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
6434
6563
  children
6435
6564
  })
6436
6565
  });
6437
- if (!autoScroll) {
6566
+ if (mode === "none") {
6438
6567
  const BoundFeather = renderSlot(feather, CopilotChatView.Feather, {});
6439
6568
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ScrollElementContext.Provider, {
6440
6569
  value: nonAutoScrollEl,
@@ -6458,6 +6587,21 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
6458
6587
  })
6459
6588
  });
6460
6589
  }
6590
+ if (mode === "pin-to-send") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PinToSendScrollContainer, {
6591
+ scrollRef,
6592
+ contentRef,
6593
+ scrollToBottom,
6594
+ scrollToBottomButton,
6595
+ feather,
6596
+ inputContainerHeight,
6597
+ isResizing,
6598
+ nonAutoScrollEl,
6599
+ nonAutoScrollRefCallback,
6600
+ showScrollButton,
6601
+ className,
6602
+ ...props,
6603
+ children
6604
+ });
6461
6605
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(use_stick_to_bottom.StickToBottom, {
6462
6606
  className: cn("cpk:flex-1 cpk:max-h-full cpk:flex cpk:flex-col cpk:min-h-0", className),
6463
6607
  resize: "smooth",
@@ -6650,7 +6794,8 @@ async function transcribeAudio(core, audioBlob, filename = "recording.webm") {
6650
6794
  function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen, attachments: attachmentsConfig, onError, throttleMs, ...props }) {
6651
6795
  const existingConfig = useCopilotChatConfiguration();
6652
6796
  const resolvedAgentId = agentId ?? existingConfig?.agentId ?? _copilotkit_shared.DEFAULT_AGENT_ID;
6653
- const resolvedThreadId = (0, react.useMemo)(() => threadId ?? existingConfig?.threadId ?? (0, _copilotkit_shared.randomUUID)(), [threadId, existingConfig?.threadId]);
6797
+ const providedThreadId = threadId ?? existingConfig?.threadId;
6798
+ const resolvedThreadId = (0, react.useMemo)(() => providedThreadId ?? (0, _copilotkit_shared.randomUUID)(), [providedThreadId]);
6654
6799
  const { agent } = useAgent({
6655
6800
  agentId: resolvedAgentId,
6656
6801
  threadId: resolvedThreadId,
@@ -6688,7 +6833,10 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
6688
6833
  const isTranscriptionEnabled = copilotkit.audioFileTranscriptionEnabled;
6689
6834
  const isMediaRecorderSupported = typeof window !== "undefined" && typeof MediaRecorder !== "undefined";
6690
6835
  const { messageView: providedMessageView, suggestionView: providedSuggestionView, onStop: providedStopHandler, ...restProps } = props;
6836
+ const [lastConnectedThreadId, setLastConnectedThreadId] = (0, react.useState)(null);
6837
+ const isConnecting = !!providedThreadId && lastConnectedThreadId !== resolvedThreadId;
6691
6838
  (0, react.useEffect)(() => {
6839
+ if (!providedThreadId) return;
6692
6840
  let detached = false;
6693
6841
  const connectAbortController = new AbortController();
6694
6842
  if (agent instanceof _ag_ui_client.HttpAgent) agent.abortController = connectAbortController;
@@ -6698,6 +6846,10 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
6698
6846
  } catch (error) {
6699
6847
  if (detached) return;
6700
6848
  console.error("CopilotChat: connectAgent failed", error);
6849
+ } finally {
6850
+ if (!detached) (typeof requestAnimationFrame === "function" ? requestAnimationFrame : (cb) => setTimeout(cb, 16))(() => {
6851
+ if (!detached) setLastConnectedThreadId(resolvedThreadId);
6852
+ });
6701
6853
  }
6702
6854
  };
6703
6855
  connect(agent);
@@ -6709,7 +6861,8 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
6709
6861
  }, [
6710
6862
  resolvedThreadId,
6711
6863
  agent,
6712
- resolvedAgentId
6864
+ resolvedAgentId,
6865
+ providedThreadId
6713
6866
  ]);
6714
6867
  const onSubmitInput = (0, react.useCallback)(async (value) => {
6715
6868
  if (selectedAttachments.some((a) => a.status === "uploading")) {
@@ -6862,6 +7015,22 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
6862
7015
  const toolCallsKey = "toolCalls" in m && Array.isArray(m.toolCalls) ? m.toolCalls.map((tc) => `${tc.id}:${tc.function?.arguments?.length ?? 0}`).join(";") : "";
6863
7016
  return `${m.id}:${m.role}:${contentKey}:${toolCallsKey}`;
6864
7017
  }).join(",")]);
7018
+ const lastUserMessageId = (0, react.useMemo)(() => {
7019
+ for (let i = messages.length - 1; i >= 0; i--) if (messages[i].role === "user") return messages[i].id;
7020
+ return null;
7021
+ }, [messages]);
7022
+ const [sendNonce, setSendNonce] = (0, react.useState)(0);
7023
+ const prevLastUserMessageIdRef = (0, react.useRef)(lastUserMessageId);
7024
+ (0, react.useEffect)(() => {
7025
+ if (lastUserMessageId && lastUserMessageId !== prevLastUserMessageIdRef.current) {
7026
+ setSendNonce((n) => n + 1);
7027
+ prevLastUserMessageIdRef.current = lastUserMessageId;
7028
+ }
7029
+ }, [lastUserMessageId]);
7030
+ const lastUserMessageState = (0, react.useMemo)(() => ({
7031
+ id: lastUserMessageId,
7032
+ sendNonce
7033
+ }), [lastUserMessageId, sendNonce]);
6865
7034
  const RenderedChatView = renderSlot(chatView, CopilotChatView, {
6866
7035
  ...mergedProps,
6867
7036
  messages,
@@ -6880,7 +7049,9 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
6880
7049
  dragOver,
6881
7050
  onDragOver: handleDragOver,
6882
7051
  onDragLeave: handleDragLeave,
6883
- onDrop: handleDrop
7052
+ onDrop: handleDrop,
7053
+ isConnecting,
7054
+ hasExplicitThreadId: !!providedThreadId
6884
7055
  });
6885
7056
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopilotChatConfigurationProvider, {
6886
7057
  agentId: resolvedAgentId,
@@ -6915,7 +7086,10 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
6915
7086
  },
6916
7087
  children: transcriptionError
6917
7088
  }),
6918
- RenderedChatView
7089
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LastUserMessageContext.Provider, {
7090
+ value: lastUserMessageState,
7091
+ children: RenderedChatView
7092
+ })
6919
7093
  ]
6920
7094
  })
6921
7095
  });
@@ -10017,4 +10191,4 @@ Object.defineProperty(exports, 'useToast', {
10017
10191
  return useToast;
10018
10192
  }
10019
10193
  });
10020
- //# sourceMappingURL=copilotkit-CSJw5BG8.cjs.map
10194
+ //# sourceMappingURL=copilotkit-D5JT2Pu3.cjs.map