feat: 集成语音输入服务到主窗口和设置页面

主窗口新增 VoiceInputService 生命周期管理,支持通过配置页面
的开关动态启停 CapsLock 语音输入功能,窗口关闭时自动清理。

设置页面新增两个复选框:
- 调试录音:保存每次识别的原始音频到临时文件夹
- 快捷语音:启用 CapsLock 长按语音输入

转写页面和 STT 测试页面同步配置中的调试音频开关到引擎。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Alvin Young 2026-05-13 11:13:15 +08:00
parent cda68e5376
commit baec3482a7
6 changed files with 64 additions and 0 deletions

View File

@ -174,6 +174,10 @@ void FileTranscribePage::onStartTranscribe() {
return; return;
} }
// 从配置同步调试开关到引擎
sttEngine_->setDebugSaveAudio(
configManager_->get("stt.debug_save_audio").toBool());
isTranscribing_ = true; isTranscribing_ = true;
currentTaskIndex_ = 0; currentTaskIndex_ = 0;
progressBar_->setVisible(true); progressBar_->setVisible(true);

View File

@ -2,6 +2,7 @@
#include "stt_test_page.h" #include "stt_test_page.h"
#include "file_transcribe_page.h" #include "file_transcribe_page.h"
#include "settings_page.h" #include "settings_page.h"
#include "core/voice_input_service.h"
#include "app/config_manager.h" #include "app/config_manager.h"
#include "utils/logger.h" #include "utils/logger.h"
@ -26,6 +27,28 @@ MainWindow::MainWindow(ConfigManager* configManager, QWidget* parent)
setupMenuBar(); setupMenuBar();
loadStyleSheet(); loadStyleSheet();
// 初始化语音输入服务
voiceInputService_ = new VoiceInputService(configManager_, this);
connect(voiceInputService_, &VoiceInputService::statusChanged,
this, [this](const QString& status) {
LOG_DEBUG(kTag, QString("语音输入状态: %1").arg(status));
});
connect(voiceInputService_, &VoiceInputService::error,
this, [this](const QString& err) {
LOG_ERROR(kTag, err);
});
connect(voiceInputService_, &VoiceInputService::recognitionResult,
this, [this](const QString& text) {
LOG_INFO(kTag, QString("语音识别结果: %1").arg(text));
});
// 监听配置变化,动态启停语音输入服务
connect(configManager_, &ConfigManager::configChanged,
this, &MainWindow::onVoiceInputConfigChanged);
// 启动时检查配置
onVoiceInputConfigChanged();
LOG_INFO(kTag, "主窗口已创建"); LOG_INFO(kTag, "主窗口已创建");
} }
@ -79,8 +102,24 @@ void MainWindow::loadStyleSheet() {
} }
void MainWindow::closeEvent(QCloseEvent* event) { void MainWindow::closeEvent(QCloseEvent* event) {
if (voiceInputService_) {
voiceInputService_->stop();
}
LOG_INFO(kTag, "主窗口关闭"); LOG_INFO(kTag, "主窗口关闭");
QMainWindow::closeEvent(event); QMainWindow::closeEvent(event);
} }
void MainWindow::onVoiceInputConfigChanged() {
if (!voiceInputService_) return;
bool enabled = configManager_->get("stt.capslock_voice_enabled").toBool();
if (enabled && !voiceInputService_->isRunning()) {
voiceInputService_->start();
LOG_INFO(kTag, "CapsLock 语音输入已启用");
} else if (!enabled && voiceInputService_->isRunning()) {
voiceInputService_->stop();
LOG_INFO(kTag, "CapsLock 语音输入已关闭");
}
}
} // namespace impress } // namespace impress

View File

