重构 GlobalChatbox 组件,拆分为多个模块
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import type { SpeechState } from "./GlobalChatbox.types";
|
||||
|
||||
// WebKit Speech Recognition compatibility
|
||||
interface SpeechRecognitionEvent extends Event {
|
||||
readonly resultIndex: number;
|
||||
readonly results: SpeechRecognitionResultList;
|
||||
}
|
||||
|
||||
interface SpeechRecognition extends EventTarget {
|
||||
lang: string;
|
||||
continuous: boolean;
|
||||
interimResults: boolean;
|
||||
onresult: ((event: SpeechRecognitionEvent) => void) | null;
|
||||
onerror: ((event: Event) => void) | null;
|
||||
onend: (() => void) | null;
|
||||
start(): void;
|
||||
stop(): void;
|
||||
abort(): void;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
SpeechRecognition?: {
|
||||
new (): SpeechRecognition;
|
||||
prototype: SpeechRecognition;
|
||||
};
|
||||
webkitSpeechRecognition?: {
|
||||
new (): SpeechRecognition;
|
||||
prototype: SpeechRecognition;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function useSpeechSynthesis() {
|
||||
const [speechState, setSpeechState] = useState<SpeechState>("idle");
|
||||
const [speakingMessageId, setSpeakingMessageId] = useState<string | null>(null);
|
||||
const utteranceRef = useRef<SpeechSynthesisUtterance | null>(null);
|
||||
|
||||
const isSupported = typeof window !== "undefined" && "speechSynthesis" in window;
|
||||
|
||||
const stop = useCallback(() => {
|
||||
if (!isSupported) return;
|
||||
window.speechSynthesis.cancel();
|
||||
utteranceRef.current = null;
|
||||
setSpeechState("idle");
|
||||
setSpeakingMessageId(null);
|
||||
}, [isSupported]);
|
||||
|
||||
const speak = useCallback(
|
||||
(messageId: string, text: string) => {
|
||||
if (!isSupported || !text) return;
|
||||
window.speechSynthesis.cancel();
|
||||
|
||||
const utterance = new SpeechSynthesisUtterance(text);
|
||||
utterance.lang = "zh-CN";
|
||||
utterance.rate = 1;
|
||||
utterance.onend = () => {
|
||||
setSpeechState("idle");
|
||||
setSpeakingMessageId(null);
|
||||
utteranceRef.current = null;
|
||||
};
|
||||
utterance.onerror = () => {
|
||||
setSpeechState("idle");
|
||||
setSpeakingMessageId(null);
|
||||
utteranceRef.current = null;
|
||||
};
|
||||
utterance.onpause = () => setSpeechState("paused");
|
||||
utterance.onresume = () => setSpeechState("playing");
|
||||
|
||||
utteranceRef.current = utterance;
|
||||
setSpeakingMessageId(messageId);
|
||||
setSpeechState("playing");
|
||||
window.speechSynthesis.speak(utterance);
|
||||
},
|
||||
[isSupported],
|
||||
);
|
||||
|
||||
const pause = useCallback(() => {
|
||||
if (!isSupported) return;
|
||||
window.speechSynthesis.pause();
|
||||
}, [isSupported]);
|
||||
|
||||
const resume = useCallback(() => {
|
||||
if (!isSupported) return;
|
||||
window.speechSynthesis.resume();
|
||||
}, [isSupported]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (typeof window !== "undefined" && "speechSynthesis" in window) {
|
||||
window.speechSynthesis.cancel();
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return { speechState, speakingMessageId, speak, pause, resume, stop, isSupported };
|
||||
}
|
||||
|
||||
export function useSpeechRecognition(onResult: (text: string) => void) {
|
||||
const [isListening, setIsListening] = useState(false);
|
||||
const recognitionRef = useRef<SpeechRecognition | null>(null);
|
||||
const onResultRef = useRef(onResult);
|
||||
useEffect(() => {
|
||||
onResultRef.current = onResult;
|
||||
}, [onResult]);
|
||||
|
||||
const isSupported =
|
||||
typeof window !== "undefined" &&
|
||||
("SpeechRecognition" in window || "webkitSpeechRecognition" in window);
|
||||
|
||||
const start = useCallback(() => {
|
||||
if (!isSupported || recognitionRef.current) return;
|
||||
const Ctor = window.SpeechRecognition ?? window.webkitSpeechRecognition;
|
||||
if (!Ctor) return;
|
||||
|
||||
const recognition = new Ctor();
|
||||
recognition.lang = "zh-CN";
|
||||
recognition.continuous = true;
|
||||
recognition.interimResults = false;
|
||||
|
||||
recognition.onresult = (event: SpeechRecognitionEvent) => {
|
||||
for (let i = event.resultIndex; i < event.results.length; i++) {
|
||||
if (event.results[i].isFinal) {
|
||||
onResultRef.current(event.results[i][0].transcript);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
recognition.onerror = () => {
|
||||
setIsListening(false);
|
||||
recognitionRef.current = null;
|
||||
};
|
||||
|
||||
recognition.onend = () => {
|
||||
setIsListening(false);
|
||||
recognitionRef.current = null;
|
||||
};
|
||||
|
||||
recognitionRef.current = recognition;
|
||||
recognition.start();
|
||||
setIsListening(true);
|
||||
}, [isSupported]);
|
||||
|
||||
const stop = useCallback(() => {
|
||||
recognitionRef.current?.stop();
|
||||
recognitionRef.current = null;
|
||||
setIsListening(false);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
recognitionRef.current?.stop();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return { isListening, start, stop, isSupported };
|
||||
}
|
||||
Reference in New Issue
Block a user