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

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 (159) hide show
  1. package/.turbo/turbo-build.log +174 -167
  2. package/CHANGELOG.md +12 -0
  3. package/dist/{chunk-DMAQBCTX.mjs → chunk-4MKP23AD.mjs} +6 -4
  4. package/dist/chunk-4MKP23AD.mjs.map +1 -0
  5. package/dist/{chunk-UVMROYDT.mjs → chunk-6XLZXLM5.mjs} +5 -5
  6. package/dist/{chunk-UVMROYDT.mjs.map → chunk-6XLZXLM5.mjs.map} +1 -1
  7. package/dist/{chunk-XYM43AHP.mjs → chunk-7FES2IQA.mjs} +3 -3
  8. package/dist/chunk-ANO23V2M.mjs +135 -0
  9. package/dist/chunk-ANO23V2M.mjs.map +1 -0
  10. package/dist/{chunk-DRNCOXZO.mjs → chunk-BL65ZC6L.mjs} +28 -7
  11. package/dist/chunk-BL65ZC6L.mjs.map +1 -0
  12. package/dist/{chunk-PGZDQT74.mjs → chunk-CE7PJAAO.mjs} +2 -2
  13. package/dist/{chunk-FWTPMPSN.mjs → chunk-FZC7X5PK.mjs} +23 -3
  14. package/dist/{chunk-FWTPMPSN.mjs.map → chunk-FZC7X5PK.mjs.map} +1 -1
  15. package/dist/{chunk-Z45ZEXJW.mjs → chunk-LTCJCXCP.mjs} +5 -8
  16. package/dist/chunk-LTCJCXCP.mjs.map +1 -0
  17. package/dist/chunk-MRFF7GSQ.mjs +1 -0
  18. package/dist/{chunk-SKC7AJIV.mjs → chunk-MRXNTQOX.mjs} +1 -3
  19. package/dist/{chunk-MWFHYCQB.mjs → chunk-PAQWLSA4.mjs} +2 -2
  20. package/dist/chunk-RT2XG2T7.mjs +25 -0
  21. package/dist/chunk-RT2XG2T7.mjs.map +1 -0
  22. package/dist/chunk-T3JTSIHT.mjs +93 -0
  23. package/dist/chunk-T3JTSIHT.mjs.map +1 -0
  24. package/dist/{chunk-A7J4KGLP.mjs → chunk-UPTB2MVO.mjs} +2 -2
  25. package/dist/{chunk-KZME7C5S.mjs → chunk-VUZA5AFH.mjs} +8 -11
  26. package/dist/chunk-VUZA5AFH.mjs.map +1 -0
  27. package/dist/{chunk-XWWMYJJF.mjs → chunk-XRODMID5.mjs} +5 -5
  28. package/dist/{chunk-XWWMYJJF.mjs.map → chunk-XRODMID5.mjs.map} +1 -1
  29. package/dist/{chunk-ZKLK3M77.mjs → chunk-YQ3D5IQV.mjs} +3 -3
  30. package/dist/{chunk-WM6BS77F.mjs → chunk-YQFVRDNC.mjs} +2 -2
  31. package/dist/{chunk-WM6BS77F.mjs.map → chunk-YQFVRDNC.mjs.map} +1 -1
  32. package/dist/chunk-ZO3GLN23.mjs +137 -0
  33. package/dist/chunk-ZO3GLN23.mjs.map +1 -0
  34. package/dist/components/chat/Button.d.ts +1 -1
  35. package/dist/components/chat/Button.js +2 -30
  36. package/dist/components/chat/Button.js.map +1 -1
  37. package/dist/components/chat/Button.mjs +4 -4
  38. package/dist/components/chat/Chat.d.ts +66 -47
  39. package/dist/components/chat/Chat.js +274 -430
  40. package/dist/components/chat/Chat.js.map +1 -1
  41. package/dist/components/chat/Chat.mjs +16 -17
  42. package/dist/components/chat/ChatContext.d.ts +17 -22
  43. package/dist/components/chat/ChatContext.js +23 -8
  44. package/dist/components/chat/ChatContext.js.map +1 -1
  45. package/dist/components/chat/ChatContext.mjs +3 -3
  46. package/dist/components/chat/CodeBlock.js.map +1 -1
  47. package/dist/components/chat/CodeBlock.mjs +3 -3
  48. package/dist/components/chat/Header.js.map +1 -1
  49. package/dist/components/chat/Header.mjs +4 -4
  50. package/dist/components/chat/Icons.d.ts +6 -5
  51. package/dist/components/chat/Icons.js +21 -0
  52. package/dist/components/chat/Icons.js.map +1 -1
  53. package/dist/components/chat/Icons.mjs +4 -2
  54. package/dist/components/chat/Input.js +147 -9
  55. package/dist/components/chat/Input.js.map +1 -1
  56. package/dist/components/chat/Input.mjs +6 -5
  57. package/dist/components/chat/Markdown.js.map +1 -1
  58. package/dist/components/chat/Markdown.mjs +4 -4
  59. package/dist/components/chat/Messages.js.map +1 -1
  60. package/dist/components/chat/Messages.mjs +6 -6
  61. package/dist/components/chat/Modal.d.ts +50 -0
  62. package/dist/components/chat/Modal.js +1584 -0
  63. package/dist/components/chat/Modal.js.map +1 -0
  64. package/dist/components/chat/Modal.mjs +23 -0
  65. package/dist/components/chat/Popup.d.ts +6 -5
  66. package/dist/components/chat/Popup.js +288 -249
  67. package/dist/components/chat/Popup.js.map +1 -1
  68. package/dist/components/chat/Popup.mjs +16 -15
  69. package/dist/components/chat/Response.js.map +1 -1
  70. package/dist/components/chat/Response.mjs +4 -4
  71. package/dist/components/chat/Sidebar.d.ts +6 -5
  72. package/dist/components/chat/Sidebar.js +290 -251
  73. package/dist/components/chat/Sidebar.js.map +1 -1
  74. package/dist/components/chat/Sidebar.mjs +16 -15
  75. package/dist/components/chat/Suggestion.d.ts +1 -2
  76. package/dist/components/chat/Suggestion.js.map +1 -1
  77. package/dist/components/chat/Suggestion.mjs +3 -3
  78. package/dist/components/chat/Textarea.d.ts +4 -4
  79. package/dist/components/chat/Textarea.js +1 -1
  80. package/dist/components/chat/Textarea.js.map +1 -1
  81. package/dist/components/chat/Textarea.mjs +2 -2
  82. package/dist/components/chat/Window.mjs +1 -1
  83. package/dist/components/chat/index.d.ts +2 -1
  84. package/dist/components/chat/index.js +294 -253
  85. package/dist/components/chat/index.js.map +1 -1
  86. package/dist/components/chat/index.mjs +23 -19
  87. package/dist/components/chat/props.d.ts +1 -3
  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 +294 -253
  91. package/dist/components/index.js.map +1 -1
  92. package/dist/components/index.mjs +23 -19
  93. package/dist/hooks/index.d.ts +0 -1
  94. package/dist/hooks/index.js +6 -31
  95. package/dist/hooks/index.js.map +1 -1
  96. package/dist/hooks/index.mjs +2 -22
  97. package/dist/hooks/use-copilot-chat-suggestions.d.ts +26 -4
  98. package/dist/hooks/use-copilot-chat-suggestions.js +6 -31
  99. package/dist/hooks/use-copilot-chat-suggestions.js.map +1 -1
  100. package/dist/hooks/use-copilot-chat-suggestions.mjs +2 -22
  101. package/dist/hooks/use-copy-to-clipboard.mjs +1 -1
  102. package/dist/hooks/use-push-to-talk.d.ts +19 -0
  103. package/dist/hooks/use-push-to-talk.js +177 -0
  104. package/dist/hooks/use-push-to-talk.js.map +1 -0
  105. package/dist/hooks/use-push-to-talk.mjs +12 -0
  106. package/dist/hooks/use-push-to-talk.mjs.map +1 -0
  107. package/dist/index.css +60 -8
  108. package/dist/index.css.map +1 -1
  109. package/dist/index.d.ts +2 -1
  110. package/dist/index.js +300 -258
  111. package/dist/index.js.map +1 -1
  112. package/dist/index.mjs +28 -24
  113. package/dist/lib/utils.mjs +1 -1
  114. package/dist/types/suggestions.d.ts +1 -21
  115. package/dist/types/suggestions.js.map +1 -1
  116. package/package.json +6 -6
  117. package/src/components/chat/Button.tsx +2 -35
  118. package/src/components/chat/Chat.tsx +126 -255
  119. package/src/components/chat/ChatContext.tsx +8 -22
  120. package/src/components/chat/Icons.tsx +17 -0
  121. package/src/components/chat/Input.tsx +37 -5
  122. package/src/components/chat/Modal.tsx +115 -0
  123. package/src/components/chat/Popup.tsx +3 -3
  124. package/src/components/chat/Sidebar.tsx +4 -4
  125. package/src/components/chat/Suggestion.tsx +6 -2
  126. package/src/components/chat/Textarea.tsx +1 -1
  127. package/src/components/chat/index.tsx +1 -0
  128. package/src/components/chat/props.ts +1 -3
  129. package/src/css/input.css +18 -8
  130. package/src/css/panel.css +38 -0
  131. package/src/css/window.css +3 -1
  132. package/src/hooks/use-copilot-chat-suggestions.tsx +31 -5
  133. package/src/hooks/use-push-to-talk.tsx +162 -0
  134. package/src/styles.css +1 -0
  135. package/src/types/suggestions.ts +0 -24
  136. package/dist/chunk-5ASYNEHX.mjs +0 -53
  137. package/dist/chunk-5ASYNEHX.mjs.map +0 -1
  138. package/dist/chunk-DMAQBCTX.mjs.map +0 -1
  139. package/dist/chunk-DRNCOXZO.mjs.map +0 -1
  140. package/dist/chunk-JPX5ODUX.mjs +0 -266
  141. package/dist/chunk-JPX5ODUX.mjs.map +0 -1
  142. package/dist/chunk-KZME7C5S.mjs.map +0 -1
  143. package/dist/chunk-PEDSZYHE.mjs +0 -36
  144. package/dist/chunk-PEDSZYHE.mjs.map +0 -1
  145. package/dist/chunk-UGQQ4WEQ.mjs +0 -1
  146. package/dist/chunk-Z45ZEXJW.mjs.map +0 -1
  147. package/dist/components/chat/audio.d.ts +0 -7
  148. package/dist/components/chat/audio.js +0 -77
  149. package/dist/components/chat/audio.js.map +0 -1
  150. package/dist/components/chat/audio.mjs +0 -10
  151. package/src/components/chat/audio.ts +0 -26
  152. /package/dist/{chunk-XYM43AHP.mjs.map → chunk-7FES2IQA.mjs.map} +0 -0
  153. /package/dist/{chunk-PGZDQT74.mjs.map → chunk-CE7PJAAO.mjs.map} +0 -0
  154. /package/dist/{chunk-SKC7AJIV.mjs.map → chunk-MRFF7GSQ.mjs.map} +0 -0
  155. /package/dist/{chunk-UGQQ4WEQ.mjs.map → chunk-MRXNTQOX.mjs.map} +0 -0
  156. /package/dist/{chunk-MWFHYCQB.mjs.map → chunk-PAQWLSA4.mjs.map} +0 -0
  157. /package/dist/{chunk-A7J4KGLP.mjs.map → chunk-UPTB2MVO.mjs.map} +0 -0
  158. /package/dist/{chunk-ZKLK3M77.mjs.map → chunk-YQ3D5IQV.mjs.map} +0 -0
  159. /package/dist/components/chat/{audio.mjs.map → Modal.mjs.map} +0 -0