@ -10,6 +10,7 @@ class ConfigManager;
class STTTestPage; class STTTestPage;
class FileTranscribePage; class FileTranscribePage;
class SettingsPage; class SettingsPage;
class VoiceInputService;
/** /**
* @brief * @brief
@ -29,8 +30,10 @@ private:
void setupUI(); void setupUI();
void setupMenuBar(); void setupMenuBar();
void loadStyleSheet(); void loadStyleSheet();
void onVoiceInputConfigChanged();
ConfigManager* configManager_; ConfigManager* configManager_;
VoiceInputService* voiceInputService_;
STTTestPage* sttPage_; STTTestPage* sttPage_;
FileTranscribePage* transcribePage_; FileTranscribePage* transcribePage_;
SettingsPage* settingsPage_; SettingsPage* settingsPage_;

View File

@ -83,6 +83,14 @@ void SettingsPage::setupUI() {
streamingCheck_->setChecked(true); streamingCheck_->setChecked(true);
sttLayout->addRow("流式识别:", streamingCheck_); sttLayout->addRow("流式识别:", streamingCheck_);
debugSaveAudioCheck_ = new QCheckBox("保存调试音频到 /tmp/impress_audio_debug/", this);
debugSaveAudioCheck_->setToolTip("开启后,每次识别会将原始音频保存为 WAV 文件,用于调试音频质量问题");
sttLayout->addRow("调试录音:", debugSaveAudioCheck_);
capslockVoiceCheck_ = new QCheckBox("启用 CapsLock 长按语音输入", this);
capslockVoiceCheck_->setToolTip("长按 CapsLock 键 1 秒后触发录音,松开后自动转写并输入到光标位置");
sttLayout->addRow("快捷语音:", capslockVoiceCheck_);
beamSizeSpin_ = new QSpinBox(this); beamSizeSpin_ = new QSpinBox(this);
beamSizeSpin_->setRange(1, 20); beamSizeSpin_->setRange(1, 20);
beamSizeSpin_->setValue(5); beamSizeSpin_->setValue(5);
@ -174,6 +182,8 @@ void SettingsPage::loadFromConfig() {
sampleRateSpin_->setValue(configManager_->get("stt.sample_rate").toInt()); sampleRateSpin_->setValue(configManager_->get("stt.sample_rate").toInt());
languageCombo_->setCurrentText(configManager_->get("stt.language").toString()); languageCombo_->setCurrentText(configManager_->get("stt.language").toString());
streamingCheck_->setChecked(configManager_->get("stt.streaming").toBool()); streamingCheck_->setChecked(configManager_->get("stt.streaming").toBool());
debugSaveAudioCheck_->setChecked(configManager_->get("stt.debug_save_audio").toBool());
capslockVoiceCheck_->setChecked(configManager_->get("stt.capslock_voice_enabled").toBool());
beamSizeSpin_->setValue(configManager_->get("stt.beam_size").toInt()); beamSizeSpin_->setValue(configManager_->get("stt.beam_size").toInt());
temperatureSpin_->setValue(configManager_->get("stt.temperature").toDouble()); temperatureSpin_->setValue(configManager_->get("stt.temperature").toDouble());
@ -196,6 +206,8 @@ void SettingsPage::saveToConfig() {
configManager_->set("stt.sample_rate", sampleRateSpin_->value()); configManager_->set("stt.sample_rate", sampleRateSpin_->value());
configManager_->set("stt.language", languageCombo_->currentText()); configManager_->set("stt.language", languageCombo_->currentText());
configManager_->set("stt.streaming", streamingCheck_->isChecked()); configManager_->set("stt.streaming", streamingCheck_->isChecked());
configManager_->set("stt.debug_save_audio", debugSaveAudioCheck_->isChecked());
configManager_->set("stt.capslock_voice_enabled", capslockVoiceCheck_->isChecked());
configManager_->set("stt.beam_size", beamSizeSpin_->value()); configManager_->set("stt.beam_size", beamSizeSpin_->value());
configManager_->set("stt.temperature", temperatureSpin_->value()); configManager_->set("stt.temperature", temperatureSpin_->value());

View File

@ -51,6 +51,8 @@ private:
QSpinBox* sampleRateSpin_; QSpinBox* sampleRateSpin_;
QComboBox* languageCombo_; QComboBox* languageCombo_;
QCheckBox* streamingCheck_; QCheckBox* streamingCheck_;
QCheckBox* debugSaveAudioCheck_;
QCheckBox* capslockVoiceCheck_;
QSpinBox* beamSizeSpin_; QSpinBox* beamSizeSpin_;
QDoubleSpinBox* temperatureSpin_; QDoubleSpinBox* temperatureSpin_;

View File

@ -135,6 +135,10 @@ void STTTestPage::onToggleRecording() {
return; return;
} }
// 从配置同步调试开关到引擎
sttEngine_->setDebugSaveAudio(
configManager_->get("stt.debug_save_audio").toBool());
// 异步加载模型 // 异步加载模型
if (!sttEngine_->isLoaded() || if (!sttEngine_->isLoaded() ||
currentModelPath_ != modelPath) { currentModelPath_ != modelPath) {