feat: realtime config

This commit is contained in:
Dogtiti
2024-11-07 21:28:23 +08:00
parent 283caba8ce
commit e44ebe3f0e
7 changed files with 320 additions and 58 deletions

View File

@@ -1,4 +1,3 @@
import { useDebouncedCallback } from "use-debounce";
import VoiceIcon from "@/app/icons/voice.svg";
import VoiceOffIcon from "@/app/icons/voice-off.svg";
import PowerIcon from "@/app/icons/power.svg";
@@ -8,12 +7,7 @@ import clsx from "clsx";
import { useState, useRef, useEffect } from "react";
import {
useAccessStore,
useChatStore,
ChatMessage,
createMessage,
} from "@/app/store";
import { useChatStore, createMessage, useAppConfig } from "@/app/store";
import { IconButton } from "@/app/components/button";
@@ -23,7 +17,6 @@ import {
RTInputAudioItem,
RTResponse,
TurnDetection,
Voice,
} from "rt-client";
import { AudioHandler } from "@/app/lib/audio";
import { uploadImage } from "@/app/utils/chat";
@@ -39,41 +32,40 @@ export function RealtimeChat({
onStartVoice,
onPausedVoice,
}: RealtimeChatProps) {
const currentItemId = useRef<string>("");
const currentBotMessage = useRef<ChatMessage | null>();
const currentUserMessage = useRef<ChatMessage | null>();
const accessStore = useAccessStore.getState();
const chatStore = useChatStore();
const session = chatStore.currentSession();
const config = useAppConfig();
const [status, setStatus] = useState("");
const [isRecording, setIsRecording] = useState(false);
const [isConnected, setIsConnected] = useState(false);
const [isConnecting, setIsConnecting] = useState(false);
const [modality, setModality] = useState("audio");
const [isAzure, setIsAzure] = useState(false);
const [endpoint, setEndpoint] = useState("");
const [deployment, setDeployment] = useState("");
const [useVAD, setUseVAD] = useState(true);
const [voice, setVoice] = useState<Voice>("alloy");
const [temperature, setTemperature] = useState(0.9);
const clientRef = useRef<RTClient | null>(null);
const audioHandlerRef = useRef<AudioHandler | null>(null);
const initRef = useRef(false);
const apiKey = accessStore.openaiApiKey;
const temperature = config.realtimeConfig.temperature;
const apiKey = config.realtimeConfig.apiKey;
const model = config.realtimeConfig.model;
const azure = config.realtimeConfig.provider === "Azure";
const azureEndpoint = config.realtimeConfig.azure.endpoint;
const azureDeployment = config.realtimeConfig.azure.deployment;
const voice = config.realtimeConfig.voice;
const handleConnect = async () => {
if (isConnecting) return;
if (!isConnected) {
try {
setIsConnecting(true);
clientRef.current = isAzure
? new RTClient(new URL(endpoint), { key: apiKey }, { deployment })
: new RTClient(
clientRef.current = azure
? new RTClient(
new URL(azureEndpoint),
{ key: apiKey },
{ model: "gpt-4o-realtime-preview-2024-10-01" },
);
{ deployment: azureDeployment },
)
: new RTClient({ key: apiKey }, { model });
const modalities: Modality[] =
modality === "audio" ? ["text", "audio"] : ["text"];
const turnDetection: TurnDetection = useVAD
@@ -191,7 +183,6 @@ export function RealtimeChat({
const blob = audioHandlerRef.current?.savePlayFile();
uploadImage(blob!).then((audio_url) => {
botMessage.audio_url = audio_url;
// botMessage.date = new Date().toLocaleString();
// update text and audio_url
chatStore.updateTargetSession(session, (session) => {
session.messages = session.messages.concat();
@@ -258,31 +249,32 @@ export function RealtimeChat({
}
};
useEffect(
useDebouncedCallback(() => {
const initAudioHandler = async () => {
const handler = new AudioHandler();
await handler.initialize();
audioHandlerRef.current = handler;
await handleConnect();
await toggleRecording();
};
useEffect(() => {
// 防止重复初始化
if (initRef.current) return;
initRef.current = true;
initAudioHandler().catch((error) => {
setStatus(error);
console.error(error);
});
const initAudioHandler = async () => {
const handler = new AudioHandler();
await handler.initialize();
audioHandlerRef.current = handler;
await handleConnect();
await toggleRecording();
};
return () => {
if (isRecording) {
toggleRecording();
}
audioHandlerRef.current?.close().catch(console.error);
disconnect();
};
}),
[],
);
initAudioHandler().catch((error) => {
setStatus(error);
console.error(error);
});
return () => {
if (isRecording) {
toggleRecording();
}
audioHandlerRef.current?.close().catch(console.error);
disconnect();
};
}, []);
// update session params
useEffect(() => {
@@ -304,7 +296,7 @@ export function RealtimeChat({
<div className={styles["realtime-chat"]}>
<div
className={clsx(styles["circle-mic"], {
[styles["pulse"]]: true,
[styles["pulse"]]: isRecording,
})}
>
<div className={styles["icon-center"]}></div>
@@ -312,10 +304,11 @@ export function RealtimeChat({
<div className={styles["bottom-icons"]}>
<div>
<IconButton
icon={isRecording ? <VoiceOffIcon /> : <VoiceIcon />}
icon={isRecording ? <VoiceIcon /> : <VoiceOffIcon />}
onClick={toggleRecording}
disabled={!isConnected}
type={isRecording ? "danger" : isConnected ? "primary" : null}
shadow
bordered
/>
</div>
<div className={styles["icon-center"]}>{status}</div>
@@ -323,7 +316,8 @@ export function RealtimeChat({
<IconButton
icon={<PowerIcon />}
onClick={handleClose}
type={isConnecting || isConnected ? "danger" : "primary"}
shadow
bordered
/>
</div>
</div>