Files
QSmartAssistant/ModelSettingsDialog.cpp
lizhuoran e92cb0b4e5 feat: 完整的语音助手系统实现
主要功能:
-  离线语音识别 (ASR) - Paraformer中文模型
-  在线语音识别 - Streaming Paraformer中英文双语模型
-  语音合成 (TTS) - MeloTTS中英文混合模型
-  语音唤醒 (KWS) - Zipformer关键词检测模型
-  麦克风录音功能 - 支持多种格式和实时转换
-  模型设置界面 - 完整的图形化配置管理

KWS优化亮点:
- 🎯 成功实现关键词检测 (测试成功率10%→预期50%+)
- ⚙️ 可调参数: 阈值、活跃路径、尾随空白、分数权重、线程数
- 🔧 智能参数验证和实时反馈
- 📊 详细的调试信息和成功统计
- 🎛️ 用户友好的设置界面

技术架构:
- 模块化设计: ASRManager, TTSManager, KWSManager
- 实时音频处理: 自动格式转换 (任意格式→16kHz单声道)
- 智能设备检测: 自动选择最佳音频格式
- 完整资源管理: 正确的创建和销毁流程
- 跨平台支持: macOS优化的音频权限处理

界面特性:
- 2×2网格布局: ASR、TTS、录音、KWS四大功能模块
- 分离录音设置: 设备参数 + 输出格式独立配置
- 实时状态显示: 音频电平、处理次数、成功统计
- 详细的用户指导和错误提示
2025-12-23 13:47:00 +08:00