@@ -2,9 +2,17 @@ import React, { useEffect, useRef, useState } from "react";
2
2
  import { InputProps } from "./props";
3
3
  import { useChatContext } from "./ChatContext";
4
4
  import AutoResizingTextarea from "./Textarea";
5
+ import { usePushToTalk } from "../../hooks/use-push-to-talk";
6
+ import { useCopilotContext } from "@copilotkit/react-core";
5
7
 
6
8
  export const Input = ({ inProgress, onSend, isVisible = false }: InputProps) => {
7
9
  const context = useChatContext();
10
+ const copilotContext = useCopilotContext();
11
+
12
+ const pushToTalkConfigured =
13
+ copilotContext.copilotApiConfig.textToSpeechUrl !== undefined &&
14
+ copilotContext.copilotApiConfig.transcribeAudioUrl !== undefined;
15
+
8
16
  const textareaRef = useRef<HTMLTextAreaElement>(null);
9
17
 
10
18
  const handleDivClick = (event: React.MouseEvent<HTMLDivElement>) => {
@@ -29,14 +37,23 @@ export const Input = ({ inProgress, onSend, isVisible = false }: InputProps) =>
29
37
  }
30
38
  }, [isVisible]);
31
39
 
32
- const icon = inProgress ? context.icons.activityIcon : context.icons.sendIcon;
33
- const disabled = inProgress || text.length === 0;
40
+ const { pushToTalkState, setPushToTalkState } = usePushToTalk({
41
+ sendFunction: onSend,
42
+ inProgress,
43
+ });
44
+
45
+ const sendIcon =
46
+ inProgress || pushToTalkState === "transcribing"
47
+ ? context.icons.activityIcon
48
+ : context.icons.sendIcon;
49
+ const showPushToTalk =
50
+ pushToTalkConfigured &&
51
+ (pushToTalkState === "idle" || pushToTalkState === "recording") &&
52
+ !inProgress;
53
+ const sendDisabled = inProgress || text.length === 0 || pushToTalkState !== "idle";
34
54
 
