feat: add voice reader
This commit is contained in:
parent
8b4db412d8
commit
14e2b8dc31
|
@ -30,6 +30,7 @@ import dynamic from "next/dynamic";
|
||||||
import { REPO_URL } from "../constant";
|
import { REPO_URL } from "../constant";
|
||||||
import { ControllerPool } from "../requests";
|
import { ControllerPool } from "../requests";
|
||||||
import { Prompt, usePromptStore } from "../store/prompt";
|
import { Prompt, usePromptStore } from "../store/prompt";
|
||||||
|
import { Voice } from "./voice";
|
||||||
|
|
||||||
export function Loading(props: { noLogo?: boolean }) {
|
export function Loading(props: { noLogo?: boolean }) {
|
||||||
return (
|
return (
|
||||||
|
@ -396,6 +397,7 @@ export function Chat(props: {
|
||||||
<div className={styles["chat-message-container"]}>
|
<div className={styles["chat-message-container"]}>
|
||||||
<div className={styles["chat-message-avatar"]}>
|
<div className={styles["chat-message-avatar"]}>
|
||||||
<Avatar role={message.role} />
|
<Avatar role={message.role} />
|
||||||
|
<Voice text={message.content}></Voice>
|
||||||
</div>
|
</div>
|
||||||
{(message.preview || message.streaming) && (
|
{(message.preview || message.streaming) && (
|
||||||
<div className={styles["chat-message-status"]}>
|
<div className={styles["chat-message-status"]}>
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
// implement a voice component by speech synthesis to read the text with the voice of the user's choice
|
||||||
|
import * as React from "react";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useSpeechSynthesis } from "react-speech-kit";
|
||||||
|
import { IconButton } from "./button";
|
||||||
|
import VoiceIcon from "../icons/voice.svg";
|
||||||
|
|
||||||
|
const voices = window.speechSynthesis.getVoices();
|
||||||
|
export function Voice(props: { text: string }) {
|
||||||
|
const { speak } = useSpeechSynthesis();
|
||||||
|
const [voice, setVoice] = useState<SpeechSynthesisVoice | null>(null);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
if (voice) {
|
||||||
|
speak({ text: props.text, voice });
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
icon={<VoiceIcon />}
|
||||||
|
title="Read"
|
||||||
|
/>
|
||||||
|
<select
|
||||||
|
onChange={(e) => {
|
||||||
|
const voice = voices.find((v) => v.name === e.target.value);
|
||||||
|
if (voice) {
|
||||||
|
setVoice(voice);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{voices.map((v) => (
|
||||||
|
<option key={v.name} value={v.name}>
|
||||||
|
{v.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
<svg focusable="false" aria-hidden="true" viewBox="0 0 24 24" data-testid="PlayCircleIcon"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zM9.5 16.5v-9l7 4.5-7 4.5z"></path></svg>
|
After Width: | Height: | Size: 206 B |
|
@ -23,6 +23,7 @@
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-markdown": "^8.0.5",
|
"react-markdown": "^8.0.5",
|
||||||
|
"react-speech-kit": "^3.0.1",
|
||||||
"rehype-katex": "^6.0.2",
|
"rehype-katex": "^6.0.2",
|
||||||
"rehype-prism-plus": "^1.5.1",
|
"rehype-prism-plus": "^1.5.1",
|
||||||
"remark-gfm": "^3.0.1",
|
"remark-gfm": "^3.0.1",
|
||||||
|
|
Loading…
Reference in New Issue