@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.
- package/.turbo/turbo-build.log +174 -167
- package/CHANGELOG.md +12 -0
- package/dist/{chunk-DMAQBCTX.mjs → chunk-4MKP23AD.mjs} +6 -4
- package/dist/chunk-4MKP23AD.mjs.map +1 -0
- package/dist/{chunk-UVMROYDT.mjs → chunk-6XLZXLM5.mjs} +5 -5
- package/dist/{chunk-UVMROYDT.mjs.map → chunk-6XLZXLM5.mjs.map} +1 -1
- package/dist/{chunk-XYM43AHP.mjs → chunk-7FES2IQA.mjs} +3 -3
- package/dist/chunk-ANO23V2M.mjs +135 -0
- package/dist/chunk-ANO23V2M.mjs.map +1 -0
- package/dist/{chunk-DRNCOXZO.mjs → chunk-BL65ZC6L.mjs} +28 -7
- package/dist/chunk-BL65ZC6L.mjs.map +1 -0
- package/dist/{chunk-PGZDQT74.mjs → chunk-CE7PJAAO.mjs} +2 -2
- package/dist/{chunk-FWTPMPSN.mjs → chunk-FZC7X5PK.mjs} +23 -3
- package/dist/{chunk-FWTPMPSN.mjs.map → chunk-FZC7X5PK.mjs.map} +1 -1
- package/dist/{chunk-Z45ZEXJW.mjs → chunk-LTCJCXCP.mjs} +5 -8
- package/dist/chunk-LTCJCXCP.mjs.map +1 -0
- package/dist/chunk-MRFF7GSQ.mjs +1 -0
- package/dist/{chunk-SKC7AJIV.mjs → chunk-MRXNTQOX.mjs} +1 -3
- package/dist/{chunk-MWFHYCQB.mjs → chunk-PAQWLSA4.mjs} +2 -2
- package/dist/chunk-RT2XG2T7.mjs +25 -0
- package/dist/chunk-RT2XG2T7.mjs.map +1 -0
- package/dist/chunk-T3JTSIHT.mjs +93 -0
- package/dist/chunk-T3JTSIHT.mjs.map +1 -0
- package/dist/{chunk-A7J4KGLP.mjs → chunk-UPTB2MVO.mjs} +2 -2
- package/dist/{chunk-KZME7C5S.mjs → chunk-VUZA5AFH.mjs} +8 -11
- package/dist/chunk-VUZA5AFH.mjs.map +1 -0
- package/dist/{chunk-XWWMYJJF.mjs → chunk-XRODMID5.mjs} +5 -5
- package/dist/{chunk-XWWMYJJF.mjs.map → chunk-XRODMID5.mjs.map} +1 -1
- package/dist/{chunk-ZKLK3M77.mjs → chunk-YQ3D5IQV.mjs} +3 -3
- package/dist/{chunk-WM6BS77F.mjs → chunk-YQFVRDNC.mjs} +2 -2
- package/dist/{chunk-WM6BS77F.mjs.map → chunk-YQFVRDNC.mjs.map} +1 -1
- package/dist/chunk-ZO3GLN23.mjs +137 -0
- package/dist/chunk-ZO3GLN23.mjs.map +1 -0
- package/dist/components/chat/Button.d.ts +1 -1
- package/dist/components/chat/Button.js +2 -30
- package/dist/components/chat/Button.js.map +1 -1
- package/dist/components/chat/Button.mjs +4 -4
- package/dist/components/chat/Chat.d.ts +66 -47
- package/dist/components/chat/Chat.js +274 -430
- package/dist/components/chat/Chat.js.map +1 -1
- package/dist/components/chat/Chat.mjs +16 -17
- package/dist/components/chat/ChatContext.d.ts +17 -22
- package/dist/components/chat/ChatContext.js +23 -8
- package/dist/components/chat/ChatContext.js.map +1 -1
- package/dist/components/chat/ChatContext.mjs +3 -3
- package/dist/components/chat/CodeBlock.js.map +1 -1
- package/dist/components/chat/CodeBlock.mjs +3 -3
- package/dist/components/chat/Header.js.map +1 -1
- package/dist/components/chat/Header.mjs +4 -4
- package/dist/components/chat/Icons.d.ts +6 -5
- package/dist/components/chat/Icons.js +21 -0
- package/dist/components/chat/Icons.js.map +1 -1
- package/dist/components/chat/Icons.mjs +4 -2
- package/dist/components/chat/Input.js +147 -9
- package/dist/components/chat/Input.js.map +1 -1
- package/dist/components/chat/Input.mjs +6 -5
- package/dist/components/chat/Markdown.js.map +1 -1
- package/dist/components/chat/Markdown.mjs +4 -4
- package/dist/components/chat/Messages.js.map +1 -1
- package/dist/components/chat/Messages.mjs +6 -6
- package/dist/components/chat/Modal.d.ts +50 -0
- package/dist/components/chat/Modal.js +1584 -0
- package/dist/components/chat/Modal.js.map +1 -0
- package/dist/components/chat/Modal.mjs +23 -0
- package/dist/components/chat/Popup.d.ts +6 -5
- package/dist/components/chat/Popup.js +288 -249
- package/dist/components/chat/Popup.js.map +1 -1
- package/dist/components/chat/Popup.mjs +16 -15
- package/dist/components/chat/Response.js.map +1 -1
- package/dist/components/chat/Response.mjs +4 -4
- package/dist/components/chat/Sidebar.d.ts +6 -5
- package/dist/components/chat/Sidebar.js +290 -251
- package/dist/components/chat/Sidebar.js.map +1 -1
- package/dist/components/chat/Sidebar.mjs +16 -15
- package/dist/components/chat/Suggestion.d.ts +1 -2
- package/dist/components/chat/Suggestion.js.map +1 -1
- package/dist/components/chat/Suggestion.mjs +3 -3
- package/dist/components/chat/Textarea.d.ts +4 -4
- package/dist/components/chat/Textarea.js +1 -1
- package/dist/components/chat/Textarea.js.map +1 -1
- package/dist/components/chat/Textarea.mjs +2 -2
- package/dist/components/chat/Window.mjs +1 -1
- package/dist/components/chat/index.d.ts +2 -1
- package/dist/components/chat/index.js +294 -253
- package/dist/components/chat/index.js.map +1 -1
- package/dist/components/chat/index.mjs +23 -19
- package/dist/components/chat/props.d.ts +1 -3
- package/dist/components/chat/props.js.map +1 -1
- package/dist/components/index.d.ts +2 -1
- package/dist/components/index.js +294 -253
- package/dist/components/index.js.map +1 -1
- package/dist/components/index.mjs +23 -19
- package/dist/hooks/index.d.ts +0 -1
- package/dist/hooks/index.js +6 -31
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/index.mjs +2 -22
- package/dist/hooks/use-copilot-chat-suggestions.d.ts +26 -4
- package/dist/hooks/use-copilot-chat-suggestions.js +6 -31
- package/dist/hooks/use-copilot-chat-suggestions.js.map +1 -1
- package/dist/hooks/use-copilot-chat-suggestions.mjs +2 -22
- package/dist/hooks/use-copy-to-clipboard.mjs +1 -1
- package/dist/hooks/use-push-to-talk.d.ts +19 -0
- package/dist/hooks/use-push-to-talk.js +177 -0
- package/dist/hooks/use-push-to-talk.js.map +1 -0
- package/dist/hooks/use-push-to-talk.mjs +12 -0
- package/dist/hooks/use-push-to-talk.mjs.map +1 -0
- package/dist/index.css +60 -8
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +300 -258
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +28 -24
- package/dist/lib/utils.mjs +1 -1
- package/dist/types/suggestions.d.ts +1 -21
- package/dist/types/suggestions.js.map +1 -1
- package/package.json +6 -6
- package/src/components/chat/Button.tsx +2 -35
- package/src/components/chat/Chat.tsx +126 -255
- package/src/components/chat/ChatContext.tsx +8 -22
- package/src/components/chat/Icons.tsx +17 -0
- package/src/components/chat/Input.tsx +37 -5
- package/src/components/chat/Modal.tsx +115 -0
- package/src/components/chat/Popup.tsx +3 -3
- package/src/components/chat/Sidebar.tsx +4 -4
- package/src/components/chat/Suggestion.tsx +6 -2
- package/src/components/chat/Textarea.tsx +1 -1
- package/src/components/chat/index.tsx +1 -0
- package/src/components/chat/props.ts +1 -3
- package/src/css/input.css +18 -8
- package/src/css/panel.css +38 -0
- package/src/css/window.css +3 -1
- package/src/hooks/use-copilot-chat-suggestions.tsx +31 -5
- package/src/hooks/use-push-to-talk.tsx +162 -0
- package/src/styles.css +1 -0
- package/src/types/suggestions.ts +0 -24
- package/dist/chunk-5ASYNEHX.mjs +0 -53
- package/dist/chunk-5ASYNEHX.mjs.map +0 -1
- package/dist/chunk-DMAQBCTX.mjs.map +0 -1
- package/dist/chunk-DRNCOXZO.mjs.map +0 -1
- package/dist/chunk-JPX5ODUX.mjs +0 -266
- package/dist/chunk-JPX5ODUX.mjs.map +0 -1
- package/dist/chunk-KZME7C5S.mjs.map +0 -1
- package/dist/chunk-PEDSZYHE.mjs +0 -36
- package/dist/chunk-PEDSZYHE.mjs.map +0 -1
- package/dist/chunk-UGQQ4WEQ.mjs +0 -1
- package/dist/chunk-Z45ZEXJW.mjs.map +0 -1
- package/dist/components/chat/audio.d.ts +0 -7
- package/dist/components/chat/audio.js +0 -77
- package/dist/components/chat/audio.js.map +0 -1
- package/dist/components/chat/audio.mjs +0 -10
- package/src/components/chat/audio.ts +0 -26
- /package/dist/{chunk-XYM43AHP.mjs.map → chunk-7FES2IQA.mjs.map} +0 -0
- /package/dist/{chunk-PGZDQT74.mjs.map → chunk-CE7PJAAO.mjs.map} +0 -0
- /package/dist/{chunk-SKC7AJIV.mjs.map → chunk-MRFF7GSQ.mjs.map} +0 -0
- /package/dist/{chunk-UGQQ4WEQ.mjs.map → chunk-MRXNTQOX.mjs.map} +0 -0
- /package/dist/{chunk-MWFHYCQB.mjs.map → chunk-PAQWLSA4.mjs.map} +0 -0
- /package/dist/{chunk-A7J4KGLP.mjs.map → chunk-UPTB2MVO.mjs.map} +0 -0
- /package/dist/{chunk-ZKLK3M77.mjs.map → chunk-YQ3D5IQV.mjs.map} +0 -0
- /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
|
|
33
|
-
|
|
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 {
|
|
51
|
+
import { CopilotModal, CopilotModalProps } from "./Modal";
|
|
52
52
|
|
|
53
|
-
export function CopilotPopup(props:
|
|
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 <
|
|
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 {
|
|
59
|
+
import { CopilotModal, CopilotModalProps } from "./Modal";
|
|
60
60
|
|
|
61
|
-
export function CopilotSidebar(props:
|
|
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
|
-
<
|
|
77
|
+
<CopilotModal {...props} {...{ onSetOpen }}>
|
|
78
78
|
{props.children}
|
|
79
|
-
</
|
|
79
|
+
</CopilotModal>
|
|
80
80
|
</div>
|
|
81
81
|
);
|
|
82
82
|
}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
import {
|
|
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
|
|
8
|
+
import { CopilotChatSuggestion } from "../../types/suggestions";
|
|
5
9
|
|
|
6
10
|
export function Suggestion({ title, message, onClick, partial, className }: SuggestionsProps) {
|
|
7
11
|
return (
|
|
@@ -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) =>
|
|
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:
|
|
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
|
-
|
|
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
|
+
}
|
package/src/css/window.css
CHANGED
|
@@ -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:
|
|
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 "
|
|
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
|
-
}:
|
|
98
|
+
}: UseCopilotChatSuggestionsConfiguration,
|
|
73
99
|
dependencies: any[] = [],
|
|
74
100
|
) {
|
|
75
|
-
const
|
|
101
|
+
const context = useCopilotContext();
|
|
76
102
|
|
|
77
103
|
useEffect(() => {
|
|
78
104
|
const id = nanoid();
|
|
79
105
|
|
|
80
|
-
|
|
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
|
-
|
|
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
package/src/types/suggestions.ts
CHANGED
|
@@ -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;
|