1120 lines
46 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "ModelSettingsDialog.h"
#include <QDir>
#include <QFileInfo>
#include <QStandardPaths>
#include <QDebug>
ModelSettingsDialog::ModelSettingsDialog(QWidget* parent)
: QDialog(parent), settings(new QSettings(this)) {
setWindowTitle("模型设置");
setModal(true);
resize(800, 600);
setupUI();
connectSignals();
loadSettings();
}
ModelSettingsDialog::~ModelSettingsDialog() {
}
void ModelSettingsDialog::setupUI() {
auto* mainLayout = new QVBoxLayout(this);
// 创建标签页控件
tabWidget = new QTabWidget(this);
setupOfflineASRTab();
setupOnlineASRTab();
setupKWSTab();
setupTTSTab();
setupAdvancedTab();
mainLayout->addWidget(tabWidget);
// 按钮区域
auto* buttonLayout = new QHBoxLayout();
scanBtn = new QPushButton("扫描模型", this);
scanBtn->setToolTip("自动扫描系统中的可用模型");
resetBtn = new QPushButton("重置默认", this);
resetBtn->setToolTip("重置为默认配置");
buttonLayout->addWidget(scanBtn);
buttonLayout->addWidget(resetBtn);
buttonLayout->addStretch();
saveBtn = new QPushButton("保存", this);
saveBtn->setDefault(true);
cancelBtn = new QPushButton("取消", this);
buttonLayout->addWidget(saveBtn);
buttonLayout->addWidget(cancelBtn);
mainLayout->addLayout(buttonLayout);
}
void ModelSettingsDialog::setupOfflineASRTab() {
offlineAsrTab = new QWidget();
tabWidget->addTab(offlineAsrTab, "离线语音识别");
auto* layout = new QVBoxLayout(offlineAsrTab);
// 模型选择组
auto* modelGroup = new QGroupBox("模型选择", this);
auto* modelLayout = new QGridLayout(modelGroup);
modelLayout->addWidget(new QLabel("预设模型:"), 0, 0);
offlineAsrModelCombo = new QComboBox(this);
offlineAsrModelCombo->addItem("自定义", "custom");
offlineAsrModelCombo->addItem("Paraformer中文模型", "paraformer-zh");
offlineAsrModelCombo->addItem("Whisper多语言模型", "whisper-multilingual");
modelLayout->addWidget(offlineAsrModelCombo, 0, 1, 1, 2);
layout->addWidget(modelGroup);
// 模型路径组
auto* pathGroup = new QGroupBox("模型路径", this);
auto* pathLayout = new QGridLayout(pathGroup);
pathLayout->addWidget(new QLabel("模型文件:"), 0, 0);
offlineAsrModelPathEdit = new QLineEdit(this);
offlineAsrModelPathEdit->setPlaceholderText("选择.onnx模型文件...");
auto* browseModelBtn = new QPushButton("浏览", this);
pathLayout->addWidget(offlineAsrModelPathEdit, 0, 1);
pathLayout->addWidget(browseModelBtn, 0, 2);
pathLayout->addWidget(new QLabel("词汇表文件:"), 1, 0);
offlineAsrTokensPathEdit = new QLineEdit(this);
offlineAsrTokensPathEdit->setPlaceholderText("选择tokens.txt文件...");
auto* browseTokensBtn = new QPushButton("浏览", this);
pathLayout->addWidget(offlineAsrTokensPathEdit, 1, 1);
pathLayout->addWidget(browseTokensBtn, 1, 2);
layout->addWidget(pathGroup);
// 模型信息组
auto* infoGroup = new QGroupBox("模型信息", this);
auto* infoLayout = new QVBoxLayout(infoGroup);
offlineAsrModelInfoEdit = new QTextEdit(this);
offlineAsrModelInfoEdit->setMaximumHeight(100);
offlineAsrModelInfoEdit->setPlaceholderText("模型信息将显示在这里...");
infoLayout->addWidget(offlineAsrModelInfoEdit);
auto* testLayout = new QHBoxLayout();
testOfflineASRBtn = new QPushButton("测试模型", this);
testOfflineASRBtn->setEnabled(false);
testLayout->addStretch();
testLayout->addWidget(testOfflineASRBtn);
infoLayout->addLayout(testLayout);
layout->addWidget(infoGroup);
layout->addStretch();
// 连接信号
connect(browseModelBtn, &QPushButton::clicked, this, &ModelSettingsDialog::browseOfflineASRModel);
connect(browseTokensBtn, &QPushButton::clicked, this, &ModelSettingsDialog::browseOfflineASRTokens);
}
void ModelSettingsDialog::setupOnlineASRTab() {
onlineAsrTab = new QWidget();
tabWidget->addTab(onlineAsrTab, "在线语音识别");
auto* layout = new QVBoxLayout(onlineAsrTab);
// 模型选择组
auto* modelGroup = new QGroupBox("模型选择", this);
auto* modelLayout = new QGridLayout(modelGroup);
modelLayout->addWidget(new QLabel("预设模型:"), 0, 0);
onlineAsrModelCombo = new QComboBox(this);
onlineAsrModelCombo->addItem("自定义", "custom");
onlineAsrModelCombo->addItem("Streaming Paraformer中英文模型", "streaming-paraformer-zh-en");
onlineAsrModelCombo->addItem("Streaming Zipformer中英文模型", "streaming-zipformer-zh-en");
modelLayout->addWidget(onlineAsrModelCombo, 0, 1, 1, 2);
layout->addWidget(modelGroup);
// 模型路径组
auto* pathGroup = new QGroupBox("模型路径", this);
auto* pathLayout = new QGridLayout(pathGroup);
pathLayout->addWidget(new QLabel("编码器文件:"), 0, 0);
onlineAsrModelPathEdit = new QLineEdit(this);
onlineAsrModelPathEdit->setPlaceholderText("选择encoder.onnx文件...");
auto* browseModelBtn = new QPushButton("浏览", this);
pathLayout->addWidget(onlineAsrModelPathEdit, 0, 1);
pathLayout->addWidget(browseModelBtn, 0, 2);
pathLayout->addWidget(new QLabel("词汇表文件:"), 1, 0);
onlineAsrTokensPathEdit = new QLineEdit(this);
onlineAsrTokensPathEdit->setPlaceholderText("选择tokens.txt文件...");
auto* browseTokensBtn = new QPushButton("浏览", this);
pathLayout->addWidget(onlineAsrTokensPathEdit, 1, 1);
pathLayout->addWidget(browseTokensBtn, 1, 2);
layout->addWidget(pathGroup);
// 模型信息组
auto* infoGroup = new QGroupBox("模型信息", this);
auto* infoLayout = new QVBoxLayout(infoGroup);
onlineAsrModelInfoEdit = new QTextEdit(this);
onlineAsrModelInfoEdit->setMaximumHeight(100);
onlineAsrModelInfoEdit->setPlaceholderText("模型信息将显示在这里...");
infoLayout->addWidget(onlineAsrModelInfoEdit);
auto* testLayout = new QHBoxLayout();
testOnlineASRBtn = new QPushButton("测试模型", this);
testOnlineASRBtn->setEnabled(false);
testLayout->addStretch();
testLayout->addWidget(testOnlineASRBtn);
infoLayout->addLayout(testLayout);
layout->addWidget(infoGroup);
layout->addStretch();
// 连接信号
connect(browseModelBtn, &QPushButton::clicked, this, &ModelSettingsDialog::browseOnlineASRModel);
connect(browseTokensBtn, &QPushButton::clicked, this, &ModelSettingsDialog::browseOnlineASRTokens);
}
void ModelSettingsDialog::setupKWSTab() {
kwsTab = new QWidget();
tabWidget->addTab(kwsTab, "语音唤醒 (KWS)");
auto* layout = new QVBoxLayout(kwsTab);
// 模型选择组
auto* modelGroup = new QGroupBox("模型选择", this);
auto* modelLayout = new QGridLayout(modelGroup);
modelLayout->addWidget(new QLabel("预设模型:"), 0, 0);
kwsModelCombo = new QComboBox(this);
kwsModelCombo->addItem("自定义", "custom");
kwsModelCombo->addItem("Zipformer Wenetspeech 3.3M", "zipformer-wenetspeech-3.3m");
kwsModelCombo->addItem("Zipformer Gigaspeech", "zipformer-gigaspeech");
modelLayout->addWidget(kwsModelCombo, 0, 1, 1, 2);
layout->addWidget(modelGroup);
// 模型路径组
auto* pathGroup = new QGroupBox("模型路径", this);
auto* pathLayout = new QGridLayout(pathGroup);
pathLayout->addWidget(new QLabel("模型文件:"), 0, 0);
kwsModelPathEdit = new QLineEdit(this);
kwsModelPathEdit->setPlaceholderText("选择.onnx模型文件...");
auto* browseModelBtn = new QPushButton("浏览", this);
pathLayout->addWidget(kwsModelPathEdit, 0, 1);
pathLayout->addWidget(browseModelBtn, 0, 2);
pathLayout->addWidget(new QLabel("词汇表文件:"), 1, 0);
kwsTokensPathEdit = new QLineEdit(this);
kwsTokensPathEdit->setPlaceholderText("选择tokens.txt文件...");
auto* browseTokensBtn = new QPushButton("浏览", this);
pathLayout->addWidget(kwsTokensPathEdit, 1, 1);
pathLayout->addWidget(browseTokensBtn, 1, 2);
pathLayout->addWidget(new QLabel("关键词文件:"), 2, 0);
kwsKeywordsPathEdit = new QLineEdit(this);
kwsKeywordsPathEdit->setPlaceholderText("选择keywords.txt文件...");
auto* browseKeywordsBtn = new QPushButton("浏览", this);
pathLayout->addWidget(kwsKeywordsPathEdit, 2, 1);
pathLayout->addWidget(browseKeywordsBtn, 2, 2);
layout->addWidget(pathGroup);
// 模型信息组
auto* infoGroup = new QGroupBox("模型信息", this);
auto* infoLayout = new QVBoxLayout(infoGroup);
kwsModelInfoEdit = new QTextEdit(this);
kwsModelInfoEdit->setMaximumHeight(100);
kwsModelInfoEdit->setPlaceholderText("模型信息将显示在这里...");
infoLayout->addWidget(kwsModelInfoEdit);
auto* testLayout = new QHBoxLayout();
testKWSBtn = new QPushButton("测试模型", this);
testKWSBtn->setEnabled(false);
testLayout->addStretch();
testLayout->addWidget(testKWSBtn);
infoLayout->addLayout(testLayout);
layout->addWidget(infoGroup);
// KWS参数设置组
kwsParamsGroup = new QGroupBox("识别参数设置", this);
auto* paramsLayout = new QGridLayout(kwsParamsGroup);
// 关键词阈值
paramsLayout->addWidget(new QLabel("关键词阈值:"), 0, 0);
kwsThresholdEdit = new QLineEdit(this);
kwsThresholdEdit->setText("0.25");
kwsThresholdEdit->setToolTip("范围: 0.01-1.0,越低越容易检测,建议: 0.25");
paramsLayout->addWidget(kwsThresholdEdit, 0, 1);
paramsLayout->addWidget(new QLabel("(0.01-1.0, 推荐0.25)"), 0, 2);
// 最大活跃路径数
paramsLayout->addWidget(new QLabel("最大活跃路径:"), 1, 0);
kwsMaxActivePathsEdit = new QLineEdit(this);
kwsMaxActivePathsEdit->setText("8");
kwsMaxActivePathsEdit->setToolTip("范围: 1-16越大识别率越高但速度越慢建议: 8");
paramsLayout->addWidget(kwsMaxActivePathsEdit, 1, 1);
paramsLayout->addWidget(new QLabel("(1-16, 推荐8)"), 1, 2);
// 尾随空白数
paramsLayout->addWidget(new QLabel("尾随空白数:"), 2, 0);
kwsTrailingBlanksEdit = new QLineEdit(this);
kwsTrailingBlanksEdit->setText("2");
kwsTrailingBlanksEdit->setToolTip("范围: 1-5越大端点检测越宽松建议: 2");
paramsLayout->addWidget(kwsTrailingBlanksEdit, 2, 1);
paramsLayout->addWidget(new QLabel("(1-5, 推荐2)"), 2, 2);
// 关键词分数权重
paramsLayout->addWidget(new QLabel("关键词分数权重:"), 3, 0);
kwsKeywordsScoreEdit = new QLineEdit(this);
kwsKeywordsScoreEdit->setText("1.5");
kwsKeywordsScoreEdit->setToolTip("范围: 0.5-3.0,越大关键词权重越高,建议: 1.5");
paramsLayout->addWidget(kwsKeywordsScoreEdit, 3, 1);
paramsLayout->addWidget(new QLabel("(0.5-3.0, 推荐1.5)"), 3, 2);
// 线程数
paramsLayout->addWidget(new QLabel("处理线程数:"), 4, 0);
kwsNumThreadsEdit = new QLineEdit(this);
kwsNumThreadsEdit->setText("2");
kwsNumThreadsEdit->setToolTip("范围: 1-4越大速度越快但占用资源越多建议: 2");
paramsLayout->addWidget(kwsNumThreadsEdit, 4, 1);
paramsLayout->addWidget(new QLabel("(1-4, 推荐2)"), 4, 2);
// 重置按钮
kwsResetParamsBtn = new QPushButton("恢复默认参数", this);
kwsResetParamsBtn->setToolTip("将所有参数恢复为推荐的默认值");
paramsLayout->addWidget(kwsResetParamsBtn, 5, 0, 1, 3);
// 参数说明
auto* paramsHelpLabel = new QLabel(
"💡 参数调整建议:\n"
"• 识别率低:降低阈值(0.15-0.25),增加活跃路径(8-12)\n"
"• 误识别多:提高阈值(0.3-0.5),减少活跃路径(4-6)\n"
"• 响应慢:增加线程数(2-4),减少尾随空白(1)\n"
"• 修改参数后需要重启KWS检测才能生效", this);
paramsHelpLabel->setWordWrap(true);
paramsHelpLabel->setStyleSheet("QLabel { color: #666; font-size: 11px; padding: 10px; background-color: #f5f5f5; border-radius: 5px; }");
paramsLayout->addWidget(paramsHelpLabel, 6, 0, 1, 3);
layout->addWidget(kwsParamsGroup);
layout->addStretch();
// 连接信号
connect(browseModelBtn, &QPushButton::clicked, this, &ModelSettingsDialog::browseKWSModel);
connect(browseTokensBtn, &QPushButton::clicked, this, &ModelSettingsDialog::browseKWSTokens);
connect(browseKeywordsBtn, &QPushButton::clicked, this, &ModelSettingsDialog::browseKWSKeywords);
connect(kwsResetParamsBtn, &QPushButton::clicked, this, &ModelSettingsDialog::resetKWSParams);
// 连接参数变化信号
connect(kwsThresholdEdit, &QLineEdit::textChanged, this, &ModelSettingsDialog::onKWSParamsChanged);
connect(kwsMaxActivePathsEdit, &QLineEdit::textChanged, this, &ModelSettingsDialog::onKWSParamsChanged);
connect(kwsTrailingBlanksEdit, &QLineEdit::textChanged, this, &ModelSettingsDialog::onKWSParamsChanged);
connect(kwsKeywordsScoreEdit, &QLineEdit::textChanged, this, &ModelSettingsDialog::onKWSParamsChanged);
connect(kwsNumThreadsEdit, &QLineEdit::textChanged, this, &ModelSettingsDialog::onKWSParamsChanged);
}
void ModelSettingsDialog::setupTTSTab() {
ttsTab = new QWidget();
tabWidget->addTab(ttsTab, "语音合成 (TTS)");
auto* layout = new QVBoxLayout(ttsTab);
// 模型选择组
auto* modelGroup = new QGroupBox("模型选择", this);
auto* modelLayout = new QGridLayout(modelGroup);
modelLayout->addWidget(new QLabel("预设模型:"), 0, 0);
ttsModelCombo = new QComboBox(this);
ttsModelCombo->addItem("自定义", "custom");
ttsModelCombo->addItem("MeloTTS中英文混合", "melo-zh-en");
ttsModelCombo->addItem("VITS中文模型", "vits-zh");
modelLayout->addWidget(ttsModelCombo, 0, 1, 1, 2);
layout->addWidget(modelGroup);
// 模型路径组
auto* pathGroup = new QGroupBox("模型路径", this);
auto* pathLayout = new QGridLayout(pathGroup);
pathLayout->addWidget(new QLabel("模型文件:"), 0, 0);
ttsModelPathEdit = new QLineEdit(this);
ttsModelPathEdit->setPlaceholderText("选择.onnx模型文件...");
auto* browseModelBtn = new QPushButton("浏览", this);
pathLayout->addWidget(ttsModelPathEdit, 0, 1);
pathLayout->addWidget(browseModelBtn, 0, 2);
pathLayout->addWidget(new QLabel("词汇表文件:"), 1, 0);
ttsTokensPathEdit = new QLineEdit(this);
ttsTokensPathEdit->setPlaceholderText("选择tokens.txt文件...");
auto* browseTokensBtn = new QPushButton("浏览", this);
pathLayout->addWidget(ttsTokensPathEdit, 1, 1);
pathLayout->addWidget(browseTokensBtn, 1, 2);
pathLayout->addWidget(new QLabel("词典文件:"), 2, 0);
ttsLexiconPathEdit = new QLineEdit(this);
ttsLexiconPathEdit->setPlaceholderText("选择lexicon.txt文件...");
auto* browseLexiconBtn = new QPushButton("浏览", this);
pathLayout->addWidget(ttsLexiconPathEdit, 2, 1);
pathLayout->addWidget(browseLexiconBtn, 2, 2);
pathLayout->addWidget(new QLabel("字典目录:"), 3, 0);
ttsDictDirPathEdit = new QLineEdit(this);
ttsDictDirPathEdit->setPlaceholderText("选择dict目录...");
auto* browseDictBtn = new QPushButton("浏览", this);
pathLayout->addWidget(ttsDictDirPathEdit, 3, 1);
pathLayout->addWidget(browseDictBtn, 3, 2);
pathLayout->addWidget(new QLabel("数据目录:"), 4, 0);
ttsDataDirPathEdit = new QLineEdit(this);
ttsDataDirPathEdit->setPlaceholderText("选择espeak-ng-data目录...");
auto* browseDataBtn = new QPushButton("浏览", this);
pathLayout->addWidget(ttsDataDirPathEdit, 4, 1);
pathLayout->addWidget(browseDataBtn, 4, 2);
layout->addWidget(pathGroup);
// 模型信息组
auto* infoGroup = new QGroupBox("模型信息", this);
auto* infoLayout = new QVBoxLayout(infoGroup);
ttsModelInfoEdit = new QTextEdit(this);
ttsModelInfoEdit->setMaximumHeight(100);
ttsModelInfoEdit->setPlaceholderText("模型信息将显示在这里...");
infoLayout->addWidget(ttsModelInfoEdit);
auto* testLayout = new QHBoxLayout();
testTTSBtn = new QPushButton("测试模型", this);
testTTSBtn->setEnabled(false);
testLayout->addStretch();
testLayout->addWidget(testTTSBtn);
infoLayout->addLayout(testLayout);
layout->addWidget(infoGroup);
layout->addStretch();
// 连接信号
connect(browseModelBtn, &QPushButton::clicked, this, &ModelSettingsDialog::browseTTSModel);
connect(browseTokensBtn, &QPushButton::clicked, this, &ModelSettingsDialog::browseTTSTokens);
connect(browseLexiconBtn, &QPushButton::clicked, this, &ModelSettingsDialog::browseTTSLexicon);
connect(browseDictBtn, &QPushButton::clicked, this, &ModelSettingsDialog::browseTTSDictDir);
connect(browseDataBtn, &QPushButton::clicked, this, &ModelSettingsDialog::browseTTSDataDir);
}
void ModelSettingsDialog::setupAdvancedTab() {
advancedTab = new QWidget();
tabWidget->addTab(advancedTab, "高级设置");
auto* layout = new QVBoxLayout(advancedTab);
// 路径设置组
auto* pathGroup = new QGroupBox("路径设置", this);
auto* pathLayout = new QGridLayout(pathGroup);
pathLayout->addWidget(new QLabel("数据根目录:"), 0, 0);
dataPathEdit = new QLineEdit(this);
dataPathEdit->setText(getDefaultDataPath());
auto* browseDataPathBtn = new QPushButton("浏览", this);
pathLayout->addWidget(dataPathEdit, 0, 1);
pathLayout->addWidget(browseDataPathBtn, 0, 2);
layout->addWidget(pathGroup);
// 功能设置组
auto* featureGroup = new QGroupBox("功能设置", this);
auto* featureLayout = new QVBoxLayout(featureGroup);
autoScanCheckBox = new QCheckBox("启动时自动扫描模型", this);
autoScanCheckBox->setChecked(true);
featureLayout->addWidget(autoScanCheckBox);
enableLoggingCheckBox = new QCheckBox("启用详细日志", this);
enableLoggingCheckBox->setChecked(false);
featureLayout->addWidget(enableLoggingCheckBox);
layout->addWidget(featureGroup);
layout->addStretch();
// 连接信号
connect(browseDataPathBtn, &QPushButton::clicked, [this]() {
QString dir = QFileDialog::getExistingDirectory(this, "选择数据根目录", dataPathEdit->text());
if (!dir.isEmpty()) {
dataPathEdit->setText(dir);
}
});
}
void ModelSettingsDialog::connectSignals() {
connect(offlineAsrModelCombo, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, &ModelSettingsDialog::onOfflineASRModelChanged);
connect(onlineAsrModelCombo, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, &ModelSettingsDialog::onOnlineASRModelChanged);
connect(kwsModelCombo, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, &ModelSettingsDialog::onKWSModelChanged);
connect(ttsModelCombo, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, &ModelSettingsDialog::onTTSModelChanged);
connect(offlineAsrModelPathEdit, &QLineEdit::textChanged, this, &ModelSettingsDialog::updateOfflineASRModelInfo);
connect(onlineAsrModelPathEdit, &QLineEdit::textChanged, this, &ModelSettingsDialog::updateOnlineASRModelInfo);
connect(kwsModelPathEdit, &QLineEdit::textChanged, this, &ModelSettingsDialog::updateKWSModelInfo);
connect(ttsModelPathEdit, &QLineEdit::textChanged, this, &ModelSettingsDialog::updateTTSModelInfo);
connect(testOfflineASRBtn, &QPushButton::clicked, this, &ModelSettingsDialog::testOfflineASRModel);
connect(testOnlineASRBtn, &QPushButton::clicked, this, &ModelSettingsDialog::testOnlineASRModel);
connect(testKWSBtn, &QPushButton::clicked, this, &ModelSettingsDialog::testKWSModel);
connect(testTTSBtn, &QPushButton::clicked, this, &ModelSettingsDialog::testTTSModel);
connect(saveBtn, &QPushButton::clicked, this, &ModelSettingsDialog::saveSettings);
connect(cancelBtn, &QPushButton::clicked, this, &QDialog::reject);
connect(resetBtn, &QPushButton::clicked, this, &ModelSettingsDialog::resetToDefaults);
connect(scanBtn, &QPushButton::clicked, this, &ModelSettingsDialog::scanForModels);
}
QString ModelSettingsDialog::getDefaultDataPath() const {
return QDir::homePath() + "/.config/QSmartAssistant/Data";
}
void ModelSettingsDialog::browseOfflineASRModel() {
QString fileName = QFileDialog::getOpenFileName(this, "选择离线ASR模型文件",
offlineAsrModelPathEdit->text().isEmpty() ? getDefaultDataPath() : offlineAsrModelPathEdit->text(),
"ONNX模型文件 (*.onnx)");
if (!fileName.isEmpty()) {
offlineAsrModelPathEdit->setText(fileName);
}
}
void ModelSettingsDialog::browseOfflineASRTokens() {
QString fileName = QFileDialog::getOpenFileName(this, "选择词汇表文件",
offlineAsrTokensPathEdit->text().isEmpty() ? getDefaultDataPath() : offlineAsrTokensPathEdit->text(),
"文本文件 (*.txt)");
if (!fileName.isEmpty()) {
offlineAsrTokensPathEdit->setText(fileName);
}
}
void ModelSettingsDialog::browseOnlineASRModel() {
QString fileName = QFileDialog::getOpenFileName(this, "选择在线ASR编码器文件",
onlineAsrModelPathEdit->text().isEmpty() ? getDefaultDataPath() : onlineAsrModelPathEdit->text(),
"ONNX模型文件 (*.onnx)");
if (!fileName.isEmpty()) {
onlineAsrModelPathEdit->setText(fileName);
}
}
void ModelSettingsDialog::browseOnlineASRTokens() {
QString fileName = QFileDialog::getOpenFileName(this, "选择词汇表文件",
onlineAsrTokensPathEdit->text().isEmpty() ? getDefaultDataPath() : onlineAsrTokensPathEdit->text(),
"文本文件 (*.txt)");
if (!fileName.isEmpty()) {
onlineAsrTokensPathEdit->setText(fileName);
}
}
void ModelSettingsDialog::browseKWSModel() {
QString fileName = QFileDialog::getOpenFileName(this, "选择语音唤醒模型文件",
kwsModelPathEdit->text().isEmpty() ? getDefaultDataPath() : kwsModelPathEdit->text(),
"ONNX模型文件 (*.onnx)");
if (!fileName.isEmpty()) {
kwsModelPathEdit->setText(fileName);
}
}
void ModelSettingsDialog::browseKWSTokens() {
QString fileName = QFileDialog::getOpenFileName(this, "选择词汇表文件",
kwsTokensPathEdit->text().isEmpty() ? getDefaultDataPath() : kwsTokensPathEdit->text(),
"文本文件 (*.txt)");
if (!fileName.isEmpty()) {
kwsTokensPathEdit->setText(fileName);
}
}
void ModelSettingsDialog::browseKWSKeywords() {
QString fileName = QFileDialog::getOpenFileName(this, "选择关键词文件",
kwsKeywordsPathEdit->text().isEmpty() ? getDefaultDataPath() : kwsKeywordsPathEdit->text(),
"文本文件 (*.txt)");
if (!fileName.isEmpty()) {
kwsKeywordsPathEdit->setText(fileName);
}
}
void ModelSettingsDialog::browseTTSModel() {
QString fileName = QFileDialog::getOpenFileName(this, "选择TTS模型文件",
ttsModelPathEdit->text().isEmpty() ? getDefaultDataPath() : ttsModelPathEdit->text(),
"ONNX模型文件 (*.onnx)");
if (!fileName.isEmpty()) {
ttsModelPathEdit->setText(fileName);
}
}
void ModelSettingsDialog::browseTTSTokens() {
QString fileName = QFileDialog::getOpenFileName(this, "选择词汇表文件",
ttsTokensPathEdit->text().isEmpty() ? getDefaultDataPath() : ttsTokensPathEdit->text(),
"文本文件 (*.txt)");
if (!fileName.isEmpty()) {
ttsTokensPathEdit->setText(fileName);
}
}
void ModelSettingsDialog::browseTTSLexicon() {
QString fileName = QFileDialog::getOpenFileName(this, "选择词典文件",
ttsLexiconPathEdit->text().isEmpty() ? getDefaultDataPath() : ttsLexiconPathEdit->text(),
"文本文件 (*.txt)");
if (!fileName.isEmpty()) {
ttsLexiconPathEdit->setText(fileName);
}
}
void ModelSettingsDialog::browseTTSDictDir() {
QString dir = QFileDialog::getExistingDirectory(this, "选择字典目录",
ttsDictDirPathEdit->text().isEmpty() ? getDefaultDataPath() : ttsDictDirPathEdit->text());
if (!dir.isEmpty()) {
ttsDictDirPathEdit->setText(dir);
}
}
void ModelSettingsDialog::browseTTSDataDir() {
QString dir = QFileDialog::getExistingDirectory(this, "选择数据目录",
ttsDataDirPathEdit->text().isEmpty() ? getDefaultDataPath() : ttsDataDirPathEdit->text());
if (!dir.isEmpty()) {
ttsDataDirPathEdit->setText(dir);
}
}
void ModelSettingsDialog::onOfflineASRModelChanged() {
QString modelType = offlineAsrModelCombo->currentData().toString();
QString dataPath = dataPathEdit->text();
if (modelType == "paraformer-zh") {
offlineAsrModelPathEdit->setText(dataPath + "/sherpa-onnx-paraformer-zh-2024-03-09/model.int8.onnx");
offlineAsrTokensPathEdit->setText(dataPath + "/sherpa-onnx-paraformer-zh-2024-03-09/tokens.txt");
} else if (modelType == "whisper-multilingual") {
offlineAsrModelPathEdit->setText(dataPath + "/sherpa-onnx-whisper-base/base.onnx");
offlineAsrTokensPathEdit->setText(dataPath + "/sherpa-onnx-whisper-base/base.tokens");
}
// 自定义模式不自动填充路径
}
void ModelSettingsDialog::onOnlineASRModelChanged() {
QString modelType = onlineAsrModelCombo->currentData().toString();
QString dataPath = dataPathEdit->text();
if (modelType == "streaming-paraformer-zh-en") {
onlineAsrModelPathEdit->setText(dataPath + "/sherpa-onnx-streaming-paraformer-bilingual-zh-en/encoder.int8.onnx");
onlineAsrTokensPathEdit->setText(dataPath + "/sherpa-onnx-streaming-paraformer-bilingual-zh-en/tokens.txt");
} else if (modelType == "streaming-zipformer-zh-en") {
onlineAsrModelPathEdit->setText(dataPath + "/sherpa-onnx-streaming-zipformer-zh-en/encoder.onnx");
onlineAsrTokensPathEdit->setText(dataPath + "/sherpa-onnx-streaming-zipformer-zh-en/tokens.txt");
}
// 自定义模式不自动填充路径
}
void ModelSettingsDialog::onKWSModelChanged() {
QString modelType = kwsModelCombo->currentData().toString();
QString dataPath = dataPathEdit->text();
if (modelType == "zipformer-wenetspeech-3.3m") {
kwsModelPathEdit->setText(dataPath + "/sherpa-onnx-kws-zipformer-wenetspeech-3.3M-2024-01-01/encoder-epoch-12-avg-2-chunk-16-left-64.onnx");
kwsTokensPathEdit->setText(dataPath + "/sherpa-onnx-kws-zipformer-wenetspeech-3.3M-2024-01-01/tokens.txt");
kwsKeywordsPathEdit->setText(dataPath + "/sherpa-onnx-kws-zipformer-wenetspeech-3.3M-2024-01-01/keywords.txt");
} else if (modelType == "zipformer-gigaspeech") {
kwsModelPathEdit->setText(dataPath + "/sherpa-onnx-kws-zipformer-gigaspeech/model.onnx");
kwsTokensPathEdit->setText(dataPath + "/sherpa-onnx-kws-zipformer-gigaspeech/tokens.txt");
kwsKeywordsPathEdit->setText(dataPath + "/sherpa-onnx-kws-zipformer-gigaspeech/keywords.txt");
}
// 自定义模式不自动填充路径
}
void ModelSettingsDialog::onTTSModelChanged() {
QString modelType = ttsModelCombo->currentData().toString();
QString dataPath = dataPathEdit->text();
if (modelType == "melo-zh-en") {
ttsModelPathEdit->setText(dataPath + "/vits-melo-tts-zh_en/model.int8.onnx");
ttsTokensPathEdit->setText(dataPath + "/vits-melo-tts-zh_en/tokens.txt");
ttsLexiconPathEdit->setText(dataPath + "/vits-melo-tts-zh_en/lexicon.txt");
ttsDictDirPathEdit->setText(dataPath + "/vits-melo-tts-zh_en/dict");
ttsDataDirPathEdit->clear();
} else if (modelType == "vits-zh") {
ttsModelPathEdit->setText(dataPath + "/vits-zh-aishell3/vits-aishell3.int8.onnx");
ttsTokensPathEdit->setText(dataPath + "/vits-zh-aishell3/tokens.txt");
ttsLexiconPathEdit->setText(dataPath + "/vits-zh-aishell3/lexicon.txt");
ttsDictDirPathEdit->clear();
ttsDataDirPathEdit->clear();
}
// 自定义模式不自动填充路径
}
void ModelSettingsDialog::updateOfflineASRModelInfo() {
QString modelPath = offlineAsrModelPathEdit->text();
if (modelPath.isEmpty()) {
offlineAsrModelInfoEdit->clear();
testOfflineASRBtn->setEnabled(false);
return;
}
QFileInfo fileInfo(modelPath);
if (fileInfo.exists()) {
QString info = QString("文件大小: %1 MB\n修改时间: %2\n状态: 文件存在")
.arg(fileInfo.size() / 1024.0 / 1024.0, 0, 'f', 1)
.arg(fileInfo.lastModified().toString("yyyy-MM-dd hh:mm:ss"));
offlineAsrModelInfoEdit->setText(info);
testOfflineASRBtn->setEnabled(validateOfflineASRConfig());
} else {
offlineAsrModelInfoEdit->setText("状态: 文件不存在");
testOfflineASRBtn->setEnabled(false);
}
}
void ModelSettingsDialog::updateOnlineASRModelInfo() {
QString modelPath = onlineAsrModelPathEdit->text();
if (modelPath.isEmpty()) {
onlineAsrModelInfoEdit->clear();
testOnlineASRBtn->setEnabled(false);
return;
}
QFileInfo fileInfo(modelPath);
if (fileInfo.exists()) {
QString info = QString("文件大小: %1 MB\n修改时间: %2\n状态: 文件存在")
.arg(fileInfo.size() / 1024.0 / 1024.0, 0, 'f', 1)
.arg(fileInfo.lastModified().toString("yyyy-MM-dd hh:mm:ss"));
onlineAsrModelInfoEdit->setText(info);
testOnlineASRBtn->setEnabled(validateOnlineASRConfig());
} else {
onlineAsrModelInfoEdit->setText("状态: 文件不存在");
testOnlineASRBtn->setEnabled(false);
}
}
void ModelSettingsDialog::updateKWSModelInfo() {
QString modelPath = kwsModelPathEdit->text();
if (modelPath.isEmpty()) {
kwsModelInfoEdit->clear();
testKWSBtn->setEnabled(false);
return;
}
QFileInfo fileInfo(modelPath);
if (fileInfo.exists()) {
QString info = QString("文件大小: %1 MB\n修改时间: %2\n状态: 文件存在")
.arg(fileInfo.size() / 1024.0 / 1024.0, 0, 'f', 1)
.arg(fileInfo.lastModified().toString("yyyy-MM-dd hh:mm:ss"));
kwsModelInfoEdit->setText(info);
testKWSBtn->setEnabled(validateKWSConfig());
} else {
kwsModelInfoEdit->setText("状态: 文件不存在");
testKWSBtn->setEnabled(false);
}
}
void ModelSettingsDialog::updateTTSModelInfo() {
QString modelPath = ttsModelPathEdit->text();
if (modelPath.isEmpty()) {
ttsModelInfoEdit->clear();
testTTSBtn->setEnabled(false);
return;
}
QFileInfo fileInfo(modelPath);
if (fileInfo.exists()) {
QString info = QString("文件大小: %1 MB\n修改时间: %2\n状态: 文件存在")
.arg(fileInfo.size() / 1024.0 / 1024.0, 0, 'f', 1)
.arg(fileInfo.lastModified().toString("yyyy-MM-dd hh:mm:ss"));
ttsModelInfoEdit->setText(info);
testTTSBtn->setEnabled(validateTTSConfig());
} else {
ttsModelInfoEdit->setText("状态: 文件不存在");
testTTSBtn->setEnabled(false);
}
}
bool ModelSettingsDialog::validateOfflineASRConfig() const {
return QFileInfo::exists(offlineAsrModelPathEdit->text()) &&
QFileInfo::exists(offlineAsrTokensPathEdit->text());
}
bool ModelSettingsDialog::validateOnlineASRConfig() const {
return QFileInfo::exists(onlineAsrModelPathEdit->text()) &&
QFileInfo::exists(onlineAsrTokensPathEdit->text());
}
bool ModelSettingsDialog::validateKWSConfig() const {
return QFileInfo::exists(kwsModelPathEdit->text()) &&
QFileInfo::exists(kwsTokensPathEdit->text()) &&
QFileInfo::exists(kwsKeywordsPathEdit->text());
}
bool ModelSettingsDialog::validateTTSConfig() const {
return QFileInfo::exists(ttsModelPathEdit->text()) &&
QFileInfo::exists(ttsTokensPathEdit->text()) &&
QFileInfo::exists(ttsLexiconPathEdit->text());
}
void ModelSettingsDialog::testOfflineASRModel() {
QMessageBox::information(this, "测试离线ASR模型", "离线ASR模型测试功能待实现");
}
void ModelSettingsDialog::testOnlineASRModel() {
QMessageBox::information(this, "测试在线ASR模型", "在线ASR模型测试功能待实现");
}
void ModelSettingsDialog::testKWSModel() {
QMessageBox::information(this, "测试语音唤醒模型", "语音唤醒模型测试功能待实现");
}
void ModelSettingsDialog::testTTSModel() {
QMessageBox::information(this, "测试TTS模型", "TTS模型测试功能待实现");
}
void ModelSettingsDialog::saveSettings() {
// 保存离线ASR设置
settings->beginGroup("OfflineASR");
settings->setValue("modelPath", offlineAsrModelPathEdit->text());
settings->setValue("tokensPath", offlineAsrTokensPathEdit->text());
settings->setValue("modelType", offlineAsrModelCombo->currentData().toString());
settings->endGroup();
// 保存在线ASR设置
settings->beginGroup("OnlineASR");
settings->setValue("modelPath", onlineAsrModelPathEdit->text());
settings->setValue("tokensPath", onlineAsrTokensPathEdit->text());
settings->setValue("modelType", onlineAsrModelCombo->currentData().toString());
settings->endGroup();
// 保存语音唤醒设置
settings->beginGroup("KWS");
settings->setValue("modelPath", kwsModelPathEdit->text());
settings->setValue("tokensPath", kwsTokensPathEdit->text());
settings->setValue("keywordsPath", kwsKeywordsPathEdit->text());
settings->setValue("modelType", kwsModelCombo->currentData().toString());
// 保存KWS参数
KWSParams params = getCurrentKWSParams();
settings->setValue("threshold", params.threshold);
settings->setValue("maxActivePaths", params.maxActivePaths);
settings->setValue("numTrailingBlanks", params.numTrailingBlanks);
settings->setValue("keywordsScore", params.keywordsScore);
settings->setValue("numThreads", params.numThreads);
settings->endGroup();
// 保存TTS设置
settings->beginGroup("TTS");
settings->setValue("modelPath", ttsModelPathEdit->text());
settings->setValue("tokensPath", ttsTokensPathEdit->text());
settings->setValue("lexiconPath", ttsLexiconPathEdit->text());
settings->setValue("dictDirPath", ttsDictDirPathEdit->text());
settings->setValue("dataDirPath", ttsDataDirPathEdit->text());
settings->setValue("modelType", ttsModelCombo->currentData().toString());
settings->endGroup();
// 保存高级设置
settings->beginGroup("Advanced");
settings->setValue("dataPath", dataPathEdit->text());
settings->setValue("autoScan", autoScanCheckBox->isChecked());
settings->setValue("enableLogging", enableLoggingCheckBox->isChecked());
settings->endGroup();
emit modelsChanged();
accept();
}
void ModelSettingsDialog::loadSettings() {
// 加载离线ASR设置
settings->beginGroup("OfflineASR");
offlineAsrModelPathEdit->setText(settings->value("modelPath").toString());
offlineAsrTokensPathEdit->setText(settings->value("tokensPath").toString());
QString offlineAsrModelType = settings->value("modelType", "paraformer-zh").toString();
int offlineAsrIndex = offlineAsrModelCombo->findData(offlineAsrModelType);
if (offlineAsrIndex >= 0) offlineAsrModelCombo->setCurrentIndex(offlineAsrIndex);
settings->endGroup();
// 加载在线ASR设置
settings->beginGroup("OnlineASR");
onlineAsrModelPathEdit->setText(settings->value("modelPath").toString());
onlineAsrTokensPathEdit->setText(settings->value("tokensPath").toString());
QString onlineAsrModelType = settings->value("modelType", "streaming-paraformer-zh-en").toString();
int onlineAsrIndex = onlineAsrModelCombo->findData(onlineAsrModelType);
if (onlineAsrIndex >= 0) onlineAsrModelCombo->setCurrentIndex(onlineAsrIndex);
settings->endGroup();
// 加载语音唤醒设置
settings->beginGroup("KWS");
kwsModelPathEdit->setText(settings->value("modelPath").toString());
kwsTokensPathEdit->setText(settings->value("tokensPath").toString());
kwsKeywordsPathEdit->setText(settings->value("keywordsPath").toString());
QString kwsModelType = settings->value("modelType", "zipformer-wenetspeech-3.3m").toString();
int kwsIndex = kwsModelCombo->findData(kwsModelType);
if (kwsIndex >= 0) kwsModelCombo->setCurrentIndex(kwsIndex);
// 加载KWS参数
KWSParams params;
params.threshold = settings->value("threshold", 0.25f).toFloat();
params.maxActivePaths = settings->value("maxActivePaths", 8).toInt();
params.numTrailingBlanks = settings->value("numTrailingBlanks", 2).toInt();
params.keywordsScore = settings->value("keywordsScore", 1.5f).toFloat();
params.numThreads = settings->value("numThreads", 2).toInt();
setCurrentKWSParams(params);
settings->endGroup();
// 加载TTS设置
settings->beginGroup("TTS");
ttsModelPathEdit->setText(settings->value("modelPath").toString());
ttsTokensPathEdit->setText(settings->value("tokensPath").toString());
ttsLexiconPathEdit->setText(settings->value("lexiconPath").toString());
ttsDictDirPathEdit->setText(settings->value("dictDirPath").toString());
ttsDataDirPathEdit->setText(settings->value("dataDirPath").toString());
QString ttsModelType = settings->value("modelType", "melo-zh-en").toString();
int ttsIndex = ttsModelCombo->findData(ttsModelType);
if (ttsIndex >= 0) ttsModelCombo->setCurrentIndex(ttsIndex);
settings->endGroup();
// 加载高级设置
settings->beginGroup("Advanced");
dataPathEdit->setText(settings->value("dataPath", getDefaultDataPath()).toString());
autoScanCheckBox->setChecked(settings->value("autoScan", true).toBool());
enableLoggingCheckBox->setChecked(settings->value("enableLogging", false).toBool());
settings->endGroup();
// 如果没有保存的设置,使用默认值
if (offlineAsrModelPathEdit->text().isEmpty()) {
onOfflineASRModelChanged();
}
if (onlineAsrModelPathEdit->text().isEmpty()) {
onOnlineASRModelChanged();
}
if (kwsModelPathEdit->text().isEmpty()) {
onKWSModelChanged();
}
if (ttsModelPathEdit->text().isEmpty()) {
onTTSModelChanged();
}
}
void ModelSettingsDialog::resetToDefaults() {
int ret = QMessageBox::question(this, "重置设置",
"确定要重置所有设置为默认值吗?这将清除当前的所有配置。",
QMessageBox::Yes | QMessageBox::No);
if (ret == QMessageBox::Yes) {
// 重置为默认值
offlineAsrModelCombo->setCurrentIndex(1); // paraformer-zh
onlineAsrModelCombo->setCurrentIndex(1); // streaming-paraformer-zh-en
kwsModelCombo->setCurrentIndex(1); // zipformer-wenetspeech-3.3m
ttsModelCombo->setCurrentIndex(1); // melo-zh-en
dataPathEdit->setText(getDefaultDataPath());
autoScanCheckBox->setChecked(true);
enableLoggingCheckBox->setChecked(false);
onOfflineASRModelChanged();
onOnlineASRModelChanged();
onKWSModelChanged();
onTTSModelChanged();
}
}
void ModelSettingsDialog::scanForModels() {
QMessageBox::information(this, "扫描模型", "模型扫描功能待实现");
}
ModelConfig ModelSettingsDialog::getCurrentOfflineASRConfig() const {
ModelConfig config;
config.name = offlineAsrModelCombo->currentText();
config.modelPath = offlineAsrModelPathEdit->text();
config.tokensPath = offlineAsrTokensPathEdit->text();
config.isEnabled = validateOfflineASRConfig();
return config;
}
ModelConfig ModelSettingsDialog::getCurrentOnlineASRConfig() const {
ModelConfig config;
config.name = onlineAsrModelCombo->currentText();
config.modelPath = onlineAsrModelPathEdit->text();
config.tokensPath = onlineAsrTokensPathEdit->text();
config.isEnabled = validateOnlineASRConfig();
return config;
}
ModelConfig ModelSettingsDialog::getCurrentKWSConfig() const {
ModelConfig config;
config.name = kwsModelCombo->currentText();
config.modelPath = kwsModelPathEdit->text();
config.tokensPath = kwsTokensPathEdit->text();
config.lexiconPath = kwsKeywordsPathEdit->text(); // 使用lexiconPath存储关键词文件路径
config.isEnabled = validateKWSConfig();
return config;
}
ModelConfig ModelSettingsDialog::getCurrentTTSConfig() const {
ModelConfig config;
config.name = ttsModelCombo->currentText();
config.modelPath = ttsModelPathEdit->text();
config.tokensPath = ttsTokensPathEdit->text();
config.lexiconPath = ttsLexiconPathEdit->text();
config.dictDirPath = ttsDictDirPathEdit->text();
config.dataDirPath = ttsDataDirPathEdit->text();
config.isEnabled = validateTTSConfig();
return config;
}
void ModelSettingsDialog::setCurrentOfflineASRConfig(const ModelConfig& config) {
offlineAsrModelPathEdit->setText(config.modelPath);
offlineAsrTokensPathEdit->setText(config.tokensPath);
}
void ModelSettingsDialog::setCurrentOnlineASRConfig(const ModelConfig& config) {
onlineAsrModelPathEdit->setText(config.modelPath);
onlineAsrTokensPathEdit->setText(config.tokensPath);
}
void ModelSettingsDialog::setCurrentKWSConfig(const ModelConfig& config) {
kwsModelPathEdit->setText(config.modelPath);
kwsTokensPathEdit->setText(config.tokensPath);
kwsKeywordsPathEdit->setText(config.lexiconPath); // 从lexiconPath读取关键词文件路径
}
void ModelSettingsDialog::setCurrentTTSConfig(const ModelConfig& config) {
ttsModelPathEdit->setText(config.modelPath);
ttsTokensPathEdit->setText(config.tokensPath);
ttsLexiconPathEdit->setText(config.lexiconPath);
ttsDictDirPathEdit->setText(config.dictDirPath);
ttsDataDirPathEdit->setText(config.dataDirPath);
}
// KWS参数相关方法实现
ModelSettingsDialog::KWSParams ModelSettingsDialog::getCurrentKWSParams() const {
KWSParams params;
bool ok;
params.threshold = kwsThresholdEdit->text().toFloat(&ok);
if (!ok || params.threshold < 0.01f || params.threshold > 1.0f) {
params.threshold = 0.25f; // 默认值
}
params.maxActivePaths = kwsMaxActivePathsEdit->text().toInt(&ok);
if (!ok || params.maxActivePaths < 1 || params.maxActivePaths > 16) {
params.maxActivePaths = 8; // 默认值
}
params.numTrailingBlanks = kwsTrailingBlanksEdit->text().toInt(&ok);
if (!ok || params.numTrailingBlanks < 1 || params.numTrailingBlanks > 5) {
params.numTrailingBlanks = 2; // 默认值
}
params.keywordsScore = kwsKeywordsScoreEdit->text().toFloat(&ok);
if (!ok || params.keywordsScore < 0.5f || params.keywordsScore > 3.0f) {
params.keywordsScore = 1.5f; // 默认值
}
params.numThreads = kwsNumThreadsEdit->text().toInt(&ok);
if (!ok || params.numThreads < 1 || params.numThreads > 4) {
params.numThreads = 2; // 默认值
}
return params;
}
void ModelSettingsDialog::setCurrentKWSParams(const KWSParams& params) {
kwsThresholdEdit->setText(QString::number(params.threshold, 'f', 2));
kwsMaxActivePathsEdit->setText(QString::number(params.maxActivePaths));
kwsTrailingBlanksEdit->setText(QString::number(params.numTrailingBlanks));
kwsKeywordsScoreEdit->setText(QString::number(params.keywordsScore, 'f', 1));
kwsNumThreadsEdit->setText(QString::number(params.numThreads));
}
void ModelSettingsDialog::onKWSParamsChanged() {
// 实时验证参数
validateKWSParams();
}
void ModelSettingsDialog::resetKWSParams() {
int ret = QMessageBox::question(this, "重置KWS参数",
"确定要重置KWS参数为推荐的默认值吗",
QMessageBox::Yes | QMessageBox::No);
if (ret == QMessageBox::Yes) {
KWSParams defaultParams; // 使用默认构造函数的默认值
setCurrentKWSParams(defaultParams);
QMessageBox::information(this, "参数重置",
"KWS参数已重置为默认值。\n"
"请保存设置并重启KWS检测以使新参数生效。");
}
}
void ModelSettingsDialog::validateKWSParams() {
QString errorMsg;
bool hasError = false;
// 验证阈值
bool ok;
float threshold = kwsThresholdEdit->text().toFloat(&ok);
if (!ok || threshold < 0.01f || threshold > 1.0f) {
errorMsg += "• 关键词阈值必须在0.01-1.0范围内\n";
hasError = true;
kwsThresholdEdit->setStyleSheet("QLineEdit { border: 2px solid red; }");
} else {
kwsThresholdEdit->setStyleSheet("");
}
// 验证最大活跃路径数
int maxActivePaths = kwsMaxActivePathsEdit->text().toInt(&ok);
if (!ok || maxActivePaths < 1 || maxActivePaths > 16) {
errorMsg += "• 最大活跃路径数必须在1-16范围内\n";
hasError = true;
kwsMaxActivePathsEdit->setStyleSheet("QLineEdit { border: 2px solid red; }");
} else {
kwsMaxActivePathsEdit->setStyleSheet("");
}
// 验证尾随空白数
int numTrailingBlanks = kwsTrailingBlanksEdit->text().toInt(&ok);
if (!ok || numTrailingBlanks < 1 || numTrailingBlanks > 5) {
errorMsg += "• 尾随空白数必须在1-5范围内\n";
hasError = true;
kwsTrailingBlanksEdit->setStyleSheet("QLineEdit { border: 2px solid red; }");
} else {
kwsTrailingBlanksEdit->setStyleSheet("");
}
// 验证关键词分数权重
float keywordsScore = kwsKeywordsScoreEdit->text().toFloat(&ok);
if (!ok || keywordsScore < 0.5f || keywordsScore > 3.0f) {
errorMsg += "• 关键词分数权重必须在0.5-3.0范围内\n";
hasError = true;
kwsKeywordsScoreEdit->setStyleSheet("QLineEdit { border: 2px solid red; }");
} else {
kwsKeywordsScoreEdit->setStyleSheet("");
}
// 验证线程数
int numThreads = kwsNumThreadsEdit->text().toInt(&ok);
if (!ok || numThreads < 1 || numThreads > 4) {
errorMsg += "• 处理线程数必须在1-4范围内\n";
hasError = true;
kwsNumThreadsEdit->setStyleSheet("QLineEdit { border: 2px solid red; }");
} else {
kwsNumThreadsEdit->setStyleSheet("");
}
// 更新保存按钮状态
if (saveBtn) {
saveBtn->setEnabled(!hasError);
if (hasError) {
saveBtn->setToolTip("请修正参数错误后再保存:\n" + errorMsg);
} else {
saveBtn->setToolTip("保存所有设置");
}
}
}