@copilotkit/react-core 1.54.1 → 1.55.0-next.8

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 (183) hide show
  1. package/CHANGELOG.md +127 -116
  2. package/dist/copilotkit-B3Mb1yVE.cjs +7975 -0
  3. package/dist/copilotkit-B3Mb1yVE.cjs.map +1 -0
  4. package/dist/copilotkit-DBzgOMby.d.cts +2182 -0
  5. package/dist/copilotkit-DBzgOMby.d.cts.map +1 -0
  6. package/dist/copilotkit-DNYSFuz5.mjs +7562 -0
  7. package/dist/copilotkit-DNYSFuz5.mjs.map +1 -0
  8. package/dist/copilotkit-Dy5w3qEV.d.mts +2182 -0
  9. package/dist/copilotkit-Dy5w3qEV.d.mts.map +1 -0
  10. package/dist/index.cjs +27 -28
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.d.cts +3 -3
  13. package/dist/index.d.cts.map +1 -1
  14. package/dist/index.d.mts +3 -3
  15. package/dist/index.d.mts.map +1 -1
  16. package/dist/index.mjs +4 -5
  17. package/dist/index.mjs.map +1 -1
  18. package/dist/index.umd.js +1941 -35
  19. package/dist/index.umd.js.map +1 -1
  20. package/dist/v2/index.cjs +77 -7
  21. package/dist/v2/index.css +1 -2
  22. package/dist/v2/index.d.cts +6 -4
  23. package/dist/v2/index.d.mts +6 -4
  24. package/dist/v2/index.mjs +7 -4
  25. package/dist/v2/index.umd.js +5725 -24
  26. package/dist/v2/index.umd.js.map +1 -1
  27. package/package.json +37 -9
  28. package/scripts/scope-preflight.mjs +101 -0
  29. package/src/components/CopilotListeners.tsx +2 -6
  30. package/src/components/copilot-provider/copilot-messages.tsx +1 -1
  31. package/src/components/copilot-provider/copilotkit-props.tsx +1 -1
  32. package/src/components/copilot-provider/copilotkit.tsx +4 -4
  33. package/src/context/copilot-messages-context.tsx +1 -1
  34. package/src/hooks/__tests__/use-coagent-config.test.ts +2 -2
  35. package/src/hooks/__tests__/use-coagent-state-render.e2e.test.tsx +2 -2
  36. package/src/hooks/__tests__/use-copilot-chat-internal-connect.test.tsx +3 -7
  37. package/src/hooks/__tests__/use-frontend-tool-available.test.tsx +1 -1
  38. package/src/hooks/__tests__/use-frontend-tool-remount.e2e.test.tsx +4 -4
  39. package/src/hooks/use-agent-nodename.ts +1 -1
  40. package/src/hooks/use-coagent-state-render-bridge.tsx +1 -4
  41. package/src/hooks/use-coagent.ts +1 -1
  42. package/src/hooks/use-configure-chat-suggestions.tsx +2 -2
  43. package/src/hooks/use-copilot-chat-suggestions.tsx +2 -2
  44. package/src/hooks/use-copilot-chat_internal.ts +2 -2
  45. package/src/hooks/use-copilot-readable.ts +1 -1
  46. package/src/hooks/use-frontend-tool.ts +2 -2
  47. package/src/hooks/use-human-in-the-loop.ts +2 -2
  48. package/src/hooks/use-langgraph-interrupt.ts +2 -5
  49. package/src/hooks/use-lazy-tool-renderer.tsx +1 -1
  50. package/src/hooks/use-render-tool-call.ts +1 -1
  51. package/src/lib/copilot-task.ts +1 -1
  52. package/src/setupTests.ts +18 -14
  53. package/src/v2/__tests__/A2UIMessageRenderer.test.tsx +176 -0
  54. package/src/v2/__tests__/globalSetup.ts +14 -0
  55. package/src/v2/__tests__/setup.ts +93 -0
  56. package/src/v2/__tests__/utils/test-helpers.tsx +470 -0
  57. package/src/v2/a2ui/A2UIMessageRenderer.tsx +206 -0
  58. package/src/v2/components/CopilotKitInspector.tsx +50 -0
  59. package/src/v2/components/MCPAppsActivityRenderer.tsx +785 -0
  60. package/src/v2/components/WildcardToolCallRender.tsx +86 -0
  61. package/src/v2/components/__tests__/license-warning-banner.test.tsx +46 -0
  62. package/src/v2/components/chat/CopilotChat.tsx +431 -0
  63. package/src/v2/components/chat/CopilotChatAssistantMessage.tsx +375 -0
  64. package/src/v2/components/chat/CopilotChatAudioRecorder.tsx +350 -0
  65. package/src/v2/components/chat/CopilotChatInput.tsx +1302 -0
  66. package/src/v2/components/chat/CopilotChatMessageView.tsx +556 -0
  67. package/src/v2/components/chat/CopilotChatReasoningMessage.tsx +252 -0
  68. package/src/v2/components/chat/CopilotChatSuggestionPill.tsx +59 -0
  69. package/src/v2/components/chat/CopilotChatSuggestionView.tsx +133 -0
  70. package/src/v2/components/chat/CopilotChatToggleButton.tsx +171 -0
  71. package/src/v2/components/chat/CopilotChatToolCallsView.tsx +40 -0
  72. package/src/v2/components/chat/CopilotChatUserMessage.tsx +388 -0
  73. package/src/v2/components/chat/CopilotChatView.tsx +598 -0
  74. package/src/v2/components/chat/CopilotModalHeader.tsx +129 -0
  75. package/src/v2/components/chat/CopilotPopup.tsx +81 -0
  76. package/src/v2/components/chat/CopilotPopupView.tsx +317 -0
  77. package/src/v2/components/chat/CopilotSidebar.tsx +76 -0
  78. package/src/v2/components/chat/CopilotSidebarView.tsx +255 -0
  79. package/src/v2/components/chat/__tests__/CopilotChat.e2e.test.tsx +1113 -0
  80. package/src/v2/components/chat/__tests__/CopilotChat.onError.test.tsx +73 -0
  81. package/src/v2/components/chat/__tests__/CopilotChat.slots.e2e.test.tsx +432 -0
  82. package/src/v2/components/chat/__tests__/CopilotChatActivityRendering.e2e.test.tsx +150 -0
  83. package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.slots.e2e.test.tsx +624 -0
  84. package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.test.tsx +702 -0
  85. package/src/v2/components/chat/__tests__/CopilotChatCssClasses.test.tsx +107 -0
  86. package/src/v2/components/chat/__tests__/CopilotChatInput.slots.e2e.test.tsx +929 -0
  87. package/src/v2/components/chat/__tests__/CopilotChatInput.test.tsx +986 -0
  88. package/src/v2/components/chat/__tests__/CopilotChatMessageView.slots.e2e.test.tsx +1004 -0
  89. package/src/v2/components/chat/__tests__/CopilotChatMessageView.test.tsx +169 -0
  90. package/src/v2/components/chat/__tests__/CopilotChatSuggestionView.slots.e2e.test.tsx +530 -0
  91. package/src/v2/components/chat/__tests__/CopilotChatToolRendering.e2e.test.tsx +782 -0
  92. package/src/v2/components/chat/__tests__/CopilotChatToolRerenders.e2e.test.tsx +2413 -0
  93. package/src/v2/components/chat/__tests__/CopilotChatUserMessage.slots.e2e.test.tsx +621 -0
  94. package/src/v2/components/chat/__tests__/CopilotChatView.onClick.e2e.test.tsx +853 -0
  95. package/src/v2/components/chat/__tests__/CopilotChatView.slots.e2e.test.tsx +1050 -0
  96. package/src/v2/components/chat/__tests__/CopilotModalHeader.slots.e2e.test.tsx +484 -0
  97. package/src/v2/components/chat/__tests__/CopilotPopupView.slots.e2e.test.tsx +612 -0
  98. package/src/v2/components/chat/__tests__/CopilotSidebarView.slots.e2e.test.tsx +502 -0
  99. package/src/v2/components/chat/__tests__/MCPAppsActivityRenderer.e2e.test.tsx +1011 -0
  100. package/src/v2/components/chat/__tests__/setup.ts +1 -0
  101. package/src/v2/components/chat/index.ts +79 -0
  102. package/src/v2/components/index.ts +7 -0
  103. package/src/v2/components/license-warning-banner.tsx +198 -0
  104. package/src/v2/components/ui/button.tsx +123 -0
  105. package/src/v2/components/ui/dropdown-menu.tsx +258 -0
  106. package/src/v2/components/ui/tooltip.tsx +60 -0
  107. package/src/v2/hooks/__tests__/standard-schema-types.test.tsx +152 -0
  108. package/src/v2/hooks/__tests__/standard-schema.test.tsx +282 -0
  109. package/src/v2/hooks/__tests__/use-agent-context-timing.e2e.test.tsx +132 -0
  110. package/src/v2/hooks/__tests__/use-agent-context.test.tsx +401 -0
  111. package/src/v2/hooks/__tests__/use-agent-error-state.test.tsx +44 -0
  112. package/src/v2/hooks/__tests__/use-agent-stability.test.tsx +205 -0
  113. package/src/v2/hooks/__tests__/use-agent.e2e.test.tsx +148 -0
  114. package/src/v2/hooks/__tests__/use-component.test.tsx +123 -0
  115. package/src/v2/hooks/__tests__/use-configure-suggestions.e2e.test.tsx +696 -0
  116. package/src/v2/hooks/__tests__/use-default-render-tool.test.tsx +153 -0
  117. package/src/v2/hooks/__tests__/use-frontend-tool-available.test.tsx +167 -0
  118. package/src/v2/hooks/__tests__/use-frontend-tool.e2e.test.tsx +2129 -0
  119. package/src/v2/hooks/__tests__/use-human-in-the-loop.e2e.test.tsx +1261 -0
  120. package/src/v2/hooks/__tests__/use-interrupt.test.tsx +397 -0
  121. package/src/v2/hooks/__tests__/use-katex-styles.test.tsx +56 -0
  122. package/src/v2/hooks/__tests__/use-keyboard-height.test.tsx +192 -0
  123. package/src/v2/hooks/__tests__/use-render-tool.test.tsx +259 -0
  124. package/src/v2/hooks/__tests__/use-suggestions.e2e.test.tsx +524 -0
  125. package/src/v2/hooks/__tests__/use-threads.test.tsx +433 -0
  126. package/src/v2/hooks/__tests__/zod-regression.test.tsx +311 -0
  127. package/src/v2/hooks/index.ts +18 -0
  128. package/src/v2/hooks/use-agent-context.tsx +45 -0
  129. package/src/v2/hooks/use-agent.tsx +155 -0
  130. package/src/v2/hooks/use-component.tsx +89 -0
  131. package/src/v2/hooks/use-configure-suggestions.tsx +187 -0
  132. package/src/v2/hooks/use-default-render-tool.tsx +254 -0
  133. package/src/v2/hooks/use-frontend-tool.tsx +43 -0
  134. package/src/v2/hooks/use-human-in-the-loop.tsx +81 -0
  135. package/src/v2/hooks/use-interrupt.tsx +305 -0
  136. package/src/v2/hooks/use-keyboard-height.tsx +67 -0
  137. package/src/v2/hooks/use-render-activity-message.tsx +73 -0
  138. package/src/v2/hooks/use-render-custom-messages.tsx +93 -0
  139. package/src/v2/hooks/use-render-tool-call.tsx +175 -0
  140. package/src/v2/hooks/use-render-tool.tsx +181 -0
  141. package/src/v2/hooks/use-suggestions.tsx +91 -0
  142. package/src/v2/hooks/use-threads.tsx +256 -0
  143. package/src/v2/hooks/useKatexStyles.ts +27 -0
  144. package/src/v2/index.css +1 -1
  145. package/src/v2/index.ts +18 -2
  146. package/src/v2/lib/__tests__/completePartialMarkdown.test.ts +495 -0
  147. package/src/v2/lib/__tests__/renderSlot.test.tsx +588 -0
  148. package/src/v2/lib/react-core.ts +156 -0
  149. package/src/v2/lib/slots.tsx +143 -0
  150. package/src/v2/lib/transcription-client.ts +184 -0
  151. package/src/v2/lib/utils.ts +8 -0
  152. package/src/v2/providers/CopilotChatConfigurationProvider.tsx +162 -0
  153. package/src/v2/providers/CopilotKitProvider.tsx +600 -0
  154. package/src/v2/providers/__tests__/CopilotChatConfigurationProvider.test.tsx +546 -0
  155. package/src/v2/providers/__tests__/CopilotKitProvider.license.test.tsx +101 -0
  156. package/src/v2/providers/__tests__/CopilotKitProvider.onError.test.tsx +69 -0
  157. package/src/v2/providers/__tests__/CopilotKitProvider.renderCustomMessages.e2e.test.tsx +881 -0
  158. package/src/v2/providers/__tests__/CopilotKitProvider.stability.test.tsx +740 -0
  159. package/src/v2/providers/__tests__/CopilotKitProvider.test.tsx +642 -0
  160. package/src/v2/providers/__tests__/CopilotKitProvider.wildcard.test.tsx +294 -0
  161. package/src/v2/providers/index.ts +14 -0
  162. package/src/v2/styles/globals.css +230 -0
  163. package/src/v2/types/__tests__/defineToolCallRenderer.test.tsx +525 -0
  164. package/src/v2/types/defineToolCallRenderer.ts +65 -0
  165. package/src/v2/types/frontend-tool.ts +8 -0
  166. package/src/v2/types/human-in-the-loop.ts +33 -0
  167. package/src/v2/types/index.ts +7 -0
  168. package/src/v2/types/interrupt.ts +15 -0
  169. package/src/v2/types/react-activity-message-renderer.ts +27 -0
  170. package/src/v2/types/react-custom-message-renderer.ts +17 -0
  171. package/src/v2/types/react-tool-call-renderer.ts +32 -0
  172. package/tsdown.config.ts +34 -10
  173. package/vitest.config.mjs +4 -3
  174. package/LICENSE +0 -21
  175. package/dist/copilotkit-BRPQ2sqS.d.cts +0 -670
  176. package/dist/copilotkit-BRPQ2sqS.d.cts.map +0 -1
  177. package/dist/copilotkit-C94ayZbs.cjs +0 -2161
  178. package/dist/copilotkit-C94ayZbs.cjs.map +0 -1
  179. package/dist/copilotkit-CwZMFmSK.d.mts +0 -670
  180. package/dist/copilotkit-CwZMFmSK.d.mts.map +0 -1
  181. package/dist/copilotkit-Yh_Ld_FX.mjs +0 -2031
  182. package/dist/copilotkit-Yh_Ld_FX.mjs.map +0 -1
  183. package/dist/v2/index.css.map +0 -1
