@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
@@ -18,7 +18,7 @@ import { A2UIProvider, A2UIRenderer, A2UI_SCHEMA_CONTEXT_DESCRIPTION, DEFAULT_SU
18
18
  import { zodToJsonSchema } from "zod-to-json-schema";
19
19
  import { useVirtualizer } from "@tanstack/react-virtual";
20
20
  import { createPortal, flushSync } from "react-dom";
21
- import { StickToBottom, useStickToBottom, useStickToBottomContext } from "use-stick-to-bottom";
21
+ import { StickToBottom, useStickToBottomContext } from "use-stick-to-bottom";
22
22
  import ReactMarkdown from "react-markdown";
23
23
 
24
24
  //#region src/v2/lib/slots.tsx
@@ -580,7 +580,7 @@ CopilotChatAudioRecorder.displayName = "CopilotChatAudioRecorder";
580
580
  //#region src/v2/components/chat/CopilotChatInput.tsx
581
581
  const SLASH_MENU_MAX_VISIBLE_ITEMS = 5;
582
582
  const SLASH_MENU_ITEM_HEIGHT_PX = 40;
583
- 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 }) {
583
+ 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 }) {
584
584
  const isControlled = value !== void 0;
585
585
  const [internalValue, setInternalValue] = useState(() => value ?? "");
586
586
  useEffect(() => {
@@ -1104,7 +1104,8 @@ function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning =
1104
1104
  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),
1105
1105
  style: {
1106
1106
  transform: keyboardHeight > 0 ? `translateY(-${keyboardHeight}px)` : void 0,
1107
- transition: "transform 0.2s ease-out"
1107
+ transition: "transform 0.2s ease-out",
1108
+ ...positioning === "absolute" || bottomAnchored ? { paddingBottom: "var(--copilotkit-license-banner-offset, 0px)" } : {}
1108
1109
  },
1109
1110
  ...props,
1110
1111
  children: [/* @__PURE__ */ jsx("div", {
@@ -1319,6 +1320,8 @@ CopilotKitInspector.displayName = "CopilotKitInspector";
1319
1320
 
1320
1321
  //#endregion
1321
1322
  //#region src/v2/components/license-warning-banner.tsx
1323
+ const LICENSE_BANNER_OFFSET_PX = 52;
1324
+ const LICENSE_BANNER_OFFSET_VAR = "--copilotkit-license-banner-offset";
1322
1325
  const BANNER_STYLES = {
1323
1326
  base: {
1324
1327
  position: "fixed",
@@ -1360,6 +1363,14 @@ function getSeverityStyle(severity) {
1360
1363
  }
1361
1364
  }
1362
1365
  function BannerShell({ severity, message, actionLabel, actionUrl, onDismiss }) {
1366
+ useEffect(() => {
1367
+ if (typeof document === "undefined") return;
1368
+ const root = document.documentElement;
1369
+ root.style.setProperty(LICENSE_BANNER_OFFSET_VAR, `${LICENSE_BANNER_OFFSET_PX}px`);
1370
+ return () => {
1371
+ root.style.removeProperty(LICENSE_BANNER_OFFSET_VAR);
1372
+ };
1373
+ }, []);
1363
1374
  return /* @__PURE__ */ jsxs("div", {
1364
1375
  style: {
1365
1376
  ...BANNER_STYLES.base,
@@ -3312,7 +3323,6 @@ const CopilotKitProvider = ({ children, runtimeUrl, headers: headersProp = {}, c
3312
3323
  didMountRef.current = true;
3313
3324
  }, []);
3314
3325
  useEffect(() => {
3315
- 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.`);
3316
3326
  copilotkit.setDefaultThrottleMs(defaultThrottleMs);
3317
3327
  }, [copilotkit, defaultThrottleMs]);
3318
3328
  const designSkill = openGenerativeUI?.designSkill ?? DEFAULT_DESIGN_SKILL;
@@ -3529,15 +3539,6 @@ function useAgent({ agentId, threadId, updates, throttleMs } = {}) {
3529
3539
  const providerThrottleMs = copilotkit.defaultThrottleMs;
3530
3540
  const chatConfig = useCopilotChatConfiguration();
3531
3541
  threadId ??= chatConfig?.threadId;
3532
- const effectiveThrottleMs = useMemo(() => {
3533
- const resolved = throttleMs ?? providerThrottleMs ?? 0;
3534
- if (!Number.isFinite(resolved) || resolved < 0) {
3535
- const source = throttleMs !== void 0 ? "hook-level throttleMs" : "provider-level defaultThrottleMs";
3536
- console.error(`useAgent: ${source} must be a non-negative finite number, got ${resolved}. Falling back to unthrottled.`);
3537
- return 0;
3538
- }
3539
- return resolved;
3540
- }, [throttleMs, providerThrottleMs]);
3541
3542
  const [, forceUpdate] = useReducer((x) => x + 1, 0);
3542
3543
  const updateFlags = useMemo(() => updates ?? ALL_UPDATES, [JSON.stringify(updates)]);
3543
3544
  const provisionalAgentCache = useRef(/* @__PURE__ */ new Map());
@@ -3600,9 +3601,8 @@ function useAgent({ agentId, threadId, updates, throttleMs } = {}) {
3600
3601
  ]);
3601
3602
  useEffect(() => {
3602
3603
  if (updateFlags.length === 0) return;
3603
- const handlers = {};
3604
- let timerId = null;
3605
3604
  let active = true;
3605
+ const handlers = {};
3606
3606
  let batchScheduled = false;
3607
3607
  const batchedForceUpdate = () => {
3608
3608
  if (!active) return;
@@ -3614,46 +3614,24 @@ function useAgent({ agentId, threadId, updates, throttleMs } = {}) {
3614
3614
  });
3615
3615
  }
3616
3616
  };
3617
- if (updateFlags.includes(UseAgentUpdate.OnMessagesChanged)) {
3618
- const ms = effectiveThrottleMs;
3619
- if (ms > 0) {
3620
- let throttleActive = false;
3621
- let pending = false;
3622
- const throttledNotify = () => {
3623
- if (!active) return;
3624
- if (!throttleActive) {
3625
- throttleActive = true;
3626
- pending = false;
3627
- forceUpdate();
3628
- timerId = setTimeout(function trailingEdge() {
3629
- timerId = null;
3630
- if (active && pending) {
3631
- pending = false;
3632
- forceUpdate();
3633
- timerId = setTimeout(trailingEdge, ms);
3634
- } else throttleActive = false;
3635
- }, ms);
3636
- } else pending = true;
3637
- };
3638
- handlers.onMessagesChanged = throttledNotify;
3639
- } else handlers.onMessagesChanged = forceUpdate;
3640
- }
3617
+ if (updateFlags.includes(UseAgentUpdate.OnMessagesChanged)) handlers.onMessagesChanged = forceUpdate;
3641
3618
  if (updateFlags.includes(UseAgentUpdate.OnStateChanged)) handlers.onStateChanged = batchedForceUpdate;
3642
3619
  if (updateFlags.includes(UseAgentUpdate.OnRunStatusChanged)) {
3643
3620
  handlers.onRunInitialized = batchedForceUpdate;
3644
3621
  handlers.onRunFinalized = batchedForceUpdate;
3645
3622
  handlers.onRunFailed = batchedForceUpdate;
3623
+ handlers.onRunErrorEvent = batchedForceUpdate;
3646
3624
  }
3647
- const subscription = agent.subscribe(handlers);
3625
+ const subscription = copilotkit.subscribeToAgentWithOptions(agent, handlers, { throttleMs });
3648
3626
  return () => {
3649
3627
  active = false;
3650
- if (timerId !== null) clearTimeout(timerId);
3651
3628
  subscription.unsubscribe();
3652
3629
  };
3653
3630
  }, [
3654
3631
  agent,
3655
3632
  forceUpdate,
3656
- effectiveThrottleMs,
3633
+ throttleMs,
3634
+ providerThrottleMs,
3657
3635
  updateFlags
3658
3636
  ]);
3659
3637
  useEffect(() => {
@@ -4607,13 +4585,14 @@ function useThreads$1({ agentId, includeArchived, limit }) {
4607
4585
  const { copilotkit } = useCopilotKit();
4608
4586
  const [store] = useState(() => ɵcreateThreadStore({ fetch: globalThis.fetch }));
4609
4587
  const coreThreads = useThreadStoreSelector(store, ɵselectThreads);
4610
- const threads = useMemo(() => coreThreads.map(({ id, agentId, name, archived, createdAt, updatedAt }) => ({
4588
+ const threads = useMemo(() => coreThreads.map(({ id, agentId, name, archived, createdAt, updatedAt, lastRunAt }) => ({
4611
4589
  id,
4612
4590
  agentId,
4613
4591
  name,
4614
4592
  archived,
4615
4593
  createdAt,
4616
- updatedAt
4594
+ updatedAt,
4595
+ ...lastRunAt !== void 0 ? { lastRunAt } : {}
4617
4596
  })), [coreThreads]);
4618
4597
  const storeIsLoading = useThreadStoreSelector(store, ɵselectThreadsIsLoading);
4619
4598
  const storeError = useThreadStoreSelector(store, ɵselectThreadsError);
@@ -4626,7 +4605,9 @@ function useThreads$1({ agentId, includeArchived, limit }) {
4626
4605
  if (copilotkit.runtimeUrl) return null;
4627
4606
  return /* @__PURE__ */ new Error("Runtime URL is not configured");
4628
4607
  }, [copilotkit.runtimeUrl]);
4629
- const isLoading = runtimeError ? false : storeIsLoading;
4608
+ const [hasDispatchedContext, setHasDispatchedContext] = useState(false);
4609
+ const preConnectLoading = !!copilotkit.runtimeUrl && !hasDispatchedContext;
4610
+ const isLoading = runtimeError ? false : preConnectLoading || storeIsLoading;
4630
4611
  const error = runtimeError ?? storeError;
4631
4612
  useEffect(() => {
4632
4613
  store.start();
@@ -4634,19 +4615,27 @@ function useThreads$1({ agentId, includeArchived, limit }) {
4634
4615
  store.stop();
4635
4616
  };
4636
4617
  }, [store]);
4618
+ const runtimeStatus = copilotkit.runtimeConnectionStatus;
4637
4619
  useEffect(() => {
4638
- const context = copilotkit.runtimeUrl ? {
4620
+ if (!copilotkit.runtimeUrl) {
4621
+ store.setContext(null);
4622
+ return;
4623
+ }
4624
+ if (runtimeStatus !== CopilotKitCoreRuntimeConnectionStatus.Connected) return;
4625
+ const context = {
4639
4626
  runtimeUrl: copilotkit.runtimeUrl,
4640
4627
  headers: { ...copilotkit.headers },
4641
4628
  wsUrl: copilotkit.intelligence?.wsUrl,
4642
4629
  agentId,
4643
4630
  includeArchived,
4644
4631
  limit
4645
- } : null;
4632
+ };
4646
4633
  store.setContext(context);
4634
+ setHasDispatchedContext(true);
4647
4635
  }, [
4648
4636
  store,
4649
4637
  copilotkit.runtimeUrl,
4638
+ runtimeStatus,
4650
4639
  headersKey,
4651
4640
  copilotkit.intelligence?.wsUrl,
4652
4641
  agentId,
@@ -6171,9 +6160,97 @@ function useKeyboardHeight() {
6171
6160
  return keyboardState;
6172
6161
  }
6173
6162
 
6163
+ //#endregion
6164
+ //#region src/v2/components/chat/normalize-auto-scroll.ts
6165
+ const VALID = [
6166
+ "pin-to-bottom",
6167
+ "pin-to-send",
6168
+ "none"
6169
+ ];
6170
+ function normalizeAutoScroll(value) {
6171
+ if (value === void 0) return "pin-to-bottom";
6172
+ if (value === true) return "pin-to-bottom";
6173
+ if (value === false) return "none";
6174
+ if (VALID.includes(value)) return value;
6175
+ return "pin-to-bottom";
6176
+ }
6177
+
6178
+ //#endregion
6179
+ //#region src/v2/components/chat/last-user-message-context.ts
6180
+ const LastUserMessageContext = React.createContext({
6181
+ id: null,
6182
+ sendNonce: 0
6183
+ });
6184
+
6185
+ //#endregion
6186
+ //#region src/v2/hooks/use-pin-to-send.ts
6187
+ function usePinToSend({ scrollRef, contentRef, spacerRef, topOffset = 16 }) {
6188
+ const { id, sendNonce } = useContext(LastUserMessageContext);
6189
+ const lastNonceRef = useRef(-1);
6190
+ const currentSpacerHeightRef = useRef(0);
6191
+ useEffect(() => {
6192
+ if (sendNonce === lastNonceRef.current) return;
6193
+ lastNonceRef.current = sendNonce;
6194
+ if (!id) return;
6195
+ const scrollEl = scrollRef.current;
6196
+ const contentEl = contentRef.current;
6197
+ const spacerEl = spacerRef.current;
6198
+ if (!scrollEl || !contentEl || !spacerEl) return;
6199
+ const escaped = typeof CSS !== "undefined" && CSS.escape ? CSS.escape(id) : id.replace(/[!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g, "\\$&");
6200
+ const targetEl = contentEl.querySelector(`[data-message-id="${escaped}"]`);
6201
+ if (!targetEl) return;
6202
+ const viewportHeight = scrollEl.clientHeight;
6203
+ const userMessageHeight = targetEl.getBoundingClientRect().height;
6204
+ const paddingTop = parseFloat(getComputedStyle(targetEl).paddingTop) || 0;
6205
+ const bubbleHeight = Math.max(0, userMessageHeight - paddingTop);
6206
+ const spacerHeight = Math.max(0, viewportHeight - bubbleHeight - topOffset);
6207
+ spacerEl.style.height = `${spacerHeight}px`;
6208
+ currentSpacerHeightRef.current = spacerHeight;
6209
+ const raf = requestAnimationFrame(() => {
6210
+ const targetTop = computeOffsetTop(targetEl, scrollEl) + paddingTop - topOffset;
6211
+ scrollEl.scrollTo({
6212
+ top: Math.max(0, targetTop),
6213
+ behavior: "smooth"
6214
+ });
6215
+ });
6216
+ const ro = new ResizeObserver(() => {
6217
+ if (!contentEl || !spacerEl || !scrollEl) return;
6218
+ const consumedBelow = contentEl.getBoundingClientRect().height - computeOffsetTop(targetEl, contentEl) - userMessageHeight;
6219
+ const remaining = Math.max(0, spacerHeight - consumedBelow);
6220
+ if (remaining < currentSpacerHeightRef.current) {
6221
+ spacerEl.style.height = `${remaining}px`;
6222
+ currentSpacerHeightRef.current = remaining;
6223
+ }
6224
+ });
6225
+ ro.observe(contentEl);
6226
+ return () => {
6227
+ cancelAnimationFrame(raf);
6228
+ ro.disconnect();
6229
+ };
6230
+ }, [
6231
+ id,
6232
+ sendNonce,
6233
+ scrollRef,
6234
+ contentRef,
6235
+ spacerRef,
6236
+ topOffset
6237
+ ]);
6238
+ }
6239
+ function computeOffsetTop(el, stopAt) {
6240
+ const elRect = el.getBoundingClientRect();
6241
+ const stopRect = stopAt.getBoundingClientRect();
6242
+ return elRect.top - stopRect.top + stopAt.scrollTop;
6243
+ }
6244
+
6174
6245
  //#endregion
6175
6246
  //#region src/v2/components/chat/CopilotChatView.tsx
6176
6247
  const FEATHER_HEIGHT = 96;
6248
+ const PIN_TO_SEND_FEATHER_HEIGHT = 48;
6249
+ const PinToSendSoftFeather = ({ className, style, ...props }) => /* @__PURE__ */ jsx("div", {
6250
+ 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),
6251
+ style,
6252
+ ...props
6253
+ });
6177
6254
  function DropOverlay() {
6178
6255
  return /* @__PURE__ */ jsx("div", {
6179
6256
  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"),
@@ -6186,7 +6263,7 @@ function DropOverlay() {
6186
6263
  })
6187
6264
  });
6188
6265
  }
6189
- 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 }) {
6266
+ 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 }) {
6190
6267
  const inputContainerRef = useRef(null);
6191
6268
  const [inputContainerHeight, setInputContainerHeight] = useState(0);
6192
6269
  const [isResizing, setIsResizing] = useState(false);
@@ -6238,9 +6315,10 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
6238
6315
  keyboardHeight: isKeyboardOpen ? keyboardHeight : 0,
6239
6316
  containerRef: inputContainerRef,
6240
6317
  showDisclaimer: true,
6318
+ bottomAnchored: true,
6241
6319
  ...disclaimer !== void 0 ? { disclaimer } : {}
6242
6320
  });
6243
- const hasSuggestions = Array.isArray(suggestions) && suggestions.length > 0;
6321
+ const hasSuggestions = !isConnecting && !isRunning && Array.isArray(suggestions) && suggestions.length > 0;
6244
6322
  const BoundSuggestionView = hasSuggestions ? renderSlot(suggestionView, CopilotChatSuggestionView, {
6245
6323
  suggestions,
6246
6324
  loadingIndexes: suggestionLoadingIndexes,
@@ -6262,7 +6340,7 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
6262
6340
  })
6263
6341
  })
6264
6342
  });
6265
- if (messages.length === 0 && !(welcomeScreen === false)) {
6343
+ if (messages.length === 0 && !(welcomeScreen === false) && !isConnecting && !hasExplicitThreadId) {
6266
6344
  const BoundInputForWelcome = renderSlot(input, CopilotChatInput_default, {
6267
6345
  onSubmitMessage,
6268
6346
  onStop,
@@ -6369,9 +6447,60 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
6369
6447
  ] })
6370
6448
  });
6371
6449
  };
6372
- _CopilotChatView.ScrollView = ({ children, autoScroll = true, scrollToBottomButton, feather, inputContainerHeight = 0, isResizing = false, className, ...props }) => {
6450
+ const PinToSendScrollContainer = ({ children, scrollRef, contentRef, scrollToBottom, scrollToBottomButton, feather, inputContainerHeight, isResizing, nonAutoScrollEl, nonAutoScrollRefCallback, showScrollButton, className, ...props }) => {
6451
+ const spacerRef = useRef(null);
6452
+ usePinToSend({
6453
+ scrollRef,
6454
+ contentRef,
6455
+ spacerRef,
6456
+ topOffset: 16
6457
+ });
6458
+ const BoundFeather = renderSlot(feather, PinToSendSoftFeather, {});
6459
+ return /* @__PURE__ */ jsx(ScrollElementContext.Provider, {
6460
+ value: nonAutoScrollEl,
6461
+ children: /* @__PURE__ */ jsxs("div", {
6462
+ className: cn("cpk:h-full cpk:max-h-full cpk:flex cpk:flex-col cpk:min-h-0 cpk:relative", className),
6463
+ children: [
6464
+ /* @__PURE__ */ jsxs("div", {
6465
+ ref: nonAutoScrollRefCallback,
6466
+ className: "cpk:flex-1 cpk:min-h-0 cpk:overflow-y-auto cpk:overflow-x-hidden",
6467
+ ...props,
6468
+ children: [/* @__PURE__ */ jsx("div", {
6469
+ ref: contentRef,
6470
+ className: "cpk:px-4 cpk:sm:px-0 cpk:[div[data-sidebar-chat]_&]:px-8 cpk:[div[data-popup-chat]_&]:px-6",
6471
+ children
6472
+ }), /* @__PURE__ */ jsx("div", {
6473
+ ref: spacerRef,
6474
+ "data-pin-to-send-spacer": true,
6475
+ "aria-hidden": "true",
6476
+ style: {
6477
+ height: 0,
6478
+ flex: "0 0 auto"
6479
+ }
6480
+ })]
6481
+ }),
6482
+ BoundFeather,
6483
+ showScrollButton && !isResizing && /* @__PURE__ */ jsx("div", {
6484
+ className: "cpk:absolute cpk:inset-x-0 cpk:flex cpk:justify-center cpk:z-30 cpk:pointer-events-none",
6485
+ style: { bottom: `${inputContainerHeight + PIN_TO_SEND_FEATHER_HEIGHT + 16}px` },
6486
+ children: renderSlot(scrollToBottomButton, CopilotChatView.ScrollToBottomButton, { onClick: () => scrollToBottom() })
6487
+ })
6488
+ ]
6489
+ })
6490
+ });
6491
+ };
6492
+ _CopilotChatView.ScrollView = ({ children, autoScroll = "pin-to-bottom", scrollToBottomButton, feather, inputContainerHeight = 0, isResizing = false, className, ...props }) => {
6493
+ const mode = normalizeAutoScroll(autoScroll);
6373
6494
  const [hasMounted, setHasMounted] = useState(false);
6374
- const { scrollRef, contentRef, scrollToBottom } = useStickToBottom();
6495
+ const scrollRef = useRef(null);
6496
+ const contentRef = useRef(null);
6497
+ const scrollToBottom = useCallback(() => {
6498
+ const el = scrollRef.current;
6499
+ if (el) el.scrollTo({
6500
+ top: el.scrollHeight,
6501
+ behavior: "smooth"
6502
+ });
6503
+ }, []);
6375
6504
  const [showScrollButton, setShowScrollButton] = useState(false);
6376
6505
  const [nonAutoScrollEl, setNonAutoScrollEl] = useState(null);
6377
6506
  const nonAutoScrollRefCallback = useCallback((el) => {
@@ -6382,7 +6511,7 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
6382
6511
  setHasMounted(true);
6383
6512
  }, []);
6384
6513
  useEffect(() => {
6385
- if (autoScroll) return;
6514
+ if (mode === "pin-to-bottom") return;
6386
6515
  const scrollElement = scrollRef.current;
6387
6516
  if (!scrollElement) return;
6388
6517
  const checkScroll = () => {
@@ -6396,7 +6525,7 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
6396
6525
  scrollElement.removeEventListener("scroll", checkScroll);
6397
6526
  resizeObserver.disconnect();
6398
6527
  };
6399
- }, [scrollRef, autoScroll]);
6528
+ }, [scrollRef, mode]);
6400
6529
  if (!hasMounted) return /* @__PURE__ */ jsx("div", {
6401
6530
  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",
6402
6531
  children: /* @__PURE__ */ jsx("div", {
@@ -6404,7 +6533,7 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
6404
6533
  children
6405
6534
  })
6406
6535
  });
6407
- if (!autoScroll) {
6536
+ if (mode === "none") {
6408
6537
  const BoundFeather = renderSlot(feather, CopilotChatView.Feather, {});
6409
6538
  return /* @__PURE__ */ jsx(ScrollElementContext.Provider, {
6410
6539
  value: nonAutoScrollEl,
@@ -6428,6 +6557,21 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
6428
6557
  })
6429
6558
  });
6430
6559
  }
6560
+ if (mode === "pin-to-send") return /* @__PURE__ */ jsx(PinToSendScrollContainer, {
6561
+ scrollRef,
6562
+ contentRef,
6563
+ scrollToBottom,
6564
+ scrollToBottomButton,
6565
+ feather,
6566
+ inputContainerHeight,
6567
+ isResizing,
6568
+ nonAutoScrollEl,
6569
+ nonAutoScrollRefCallback,
6570
+ showScrollButton,
6571
+ className,
6572
+ ...props,
6573
+ children
6574
+ });
6431
6575
  return /* @__PURE__ */ jsx(StickToBottom, {
6432
6576
  className: cn("cpk:flex-1 cpk:max-h-full cpk:flex cpk:flex-col cpk:min-h-0", className),
6433
6577
  resize: "smooth",
@@ -6620,7 +6764,8 @@ async function transcribeAudio(core, audioBlob, filename = "recording.webm") {
6620
6764
  function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen, attachments: attachmentsConfig, onError, throttleMs, ...props }) {
6621
6765
  const existingConfig = useCopilotChatConfiguration();
6622
6766
  const resolvedAgentId = agentId ?? existingConfig?.agentId ?? DEFAULT_AGENT_ID;
6623
- const resolvedThreadId = useMemo(() => threadId ?? existingConfig?.threadId ?? randomUUID(), [threadId, existingConfig?.threadId]);
6767
+ const providedThreadId = threadId ?? existingConfig?.threadId;
6768
+ const resolvedThreadId = useMemo(() => providedThreadId ?? randomUUID(), [providedThreadId]);
6624
6769
  const { agent } = useAgent({
6625
6770
  agentId: resolvedAgentId,
6626
6771
  threadId: resolvedThreadId,
@@ -6658,7 +6803,10 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
6658
6803
  const isTranscriptionEnabled = copilotkit.audioFileTranscriptionEnabled;
6659
6804
  const isMediaRecorderSupported = typeof window !== "undefined" && typeof MediaRecorder !== "undefined";
6660
6805
  const { messageView: providedMessageView, suggestionView: providedSuggestionView, onStop: providedStopHandler, ...restProps } = props;
6806
+ const [lastConnectedThreadId, setLastConnectedThreadId] = useState(null);
6807
+ const isConnecting = !!providedThreadId && lastConnectedThreadId !== resolvedThreadId;
6661
6808
  useEffect(() => {
6809
+ if (!providedThreadId) return;
6662
6810
  let detached = false;
6663
6811
  const connectAbortController = new AbortController();
6664
6812
  if (agent instanceof HttpAgent) agent.abortController = connectAbortController;
@@ -6668,6 +6816,10 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
6668
6816
  } catch (error) {
6669
6817
  if (detached) return;
6670
6818
  console.error("CopilotChat: connectAgent failed", error);
6819
+ } finally {
6820
+ if (!detached) (typeof requestAnimationFrame === "function" ? requestAnimationFrame : (cb) => setTimeout(cb, 16))(() => {
6821
+ if (!detached) setLastConnectedThreadId(resolvedThreadId);
6822
+ });
6671
6823
  }
6672
6824
  };
6673
6825
  connect(agent);
@@ -6679,7 +6831,8 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
6679
6831
  }, [
6680
6832
  resolvedThreadId,
6681
6833
  agent,
6682
- resolvedAgentId
6834
+ resolvedAgentId,
6835
+ providedThreadId
6683
6836
  ]);
6684
6837
  const onSubmitInput = useCallback(async (value) => {
6685
6838
  if (selectedAttachments.some((a) => a.status === "uploading")) {
@@ -6832,6 +6985,22 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
6832
6985
  const toolCallsKey = "toolCalls" in m && Array.isArray(m.toolCalls) ? m.toolCalls.map((tc) => `${tc.id}:${tc.function?.arguments?.length ?? 0}`).join(";") : "";
6833
6986
  return `${m.id}:${m.role}:${contentKey}:${toolCallsKey}`;
6834
6987
  }).join(",")]);
6988
+ const lastUserMessageId = useMemo(() => {
6989
+ for (let i = messages.length - 1; i >= 0; i--) if (messages[i].role === "user") return messages[i].id;
6990
+ return null;
6991
+ }, [messages]);
6992
+ const [sendNonce, setSendNonce] = useState(0);
6993
+ const prevLastUserMessageIdRef = useRef(lastUserMessageId);
6994
+ useEffect(() => {
6995
+ if (lastUserMessageId && lastUserMessageId !== prevLastUserMessageIdRef.current) {
6996
+ setSendNonce((n) => n + 1);
6997
+ prevLastUserMessageIdRef.current = lastUserMessageId;
6998
+ }
6999
+ }, [lastUserMessageId]);
7000
+ const lastUserMessageState = useMemo(() => ({
7001
+ id: lastUserMessageId,
7002
+ sendNonce
7003
+ }), [lastUserMessageId, sendNonce]);
6835
7004
  const RenderedChatView = renderSlot(chatView, CopilotChatView, {
6836
7005
  ...mergedProps,
6837
7006
  messages,
@@ -6850,7 +7019,9 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
6850
7019
  dragOver,
6851
7020
  onDragOver: handleDragOver,
6852
7021
  onDragLeave: handleDragLeave,
6853
- onDrop: handleDrop
7022
+ onDrop: handleDrop,
7023
+ isConnecting,
7024
+ hasExplicitThreadId: !!providedThreadId
6854
7025
  });
6855
7026
  return /* @__PURE__ */ jsx(CopilotChatConfigurationProvider, {
6856
7027
  agentId: resolvedAgentId,
@@ -6885,7 +7056,10 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
6885
7056
  },
6886
7057
  children: transcriptionError
6887
7058
  }),
6888
- RenderedChatView
7059
+ /* @__PURE__ */ jsx(LastUserMessageContext.Provider, {
7060
+ value: lastUserMessageState,
7061
+ children: RenderedChatView
7062
+ })
6889
7063
  ]
6890
7064
  })
6891
7065
  });
@@ -9568,4 +9742,4 @@ function validateProps(props) {
9568
9742
 
9569
9743
  //#endregion
9570
9744
  export { CopilotKitProvider as $, CopilotChatSuggestionView as A, useConfigureSuggestions as B, CopilotChatToggleButton as C, CopilotChatView_default as D, CopilotChat as E, CopilotChatAssistantMessage_default as F, useRenderTool as G, useCapabilities as H, CopilotChatToolCallsView as I, useRenderActivityMessage as J, useComponent as K, useAttachments as L, CopilotChatReasoningMessage_default as M, CopilotChatUserMessage_default as N, CopilotChatAttachmentQueue as O, CopilotChatAttachmentRenderer as P, useRenderToolCall as Q, useThreads$1 as R, CopilotModalHeader as S, DefaultOpenIcon as T, useHumanInTheLoop as U, useSuggestions as V, useDefaultRenderTool as W, UseAgentUpdate as X, useRenderCustomMessages as Y, useAgent as Z, WildcardToolCallRender as _, ThreadsProvider as a, SandboxFunctionsContext as at, CopilotPopupView as b, CoAgentStateRendersProvider as c, MCPAppsActivityRenderer as ct, shouldShowDevConsole as d, CopilotChatInput_default as dt, useCopilotKit as et, useToast as f, AudioRecorderError as ft, useCopilotContext as g, CopilotContext as h, useCopilotChatConfiguration as ht, ThreadsContext as i, createA2UIMessageRenderer as it, CopilotChatSuggestionPill as j, CopilotChatMessageView as k, useCoAgentStateRenders as l, MCPAppsActivityType as lt, useCopilotMessagesContext as m, CopilotChatConfigurationProvider as mt, defaultCopilotContextCategories as n, useAgentContext as nt, useThreads as o, useSandboxFunctions as ot, CopilotMessagesContext as p, CopilotChatAudioRecorder as pt, useFrontendTool as q, CoAgentStateRenderBridge as r, defineToolCallRenderer as rt, CoAgentStateRendersContext as s, MCPAppsActivityContentSchema as st, CopilotKit as t, CopilotKitCoreReact as tt, useAsyncCallback as u, CopilotKitInspector as ut, CopilotPopup as v, DefaultCloseIcon as w, CopilotSidebarView as x, CopilotSidebar as y, useInterrupt as z };
9571
- //# sourceMappingURL=copilotkit-Cj2ZIxVr.mjs.map
9745
+ //# sourceMappingURL=copilotkit-BBYbekCa.mjs.map