feat: add voice switcher
This commit is contained in:
@@ -388,9 +388,14 @@ export function ChatInterface() {
|
|||||||
|
|
||||||
const startRecording = async () => {
|
const startRecording = async () => {
|
||||||
try {
|
try {
|
||||||
|
const voiceEnabled = localStorage.getItem("whisper_enabled") === "true";
|
||||||
|
if (!voiceEnabled) {
|
||||||
|
alert(t('voiceInputNotEnabled', '语音输入未开启,请先到左下角用户名 -> 更多 -> 语音输入配置中开启'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
const configuredWhisperUrl = (localStorage.getItem("whisper_url") || "").trim();
|
const configuredWhisperUrl = (localStorage.getItem("whisper_url") || "").trim();
|
||||||
if (!configuredWhisperUrl) {
|
if (!configuredWhisperUrl) {
|
||||||
alert(t('voiceServerNotConfigured', '请先配置语音识别服务地址:点击左下角用户名 -> 语音输入配置'));
|
alert(t('voiceServerNotConfigured', '请先配置语音识别服务地址:点击左下角用户名 -> 更多 -> 语音输入配置'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { api } from "@/lib/api";
|
|||||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Switch } from "@/components/ui/switch";
|
||||||
|
|
||||||
interface SessionInfo {
|
interface SessionInfo {
|
||||||
key: string;
|
key: string;
|
||||||
@@ -377,6 +378,7 @@ function SidebarBody() {
|
|||||||
const [showUserMenu, setShowUserMenu] = useState(false);
|
const [showUserMenu, setShowUserMenu] = useState(false);
|
||||||
const menuRef = useRef<HTMLDivElement>(null);
|
const menuRef = useRef<HTMLDivElement>(null);
|
||||||
const [voiceSettingsOpen, setVoiceSettingsOpen] = useState(false);
|
const [voiceSettingsOpen, setVoiceSettingsOpen] = useState(false);
|
||||||
|
const [voiceEnabledDraft, setVoiceEnabledDraft] = useState(false);
|
||||||
const [showKnowledgeSubmenu, setShowKnowledgeSubmenu] = useState(false);
|
const [showKnowledgeSubmenu, setShowKnowledgeSubmenu] = useState(false);
|
||||||
const [showMoreSubmenu, setShowMoreSubmenu] = useState(false);
|
const [showMoreSubmenu, setShowMoreSubmenu] = useState(false);
|
||||||
const [whisperUrlDraft, setWhisperUrlDraft] = useState("");
|
const [whisperUrlDraft, setWhisperUrlDraft] = useState("");
|
||||||
@@ -453,7 +455,9 @@ function SidebarBody() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const openVoiceSettings = () => {
|
const openVoiceSettings = () => {
|
||||||
|
const enabled = localStorage.getItem("whisper_enabled") === "true";
|
||||||
const saved = (localStorage.getItem("whisper_url") || "").trim();
|
const saved = (localStorage.getItem("whisper_url") || "").trim();
|
||||||
|
setVoiceEnabledDraft(enabled);
|
||||||
setWhisperUrlDraft(saved);
|
setWhisperUrlDraft(saved);
|
||||||
setVoiceTestStatus(null);
|
setVoiceTestStatus(null);
|
||||||
setVoiceTestMessage("");
|
setVoiceTestMessage("");
|
||||||
@@ -461,6 +465,11 @@ function SidebarBody() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSaveVoiceSettings = () => {
|
const handleSaveVoiceSettings = () => {
|
||||||
|
localStorage.setItem("whisper_enabled", String(voiceEnabledDraft));
|
||||||
|
if (!voiceEnabledDraft) {
|
||||||
|
setVoiceSettingsOpen(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const normalized = whisperUrlDraft.trim();
|
const normalized = whisperUrlDraft.trim();
|
||||||
if (!normalized) {
|
if (!normalized) {
|
||||||
alert(t('voiceServerRequired', '请填写语音识别服务地址'));
|
alert(t('voiceServerRequired', '请填写语音识别服务地址'));
|
||||||
@@ -471,6 +480,10 @@ function SidebarBody() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleTestVoiceConnection = async () => {
|
const handleTestVoiceConnection = async () => {
|
||||||
|
if (!voiceEnabledDraft) {
|
||||||
|
alert(t('voiceInputDisabledHint', '请先开启语音输入'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
const normalized = whisperUrlDraft.trim();
|
const normalized = whisperUrlDraft.trim();
|
||||||
if (!normalized) {
|
if (!normalized) {
|
||||||
alert(t('voiceServerRequired', '请填写语音识别服务地址'));
|
alert(t('voiceServerRequired', '请填写语音识别服务地址'));
|
||||||
@@ -828,13 +841,20 @@ function SidebarBody() {
|
|||||||
<DialogTitle>{t('voiceSettings', '语音输入配置')}</DialogTitle>
|
<DialogTitle>{t('voiceSettings', '语音输入配置')}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="py-4 space-y-3">
|
<div className="py-4 space-y-3">
|
||||||
|
<div className="flex items-center justify-between rounded-md border border-border p-3">
|
||||||
|
<span className="text-sm text-foreground">{t('enableVoiceInput', '启用语音输入')}</span>
|
||||||
|
<Switch checked={voiceEnabledDraft} onCheckedChange={setVoiceEnabledDraft} />
|
||||||
|
</div>
|
||||||
<Input
|
<Input
|
||||||
value={whisperUrlDraft}
|
value={whisperUrlDraft}
|
||||||
onChange={(e) => setWhisperUrlDraft(e.target.value)}
|
onChange={(e) => setWhisperUrlDraft(e.target.value)}
|
||||||
placeholder="http://localhost:8001"
|
placeholder="http://localhost:8001"
|
||||||
|
disabled={!voiceEnabledDraft}
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
{t('voiceSettingsHint', '请输入语音识别服务地址,例如:http://localhost:8001')}
|
{voiceEnabledDraft
|
||||||
|
? t('voiceSettingsHint', '请输入语音识别服务地址,例如:http://localhost:8001')
|
||||||
|
: t('voiceSettingsDisabledHint', '请先开启语音输入,再配置服务地址')}
|
||||||
</p>
|
</p>
|
||||||
{voiceTestStatus && (
|
{voiceTestStatus && (
|
||||||
<div className={`flex items-center gap-2 text-xs ${voiceTestStatus === "success" ? "text-emerald-600" : "text-red-600"}`}>
|
<div className={`flex items-center gap-2 text-xs ${voiceTestStatus === "success" ? "text-emerald-600" : "text-red-600"}`}>
|
||||||
|
|||||||
@@ -369,5 +369,9 @@
|
|||||||
"moreGroup": "More",
|
"moreGroup": "More",
|
||||||
"knowledgeBases": "Knowledge Bases",
|
"knowledgeBases": "Knowledge Bases",
|
||||||
"voiceSettings": "Voice Input Settings",
|
"voiceSettings": "Voice Input Settings",
|
||||||
|
"enableVoiceInput": "Enable Voice Input",
|
||||||
|
"voiceSettingsDisabledHint": "Enable voice input first, then configure server URL",
|
||||||
|
"voiceInputDisabledHint": "Please enable voice input first",
|
||||||
|
"voiceInputNotEnabled": "Voice input is disabled. Enable it from profile menu -> More -> Voice Input Settings first.",
|
||||||
"embeddingModels": "Embedding Models"
|
"embeddingModels": "Embedding Models"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -369,5 +369,9 @@
|
|||||||
"moreGroup": "更多",
|
"moreGroup": "更多",
|
||||||
"knowledgeBases": "知识库管理",
|
"knowledgeBases": "知识库管理",
|
||||||
"voiceSettings": "语音输入配置",
|
"voiceSettings": "语音输入配置",
|
||||||
|
"enableVoiceInput": "启用语音输入",
|
||||||
|
"voiceSettingsDisabledHint": "请先开启语音输入,再配置服务地址",
|
||||||
|
"voiceInputDisabledHint": "请先开启语音输入",
|
||||||
|
"voiceInputNotEnabled": "语音输入未开启,请先到左下角用户名 -> 更多 -> 语音输入配置中开启",
|
||||||
"embeddingModels": "Embedding 模型"
|
"embeddingModels": "Embedding 模型"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user