Skip to content

Commit 7bdc45e

Browse files
committed
connect realtime model when open panel
1 parent 88cd3ac commit 7bdc45e

File tree

2 files changed

+41
-236
lines changed

2 files changed

+41
-236
lines changed

app/components/chat.tsx

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2035,14 +2035,16 @@ function _Chat() {
20352035
[styles["chat-side-panel-show"]]: showChatSidePanel,
20362036
})}
20372037
>
2038-
<RealtimeChat
2039-
onClose={() => {
2040-
setShowChatSidePanel(false);
2041-
}}
2042-
onStartVoice={async () => {
2043-
console.log("start voice");
2044-
}}
2045-
/>
2038+
{showChatSidePanel && (
2039+
<RealtimeChat
2040+
onClose={() => {
2041+
setShowChatSidePanel(false);
2042+
}}
2043+
onStartVoice={async () => {
2044+
console.log("start voice");
2045+
}}
2046+
/>
2047+
)}
20462048
</div>
20472049
</div>
20482050
</div>

app/components/realtime-chat/realtime-chat.tsx

Lines changed: 31 additions & 228 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1+
import { useDebouncedCallback } from "use-debounce";
12
import VoiceIcon from "@/app/icons/voice.svg";
23
import VoiceOffIcon from "@/app/icons/voice-off.svg";
3-
import Close24Icon from "@/app/icons/close-24.svg";
44
import PowerIcon from "@/app/icons/power.svg";
55

