@copilotkit/react-ui 0.22.0 → 0.36.0-mme-push-to-talk.0

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 (152) hide show
  1. package/.turbo/turbo-build.log +204 -178
  2. package/CHANGELOG.md +12 -0
  3. package/dist/chunk-5ASYNEHX.mjs +53 -0
  4. package/dist/chunk-5ASYNEHX.mjs.map +1 -0
  5. package/dist/{chunk-MBYUBR3F.mjs → chunk-A7J4KGLP.mjs} +2 -2
  6. package/dist/chunk-DMAQBCTX.mjs +95 -0
  7. package/dist/chunk-DMAQBCTX.mjs.map +1 -0
  8. package/dist/{chunk-KE3N45ZY.mjs → chunk-DRNCOXZO.mjs} +3 -4
  9. package/dist/chunk-DRNCOXZO.mjs.map +1 -0
  10. package/dist/{chunk-7YXG7D47.mjs → chunk-FWTPMPSN.mjs} +37 -2
  11. package/dist/chunk-FWTPMPSN.mjs.map +1 -0
  12. package/dist/chunk-JPX5ODUX.mjs +266 -0
  13. package/dist/chunk-JPX5ODUX.mjs.map +1 -0
  14. package/dist/chunk-KZME7C5S.mjs +76 -0
  15. package/dist/chunk-KZME7C5S.mjs.map +1 -0
  16. package/dist/{chunk-7JYUCW7H.mjs → chunk-MWFHYCQB.mjs} +2 -2
  17. package/dist/chunk-PEDSZYHE.mjs +36 -0
  18. package/dist/chunk-PEDSZYHE.mjs.map +1 -0
  19. package/dist/{chunk-YEHO5VMA.mjs → chunk-PGZDQT74.mjs} +2 -2
  20. package/dist/{chunk-MRXNTQOX.mjs → chunk-SKC7AJIV.mjs} +3 -1
  21. package/dist/chunk-T26KLXLH.mjs +1 -0
  22. package/dist/{chunk-T3WYKWNC.mjs → chunk-UVMROYDT.mjs} +5 -5
  23. package/dist/chunk-UVMROYDT.mjs.map +1 -0
  24. package/dist/{chunk-YAORLSQ3.mjs → chunk-XWWMYJJF.mjs} +5 -5
  25. package/dist/chunk-XWWMYJJF.mjs.map +1 -0
  26. package/dist/{chunk-73EBDGYK.mjs → chunk-XYM43AHP.mjs} +5 -5
  27. package/dist/chunk-XYM43AHP.mjs.map +1 -0
  28. package/dist/chunk-Z45ZEXJW.mjs +32 -0
  29. package/dist/chunk-Z45ZEXJW.mjs.map +1 -0
  30. package/dist/{chunk-WDHLWSSU.mjs → chunk-ZKLK3M77.mjs} +3 -3
  31. package/dist/components/chat/Button.d.ts +1 -1
  32. package/dist/components/chat/Button.js +30 -2
  33. package/dist/components/chat/Button.js.map +1 -1
  34. package/dist/components/chat/Button.mjs +4 -4
  35. package/dist/components/chat/Chat.d.ts +3 -2
  36. package/dist/components/chat/Chat.js +397 -86
  37. package/dist/components/chat/Chat.js.map +1 -1
  38. package/dist/components/chat/Chat.mjs +13 -11
  39. package/dist/components/chat/ChatContext.d.ts +6 -1
  40. package/dist/components/chat/ChatContext.js +29 -28
  41. package/dist/components/chat/ChatContext.js.map +1 -1
  42. package/dist/components/chat/ChatContext.mjs +3 -3
  43. package/dist/components/chat/CodeBlock.js.map +1 -1
  44. package/dist/components/chat/CodeBlock.mjs +3 -3
  45. package/dist/components/chat/Header.js.map +1 -1
  46. package/dist/components/chat/Header.mjs +4 -4
  47. package/dist/components/chat/Icons.d.ts +2 -1
  48. package/dist/components/chat/Icons.js +36 -0
  49. package/dist/components/chat/Icons.js.map +1 -1
  50. package/dist/components/chat/Icons.mjs +4 -2
  51. package/dist/components/chat/Input.d.ts +1 -1
  52. package/dist/components/chat/Input.js +1 -2
  53. package/dist/components/chat/Input.js.map +1 -1
  54. package/dist/components/chat/Input.mjs +4 -4
  55. package/dist/components/chat/Markdown.js.map +1 -1
  56. package/dist/components/chat/Markdown.mjs +4 -4
  57. package/dist/components/chat/Messages.d.ts +1 -1
  58. package/dist/components/chat/Messages.js +2 -2
  59. package/dist/components/chat/Messages.js.map +1 -1
  60. package/dist/components/chat/Messages.mjs +6 -6
  61. package/dist/components/chat/Popup.d.ts +52 -1
  62. package/dist/components/chat/Popup.js +401 -90
  63. package/dist/components/chat/Popup.js.map +1 -1
  64. package/dist/components/chat/Popup.mjs +14 -12
  65. package/dist/components/chat/Response.js.map +1 -1
  66. package/dist/components/chat/Response.mjs +4 -4
  67. package/dist/components/chat/Sidebar.d.ts +3 -5
  68. package/dist/components/chat/Sidebar.js +403 -92
  69. package/dist/components/chat/Sidebar.js.map +1 -1
  70. package/dist/components/chat/Sidebar.mjs +14 -12
  71. package/dist/components/chat/Suggestion.d.ts +14 -0
  72. package/dist/components/chat/Suggestion.js +172 -0
  73. package/dist/components/chat/Suggestion.js.map +1 -0
  74. package/dist/components/chat/Suggestion.mjs +11 -0
  75. package/dist/components/chat/Suggestion.mjs.map +1 -0
  76. package/dist/components/chat/Textarea.mjs +1 -1
  77. package/dist/components/chat/Window.mjs +1 -1
  78. package/dist/components/chat/audio.d.ts +7 -0
  79. package/dist/components/chat/audio.js +77 -0
  80. package/dist/components/chat/audio.js.map +1 -0
  81. package/dist/components/chat/audio.mjs +10 -0
  82. package/dist/components/chat/audio.mjs.map +1 -0
  83. package/dist/components/chat/index.d.ts +2 -1
  84. package/dist/components/chat/index.js +407 -96
  85. package/dist/components/chat/index.js.map +1 -1
  86. package/dist/components/chat/index.mjs +15 -13
  87. package/dist/components/chat/props.d.ts +11 -2
  88. package/dist/components/chat/props.js.map +1 -1
  89. package/dist/components/index.d.ts +2 -1
  90. package/dist/components/index.js +407 -96
  91. package/dist/components/index.js.map +1 -1
  92. package/dist/components/index.mjs +15 -13
  93. package/dist/hooks/index.d.ts +2 -2
  94. package/dist/hooks/index.js +61 -0
  95. package/dist/hooks/index.js.map +1 -1
  96. package/dist/hooks/index.mjs +28 -1
  97. package/dist/hooks/use-copilot-chat-suggestions.d.ts +65 -0
  98. package/dist/hooks/use-copilot-chat-suggestions.js +78 -0
  99. package/dist/hooks/use-copilot-chat-suggestions.js.map +1 -0
  100. package/dist/hooks/use-copilot-chat-suggestions.mjs +28 -0
  101. package/dist/hooks/use-copilot-chat-suggestions.mjs.map +1 -0
  102. package/dist/hooks/use-copy-to-clipboard.mjs +1 -1
  103. package/dist/index.css +41 -5
  104. package/dist/index.css.map +1 -1
  105. package/dist/index.d.ts +3 -1
  106. package/dist/index.js +435 -98
  107. package/dist/index.js.map +1 -1
  108. package/dist/index.mjs +23 -17
  109. package/dist/lib/utils.mjs +1 -1
  110. package/dist/types/suggestions.d.ts +28 -0
  111. package/dist/types/suggestions.js +19 -0
  112. package/dist/types/suggestions.js.map +1 -0
  113. package/dist/types/suggestions.mjs +1 -0
  114. package/dist/types/suggestions.mjs.map +1 -0
  115. package/package.json +6 -6
  116. package/src/components/chat/Button.tsx +36 -4
  117. package/src/components/chat/Chat.tsx +204 -16
  118. package/src/components/chat/ChatContext.tsx +44 -32
  119. package/src/components/chat/Icons.tsx +27 -0
  120. package/src/components/chat/Input.tsx +1 -2
  121. package/src/components/chat/Messages.tsx +3 -3
  122. package/src/components/chat/Popup.tsx +52 -6
  123. package/src/components/chat/Sidebar.tsx +59 -6
  124. package/src/components/chat/Suggestion.tsx +105 -0
  125. package/src/components/chat/audio.ts +26 -0
  126. package/src/components/chat/props.ts +11 -1
  127. package/src/css/messages.css +7 -1
  128. package/src/css/response.css +1 -4
  129. package/src/css/suggestions.css +35 -0
  130. package/src/hooks/index.ts +1 -1
  131. package/src/hooks/use-copilot-chat-suggestions.tsx +91 -0
  132. package/src/styles.css +1 -0
  133. package/src/types/suggestions.ts +30 -0
  134. package/typedoc.json +4 -0
  135. package/dist/chunk-6U3O2JZP.mjs +0 -100
  136. package/dist/chunk-6U3O2JZP.mjs.map +0 -1
  137. package/dist/chunk-73EBDGYK.mjs.map +0 -1
  138. package/dist/chunk-7LMXXGJT.mjs +0 -75
  139. package/dist/chunk-7LMXXGJT.mjs.map +0 -1
  140. package/dist/chunk-7YXG7D47.mjs.map +0 -1
  141. package/dist/chunk-H4VKQGVU.mjs +0 -1
  142. package/dist/chunk-KE3N45ZY.mjs.map +0 -1
  143. package/dist/chunk-T3WYKWNC.mjs.map +0 -1
  144. package/dist/chunk-YAORLSQ3.mjs.map +0 -1
  145. package/dist/chunk-YTIGBBTC.mjs +0 -25
  146. package/dist/chunk-YTIGBBTC.mjs.map +0 -1
  147. /package/dist/{chunk-MBYUBR3F.mjs.map → chunk-A7J4KGLP.mjs.map} +0 -0
  148. /package/dist/{chunk-7JYUCW7H.mjs.map → chunk-MWFHYCQB.mjs.map} +0 -0
  149. /package/dist/{chunk-YEHO5VMA.mjs.map → chunk-PGZDQT74.mjs.map} +0 -0
  150. /package/dist/{chunk-H4VKQGVU.mjs.map → chunk-SKC7AJIV.mjs.map} +0 -0
  151. /package/dist/{chunk-MRXNTQOX.mjs.map → chunk-T26KLXLH.mjs.map} +0 -0
  152. /package/dist/{chunk-WDHLWSSU.mjs.map → chunk-ZKLK3M77.mjs.map} +0 -0
