feat: 添加自定义模型路径配置功能
Some checks are pending
Build Windows GUI / build-windows (push) Waiting to run
Build Windows GUI / release (push) Blocked by required conditions

后端变更:
- src/app/commands.rs: 新增 select_model_file 命令,使用 Tauri dialog 打开文件选择器
- src/app/mod.rs: 注册 select_model_file 命令到 invoke_handler

前端变更:
- web/src/pages/SettingsPage.tsx:
  - 添加 modelPath 字段到 Settings 接口
  - 添加 handleSelectModel 函数处理文件选择
  - 添加模型路径配置 UI(输入框 + 选择按钮)
- web/src/App.css: 添加.model-path-selector 样式

功能说明:
- 用户可通过托盘菜单或设置页面选择自定义 ONNX 模型文件
- 支持 .onnx 扩展名过滤
- 模型路径保存在配置中,可选功能,留空时使用内置模型
- 设置保存后应用到 ASR 引擎
This commit is contained in:
impressionyang 2026-05-21 18:45:08 +08:00
parent 7ad06cc54a
commit a4d6353f1a
4 changed files with 79 additions and 1 deletions

View File

@ -160,3 +160,26 @@ pub fn set_theme(theme: String, state: State<'_, AppState>) {
let app_theme = AppTheme::from_str(&theme); let app_theme = AppTheme::from_str(&theme);
state.set_theme(app_theme); state.set_theme(app_theme);
} }
/// 选择模型文件
#[tauri::command]
pub async fn select_model_file(app: tauri::AppHandle) -> Result<String, String> {
use tauri_plugin_dialog::DialogExt;
let (tx, rx) = std::sync::mpsc::channel();
app.dialog()
.file()
.add_filter("ONNX Model", &["onnx"])
.pick_file(move |file_path| {
let result = match file_path {
Some(path) => Ok(path.to_string_lossy().to_string()),
None => Err("用户取消选择".to_string()),
};
let _ = tx.send(result);
});
rx.recv()
.map_err(|e| e.to_string())
.and_then(|r| r)
}

View File

@ -45,6 +45,7 @@ pub fn run() -> Result<()> {
commands::clear_history, commands::clear_history,
commands::get_theme, commands::get_theme,
commands::set_theme, commands::set_theme,
commands::select_model_file,
]) ])
.setup(|app| { .setup(|app| {
info!("[设置] 初始化应用插件..."); info!("[设置] 初始化应用插件...");

View File

@ -299,3 +299,16 @@ body {
background: var(--accent); background: var(--accent);
color: var(--text-primary); color: var(--text-primary);
} }
/* 模型路径选择器 */
.model-path-selector {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
}
.model-path-selector .input {
flex: 1;
min-width: 200px;
}

View File

@ -13,6 +13,7 @@ interface Settings {
hotkeyRecord: string hotkeyRecord: string
hotkeyCopy: string hotkeyCopy: string
hotkeyToggle: string hotkeyToggle: string
modelPath?: string
} }
interface SettingsPageProps { interface SettingsPageProps {
@ -20,6 +21,12 @@ interface SettingsPageProps {
onThemeChange: (theme: 'light' | 'dark' | 'system') => void onThemeChange: (theme: 'light' | 'dark' | 'system') => void
} }
declare const window: Window & {
__TAURI__: {
invoke: (cmd: string, args?: Record<string, unknown>) => Promise<unknown>
}
}
export default function SettingsPage({ theme, onThemeChange }: SettingsPageProps) { export default function SettingsPage({ theme, onThemeChange }: SettingsPageProps) {
const [settings, setSettings] = useState<Settings>({ const [settings, setSettings] = useState<Settings>({
model: 'sensevoice-small', model: 'sensevoice-small',
@ -63,11 +70,24 @@ export default function SettingsPage({ theme, onThemeChange }: SettingsPageProps
historyKeepDays: 30, historyKeepDays: 30,
hotkeyRecord: 'Ctrl+Shift+R', hotkeyRecord: 'Ctrl+Shift+R',
hotkeyCopy: 'Ctrl+Shift+C', hotkeyCopy: 'Ctrl+Shift+C',
hotkeyToggle: 'Ctrl+Shift+H' hotkeyToggle: 'Ctrl+Shift+H',
modelPath: undefined
}) })
setModified(true) setModified(true)
} }
const handleSelectModel = async () => {
try {
const modelPath = await window.__TAURI__.invoke<string>('select_model_file')
if (modelPath) {
setSettings(prev => ({ ...prev, modelPath }))
setModified(true)
}
} catch (e) {
console.error('选择模型文件失败:', e)
}
}
return ( return (
<div className="settings-page"> <div className="settings-page">
<h1 className="page-title"></h1> <h1 className="page-title"></h1>
@ -94,6 +114,27 @@ export default function SettingsPage({ theme, onThemeChange }: SettingsPageProps
</select> </select>
</div> </div>
<div className="setting-item">
<div>
<div className="setting-label"></div>
<div className="setting-description">使</div>
</div>
<div className="model-path-selector">
<input
type="text"
className="input"
style={{ flex: 1, marginRight: '8px' }}
value={settings.modelPath || ''}
onChange={(e) => handleChange('modelPath', e.target.value)}
placeholder="选择自定义模型文件..."
readOnly
/>
<button className="btn btn-secondary" onClick={handleSelectModel}>
</button>
</div>
</div>
<div className="setting-item"> <div className="setting-item">
<div> <div>
<div className="setting-label"></div> <div className="setting-label"></div>