66
import styles from "./realtime-chat.module.scss";
@@ -60,6 +60,7 @@ export function RealtimeChat({
6060
const apiKey = accessStore.openaiApiKey;
6161

6262
const handleConnect = async () => {
63+
if (isConnecting) return;
6364
if (!isConnected) {
6465
try {
6566
setIsConnecting(true);
@@ -230,215 +231,34 @@ export function RealtimeChat({
230231
}
231232
};
232233

233-
useEffect(() => {
234-
const initAudioHandler = async () => {
235-
const handler = new AudioHandler();
236-
await handler.initialize();
237-
audioHandlerRef.current = handler;
238-
};
234+
useEffect(
235+
useDebouncedCallback(() => {
236+
const initAudioHandler = async () => {
237+
const handler = new AudioHandler();
238+
await handler.initialize();
239+
audioHandlerRef.current = handler;
240+
await handleConnect();
241+
await toggleRecording();
242+
};
239243

240-
initAudioHandler().catch(console.error);
244+
initAudioHandler().catch(console.error);
241245

242-
return () => {
243-
disconnect();
244-
audioHandlerRef.current?.close().catch(console.error);
245-
};
246-
}, []);
247-
248-
// useEffect(() => {
249-
// if (
250-
// clientRef.current?.getTurnDetectionType() === "server_vad" &&
251-
// audioData
252-
// ) {
253-
// // console.log("appendInputAudio", audioData);
254-
// // 将录制的16PCM音频发送给openai
255-
// clientRef.current?.appendInputAudio(audioData);
256-
// }
257-
// }, [audioData]);
258-
259-
// useEffect(() => {
260-
// console.log("isRecording", isRecording);
261-
// if (!isRecording.current) return;
262-
// if (!clientRef.current) {
263-
// const apiKey = accessStore.openaiApiKey;
264-
// const client = (clientRef.current = new RealtimeClient({
265-
// url: "wss://api.openai.com/v1/realtime",
266-
// apiKey,
267-
// dangerouslyAllowAPIKeyInBrowser: true,
268-
// debug: true,
269-
// }));
270-
// client
271-
// .connect()
272-
// .then(() => {
273-
// // TODO 设置真实的上下文
274-
// client.sendUserMessageContent([
275-
// {
276-
// type: `input_text`,
277-
// text: `Hi`,
278-
// // text: `For testing purposes, I want you to list ten car brands. Number each item, e.g. "one (or whatever number you are one): the item name".`
279-
// },
280-
// ]);
281-
282-
// // 配置服务端判断说话人开启还是结束
283-
// client.updateSession({
284-
// turn_detection: { type: "server_vad" },
285-
// });
286-
287-
// client.on("realtime.event", (realtimeEvent) => {
288-
// // 调试
289-
// console.log("realtime.event", realtimeEvent);
290-
// });
291-
292-
// client.on("conversation.interrupted", async () => {
293-
// if (currentBotMessage.current) {
294-
// stopPlaying();
295-
// try {
296-
// client.cancelResponse(
297-
// currentBotMessage.current?.id,
298-
// currentTime(),
299-
// );
300-
// } catch (e) {
301-
// console.error(e);
302-
// }
303-
// }
304-
// });
305-
// client.on("conversation.updated", async (event: any) => {
306-
// // console.log("currentSession", chatStore.currentSession());
307-
// // const items = client.conversation.getItems();
308-
// const content = event?.item?.content?.[0]?.transcript || "";
309-
// const text = event?.item?.content?.[0]?.text || "";
310-
// // console.log(
311-
// // "conversation.updated",
312-
// // event,
313-
// // "content[0]",
314-
// // event?.item?.content?.[0]?.transcript,
315-
// // "formatted",
316-
// // event?.item?.formatted?.transcript,
317-
// // "content",
318-
// // content,
319-
// // "text",
320-
// // text,
321-
// // event?.item?.status,
322-
// // event?.item?.role,
323-
// // items.length,
324-
// // items,
325-
// // );
326-
// const { item, delta } = event;
327-
// const { role, id, status, formatted } = item || {};
328-
// if (id && role == "assistant") {
329-
// if (
330-
// !currentBotMessage.current ||
331-
// currentBotMessage.current?.id != id
332-
// ) {
333-
// // create assistant message and save to session
334-
// currentBotMessage.current = createMessage({ id, role });
335-
// chatStore.updateCurrentSession((session) => {
336-
// session.messages = session.messages.concat([
337-
// currentBotMessage.current!,
338-
// ]);
339-
// });
340-
// }
341-
// if (currentBotMessage.current?.id != id) {
342-
// stopPlaying();
343-
// }
344-
// if (content) {
345-
// currentBotMessage.current.content = content;
346-
// chatStore.updateCurrentSession((session) => {
347-
// session.messages = session.messages.concat();
348-
// });
349-
// }
350-
// if (delta?.audio) {
351-
// // typeof delta.audio is Int16Array
352-
// // 直接播放
353-
// addInt16PCM(delta.audio);
354-
// }
355-
// // console.log(
356-
// // "updated try save wavFile",
357-
// // status,
358-
// // currentBotMessage.current?.audio_url,
359-
// // formatted?.audio,
360-
// // );
361-
// if (
362-
// status == "completed" &&
363-
// !currentBotMessage.current?.audio_url &&
364-
// formatted?.audio?.length
365-
// ) {
366-
// // 转换为wav文件保存 TODO 使用mp3格式会更节省空间
367-
// const botMessage = currentBotMessage.current;
368-
// const wavFile = new WavPacker().pack(sampleRate, {
369-
// bitsPerSample: 16,
370-
// channelCount: 1,
371-
// data: formatted?.audio,
372-
// });
373-
// // 这里将音频文件放到对象里面wavFile.url可以使用<audio>标签播放
374-
// item.formatted.file = wavFile;
375-
// uploadImageRemote(wavFile.blob).then((audio_url) => {
376-
// botMessage.audio_url = audio_url;
377-
// chatStore.updateCurrentSession((session) => {
378-
// session.messages = session.messages.concat();
379-
// });
380-
// });
381-
// }
382-
// if (
383-
// status == "completed" &&
384-
// !currentBotMessage.current?.content
385-
// ) {
386-
// chatStore.updateCurrentSession((session) => {
387-
// session.messages = session.messages.filter(
388-
// (m) => m.id !== currentBotMessage.current?.id,
389-
// );
390-
// });
391-
// }
392-
// }
393-
// if (id && role == "user" && !text) {
394-
// if (
395-
// !currentUserMessage.current ||
396-
// currentUserMessage.current?.id != id
397-
// ) {
398-
// // create assistant message and save to session
399-
// currentUserMessage.current = createMessage({ id, role });
400-
// chatStore.updateCurrentSession((session) => {
401-
// session.messages = session.messages.concat([
402-
// currentUserMessage.current!,
403-
// ]);
404-
// });
405-
// }
406-
// if (content) {
407-
// // 转换为wav文件保存 TODO 使用mp3格式会更节省空间
408-
// const userMessage = currentUserMessage.current;
409-
// const wavFile = new WavPacker().pack(sampleRate, {
410-
// bitsPerSample: 16,
411-
// channelCount: 1,
412-
// data: formatted?.audio,
413-
// });
414-
// // 这里将音频文件放到对象里面wavFile.url可以使用<audio>标签播放
415-
// item.formatted.file = wavFile;
416-
// uploadImageRemote(wavFile.blob).then((audio_url) => {
417-
// // update message content
418-
// userMessage.content = content;
419-
// // update message audio_url
420-
// userMessage.audio_url = audio_url;
421-
// chatStore.updateCurrentSession((session) => {
422-
// session.messages = session.messages.concat();
423-
// });
424-
// });
425-
// }
426-
// }
427-
// });
428-
// })
429-
// .catch((e) => {
430-
// console.error("Error", e);
431-
// });
432-
// }
433-
// return () => {
434-
// stop();
435-
// // TODO close client
436-
// clientRef.current?.disconnect();
437-
// };
438-
// }, [isRecording.current]);
246+
return () => {
247+
if (isRecording) {
248+
toggleRecording();
249+
}
250+
audioHandlerRef.current?.close().catch(console.error);
251+
disconnect();
252+
};
253+
}),
254+
[],
255+
);
439256

440-
const handleClose = () => {
257+
const handleClose = async () => {
441258
onClose?.();
259+
if (isRecording) {
260+
toggleRecording();
261+
}
442262
disconnect();
443263
};
444264

@@ -454,35 +274,18 @@ export function RealtimeChat({
454274
<div className={styles["bottom-icons"]}>
455275
<div>
456276
<IconButton
457-
icon={isRecording ? <VoiceOffIcon /> : <VoiceIcon />}
277+
icon={!isRecording ? <VoiceOffIcon /> : <VoiceIcon />}
458278
onClick={toggleRecording}
459279
disabled={!isConnected}
460-
bordered
461-
shadow
280+
type={isConnecting || isConnected ? "danger" : "primary"}
462281
/>
463282
</div>
464-
<div className={styles["icon-center"]}>
283+
<div className={styles["icon-center"]}></div>
284+
<div>
465285
<IconButton
466286
icon={<PowerIcon />}
467-
text={
468-
isConnecting
469-
? "Connecting..."
470-
: isConnected
471-
? "Disconnect"
472-
: "Connect"
473-
}
474-
onClick={handleConnect}
475-
disabled={isConnecting}
476-
bordered
477-
shadow
478-
/>
479-
</div>
480-
<div onClick={handleClose}>
481-
<IconButton
482-
icon={<Close24Icon />}
483287
onClick={handleClose}
484-
bordered
485-
shadow
288+
type={isConnecting || isConnected ? "danger" : "primary"}
486289
/>
487290
</div>
488291
</div>

0 commit comments

Comments
 (0)