35
55
  return (
36
56
  <div className="copilotKitInput" onClick={handleDivClick}>
37
- <button className="copilotKitSendButton" disabled={disabled} onClick={send}>
38
- {icon}
39
- </button>
40
57
  <AutoResizingTextarea
41
58
  ref={textareaRef}
42
59
  placeholder={context.labels.placeholder}
@@ -51,6 +68,21 @@ export const Input = ({ inProgress, onSend, isVisible = false }: InputProps) =>
51
68
  }
52
69
  }}
53
70
  />
71
+ <div className="copilotKitInputControls">
72
+ {showPushToTalk && (
73
+ <button
74
+ onClick={() =>
75
+ setPushToTalkState(pushToTalkState === "idle" ? "recording" : "transcribing")
76
+ }
77
+ className={pushToTalkState === "recording" ? "copilotKitPushToTalkRecording" : ""}
78
+ >
79
+ {context.icons.pushToTalkIcon}
80
+ </button>
81
+ )}
82
+ <button disabled={sendDisabled} onClick={send}>
83
+ {sendIcon}
84
+ </button>
85
+ </div>
54
86
  </div>
55
87
  );
56
88
  };
@@ -0,0 +1,115 @@
1
+ import React from "react";
2
+ import { ChatContextProvider } from "./ChatContext";
3
+ import { ButtonProps, HeaderProps, WindowProps } from "./props";
4
+ import { Window as DefaultWindow } from "./Window";
5
+ import { Button as DefaultButton } from "./Button";
6
+ import { Header as DefaultHeader } from "./Header";
7
+ import { Messages as DefaultMessages } from "./Messages";
8
+ import { Input as DefaultInput } from "./Input";
9
+ import { ResponseButton as DefaultResponseButton } from "./Response";
10
+ import { CopilotChat, CopilotChatProps } from "./Chat";
11
+
12
+ export interface CopilotModalProps extends CopilotChatProps {
13
+ /**
14
+ * Whether the chat window should be open by default.
15
+ * @default false
16
+ */
17
+ defaultOpen?: boolean;
18
+
19
+ /**
20
+ * If the chat window should close when the user clicks outside of it.
21
+ * @default true
22
+ */
23
+ clickOutsideToClose?: boolean;
24
+
25
+ /**
26
+ * If the chat window should close when the user hits the Escape key.
27
+ * @default true
28
+ */
29
+ hitEscapeToClose?: boolean;
30
+
31
+ /**
32
+ * The shortcut key to open the chat window.
33
+ * Uses Command-[shortcut] on a Mac and Ctrl-[shortcut] on Windows.
34
+ * @default "/"
35
+ */
36
+ shortcut?: string;
37
+
38
+ /**
39
+ * A callback that gets called when the chat window opens or closes.
40
+ */
41
+ onSetOpen?: (open: boolean) => void;
42
+
43
+ /**
44
+ * A custom Window component to use instead of the default.
45
+ */
46
+ Window?: React.ComponentType<WindowProps>;
47
+
48
+ /**
49
+ * A custom Button component to use instead of the default.
50
+ */
51
+ Button?: React.ComponentType<ButtonProps>;
52
+
53
+ /**
54
+ * A custom Header component to use instead of the default.
55
+ */
56
+ Header?: React.ComponentType<HeaderProps>;
57
+ }
58
+
59
+ export const CopilotModal = ({
60
+ instructions,
61
+ defaultOpen = false,
62
+ clickOutsideToClose = true,
63
+ hitEscapeToClose = true,
64
+ onSetOpen,
65
+ onSubmitMessage,
66
+ shortcut = "/",
67
+ icons,
68
+ labels,
69
+ makeSystemMessage,
70
+ showResponseButton = true,
71
+ onInProgress,
72
+ Window = DefaultWindow,
73
+ Button = DefaultButton,
74
+ Header = DefaultHeader,
75
+ Messages = DefaultMessages,
76
+ Input = DefaultInput,
77
+ ResponseButton = DefaultResponseButton,
78
+ className,
79
+ children,
80
+ }: CopilotModalProps) => {
81
+ const [openState, setOpenState] = React.useState(defaultOpen);
82
+
83
+ const setOpen = (open: boolean) => {
84
+ onSetOpen?.(open);
85
+ setOpenState(open);
86
+ };
87
+
88
+ return (
89
+ <ChatContextProvider icons={icons} labels={labels} open={openState} setOpen={setOpenState}>
90
+ {children}
91
+ <div className={className}>
92
+ <Button open={openState} setOpen={setOpen}></Button>
93
+ <Window
94
+ open={openState}
95
+ setOpen={setOpen}
96
+ clickOutsideToClose={clickOutsideToClose}
97
+ shortcut={shortcut}
98
+ hitEscapeToClose={hitEscapeToClose}
99
+ >
100
+ <Header open={openState} setOpen={setOpen} />
101
+ <CopilotChat
102
+ instructions={instructions}
103
+ makeSystemMessage={makeSystemMessage}
104
+ onInProgress={onInProgress}
105
+ onSubmitMessage={onSubmitMessage}
106
+ showResponseButton={showResponseButton}
107
+ Messages={Messages}
108
+ Input={Input}
109
+ ResponseButton={ResponseButton}
110
+ />
111
+ </Window>
112
+ </div>
113
+ </ChatContextProvider>
114
+ );
115
+ };
@@ -48,12 +48,12 @@
48
48
  * ```
49
49
  */
50
50
 
51
- import { CopilotChat, CopilotChatProps } from "./Chat";
51
+ import { CopilotModal, CopilotModalProps } from "./Modal";
52
52
 
53
- export function CopilotPopup(props: CopilotChatProps) {
53
+ export function CopilotPopup(props: CopilotModalProps) {
54
54
  props = {
55
55
  ...props,
56
56
  className: props.className ? props.className + " copilotKitPopup" : "copilotKitPopup",
57
57
  };
58
- return <CopilotChat {...props}>{props.children}</CopilotChat>;
58
+ return <CopilotModal {...props}>{props.children}</CopilotModal>;
59
59
  }
@@ -56,9 +56,9 @@
56
56
  * ```
