1. 实现单次对话功能,支持语音录制和数据传输

2. 优化静音检测算法,改进RMS平滑处理
3. 添加自动化代理处理,支持点击、拖拽、输入等操作
4. 修复多屏幕环境下鼠标定位问题
This commit is contained in:
Misaki
2026-02-03 01:29:41 +08:00
parent 015ef0f962
commit 46da4870c9
15 changed files with 342 additions and 55 deletions
+8 -4
View File
@@ -29,15 +29,14 @@ if(APPLE)
find_library(APPLICATIONSERVICES_LIBRARY ApplicationServices REQUIRED)
target_link_libraries(autogui-cpp PUBLIC ${APPLICATIONSERVICES_LIBRARY})
# Windows平台
# Windows平台
elseif(WIN32)
# 链接User32.lib (提供SendInput, GetCursorPos等API)
target_link_libraries(autogui-cpp PUBLIC User32)
# Linux/Unix平台
# Linux/Unix平台
elseif(UNIX AND NOT APPLE)
# Linux需要X11后端(Wayland尚未支持)
# Linux需要X11后端
find_package(X11 REQUIRED)
# 查找Xtst库(XTest扩展)
find_library(X11_Xtst_LIB Xtst)
@@ -49,15 +48,20 @@ elseif(UNIX AND NOT APPLE)
if(NOT X11_Xext_LIB)
message(FATAL_ERROR "Xext library not found.")
endif()
# 查找XRandR库(Xrandr扩展)
find_library(X11_Xrandr_LIB Xrandr)
if(NOT X11_Xrandr_LIB)
message(FATAL_ERROR "XRandR library not found. Install libxrandr-dev.")
endif()
# 链接所有必需的X11库
target_link_libraries(autogui-cpp PUBLIC
${X11_LIBRARIES}
${X11_Xtst_LIB}
${X11_Xext_LIB}
${X11_Xrandr_LIB}
)
target_include_directories(autogui-cpp PUBLIC ${X11_INCLUDE_DIR})
endif()
# 集成方式: add_subdirectory
# 在你的项目CMakeLists.txt中:
# add_subdirectory(autogui-cpp)
+193 -14
View File
@@ -10,11 +10,10 @@
#include <iostream>
#include <random>
#include <thread>
#include <cstring>
#ifndef M_PI
# define M_PI 3.14159265358979323846
#endif
namespace AutoGUI {
// 内部辅助函数
@@ -33,6 +32,139 @@ Robot::Point getCurrentPosition() { return Robot::Mouse::GetPosition(); }
} // namespace
#if defined(__linux__)
#include <X11/Xlib.h>
#include <X11/extensions/Xrandr.h>
#include <algorithm>
// 获取所有屏幕信息
std::vector<ScreenInfo> getAllScreens() {
std::vector<ScreenInfo> screens;
Display* display = XOpenDisplay(nullptr);
if (!display) return screens;
XRRScreenResources* resources = XRRGetScreenResources(display, DefaultRootWindow(display));
if (resources) {
for (int i = 0; i < resources->noutput; i++) {
XRROutputInfo* output = XRRGetOutputInfo(display, resources, resources->outputs[i]);
if (output && output->connection == RR_Connected && output->crtc) {
XRRCrtcInfo* crtc = XRRGetCrtcInfo(display, resources, output->crtc);
if (crtc) {
ScreenInfo info;
info.id = i;
info.x = crtc->x;
info.y = crtc->y;
info.width = crtc->width;
info.height = crtc->height;
info.isPrimary = (output->name && strcmp(output->name, "primary") == 0);
// 或者使用 XRRGetOutputPrimary 来判断
screens.push_back(info);
XRRFreeCrtcInfo(crtc);
}
XRRFreeOutputInfo(output);
}
}
XRRFreeScreenResources(resources);
}
XCloseDisplay(display);
return screens;
}
// 获取当前鼠标所在的屏幕
ScreenInfo getCurrentScreen() {
auto screens = getAllScreens();
if (screens.empty()) return {0, 0, 0, 1920, 1080, true};
// 获取当前鼠标位置(虚拟桌面绝对坐标)
Robot::Point mouse = position();
// 找到包含该点的屏幕
for (const auto& screen : screens) {
if (mouse.x >= screen.x && mouse.x < screen.x + screen.width &&
mouse.y >= screen.y && mouse.y < screen.y + screen.height) {
return screen;
}
}
// 默认返回主屏或第一个屏幕
auto it = std::find_if(screens.begin(), screens.end(),
[](const ScreenInfo& s) { return s.isPrimary; });
return (it != screens.end()) ? *it : screens[0];
}
// 相对于当前屏幕移动
void moveToOnCurrentScreen(int x, int y, double duration) {
const ScreenInfo current = getCurrentScreen();
// 转换为虚拟桌面绝对坐标
int absX = current.x + x;
int absY = current.y + y;
moveTo(absX, absY, duration);
}
// 获取当前屏幕尺寸
Robot::Point getScreenSize(int screenId) {
if (screenId == -1) {
ScreenInfo current = getCurrentScreen();
return {current.width, current.height};
}
auto screens = getAllScreens();
for (const auto& s : screens) {
if (s.id == screenId) return {s.width, s.height};
}
return {1920, 1080};
}
#endif
#if defined(_WIN32)
#include <Windows.h>
std::vector<ScreenInfo> getAllScreens() {
std::vector<ScreenInfo> screens;
EnumDisplayMonitors(nullptr, nullptr,
[](HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) -> BOOL {
auto* screens = reinterpret_cast<std::vector<ScreenInfo>*>(dwData);
MONITORINFOEX info;
info.cbSize = sizeof(info);
if (GetMonitorInfo(hMonitor, &info)) {
ScreenInfo si;
si.id = screens->size();
si.x = info.rcMonitor.left;
si.y = info.rcMonitor.top;
si.width = info.rcMonitor.right - info.rcMonitor.left;
si.height = info.rcMonitor.bottom - info.rcMonitor.top;
si.isPrimary = (info.dwFlags & MONITORINFOF_PRIMARY) != 0;
screens->push_back(si);
}
return TRUE;
}, reinterpret_cast<LPARAM>(&screens));
return screens;
}
ScreenInfo getCurrentScreen() {
// 获取当前鼠标位置
POINT pt;
GetCursorPos(&pt);
// 查找包含该点的显示器
HMONITOR hMonitor = MonitorFromPoint(pt, MONITOR_DEFAULTTONEAREST);
MONITORINFOEX info;
info.cbSize = sizeof(info);
GetMonitorInfo(hMonitor, &info);
ScreenInfo si;
si.x = info.rcMonitor.left;
si.y = info.rcMonitor.top;
si.width = info.rcMonitor.right - info.rcMonitor.left;
si.height = info.rcMonitor.bottom - info.rcMonitor.top;
return si;
}
void moveToOnCurrentScreen(int x, int y, double duration) {
ScreenInfo current = getCurrentScreen();
moveTo(current.x + x, current.y + y, duration);
}
#endif
// 实现主要API函数
void moveTo(int x, int y, double duration) {
Robot::Point target{x, y};
@@ -269,37 +401,84 @@ void sleep(double seconds) {
}
}
#if defined(_WIN32)
#include <Windows.h>
#elif defined(__APPLE__)
#include <CoreGraphics/CoreGraphics.h>
#elif defined(__linux__)
#include <X11/Xlib.h>
#include <X11/extensions/Xrandr.h>
#endif
// 平台特定函数实现
Robot::Point size() {
// 平台特定实现
#ifdef _WIN32
// Windows实现
#include <Windows.h>
int width = GetSystemMetrics(SM_CXSCREEN);
int height = GetSystemMetrics(SM_CYSCREEN);
return {width, height};
#elif defined(__APPLE__)
// macOS实现
#include <CoreGraphics/CoreGraphics.h>
CGRect mainDisplayBounds = CGDisplayBounds(CGMainDisplayID());
int width = static_cast<int>(CGRectGetWidth(mainDisplayBounds));
int height = static_cast<int>(CGRectGetHeight(mainDisplayBounds));
return {width, height};
#elif defined(__linux__)
// Linux实现(X11
#include <X11/Xlib.h>
Display *display = XOpenDisplay(nullptr);
if (display) {
const Screen *screen = DefaultScreenOfDisplay(display);
const int width = WidthOfScreen(screen);
const int height = HeightOfScreen(screen);
XCloseDisplay(display);
return {width, height};
if (!display) return {1920, 1080};
int width = 0, height = 0;
// 使用 XRRGetScreenResources (兼容 XRandR 1.2+,比 Current 版本兼容性更好)
XRRScreenResources *resources = XRRGetScreenResources(display, DefaultRootWindow(display));
if (resources) {
RROutput target_output = None;
// 条件编译:只在 XRandR 1.3+ 时使用 Primary Output 功能
#if defined(RANDR_MAJOR) && defined(RANDR_MINOR)
#if RANDR_MAJOR > 1 || (RANDR_MAJOR == 1 && RANDR_MINOR >= 3)
target_output = XRRGetOutputPrimary(display, DefaultRootWindow(display));
#endif
#endif
// 如果没有获取到主显示器(或版本太低),使用第一个已连接的显示器
if (target_output == None) {
for (int i = 0; i < resources->noutput; i++) {
XRROutputInfo *info = XRRGetOutputInfo(display, resources, resources->outputs[i]);
if (info) {
if (info->connection == 0) {
target_output = resources->outputs[i];
XRRFreeOutputInfo(info);
break;
}
XRRFreeOutputInfo(info);
}
}
}
// 获取选中显示器的分辨率
if (target_output != None) {
XRROutputInfo *output_info = XRRGetOutputInfo(display, resources, target_output);
if (output_info && output_info->crtc) {
XRRCrtcInfo *crtc_info = XRRGetCrtcInfo(display, resources, output_info->crtc);
if (crtc_info) {
width = static_cast<int>(crtc_info->width);
height = static_cast<int>(crtc_info->height);
XRRFreeCrtcInfo(crtc_info);
}
XRRFreeOutputInfo(output_info);
}
}
XRRFreeScreenResources(resources);
}
return {1920, 1080}; // 默认值
XCloseDisplay(display);
return (width > 0 && height > 0) ? Robot::Point{width, height} : Robot::Point{1920, 1080};
#else
// 其他平台
+21
View File
@@ -125,6 +125,27 @@ inline bool isSpecialKey(const std::string& key) {
return std::find(specialKeys.begin(), specialKeys.end(), lower) != specialKeys.end();
}
// 解决多屏幕移动鼠标问题
struct ScreenInfo {
int id; // 屏幕ID
int x, y; // 相对于虚拟桌面原点的偏移
int width, height;// 屏幕尺寸
bool isPrimary; // 是否主屏
};
// 获取所有屏幕信息
std::vector<ScreenInfo> getAllScreens();
// 获取当前鼠标所在的屏幕
ScreenInfo getCurrentScreen();
// 相对于当前屏幕移动鼠标(以当前屏幕左上角为原点)
void moveToOnCurrentScreen(int x, int y, double duration = 0.0);
// 获取指定屏幕的尺寸(替代原来的size(),支持多屏)
Robot::Point getScreenSize(int screenId = -1); // -1表示当前屏幕
// 主要 API 函数
/**
* @brief 移动鼠标到指定位置
+1 -1
View File
@@ -53,7 +53,7 @@ make
```
注意:本项目只是Yosuga的客户端部分,完整的还包括服务端
- 服务端项目地址见:
- 服务端项目地址见:https://github.com/Misakityan/Yosuga_server
当前支持平台(已测试过的)
- Windows: Windows 10
+4 -1
View File
@@ -27,7 +27,7 @@ private:
static QMutex m_mutex;
private slots:
// 业务接收槽函数
void onRecordingFinished_Byte(const QByteArray &wavData);
public:
// 单例访问点
static AppCore *getInstance();
@@ -36,4 +36,7 @@ public:
~AppCore() override;
public:
// 单次对话
void SingleExchange();
};
+25
View File
@@ -9,6 +9,9 @@
#include "AutoAgentHandle.h"
#include "ScreenShotReqDataHandle.h"
#include "AudioInput.h"
#include "NetWorkDO.h"
#include "websocketmanager.h"
// 初始化静态成员
QScopedPointer<AppCore> AppCore::m_instance;
QMutex AppCore::m_mutex;
@@ -40,6 +43,15 @@ AppCore::AppCore(QObject *parent) : QObject(parent)
AudioDataHandle::getInstance();
AutoAgentHandle::getInstance();
ScreenShotReqDataHandle::getInstance();
// 注入发送接口
NetworkDO::getInstance()->registerSender([](const QString& type, const QJsonObject& data){
WebSocketClient::getInstance()->sendJson(type, data);
});
// TODO Test
AudioInput::getInstance()->setAudioPath(QDir::currentPath(), "/temp.wav");
// 连接必要的信号
connect(AudioInput::getInstance(), &AudioInput::recordingFinished_Byte,
this, &AppCore::onRecordingFinished_Byte); // 录音完成信号
}
AppCore::~AppCore()
@@ -52,3 +64,16 @@ AppCore::~AppCore()
qDebug() << "AppCore destroyed";
}
void AppCore::SingleExchange() {
// 开始录音,录音结束后会触发录音完成信号
AudioInput::getInstance()->startAutoStopAudio(AudioInput::getInstance()->getSilenceThreshold(), 800);
}
void AppCore::onRecordingFinished_Byte(const QByteArray &wavData) {
// 将录音数据发送给服务端
AudioDataTransferObject packet;
packet.setData("isStream", false).setData("data", wavData.toBase64().data());
NetworkDO::getInstance()->sendPacket(packet);
}
+1 -1
View File
@@ -28,7 +28,7 @@ public:
// 静态工厂方法
static ScreenShotDataTransferObject fromJson(const QJsonObject& json);
[[nodiscard]] QString type() const override { return "screenshot_req"; }
[[nodiscard]] QString type() const override { return "screenshot_data"; }
// 序列化
[[nodiscard]] QJsonObject toJson() const override; // 通过多态即可统一调用方式
+1 -1
View File
@@ -72,7 +72,7 @@ void NetworkDO::onDataReceived(const QString& type, const QJsonObject& data)
else if (type == "auto_agent") {
emit autoAgentPacketReceived(AutoAgentDataObject::fromJson(data));
}
else if (type == "screenshot_req") {
else if (type == "screenshot_data") {
emit screenShotPacketReceived(ScreenShotDataTransferObject::fromJson(data));
}
else {
+1 -1
View File
@@ -34,7 +34,7 @@ ScreenShotDataTransferObject ScreenShotDataTransferObject::fromJson(const QJsonO
// 调用构造函数创建对象
return ScreenShotDataTransferObject(owner, isSuccess, realtimeScreenShot,
width, height, describeInfo);
width, height, describeInfo, LLMResponse);
}
// 序列化为 JSON
+2
View File
@@ -148,5 +148,7 @@ private:
qreal m_silenceThreshold = 1200; /// 静音阈值
int m_silenceDuration = 1500; /// 静音持续时间
qreal m_smoothRms = 0.0; /// 平滑RMS值(用于防止低频杂波突然打断静音检测)
bool m_hasVoiceDetected = false; /// 是否已检测到人声
};
+37 -7
View File
@@ -148,7 +148,7 @@ void AudioInput::onReadyRead()
const qreal currentRms = calculateRMS(data);
m_rmsValue = currentRms;
// 计算平滑RMS (用于防止低频杂波突然打断静音检测)
constexpr qreal alpha = 0.3; // 70% 历史权重, 30% 当前权重
constexpr qreal alpha = 0.15; // 85% 历史权重, 15% 当前权重
if (qFuzzyIsNull(m_smoothRms)) {
// 如果是第一帧数据,直接赋值,避免从0开始慢慢爬升
m_smoothRms = currentRms;
@@ -163,13 +163,30 @@ void AudioInput::onReadyRead()
qDebug() << "Raw:" << currentRms << " Smooth:" << m_smoothRms;
if (m_smoothRms < m_silenceThreshold) {
// 静音状态
if (!m_silenceTimer->isActive()) {
m_silenceTimer->start(m_silenceDuration);
// [当前是静音]
// 如果之前已经检测到过人声(说明是话说完了,或者是句间停顿)
if (m_hasVoiceDetected) {
// 启动/保持“短时”静音检测 (由 AppCore 传入,例如 500ms 或 1500ms)
if (!m_silenceTimer->isActive()) {
m_silenceTimer->start(m_silenceDuration);
}
// 如果 Timer 正在运行,就让它继续倒计时,超时会自动触发 stopAudio
}
else {
// [还没有检测到过人声] (起始静音)
// 这里不需要做额外操作,startAutoStopAudio 里设置的 5000ms 长定时器在跑
// 允许用户深呼吸或准备
}
} else {
// 有声音,重置定时器
// [当前有声音]
m_hasVoiceDetected = true; // 标记:已经有人说话了
// 重置静音定时器
// 只要有人说话,就不断重置定时器,防止断录
m_silenceTimer->stop();
// 这里可以预设启动,也可以不启动,只要有声音就会一直 stop
// 为了安全,设为 silenceDuration
m_silenceTimer->start(m_silenceDuration);
}
}
@@ -246,11 +263,20 @@ void AudioInput::startAutoStopAudio(const qreal silenceThreshold, const int sile
m_silenceThreshold = silenceThreshold;
m_silenceDuration = silenceDuration;
// 重置状态
m_hasVoiceDetected = false;
m_smoothRms = 0.0;
startAudio();
// 延迟200ms是为了避开硬件启动时的爆音,但不需要立即启动短时倒计时
// 延迟启动静音检测,给一点缓冲时间
QTimer::singleShot(200, this, [this](){
m_silenceTimer->start(m_silenceDuration);
if(isAutoRecording) { // 确保还在录音状态
// 如果还没检测到声音,给5秒的等待时间;如果检测到了,逻辑由onReadyRead接管
if(!m_hasVoiceDetected) {
m_silenceTimer->start(5000); // 5秒没声音就停止
}
}
});
}
@@ -285,8 +311,12 @@ void AudioInput::thresholdTimeout()
// 防止浮点误差导致负数
if (variance < 0) variance = 0;
const double stdDev = std::sqrt(variance);
// 增加一个固定的偏移量 offset
// 确保即使环境有轻微波动,也不会触发录音
constexpr double offset = 80.0;
// 阈值 = 均值 + 2 * 标准差
const double bestThreshold = mean + 3 * stdDev;
const double bestThreshold = mean + (3 * stdDev) + offset;
m_silenceThreshold = std::max(bestThreshold, 150.0);
m_silenceThreshold = std::min(m_silenceThreshold, 30000.0);
qDebug() << "Auto Threshold Calc -> Mean:" << mean
@@ -42,24 +42,40 @@ AutoAgentHandle::~AutoAgentHandle()
}
void AutoAgentHandle::onAutoAgentPacketReceived(const AutoAgentDataObject &packet) {
qDebug() << "Received AutoAgent packet: " << packet.getAction();
if (packet.getAction() == "click") { // 单击
qDebug() << "Click: " << packet.getX1() << ", " << packet.getY1();
AutoGUI::moveTo(packet.getX1(), packet.getY1(), 0.6);
AutoGUI::moveToOnCurrentScreen(packet.getX1(), packet.getY1(), 0.6);
AutoGUI::click(packet.getX1(), packet.getY1());
}
if (packet.getAction() == "left_double") { // 双击
qDebug() << "Double click: " << packet.getX1() << ", " << packet.getY1();
AutoGUI::moveTo(packet.getX1(), packet.getY1(), 0.6);
AutoGUI::moveToOnCurrentScreen(packet.getX1(), packet.getY1(), 0.6);
AutoGUI::leftDouble(packet.getX1(), packet.getY1());
}
if (packet.getAction() == "right_single") { // 右键单击
qDebug() << "Right click: " << packet.getX1() << ", " << packet.getY1();
AutoGUI::moveTo(packet.getX1(), packet.getY1(), 0.6);
AutoGUI::moveToOnCurrentScreen(packet.getX1(), packet.getY1(), 0.6);
AutoGUI::rightSingle(packet.getX1(), packet.getY1());
}
if (packet.getAction() == "drag") { // 拖拽
qDebug() << "Drag: " << packet.getX1() << ", " << packet.getY1() << " to " << packet.getX2() << ", " << packet.getY2();
AutoGUI::drag(packet.getX1(), packet.getY1(), packet.getX2(), packet.getY2(), 0.8);
AutoGUI::drag(packet.getX1(), packet.getY1(), packet.getX2(), packet.getY2(), 1.2);
}
// TODO: 快捷键,输入文本,滚动待实现
}
if (packet.getAction() == "type") { // 输入文本
qDebug() << "Type: " << packet.getContent();
AutoGUI::type(packet.getContent().toStdString(), 0.08);
}
if (packet.getAction() == "scroll") { // 滚动
qDebug() << "Scroll: " << packet.getX1() << ", " << packet.getY1() << packet.getDirection();
if (packet.getDirection() == "up") {
AutoGUI::moveToOnCurrentScreen(packet.getX1(), packet.getY1(), 0.1);
AutoGUI::scroll(40, 1);
}
if (packet.getDirection() == "down") {
AutoGUI::moveToOnCurrentScreen(packet.getX1(), packet.getY1(), 0.1);
AutoGUI::scroll(40, -1);
}
}
}
@@ -42,6 +42,7 @@ ScreenShotReqDataHandle::ScreenShotReqDataHandle(QObject *parent) : QObject(pare
const QString sysText = QString("System: %1 OS Version: %2 Display Server: %3")
.arg(sysInfo.osType, sysInfo.osVersion, sysInfo.displayServer);
this->m_systemInfo = sysText;
qDebug() << "当前平台信息为: " << sysText;
}
ScreenShotReqDataHandle::~ScreenShotReqDataHandle()
@@ -55,12 +56,14 @@ void ScreenShotReqDataHandle::onScreenShotPacketReceived(const ScreenShotDataTra
const ScreenHelper::ScreenshotResult result = ScreenHelper::captureFocusedScreen(); // 获取当前屏幕截图
if (!result.success) { // 如果截图失败
// TODO: 考虑失败时候构造一个错误DTO给服务端
qDebug() << "截图失败: " << result.errorMsg;
return;
}
ScreenShotDataTransferObject reback; // 构造返回的DTO
reback.setData("isSuccess", true).setData("RealTimeScreenShot", result.base64Data)
.setData("Width", result.width).setData("Height", result.height)
.setData("DescribeInfo", this->m_systemInfo);
.setData("DescribeInfo", this->m_systemInfo).setData("LLMResponse", packet.LLMResponse());
// 发送DTO
NetworkDO::getInstance()->sendPacket(reback);
qDebug() << "ScreenShot packet sent to:" << packet.owner();
}
+8 -8
View File
@@ -7,23 +7,22 @@
* 基于Ela UI的菜单控件
*/
#include "ElaMenu.h"
#include <ElaMenu.h>
#include <QMenu>
#include <QAction>
#include <QPoint>
#include <QScopedPointer> // 智能指针
#include "Setting.h"
#include "networkmanager.h"
#include "socketmanager.h"
class Menu : public ElaMenu
class Menu final : public ElaMenu
{
Q_OBJECT
public:
explicit Menu(QWidget *parent = nullptr);
~Menu();
~Menu() override;
void showMenu(const QPoint &pos);
signals:
@@ -33,10 +32,11 @@ signals:
private:
void createMenu();
QAction *toggleThe; /// 切换主题(全局)
QAction *startExchangeAction; /// 开启对话
QAction *settingsAction; /// 设置
QAction *closeAction; /// 关闭
QAction *toggleThe; /// 切换主题(全局)
QAction *startSingleExchangeAction; /// 开启单次对话
QAction *startContinueExchangeAction; /// 开启连续对话
QAction *settingsAction; /// 设置
QAction *closeAction; /// 关闭
QScopedPointer<Setting> settingWindow; // 使用智能指针管理 Setting 窗口
+15 -11
View File
@@ -5,29 +5,29 @@
#include <QTimer>
#include "TextRenderer.h"
// #include "AudioInput.h"
// #include "AudioOutput.h"
#include "AppCore.h"
Menu::Menu(QWidget *parent)
: ElaMenu(parent)
{
// 设置默认主题
eTheme->setThemeMode(ElaThemeType::Dark);
createMenu();
}
Menu::~Menu()
{
Menu::~Menu() {
AppCore::destroy(); // 显式销毁 AppCore
}
void Menu::createMenu()
{
toggleThe = addAction("切换主题");
// 单次对话功能按钮
startSingleExchangeAction = addAction("单次对话(测试)");
// 连续对话功能按钮
startExchangeAction = addAction("连续对话(测试)");
startContinueExchangeAction = addAction("连续对话(测试)");
// 添加设置按钮
settingsAction = addAction("设置");
@@ -42,10 +42,14 @@ void Menu::createMenu()
toggleTheme();
});
// TODO 连续对话功能,需要优化实现
connect(startExchangeAction, &QAction::triggered, this, [this]() {
// startExchange();
qDebug() << "Start Exchange triggered";
// 单次对话功能
connect(startSingleExchangeAction, &QAction::triggered, this, []() {
qDebug() << "Start SingleExchange triggered";
AppCore::getInstance()->SingleExchange();
});
// 连续对话功能 TODO: 待开发
connect(startContinueExchangeAction, &QAction::triggered, this, []() {
qDebug() << "Start ContinueExchange triggered";
});