feat: add voice action
This commit is contained in:
parent
93f1762e6c
commit
f86b220c92
|
@ -453,6 +453,7 @@ export function ChatActions(props: {
|
||||||
showPromptHints: () => void;
|
showPromptHints: () => void;
|
||||||
hitBottom: boolean;
|
hitBottom: boolean;
|
||||||
uploading: boolean;
|
uploading: boolean;
|
||||||
|
setUserInput: (input: string) => void;
|
||||||
}) {
|
}) {
|
||||||
const config = useAppConfig();
|
const config = useAppConfig();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
@ -544,6 +545,44 @@ export function ChatActions(props: {
|
||||||
}
|
}
|
||||||
}, [chatStore, currentModel, models]);
|
}, [chatStore, currentModel, models]);
|
||||||
|
|
||||||
|
const [isListening, setIsListening] = useState(false);
|
||||||
|
const [isTranscription, setIsTranscription] = useState(false);
|
||||||
|
const [speechApi, setSpeechApi] = useState<any>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isFirefox()) config.sttConfig.engine = FIREFOX_DEFAULT_STT_ENGINE;
|
||||||
|
setSpeechApi(
|
||||||
|
config.sttConfig.engine === DEFAULT_STT_ENGINE
|
||||||
|
? new WebTranscriptionApi((transcription) =>
|
||||||
|
onRecognitionEnd(transcription),
|
||||||
|
)
|
||||||
|
: new OpenAITranscriptionApi((transcription) =>
|
||||||
|
onRecognitionEnd(transcription),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const startListening = async () => {
|
||||||
|
if (speechApi) {
|
||||||
|
await speechApi.start();
|
||||||
|
setIsListening(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const stopListening = async () => {
|
||||||
|
if (speechApi) {
|
||||||
|
if (config.sttConfig.engine !== DEFAULT_STT_ENGINE)
|
||||||
|
setIsTranscription(true);
|
||||||
|
await speechApi.stop();
|
||||||
|
setIsListening(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const onRecognitionEnd = (finalTranscript: string) => {
|
||||||
|
console.log(finalTranscript);
|
||||||
|
if (finalTranscript) props.setUserInput(finalTranscript);
|
||||||
|
if (config.sttConfig.engine !== DEFAULT_STT_ENGINE)
|
||||||
|
setIsTranscription(false);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles["chat-input-actions"]}>
|
<div className={styles["chat-input-actions"]}>
|
||||||
{couldStop && (
|
{couldStop && (
|
||||||
|
@ -768,6 +807,16 @@ export function ChatActions(props: {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{config.sttConfig.enable && (
|
||||||
|
<ChatAction
|
||||||
|
onClick={async () =>
|
||||||
|
isListening ? await stopListening() : await startListening()
|
||||||
|
}
|
||||||
|
text={isListening ? Locale.Chat.StopSpeak : Locale.Chat.StartSpeak}
|
||||||
|
icon={<VoiceWhiteIcon />}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -940,33 +989,6 @@ function _Chat() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const [isListening, setIsListening] = useState(false);
|
|
||||||
const [isTranscription, setIsTranscription] = useState(false);
|
|
||||||
const [speechApi, setSpeechApi] = useState<any>(null);
|
|
||||||
|
|
||||||
const startListening = async () => {
|
|
||||||
if (speechApi) {
|
|
||||||
await speechApi.start();
|
|
||||||
setIsListening(true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const stopListening = async () => {
|
|
||||||
if (speechApi) {
|
|
||||||
if (config.sttConfig.engine !== DEFAULT_STT_ENGINE)
|
|
||||||
setIsTranscription(true);
|
|
||||||
await speechApi.stop();
|
|
||||||
setIsListening(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onRecognitionEnd = (finalTranscript: string) => {
|
|
||||||
console.log(finalTranscript);
|
|
||||||
if (finalTranscript) setUserInput(finalTranscript);
|
|
||||||
if (config.sttConfig.engine !== DEFAULT_STT_ENGINE)
|
|
||||||
setIsTranscription(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const doSubmit = (userInput: string) => {
|
const doSubmit = (userInput: string) => {
|
||||||
if (userInput.trim() === "") return;
|
if (userInput.trim() === "") return;
|
||||||
const matchCommand = chatCommands.match(userInput);
|
const matchCommand = chatCommands.match(userInput);
|
||||||
|
@ -1037,16 +1059,6 @@ function _Chat() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
if (isFirefox()) config.sttConfig.engine = FIREFOX_DEFAULT_STT_ENGINE;
|
|
||||||
setSpeechApi(
|
|
||||||
config.sttConfig.engine === DEFAULT_STT_ENGINE
|
|
||||||
? new WebTranscriptionApi((transcription) =>
|
|
||||||
onRecognitionEnd(transcription),
|
|
||||||
)
|
|
||||||
: new OpenAITranscriptionApi((transcription) =>
|
|
||||||
onRecognitionEnd(transcription),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// check if should send message
|
// check if should send message
|
||||||
|
@ -1784,6 +1796,7 @@ function _Chat() {
|
||||||
setUserInput("/");
|
setUserInput("/");
|
||||||
onSearch("");
|
onSearch("");
|
||||||
}}
|
}}
|
||||||
|
setUserInput={setUserInput}
|
||||||
/>
|
/>
|
||||||
<label
|
<label
|
||||||
className={`${styles["chat-input-panel-inner"]} ${
|
className={`${styles["chat-input-panel-inner"]} ${
|
||||||
|
@ -1834,20 +1847,6 @@ function _Chat() {
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{config.sttConfig.enable ? (
|
|
||||||
<IconButton
|
|
||||||
icon={<VoiceWhiteIcon />}
|
|
||||||
text={
|
|
||||||
isListening ? Locale.Chat.StopSpeak : Locale.Chat.StartSpeak
|
|
||||||
}
|
|
||||||
className={styles["chat-input-send"]}
|
|
||||||
type="primary"
|
|
||||||
onClick={async () =>
|
|
||||||
isListening ? await stopListening() : await startListening()
|
|
||||||
}
|
|
||||||
loding={isTranscription}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={<SendWhiteIcon />}
|
icon={<SendWhiteIcon />}
|
||||||
text={Locale.Chat.Send}
|
text={Locale.Chat.Send}
|
||||||
|
@ -1855,14 +1854,6 @@ function _Chat() {
|
||||||
type="primary"
|
type="primary"
|
||||||
onClick={() => doSubmit(userInput)}
|
onClick={() => doSubmit(userInput)}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
{/* <IconButton
|
|
||||||
icon={<SendWhiteIcon />}
|
|
||||||
text={Locale.Chat.Send}
|
|
||||||
className={styles["chat-input-send"]}
|
|
||||||
type="primary"
|
|
||||||
onClick={() => doSubmit(userInput)}
|
|
||||||
/> */}
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20" height="20" fill="none" viewBox="0 0 20 20">
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" fill="none" viewBox="0 0 20 20">
|
||||||
<defs>
|
<defs>
|
||||||
<rect id="path_0" width="20" height="20" x="0" y="0" />
|
<rect id="path_0" width="20" height="20" x="0" y="0" />
|
||||||
</defs>
|
</defs>
|
||||||
|
@ -7,9 +7,9 @@
|
||||||
<use xlink:href="#path_0" />
|
<use xlink:href="#path_0" />
|
||||||
</mask>
|
</mask>
|
||||||
<g mask="url(#bg-mask-0)">
|
<g mask="url(#bg-mask-0)">
|
||||||
<path d="M7 4a3 3 0 016 0v6a3 3 0 11-6 0V4z">
|
<path d="M7 4a3 3 0 016 0v6a3 3 0 11-6 0V4z" fill="#333333">
|
||||||
</path>
|
</path>
|
||||||
<path d="M5.5 9.643a.75.75 0 00-1.5 0V10c0 3.06 2.29 5.585 5.25 5.954V17.5h-1.5a.75.75 0 000 1.5h4.5a.75.75 0 000-1.5h-1.5v-1.546A6.001 6.001 0 0016 10v-.357a.75.75 0 00-1.5 0V10a4.5 4.5 0 01-9 0v-.357z">
|
<path d="M5.5 9.643a.75.75 0 00-1.5 0V10c0 3.06 2.29 5.585 5.25 5.954V17.5h-1.5a.75.75 0 000 1.5h4.5a.75.75 0 000-1.5h-1.5v-1.546A6.001 6.001 0 0016 10v-.357a.75.75 0 00-1.5 0V10a4.5 4.5 0 01-9 0v-.357z" fill="#333333">
|
||||||
</path>
|
</path>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
|
|
Before Width: | Height: | Size: 678 B After Width: | Height: | Size: 708 B |
Loading…
Reference in New Issue