1
+ import { useDebouncedCallback } from "use-debounce" ;
1
2
import VoiceIcon from "@/app/icons/voice.svg" ;
2
3
import VoiceOffIcon from "@/app/icons/voice-off.svg" ;
3
- import Close24Icon from "@/app/icons/close-24.svg" ;
4
4
import PowerIcon from "@/app/icons/power.svg" ;
5
5
6
6
import styles from "./realtime-chat.module.scss" ;
@@ -60,6 +60,7 @@ export function RealtimeChat({
60
60
const apiKey = accessStore . openaiApiKey ;
61
61
62
62
const handleConnect = async ( ) => {
63
+ if ( isConnecting ) return ;
63
64
if ( ! isConnected ) {
64
65
try {
65
66
setIsConnecting ( true ) ;
@@ -230,215 +231,34 @@ export function RealtimeChat({
230
231
}
231
232
} ;
232
233
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
+ } ;
239
243
240
- initAudioHandler ( ) . catch ( console . error ) ;
244
+ initAudioHandler ( ) . catch ( console . error ) ;
241
245
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
+ ) ;
439
256
440
- const handleClose = ( ) => {
257
+ const handleClose = async ( ) => {
441
258
onClose ?.( ) ;
259
+ if ( isRecording ) {
260
+ toggleRecording ( ) ;
261
+ }
442
262
disconnect ( ) ;
443
263
} ;
444
264
@@ -454,35 +274,18 @@ export function RealtimeChat({
454
274
< div className = { styles [ "bottom-icons" ] } >
455
275
< div >
456
276
< IconButton
457
- icon = { isRecording ? < VoiceOffIcon /> : < VoiceIcon /> }
277
+ icon = { ! isRecording ? < VoiceOffIcon /> : < VoiceIcon /> }
458
278
onClick = { toggleRecording }
459
279
disabled = { ! isConnected }
460
- bordered
461
- shadow
280
+ type = { isConnecting || isConnected ? "danger" : "primary" }
462
281
/>
463
282
</ div >
464
- < div className = { styles [ "icon-center" ] } >
283
+ < div className = { styles [ "icon-center" ] } > </ div >
284
+ < div >
465
285
< IconButton
466
286
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 /> }
483
287
onClick = { handleClose }
484
- bordered
485
- shadow
288
+ type = { isConnecting || isConnected ? "danger" : "primary" }
486
289
/>
487
290
</ div >
488
291
</ div >
0 commit comments