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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/dist/{copilotkit-CSJw5BG8.cjs → copilotkit-BAkj3zUc.cjs} +359 -157
  2. package/dist/copilotkit-BAkj3zUc.cjs.map +1 -0
  3. package/dist/{copilotkit-Cj2ZIxVr.mjs → copilotkit-DAatqMh2.mjs} +360 -158
  4. package/dist/copilotkit-DAatqMh2.mjs.map +1 -0
  5. package/dist/{copilotkit-CCbxm6JM.d.mts → copilotkit-DFaI4j2r.d.mts} +64 -18
  6. package/dist/copilotkit-DFaI4j2r.d.mts.map +1 -0
  7. package/dist/{copilotkit-BtP7w7cT.d.cts → copilotkit-Dg4r4Gi_.d.cts} +64 -18
  8. package/dist/copilotkit-Dg4r4Gi_.d.cts.map +1 -0
  9. package/dist/index.cjs +1 -1
  10. package/dist/index.d.cts +2 -1
  11. package/dist/index.d.cts.map +1 -1
  12. package/dist/index.d.mts +2 -1
  13. package/dist/index.d.mts.map +1 -1
  14. package/dist/index.mjs +1 -1
  15. package/dist/index.umd.js +31 -44
  16. package/dist/index.umd.js.map +1 -1
  17. package/dist/v2/index.cjs +1 -1
  18. package/dist/v2/index.css +1 -1
  19. package/dist/v2/index.d.cts +2 -2
  20. package/dist/v2/index.d.mts +2 -2
  21. package/dist/v2/index.mjs +1 -1
  22. package/dist/v2/index.umd.js +361 -163
  23. package/dist/v2/index.umd.js.map +1 -1
  24. package/package.json +8 -8
  25. package/src/components/copilot-provider/__tests__/v1-explicit-threadid-bridge.test.tsx +107 -0
  26. package/src/components/copilot-provider/copilotkit.tsx +6 -1
  27. package/src/context/__tests__/threads-context.test.tsx +116 -3
  28. package/src/context/threads-context.tsx +18 -1
  29. package/src/v2/components/chat/CopilotChat.tsx +91 -4
  30. package/src/v2/components/chat/CopilotChatAttachmentQueue.tsx +7 -114
  31. package/src/v2/components/chat/CopilotChatAttachmentRenderer.tsx +26 -6
  32. package/src/v2/components/chat/CopilotChatInput.tsx +22 -0
  33. package/src/v2/components/chat/CopilotChatUserMessage.tsx +2 -2
  34. package/src/v2/components/chat/CopilotChatView.tsx +226 -48
  35. package/src/v2/components/chat/Lightbox.tsx +103 -0
  36. package/src/v2/components/chat/__tests__/CopilotChat.absentThreadConnect.test.tsx +66 -0
  37. package/src/v2/components/chat/__tests__/CopilotChat.suggestionsAlways.test.tsx +189 -0
  38. package/src/v2/components/chat/__tests__/CopilotChat.welcomeGate.test.tsx +186 -0
  39. package/src/v2/components/chat/__tests__/CopilotChatActivityRendering.e2e.test.tsx +438 -4
  40. package/src/v2/components/chat/__tests__/CopilotChatView.connectingGate.test.tsx +56 -0
  41. package/src/v2/components/chat/__tests__/CopilotChatView.inputOverlay.test.tsx +264 -0
  42. package/src/v2/components/chat/__tests__/CopilotChatView.pinToSend.test.tsx +94 -0
  43. package/src/v2/components/chat/__tests__/copilot-chat-throttle.test.tsx +0 -1
  44. package/src/v2/components/chat/__tests__/normalize-auto-scroll.test.ts +37 -0
  45. package/src/v2/components/chat/index.ts +2 -0
  46. package/src/v2/components/chat/last-user-message-context.ts +21 -0
  47. package/src/v2/components/chat/normalize-auto-scroll.ts +17 -0
  48. package/src/v2/components/license-warning-banner.tsx +20 -1
  49. package/src/v2/hooks/__tests__/use-agent-stability.test.tsx +6 -0
  50. package/src/v2/hooks/__tests__/use-agent-thread-isolation.test.tsx +6 -0
  51. package/src/v2/hooks/__tests__/use-agent-throttle.test.tsx +76 -50
  52. package/src/v2/hooks/__tests__/use-pin-to-send.test.tsx +219 -0
  53. package/src/v2/hooks/__tests__/use-threads.test.tsx +68 -0
  54. package/src/v2/hooks/use-agent.tsx +34 -77
  55. package/src/v2/hooks/use-pin-to-send.ts +94 -0
  56. package/src/v2/hooks/use-threads.tsx +55 -12
  57. package/src/v2/providers/CopilotChatConfigurationProvider.tsx +29 -1
  58. package/src/v2/providers/CopilotKitProvider.tsx +2 -11
  59. package/src/v2/providers/__tests__/CopilotChatConfigurationProvider.test.tsx +106 -0
  60. package/dist/copilotkit-BtP7w7cT.d.cts.map +0 -1
  61. package/dist/copilotkit-CCbxm6JM.d.mts.map +0 -1
  62. package/dist/copilotkit-CSJw5BG8.cjs.map +0 -1
  63. package/dist/copilotkit-Cj2ZIxVr.mjs.map +0 -1
@@ -16,9 +16,9 @@ import { z } from "zod";
16
16
  import { createComponent } from "@lit-labs/react";
17
17
  import { A2UIProvider, A2UIRenderer, A2UI_SCHEMA_CONTEXT_DESCRIPTION, DEFAULT_SURFACE_ID, buildCatalogContextValue, extractCatalogComponentSchemas, initializeDefaultCatalog, injectStyles, useA2UIActions, useA2UIError, viewerTheme } from "@copilotkit/a2ui-renderer";
18
18
  import { zodToJsonSchema } from "zod-to-json-schema";
19
- import { useVirtualizer } from "@tanstack/react-virtual";
20
19
  import { createPortal, flushSync } from "react-dom";
21
- import { StickToBottom, useStickToBottom, useStickToBottomContext } from "use-stick-to-bottom";
20
+ import { useVirtualizer } from "@tanstack/react-virtual";
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
@@ -145,7 +145,7 @@ const CopilotChatDefaultLabels = {
145
145
  welcomeMessageText: "How can I help you today?"
146
146
  };
147
147
  const CopilotChatConfiguration = createContext(null);
