feat: 集成语音输入服务到主窗口和设置页面
主窗口新增 VoiceInputService 生命周期管理,支持通过配置页面 的开关动态启停 CapsLock 语音输入功能,窗口关闭时自动清理。 设置页面新增两个复选框: - 调试录音:保存每次识别的原始音频到临时文件夹 - 快捷语音:启用 CapsLock 长按语音输入 转写页面和 STT 测试页面同步配置中的调试音频开关到引擎。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
cda68e5376
commit
baec3482a7
@ -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);
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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_;
|
||||||
|
|||||||
@ -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());
|
||||||
|
|
||||||
|
|||||||
@ -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_;
|
||||||
|
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user