集成语音模块

pull/3/head
root 2 weeks ago
parent b20c5ad72d
commit c651f19d89

@ -0,0 +1,694 @@
#include "IntelligenceUI.h"
#include "ui_IntelligenceUI.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QMessageBox>
#include <QFileDialog>
#include <QTimer>
#include <QDateTime>
#include <QDir>
#include <QFile>
#include <QProcess>
#include <QDebug>
#include <QFileInfo>
#include <QAudioRecorder>
#include <QAudioEncoderSettings>
#include <QMediaRecorder>
#include <QRandomGenerator>
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<QPushButton*>("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<int, QProcess::ExitStatus>::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<int, QProcess::ExitStatus>::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<QMediaRecorder::Error>::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<int, QProcess::ExitStatus>::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<int, QProcess::ExitStatus>::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<int, QProcess::ExitStatus>::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);
}

@ -0,0 +1,121 @@
#ifndef INTELLIGENCEUI_H
#define INTELLIGENCEUI_H
#include <QMainWindow>
#include <QPushButton>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QProcess>
#include <QMessageBox>
#include <QDebug>
#include <QComboBox>
#include <QTextEdit>
#include <QProgressBar>
#include <QFileDialog>
#include <QTimer>
#include <QMediaRecorder>
#include <QAudioRecorder>
#include <QAudioInput>
#include <QAudioFormat>
#include <QUrl>
#include <QDir>
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