@@ -1,12 +1,44 @@
1
+ import { useEffect, useRef, useState } from "react";
1
2
  import { ButtonProps } from "./props";
2
3
  import { useChatContext } from "./ChatContext";
3
4
 
4
- export const Button = ({ open, setOpen }: ButtonProps) => {
5
+ export const Button = ({ open, setOpen, pushToTalk, setPushToTalk }: ButtonProps) => {
5
6
  const context = useChatContext();
6
- // To ensure that the mouse handler fires even when the button is scaled down
7
- // we wrap the button in a div and attach the handler to the div
7
+ const timerRef = useRef<NodeJS.Timeout | null>(null);
8
+ const [isLongPress, setIsLongPress] = useState(false);
9
+
10
+ const handleMouseDown = () => {
11
+ timerRef.current = setTimeout(() => {
12
+ setPushToTalk(true);
13
+ setIsLongPress(true);
14
+ }, 500); // 500ms for long press
15
+ };
16
+
17
+ const handleMouseUp = () => {
18
+ if (timerRef.current) {
19
+ clearTimeout(timerRef.current);
20
+ setPushToTalk(false);
21
+ }
22
+ };
23
+
24
+ const handleClick = () => {
25
+ if (!isLongPress) {
26
+ setOpen(!open);
27
+ } else {
28
+ setIsLongPress(false);
29
+ }
30
+ };
31
+
32
+ // we want to handle the mouse up event event outside of the button component
33
+ useEffect(() => {
34
+ document.addEventListener("mouseup", handleMouseUp);
35
+ return () => {
36
+ document.removeEventListener("mouseup", handleMouseUp);
37
+ };
38
+ }, []);
39
+
8
40
  return (
9
- <div onClick={() => setOpen(!open)}>
41
+ <div onClick={handleClick} onMouseDown={handleMouseDown}>
10
42
  <button
11
43
  className={`copilotKitButton ${open ? "open" : ""}`}
12
44
  aria-label={open ? "Close Chat" : "Open Chat"}
@@ -1,6 +1,11 @@
1
- import React, { useEffect } from "react";
1
+ import React, { useEffect, useRef, useState } from "react";
2
2
  import { CopilotChatIcons, ChatContextProvider, CopilotChatLabels } from "./ChatContext";
3
- import { SystemMessageFunction, useCopilotChat } from "@copilotkit/react-core";
3
+ import {
4
+ SystemMessageFunction,
5
+ extract,
6
+ useCopilotChat,
7
+ useCopilotContext,
8
+ } from "@copilotkit/react-core";
4
9
  import {
5
10
  ButtonProps,
6
11
  HeaderProps,
@@ -8,6 +13,7 @@ import {
8
13
  MessagesProps,
9
14
  InputProps,
10
15
  ResponseButtonProps,
16
+ SuggestionsProps,
11
17
  } from "./props";
12
18
  import { Window as DefaultWindow } from "./Window";
13
19
  import { Button as DefaultButton } from "./Button";
@@ -16,6 +22,10 @@ import { Messages as DefaultMessages } from "./Messages";
16
22
  import { Input as DefaultInput } from "./Input";
17
23
  import { nanoid } from "nanoid";
18
24
  import { ResponseButton as DefaultResponseButton } from "./Response";
25
+ import { Suggestion, reloadSuggestions } from "./Suggestion";
26
+ import { CopilotChatSuggestion, CopilotChatSuggestionConfiguration } from "../../types/suggestions";
27
+ import { requestMicAndPlaybackPermission } from "./audio";
28
+ import { Message } from "@copilotkit/shared";
19
29
 
20
30
  /**
21
31
  * Props for CopilotChat component.
@@ -66,8 +76,8 @@ export interface CopilotChatProps {
66
76
 
67
77
  /**
68
78
  * The shortcut key to open the chat window.
69
- * Uses Command-<shortcut> on a Mac and Ctrl-<shortcut> on Windows.
70
- * @default "e"
79
+ * Uses Command-[shortcut] on a Mac and Ctrl-[shortcut] on Windows.
80
+ * @default "/"
71
81
  */
72
82
  shortcut?: string;
73
83
 
@@ -136,6 +146,8 @@ export interface CopilotChatProps {
136
146
  children?: React.ReactNode;
137
147
  }
138
148
 
149
+ const SUGGESTIONS_DEBOUNCE_TIMEOUT = 1000;
150
+
139
151
  export const CopilotChat = ({
140
152
  instructions,
141
153
  defaultOpen = false,
@@ -164,31 +176,190 @@ export const CopilotChat = ({
164
176
  additionalInstructions: instructions,
165
177
  });
166
178
 
179
+ const [currentSuggestions, setCurrentSuggestions] = React.useState<CopilotChatSuggestion[]>([]);
180
+ const suggestionsAbortControllerRef = useRef<AbortController | null>(null);
181
+ const debounceTimerRef = useRef<any>();
182
+
183
+ const abortSuggestions = () => {
184
+ suggestionsAbortControllerRef.current?.abort();
185
+ suggestionsAbortControllerRef.current = null;
186
+ };
187
+
188
+ const context = useCopilotContext();
189
+
190
+ const [chatSuggestionConfiguration, setChatSuggestionConfiguration] = useState<{
191
+ [key: string]: CopilotChatSuggestionConfiguration;
192
+ }>({});
193
+
194
+ const addChatSuggestionConfiguration = (
195
+ id: string,
196
+ suggestion: CopilotChatSuggestionConfiguration,
197
+ ) => {
198
+ setChatSuggestionConfiguration((prev) => ({ ...prev, [id]: suggestion }));
199
+ };
200
+
201
+ const removeChatSuggestion = (id: string) => {
202
+ setChatSuggestionConfiguration((prev) => {
203
+ const { [id]: _, ...rest } = prev;
204
+ return rest;
205
+ });
206
+ };
207
+
167
208
  useEffect(() => {
168
209
  onInProgress?.(isLoading);
169
- }, [isLoading]);
170
210
 
171
- const [openState, setOpenState] = React.useState(defaultOpen);
211
+ abortSuggestions();
212
+
213
+ debounceTimerRef.current = setTimeout(
214
+ () => {
215
+ if (!isLoading && Object.keys(chatSuggestionConfiguration).length !== 0) {
216
+ suggestionsAbortControllerRef.current = new AbortController();
217
+ reloadSuggestions(
218
+ context,
219
+ chatSuggestionConfiguration,
220
+ setCurrentSuggestions,
221
+ suggestionsAbortControllerRef,
222
+ );
223
+ }
224
+ },
225
+ currentSuggestions.length == 0 ? 0 : SUGGESTIONS_DEBOUNCE_TIMEOUT,
226
+ );
227
+
228
+ return () => {
229
+ clearTimeout(debounceTimerRef.current);
230
+ };
231
+ }, [isLoading, chatSuggestionConfiguration]);
172
232
 
173
233
  const setOpen = (open: boolean) => {
174
234
  onSetOpen?.(open);
175
235
  setOpenState(open);
176
236
  };
177
237
 
178
- const sendMessage = async (message: string) => {
179
- onSubmitMessage?.(message);
180
- append({
238
+ const sendMessage = async (messageContent: string) => {
239
+ abortSuggestions();
240
+ setCurrentSuggestions([]);
241
+ onSubmitMessage?.(messageContent);
242
+ const message: Message = {
181
243
  id: nanoid(),
182
- content: message,
244
+ content: messageContent,
183
245
  role: "user",
184
- });
246
+ };
247
+ append(message);
248
+ return message;
185
249
  };
186
250
 
251
+ const [openState, setOpenState] = React.useState(defaultOpen);
252
+ const [pushToTalkState, setPushToTalkState] = React.useState(false);
253
+ const mediaStreamRef = useRef<MediaStream | null>(null);
254
+ const audioContextRef = useRef<AudioContext | null>(null);
255
+ const mediaRecorderRef = useRef<MediaRecorder | null>(null);
256
+ const [lastMessageIdBeforeAudio, setLastMessageIdBeforeAudio] = useState<string | null>(null);
257
+
258
+ useEffect(() => {
259
+ if (pushToTalkState) {
260
+ console.log("HERE");
261
+ if (!mediaStreamRef.current || !audioContextRef.current) {
262
+ setPushToTalkState(false);
263
+ requestMicAndPlaybackPermission().then((res) => {
264
+ if (res) {
265
+ mediaStreamRef.current = res.stream;
266
+ audioContextRef.current = res.audioContext;
267
+ }
268
+ });
269
+ } else {
270
+ console.log("Recording started");
271
+ const recordedChunks: Blob[] = [];
272
+
273
+ mediaRecorderRef.current = new MediaRecorder(mediaStreamRef.current);
274
+ mediaRecorderRef.current.start(1000);
275
+ mediaRecorderRef.current.ondataavailable = async (event) => {
276
+ console.log("Recorded audio: ", event.data);
277
+ recordedChunks.push(event.data);
278
+ };
279
+ mediaRecorderRef.current.onstop = async () => {
280
+ console.log("Recording stopped");
281
+ const completeBlob = new Blob(recordedChunks, { type: "audio/mp4" });
282
+
283
+ const formData = new FormData();
284
+ formData.append("file", completeBlob, "recording.mp4");
285
+
286
+ const response = await fetch(context.copilotApiConfig.transcribeAudioUrl!, {
287
+ method: "POST",
288
+ body: formData,
289
+ });
290
+
291
+ if (!response.ok) {
292
+ throw new Error(`Error: ${response.statusText}`);
293
+ }
294
+
295
+ const transcription = await response.json();
296
+ const message = await sendMessage(transcription.text);
297
+ setLastMessageIdBeforeAudio(message.id);
298
+ };
299
+ }
300
+ } else {
301
+ if (mediaRecorderRef.current && mediaRecorderRef.current.state !== "inactive") {
302
+ mediaRecorderRef.current.stop();
303
+ }
304
+ }
305
+
306
+ return () => {
307
+ if (mediaRecorderRef.current && mediaRecorderRef.current.state !== "inactive") {
308
+ mediaRecorderRef.current.stop();
309
+ }
310
+ };
311
+ }, [pushToTalkState]);
312
+
313
+ useEffect(() => {
314
+ if (lastMessageIdBeforeAudio && !isLoading) {
315
+ if (audioContextRef.current) {
316
+ const lastMessageIndex = context.messages.findIndex(
317
+ (message) => message.id === lastMessageIdBeforeAudio,
318
+ );
319
+
320
+ const messagesAfterLast = context.messages
321
+ .slice(lastMessageIndex + 1)
322
+ .filter((message) => message.role === "assistant" && message.content);
323
+
324
+ const text = messagesAfterLast.map((message) => message.content).join("\n");
325
+ const encodedText = encodeURIComponent(text);
326
+ const url = `${context.copilotApiConfig.textToSpeechUrl}?text=${encodedText}`;
327
+
328
+ fetch(url)
329
+ .then((response) => response.arrayBuffer())
330
+ .then((arrayBuffer) => audioContextRef.current!.decodeAudioData(arrayBuffer))
331
+ .then((audioBuffer) => {
332
+ const source = audioContextRef.current!.createBufferSource();
333
+ source.buffer = audioBuffer;
334
+ source.connect(audioContextRef.current!.destination);
335
+ source.start(0);
336
+ })
337
+ .catch((error) => {
338
+ console.error("Error with decoding audio data", error);
339
+ });
340
+
341
+ setLastMessageIdBeforeAudio(null);
342
+ }
343
+ }
344
+ }, [isLoading]);
345
+
187
346
  return (
188
- <ChatContextProvider icons={icons} labels={labels} open={openState} setOpen={setOpenState}>
347
+ <ChatContextProvider
348
+ icons={icons}
349
+ labels={labels}
350
+ open={openState}
351
+ setOpen={setOpenState}
352
+ addChatSuggestionConfiguration={addChatSuggestionConfiguration}
353
+ removeChatSuggestionConfiguration={removeChatSuggestion}
354
+ >
189
355
  {children}
190
356
  <div className={className}>
191
- <Button open={openState} setOpen={setOpen}></Button>
357
+ <Button
358
+ open={openState}
359
+ setOpen={setOpen}
360
+ pushToTalk={pushToTalkState}
361
+ setPushToTalk={setPushToTalkState}
362
+ ></Button>
192
363
  <Window
193
364
  open={openState}
194
365
  setOpen={setOpen}
@@ -197,12 +368,29 @@ export const CopilotChat = ({
197
368
  hitEscapeToClose={hitEscapeToClose}
198
369
  >
199
370
  <Header open={openState} setOpen={setOpen} />
200
- <Messages messages={visibleMessages} inProgress={isLoading} />
201
- <Input inProgress={isLoading} onSend={sendMessage} isVisible={openState}>
371
+ <Messages messages={visibleMessages} inProgress={isLoading}>
372
+ {currentSuggestions.length > 0 && (
373
+ <div>
374
+ <h6>Suggested:</h6>
375
+ <div className="suggestions">
376
+ {currentSuggestions.map((suggestion, index) => (
377
+ <Suggestion
378
+ key={index}
379
+ title={suggestion.title}
380
+ message={suggestion.message}
381
+ partial={suggestion.partial}
382
+ className={suggestion.className}
383
+ onClick={(message) => sendMessage(message)}
384
+ />
385
+ ))}
386
+ </div>
387
+ </div>
388
+ )}
202
389
  {showResponseButton && visibleMessages.length > 0 && (
203
390
  <ResponseButton onClick={isLoading ? stop : reload} inProgress={isLoading} />
204
391
  )}
205
- </Input>
392
+ </Messages>
393
+ <Input inProgress={isLoading} onSend={sendMessage} isVisible={openState} />
206
394
  </Window>
207
395
  </div>
208
396
  </ChatContextProvider>
@@ -1,5 +1,6 @@
1
- import React, { useMemo } from "react";
1
+ import React, { useMemo, useState } from "react";
2
2
  import * as DefaultIcons from "./Icons";
3
+ import { CopilotChatSuggestion, CopilotChatSuggestionConfiguration } from "../../types/suggestions";
3
4
 
4
5
  /**
5
6
  * Icons for CopilotChat component.
@@ -105,6 +106,11 @@ interface ChatContext {
105
106
  icons: Required<CopilotChatIcons>;
106
107
  open: boolean;
107
108
  setOpen: (open: boolean) => void;
109
+ addChatSuggestionConfiguration: (
110
+ id: string,
111
+ suggestion: CopilotChatSuggestionConfiguration,
112
+ ) => void;
113
+ removeChatSuggestionConfiguration: (id: string) => void;
108
114
  }
109
115
 
110
116
  export const ChatContext = React.createContext<ChatContext | undefined>(undefined);
@@ -128,6 +134,11 @@ interface ChatContextProps {
128
134
  children?: React.ReactNode;
129
135
  open: boolean;
130
136
  setOpen: (open: boolean) => void;
137
+ addChatSuggestionConfiguration: (
138
+ id: string,
139
+ suggestion: CopilotChatSuggestionConfiguration,
140
+ ) => void;
141
+ removeChatSuggestionConfiguration: (id: string) => void;
131
142
  }
132
143
 
133
144
  export const ChatContextProvider = ({
@@ -139,39 +150,40 @@ export const ChatContextProvider = ({
139
150
  children,
140
151
  open,
141
152
  setOpen,
153
+ addChatSuggestionConfiguration,
154
+ removeChatSuggestionConfiguration,
142
155
  }: ChatContextProps) => {
143
- const context = useMemo(
144
- () => ({
145
- labels: {
146
- ...{
147
- initial: "",
148
- title: "CopilotKit",
149
- placeholder: "Type a message...",
150
- thinking: "Thinking...",
151
- error: " An error occurred. Please try again.",
152
- stopGenerating: "Stop generating",
153
- regenerateResponse: "Regenerate response",
154
- },
155
- ...labels,
156
+ const context = {
157
+ labels: {
158
+ ...{
159
+ initial: "",
160
+ title: "CopilotKit",
161
+ placeholder: "Type a message...",
162
+ thinking: "Thinking...",
163
+ error: "❌ An error occurred. Please try again.",
164
+ stopGenerating: "Stop generating",
165
+ regenerateResponse: "Regenerate response",
156
166
  },
157
-
158
- icons: {
159
- ...{
160
- openIcon: DefaultIcons.OpenIcon,
161
- closeIcon: DefaultIcons.CloseIcon,
162
- headerCloseIcon: DefaultIcons.HeaderCloseIcon,
163
- sendIcon: DefaultIcons.SendIcon,
164
- activityIcon: DefaultIcons.ActivityIcon,
165
- spinnerIcon: DefaultIcons.SpinnerIcon,
166
- stopIcon: DefaultIcons.StopIcon,
167
- regenerateIcon: DefaultIcons.RegenerateIcon,
168
- },
169
- icons,
167
+ ...labels,
168
+ },
169
+
170
+ icons: {
171
+ ...{
172
+ openIcon: DefaultIcons.OpenIcon,
173
+ closeIcon: DefaultIcons.CloseIcon,
174
+ headerCloseIcon: DefaultIcons.HeaderCloseIcon,
175
+ sendIcon: DefaultIcons.SendIcon,
176
+ activityIcon: DefaultIcons.ActivityIcon,
177
+ spinnerIcon: DefaultIcons.SpinnerIcon,
178
+ stopIcon: DefaultIcons.StopIcon,
179
+ regenerateIcon: DefaultIcons.RegenerateIcon,
170
180
  },
171
- open,
172
- setOpen,
173
- }),
174
- [labels, icons, open, setOpen],
175
- );
181
+ icons,
182
+ },
183
+ open,
184
+ setOpen,
185
+ addChatSuggestionConfiguration,
186
+ removeChatSuggestionConfiguration,
187
+ };
176
188
  return <ChatContext.Provider value={context}>{children}</ChatContext.Provider>;
177
189
  };
@@ -92,6 +92,33 @@ export const SpinnerIcon = (
92
92
  </svg>
93
93
  );
94
94
 
95
+ export const SmallSpinnerIcon = (
96
+ <svg
97
+ style={{
98
+ animation: "copilotKitSpinAnimation 1s linear infinite",
99
+ }}
100
+ width="13"
101
+ height="13"
102
+ xmlns="http://www.w3.org/2000/svg"
103
+ fill="none"
104
+ viewBox="0 0 24 24"
105
+ >
106
+ <circle
107
+ style={{ opacity: 0.25 }}
108
+ cx="12"
109
+ cy="12"
110
+ r="10"
111
+ stroke="currentColor"
112
+ strokeWidth="4"
113
+ ></circle>
114
+ <path
115
+ style={{ opacity: 0.75 }}
116
+ fill="currentColor"
117
+ d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
118
+ ></path>
119
+ </svg>
120
+ );
121
+
95
122
  export const ActivityIcon = (
96
123
  <svg
97
124
  style={{
@@ -3,7 +3,7 @@ import { InputProps } from "./props";
3
3
  import { useChatContext } from "./ChatContext";
4
4
  import AutoResizingTextarea from "./Textarea";
5
5
 
6
- export const Input = ({ inProgress, onSend, children, isVisible = false }: InputProps) => {
6
+ export const Input = ({ inProgress, onSend, isVisible = false }: InputProps) => {
7
7
  const context = useChatContext();
8
8
  const textareaRef = useRef<HTMLTextAreaElement>(null);
9
9
 
@@ -34,7 +34,6 @@ export const Input = ({ inProgress, onSend, children, isVisible = false }: Input
34
34
 
35
35
  return (
36
36
  <div className="copilotKitInput" onClick={handleDivClick}>
37
- <span>{children}</span>
38
37
  <button className="copilotKitSendButton" disabled={disabled} onClick={send}>
39
38
  {icon}
40
39
  </button>
@@ -1,12 +1,12 @@
1
1
  import React, { useEffect, useMemo } from "react";
2
- import { MessagesProps } from "./props";
2
+ import { MessagesProps, SuggestionsProps } from "./props";
3
3
  import { useChatContext } from "./ChatContext";
4
4
  import { nanoid } from "nanoid";
5
5
  import { Message, decodeResult } from "@copilotkit/shared";
6
6
  import { Markdown } from "./Markdown";
7
7
  import { ActionRenderProps, RenderFunctionStatus, useCopilotContext } from "@copilotkit/react-core";
8
8
 
9
- export const Messages = ({ messages, inProgress }: MessagesProps) => {
9
+ export const Messages = ({ messages, inProgress, children }: MessagesProps) => {
10
10
  const { chatComponentsCache } = useCopilotContext();
11
11
  const context = useChatContext();
12
12
  const initialMessages = useMemo(
@@ -145,7 +145,7 @@ export const Messages = ({ messages, inProgress }: MessagesProps) => {
145
145
  );
146
146
  }
147
147
  })}
148
- <div ref={messagesEndRef} />
148
+ <footer ref={messagesEndRef}>{children}</footer>
149
149
  </div>
150
150
  );
151
151
  };
@@ -1,13 +1,59 @@
1
- import { CopilotChat, CopilotChatProps } from "./Chat";
1
+ /**
2
+ * A chatbot popup component for CopilotKit.
3
+ *
4
+ * <img src="/images/CopilotPopup/CopilotPopup.gif" width="500" />
5
+ *
6
+ * <img referrerPolicy="no-referrer-when-downgrade" src="https://static.scarf.sh/a.png?x-pxid=a9b290bb-38f9-4518-ac3b-8f54fdbf43be" />
7
+ *
8
+ * A chatbot popup component for the CopilotKit framework. The component allows for a high degree
9
+ * of customization through various props and custom CSS.
10
+ *
11
+ * See [CopilotSidebar](./CopilotSidebar) for a sidebar version of this component.
12
+ *
13
+ * <RequestExample>
14
+ * ```jsx CopilotPopup Example
15
+ * import { CopilotPopup } from "@copilotkit/react-ui";
16
+ *
17
+ * <CopilotPopup
18
+ * labels={{
19
+ * title: "Your Assistant",
20
+ * initial: "Hi! 👋 How can I assist you today?",
21
+ * }}
22
+ * />
23
+ * ```
24
+ * </RequestExample>
25
+ *
26
+ * ## Custom CSS
27
+ *
28
+ * You can customize the colors of the chat window by overriding the CSS variables
29
+ * defined in the [default styles](https://github.com/CopilotKit/CopilotKit/blob/main/CopilotKit/packages/react-ui/src/css/colors.css).
30
+ *
31
+ * For example, to set the primary color to purple:
32
+ *
33
+ * ```jsx
34
+ * <div style={{ "--copilot-kit-primary-color": "#7D5BA6" }}>
35
+ * <CopilotPopup />
36
+ * </div>
37
+ * ```
38
+ *
39
+ * To further customize the chat window, you can override the CSS classes defined
40
+ * [here](https://github.com/CopilotKit/CopilotKit/blob/main/CopilotKit/packages/react-ui/src/css/).
41
+ *
42
+ * For example:
43
+ *
44
+ * ```css
45
+ * .copilotKitButton {
46
+ * border-radius: 0;
47
+ * }
48
+ * ```
49
+ */
2
50
 
3
- interface CopilotPopupProps extends CopilotChatProps {
4
- children?: React.ReactNode;
5
- }
51
+ import { CopilotChat, CopilotChatProps } from "./Chat";
6
52
 
7
- export const CopilotPopup = (props: CopilotChatProps) => {
53
+ export function CopilotPopup(props: CopilotChatProps) {
8
54
  props = {
9
55
  ...props,
10
56
  className: props.className ? props.className + " copilotKitPopup" : "copilotKitPopup",
11
57
  };
12
58
  return <CopilotChat {...props}>{props.children}</CopilotChat>;
13
- };
59
+ }
@@ -1,11 +1,64 @@
1
+ /**
2
+ * A chatbot sidebar component for CopilotKit.
3
+ *
4
+ * <img src="/images/CopilotSidebar/CopilotSidebar.gif" width="500" />
5
+ *
6
+ * <img referrerPolicy="no-referrer-when-downgrade" src="https://static.scarf.sh/a.png?x-pxid=a9b290bb-38f9-4518-ac3b-8f54fdbf43be" />
7
+ *
8
+ * A chatbot sidebar component for the CopilotKit framework. Highly customizable through various props and custom CSS.
9
+ *
10
+ * <RequestExample>
11
+ * ```jsx CopilotSidebar Example
12
+ * import { CopilotSidebar } from "@copilotkit/react-ui";
13
+ *
14
+ * <CopilotSidebar
15
+ * labels={{
16
+ * title: "Your Assistant",
17
+ * initial: "Hi! 👋 How can I assist you today?",
18
+ * }}
19
+ * >
20
+ * <YourApp/>
21
+ * </CopilotSidebar>
22
+ * ```
23
+ * </RequestExample>
24
+ *
25
+ *
26
+ * See [CopilotPopup](./CopilotPopup) for a popup version of this component.
27
+ *
28
+ * <Note>
29
+ * To make the sidebar push your content to the side, wrap your content in the
30
+ * sidebar component. If you want the sidebar to overlay your content, place the
31
+ * sidebar component outside of your content.
32
+ * </Note>
33
+ *
34
+ * ## Custom CSS
35
+ *
36
+ * You can customize the colors of the chat window by overriding the CSS variables
37
+ * defined in the [default styles](https://github.com/CopilotKit/CopilotKit/blob/main/CopilotKit/packages/react-ui/src/css/colors.css).
38
+ *
39
+ * For example, to set the primary color to purple:
40
+ *
41
+ * ```jsx
42
+ * <div style={{ "--copilot-kit-primary-color": "#7D5BA6" }}>
43
+ * <CopilotSidebar />
44
+ * </div>
45
+ * ```
46
+ *
47
+ * To further customize the chat window, you can override the CSS classes defined
48
+ * [here](https://github.com/CopilotKit/CopilotKit/blob/main/CopilotKit/packages/react-ui/src/css/).
49
+ *
50
+ * For example:
51
+ *
52
+ * ```css
53
+ * .copilotKitButton {
54
+ * border-radius: 0;
55
+ * }
56
+ * ```
57
+ */
1
58
  import React, { useState } from "react";
2
59
  import { CopilotChat, CopilotChatProps } from "./Chat";
3
60
 
4
- interface CopilotSidebarProps extends CopilotChatProps {
5
- children?: React.ReactNode;
6
- }
7
-
8
- export const CopilotSidebar = (props: CopilotSidebarProps) => {
61
+ export function CopilotSidebar(props: CopilotChatProps) {
9
62
  props = {
10
63
  ...props,
11
64
  className: props.className ? props.className + " copilotKitSidebar" : "copilotKitSidebar",
@@ -26,4 +79,4 @@ export const CopilotSidebar = (props: CopilotSidebarProps) => {
26
79
  </CopilotChat>
27
80
  </div>
28
81
  );
29
- };
82
+ }