148
- const CopilotChatConfigurationProvider = ({ children, labels, agentId, threadId, isModalDefaultOpen }) => {
148
+ const CopilotChatConfigurationProvider = ({ children, labels, agentId, threadId, hasExplicitThreadId, isModalDefaultOpen }) => {
149
149
  const parentConfig = useContext(CopilotChatConfiguration);
150
150
  const stableLabels = useShallowStableRef(labels);
151
151
  const mergedLabels = useMemo(() => ({
@@ -159,6 +159,7 @@ const CopilotChatConfigurationProvider = ({ children, labels, agentId, threadId,
159
159
  if (parentConfig?.threadId) return parentConfig.threadId;
160
160
  return randomUUID();
161
161
  }, [threadId, parentConfig?.threadId]);
162
+ const resolvedHasExplicitThreadId = (hasExplicitThreadId !== void 0 ? hasExplicitThreadId : !!threadId) || !!parentConfig?.hasExplicitThreadId;
162
163
  const [internalModalOpen, setInternalModalOpen] = useState(isModalDefaultOpen ?? true);
163
164
  const hasExplicitDefault = isModalDefaultOpen !== void 0;
164
165
  const setAndSync = useCallback((open) => {
@@ -181,12 +182,14 @@ const CopilotChatConfigurationProvider = ({ children, labels, agentId, threadId,
181
182
  labels: mergedLabels,
182
183
  agentId: resolvedAgentId,
183
184
  threadId: resolvedThreadId,
185
+ hasExplicitThreadId: resolvedHasExplicitThreadId,
184
186
  isModalOpen: resolvedIsModalOpen,
185
187
  setModalOpen: resolvedSetModalOpen
186
188
  }), [
187
189
  mergedLabels,
188
190
  resolvedAgentId,
189
191
  resolvedThreadId,
192
+ resolvedHasExplicitThreadId,
190
193
  resolvedIsModalOpen,
191
194
  resolvedSetModalOpen
192
195
  ]);
@@ -580,7 +583,7 @@ CopilotChatAudioRecorder.displayName = "CopilotChatAudioRecorder";
580
583
  //#region src/v2/components/chat/CopilotChatInput.tsx
581
584
  const SLASH_MENU_MAX_VISIBLE_ITEMS = 5;
582
585
  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 }) {
586
+ 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
587
  const isControlled = value !== void 0;
585
588
  const [internalValue, setInternalValue] = useState(() => value ?? "");
586
589
  useEffect(() => {
@@ -1104,7 +1107,8 @@ function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning =
1104
1107
  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
1108
  style: {
1106
1109
  transform: keyboardHeight > 0 ? `translateY(-${keyboardHeight}px)` : void 0,
1107
- transition: "transform 0.2s ease-out"
1110
+ transition: "transform 0.2s ease-out",
1111
+ ...positioning === "absolute" || bottomAnchored ? { paddingBottom: "var(--copilotkit-license-banner-offset, 0px)" } : {}
1108
1112
  },
1109
1113
  ...props,
1110
1114
  children: [/* @__PURE__ */ jsx("div", {
@@ -1319,6 +1323,8 @@ CopilotKitInspector.displayName = "CopilotKitInspector";
1319
1323
 
1320
1324
  //#endregion
1321
1325
  //#region src/v2/components/license-warning-banner.tsx
1326
+ const LICENSE_BANNER_OFFSET_PX = 52;
1327
+ const LICENSE_BANNER_OFFSET_VAR = "--copilotkit-license-banner-offset";
1322
1328
  const BANNER_STYLES = {
1323
1329
  base: {
1324
1330
  position: "fixed",
@@ -1360,6 +1366,14 @@ function getSeverityStyle(severity) {
1360
1366
  }
1361
1367
  }
1362
1368
  function BannerShell({ severity, message, actionLabel, actionUrl, onDismiss }) {
1369
+ useEffect(() => {
1370
+ if (typeof document === "undefined") return;
1371
+ const root = document.documentElement;
1372
+ root.style.setProperty(LICENSE_BANNER_OFFSET_VAR, `${LICENSE_BANNER_OFFSET_PX}px`);
1373
+ return () => {
1374
+ root.style.removeProperty(LICENSE_BANNER_OFFSET_VAR);
1375
+ };
1376
+ }, []);
1363
1377
  return /* @__PURE__ */ jsxs("div", {
1364
1378
  style: {
1365
1379
  ...BANNER_STYLES.base,
@@ -3312,7 +3326,6 @@ const CopilotKitProvider = ({ children, runtimeUrl, headers: headersProp = {}, c
3312
3326
  didMountRef.current = true;
3313
3327
  }, []);
3314
3328
  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
3329
  copilotkit.setDefaultThrottleMs(defaultThrottleMs);
3317
3330
  }, [copilotkit, defaultThrottleMs]);
3318
3331
  const designSkill = openGenerativeUI?.designSkill ?? DEFAULT_DESIGN_SKILL;
@@ -3529,15 +3542,6 @@ function useAgent({ agentId, threadId, updates, throttleMs } = {}) {
3529
3542
  const providerThrottleMs = copilotkit.defaultThrottleMs;
3530
3543
  const chatConfig = useCopilotChatConfiguration();
3531
3544
  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
3545
  const [, forceUpdate] = useReducer((x) => x + 1, 0);
3542
3546
  const updateFlags = useMemo(() => updates ?? ALL_UPDATES, [JSON.stringify(updates)]);
3543
3547
  const provisionalAgentCache = useRef(/* @__PURE__ */ new Map());
@@ -3600,9 +3604,8 @@ function useAgent({ agentId, threadId, updates, throttleMs } = {}) {
3600
3604
  ]);
3601
3605
  useEffect(() => {
3602
3606
  if (updateFlags.length === 0) return;
3603
- const handlers = {};
3604
- let timerId = null;
3605
3607
  let active = true;
3608
+ const handlers = {};
3606
3609
  let batchScheduled = false;
3607
3610
  const batchedForceUpdate = () => {
3608
3611
  if (!active) return;
@@ -3614,46 +3617,24 @@ function useAgent({ agentId, threadId, updates, throttleMs } = {}) {
3614
3617
  });
3615
3618
  }
3616
3619
  };
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
- }
3620
+ if (updateFlags.includes(UseAgentUpdate.OnMessagesChanged)) handlers.onMessagesChanged = forceUpdate;
3641
3621
  if (updateFlags.includes(UseAgentUpdate.OnStateChanged)) handlers.onStateChanged = batchedForceUpdate;
3642
3622
  if (updateFlags.includes(UseAgentUpdate.OnRunStatusChanged)) {
3643
3623
  handlers.onRunInitialized = batchedForceUpdate;
3644
3624
  handlers.onRunFinalized = batchedForceUpdate;
3645
3625
  handlers.onRunFailed = batchedForceUpdate;
3626
+ handlers.onRunErrorEvent = batchedForceUpdate;
3646
3627
  }
3647
- const subscription = agent.subscribe(handlers);
3628
+ const subscription = copilotkit.subscribeToAgentWithOptions(agent, handlers, { throttleMs });
3648
3629
  return () => {
3649
3630
  active = false;
3650
- if (timerId !== null) clearTimeout(timerId);
3651
3631
  subscription.unsubscribe();
3652
3632
  };
3653
3633
  }, [
3654
3634
  agent,
3655
3635
  forceUpdate,
3656
- effectiveThrottleMs,
3636
+ throttleMs,
3637
+ providerThrottleMs,
3657
3638
  updateFlags
3658
3639
  ]);
3659
3640
  useEffect(() => {
@@ -4607,13 +4588,14 @@ function useThreads$1({ agentId, includeArchived, limit }) {
4607
4588
  const { copilotkit } = useCopilotKit();
4608
4589
  const [store] = useState(() => ɵcreateThreadStore({ fetch: globalThis.fetch }));
4609
4590
  const coreThreads = useThreadStoreSelector(store, ɵselectThreads);
4610
- const threads = useMemo(() => coreThreads.map(({ id, agentId, name, archived, createdAt, updatedAt }) => ({
4591
+ const threads = useMemo(() => coreThreads.map(({ id, agentId, name, archived, createdAt, updatedAt, lastRunAt }) => ({
4611
4592
  id,
4612
4593
  agentId,
4613
4594
  name,
4614
4595
  archived,
4615
4596
  createdAt,
4616
- updatedAt
4597
+ updatedAt,
4598
+ ...lastRunAt !== void 0 ? { lastRunAt } : {}
4617
4599
  })), [coreThreads]);
4618
4600
  const storeIsLoading = useThreadStoreSelector(store, ɵselectThreadsIsLoading);
4619
4601
  const storeError = useThreadStoreSelector(store, ɵselectThreadsError);
@@ -4626,7 +4608,9 @@ function useThreads$1({ agentId, includeArchived, limit }) {
4626
4608
  if (copilotkit.runtimeUrl) return null;
4627
4609
  return /* @__PURE__ */ new Error("Runtime URL is not configured");
4628
4610
  }, [copilotkit.runtimeUrl]);
4629
- const isLoading = runtimeError ? false : storeIsLoading;
4611
+ const [hasDispatchedContext, setHasDispatchedContext] = useState(false);
4612
+ const preConnectLoading = !!copilotkit.runtimeUrl && !hasDispatchedContext;
4613
+ const isLoading = runtimeError ? false : preConnectLoading || storeIsLoading;
4630
4614
  const error = runtimeError ?? storeError;
4631
4615
  useEffect(() => {
4632
4616
  store.start();
@@ -4634,19 +4618,27 @@ function useThreads$1({ agentId, includeArchived, limit }) {
4634
4618
  store.stop();
4635
4619
  };
4636
4620
  }, [store]);
4621
+ const runtimeStatus = copilotkit.runtimeConnectionStatus;
4637
4622
  useEffect(() => {
4638
- const context = copilotkit.runtimeUrl ? {
4623
+ if (!copilotkit.runtimeUrl) {
4624
+ store.setContext(null);
4625
+ return;
4626
+ }
4627
+ if (runtimeStatus !== CopilotKitCoreRuntimeConnectionStatus.Connected) return;
4628
+ const context = {
4639
4629
  runtimeUrl: copilotkit.runtimeUrl,
4640
4630
  headers: { ...copilotkit.headers },
4641
4631
  wsUrl: copilotkit.intelligence?.wsUrl,
4642
4632
  agentId,
4643
4633
  includeArchived,
4644
4634
  limit
4645
- } : null;
4635
+ };
4646
4636
  store.setContext(context);
4637
+ setHasDispatchedContext(true);
4647
4638
  }, [
4648
4639
  store,
4649
4640
  copilotkit.runtimeUrl,
4641
+ runtimeStatus,
4650
4642
  headersKey,
4651
4643
  copilotkit.intelligence?.wsUrl,
4652
4644
  agentId,
@@ -5014,20 +5006,101 @@ CopilotChatAssistantMessage.ReadAloudButton.displayName = "CopilotChatAssistantM
5014
5006
  CopilotChatAssistantMessage.RegenerateButton.displayName = "CopilotChatAssistantMessage.RegenerateButton";
5015
5007
  var CopilotChatAssistantMessage_default = CopilotChatAssistantMessage;
5016
5008
 
5009
+ //#endregion
5010
+ //#region src/v2/components/chat/Lightbox.tsx
5011
+ function Lightbox({ onClose, children }) {
5012
+ useEffect(() => {
5013
+ const handleKey = (e) => {
5014
+ if (e.key === "Escape") onClose();
5015
+ };
5016
+ document.addEventListener("keydown", handleKey);
5017
+ return () => document.removeEventListener("keydown", handleKey);
5018
+ }, [onClose]);
5019
+ if (typeof document === "undefined") return null;
5020
+ return createPortal(/* @__PURE__ */ jsxs("div", {
5021
+ className: "cpk:fixed cpk:inset-0 cpk:z-[9999] cpk:flex cpk:items-center cpk:justify-center cpk:bg-black/80 cpk:animate-fade-in",
5022
+ onClick: onClose,
5023
+ children: [/* @__PURE__ */ jsx("button", {
5024
+ onClick: onClose,
5025
+ className: "cpk:absolute cpk:top-4 cpk:right-4 cpk:text-white cpk:bg-white/10 cpk:hover:bg-white/20 cpk:rounded-full cpk:w-10 cpk:h-10 cpk:flex cpk:items-center cpk:justify-center cpk:cursor-pointer cpk:border-none cpk:z-10",
5026
+ "aria-label": "Close preview",
5027
+ children: /* @__PURE__ */ jsx(X, { className: "cpk:w-5 cpk:h-5" })
5028
+ }), /* @__PURE__ */ jsx("div", {
5029
+ onClick: (e) => e.stopPropagation(),
5030
+ children
5031
+ })]
5032
+ }), document.body);
5033
+ }
5034
+ /**
5035
+ * Hook that manages lightbox open/close and uses the View Transition API to
5036
+ * morph the thumbnail into fullscreen content.
5037
+ *
5038
+ * The trick: `view-transition-name` must live on exactly ONE element at a time.
5039
+ * - Old state (thumbnail visible): name is on the thumbnail.
5040
+ * - New state (lightbox visible): name moves to the lightbox content.
5041
+ * `flushSync` ensures React commits the DOM change synchronously inside the
5042
+ * `startViewTransition` callback so the API can snapshot old → new correctly.
5043
+ */
5044
+ function useLightbox() {
5045
+ const thumbnailRef = useRef(null);
5046
+ const [open, setOpen] = useState(false);
5047
+ const vtName = useId();
5048
+ return {
5049
+ thumbnailRef,
5050
+ vtName,
5051
+ open,
5052
+ openLightbox: useCallback(() => {
5053
+ const thumb = thumbnailRef.current;
5054
+ const doc = document;
5055
+ if (doc.startViewTransition && thumb) {
5056
+ thumb.style.viewTransitionName = vtName;
5057
+ doc.startViewTransition(() => {
5058
+ thumb.style.viewTransitionName = "";
5059
+ flushSync(() => setOpen(true));
5060
+ });
5061
+ } else setOpen(true);
5062
+ }, [vtName]),
5063
+ closeLightbox: useCallback(() => {
5064
+ const thumb = thumbnailRef.current;
5065
+ const doc = document;
5066
+ if (doc.startViewTransition && thumb) doc.startViewTransition(() => {
5067
+ flushSync(() => setOpen(false));
5068
+ thumb.style.viewTransitionName = vtName;
5069
+ }).finished.then(() => {
5070
+ thumb.style.viewTransitionName = "";
5071
+ }).catch(() => {
5072
+ thumb.style.viewTransitionName = "";
5073
+ });
5074
+ else setOpen(false);
5075
+ }, [vtName])
5076
+ };
5077
+ }
5078
+
5017
5079
  //#endregion
5018
5080
  //#region src/v2/components/chat/CopilotChatAttachmentRenderer.tsx
5019
5081
  const ImageAttachment = memo(function ImageAttachment({ src, className }) {
5020
5082
  const [error, setError] = useState(false);
5083
+ const { thumbnailRef, vtName, open, openLightbox, closeLightbox } = useLightbox();
5021
5084
  if (error) return /* @__PURE__ */ jsx("div", {
5022
5085
  className: cn("cpk:flex cpk:flex-col cpk:items-center cpk:justify-center cpk:rounded-lg cpk:bg-muted cpk:p-4 cpk:text-sm cpk:text-muted-foreground", className),
5023
5086
  children: /* @__PURE__ */ jsx("span", { children: "Failed to load image" })
5024
5087
  });
5025
- return /* @__PURE__ */ jsx("img", {
5088
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("img", {
5089
+ ref: thumbnailRef,
5026
5090
  src,
5027
5091
  alt: "Image attachment",
5028
- className: cn("cpk:max-w-full cpk:h-auto cpk:rounded-lg", className),
5092
+ className: cn("cpk:max-w-[80px] cpk:max-h-[80px] cpk:w-auto cpk:h-auto cpk:rounded-xl cpk:object-cover cpk:cursor-pointer cpk:bg-muted", className),
5093
+ onClick: openLightbox,
5029
5094
  onError: () => setError(true)
5030
- });
5095
+ }), open && /* @__PURE__ */ jsx(Lightbox, {
5096
+ onClose: closeLightbox,
5097
+ children: /* @__PURE__ */ jsx("img", {
5098
+ style: { viewTransitionName: vtName },
5099
+ src,
5100
+ alt: "Image attachment",
5101
+ className: "cpk:max-w-[90vw] cpk:max-h-[90vh] cpk:object-contain cpk:rounded-lg"
5102
+ })
5103
+ })] });
5031
5104
  });
5032
5105
  const AudioAttachment = memo(function AudioAttachment({ src, filename, className }) {
5033
5106
  return /* @__PURE__ */ jsxs("div", {
@@ -5152,15 +5225,15 @@ function CopilotChatUserMessage({ message, onEditMessage, branchIndex, numberOfB
5152
5225
  "data-message-id": message.id,
5153
5226
  ...props,
5154
5227
  children: [
5155
- BoundMessageRenderer,
5156
5228
  mediaParts.length > 0 && /* @__PURE__ */ jsx("div", {
5157
- className: "cpk:flex cpk:flex-col cpk:items-end cpk:gap-2 cpk:mt-2",
5229
+ className: "cpk:flex cpk:flex-row cpk:flex-wrap cpk:justify-end cpk:gap-2 cpk:mb-2",
5158
5230
  children: mediaParts.map((part, index) => /* @__PURE__ */ jsx(CopilotChatAttachmentRenderer, {
5159
5231
  type: part.type,
5160
5232
  source: part.source,
5161
5233
  filename: getFilename(part)
5162
5234
  }, index))
5163
5235
  }),
5236
+ BoundMessageRenderer,
5164
5237
  BoundToolbar
5165
5238
  ]
5166
5239
  });
@@ -5829,6 +5902,7 @@ CopilotChatMessageView.Cursor = function Cursor({ className, ...props }) {
5829
5902
  const CopilotChatAttachmentQueue = ({ attachments, onRemoveAttachment, className }) => {
5830
5903
  if (attachments.length === 0) return null;
5831
5904
  return /* @__PURE__ */ jsx("div", {
5905
+ "data-testid": "copilot-attachment-queue",
5832
5906
  className: cn("cpk:flex cpk:flex-wrap cpk:gap-2 cpk:p-2", className),
5833
5907
  children: attachments.map((attachment) => {
5834
5908
  const isMedia = attachment.type === "image" || attachment.type === "video";
@@ -5863,73 +5937,6 @@ function AttachmentPreview({ attachment }) {
5863
5937
  case "document": return /* @__PURE__ */ jsx(DocumentPreview, { attachment });
5864
5938
  }
5865
5939
  }
5866
- function Lightbox({ onClose, children }) {
5867
- useEffect(() => {
5868
- const handleKey = (e) => {
5869
- if (e.key === "Escape") onClose();
5870
- };
5871
- document.addEventListener("keydown", handleKey);
5872
- return () => document.removeEventListener("keydown", handleKey);
5873
- }, [onClose]);
5874
- if (typeof document === "undefined") return null;
5875
- return createPortal(/* @__PURE__ */ jsxs("div", {
5876
- className: "cpk:fixed cpk:inset-0 cpk:z-[9999] cpk:flex cpk:items-center cpk:justify-center cpk:bg-black/80 cpk:animate-fade-in",
5877
- onClick: onClose,
5878
- children: [/* @__PURE__ */ jsx("button", {
5879
- onClick: onClose,
5880
- className: "cpk:absolute cpk:top-4 cpk:right-4 cpk:text-white cpk:bg-white/10 cpk:hover:bg-white/20 cpk:rounded-full cpk:w-10 cpk:h-10 cpk:flex cpk:items-center cpk:justify-center cpk:cursor-pointer cpk:border-none cpk:z-10",
5881
- "aria-label": "Close preview",
5882
- children: /* @__PURE__ */ jsx(X, { className: "cpk:w-5 cpk:h-5" })
5883
- }), /* @__PURE__ */ jsx("div", {
5884
- onClick: (e) => e.stopPropagation(),
5885
- children
5886
- })]
5887
- }), document.body);
5888
- }
5889
- /**
5890
- * Hook that manages lightbox open/close and uses the View Transition API to
5891
- * morph the thumbnail into fullscreen content.
5892
- *
5893
- * The trick: `view-transition-name` must live on exactly ONE element at a time.
5894
- * - Old state (thumbnail visible): name is on the thumbnail.
5895
- * - New state (lightbox visible): name moves to the lightbox content.
5896
- * `flushSync` ensures React commits the DOM change synchronously inside the
5897
- * `startViewTransition` callback so the API can snapshot old → new correctly.
5898
- */
5899
- function useLightbox() {
5900
- const thumbnailRef = useRef(null);
5901
- const [open, setOpen] = useState(false);
5902
- const vtName = useId();
5903
- return {
5904
- thumbnailRef,
5905
- vtName,
5906
- open,
5907
- openLightbox: useCallback(() => {
5908
- const thumb = thumbnailRef.current;
5909
- const doc = document;
5910
- if (doc.startViewTransition && thumb) {
5911
- thumb.style.viewTransitionName = vtName;
5912
- doc.startViewTransition(() => {
5913
- thumb.style.viewTransitionName = "";
5914
- flushSync(() => setOpen(true));
5915
- });
5916
- } else setOpen(true);
5917
- }, []),
5918
- closeLightbox: useCallback(() => {
5919
- const thumb = thumbnailRef.current;
5920
- const doc = document;
5921
- if (doc.startViewTransition && thumb) doc.startViewTransition(() => {
5922
- flushSync(() => setOpen(false));
5923
- thumb.style.viewTransitionName = vtName;
5924
- }).finished.then(() => {
5925
- thumb.style.viewTransitionName = "";
5926
- }).catch(() => {
5927
- thumb.style.viewTransitionName = "";
5928
- });
5929
- else setOpen(false);
5930
- }, [])
5931
- };
5932
- }
5933
5940
  function ImagePreview({ attachment }) {
5934
5941
  const src = getSourceUrl(attachment.source);
5935
5942
  const { thumbnailRef, vtName, open, openLightbox, closeLightbox } = useLightbox();
@@ -6171,9 +6178,91 @@ function useKeyboardHeight() {
6171
6178
  return keyboardState;
6172
6179
  }
6173
6180
 
6181
+ //#endregion
6182
+ //#region src/v2/components/chat/normalize-auto-scroll.ts
6183
+ const VALID = [
6184
+ "pin-to-bottom",
6185
+ "pin-to-send",
6186
+ "none"
6187
+ ];
6188
+ function normalizeAutoScroll(value) {
6189
+ if (value === void 0) return "pin-to-bottom";
6190
+ if (value === true) return "pin-to-bottom";
6191
+ if (value === false) return "none";
6192
+ if (VALID.includes(value)) return value;
6193
+ return "pin-to-bottom";
6194
+ }
6195
+
6196
+ //#endregion
6197
+ //#region src/v2/components/chat/last-user-message-context.ts
6198
+ const LastUserMessageContext = React.createContext({
6199
+ id: null,
6200
+ sendNonce: 0
6201
+ });
6202
+
6203
+ //#endregion
6204
+ //#region src/v2/hooks/use-pin-to-send.ts
6205
+ function usePinToSend({ scrollRef, contentRef, spacerRef, topOffset = 16 }) {
6206
+ const { id, sendNonce } = useContext(LastUserMessageContext);
6207
+ const lastNonceRef = useRef(-1);
6208
+ const currentSpacerHeightRef = useRef(0);
6209
+ useEffect(() => {
6210
+ if (sendNonce === lastNonceRef.current) return;
6211
+ lastNonceRef.current = sendNonce;
6212
+ if (!id) return;
6213
+ const scrollEl = scrollRef.current;
6214
+ const contentEl = contentRef.current;
6215
+ const spacerEl = spacerRef.current;
6216
+ if (!scrollEl || !contentEl || !spacerEl) return;
6217
+ const escaped = typeof CSS !== "undefined" && CSS.escape ? CSS.escape(id) : id.replace(/[!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g, "\\$&");
6218
+ const targetEl = contentEl.querySelector(`[data-message-id="${escaped}"]`);
6219
+ if (!targetEl) return;
6220
+ const viewportHeight = scrollEl.clientHeight;
6221
+ const userMessageHeight = targetEl.getBoundingClientRect().height;
6222
+ const paddingTop = parseFloat(getComputedStyle(targetEl).paddingTop) || 0;
6223
+ const bubbleHeight = Math.max(0, userMessageHeight - paddingTop);
6224
+ const spacerHeight = Math.max(0, viewportHeight - bubbleHeight - topOffset);
6225
+ spacerEl.style.height = `${spacerHeight}px`;
6226
+ currentSpacerHeightRef.current = spacerHeight;
6227
+ const raf = requestAnimationFrame(() => {
6228
+ const targetTop = computeOffsetTop(targetEl, scrollEl) + paddingTop - topOffset;
6229
+ scrollEl.scrollTo({
6230
+ top: Math.max(0, targetTop),
6231
+ behavior: "smooth"
6232
+ });
6233
+ });
6234
+ const ro = new ResizeObserver(() => {
6235
+ if (!contentEl || !spacerEl || !scrollEl) return;
6236
+ const consumedBelow = contentEl.getBoundingClientRect().height - computeOffsetTop(targetEl, contentEl) - userMessageHeight;
6237
+ const remaining = Math.max(0, spacerHeight - consumedBelow);
6238
+ if (remaining < currentSpacerHeightRef.current) {
6239
+ spacerEl.style.height = `${remaining}px`;
6240
+ currentSpacerHeightRef.current = remaining;
6241
+ }
6242
+ });
6243
+ ro.observe(contentEl);
6244
+ return () => {
6245
+ cancelAnimationFrame(raf);
6246
+ ro.disconnect();
6247
+ };
6248
+ }, [
6249
+ id,
6250
+ sendNonce,
6251
+ scrollRef,
6252
+ contentRef,
6253
+ spacerRef,
6254
+ topOffset
6255
+ ]);
6256
+ }
6257
+ function computeOffsetTop(el, stopAt) {
6258
+ const elRect = el.getBoundingClientRect();
6259
+ const stopRect = stopAt.getBoundingClientRect();
6260
+ return elRect.top - stopRect.top + stopAt.scrollTop;
6261
+ }
6262
+
6174
6263
  //#endregion
6175
6264
  //#region src/v2/components/chat/CopilotChatView.tsx
6176
- const FEATHER_HEIGHT = 96;
6265
+ const SCROLL_BUTTON_OFFSET = 16;
6177
6266
  function DropOverlay() {
6178
6267
  return /* @__PURE__ */ jsx("div", {
6179
6268
  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,15 +6275,18 @@ function DropOverlay() {
6186
6275
  })
6187
6276
  });
6188
6277
  }
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 }) {
6190
- const inputContainerRef = useRef(null);
6278
+ 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 }) {
6279
+ const [inputContainerEl, setInputContainerEl] = useState(null);
6191
6280
  const [inputContainerHeight, setInputContainerHeight] = useState(0);
6192
6281
  const [isResizing, setIsResizing] = useState(false);
6193
6282
  const resizeTimeoutRef = useRef(null);
6194
6283
  const { isKeyboardOpen, keyboardHeight, availableHeight } = useKeyboardHeight();
6195
6284
  useEffect(() => {
6196
- const element = inputContainerRef.current;
6197
- if (!element) return;
6285
+ const element = inputContainerEl;
6286
+ if (!element) {
6287
+ setInputContainerHeight(0);
6288
+ return;
6289
+ }
6198
6290
  const resizeObserver = new ResizeObserver((entries) => {
6199
6291
  for (const entry of entries) {
6200
6292
  const newHeight = entry.contentRect.height;
@@ -6217,7 +6309,7 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
6217
6309
  resizeObserver.disconnect();
6218
6310
  if (resizeTimeoutRef.current) clearTimeout(resizeTimeoutRef.current);
6219
6311
  };
6220
- }, []);
6312
+ }, [inputContainerEl]);
6221
6313
  const BoundMessageView = renderSlot(messageView, CopilotChatMessageView, {
6222
6314
  messages,
6223
6315
  isRunning
@@ -6236,11 +6328,11 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
6236
6328
  onAddFile,
6237
6329
  positioning: "static",
6238
6330
  keyboardHeight: isKeyboardOpen ? keyboardHeight : 0,
6239
- containerRef: inputContainerRef,
6240
6331
  showDisclaimer: true,
6332
+ bottomAnchored: true,
6241
6333
  ...disclaimer !== void 0 ? { disclaimer } : {}
6242
6334
  });
6243
- const hasSuggestions = Array.isArray(suggestions) && suggestions.length > 0;
6335
+ const hasSuggestions = !isConnecting && Array.isArray(suggestions) && suggestions.length > 0;
6244
6336
  const BoundSuggestionView = hasSuggestions ? renderSlot(suggestionView, CopilotChatSuggestionView, {
6245
6337
  suggestions,
6246
6338
  loadingIndexes: suggestionLoadingIndexes,
@@ -6252,7 +6344,8 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
6252
6344
  inputContainerHeight,
6253
6345
  isResizing,
6254
6346
  children: /* @__PURE__ */ jsx("div", {
6255
- style: { paddingBottom: `${hasSuggestions ? 4 : 32}px` },
6347
+ "data-testid": "copilot-scroll-content",
6348
+ style: { paddingBottom: `${inputContainerHeight + (hasSuggestions ? 4 : 32)}px` },
6256
6349
  children: /* @__PURE__ */ jsxs("div", {
6257
6350
  className: "cpk:max-w-3xl cpk:mx-auto",
6258
6351
  children: [BoundMessageView, hasSuggestions ? /* @__PURE__ */ jsx("div", {
@@ -6262,7 +6355,7 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
6262
6355
  })
6263
6356
  })
6264
6357
  });
6265
- if (messages.length === 0 && !(welcomeScreen === false)) {
6358
+ if (messages.length === 0 && !(welcomeScreen === false) && !isConnecting && !hasExplicitThreadId) {
6266
6359
  const BoundInputForWelcome = renderSlot(input, CopilotChatInput_default, {
6267
6360
  onSubmitMessage,
6268
6361
  onStop,
@@ -6326,15 +6419,19 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
6326
6419
  children: [
6327
6420
  dragOver && /* @__PURE__ */ jsx(DropOverlay, {}),
6328
6421
  BoundScrollView,
6329
- /* @__PURE__ */ jsx("div", {
6330
- className: "cpk:max-w-3xl cpk:mx-auto cpk:w-full",
6331
- children: attachments && attachments.length > 0 && /* @__PURE__ */ jsx(CopilotChatAttachmentQueue, {
6332
- attachments,
6333
- onRemoveAttachment: (id) => onRemoveAttachment?.(id),
6334
- className: "cpk:px-4"
6335
- })
6336
- }),
6337
- BoundInput
6422
+ /* @__PURE__ */ jsxs("div", {
6423
+ ref: setInputContainerEl,
6424
+ "data-testid": "copilot-input-overlay",
6425
+ className: "cpk:absolute cpk:bottom-0 cpk:left-0 cpk:right-0 cpk:z-20 cpk:pointer-events-none",
6426
+ children: [attachments && attachments.length > 0 && /* @__PURE__ */ jsx("div", {
6427
+ className: "cpk:max-w-3xl cpk:mx-auto cpk:w-full cpk:pointer-events-auto",
6428
+ children: /* @__PURE__ */ jsx(CopilotChatAttachmentQueue, {
6429
+ attachments,
6430
+ onRemoveAttachment: (id) => onRemoveAttachment?.(id),
6431
+ className: "cpk:px-4"
6432
+ })
6433
+ }), BoundInput]
6434
+ })
6338
6435
  ]
6339
6436
  });
6340
6437
  }
@@ -6363,15 +6460,66 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
6363
6460
  BoundFeather,
6364
6461
  !isAtBottom && !isResizing && /* @__PURE__ */ jsx("div", {
6365
6462
  className: "cpk:absolute cpk:inset-x-0 cpk:flex cpk:justify-center cpk:z-30 cpk:pointer-events-none",
6366
- style: { bottom: `${inputContainerHeight + FEATHER_HEIGHT + 16}px` },
6463
+ style: { bottom: `${inputContainerHeight + SCROLL_BUTTON_OFFSET}px` },
6367
6464
  children: renderSlot(scrollToBottomButton, CopilotChatView.ScrollToBottomButton, { onClick: () => scrollToBottom() })
6368
6465
  })
6369
6466
  ] })
6370
6467
  });
6371
6468
  };
6372
- _CopilotChatView.ScrollView = ({ children, autoScroll = true, scrollToBottomButton, feather, inputContainerHeight = 0, isResizing = false, className, ...props }) => {
6469
+ const PinToSendScrollContainer = ({ children, scrollRef, contentRef, scrollToBottom, scrollToBottomButton, feather, inputContainerHeight, isResizing, nonAutoScrollEl, nonAutoScrollRefCallback, showScrollButton, className, ...props }) => {
6470
+ const spacerRef = useRef(null);
6471
+ usePinToSend({
6472
+ scrollRef,
6473
+ contentRef,
6474
+ spacerRef,
6475
+ topOffset: 16
6476
+ });
6477
+ const BoundFeather = renderSlot(feather, CopilotChatView.Feather, {});
6478
+ return /* @__PURE__ */ jsx(ScrollElementContext.Provider, {
6479
+ value: nonAutoScrollEl,
6480
+ children: /* @__PURE__ */ jsxs("div", {
6481
+ className: cn("cpk:h-full cpk:max-h-full cpk:flex cpk:flex-col cpk:min-h-0 cpk:relative", className),
6482
+ children: [
6483
+ /* @__PURE__ */ jsxs("div", {
6484
+ ref: nonAutoScrollRefCallback,
6485
+ className: "cpk:flex-1 cpk:min-h-0 cpk:overflow-y-auto cpk:overflow-x-hidden",
6486
+ ...props,
6487
+ children: [/* @__PURE__ */ jsx("div", {
6488
+ ref: contentRef,
6489
+ className: "cpk:px-4 cpk:sm:px-0 cpk:[div[data-sidebar-chat]_&]:px-8 cpk:[div[data-popup-chat]_&]:px-6",
6490
+ children
6491
+ }), /* @__PURE__ */ jsx("div", {
6492
+ ref: spacerRef,
6493
+ "data-pin-to-send-spacer": true,
6494
+ "aria-hidden": "true",
6495
+ style: {
6496
+ height: 0,
6497
+ flex: "0 0 auto"
6498
+ }
6499
+ })]
6500
+ }),
6501
+ BoundFeather,
6502
+ showScrollButton && !isResizing && /* @__PURE__ */ jsx("div", {
6503
+ className: "cpk:absolute cpk:inset-x-0 cpk:flex cpk:justify-center cpk:z-30 cpk:pointer-events-none",
6504
+ style: { bottom: `${inputContainerHeight + SCROLL_BUTTON_OFFSET}px` },
6505
+ children: renderSlot(scrollToBottomButton, CopilotChatView.ScrollToBottomButton, { onClick: () => scrollToBottom() })
6506
+ })
6507
+ ]
6508
+ })
6509
+ });
6510
+ };
6511
+ _CopilotChatView.ScrollView = ({ children, autoScroll = "pin-to-bottom", scrollToBottomButton, feather, inputContainerHeight = 0, isResizing = false, className, ...props }) => {
6512
+ const mode = normalizeAutoScroll(autoScroll);
6373
6513
  const [hasMounted, setHasMounted] = useState(false);
6374
- const { scrollRef, contentRef, scrollToBottom } = useStickToBottom();
6514
+ const scrollRef = useRef(null);
6515
+ const contentRef = useRef(null);
6516
+ const scrollToBottom = useCallback(() => {
6517
+ const el = scrollRef.current;
6518
+ if (el) el.scrollTo({
6519
+ top: el.scrollHeight,
6520
+ behavior: "smooth"
6521
+ });
6522
+ }, []);
6375
6523
  const [showScrollButton, setShowScrollButton] = useState(false);
6376
6524
  const [nonAutoScrollEl, setNonAutoScrollEl] = useState(null);
6377
6525
  const nonAutoScrollRefCallback = useCallback((el) => {
@@ -6382,7 +6530,7 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
6382
6530
  setHasMounted(true);
6383
6531
  }, []);
6384
6532
  useEffect(() => {
6385
- if (autoScroll) return;
6533
+ if (mode === "pin-to-bottom") return;
6386
6534
  const scrollElement = scrollRef.current;
6387
6535
  if (!scrollElement) return;
6388
6536
  const checkScroll = () => {
@@ -6396,7 +6544,7 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
6396
6544
  scrollElement.removeEventListener("scroll", checkScroll);
6397
6545
  resizeObserver.disconnect();
6398
6546
  };
6399
- }, [scrollRef, autoScroll]);
6547
+ }, [scrollRef, mode]);
6400
6548
  if (!hasMounted) return /* @__PURE__ */ jsx("div", {
6401
6549
  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
6550
  children: /* @__PURE__ */ jsx("div", {
@@ -6404,7 +6552,7 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
6404
6552
  children
6405
6553
  })
6406
6554
  });
6407
- if (!autoScroll) {
6555
+ if (mode === "none") {
6408
6556
  const BoundFeather = renderSlot(feather, CopilotChatView.Feather, {});
6409
6557
  return /* @__PURE__ */ jsx(ScrollElementContext.Provider, {
6410
6558
  value: nonAutoScrollEl,
@@ -6421,13 +6569,28 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
6421
6569
  BoundFeather,
6422
6570
  showScrollButton && !isResizing && /* @__PURE__ */ jsx("div", {
6423
6571
  className: "cpk:absolute cpk:inset-x-0 cpk:flex cpk:justify-center cpk:z-30 cpk:pointer-events-none",
6424
- style: { bottom: `${inputContainerHeight + FEATHER_HEIGHT + 16}px` },
6572
+ style: { bottom: `${inputContainerHeight + SCROLL_BUTTON_OFFSET}px` },
6425
6573
  children: renderSlot(scrollToBottomButton, CopilotChatView.ScrollToBottomButton, { onClick: () => scrollToBottom() })
6426
6574
  })
6427
6575
  ]
6428
6576
  })
6429
6577
  });
6430
6578
  }
6579
+ if (mode === "pin-to-send") return /* @__PURE__ */ jsx(PinToSendScrollContainer, {
6580
+ scrollRef,
6581
+ contentRef,
6582
+ scrollToBottom,
6583
+ scrollToBottomButton,
6584
+ feather,
6585
+ inputContainerHeight,
6586
+ isResizing,
6587
+ nonAutoScrollEl,
6588
+ nonAutoScrollRefCallback,
6589
+ showScrollButton,
6590
+ className,
6591
+ ...props,
6592
+ children
6593
+ });
6431
6594
  return /* @__PURE__ */ jsx(StickToBottom, {
6432
6595
  className: cn("cpk:flex-1 cpk:max-h-full cpk:flex cpk:flex-col cpk:min-h-0", className),
6433
6596
  resize: "smooth",
@@ -6450,9 +6613,8 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
6450
6613
  ...props,
6451
6614
  children: /* @__PURE__ */ jsx(ChevronDown, { className: "cpk:w-4 cpk:h-4 cpk:text-gray-600 cpk:dark:text-white" })
6452
6615
  });
6453
- _CopilotChatView.Feather = ({ className, style, ...props }) => /* @__PURE__ */ jsx("div", {
6454
- className: cn("cpk:absolute cpk:bottom-0 cpk:left-0 cpk:right-4 cpk:h-24 cpk:pointer-events-none cpk:z-10 cpk:bg-gradient-to-t", "cpk:from-white cpk:via-white cpk:to-transparent", "cpk:dark:from-[rgb(33,33,33)] cpk:dark:via-[rgb(33,33,33)]", className),
6455
- style,
6616
+ _CopilotChatView.Feather = ({ className, ...props }) => /* @__PURE__ */ jsx("div", {
6617
+ className,
6456
6618
  ...props
6457
6619
  });
6458
6620
  _CopilotChatView.WelcomeMessage = ({ className, ...props }) => {
@@ -6620,7 +6782,9 @@ async function transcribeAudio(core, audioBlob, filename = "recording.webm") {
6620
6782
  function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen, attachments: attachmentsConfig, onError, throttleMs, ...props }) {
6621
6783
  const existingConfig = useCopilotChatConfiguration();
6622
6784
  const resolvedAgentId = agentId ?? existingConfig?.agentId ?? DEFAULT_AGENT_ID;
6623
- const resolvedThreadId = useMemo(() => threadId ?? existingConfig?.threadId ?? randomUUID(), [threadId, existingConfig?.threadId]);
6785
+ const providedThreadId = threadId ?? existingConfig?.threadId;
6786
+ const resolvedThreadId = useMemo(() => providedThreadId ?? randomUUID(), [providedThreadId]);
6787
+ const hasExplicitThreadId = !!threadId || !!existingConfig?.hasExplicitThreadId;
6624
6788
  const { agent } = useAgent({
6625
6789
  agentId: resolvedAgentId,
6626
6790
  threadId: resolvedThreadId,
@@ -6658,7 +6822,10 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
6658
6822
  const isTranscriptionEnabled = copilotkit.audioFileTranscriptionEnabled;
6659
6823
  const isMediaRecorderSupported = typeof window !== "undefined" && typeof MediaRecorder !== "undefined";
6660
6824
  const { messageView: providedMessageView, suggestionView: providedSuggestionView, onStop: providedStopHandler, ...restProps } = props;
6825
+ const [lastConnectedThreadId, setLastConnectedThreadId] = useState(null);
6826
+ const isConnecting = hasExplicitThreadId && lastConnectedThreadId !== resolvedThreadId;
6661
6827
  useEffect(() => {
6828
+ if (!hasExplicitThreadId) return;
6662
6829
  let detached = false;
6663
6830
  const connectAbortController = new AbortController();
6664
6831
  if (agent instanceof HttpAgent) agent.abortController = connectAbortController;
@@ -6668,6 +6835,10 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
6668
6835
  } catch (error) {
6669
6836
  if (detached) return;
6670
6837
  console.error("CopilotChat: connectAgent failed", error);
6838
+ } finally {
6839
+ if (!detached) (typeof requestAnimationFrame === "function" ? requestAnimationFrame : (cb) => setTimeout(cb, 16))(() => {
6840
+ if (!detached) setLastConnectedThreadId(resolvedThreadId);
6841
+ });
6671
6842
  }
6672
6843
  };
6673
6844
  connect(agent);
@@ -6679,7 +6850,8 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
6679
6850
  }, [
6680
6851
  resolvedThreadId,
6681
6852
  agent,
6682
- resolvedAgentId
6853
+ resolvedAgentId,
6854
+ hasExplicitThreadId
6683
6855
  ]);
6684
6856
  const onSubmitInput = useCallback(async (value) => {
6685
6857
  if (selectedAttachments.some((a) => a.status === "uploading")) {
@@ -6832,6 +7004,22 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
6832
7004
  const toolCallsKey = "toolCalls" in m && Array.isArray(m.toolCalls) ? m.toolCalls.map((tc) => `${tc.id}:${tc.function?.arguments?.length ?? 0}`).join(";") : "";
6833
7005
  return `${m.id}:${m.role}:${contentKey}:${toolCallsKey}`;
6834
7006
  }).join(",")]);
7007
+ const lastUserMessageId = useMemo(() => {
7008
+ for (let i = messages.length - 1; i >= 0; i--) if (messages[i].role === "user") return messages[i].id;
7009
+ return null;
7010
+ }, [messages]);
7011
+ const [sendNonce, setSendNonce] = useState(0);
7012
+ const prevLastUserMessageIdRef = useRef(lastUserMessageId);
7013
+ useEffect(() => {
7014
+ if (lastUserMessageId && lastUserMessageId !== prevLastUserMessageIdRef.current) {
7015
+ setSendNonce((n) => n + 1);
7016
+ prevLastUserMessageIdRef.current = lastUserMessageId;
7017
+ }
7018
+ }, [lastUserMessageId]);
7019
+ const lastUserMessageState = useMemo(() => ({
7020
+ id: lastUserMessageId,
7021
+ sendNonce
7022
+ }), [lastUserMessageId, sendNonce]);
6835
7023
  const RenderedChatView = renderSlot(chatView, CopilotChatView, {
6836
7024
  ...mergedProps,
6837
7025
  messages,
@@ -6850,11 +7038,14 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
6850
7038
  dragOver,
6851
7039
  onDragOver: handleDragOver,
6852
7040
  onDragLeave: handleDragLeave,
6853
- onDrop: handleDrop
7041
+ onDrop: handleDrop,
7042
+ isConnecting,
7043
+ hasExplicitThreadId
6854
7044
  });
6855
7045
  return /* @__PURE__ */ jsx(CopilotChatConfigurationProvider, {
6856
7046
  agentId: resolvedAgentId,
6857
7047
  threadId: resolvedThreadId,
7048
+ hasExplicitThreadId,
6858
7049
  labels,
6859
7050
  isModalDefaultOpen,
6860
7051
  children: /* @__PURE__ */ jsxs("div", {
@@ -6885,7 +7076,10 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
6885
7076
  },
6886
7077
  children: transcriptionError
6887
7078
  }),
6888
- RenderedChatView
7079
+ /* @__PURE__ */ jsx(LastUserMessageContext.Provider, {
7080
+ value: lastUserMessageState,
7081
+ children: RenderedChatView
7082
+ })
6889
7083
  ]
6890
7084
  })
6891
7085
  });
@@ -8611,12 +8805,19 @@ function useCoAgentStateRenders() {
8611
8805
  //#region src/context/threads-context.tsx
8612
8806
  const ThreadsContext = createContext(void 0);
8613
8807
  function ThreadsProvider({ children, threadId: explicitThreadId }) {
8614
- const [internalThreadId, setThreadId] = useState(() => randomUUID());
8808
+ const [internalThreadId, setInternalThreadId] = useState(() => randomUUID());
8809
+ const [internalIsExplicit, setInternalIsExplicit] = useState(false);
8615
8810
  const threadId = explicitThreadId ?? internalThreadId;
8811
+ const isThreadIdExplicit = explicitThreadId != null || internalIsExplicit;
8812
+ const setThreadId = useCallback((value) => {
8813
+ setInternalThreadId(value);
8814
+ setInternalIsExplicit(true);
8815
+ }, []);
8616
8816
  return /* @__PURE__ */ jsx(ThreadsContext.Provider, {
8617
8817
  value: {
8618
8818
  threadId,
8619
- setThreadId
8819
+ setThreadId,
8820
+ isThreadIdExplicit
8620
8821
  },
8621
8822
  children
8622
8823
  });
@@ -9318,7 +9519,7 @@ function CopilotKitInternal(cpkProps) {
9318
9519
  if (props.agent) setAgentSession({ agentName: props.agent });
9319
9520
  else setAgentSession(null);
9320
9521
  }, [props.agent]);
9321
- const { threadId, setThreadId: setInternalThreadId } = useThreads();
9522
+ const { threadId, setThreadId: setInternalThreadId, isThreadIdExplicit } = useThreads();
9322
9523
  const setThreadId = useCallback((value) => {
9323
9524
  if (props.threadId) throw new Error("Cannot call setThreadId() when threadId is provided via props.");
9324
9525
  setInternalThreadId(value);
@@ -9518,6 +9719,7 @@ function CopilotKitInternal(cpkProps) {
9518
9719
  return /* @__PURE__ */ jsx(CopilotChatConfigurationProvider, {
9519
9720
  agentId: props.agent ?? "default",
9520
9721
  threadId,
9722
+ hasExplicitThreadId: isThreadIdExplicit,
9521
9723
  children: /* @__PURE__ */ jsxs(CopilotContext.Provider, {
9522
9724
  value: copilotContextValue,
9523
9725
  children: [
@@ -9568,4 +9770,4 @@ function validateProps(props) {
9568
9770
 
9569
9771
  //#endregion
9570
9772
  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
9773
+ //# sourceMappingURL=copilotkit-DAatqMh2.mjs.map