feat: 添加自定义模型路径配置功能
后端变更: - 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:
parent
7ad06cc54a
commit
a4d6353f1a
@ -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)
|
||||||
|
}
|
||||||
|
|||||||
@ -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!("[设置] 初始化应用插件...");
|
||||||
|
|||||||
@ -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;
|
||||||
|
}
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user