@copilotkit/react-ui 1.56.0 → 1.56.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/dist/index.cjs +22 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +2 -1
- package/dist/index.css.map +1 -1
- package/dist/index.d.cts +36 -3
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +36 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +22 -14
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +23 -14
- package/dist/index.umd.js.map +1 -1
- package/package.json +4 -4
- package/src/components/chat/Markdown.tsx +9 -2
- package/src/components/chat/Messages.tsx +3 -3
- package/src/components/chat/index.tsx +2 -0
- package/src/components/dev-console/console.tsx +4 -1
- package/src/css/messages.css +2 -1
- package/src/hooks/__tests__/use-push-to-talk.test.ts +49 -0
- package/src/hooks/use-push-to-talk.tsx +19 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@copilotkit/react-ui",
|
|
3
|
-
"version": "1.56.
|
|
3
|
+
"version": "1.56.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -48,9 +48,9 @@
|
|
|
48
48
|
"rehype-raw": "^7.0.0",
|
|
49
49
|
"remark-gfm": "^4.0.1",
|
|
50
50
|
"remark-math": "^6.0.0",
|
|
51
|
-
"@copilotkit/react-core": "1.56.
|
|
52
|
-
"@copilotkit/runtime-client-gql": "1.56.
|
|
53
|
-
"@copilotkit/shared": "1.56.
|
|
51
|
+
"@copilotkit/react-core": "1.56.1",
|
|
52
|
+
"@copilotkit/runtime-client-gql": "1.56.1",
|
|
53
|
+
"@copilotkit/shared": "1.56.1"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@types/react": "^19.1.0",
|
|
@@ -126,15 +126,21 @@ const MemoizedReactMarkdown: FC<Options> = memo(
|
|
|
126
126
|
ReactMarkdown,
|
|
127
127
|
(prevProps, nextProps) =>
|
|
128
128
|
prevProps.children === nextProps.children &&
|
|
129
|
-
prevProps.components === nextProps.components
|
|
129
|
+
prevProps.components === nextProps.components &&
|
|
130
|
+
prevProps.urlTransform === nextProps.urlTransform,
|
|
130
131
|
);
|
|
131
132
|
|
|
132
133
|
type MarkdownProps = {
|
|
133
134
|
content: string;
|
|
134
135
|
components?: Components;
|
|
136
|
+
urlTransform?: Options["urlTransform"];
|
|
135
137
|
};
|
|
136
138
|
|
|
137
|
-
export const Markdown = ({
|
|
139
|
+
export const Markdown = ({
|
|
140
|
+
content,
|
|
141
|
+
components,
|
|
142
|
+
urlTransform,
|
|
143
|
+
}: MarkdownProps) => {
|
|
138
144
|
const mergedComponents = useMemo(
|
|
139
145
|
() => ({ ...defaultComponents, ...components }),
|
|
140
146
|
[components],
|
|
@@ -148,6 +154,7 @@ export const Markdown = ({ content, components }: MarkdownProps) => {
|
|
|
148
154
|
[remarkMath, { singleDollarTextMath: false }],
|
|
149
155
|
]}
|
|
150
156
|
rehypePlugins={[rehypeRaw]}
|
|
157
|
+
{...(urlTransform !== undefined ? { urlTransform } : {})}
|
|
151
158
|
>
|
|
152
159
|
{content}
|
|
153
160
|
</MemoizedReactMarkdown>
|
|
@@ -112,9 +112,9 @@ export const Messages = ({
|
|
|
112
112
|
/>
|
|
113
113
|
);
|
|
114
114
|
})}
|
|
115
|
-
{
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
{inProgress &&
|
|
116
|
+
(messages[messages.length - 1]?.role === "user" ||
|
|
117
|
+
messages[messages.length - 1]?.role === "tool") && <LoadingIcon />}
|
|
118
118
|
{interrupt}
|
|
119
119
|
{chatError && ErrorMessage && (
|
|
120
120
|
<ErrorMessage error={chatError} isCurrentMessage />
|
|
@@ -2,6 +2,8 @@ export * from "./props";
|
|
|
2
2
|
export { CopilotPopup } from "./Popup";
|
|
3
3
|
export { CopilotSidebar } from "./Sidebar";
|
|
4
4
|
export { CopilotChat } from "./Chat";
|
|
5
|
+
export { CopilotModal } from "./Modal";
|
|
6
|
+
export type { CopilotModalProps } from "./Modal";
|
|
5
7
|
export { Markdown } from "./Markdown";
|
|
6
8
|
export { AssistantMessage } from "./messages/AssistantMessage";
|
|
7
9
|
export { UserMessage } from "./messages/UserMessage";
|
|
@@ -84,13 +84,16 @@ export function CopilotDevConsole() {
|
|
|
84
84
|
};
|
|
85
85
|
|
|
86
86
|
useEffect(() => {
|
|
87
|
+
if (!showDevConsole) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
87
90
|
if (dontRunTwiceInDevMode.current === true) {
|
|
88
91
|
return;
|
|
89
92
|
}
|
|
90
93
|
dontRunTwiceInDevMode.current = true;
|
|
91
94
|
|
|
92
95
|
checkForUpdates();
|
|
93
|
-
}, []);
|
|
96
|
+
}, [showDevConsole]);
|
|
94
97
|
|
|
95
98
|
if (!showDevConsole) {
|
|
96
99
|
return null;
|
package/src/css/messages.css
CHANGED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Tests for the sendFunction handling in usePushToTalk.
|
|
5
|
+
*
|
|
6
|
+
* Issue #3011: sendFunction (wrapping sendMessage) returns Promise<void>,
|
|
7
|
+
* but the code did `const message = await sendFunction(transcription);`
|
|
8
|
+
* then `message.id` — causing a TypeError on undefined.
|
|
9
|
+
*
|
|
10
|
+
* Fix: Guard .id access with `if (message)` check, and update SendFunction
|
|
11
|
+
* type to accept `Promise<Message | void>`.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
describe("usePushToTalk sendFunction handling", () => {
|
|
15
|
+
it("should handle sendFunction returning void without crashing", async () => {
|
|
16
|
+
// Simulates what sendMessage actually returns: Promise<void>
|
|
17
|
+
const sendFunction = vi.fn().mockResolvedValue(undefined);
|
|
18
|
+
let startReadingFromMessageId: string | null = null;
|
|
19
|
+
|
|
20
|
+
const transcription = "Hello world";
|
|
21
|
+
const message = await sendFunction(transcription);
|
|
22
|
+
|
|
23
|
+
// Apply the same guard as the fix
|
|
24
|
+
if (message) {
|
|
25
|
+
startReadingFromMessageId = message.id;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Should not have set the message id (because message is void)
|
|
29
|
+
expect(startReadingFromMessageId).toBeNull();
|
|
30
|
+
expect(sendFunction).toHaveBeenCalledWith(transcription);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("should use message.id when sendFunction returns a message", async () => {
|
|
34
|
+
const sendFunction = vi.fn().mockResolvedValue({
|
|
35
|
+
id: "msg-123",
|
|
36
|
+
content: "test",
|
|
37
|
+
role: "user",
|
|
38
|
+
});
|
|
39
|
+
let startReadingFromMessageId: string | null = null;
|
|
40
|
+
|
|
41
|
+
const message = await sendFunction("Hello");
|
|
42
|
+
|
|
43
|
+
if (message) {
|
|
44
|
+
startReadingFromMessageId = message.id;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
expect(startReadingFromMessageId).toBe("msg-123");
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -58,6 +58,7 @@ const startRecording = async (
|
|
|
58
58
|
|
|
59
59
|
const stopRecording = (
|
|
60
60
|
mediaRecorderRef: MutableRefObject<MediaRecorder | null>,
|
|
61
|
+
mediaStreamRef?: MutableRefObject<MediaStream | null>,
|
|
61
62
|
) => {
|
|
62
63
|
if (
|
|
63
64
|
mediaRecorderRef.current &&
|
|
@@ -65,15 +66,22 @@ const stopRecording = (
|
|
|
65
66
|
) {
|
|
66
67
|
mediaRecorderRef.current.stop();
|
|
67
68
|
}
|
|
69
|
+
// Release microphone tracks to free the device
|
|
70
|
+
if (mediaStreamRef?.current) {
|
|
71
|
+
mediaStreamRef.current.getTracks().forEach((track) => track.stop());
|
|
72
|
+
mediaStreamRef.current = null;
|
|
73
|
+
}
|
|
68
74
|
};
|
|
69
75
|
|
|
70
76
|
const transcribeAudio = async (
|
|
71
77
|
recordedChunks: Blob[],
|
|
72
78
|
transcribeAudioUrl: string,
|
|
79
|
+
mediaType: string = "audio/mp4",
|
|
73
80
|
) => {
|
|
74
|
-
const
|
|
81
|
+
const extension = mediaType.split("/")[1] || "mp4";
|
|
82
|
+
const completeBlob = new Blob(recordedChunks, { type: mediaType });
|
|
75
83
|
const formData = new FormData();
|
|
76
|
-
formData.append("file", completeBlob,
|
|
84
|
+
formData.append("file", completeBlob, `recording.${extension}`);
|
|
77
85
|
|
|
78
86
|
const response = await fetch(transcribeAudioUrl, {
|
|
79
87
|
method: "POST",
|
|
@@ -112,14 +120,16 @@ const playAudioResponse = (
|
|
|
112
120
|
|
|
113
121
|
export type PushToTalkState = "idle" | "recording" | "transcribing";
|
|
114
122
|
|
|
115
|
-
export type SendFunction = (text: string) => Promise<Message>;
|
|
123
|
+
export type SendFunction = (text: string) => Promise<Message | void>;
|
|
116
124
|
|
|
117
125
|
export const usePushToTalk = ({
|
|
118
126
|
sendFunction,
|
|
119
127
|
inProgress,
|
|
128
|
+
mediaType = "audio/mp4",
|
|
120
129
|
}: {
|
|
121
130
|
sendFunction: SendFunction;
|
|
122
131
|
inProgress: boolean;
|
|
132
|
+
mediaType?: string;
|
|
123
133
|
}) => {
|
|
124
134
|
const [pushToTalkState, setPushToTalkState] =
|
|
125
135
|
useState<PushToTalkState>("idle");
|
|
@@ -146,22 +156,25 @@ export const usePushToTalk = ({
|
|
|
146
156
|
},
|
|
147
157
|
);
|
|
148
158
|
} else {
|
|
149
|
-
stopRecording(mediaRecorderRef);
|
|
159
|
+
stopRecording(mediaRecorderRef, mediaStreamRef);
|
|
150
160
|
if (pushToTalkState === "transcribing") {
|
|
151
161
|
transcribeAudio(
|
|
152
162
|
recordedChunks.current,
|
|
153
163
|
context.copilotApiConfig.transcribeAudioUrl!,
|
|
164
|
+
mediaType,
|
|
154
165
|
).then(async (transcription) => {
|
|
155
166
|
recordedChunks.current = [];
|
|
156
167
|
setPushToTalkState("idle");
|
|
157
168
|
const message = await sendFunction(transcription);
|
|
158
|
-
|
|
169
|
+
if (message) {
|
|
170
|
+
setStartReadingFromMessageId(message.id);
|
|
171
|
+
}
|
|
159
172
|
});
|
|
160
173
|
}
|
|
161
174
|
}
|
|
162
175
|
|
|
163
176
|
return () => {
|
|
164
|
-
stopRecording(mediaRecorderRef);
|
|
177
|
+
stopRecording(mediaRecorderRef, mediaStreamRef);
|
|
165
178
|
};
|
|
166
179
|
}, [pushToTalkState]);
|
|
167
180
|
|