diff --git a/src/Client/AudioModule/IntelligenceUI.cpp b/src/Client/AudioModule/IntelligenceUI.cpp new file mode 100644 index 00000000..ad5edd92 --- /dev/null +++ b/src/Client/AudioModule/IntelligenceUI.cpp @@ -0,0 +1,694 @@ +#include "IntelligenceUI.h" +#include "ui_IntelligenceUI.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +IntelligenceUI::IntelligenceUI(QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::IntelligenceUI) + , sshProcess(nullptr) + , fileTransferProcess(nullptr) + , m_localAudioPath("") + , audioRecorder(nullptr) + , audioInput(nullptr) + , volumeTimer(nullptr) + , ttsProcess(nullptr) + , ttsOutputPath("") +{ + ui->setupUi(this); + + // 初始化并加载SSH设置 + updateSshSettings(); + + // 初始化录制功能 + setupAudioRecorder(); + + // 初始化TTS功能 + setupTTS(); + + // 连接信号槽 + connect(ui->playSelectedAudio, &QPushButton::clicked, this, &IntelligenceUI::on_playSelectedAudio_clicked); + connect(ui->killWSAudio, &QPushButton::clicked, this, &IntelligenceUI::on_killWSAudio_clicked); + connect(ui->refreshAudioList, &QPushButton::clicked, this, &IntelligenceUI::on_refreshAudioList_clicked); + connect(ui->sshSettingsGroup->findChild("saveSshSettings"), &QPushButton::clicked, this, &IntelligenceUI::on_saveSshSettings_clicked); + connect(ui->browseButton, &QPushButton::clicked, this, &IntelligenceUI::on_browseButton_clicked); + connect(ui->uploadAndPlayButton, &QPushButton::clicked, this, &IntelligenceUI::on_uploadAndPlayButton_clicked); + + // 连接录制相关信号槽 + connect(ui->recordButton, &QPushButton::clicked, this, &IntelligenceUI::on_recordButton_clicked); + connect(ui->stopRecordButton, &QPushButton::clicked, this, &IntelligenceUI::on_stopRecordButton_clicked); + connect(ui->playRecordedButton, &QPushButton::clicked, this, &IntelligenceUI::on_playRecordedButton_clicked); + + // 连接TTS相关信号槽 + connect(ui->generateTtsButton, &QPushButton::clicked, this, &IntelligenceUI::on_generateTtsButton_clicked); + connect(ui->playTtsButton, &QPushButton::clicked, this, &IntelligenceUI::on_playTtsButton_clicked); + + // 初始化状态 + updateStatus("情报传达系统已启动,准备就绪"); + ui->progressBar->setValue(0); +} + +IntelligenceUI::~IntelligenceUI() +{ + if (sshProcess && sshProcess->state() != QProcess::NotRunning) { + sshProcess->kill(); + sshProcess->waitForFinished(3000); + } + delete ui; +} + +void IntelligenceUI::executeSSHCommand(const QString &command, const QString &description) +{ + if (sshProcess && sshProcess->state() != QProcess::NotRunning) { + updateStatus("上一个命令仍在执行中,请稍候...", true); + return; + } + + if (!sshProcess) { + sshProcess = new QProcess(this); + connect(sshProcess, QOverload::of(&QProcess::finished), + this, &IntelligenceUI::onSshProcessFinished); + connect(sshProcess, &QProcess::errorOccurred, + this, &IntelligenceUI::onSshProcessError); + } + + currentCommand = description; + updateStatus(QString("正在执行: %1").arg(description)); + ui->progressBar->setValue(25); + + // 1. 从UI获取最新的目标板卡设置 + updateSshSettings(); + + // 2. 定义跳板机和目标板卡的连接信息 + QString jumpHost = "pi@192.168.12.1"; + QString jumpPassword = "123"; + QString targetHost = QString("%1@%2").arg(m_sshUser).arg(m_sshHost); + QString targetPassword = m_sshPassword; + + // 3. 简化的SSH命令 - 直接用单条命令链接 + QString escapedCommand = command; + escapedCommand.replace("'", "'\"'\"'"); // 转义单引号 + + QString fullCommand = QString( + "sshpass -p '%1' ssh -T -n -o StrictHostKeyChecking=no -o ConnectTimeout=10 %2 " + "\"sshpass -p '%3' ssh -T -n -o StrictHostKeyChecking=no -o ConnectTimeout=10 %4 '%5'\"" + ).arg(jumpPassword) + .arg(jumpHost) + .arg(targetPassword) + .arg(targetHost) + .arg(escapedCommand); + + qDebug() << "执行SSH命令:" << fullCommand; + sshProcess->start("bash", QStringList() << "-c" << fullCommand); +} + +void IntelligenceUI::on_playSelectedAudio_clicked() +{ + QString selectedAudio = ui->audioComboBox->currentText(); + playAudioFile(selectedAudio); +} + +void IntelligenceUI::on_killWSAudio_clicked() +{ + killWSAudioProcess(); +} + +void IntelligenceUI::on_refreshAudioList_clicked() +{ + refreshAudioFileList(); +} + +void IntelligenceUI::on_saveSshSettings_clicked() +{ + updateSshSettings(); + updateStatus("SSH连接设置已更新并保存。"); +} + +void IntelligenceUI::updateSshSettings() +{ + m_sshHost = ui->lineEditIp->text(); + m_sshUser = ui->lineEditUsername->text(); + m_sshPassword = ui->lineEditPassword->text(); + + ui->deviceLabel->setText(QString("当前目标: %1 (%2)").arg(m_sshHost).arg(m_sshUser)); +} + +void IntelligenceUI::killWSAudioProcess() +{ + QString command_template = "pids=$(ps -aux | grep wsaudio | grep -v grep | awk '{print $2}'); " + "if [ ! -z \"$pids\" ]; then " + "echo \"找到wsaudio进程: $pids\"; " + "echo '%1' | sudo -S kill -9 $pids; " + "echo \"已终止wsaudio进程\"; " + "else " + "echo \"未找到wsaudio进程\"; " + "fi"; + QString command = command_template.arg(m_sshPassword); + + executeSSHCommand(command, "解除wsaudio音频占用"); +} + +void IntelligenceUI::playAudioFile(const QString &audioFile) +{ + // 根据您的手动操作日志,文件路径为 audio_file/ + QString remote_audio_path = "audio_file/" + audioFile; + + // 这是最终在目标板卡上执行的脚本,使用换行符使其更清晰 + QString command_script_template = QString( + "pids=$(ps -aux | grep wsaudio | grep -v grep | awk '{print $2}')\n" + "if [ ! -z \"$pids\" ]; then\n" + " echo \"检测到wsaudio进程,正在终止...\"\n" + " echo '%1' | sudo -S kill -9 $pids\n" + " sleep 1\n" + "fi\n" + "echo \"开始播放音频: %2\"\n" + "aplay -D plughw:2,0 %2" + ); + QString command_script = command_script_template.arg(m_sshPassword).arg(remote_audio_path); + + executeSSHCommand(command_script, QString("播放音频文件: %1").arg(audioFile)); +} + +void IntelligenceUI::refreshAudioFileList() +{ + // 根据手动操作日志,文件位于 audio_file/ 目录 + QString command = "ls audio_file/*.wav 2>/dev/null || echo '未找到wav文件'"; + executeSSHCommand(command, "刷新音频文件列表"); +} + +void IntelligenceUI::onSshProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + ui->progressBar->setValue(100); + + if (exitStatus == QProcess::NormalExit && exitCode == 0) { + updateStatus(QString("%1 - 执行成功").arg(currentCommand)); + } else { + updateStatus(QString("%1 - 执行失败 (退出码: %2)").arg(currentCommand).arg(exitCode), true); + } + + // 读取命令输出 + if (sshProcess) { + QByteArray output = sshProcess->readAllStandardOutput(); + QByteArray error = sshProcess->readAllStandardError(); + + if (!output.isEmpty()) { + updateStatus(QString("输出: %1").arg(QString::fromUtf8(output).trimmed())); + } + + if (!error.isEmpty()) { + updateStatus(QString("错误: %1").arg(QString::fromUtf8(error).trimmed()), true); + } + } + + // 强制清理进程,确保下次能正常执行 + if (sshProcess) { + sshProcess->kill(); // 强制终止 + sshProcess->waitForFinished(1000); // 等待最多1秒 + sshProcess->deleteLater(); + sshProcess = nullptr; + } + + // 重置进度条 + QTimer::singleShot(2000, [this]() { + ui->progressBar->setValue(0); + }); +} + +void IntelligenceUI::onSshProcessError(QProcess::ProcessError error) +{ + ui->progressBar->setValue(0); + + QString errorString; + switch (error) { + case QProcess::FailedToStart: + errorString = "命令启动失败"; + break; + case QProcess::Crashed: + errorString = "命令执行崩溃"; + break; + case QProcess::Timedout: + errorString = "命令执行超时"; + break; + default: + errorString = "未知错误"; + break; + } + + updateStatus(QString("%1 - %2").arg(currentCommand).arg(errorString), true); +} + +void IntelligenceUI::updateStatus(const QString &message, bool isError) +{ + QString timestamp = QDateTime::currentDateTime().toString("hh:mm:ss"); + QString logMessage = QString("[%1] %2").arg(timestamp).arg(message); + + if (isError) { + ui->logTextEdit->setTextColor(QColor(255, 100, 100)); + } else { + ui->logTextEdit->setTextColor(QColor(100, 255, 100)); + } + + ui->logTextEdit->append(logMessage); + ui->logTextEdit->setTextColor(QColor(220, 230, 240)); // 重置颜色 + + // 自动滚动到底部 + ui->logTextEdit->moveCursor(QTextCursor::End); +} + +void IntelligenceUI::on_browseButton_clicked() +{ + QString filePath = QFileDialog::getOpenFileName(this, "选择音频文件", QDir::homePath(), "音频文件 (*.wav)"); + if (!filePath.isEmpty()) { + m_localAudioPath = filePath; + ui->filePathLineEdit->setText(filePath); + updateStatus(QString("已选择文件: %1").arg(QFileInfo(filePath).fileName())); + } +} + +void IntelligenceUI::on_uploadAndPlayButton_clicked() +{ + if (m_localAudioPath.isEmpty()) { + updateStatus("错误: 请先选择一个要上传的音频文件。", true); + return; + } + + if (fileTransferProcess && fileTransferProcess->state() != QProcess::NotRunning) { + updateStatus("上一个文件传输仍在进行中,请稍候...", true); + return; + } + + if (!fileTransferProcess) { + fileTransferProcess = new QProcess(this); + connect(fileTransferProcess, QOverload::of(&QProcess::finished), + this, &IntelligenceUI::onFileUploadFinished); + } + + QFileInfo fileInfo(m_localAudioPath); + QString fileName = fileInfo.fileName(); + QString remotePath = "audio_file/" + fileName; + + currentCommand = QString("上传并播放: %1").arg(fileName); + updateStatus(QString("正在上传文件: %1...").arg(fileName)); + ui->progressBar->setValue(10); + + // --- 使用cat和管道进行文件传输 --- + updateSshSettings(); + QString jumpHost = "pi@192.168.12.1"; + QString jumpPassword = "123"; + QString targetHost = QString("%1@%2").arg(m_sshUser).arg(m_sshHost); + QString targetPassword = m_sshPassword; + + const QString commandTemplate = + "cat %1 | sshpass -p '%2' ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 %3 " + "\"sshpass -p '%4' ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 %5 'cat > %6'\""; + + QString fullCommand = QString(commandTemplate) + .arg(m_localAudioPath) // %1: 本地文件路径 + .arg(jumpPassword) // %2: 跳板机密码 + .arg(jumpHost) // %3: 跳板机地址 + .arg(targetPassword) // %4: 目标板卡密码 + .arg(targetHost) // %5: 目标板卡地址 + .arg(remotePath); // %6: 远程文件路径 + + qDebug() << "执行文件上传命令:" << fullCommand; + fileTransferProcess->start("bash", QStringList() << "-c" << fullCommand); +} + +void IntelligenceUI::onFileUploadFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + QFileInfo fileInfo(m_localAudioPath); + QString fileName = fileInfo.fileName(); + + if (exitStatus == QProcess::NormalExit && exitCode == 0) { + updateStatus(QString("文件 '%1' 上传成功。").arg(fileName)); + ui->progressBar->setValue(50); + // 上传成功后,立即播放该文件 + playAudioFile(fileName); + } else { + updateStatus(QString("文件 '%1' 上传失败 (退出码: %2)").arg(fileName).arg(exitCode), true); + ui->progressBar->setValue(0); + } +} + +void IntelligenceUI::setupAudioRecorder() +{ + // 创建音频录制器 + audioRecorder = new QAudioRecorder(this); + + // 设置音频格式 - 匹配机器狗需要的格式 + QAudioEncoderSettings audioSettings; + audioSettings.setCodec("audio/pcm"); + audioSettings.setSampleRate(22050); // 22kHz采样率,匹配warning.wav + audioSettings.setBitRate(176400); // 8位单声道的比特率 + audioSettings.setChannelCount(1); // 单声道 + audioSettings.setQuality(QMultimedia::NormalQuality); + + audioRecorder->setAudioSettings(audioSettings); + audioRecorder->setContainerFormat("wav"); + + // 连接录制相关信号 + connect(audioRecorder, &QAudioRecorder::durationChanged, this, [this](qint64 duration) { + updateRecordingStatus(QString("录制中... %1秒").arg(duration / 1000)); + }); + + connect(audioRecorder, &QAudioRecorder::statusChanged, this, [this](QMediaRecorder::Status status) { + if (status == QMediaRecorder::UnavailableStatus || status == QMediaRecorder::UnloadedStatus) { + onRecordingFinished(); + } + }); + + connect(audioRecorder, QOverload::of(&QAudioRecorder::error), + this, &IntelligenceUI::onRecordingError); + + // 创建音量监测定时器 + volumeTimer = new QTimer(this); + connect(volumeTimer, &QTimer::timeout, this, &IntelligenceUI::updateAudioLevel); + + updateRecordingStatus("录制系统就绪"); +} + +void IntelligenceUI::on_recordButton_clicked() +{ + if (!audioRecorder) { + updateStatus("错误: 录制器未初始化", true); + return; + } + + // 创建录制文件路径 + QString recordingsDir = QDir::currentPath() + "/recordings"; + QDir().mkpath(recordingsDir); + + QString timestamp = QDateTime::currentDateTime().toString("yyyyMMdd_hhmmss"); + recordedFilePath = recordingsDir + QString("/recorded_%1.wav").arg(timestamp); + + updateStatus(QString("准备录制到: %1").arg(recordedFilePath)); + + audioRecorder->setOutputLocation(QUrl::fromLocalFile(recordedFilePath)); + + // 开始录制 + audioRecorder->record(); + + enableRecordingControls(true); + updateRecordingStatus("正在录制..."); + updateStatus("开始录制语音"); + + // 启动音量监测 + volumeTimer->start(100); // 每100ms更新一次音量显示 +} + +void IntelligenceUI::on_stopRecordButton_clicked() +{ + updateStatus("尝试停止录制..."); + + if (!audioRecorder) { + updateStatus("错误: 录制器为空", true); + return; + } + + QMediaRecorder::State currentState = audioRecorder->state(); + updateStatus(QString("当前录制状态: %1").arg(currentState)); + + // 无论当前状态如何,都尝试停止 + audioRecorder->stop(); + volumeTimer->stop(); + ui->volumeMeter->setValue(0); + + enableRecordingControls(false); + updateRecordingStatus("正在停止录制..."); + + // 延迟检查文件是否生成 + QTimer::singleShot(1000, this, [this]() { + if (QFile::exists(recordedFilePath)) { + QFileInfo fileInfo(recordedFilePath); + updateRecordingStatus(QString("录制完成 - %1 (%2 KB)").arg(fileInfo.fileName()).arg(fileInfo.size() / 1024)); + updateStatus(QString("录制完成,文件保存至: %1").arg(recordedFilePath)); + + // 启用试听按钮 + ui->playRecordedButton->setEnabled(true); + + // 自动设置为要上传的文件 + m_localAudioPath = recordedFilePath; + ui->filePathLineEdit->setText(recordedFilePath); + } else { + updateRecordingStatus("录制可能失败,未找到文件"); + updateStatus(QString("录制文件未找到: %1").arg(recordedFilePath), true); + } + }); +} + +void IntelligenceUI::on_playRecordedButton_clicked() +{ + if (recordedFilePath.isEmpty() || !QFile::exists(recordedFilePath)) { + updateStatus("错误: 没有找到录制的音频文件", true); + return; + } + + // 使用系统默认播放器试听录制的音频 + QProcess *playProcess = new QProcess(this); + connect(playProcess, QOverload::of(&QProcess::finished), + playProcess, &QProcess::deleteLater); + + // 尝试使用不同的音频播放命令 + #ifdef Q_OS_LINUX + playProcess->start("aplay", QStringList() << recordedFilePath); + #elif defined(Q_OS_WIN) + playProcess->start("powershell", QStringList() << "-c" << QString("(New-Object Media.SoundPlayer '%1').PlaySync()").arg(recordedFilePath)); + #elif defined(Q_OS_MAC) + playProcess->start("afplay", QStringList() << recordedFilePath); + #endif + + updateStatus("正在本地试听录制的音频..."); +} + +void IntelligenceUI::onRecordingFinished() +{ + enableRecordingControls(false); + volumeTimer->stop(); + ui->volumeMeter->setValue(0); + + if (QFile::exists(recordedFilePath)) { + QFileInfo fileInfo(recordedFilePath); + updateRecordingStatus(QString("录制完成 - %1 (%2 KB)").arg(fileInfo.fileName()).arg(fileInfo.size() / 1024)); + updateStatus(QString("录制完成: %1").arg(fileInfo.fileName())); + } else { + updateRecordingStatus("录制失败"); + updateStatus("录制失败: 文件未生成", true); + } +} + +void IntelligenceUI::onRecordingError(QMediaRecorder::Error error) +{ + QString errorString; + switch (error) { + case QMediaRecorder::NoError: + return; + case QMediaRecorder::ResourceError: + errorString = "资源错误"; + break; + case QMediaRecorder::FormatError: + errorString = "格式错误"; + break; + case QMediaRecorder::OutOfSpaceError: + errorString = "磁盘空间不足"; + break; + default: + errorString = "未知错误"; + break; + } + + updateRecordingStatus("录制错误: " + errorString); + updateStatus("录制错误: " + errorString, true); + + enableRecordingControls(false); + volumeTimer->stop(); + ui->volumeMeter->setValue(0); +} + +void IntelligenceUI::updateAudioLevel() +{ + if (!audioRecorder || audioRecorder->state() != QMediaRecorder::RecordingState) { + return; + } + + // 简化音量显示 - 模拟录制时的音量指示 + static int volumeCounter = 0; + volumeCounter = (volumeCounter + 1) % 100; + + // 模拟音量波动(在实际项目中可以通过其他方式获取真实音量) + int volumeLevel = 50 + (QRandomGenerator::global()->bounded(30)); // 50-80之间的随机值 + + ui->volumeMeter->setValue(volumeLevel); +} + +void IntelligenceUI::updateRecordingStatus(const QString &status) +{ + ui->recordStatusLabel->setText("录制状态: " + status); +} + +void IntelligenceUI::enableRecordingControls(bool recording) +{ + ui->recordButton->setEnabled(!recording); + ui->stopRecordButton->setEnabled(recording); + + if (recording) { + ui->recordButton->setText("🎤 录制中..."); + ui->recordButton->setStyleSheet( + "QPushButton { background-color: rgb(165, 85, 45); }" + ); + } else { + ui->recordButton->setText("🎤 开始录制"); + ui->recordButton->setStyleSheet( + "QPushButton { background-color: rgb(45, 125, 65); }" + "QPushButton:hover { background-color: rgb(65, 145, 85); }" + "QPushButton:pressed { background-color: rgb(55, 135, 75); }" + ); + } +} + +// ========== TTS相关功能实现 ========== + +void IntelligenceUI::setupTTS() +{ + ttsProcess = nullptr; + updateTtsStatus("TTS系统就绪"); +} + +void IntelligenceUI::on_generateTtsButton_clicked() +{ + QString text = ui->ttsTextEdit->toPlainText().trimmed(); + if (text.isEmpty()) { + updateStatus("错误: 请输入要转换的文字内容", true); + return; + } + + if (ttsProcess && ttsProcess->state() != QProcess::NotRunning) { + updateStatus("TTS转换正在进行中,请稍候...", true); + return; + } + + // 创建TTS输出目录 + QString ttsDir = QDir::currentPath() + "/tts_output"; + QDir().mkpath(ttsDir); + + // 生成输出文件名 + QString timestamp = QDateTime::currentDateTime().toString("yyyyMMdd_hhmmss"); + ttsOutputPath = ttsDir + QString("/tts_%1.wav").arg(timestamp); + + // 获取选择的语音类型 + QString voiceType = ui->voiceComboBox->currentText(); + QString espeakVoice; + + if (voiceType == "标准女声") { + espeakVoice = "cmn"; // 中文普通话 + } else if (voiceType == "标准男声") { + espeakVoice = "cmn"; // 中文普通话 + } else if (voiceType == "儿童声") { + espeakVoice = "cmn"; // 中文普通话 + } else { + espeakVoice = "cmn"; // 默认中文普通话 + } + + updateTtsStatus("正在生成语音..."); + updateStatus(QString("正在将文字转换为语音: %1").arg(text.left(50) + (text.length() > 50 ? "..." : ""))); + + // 创建TTS进程 + if (!ttsProcess) { + ttsProcess = new QProcess(this); + connect(ttsProcess, QOverload::of(&QProcess::finished), + this, &IntelligenceUI::onTtsGenerationFinished); + } + + // 构建espeak命令 + // -v: 语音类型, -s: 语速, -a: 音量, -w: 输出到WAV文件 + QStringList arguments; + arguments << "-v" << espeakVoice + << "-s" << "150" // 语速 150 wpm + << "-a" << "100" // 音量 100 + << "-w" << ttsOutputPath // 输出文件 + << text; // 要转换的文字 + + qDebug() << "TTS命令:" << "espeak-ng" << arguments.join(" "); + ttsProcess->start("espeak-ng", arguments); +} + +void IntelligenceUI::on_playTtsButton_clicked() +{ + if (ttsOutputPath.isEmpty() || !QFile::exists(ttsOutputPath)) { + updateStatus("错误: 没有找到TTS生成的音频文件", true); + return; + } + + // 使用系统默认播放器试听TTS音频 + QProcess *playProcess = new QProcess(this); + connect(playProcess, QOverload::of(&QProcess::finished), + playProcess, &QProcess::deleteLater); + + // 尝试使用不同的音频播放命令 + #ifdef Q_OS_LINUX + playProcess->start("aplay", QStringList() << ttsOutputPath); + #elif defined(Q_OS_WIN) + playProcess->start("powershell", QStringList() << "-c" << QString("(New-Object Media.SoundPlayer '%1').PlaySync()").arg(ttsOutputPath)); + #elif defined(Q_OS_MAC) + playProcess->start("afplay", QStringList() << ttsOutputPath); + #endif + + updateStatus("正在本地试听TTS生成的音频..."); +} + +void IntelligenceUI::onTtsGenerationFinished(int exitCode, QProcess::ExitStatus exitStatus) +{ + if (exitStatus == QProcess::NormalExit && exitCode == 0) { + if (QFile::exists(ttsOutputPath)) { + QFileInfo fileInfo(ttsOutputPath); + updateTtsStatus(QString("TTS完成 - %1 (%2 KB)").arg(fileInfo.fileName()).arg(fileInfo.size() / 1024)); + updateStatus(QString("TTS生成成功: %1").arg(fileInfo.fileName())); + + // 启用试听按钮 + ui->playTtsButton->setEnabled(true); + + // 自动设置为要上传的文件 + m_localAudioPath = ttsOutputPath; + ui->filePathLineEdit->setText(ttsOutputPath); + + updateStatus("TTS音频已自动设置为上传文件,可直接点击'上传并播放'"); + } else { + updateTtsStatus("TTS失败: 文件未生成"); + updateStatus("TTS生成失败: 文件未生成", true); + } + } else { + updateTtsStatus("TTS生成失败"); + updateStatus(QString("TTS生成失败 (退出码: %1)").arg(exitCode), true); + + // 读取错误信息 + if (ttsProcess) { + QByteArray error = ttsProcess->readAllStandardError(); + if (!error.isEmpty()) { + updateStatus(QString("TTS错误: %1").arg(QString::fromUtf8(error).trimmed()), true); + } + } + } + + // 清理进程 + if (ttsProcess) { + ttsProcess->deleteLater(); + ttsProcess = nullptr; + } +} + +void IntelligenceUI::updateTtsStatus(const QString &status) +{ + ui->ttsStatusLabel->setText("TTS状态: " + status); +} \ No newline at end of file diff --git a/src/Client/AudioModule/IntelligenceUI.cppZone.Identifier b/src/Client/AudioModule/IntelligenceUI.cppZone.Identifier new file mode 100644 index 00000000..a45e1ac4 --- /dev/null +++ b/src/Client/AudioModule/IntelligenceUI.cppZone.Identifier @@ -0,0 +1,2 @@ +[ZoneTransfer] +ZoneId=3 diff --git a/src/Client/AudioModule/IntelligenceUI.h b/src/Client/AudioModule/IntelligenceUI.h new file mode 100644 index 00000000..4748619c --- /dev/null +++ b/src/Client/AudioModule/IntelligenceUI.h @@ -0,0 +1,121 @@ +#ifndef INTELLIGENCEUI_H +#define INTELLIGENCEUI_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE +namespace Ui { +class IntelligenceUI; +} +QT_END_NAMESPACE + +class IntelligenceUI : public QMainWindow +{ + Q_OBJECT + +public: + IntelligenceUI(QWidget *parent = nullptr); + ~IntelligenceUI(); + +private slots: + // 播放音频按钮 + void on_playSelectedAudio_clicked(); + + // 杀死wsaudio进程按钮 + void on_killWSAudio_clicked(); + + // 刷新音频文件列表 + void on_refreshAudioList_clicked(); + + // 保存SSH连接设置 + void on_saveSshSettings_clicked(); + + // 上传并播放 + void on_browseButton_clicked(); + void on_uploadAndPlayButton_clicked(); + void onFileUploadFinished(int exitCode, QProcess::ExitStatus exitStatus); + + // SSH进程处理 + void onSshProcessFinished(int exitCode, QProcess::ExitStatus exitStatus); + void onSshProcessError(QProcess::ProcessError error); + + // 新增录制功能相关槽函数 + void on_recordButton_clicked(); + void on_stopRecordButton_clicked(); + void on_playRecordedButton_clicked(); + + // 录制相关处理 + void onRecordingFinished(); + void onRecordingError(QMediaRecorder::Error error); + void updateAudioLevel(); + + // 新增TTS功能相关槽函数 + void on_generateTtsButton_clicked(); + void on_playTtsButton_clicked(); + void onTtsGenerationFinished(int exitCode, QProcess::ExitStatus exitStatus); + +private: + Ui::IntelligenceUI *ui; + QProcess *sshProcess; + QProcess *fileTransferProcess; + QString currentCommand; + + // SSH连接信息 + QString m_sshHost; + QString m_sshUser; + QString m_sshPassword; + + // 本地文件路径 + QString m_localAudioPath; + + // 录制相关 + QAudioRecorder *audioRecorder; + QAudioInput *audioInput; + QTimer *volumeTimer; + QString recordedFilePath; + + // TTS相关 + QProcess *ttsProcess; + QString ttsOutputPath; + + // 核心方法 + void executeSSHCommand(const QString &command, const QString &description); + //qxq: + void killWSAudioProcess(); + void playAudioFile(const QString &audioFile); + void refreshAudioFileList(); + void updateSshSettings(); + + // UI设置 + void setupUI(); + void updateStatus(const QString &message, bool isError = false); + + // 录制相关私有方法 + void setupAudioRecorder(); + void updateRecordingStatus(const QString &status); + void enableRecordingControls(bool recording); + + // TTS相关私有方法 + void setupTTS(); + void updateTtsStatus(const QString &status); +}; + +#endif // INTELLIGENCEUI_H \ No newline at end of file diff --git a/src/Client/AudioModule/IntelligenceUI.hZone.Identifier b/src/Client/AudioModule/IntelligenceUI.hZone.Identifier new file mode 100644 index 00000000..a45e1ac4 --- /dev/null +++ b/src/Client/AudioModule/IntelligenceUI.hZone.Identifier @@ -0,0 +1,2 @@ +[ZoneTransfer] +ZoneId=3 diff --git a/src/Client/AudioModule/IntelligenceUI.ui b/src/Client/AudioModule/IntelligenceUI.ui new file mode 100644 index 00000000..e2a686f1 --- /dev/null +++ b/src/Client/AudioModule/IntelligenceUI.ui @@ -0,0 +1,634 @@ + + + IntelligenceUI + + + + 0 + 0 + 900 + 800 + + + + 情报传达系统 - UnitreeGo1 + + + QMainWindow { + background-color: rgb(24, 33, 45); +} + +QPushButton { + background-color: rgb(30, 44, 62); + color: rgb(220, 230, 240); + border: 2px solid rgba(82, 194, 242, 0.5); + border-radius: 8px; + padding: 12px 20px; + font-size: 14px; + font-weight: bold; + min-height: 35px; +} + +QPushButton:hover { + background-color: rgb(50, 70, 95); + border: 2px solid rgba(82, 194, 242, 0.8); +} + +QPushButton:pressed { + background-color: rgb(40, 60, 85); + border: 2px solid rgba(82, 194, 242, 1.0); +} + +QLabel { + color: rgb(220, 230, 240); + font-size: 14px; +} + +QComboBox { + background-color: rgb(30, 44, 62); + color: rgb(220, 230, 240); + border: 2px solid rgba(82, 194, 242, 0.5); + border-radius: 5px; + padding: 8px; + font-size: 14px; +} + +QTextEdit { + background-color: rgb(15, 22, 32); + color: rgb(220, 230, 240); + border: 2px solid rgba(82, 194, 242, 0.3); + border-radius: 5px; + font-family: "Courier New", monospace; + font-size: 12px; +} + +QProgressBar { + border: 2px solid rgba(82, 194, 242, 0.5); + border-radius: 5px; + text-align: center; + background-color: rgb(30, 44, 62); + color: rgb(220, 230, 240); +} + +QProgressBar::chunk { + background-color: rgba(82, 194, 242, 0.8); + border-radius: 3px; +} + + + + + 20 + + + 30 + + + 20 + + + 30 + + + 20 + + + + + 🔊 情报传达系统 + + + Qt::AlignCenter + + + QLabel { + color: rgb(82, 194, 242); + font-size: 32px; + font-weight: bold; + padding: 20px; + background: qlineargradient(x1:0, y1:0, x2:1, y2:1, + stop:0 rgba(82, 194, 242, 0.1), + stop:1 rgba(45, 120, 180, 0.1)); + border: 2px solid rgba(82, 194, 242, 0.3); + border-radius: 10px; +} + + + + + + + SSH 连接设置 + + + QGroupBox { + font-size: 16px; + font-weight: bold; + color: rgb(220, 230, 240); + border: 1px solid rgba(82, 194, 242, 0.4); + border-radius: 8px; + margin-top: 10px; +} + +QGroupBox::title { + subcontrol-origin: margin; + subcontrol-position: top center; + padding: 0 10px; +} + + + + + + + + 目标 IP: + + + + + + + 192.168.123.13 + + + + + + + 用户名: + + + + + + + unitree + + + + + + + 密码: + + + + + + + 123 + + + QLineEdit::Password + + + + + + + + + 保存并应用设置 + + + + + + + + + + 当前目标: 192.168.123.13 (UnitreeGo1) + + + Qt::AlignCenter + + + color: rgb(160, 170, 180); font-size: 16px; + + + + + + + + + 选择音频文件: + + + font-size: 16px; font-weight: bold; + + + + + + + + 200 + 0 + + + + + warning.wav + + + + + alert.wav + + + + + emergency.wav + + + + + notification.wav + + + + + + + + 刷新列表 + + + + + + + + + + + 🔊 播放选定音频 + + + QPushButton { + background-color: rgb(45, 125, 65); + font-size: 16px; + font-weight: bold; +} + +QPushButton:hover { + background-color: rgb(65, 145, 85); +} + +QPushButton:pressed { + background-color: rgb(55, 135, 75); +} + + + + + + + 🔧 解除音频占用 + + + QPushButton { + background-color: rgb(165, 85, 45); + font-size: 16px; + font-weight: bold; +} + +QPushButton:hover { + background-color: rgb(185, 105, 65); +} + +QPushButton:pressed { + background-color: rgb(175, 95, 55); +} + + + + + + + + + 自定义音频制作 + + + + + + 🎤 录制语音: + + + font-size: 14px; font-weight: bold; color: rgb(82, 194, 242); + + + + + + + + + 🎤 开始录制 + + + QPushButton { + background-color: rgb(45, 125, 65); + font-size: 14px; + font-weight: bold; +} + +QPushButton:hover { + background-color: rgb(65, 145, 85); +} + +QPushButton:pressed { + background-color: rgb(55, 135, 75); +} + + + + + + + ⏹ 停止录制 + + + false + + + QPushButton { + background-color: rgb(165, 85, 45); + font-size: 14px; + font-weight: bold; +} + +QPushButton:hover { + background-color: rgb(185, 105, 65); +} + +QPushButton:pressed { + background-color: rgb(175, 95, 55); +} + + + + + + + ▶ 试听录音 + + + false + + + + + + + + + 录制状态: 就绪 + + + color: rgb(160, 170, 180); font-size: 12px; + + + + + + + 100 + + + 0 + + + false + + + QProgressBar { + border: 1px solid rgba(82, 194, 242, 0.5); + border-radius: 3px; + background-color: rgb(30, 44, 62); + height: 10px; +} + +QProgressBar::chunk { + background-color: rgba(45, 200, 45, 0.8); + border-radius: 2px; +} + + + + + + + 或者 + + + Qt::AlignCenter + + + color: rgb(160, 170, 180); font-size: 12px; margin: 10px; + + + + + + + 🗣 文字转语音: + + + font-size: 14px; font-weight: bold; color: rgb(82, 194, 242); + + + + + + + + 0 + 80 + + + + 输入要转换为语音的文字内容... + + + background-color: rgb(30, 44, 62); border: 1px solid rgba(82, 194, 242, 0.3); border-radius: 3px; padding: 5px; + + + + + + + + + background-color: rgb(30, 44, 62); border: 1px solid rgba(82, 194, 242, 0.3); border-radius: 3px; padding: 3px; + + + + 标准女声 + + + + + 标准男声 + + + + + 儿童声 + + + + + + + + 🎵 生成语音 + + + QPushButton { + background-color: rgb(85, 125, 165); + font-size: 14px; + font-weight: bold; +} + +QPushButton:hover { + background-color: rgb(105, 145, 185); +} + +QPushButton:pressed { + background-color: rgb(95, 135, 175); +} + + + + + + + ▶ 试听TTS + + + false + + + + + + + + + TTS状态: 就绪 + + + color: rgb(160, 170, 180); font-size: 12px; + + + + + + + 或者 + + + Qt::AlignCenter + + + color: rgb(160, 170, 180); font-size: 12px; margin: 10px; + + + + + + + 📁 上传文件: + + + font-size: 14px; font-weight: bold; color: rgb(82, 194, 242); + + + + + + + + + true + + + 请选择一个.wav音频文件... + + + + + + + 浏览... + + + + + + + + + ⬆️ 上传并播放 + + + + + + + + + + 0 + + + true + + + + + + + 执行日志: + + + font-size: 16px; font-weight: bold; + + + + + + + + 0 + 200 + + + + true + + + + + + + + + 0 + 0 + 900 + 22 + + + + + + + + \ No newline at end of file diff --git a/src/Client/AudioModule/IntelligenceUI.uiZone.Identifier b/src/Client/AudioModule/IntelligenceUI.uiZone.Identifier new file mode 100644 index 00000000..a45e1ac4 --- /dev/null +++ b/src/Client/AudioModule/IntelligenceUI.uiZone.Identifier @@ -0,0 +1,2 @@ +[ZoneTransfer] +ZoneId=3 diff --git a/src/Client/BattlefieldExplorationSystem b/src/Client/BattlefieldExplorationSystem index a2e4e1e8..91e3f101 100755 Binary files a/src/Client/BattlefieldExplorationSystem and b/src/Client/BattlefieldExplorationSystem differ diff --git a/src/Client/BattlefieldExplorationSystem.pro b/src/Client/BattlefieldExplorationSystem.pro index ec0debba..03c7df6a 100644 --- a/src/Client/BattlefieldExplorationSystem.pro +++ b/src/Client/BattlefieldExplorationSystem.pro @@ -30,7 +30,8 @@ SOURCES += \ src/ui/components/DeviceListPanel.cpp \ src/ui/components/SystemLogPanel.cpp \ src/ui/components/RightFunctionPanel.cpp \ - src/utils/SystemLogger.cpp + src/utils/SystemLogger.cpp \ + AudioModule/IntelligenceUI.cpp # Header files - 按模块组织 HEADERS += \ @@ -42,12 +43,14 @@ HEADERS += \ include/ui/components/DeviceListPanel.h \ include/ui/components/SystemLogPanel.h \ include/ui/components/RightFunctionPanel.h \ - include/utils/SystemLogger.h + include/utils/SystemLogger.h \ + AudioModule/IntelligenceUI.h # UI forms - 按模块组织 FORMS += \ forms/main/MainWindow.ui \ - forms/dialogs/DeviceDialog.ui + forms/dialogs/DeviceDialog.ui \ + AudioModule/IntelligenceUI.ui # Default rules for deployment. qnx: target.path = /tmp/$${TARGET}/bin diff --git a/src/Client/include/ui/components/RightFunctionPanel.h b/src/Client/include/ui/components/RightFunctionPanel.h index 255dc286..884f24b9 100644 --- a/src/Client/include/ui/components/RightFunctionPanel.h +++ b/src/Client/include/ui/components/RightFunctionPanel.h @@ -112,6 +112,7 @@ protected: private: QString m_deviceName; ///< 设备名称 + QLabel *m_iconLabel; ///< 图标标签 QLabel *m_nameLabel; ///< 名称标签 QLabel *m_statusLabel; ///< 状态标签 bool m_isActive = false; ///< 是否选中状态 @@ -178,26 +179,9 @@ signals: // 情报传输模块信号 /** - * @brief 开始语音通话信号 + * @brief 打开情报传达界面信号 */ - void startVoiceCall(); - - /** - * @brief 结束语音通话信号 - */ - void endVoiceCall(); - - /** - * @brief 通话静音切换信号 - * @param muted 是否静音 - */ - void muteCall(bool muted); - - /** - * @brief 设置通话音量信号 - * @param volume 音量值(0-100) - */ - void setCallVolume(int volume); + void openIntelligenceUI(); // 敌情统计模块信号 /** @@ -259,9 +243,9 @@ private slots: void onPersonRecognitionToggle(); /** - * @brief 语音通话开关槽函数 + * @brief 打开情报传达界面槽函数 */ - void onVoiceCallToggle(); + void onOpenIntelligenceUI(); /** * @brief 刷新统计槽函数 @@ -314,11 +298,7 @@ private: // 情报传输模块 ModuleCard *m_intelligenceCard; ///< 情报模块卡片 - QPushButton *m_voiceCallBtn; ///< 语音通话按钮 - QPushButton *m_muteBtn; ///< 静音按钮 - QSlider *m_volumeSlider; ///< 音量滑块 - QLabel *m_callStatusLabel; ///< 通话状态标签 - bool m_isInCall = false; ///< 是否在通话中 + QPushButton *m_voiceCallBtn; ///< 音频控制按钮 // 敌情统计模块 ModuleCard *m_statsCard; ///< 统计模块卡片 diff --git a/src/Client/include/ui/main/MainWindow.h b/src/Client/include/ui/main/MainWindow.h index 8057800a..a933dacc 100644 --- a/src/Client/include/ui/main/MainWindow.h +++ b/src/Client/include/ui/main/MainWindow.h @@ -39,7 +39,7 @@ #include // 自定义模块头文件 -// #include "AudioModule/IntelligenceUI.h" // 暂时注释掉,待实现 +#include "AudioModule/IntelligenceUI.h" #include "ui/components/DeviceListPanel.h" #include "ui/components/SystemLogPanel.h" #include "ui/components/RightFunctionPanel.h" @@ -264,15 +264,7 @@ private slots: */ void onStopPersonRecognition(); - /** - * @brief 开始语音通话槽函数 - */ - void onStartVoiceCall(); - - /** - * @brief 结束语音通话槽函数 - */ - void onEndVoiceCall(); + /** * @brief 刷新敌情统计槽函数 @@ -337,7 +329,7 @@ private: private: Ui::MainWindow *m_ui; ///< UI界面指针 - // IntelligenceUI *m_intelligenceUI; ///< 情报传达界面指针(暂时注释掉) + IntelligenceUI *m_intelligenceUI; ///< 情报传达界面指针 DeviceListPanel *m_deviceListPanel; ///< 设备列表面板组件 SystemLogPanel *m_systemLogPanel; ///< 系统日志面板组件 RightFunctionPanel *m_rightFunctionPanel; ///< 右侧功能面板组件 diff --git a/src/Client/src/ui/components/RightFunctionPanel.cpp b/src/Client/src/ui/components/RightFunctionPanel.cpp index 0507c868..a9a50295 100644 --- a/src/Client/src/ui/components/RightFunctionPanel.cpp +++ b/src/Client/src/ui/components/RightFunctionPanel.cpp @@ -92,13 +92,13 @@ RightDeviceCard::RightDeviceCard(const QString &name, const QString &iconPath, Q // 设置图标,使用更大更清晰的图标 if (name.contains("机器狗") || name.contains("robot") || name.contains("dog")) { m_iconLabel->setText("🐕"); - m_iconLabel->setStyleSheet("font-size: 32px;"); + m_iconLabel->setStyleSheet("font-size: 36px; margin-bottom: 8px;"); } else if (name.contains("无人机") || name.contains("drone") || name.contains("uav")) { m_iconLabel->setText("🚁"); - m_iconLabel->setStyleSheet("font-size: 32px;"); + m_iconLabel->setStyleSheet("font-size: 36px; margin-bottom: 8px;"); } else { m_iconLabel->setText("📡"); - m_iconLabel->setStyleSheet("font-size: 32px;"); + m_iconLabel->setStyleSheet("font-size: 36px; margin-bottom: 8px;"); } m_nameLabel = new QLabel(name); @@ -113,6 +113,7 @@ RightDeviceCard::RightDeviceCard(const QString &name, const QString &iconPath, Q // 调整布局,确保文字有足够显示空间 layout->addStretch(1); // 较小的上方弹性空间 + layout->addWidget(m_iconLabel); layout->addWidget(m_nameLabel); layout->addWidget(m_statusLabel); layout->addStretch(1); // 较小的下方弹性空间 @@ -187,12 +188,12 @@ RightFunctionPanel::RightFunctionPanel(QWidget *parent) void RightFunctionPanel::setupUI() { - setFixedWidth(360); // 进一步增加宽度 + setFixedWidth(380); // 进一步增加宽度,给更多空间 setObjectName("rightFunctionPanel"); m_mainLayout = new QVBoxLayout(this); - m_mainLayout->setSpacing(28); // 增加模块间距 - m_mainLayout->setContentsMargins(24, 24, 24, 24); // 增加内边距 + m_mainLayout->setSpacing(32); // 进一步增加模块间距 + m_mainLayout->setContentsMargins(26, 26, 26, 26); // 增加内边距 // 面板标题 - 军事风格 QLabel *titleLabel = new QLabel("⚔️ 作战控制面板"); @@ -217,8 +218,8 @@ void RightFunctionPanel::setupBattlefieldExplorationModule() QWidget *deviceSelectorWidget = new QWidget(); deviceSelectorWidget->setObjectName("device-selector"); QHBoxLayout *deviceLayout = new QHBoxLayout(deviceSelectorWidget); - deviceLayout->setSpacing(12); - deviceLayout->setContentsMargins(8, 8, 8, 8); + deviceLayout->setSpacing(16); // 增加设备卡片间距 + deviceLayout->setContentsMargins(12, 12, 12, 12); // 增加容器内边距 m_robotDogCard = new RightDeviceCard("🐕 机器狗-01", "", this); m_droneCard = new RightDeviceCard("🚁 侦察机-01", "", this); @@ -234,7 +235,7 @@ void RightFunctionPanel::setupBattlefieldExplorationModule() m_mappingBtn = new QPushButton("🗺️ 开始建图"); m_mappingBtn->setObjectName("FunctionBtn"); m_mappingBtn->setProperty("class", "primary-large"); - m_mappingBtn->setMinimumHeight(52); // 增加主要按钮高度 + m_mappingBtn->setMinimumHeight(55); // 与样式表中的设置保持一致 m_mappingBtn->setEnabled(false); connect(m_mappingBtn, &QPushButton::clicked, this, &RightFunctionPanel::onMappingToggle); m_explorationCard->addContent(m_mappingBtn); @@ -242,8 +243,8 @@ void RightFunctionPanel::setupBattlefieldExplorationModule() // 次要功能按钮 - 三列布局 QWidget *secondaryWidget = new QWidget(); QHBoxLayout *secondaryLayout = new QHBoxLayout(secondaryWidget); - secondaryLayout->setSpacing(8); - secondaryLayout->setContentsMargins(0, 8, 0, 0); + secondaryLayout->setSpacing(12); // 增加按钮间距 + secondaryLayout->setContentsMargins(0, 12, 0, 0); // 增加上边距 m_navigationBtn = new QPushButton("🧭 导航"); m_photoBtn = new QPushButton("📸 传输"); @@ -254,7 +255,7 @@ void RightFunctionPanel::setupBattlefieldExplorationModule() for(auto btn : secondaryBtns) { btn->setObjectName("FunctionBtn"); btn->setProperty("class", "secondary-small"); - btn->setMinimumHeight(38); + btn->setMinimumHeight(42); // 与样式表中的设置保持一致 btn->setEnabled(false); } @@ -276,66 +277,48 @@ void RightFunctionPanel::setupIntelligenceModule() m_intelligenceCard->setObjectName("ModuleCard"); m_intelligenceCard->setProperty("data-module", "intelligence"); - // 通话控制按钮 - 改进布局 - QWidget *callWidget = new QWidget(); - QHBoxLayout *callLayout = new QHBoxLayout(callWidget); - callLayout->setSpacing(12); - callLayout->setContentsMargins(0, 0, 0, 0); - - m_voiceCallBtn = new QPushButton("📞 开始通话"); - m_muteBtn = new QPushButton("🔇 静音"); + // 情报传达说明 + QLabel *descLabel = new QLabel("🎯 远程音频控制系统"); + descLabel->setObjectName("intelligence-description"); + descLabel->setAlignment(Qt::AlignCenter); + descLabel->setStyleSheet("color: #00a8ff; font-size: 14px; font-weight: bold; padding: 8px;"); + m_intelligenceCard->addContent(descLabel); + // 主功能按钮 - 打开音频控制界面 + m_voiceCallBtn = new QPushButton("🔊 打开音频控制界面"); m_voiceCallBtn->setObjectName("FunctionBtn"); - m_muteBtn->setObjectName("FunctionBtn"); - m_voiceCallBtn->setProperty("class", "primary-medium"); - m_muteBtn->setProperty("class", "secondary-medium"); - m_voiceCallBtn->setMinimumHeight(48); - m_muteBtn->setMinimumHeight(48); - m_muteBtn->setEnabled(false); - - callLayout->addWidget(m_voiceCallBtn, 2); // 通话按钮占更多空间 - callLayout->addWidget(m_muteBtn, 1); - - connect(m_voiceCallBtn, &QPushButton::clicked, this, &RightFunctionPanel::onVoiceCallToggle); - m_intelligenceCard->addContent(callWidget); - - // 音量控制 - 全新设计 - QWidget *volumeWidget = new QWidget(); - QVBoxLayout *volumeLayout = new QVBoxLayout(volumeWidget); - volumeLayout->setSpacing(12); - volumeLayout->setContentsMargins(0, 16, 0, 0); - - QHBoxLayout *volumeLabelLayout = new QHBoxLayout(); - QLabel *volumeLabel = new QLabel("🔊 音量控制"); - volumeLabel->setObjectName("volume-label"); - - QLabel *volumePercent = new QLabel("70%"); - volumePercent->setObjectName("volume-percent"); - volumePercent->setAlignment(Qt::AlignRight); - - volumeLabelLayout->addWidget(volumeLabel); - volumeLabelLayout->addWidget(volumePercent); - - m_volumeSlider = new QSlider(Qt::Horizontal); - m_volumeSlider->setRange(0, 100); - m_volumeSlider->setValue(70); - m_volumeSlider->setObjectName("volume-slider"); - - // 连接音量滑块信号 - connect(m_volumeSlider, &QSlider::valueChanged, [volumePercent](int value) { - volumePercent->setText(QString("%1%").arg(value)); - }); - - volumeLayout->addLayout(volumeLabelLayout); - volumeLayout->addWidget(m_volumeSlider); - m_intelligenceCard->addContent(volumeWidget); - - // 连接状态指示器 - 改进设计 - m_callStatusLabel = new QLabel("📋 未连接"); - m_callStatusLabel->setObjectName("call-status"); - m_callStatusLabel->setAlignment(Qt::AlignCenter); - m_callStatusLabel->setMinimumHeight(30); // 设置最小高度 - m_intelligenceCard->addContent(m_callStatusLabel); + m_voiceCallBtn->setProperty("class", "primary-large"); + m_voiceCallBtn->setMinimumHeight(55); + m_voiceCallBtn->setStyleSheet( + "QPushButton {" + " background: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0," + " stop:0 #00a8ff, stop:1 #0078d4);" + " color: white;" + " font-size: 16px;" + " font-weight: bold;" + " border: 2px solid #00a8ff;" + " border-radius: 8px;" + " padding: 12px;" + "}" + "QPushButton:hover {" + " background: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0," + " stop:0 #0078d4, stop:1 #005a9f);" + "}" + "QPushButton:pressed {" + " background: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:0," + " stop:0 #005a9f, stop:1 #003d6b);" + "}" + ); + + connect(m_voiceCallBtn, &QPushButton::clicked, this, &RightFunctionPanel::onOpenIntelligenceUI); + m_intelligenceCard->addContent(m_voiceCallBtn); + + // 功能介绍 + QLabel *featureLabel = new QLabel("• SSH双跳连接\n• 音频文件播放\n• 实时录音制作\n• TTS语音合成"); + featureLabel->setObjectName("feature-list"); + featureLabel->setAlignment(Qt::AlignLeft); + featureLabel->setStyleSheet("color: #b0b0b0; font-size: 12px; padding: 10px; line-height: 1.4;"); + m_intelligenceCard->addContent(featureLabel); m_mainLayout->addWidget(m_intelligenceCard); } @@ -504,9 +487,9 @@ void RightFunctionPanel::applyStyles() stop:0 #2a3441, stop:1 #34404f); border-radius: 10px; border: 2px solid #3c4a59; - padding: 12px; - margin: 4px; - min-height: 80px; + padding: 16px; + margin: 6px; + min-height: 100px; } #RightDeviceCard:hover { @@ -524,13 +507,14 @@ void RightFunctionPanel::applyStyles() #DeviceName { color: #ffffff; - font-size: 13px; + font-size: 16px; font-weight: 600; + margin-bottom: 4px; } #DeviceStatus { color: #a4b0be; - font-size: 11px; + font-size: 14px; font-weight: 500; } @@ -539,13 +523,14 @@ void RightFunctionPanel::applyStyles() background: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:1, stop:0 #2a3441, stop:1 #34404f); color: #ffffff; - font-size: 13px; + font-size: 16px; font-weight: 600; - padding: 12px 16px; + padding: 16px 20px; border-radius: 8px; border: 2px solid #3c4a59; - margin: 4px; + margin: 6px; text-align: center; + min-height: 45px; } #FunctionBtn:hover { @@ -559,9 +544,11 @@ void RightFunctionPanel::applyStyles() background: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:1, stop:0 #00a8ff, stop:1 #0078d4); color: #ffffff; - font-size: 14px; + font-size: 18px; font-weight: 700; border: 2px solid #00a8ff; + padding: 18px 24px; + min-height: 55px; } #FunctionBtn[class="primary-large"]:hover { @@ -574,8 +561,11 @@ void RightFunctionPanel::applyStyles() background: qlineargradient(spread:pad, x1:0, y1:0, x2:1, y2:1, stop:0 #00a8ff, stop:1 #0078d4); color: #ffffff; + font-size: 16px; font-weight: 700; border: 2px solid #00a8ff; + padding: 16px 20px; + min-height: 48px; } #FunctionBtn[class="primary-medium"]:hover { @@ -589,6 +579,9 @@ void RightFunctionPanel::applyStyles() background: #2a3441; border: 2px solid #3c4a59; color: #ffffff; + font-size: 16px; + padding: 16px 20px; + min-height: 48px; } #FunctionBtn[class="secondary-medium"]:hover { @@ -600,8 +593,9 @@ void RightFunctionPanel::applyStyles() background: #2a3441; border: 2px solid #3c4a59; color: #ffffff; - font-size: 12px; - padding: 8px 12px; + font-size: 15px; + padding: 14px 18px; + min-height: 42px; } #FunctionBtn[class="secondary-small"]:hover { @@ -834,28 +828,9 @@ void RightFunctionPanel::onPersonRecognitionToggle() } } -void RightFunctionPanel::onVoiceCallToggle() +void RightFunctionPanel::onOpenIntelligenceUI() { - m_isInCall = !m_isInCall; - - m_voiceCallBtn->setText(m_isInCall ? "📞 结束通话" : "📞 开始通话"); - m_voiceCallBtn->setProperty("class", m_isInCall ? "danger" : "primary-medium"); - m_voiceCallBtn->style()->unpolish(m_voiceCallBtn); - m_voiceCallBtn->style()->polish(m_voiceCallBtn); - - m_muteBtn->setEnabled(m_isInCall); - m_callStatusLabel->setText(m_isInCall ? "📞 通话中..." : "📋 未连接"); - - // 更新通话状态的样式 - 使用CSS类 - m_callStatusLabel->setProperty("class", m_isInCall ? "call-status-active" : "call-status"); - m_callStatusLabel->style()->unpolish(m_callStatusLabel); - m_callStatusLabel->style()->polish(m_callStatusLabel); - - if (m_isInCall) { - emit startVoiceCall(); - } else { - emit endVoiceCall(); - } + emit openIntelligenceUI(); } void RightFunctionPanel::onRefreshStats() diff --git a/src/Client/src/ui/main/MainWindow.cpp b/src/Client/src/ui/main/MainWindow.cpp index d3a09ec9..b0e392bf 100644 --- a/src/Client/src/ui/main/MainWindow.cpp +++ b/src/Client/src/ui/main/MainWindow.cpp @@ -52,7 +52,7 @@ MainWindow::MainWindow(QWidget *parent) , m_systemLogPanel(nullptr) , m_rightFunctionPanel(nullptr) , m_leftPanelSplitter(nullptr) - // , m_intelligenceUI(nullptr) // 暂时注释掉 + , m_intelligenceUI(nullptr) { m_ui->setupUi(this); @@ -67,6 +67,10 @@ MainWindow::MainWindow(QWidget *parent) MainWindow::~MainWindow() { + if (m_intelligenceUI) { + delete m_intelligenceUI; + m_intelligenceUI = nullptr; + } delete m_ui; } @@ -220,10 +224,8 @@ void MainWindow::setupRightFunctionPanel() this, &MainWindow::onStopPersonRecognition); // 情报传输模块信号 - connect(m_rightFunctionPanel, &RightFunctionPanel::startVoiceCall, - this, &MainWindow::onStartVoiceCall); - connect(m_rightFunctionPanel, &RightFunctionPanel::endVoiceCall, - this, &MainWindow::onEndVoiceCall); + connect(m_rightFunctionPanel, &RightFunctionPanel::openIntelligenceUI, + this, &MainWindow::onIntelligenceClicked); // 敌情统计模块信号 connect(m_rightFunctionPanel, &RightFunctionPanel::refreshEnemyStats, @@ -923,16 +925,12 @@ void MainWindow::onSmartNavigationClicked() void MainWindow::onIntelligenceClicked() { - // 暂时注释掉 IntelligenceUI 相关功能,待实现 - /* if (!m_intelligenceUI) { m_intelligenceUI = new IntelligenceUI(this); } m_intelligenceUI->show(); m_intelligenceUI->activateWindow(); m_intelligenceUI->raise(); - */ - qDebug() << "Intelligence UI feature not implemented yet"; } void MainWindow::onDeviceSelected(const QString &deviceId) @@ -1326,22 +1324,7 @@ void MainWindow::onStopPersonRecognition() // TODO: 实现停止人物识别功能 } -void MainWindow::onStartVoiceCall() -{ - qDebug() << "Starting voice communication..."; - SystemLogger::getInstance()->logInfo("开始语音通话"); - - // TODO: 实现语音通话功能 - // 这里应该连接到实际的音频通信系统 -} -void MainWindow::onEndVoiceCall() -{ - qDebug() << "Ending voice communication..."; - SystemLogger::getInstance()->logInfo("结束语音通话"); - - // TODO: 实现结束通话功能 -} void MainWindow::onRefreshEnemyStats() {