57
57
  */
58
58
  import React, { useState } from "react";
59
- import { CopilotChat, CopilotChatProps } from "./Chat";
59
+ import { CopilotModal, CopilotModalProps } from "./Modal";
60
60
 
61
- export function CopilotSidebar(props: CopilotChatProps) {
61
+ export function CopilotSidebar(props: CopilotModalProps) {
62
62
  props = {
63
63
  ...props,
64
64
  className: props.className ? props.className + " copilotKitSidebar" : "copilotKitSidebar",
@@ -74,9 +74,9 @@ export function CopilotSidebar(props: CopilotChatProps) {
74
74
 
75
75
  return (
76
76
  <div className={`copilotKitSidebarContentWrapper ${expandedClassName}`}>
77
- <CopilotChat {...props} {...{ onSetOpen }}>
77
+ <CopilotModal {...props} {...{ onSetOpen }}>
78
78
  {props.children}
79
- </CopilotChat>
79
+ </CopilotModal>
80
80
  </div>
81
81
  );
82
82
  }
@@ -1,7 +1,11 @@
1
- import { CopilotContextParams, extract } from "@copilotkit/react-core";
1
+ import {
2
+ CopilotContextParams,
3
+ extract,
4
+ CopilotChatSuggestionConfiguration,
5
+ } from "@copilotkit/react-core";
2
6
  import { SuggestionsProps } from "./props";
3
7
  import { SmallSpinnerIcon } from "./Icons";
4
- import { CopilotChatSuggestion, CopilotChatSuggestionConfiguration } from "../../types/suggestions";
8
+ import { CopilotChatSuggestion } from "../../types/suggestions";
5
9
 
6
10
  export function Suggestion({ title, message, onClick, partial, className }: SuggestionsProps) {
7
11
  return (
@@ -48,7 +48,7 @@ const AutoResizingTextarea = forwardRef<HTMLTextAreaElement, AutoResizingTextare
48
48
  onKeyDown={onKeyDown}
49
49
  placeholder={placeholder}
50
50
  style={{
51
- overflow: "hidden",
51
+ overflow: "auto",
52
52
  resize: "none",
53
53
  maxHeight: `${maxHeight}px`,
54
54
  }}
@@ -1,4 +1,5 @@
1
1
  export * from "./props";
2
2
  export { CopilotPopup } from "./Popup";
3
3
  export { CopilotSidebar } from "./Sidebar";
4
+ export { CopilotChat } from "./Chat";
4
5
  export { useChatContext } from "./ChatContext";
@@ -3,8 +3,6 @@ import { Message } from "@copilotkit/shared";
3
3
  export interface ButtonProps {
4
4
  open: boolean;
5
5
  setOpen: (open: boolean) => void;
6
- pushToTalk: boolean;
7
- setPushToTalk: (pushToTalk: boolean) => void;
8
6
  }
9
7
 
10
8
  export interface WindowProps {
@@ -37,7 +35,7 @@ export interface MessagesProps {
37
35
 
38
36
  export interface InputProps {
39
37
  inProgress: boolean;
40
- onSend: (text: string) => void;
38
+ onSend: (text: string) => Promise<Message>;
41
39
  isVisible?: boolean;
42
40
  }
43
41
 
package/src/css/input.css CHANGED
@@ -1,7 +1,7 @@
1
1
  .copilotKitInput {
2
2
  border-top: 1px solid var(--copilot-kit-input-separator-color);
3
3
  padding-left: 2rem;
4
- padding-right: 2.5rem;
4
+ padding-right: 1rem;
5
5
  padding-top: 1rem;
6
6
  padding-bottom: 1rem;
7
7
  display: flex;
@@ -13,9 +13,7 @@
13
13
  background-color: var(--copilot-kit-input-background-color);
14
14
  }
15
15
 
16
- .copilotKitInput > button {
17
- position: absolute;
18
- right: 0.5rem;
16
+ .copilotKitInput > .copilotKitInputControls > button {
19
17
  padding: 0.25rem;
20
18
  cursor: pointer;
21
19
  transition-property: transform;
@@ -38,22 +36,27 @@
38
36
  text-shadow: none;
39
37
  display: inline-block;
40
38
  text-align: center;
39
+ margin-left: 0.5rem; /* Add margin to separate button from textarea */
41
40
  }
42
41
 
43
- .copilotKitInput > button:not([disabled]) {
42
+ .copilotKitInput > .copilotKitInputControls > button:not([disabled]) {
44
43
  color: var(--copilot-kit-input-send-button-color);
45
44
  }
46
45
 
47
- .copilotKitInput > button:not([disabled]):hover {
46
+ .copilotKitInput > .copilotKitInputControls > button:not([disabled]):hover {
48
47
  transform: scale(1.1);
49
48
  }
50
49
 
51
- .copilotKitInput > button[disabled] {
50
+ .copilotKitInput > .copilotKitInputControls > button[disabled] {
52
51
  color: var(--copilot-kit-input-send-button-disabled-color);
53
52
  }
54
53
 
54
+ .copilotKitInputControls {
55
+ display: flex;
56
+ }
57
+
55
58
  .copilotKitInput > textarea {
56
- width: 100%;
59
+ flex: 1; /* Allow textarea to take up remaining space */
57
60
  outline: 2px solid transparent;
58
61
  outline-offset: 2px;
59
62
  resize: none;
@@ -77,3 +80,10 @@
77
80
  color: var(--copilot-kit-input-placeholder-color);
78
81
  opacity: 1;
79
82
  }
83
+
84
+ .copilotKitInput > .copilotKitInputControls > button.copilotKitPushToTalkRecording {
85
+ background-color: red;
86
+ color: white;
87
+ border-radius: 50%;
88
+ animation: copilotKitPulseAnimation 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
89
+ }
@@ -0,0 +1,38 @@
1
+ .copilotKitPanel {
2
+ z-index: 30;
3
+ line-height: 1.5;
4
+ -webkit-text-size-adjust: 100%;
5
+ -moz-tab-size: 4;
6
+ -o-tab-size: 4;
7
+ tab-size: 4;
8
+ font-family:
9
+ ui-sans-serif,
10
+ system-ui,
11
+ -apple-system,
12
+ BlinkMacSystemFont,
13
+ "Segoe UI",
14
+ Roboto,
15
+ "Helvetica Neue",
16
+ Arial,
17
+ "Noto Sans",
18
+ sans-serif,
19
+ "Apple Color Emoji",
20
+ "Segoe UI Emoji",
21
+ "Segoe UI Symbol",
22
+ "Noto Color Emoji";
23
+ font-feature-settings: normal;
24
+ font-variation-settings: normal;
25
+ touch-action: manipulation;
26
+ display: flex;
27
+ flex-direction: column;
28
+ height: 100%;
29
+ }
30
+
31
+ .copilotKitPanel svg {
32
+ display: inline-block;
33
+ vertical-align: middle;
34
+ }
35
+
36
+ .copilotKitPanel .copilotKitMessages {
37
+ flex-grow: 1;
38
+ }
@@ -7,7 +7,9 @@
7
7
  border-radius: 0.75rem;
8
8
  box-shadow: rgba(0, 0, 0, 0.16) 0px 5px 40px;
9
9
  flex-direction: column;
10
- transition: opacity 100ms ease-out, transform 200ms ease-out;
10
+ transition:
11
+ opacity 100ms ease-out,
12
+ transform 200ms ease-out;
11
13
  opacity: 0;
12
14
  transform: scale(0.95) translateY(20px);
13
15
  display: flex;
@@ -1,6 +1,11 @@
1
1
  /**
2
2
  * A hook for providing suggestions to the user in the Copilot chat.
3
3
  *
4
+ * <Warning>
5
+ * useCopilotChatSuggestions is experimental. The interface is not final and
6
+ * can change without further notice.
7
+ * </Warning>
8
+ *
4
9
  * <img src="/images/useCopilotChatSuggestions/use-copilot-chat-suggestions.gif" width="500" />
5
10
  *
6
11
  * <img referrerPolicy="no-referrer-when-downgrade" src="https://static.scarf.sh/a.png?x-pxid=a9b290bb-38f9-4518-ac3b-8f54fdbf43be" />
@@ -61,7 +66,28 @@
61
66
  import { useEffect } from "react";
62
67
  import { useChatContext } from "../components";
63
68
  import { nanoid } from "nanoid";
64
- import { CopilotChatSuggestionConfiguration } from "../types/suggestions";
69
+ import { CopilotChatSuggestionConfiguration, useCopilotContext } from "@copilotkit/react-core";
70
+
71
+ interface UseCopilotChatSuggestionsConfiguration {
72
+ /**
73
+ * A prompt or instructions for the GPT to generate suggestions.
74
+ */
75
+ instructions: string;
76
+ /**
77
+ * The minimum number of suggestions to generate. Defaults to `1`.
78
+ * @default 1
79
+ */
80
+ minSuggestions?: number;
81
+ /**
82
+ * The maximum number of suggestions to generate. Defaults to `3`.
83
+ * @default 1
84
+ */
85
+ maxSuggestions?: number;
86
+ /**
87
+ * An optional class name to apply to the suggestions.
88
+ */
89
+ className?: string;
90
+ }
65
91
 
66
92
  export function useCopilotChatSuggestions(
67
93
  {
@@ -69,15 +95,15 @@ export function useCopilotChatSuggestions(
69
95
  className,
70
96
  minSuggestions = 1,
71
97
  maxSuggestions = 3,
72
- }: CopilotChatSuggestionConfiguration,
98
+ }: UseCopilotChatSuggestionsConfiguration,
73
99
  dependencies: any[] = [],
74
100
  ) {
75
- const chatContext = useChatContext();
101
+ const context = useCopilotContext();
76
102
 
77
103
  useEffect(() => {
78
104
  const id = nanoid();
79
105
 
80
- chatContext.addChatSuggestionConfiguration(id, {
106
+ context.addChatSuggestionConfiguration(id, {
81
107
  instructions,
82
108
  minSuggestions,
83
109
  maxSuggestions,
@@ -85,7 +111,7 @@ export function useCopilotChatSuggestions(
85
111
  });
86
112
 
87
113
  return () => {
88
- chatContext.removeChatSuggestionConfiguration(id);
114
+ context.removeChatSuggestionConfiguration(id);
89
115
  };
90
116
  }, dependencies);
91
117
  }
@@ -0,0 +1,162 @@
1
+ import { CopilotContextParams, useCopilotContext } from "@copilotkit/react-core";
2
+ import { Message } from "@copilotkit/shared";
3
+ import { MutableRefObject, useEffect, useRef, useState } from "react";
4
+
5
+ export const checkMicrophonePermission = async () => {
6
+ try {
7
+ const permissionStatus = await navigator.permissions.query({
8
+ name: "microphone" as PermissionName,
9
+ });
10
+ if (permissionStatus.state === "granted") {
11
+ return true;
12
+ } else {
13
+ return false;
14
+ }
15
+ } catch (err) {
16
+ console.error("Error checking microphone permission", err);
17
+ }
18
+ };
19
+
20
+ export const requestMicAndPlaybackPermission = async () => {
21
+ try {
22
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
23
+ const audioContext = new window.AudioContext();
24
+ await audioContext.resume();
25
+ return { stream, audioContext };
26
+ } catch (err) {
27
+ console.error("Error requesting microphone and playback permissions", err);
28
+ return null;
29
+ }
30
+ };
31
+
32
+ const startRecording = async (
33
+ mediaStreamRef: MutableRefObject<MediaStream | null>,
34
+ mediaRecorderRef: MutableRefObject<MediaRecorder | null>,
35
+ audioContextRef: MutableRefObject<AudioContext | null>,
36
+ recordedChunks: Blob[],
37
+ onStop: () => void,
38
+ ) => {
39
+ if (!mediaStreamRef.current || !audioContextRef.current) {
40
+ mediaStreamRef.current = await navigator.mediaDevices.getUserMedia({ audio: true });
41
+ audioContextRef.current = new window.AudioContext();
42
+ await audioContextRef.current.resume();
43
+ }
44
+
45
+ mediaRecorderRef.current = new MediaRecorder(mediaStreamRef.current!);
46
+ mediaRecorderRef.current.start(1000);
47
+ mediaRecorderRef.current.ondataavailable = (event) => {
48
+ recordedChunks.push(event.data);
49
+ };
50
+ mediaRecorderRef.current.onstop = onStop;
51
+ };
52
+
53
+ const stopRecording = (mediaRecorderRef: MutableRefObject<MediaRecorder | null>) => {
54
+ if (mediaRecorderRef.current && mediaRecorderRef.current.state !== "inactive") {
55
+ mediaRecorderRef.current.stop();
56
+ }
57
+ };
58
+
59
+ const transcribeAudio = async (recordedChunks: Blob[], transcribeAudioUrl: string) => {
60
+ const completeBlob = new Blob(recordedChunks, { type: "audio/mp4" });
61
+ const formData = new FormData();
62
+ formData.append("file", completeBlob, "recording.mp4");
63
+
64
+ const response = await fetch(transcribeAudioUrl, {
65
+ method: "POST",
66
+ body: formData,
67
+ });
68
+
69
+ if (!response.ok) {
70
+ throw new Error(`Error: ${response.statusText}`);
71
+ }
72
+
73
+ const transcription = await response.json();
74
+ return transcription.text;
75
+ };
76
+
77
+ const playAudioResponse = (text: string, textToSpeechUrl: string, audioContext: AudioContext) => {
78
+ const encodedText = encodeURIComponent(text);
79
+ const url = `${textToSpeechUrl}?text=${encodedText}`;
80
+
81
+ fetch(url)
82
+ .then((response) => response.arrayBuffer())
83
+ .then((arrayBuffer) => audioContext.decodeAudioData(arrayBuffer))
84
+ .then((audioBuffer) => {
85
+ const source = audioContext.createBufferSource();
86
+ source.buffer = audioBuffer;
87
+ source.connect(audioContext.destination);
88
+ source.start(0);
89
+ })
90
+ .catch((error) => {
91
+ console.error("Error with decoding audio data", error);
92
+ });
93
+ };
94
+
95
+ export type PushToTalkState = "idle" | "recording" | "transcribing";
96
+
97
+ export type SendFunction = (text: string) => Promise<Message>;
98
+
99
+ export const usePushToTalk = ({
100
+ sendFunction,
101
+ inProgress,
102
+ }: {
103
+ sendFunction: SendFunction;
104
+ inProgress: boolean;
105
+ }) => {
106
+ const [pushToTalkState, setPushToTalkState] = useState<PushToTalkState>("idle");
107
+ const mediaStreamRef = useRef<MediaStream | null>(null);
108
+ const audioContextRef = useRef<AudioContext | null>(null);
109
+ const mediaRecorderRef = useRef<MediaRecorder | null>(null);
110
+ const recordedChunks = useRef<Blob[]>([]);
111
+ const context = useCopilotContext();
112
+ const [startReadingFromMessageId, setStartReadingFromMessageId] = useState<string | null>(null);
113
+
114
+ useEffect(() => {
115
+ if (pushToTalkState === "recording") {
116
+ startRecording(
117
+ mediaStreamRef,
118
+ mediaRecorderRef,
119
+ audioContextRef,
120
+ recordedChunks.current,
121
+ () => {
122
+ setPushToTalkState("transcribing");
123
+ },
124
+ );
125
+ } else {
126
+ stopRecording(mediaRecorderRef);
127
+ if (pushToTalkState === "transcribing") {
128
+ transcribeAudio(recordedChunks.current, context.copilotApiConfig.transcribeAudioUrl!).then(
129
+ async (transcription) => {
130
+ recordedChunks.current = [];
131
+ setPushToTalkState("idle");
132
+ const message = await sendFunction(transcription);
133
+ setStartReadingFromMessageId(message.id);
134
+ },
135
+ );
136
+ }
137
+ }
138
+
139
+ return () => {
140
+ stopRecording(mediaRecorderRef);
141
+ };
142
+ }, [pushToTalkState]);
143
+
144
+ useEffect(() => {
145
+ if (inProgress === false && startReadingFromMessageId) {
146
+ const lastMessageIndex = context.messages.findIndex(
147
+ (message) => message.id === startReadingFromMessageId,
148
+ );
149
+
150
+ const messagesAfterLast = context.messages
151
+ .slice(lastMessageIndex + 1)
152
+ .filter((message) => message.role === "assistant" && message.content);
153
+
154
+ const text = messagesAfterLast.map((message) => message.content).join("\n");
155
+ playAudioResponse(text, context.copilotApiConfig.textToSpeechUrl!, audioContextRef.current!);
156
+
157
+ setStartReadingFromMessageId(null);
158
+ }
159
+ }, [startReadingFromMessageId, inProgress]);
160
+
161
+ return { pushToTalkState, setPushToTalkState };
162
+ };
package/src/styles.css CHANGED
@@ -10,6 +10,7 @@
10
10
  @import "./css/response.css";
11
11
  @import "./css/markdown.css";
12
12
  @import "./css/suggestions.css";
13
+ @import "./css/panel.css";
13
14
 
14
15
  @tailwind base;
15
16
  @tailwind components;
@@ -1,27 +1,3 @@
1
- export interface CopilotChatSuggestionConfiguration {
2
- /**
3
- * A prompt or instructions for the GPT to generate suggestions.
4
- */
5
- instructions: string;
6
-
7
- /**
8
- * The minimum number of suggestions to generate. Defaults to `1`.
9
- * @default 1
10
- */
11
- minSuggestions?: number;
12
-
13
- /**
14
- * The maximum number of suggestions to generate. Defaults to `3`.
15
- * @default 1
16
- */
17
- maxSuggestions?: number;
18
-
19
- /**
20
- * An optional class name to apply to the suggestions.
21
- */
22
- className?: string;
23
- }
24
-
25
1
  export interface CopilotChatSuggestion {
26
2
  title: string;
27
3
  message: string;