feat: add voice switcher

This commit is contained in:
qixinbo
2026-03-29 15:31:40 +08:00
parent 039f9e4b8b
commit 320817e6db
4 changed files with 35 additions and 2 deletions
+6 -1
View File
@@ -388,9 +388,14 @@ export function ChatInterface() {
const startRecording = async () => {
try {
const voiceEnabled = localStorage.getItem("whisper_enabled") === "true";
if (!voiceEnabled) {
alert(t('voiceInputNotEnabled', '语音输入未开启,请先到左下角用户名 -> 更多 -> 语音输入配置中开启'));
return;
}
const configuredWhisperUrl = (localStorage.getItem("whisper_url") || "").trim();
if (!configuredWhisperUrl) {
alert(t('voiceServerNotConfigured', '请先配置语音识别服务地址:点击左下角用户名 -> 语音输入配置'));
alert(t('voiceServerNotConfigured', '请先配置语音识别服务地址:点击左下角用户名 -> 更多 -> 语音输入配置'));
return;
}
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
+21 -1
View File
@@ -12,6 +12,7 @@ import { api } from "@/lib/api";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Switch } from "@/components/ui/switch";
interface SessionInfo {
key: string;
@@ -377,6 +378,7 @@ function SidebarBody() {
const [showUserMenu, setShowUserMenu] = useState(false);
const menuRef = useRef<HTMLDivElement>(null);
const [voiceSettingsOpen, setVoiceSettingsOpen] = useState(false);
const [voiceEnabledDraft, setVoiceEnabledDraft] = useState(false);
const [showKnowledgeSubmenu, setShowKnowledgeSubmenu] = useState(false);
const [showMoreSubmenu, setShowMoreSubmenu] = useState(false);
const [whisperUrlDraft, setWhisperUrlDraft] = useState("");
@@ -453,7 +455,9 @@ function SidebarBody() {
};
const openVoiceSettings = () => {
const enabled = localStorage.getItem("whisper_enabled") === "true";
const saved = (localStorage.getItem("whisper_url") || "").trim();
setVoiceEnabledDraft(enabled);
setWhisperUrlDraft(saved);
setVoiceTestStatus(null);
setVoiceTestMessage("");
@@ -461,6 +465,11 @@ function SidebarBody() {
};
const handleSaveVoiceSettings = () => {
localStorage.setItem("whisper_enabled", String(voiceEnabledDraft));
if (!voiceEnabledDraft) {
setVoiceSettingsOpen(false);
return;
}
const normalized = whisperUrlDraft.trim();
if (!normalized) {
alert(t('voiceServerRequired', '请填写语音识别服务地址'));
@@ -471,6 +480,10 @@ function SidebarBody() {
};
const handleTestVoiceConnection = async () => {
if (!voiceEnabledDraft) {
alert(t('voiceInputDisabledHint', '请先开启语音输入'));
return;
}
const normalized = whisperUrlDraft.trim();
if (!normalized) {
alert(t('voiceServerRequired', '请填写语音识别服务地址'));
@@ -828,13 +841,20 @@ function SidebarBody() {
<DialogTitle>{t('voiceSettings', '语音输入配置')}</DialogTitle>
</DialogHeader>
<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
value={whisperUrlDraft}
onChange={(e) => setWhisperUrlDraft(e.target.value)}
placeholder="http://localhost:8001"
disabled={!voiceEnabledDraft}
/>
<p className="text-xs text-muted-foreground">
{t('voiceSettingsHint', '请输入语音识别服务地址,例如:http://localhost:8001')}
{voiceEnabledDraft
? t('voiceSettingsHint', '请输入语音识别服务地址,例如:http://localhost:8001')
: t('voiceSettingsDisabledHint', '请先开启语音输入,再配置服务地址')}
</p>
{voiceTestStatus && (
<div className={`flex items-center gap-2 text-xs ${voiceTestStatus === "success" ? "text-emerald-600" : "text-red-600"}`}>
+4
View File
@@ -369,5 +369,9 @@
"moreGroup": "More",
"knowledgeBases": "Knowledge Bases",
"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"
}
+4
View File
@@ -369,5 +369,9 @@
"moreGroup": "更多",
"knowledgeBases": "知识库管理",
"voiceSettings": "语音输入配置",
"enableVoiceInput": "启用语音输入",
"voiceSettingsDisabledHint": "请先开启语音输入,再配置服务地址",
"voiceInputDisabledHint": "请先开启语音输入",
"voiceInputNotEnabled": "语音输入未开启,请先到左下角用户名 -> 更多 -> 语音输入配置中开启",
"embeddingModels": "Embedding 模型"
}