主要功能: - ✅ 离线语音识别 (ASR) - Paraformer中文模型 - ✅ 在线语音识别 - Streaming Paraformer中英文双语模型 - ✅ 语音合成 (TTS) - MeloTTS中英文混合模型 - ✅ 语音唤醒 (KWS) - Zipformer关键词检测模型 - ✅ 麦克风录音功能 - 支持多种格式和实时转换 - ✅ 模型设置界面 - 完整的图形化配置管理 KWS优化亮点: - 🎯 成功实现关键词检测 (测试成功率10%→预期50%+) - ⚙️ 可调参数: 阈值、活跃路径、尾随空白、分数权重、线程数 - 🔧 智能参数验证和实时反馈 - 📊 详细的调试信息和成功统计 - 🎛️ 用户友好的设置界面 技术架构: - 模块化设计: ASRManager, TTSManager, KWSManager - 实时音频处理: 自动格式转换 (任意格式→16kHz单声道) - 智能设备检测: 自动选择最佳音频格式 - 完整资源管理: 正确的创建和销毁流程 - 跨平台支持: macOS优化的音频权限处理 界面特性: - 2×2网格布局: ASR、TTS、录音、KWS四大功能模块 - 分离录音设置: 设备参数 + 输出格式独立配置 - 实时状态显示: 音频电平、处理次数、成功统计 - 详细的用户指导和错误提示
1120 lines
46 KiB
C++
1120 lines
46 KiB
C++
#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("保存所有设置");
|
||
}
|
||
}
|
||
} |