import { useState, useEffect } from 'react' interface Settings { model: string language: string sampleRate: number microphone: string theme: string autoStart: boolean autoCheckUpdate: boolean autoCopy: boolean historyKeepDays: number hotkeyRecord: string hotkeyCopy: string hotkeyToggle: string modelPath?: string } interface SettingsPageProps { theme: 'light' | 'dark' | 'system' onThemeChange: (theme: 'light' | 'dark' | 'system') => void } interface TauriAPI { invoke: (cmd: string, args?: Record) => Promise } declare const window: Window & { __TAURI__?: TauriAPI } const isTauri = () => typeof window !== 'undefined' && !!window.__TAURI__ const defaultSettings: Settings = { model: 'sensevoice-small', language: 'zh', sampleRate: 16000, microphone: '默认设备', theme: 'dark', autoStart: false, autoCheckUpdate: true, autoCopy: false, historyKeepDays: 30, hotkeyRecord: 'Ctrl+Shift+R', hotkeyCopy: 'Ctrl+Shift+C', hotkeyToggle: 'Ctrl+Shift+H' } export default function SettingsPage({ theme, onThemeChange }: SettingsPageProps) { const [settings, setSettings] = useState(defaultSettings) const [modified, setModified] = useState(false) const [saving, setSaving] = useState(false) const [saveMessage, setSaveMessage] = useState(null) // 加载保存的配置 useEffect(() => { if (!isTauri()) return const loadConfig = async () => { try { const config = await window.__TAURI__!.invoke('get_config_cmd') as Record if (config) { setSettings(prev => ({ ...prev, model: (config.asr as any)?.model || prev.model, language: (config.asr as any)?.language || prev.language, sampleRate: (config.audio as any)?.sample_rate || prev.sampleRate, microphone: (config.audio as any)?.device || prev.microphone, modelPath: (config.asr as any)?.model_path || prev.modelPath, })) } } catch (e) { console.error('加载配置失败:', e) } } loadConfig() }, []) const handleChange = (key: keyof Settings, value: Settings[typeof key]) => { setSettings(prev => ({ ...prev, [key]: value })) setModified(true) setSaveMessage(null) } const handleSave = async () => { setSaving(true) setSaveMessage(null) try { if (isTauri()) { // 构建后端期望的配置格式 const configPayload = { asr: { model: settings.model, language: settings.language, model_path: settings.modelPath || null, }, audio: { sample_rate: settings.sampleRate, channels: 1, device: settings.microphone, }, ui: { theme: settings.theme, auto_start: settings.autoStart, auto_check_update: settings.autoCheckUpdate, auto_copy: settings.autoCopy, history_keep_days: settings.historyKeepDays, }, hotkeys: { record: settings.hotkeyRecord, copy: settings.hotkeyCopy, toggle: settings.hotkeyToggle, } } await window.__TAURI__!.invoke('save_config', { settings: configPayload }) setSaveMessage('✓ 设置已保存') } else { // 开发环境:仅日志 console.log('保存设置 (开发环境):', settings) setSaveMessage('✓ 设置已记录到控制台 (开发环境)') } setModified(false) } catch (e) { setSaveMessage(`✗ 保存失败: ${e instanceof Error ? e.message : String(e)}`) } finally { setSaving(false) } } const handleReset = () => { setSettings({ ...defaultSettings }) setModified(true) setSaveMessage(null) } const handleSelectModel = async () => { try { if (!isTauri()) { alert('仅在 Tauri 环境中可用') return } const modelPath = await window.__TAURI__!.invoke('select_model_file') if (modelPath) { const path = modelPath as string setSettings(prev => ({ ...prev, modelPath: path })) setModified(true) } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error) setSaveMessage(`✗ 选择模型失败: ${errorMessage}`) } } return (

设置

{saveMessage && (
{saveMessage}
)}
{/* 识别模型 */}

识别模型

模型选择
选择用于语音识别的 ONNX 模型
模型路径
自定义模型文件路径(可选,留空使用内置模型)
识别语言
主要识别的语言
{/* 音频输入 */}

音频输入

麦克风
选择录音用的麦克风设备
采样率
音频录制采样率
{/* 快捷键 */}

快捷键

开始/停止录音
handleChange('hotkeyRecord', e.target.value)} />
快速复制结果
handleChange('hotkeyCopy', e.target.value)} />
显示/隐藏窗口
handleChange('hotkeyToggle', e.target.value)} />
{/* 外观 */}

外观

主题
{/* 其他 */}

其他

开机自启
自动检查更新
自动复制识别结果
历史记录保留天数
超过此天数的记录将被自动清除
handleChange('historyKeepDays', Number(e.target.value))} min={1} max={365} />
) }