@ -0,0 +1,634 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>IntelligenceUI</class>
<widget class="QMainWindow" name="IntelligenceUI">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>900</width>
<height>800</height>
</rect>
</property>
<property name="windowTitle">
<string>情报传达系统 - UnitreeGo1</string>
</property>
<property name="styleSheet">
<string notr="true">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;
}</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>20</number>
</property>
<property name="leftMargin">
<number>30</number>
</property>
<property name="topMargin">
<number>20</number>
</property>
<property name="rightMargin">
<number>30</number>
</property>
<property name="bottomMargin">
<number>20</number>
</property>
<item>
<widget class="QLabel" name="titleLabel">
<property name="text">
<string>🔊 情报传达系统</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="styleSheet">
<string notr="true">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;
}</string>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="sshSettingsGroup">
<property name="title">
<string>SSH 连接设置</string>
</property>
<property name="styleSheet">
<string notr="true">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;
}</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="labelIp">
<property name="text">
<string>目标 IP:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="lineEditIp">
<property name="text">
<string>192.168.123.13</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="labelUsername">
<property name="text">
<string>用户名:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="lineEditUsername">
<property name="text">
<string>unitree</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="labelPassword">
<property name="text">
<string>密码:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="lineEditPassword">
<property name="text">
<string>123</string>
</property>
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="saveSshSettings">
<property name="text">
<string>保存并应用设置</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QLabel" name="deviceLabel">
<property name="text">
<string>当前目标: 192.168.123.13 (UnitreeGo1)</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="styleSheet">
<string notr="true">color: rgb(160, 170, 180); font-size: 16px;</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="audioSelectionLayout">
<item>
<widget class="QLabel" name="audioLabel">
<property name="text">
<string>选择音频文件:</string>
</property>
<property name="styleSheet">
<string notr="true">font-size: 16px; font-weight: bold;</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="audioComboBox">
<property name="minimumSize">
<size>
<width>200</width>
<height>0</height>
</size>
</property>
<item>
<property name="text">
<string>warning.wav</string>
</property>
</item>
<item>
<property name="text">
<string>alert.wav</string>
</property>
</item>
<item>
<property name="text">
<string>emergency.wav</string>
</property>
</item>
<item>
<property name="text">
<string>notification.wav</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QPushButton" name="refreshAudioList">
<property name="text">
<string>刷新列表</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="controlButtonsLayout">
<item>
<widget class="QPushButton" name="playSelectedAudio">
<property name="text">
<string>🔊 播放选定音频</string>
</property>
<property name="styleSheet">
<string notr="true">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);
}</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="killWSAudio">
<property name="text">
<string>🔧 解除音频占用</string>
</property>
<property name="styleSheet">
<string notr="true">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);
}</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="uploadAudioGroup">
<property name="title">
<string>自定义音频制作</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="recordLabel">
<property name="text">
<string>🎤 录制语音:</string>
</property>
<property name="styleSheet">
<string notr="true">font-size: 14px; font-weight: bold; color: rgb(82, 194, 242);</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="recordLayout">
<item>
<widget class="QPushButton" name="recordButton">
<property name="text">
<string>🎤 开始录制</string>
</property>
<property name="styleSheet">
<string notr="true">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);
}</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="stopRecordButton">
<property name="text">
<string>⏹ 停止录制</string>
</property>
<property name="enabled">
<bool>false</bool>
</property>
<property name="styleSheet">
<string notr="true">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);
}</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="playRecordedButton">
<property name="text">
<string>▶ 试听录音</string>
</property>
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="recordStatusLabel">
<property name="text">
<string>录制状态: 就绪</string>
</property>
<property name="styleSheet">
<string notr="true">color: rgb(160, 170, 180); font-size: 12px;</string>
</property>
</widget>
</item>
<item>
<widget class="QProgressBar" name="volumeMeter">
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>0</number>
</property>
<property name="textVisible">
<bool>false</bool>
</property>
<property name="styleSheet">
<string notr="true">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;
}</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="orLabel">
<property name="text">
<string>或者</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="styleSheet">
<string notr="true">color: rgb(160, 170, 180); font-size: 12px; margin: 10px;</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="ttsLabel">
<property name="text">
<string>🗣 文字转语音:</string>
</property>
<property name="styleSheet">
<string notr="true">font-size: 14px; font-weight: bold; color: rgb(82, 194, 242);</string>
</property>
</widget>
</item>
<item>
<widget class="QTextEdit" name="ttsTextEdit">
<property name="minimumSize">
<size>
<width>0</width>
<height>80</height>
</size>
</property>
<property name="placeholderText">
<string>输入要转换为语音的文字内容...</string>
</property>
<property name="styleSheet">
<string notr="true">background-color: rgb(30, 44, 62); border: 1px solid rgba(82, 194, 242, 0.3); border-radius: 3px; padding: 5px;</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="ttsLayout">
<item>
<widget class="QComboBox" name="voiceComboBox">
<property name="styleSheet">
<string notr="true">background-color: rgb(30, 44, 62); border: 1px solid rgba(82, 194, 242, 0.3); border-radius: 3px; padding: 3px;</string>
</property>
<item>
<property name="text">
<string>标准女声</string>
</property>
</item>
<item>
<property name="text">
<string>标准男声</string>
</property>
</item>
<item>
<property name="text">
<string>儿童声</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QPushButton" name="generateTtsButton">
<property name="text">
<string>🎵 生成语音</string>
</property>
<property name="styleSheet">
<string notr="true">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);
}</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="playTtsButton">
<property name="text">
<string>▶ 试听TTS</string>
</property>
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="ttsStatusLabel">
<property name="text">
<string>TTS状态: 就绪</string>
</property>
<property name="styleSheet">
<string notr="true">color: rgb(160, 170, 180); font-size: 12px;</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="orLabel2">
<property name="text">
<string>或者</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="styleSheet">
<string notr="true">color: rgb(160, 170, 180); font-size: 12px; margin: 10px;</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="uploadLabel">
<property name="text">
<string>📁 上传文件:</string>
</property>
<property name="styleSheet">
<string notr="true">font-size: 14px; font-weight: bold; color: rgb(82, 194, 242);</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="uploadFileLayout">
<item>
<widget class="QLineEdit" name="filePathLineEdit">
<property name="readOnly">
<bool>true</bool>
</property>
<property name="placeholderText">
<string>请选择一个.wav音频文件...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="browseButton">
<property name="text">
<string>浏览...</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="uploadAndPlayButton">
<property name="text">
<string>⬆️ 上传并播放</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QProgressBar" name="progressBar">
<property name="value">
<number>0</number>
</property>
<property name="textVisible">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="logLabel">
<property name="text">
<string>执行日志:</string>
</property>
<property name="styleSheet">
<string notr="true">font-size: 16px; font-weight: bold;</string>
</property>
</widget>
</item>
<item>
<widget class="QTextEdit" name="logTextEdit">
<property name="minimumSize">
<size>
<width>0</width>
<height>200</height>
</size>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>900</width>
<height>22</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>

@ -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

@ -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; ///< 统计模块卡片

@ -39,7 +39,7 @@
#include <QPair>
// 自定义模块头文件
// #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; ///< 右侧功能面板组件

@ -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));
});
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);"
"}"
);
volumeLayout->addLayout(volumeLabelLayout);
volumeLayout->addWidget(m_volumeSlider);
m_intelligenceCard->addContent(volumeWidget);
connect(m_voiceCallBtn, &QPushButton::clicked, this, &RightFunctionPanel::onOpenIntelligenceUI);
m_intelligenceCard->addContent(m_voiceCallBtn);
// 连接状态指示器 - 改进设计
m_callStatusLabel = new QLabel("📋 未连接");
m_callStatusLabel->setObjectName("call-status");
m_callStatusLabel->setAlignment(Qt::AlignCenter);
m_callStatusLabel->setMinimumHeight(30); // 设置最小高度
m_intelligenceCard->addContent(m_callStatusLabel);
// 功能介绍
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()

@ -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()
{

Loading…
Cancel
Save