@@ -0,0 +1,598 @@
1
+ import React, { useRef, useState, useEffect } from "react";
2
+ import { WithSlots, SlotValue, renderSlot } from "../../lib/slots";
3
+ import CopilotChatMessageView from "./CopilotChatMessageView";
4
+ import CopilotChatInput, {
5
+ CopilotChatInputProps,
6
+ CopilotChatInputMode,
7
+ } from "./CopilotChatInput";
8
+ import CopilotChatSuggestionView, {
9
+ CopilotChatSuggestionViewProps,
10
+ } from "./CopilotChatSuggestionView";
11
+ import { Suggestion } from "@copilotkit/core";
12
+ import { Message } from "@ag-ui/core";
13
+ import { twMerge } from "tailwind-merge";
14
+ import {
15
+ StickToBottom,
16
+ useStickToBottom,
17
+ useStickToBottomContext,
18
+ } from "use-stick-to-bottom";
19
+ import { ChevronDown } from "lucide-react";
20
+ import { Button } from "../../components/ui/button";
21
+ import { cn } from "../../lib/utils";
22
+ import {
23
+ useCopilotChatConfiguration,
24
+ CopilotChatDefaultLabels,
25
+ } from "../../providers/CopilotChatConfigurationProvider";
26
+ import { useKeyboardHeight } from "../../hooks/use-keyboard-height";
27
+
28
+ // Height of the feather gradient overlay (h-24 = 6rem = 96px)
29
+ const FEATHER_HEIGHT = 96;
30
+
31
+ // Forward declaration for WelcomeScreen component type
32
+ export type WelcomeScreenProps = WithSlots<
33
+ {
34
+ welcomeMessage: React.FC<React.HTMLAttributes<HTMLDivElement>>;
35
+ },
36
+ {
37
+ input: React.ReactElement;
38
+ suggestionView: React.ReactElement;
39
+ } & React.HTMLAttributes<HTMLDivElement>
40
+ >;
41
+
42
+ export type CopilotChatViewProps = WithSlots<
43
+ {
44
+ messageView: typeof CopilotChatMessageView;
45
+ scrollView: typeof CopilotChatView.ScrollView;
46
+ input: typeof CopilotChatInput;
47
+ suggestionView: typeof CopilotChatSuggestionView;
48
+ },
49
+ {
50
+ messages?: Message[];
51
+ autoScroll?: boolean;
52
+ isRunning?: boolean;
53
+ suggestions?: Suggestion[];
54
+ suggestionLoadingIndexes?: ReadonlyArray<number>;
55
+ onSelectSuggestion?: (suggestion: Suggestion, index: number) => void;
56
+ welcomeScreen?: SlotValue<React.FC<WelcomeScreenProps>> | boolean;
57
+ // Input behavior props
58
+ onSubmitMessage?: (value: string) => void;
59
+ onStop?: () => void;
60
+ inputMode?: CopilotChatInputMode;
61
+ inputValue?: string;
62
+ onInputChange?: (value: string) => void;
63
+ onStartTranscribe?: () => void;
64
+ onCancelTranscribe?: () => void;
65
+ onFinishTranscribe?: () => void;
66
+ onFinishTranscribeWithAudio?: (audioBlob: Blob) => Promise<void>;
67
+ /**
68
+ * @deprecated Use the `input` slot's `disclaimer` prop instead:
69
+ * ```tsx
70
+ * <CopilotChat input={{ disclaimer: MyDisclaimer }} />
71
+ * ```
72
+ */
73
+ disclaimer?: SlotValue<React.FC<React.HTMLAttributes<HTMLDivElement>>>;
74
+ } & React.HTMLAttributes<HTMLDivElement>
75
+ >;
76
+
77
+ export function CopilotChatView({
78
+ messageView,
79
+ input,
80
+ scrollView,
81
+ suggestionView,
82
+ welcomeScreen,
83
+ messages = [],
84
+ autoScroll = true,
85
+ isRunning = false,
86
+ suggestions,
87
+ suggestionLoadingIndexes,
88
+ onSelectSuggestion,
89
+ // Input behavior props
90
+ onSubmitMessage,
91
+ onStop,
92
+ inputMode,
93
+ inputValue,
94
+ onInputChange,
95
+ onStartTranscribe,
96
+ onCancelTranscribe,
97
+ onFinishTranscribe,
98
+ onFinishTranscribeWithAudio,
99
+ // Deprecated — forwarded to input slot
100
+ disclaimer,
101
+ children,
102
+ className,
103
+ ...props
104
+ }: CopilotChatViewProps) {
105
+ const inputContainerRef = useRef<HTMLDivElement>(null);
106
+ const [inputContainerHeight, setInputContainerHeight] = useState(0);
107
+ const [isResizing, setIsResizing] = useState(false);
108
+ const resizeTimeoutRef = useRef<NodeJS.Timeout | null>(null);
109
+
110
+ // Track keyboard state for mobile
111
+ const { isKeyboardOpen, keyboardHeight, availableHeight } =
112
+ useKeyboardHeight();
113
+
114
+ // Track input container height changes
115
+ useEffect(() => {
116
+ const element = inputContainerRef.current;
117
+ if (!element) return;
118
+
119
+ const resizeObserver = new ResizeObserver((entries) => {
120
+ for (const entry of entries) {
121
+ const newHeight = entry.contentRect.height;
122
+
123
+ // Update height and set resizing state
124
+ setInputContainerHeight((prevHeight) => {
125
+ if (newHeight !== prevHeight) {
126
+ setIsResizing(true);
127
+
128
+ // Clear existing timeout
129
+ if (resizeTimeoutRef.current) {
130
+ clearTimeout(resizeTimeoutRef.current);
131
+ }
132
+
133
+ // Set isResizing to false after a short delay
134
+ resizeTimeoutRef.current = setTimeout(() => {
135
+ setIsResizing(false);
136
+ }, 250);
137
+
138
+ return newHeight;
139
+ }
140
+ return prevHeight;
141
+ });
142
+ }
143
+ });
144
+
145
+ resizeObserver.observe(element);
146
+
147
+ // Set initial height
148
+ setInputContainerHeight(element.offsetHeight);
149
+
150
+ return () => {
151
+ resizeObserver.disconnect();
152
+ if (resizeTimeoutRef.current) {
153
+ clearTimeout(resizeTimeoutRef.current);
154
+ }
155
+ };
156
+ }, []);
157
+
158
+ const BoundMessageView = renderSlot(messageView, CopilotChatMessageView, {
159
+ messages,
160
+ isRunning,
161
+ });
162
+
163
+ const BoundInput = renderSlot(input, CopilotChatInput, {
164
+ onSubmitMessage,
165
+ onStop,
166
+ mode: inputMode,
167
+ value: inputValue,
168
+ onChange: onInputChange,
169
+ isRunning,
170
+ onStartTranscribe,
171
+ onCancelTranscribe,
172
+ onFinishTranscribe,
173
+ onFinishTranscribeWithAudio,
174
+ positioning: "static",
175
+ keyboardHeight: isKeyboardOpen ? keyboardHeight : 0,
176
+ containerRef: inputContainerRef,
177
+ showDisclaimer: true,
178
+ ...(disclaimer !== undefined ? { disclaimer } : {}),
179
+ } as CopilotChatInputProps);
180
+
181
+ const hasSuggestions = Array.isArray(suggestions) && suggestions.length > 0;
182
+ const BoundSuggestionView = hasSuggestions
183
+ ? renderSlot(suggestionView, CopilotChatSuggestionView, {
184
+ suggestions,
185
+ loadingIndexes: suggestionLoadingIndexes,
186
+ onSelectSuggestion,
187
+ className: "cpk:mb-3 cpk:lg:ml-4 cpk:lg:mr-4 cpk:ml-0 cpk:mr-0",
188
+ })
189
+ : null;
190
+
191
+ const BoundScrollView = renderSlot(scrollView, CopilotChatView.ScrollView, {
192
+ autoScroll,
193
+ inputContainerHeight,
194
+ isResizing,
195
+ children: (
196
+ <div
197
+ style={{
198
+ paddingBottom: `${hasSuggestions ? 4 : 32}px`,
199
+ }}
200
+ >
201
+ <div className="cpk:max-w-3xl cpk:mx-auto">
202
+ {BoundMessageView}
203
+ {hasSuggestions ? (
204
+ <div className="cpk:pl-0 cpk:pr-4 cpk:sm:px-0 cpk:mt-4">
205
+ {BoundSuggestionView}
206
+ </div>
207
+ ) : null}
208
+ </div>
209
+ </div>
210
+ ),
211
+ });
212
+
213
+ // Welcome screen logic
214
+ const isEmpty = messages.length === 0;
215
+ // Type assertion needed because TypeScript doesn't fully propagate `| boolean` through WithSlots
216
+ const welcomeScreenDisabled = (welcomeScreen as unknown) === false;
217
+ const shouldShowWelcomeScreen = isEmpty && !welcomeScreenDisabled;
218
+
219
+ if (shouldShowWelcomeScreen) {
220
+ // Create a separate input for welcome screen with static positioning and disclaimer visible
221
+ const BoundInputForWelcome = renderSlot(input, CopilotChatInput, {
222
+ onSubmitMessage,
223
+ onStop,
224
+ mode: inputMode,
225
+ value: inputValue,
226
+ onChange: onInputChange,
227
+ isRunning,
228
+ onStartTranscribe,
229
+ onCancelTranscribe,
230
+ onFinishTranscribe,
231
+ onFinishTranscribeWithAudio,
232
+ positioning: "static",
233
+ showDisclaimer: true,
234
+ ...(disclaimer !== undefined ? { disclaimer } : {}),
235
+ } as CopilotChatInputProps);
236
+
237
+ // Convert boolean `true` to undefined (use default), and exclude `false` since we've checked for it
238
+ const welcomeScreenSlot = (
239
+ welcomeScreen === true ? undefined : welcomeScreen
240
+ ) as SlotValue<React.FC<WelcomeScreenProps>> | undefined;
241
+ const BoundWelcomeScreen = renderSlot(
242
+ welcomeScreenSlot,
243
+ CopilotChatView.WelcomeScreen,
244
+ {
245
+ input: BoundInputForWelcome,
246
+ suggestionView: BoundSuggestionView ?? <></>,
247
+ },
248
+ );
249
+
250
+ return (
251
+ <div
252
+ data-copilotkit
253
+ data-testid="copilot-chat"
254
+ data-copilot-running={isRunning ? "true" : "false"}
255
+ className={twMerge(
256
+ "copilotKitChat cpk:relative cpk:h-full cpk:flex cpk:flex-col",
257
+ className,
258
+ )}
259
+ {...props}
260
+ >
261
+ {BoundWelcomeScreen}
262
+ </div>
263
+ );
264
+ }
265
+
266
+ if (children) {
267
+ return (
268
+ <div data-copilotkit style={{ display: "contents" }}>
269
+ {children({
270
+ messageView: BoundMessageView,
271
+ input: BoundInput,
272
+ scrollView: BoundScrollView,
273
+ suggestionView: BoundSuggestionView ?? <></>,
274
+ })}
275
+ </div>
276
+ );
277
+ }
278
+
279
+ return (
280
+ <div
281
+ data-copilotkit
282
+ data-testid="copilot-chat"
283
+ data-copilot-running={isRunning ? "true" : "false"}
284
+ className={twMerge(
285
+ "copilotKitChat cpk:relative cpk:h-full cpk:flex cpk:flex-col",
286
+ className,
287
+ )}
288
+ {...props}
289
+ >
290
+ {BoundScrollView}
291
+
292
+ {BoundInput}
293
+ </div>
294
+ );
295
+ }
296
+
297
+ export namespace CopilotChatView {
298
+ // Inner component that has access to StickToBottom context
299
+ const ScrollContent: React.FC<{
300
+ children: React.ReactNode;
301
+ scrollToBottomButton?: SlotValue<
302
+ React.FC<React.ButtonHTMLAttributes<HTMLButtonElement>>
303
+ >;
304
+ feather?: SlotValue<React.FC<React.HTMLAttributes<HTMLDivElement>>>;
305
+ inputContainerHeight: number;
306
+ isResizing: boolean;
307
+ }> = ({
308
+ children,
309
+ scrollToBottomButton,
310
+ feather,
311
+ inputContainerHeight,
312
+ isResizing,
313
+ }) => {
314
+ const { isAtBottom, scrollToBottom } = useStickToBottomContext();
315
+
316
+ const BoundFeather = renderSlot(feather, CopilotChatView.Feather, {});
317
+
318
+ return (
319
+ <>
320
+ <StickToBottom.Content
321
+ className="cpk:overflow-y-auto cpk:overflow-x-hidden"
322
+ style={{ flex: "1 1 0%", minHeight: 0 }}
323
+ >
324
+ <div className="cpk:px-4 cpk:sm:px-0 cpk:[div[data-sidebar-chat]_&]:px-8 cpk:[div[data-popup-chat]_&]:px-6">
325
+ {children}
326
+ </div>
327
+ </StickToBottom.Content>
328
+
329
+ {/* Feather gradient overlay */}
330
+ {BoundFeather}
331
+
332
+ {/* Scroll to bottom button - hidden during resize */}
333
+ {!isAtBottom && !isResizing && (
334
+ <div
335
+ className="cpk:absolute cpk:inset-x-0 cpk:flex cpk:justify-center cpk:z-30 cpk:pointer-events-none"
336
+ style={{
337
+ bottom: `${inputContainerHeight + FEATHER_HEIGHT + 16}px`,
338
+ }}
339
+ >
340
+ {renderSlot(
341
+ scrollToBottomButton,
342
+ CopilotChatView.ScrollToBottomButton,
343
+ {
344
+ onClick: () => scrollToBottom(),
345
+ },
346
+ )}
347
+ </div>
348
+ )}
349
+ </>
350
+ );
351
+ };
352
+
353
+ export const ScrollView: React.FC<
354
+ React.HTMLAttributes<HTMLDivElement> & {
355
+ autoScroll?: boolean;
356
+ scrollToBottomButton?: SlotValue<
357
+ React.FC<React.ButtonHTMLAttributes<HTMLButtonElement>>
358
+ >;
359
+ feather?: SlotValue<React.FC<React.HTMLAttributes<HTMLDivElement>>>;
360
+ inputContainerHeight?: number;
361
+ isResizing?: boolean;
362
+ }
363
+ > = ({
364
+ children,
365
+ autoScroll = true,
366
+ scrollToBottomButton,
367
+ feather,
368
+ inputContainerHeight = 0,
369
+ isResizing = false,
370
+ className,
371
+ ...props
372
+ }) => {
373
+ const [hasMounted, setHasMounted] = useState(false);
374
+ const { scrollRef, contentRef, scrollToBottom } = useStickToBottom();
375
+ const [showScrollButton, setShowScrollButton] = useState(false);
376
+
377
+ useEffect(() => {
378
+ setHasMounted(true);
379
+ }, []);
380
+
381
+ // Monitor scroll position for non-autoscroll mode
382
+ useEffect(() => {
383
+ if (autoScroll) return; // Skip for autoscroll mode
384
+
385
+ const scrollElement = scrollRef.current;
386
+ if (!scrollElement) return;
387
+
388
+ const checkScroll = () => {
389
+ const atBottom =
390
+ scrollElement.scrollHeight -
391
+ scrollElement.scrollTop -
392
+ scrollElement.clientHeight <
393
+ 10;
394
+ setShowScrollButton(!atBottom);
395
+ };
396
+
397
+ checkScroll();
398
+ scrollElement.addEventListener("scroll", checkScroll);
399
+
400
+ // Also check on resize
401
+ const resizeObserver = new ResizeObserver(checkScroll);
402
+ resizeObserver.observe(scrollElement);
403
+
404
+ return () => {
405
+ scrollElement.removeEventListener("scroll", checkScroll);
406
+ resizeObserver.disconnect();
407
+ };
408
+ }, [scrollRef, autoScroll]);
409
+
410
+ if (!hasMounted) {
411
+ return (
412
+ <div 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">
413
+ <div className="cpk:px-4 cpk:sm:px-0 cpk:[div[data-sidebar-chat]_&]:px-8 cpk:[div[data-popup-chat]_&]:px-6">
414
+ {children}
415
+ </div>
416
+ </div>
417
+ );
418
+ }
419
+
420
+ // When autoScroll is false, we don't use StickToBottom
421
+ if (!autoScroll) {
422
+ const BoundFeather = renderSlot(feather, CopilotChatView.Feather, {});
423
+
424
+ return (
425
+ <div
426
+ ref={scrollRef}
427
+ className={cn(
428
+ "cpk:h-full cpk:max-h-full cpk:flex cpk:flex-col cpk:min-h-0 cpk:overflow-y-auto cpk:overflow-x-hidden cpk:relative",
429
+ className,
430
+ )}
431
+ {...props}
432
+ >
433
+ <div
434
+ ref={contentRef}
435
+ className="cpk:px-4 cpk:sm:px-0 cpk:[div[data-sidebar-chat]_&]:px-8 cpk:[div[data-popup-chat]_&]:px-6"
436
+ >
437
+ {children}
438
+ </div>
439
+
440
+ {/* Feather gradient overlay */}
441
+ {BoundFeather}
442
+
443
+ {/* Scroll to bottom button for manual mode */}
444
+ {showScrollButton && !isResizing && (
445
+ <div
446
+ className="cpk:absolute cpk:inset-x-0 cpk:flex cpk:justify-center cpk:z-30 cpk:pointer-events-none"
447
+ style={{
448
+ bottom: `${inputContainerHeight + FEATHER_HEIGHT + 16}px`,
449
+ }}
450
+ >
451
+ {renderSlot(
452
+ scrollToBottomButton,
453
+ CopilotChatView.ScrollToBottomButton,
454
+ {
455
+ onClick: () => scrollToBottom(),
456
+ },
457
+ )}
458
+ </div>
459
+ )}
460
+ </div>
461
+ );
462
+ }
463
+
464
+ return (
465
+ <StickToBottom
466
+ className={cn(
467
+ "cpk:flex-1 cpk:max-h-full cpk:flex cpk:flex-col cpk:min-h-0",
468
+ className,
469
+ )}
470
+ resize="smooth"
471
+ initial="smooth"
472
+ {...props}
473
+ >
474
+ <ScrollContent
475
+ scrollToBottomButton={scrollToBottomButton}
476
+ feather={feather}
477
+ inputContainerHeight={inputContainerHeight}
478
+ isResizing={isResizing}
479
+ >
480
+ {children}
481
+ </ScrollContent>
482
+ </StickToBottom>
483
+ );
484
+ };
485
+
486
+ export const ScrollToBottomButton: React.FC<
487
+ React.ButtonHTMLAttributes<HTMLButtonElement>
488
+ > = ({ className, ...props }) => (
489
+ <Button
490
+ data-testid="copilot-scroll-to-bottom"
491
+ variant="outline"
492
+ size="sm"
493
+ className={twMerge(
494
+ "cpk:rounded-full cpk:w-10 cpk:h-10 cpk:p-0 cpk:pointer-events-auto",
495
+ "cpk:bg-white cpk:dark:bg-gray-900",
496
+ "cpk:shadow-lg cpk:border cpk:border-gray-200 cpk:dark:border-gray-700",
497
+ "cpk:hover:bg-gray-50 cpk:dark:hover:bg-gray-800",
498
+ "cpk:flex cpk:items-center cpk:justify-center cpk:cursor-pointer",
499
+ className,
500
+ )}
501
+ {...props}
502
+ >
503
+ <ChevronDown className="cpk:w-4 cpk:h-4 cpk:text-gray-600 cpk:dark:text-white" />
504
+ </Button>
505
+ );
506
+
507
+ export const Feather: React.FC<React.HTMLAttributes<HTMLDivElement>> = ({
508
+ className,
509
+ style,
510
+ ...props
511
+ }) => (
512
+ <div
513
+ className={cn(
514
+ "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",
515
+ "cpk:from-white cpk:via-white cpk:to-transparent",
516
+ "cpk:dark:from-[rgb(33,33,33)] cpk:dark:via-[rgb(33,33,33)]",
517
+ className,
518
+ )}
519
+ style={style}
520
+ {...props}
521
+ />
522
+ );
523
+
524
+ export const WelcomeMessage: React.FC<
525
+ React.HTMLAttributes<HTMLDivElement>
526
+ > = ({ className, ...props }) => {
527
+ const config = useCopilotChatConfiguration();
528
+ const labels = config?.labels ?? CopilotChatDefaultLabels;
529
+
530
+ return (
531
+ <h1
532
+ className={cn(
533
+ "cpk:text-xl cpk:sm:text-2xl cpk:font-medium cpk:text-foreground cpk:text-center",
534
+ className,
535
+ )}
536
+ {...props}
537
+ >
538
+ {labels.welcomeMessageText}
539
+ </h1>
540
+ );
541
+ };
542
+
543
+ export const WelcomeScreen: React.FC<WelcomeScreenProps> = ({
544
+ welcomeMessage,
545
+ input,
546
+ suggestionView,
547
+ className,
548
+ children,
549
+ ...props
550
+ }) => {
551
+ // Render the welcomeMessage slot internally
552
+ const BoundWelcomeMessage = renderSlot(
553
+ welcomeMessage,
554
+ CopilotChatView.WelcomeMessage,
555
+ {},
556
+ );
557
+
558
+ if (children) {
559
+ return (
560
+ <div data-copilotkit style={{ display: "contents" }}>
561
+ {children({
562
+ welcomeMessage: BoundWelcomeMessage,
563
+ input,
564
+ suggestionView,
565
+ className,
566
+ ...props,
567
+ })}
568
+ </div>
569
+ );
570
+ }
571
+
572
+ return (
573
+ <div
574
+ data-testid="copilot-welcome-screen"
575
+ className={cn(
576
+ "cpk:flex-1 cpk:flex cpk:flex-col cpk:items-center cpk:justify-center cpk:px-4",
577
+ className,
578
+ )}
579
+ {...props}
580
+ >
581
+ <div className="cpk:w-full cpk:max-w-3xl cpk:flex cpk:flex-col cpk:items-center">
582
+ {/* Welcome message */}
583
+ <div className="cpk:mb-6">{BoundWelcomeMessage}</div>
584
+
585
+ {/* Input */}
586
+ <div className="cpk:w-full">{input}</div>
587
+
588
+ {/* Suggestions */}
589
+ <div className="cpk:mt-4 cpk:flex cpk:justify-center">
590
+ {suggestionView}
591
+ </div>
592
+ </div>
593
+ </div>
594
+ );
595
+ };
596
+ }
597
+
598
+ export default CopilotChatView;