feat(panel/chat): 显隐切换、暂存清空隐藏、菜单避让、待办联动、DTK 关于
- 传送门 desktop 入口,--show 再点一次回 daemon;右键菜单加隐藏 - 暂存区清空后整个面板隐藏 - 右键菜单弹到面板左侧,避免被自己遮挡 - 启动悬停 80ms - 添加待办后立即打开待办窗口;待办独立图标 - 分词胶囊按浅/深色取色,修复透明不可见 - 关于改用 DAboutDialog - 聊天消息下方常驻 复制/编辑/重试;助手消息可编辑、可从任意一条重试
@@ -41,6 +41,7 @@ set(GXDE_AI_SEE_SOURCES
|
||||
src/chat/markdown.cpp
|
||||
src/chat/chatview.cpp
|
||||
src/ui/portalpanel.cpp
|
||||
src/ui/aboutdialog.cpp
|
||||
)
|
||||
|
||||
add_executable(gxde-ai-see ${GXDE_AI_SEE_SOURCES})
|
||||
@@ -119,7 +120,8 @@ if (LRELEASE_EXECUTABLE AND TS_FILES)
|
||||
endif()
|
||||
|
||||
install(TARGETS gxde-ai-see RUNTIME DESTINATION bin)
|
||||
install(FILES data/gxde-ai-see.desktop data/gxde-ai-see-todo.desktop DESTINATION share/applications)
|
||||
install(FILES data/gxde-ai-see.desktop data/gxde-ai-see-todo.desktop
|
||||
data/gxde-ai-see-portal.desktop DESTINATION share/applications)
|
||||
install(DIRECTORY data/icons/hicolor/ DESTINATION share/icons/hicolor)
|
||||
|
||||
include(GNUInstallDirs)
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Version=1.0
|
||||
Name=GXDE Portal
|
||||
Name[zh_CN]=传送门
|
||||
Comment=Show or hide the GXDE AI See portal panel
|
||||
Comment[zh_CN]=显示或隐藏小埃同学的传送门面板
|
||||
Exec=gxde-ai-see --show
|
||||
Icon=gxde-ai-see
|
||||
Terminal=false
|
||||
Categories=Utility;
|
||||
StartupNotify=false
|
||||
@@ -6,7 +6,7 @@ Name[zh_CN]=GXDE 待办
|
||||
Comment=Floating todo list of GXDE AI See
|
||||
Comment[zh_CN]=小埃同学的待办
|
||||
Exec=gxde-ai-see --todo
|
||||
Icon=gxde-ai-see
|
||||
Icon=gxde-ai-see-todo
|
||||
Terminal=false
|
||||
Categories=Utility;
|
||||
StartupNotify=false
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 256 256">
|
||||
<defs>
|
||||
<linearGradient id="g" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" stop-color="#4a90ff"/>
|
||||
<stop offset="1" stop-color="#2f6bd8"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect x="8" y="8" width="240" height="240" rx="54" fill="url(#g)"/>
|
||||
<rect x="64" y="56" width="128" height="152" rx="16" fill="#ffffff" opacity="0.96"/>
|
||||
<rect x="96" y="40" width="64" height="30" rx="10" fill="#dbe8ff"/>
|
||||
<g stroke="#3d7fe8" stroke-width="9" stroke-linecap="round" fill="none">
|
||||
<path d="M84 114 l14 14 26 -28"/>
|
||||
<path d="M84 158 l14 14 26 -28"/>
|
||||
<path d="M146 118 h30"/>
|
||||
<path d="M146 162 h30"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 727 B |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 560 B |
|
After Width: | Height: | Size: 855 B |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 955 B |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
@@ -52,7 +52,7 @@ bool App::init(bool daemon, bool forceShow, bool showTodo, bool showChat)
|
||||
} else if (data.contains("todo")) {
|
||||
showTodoWindow();
|
||||
} else if (data.contains("show")) {
|
||||
showPanel();
|
||||
togglePanel();
|
||||
}
|
||||
});
|
||||
connect(socket, &QLocalSocket::disconnected, socket,
|
||||
@@ -97,7 +97,7 @@ bool App::init(bool daemon, bool forceShow, bool showTodo, bool showChat)
|
||||
}
|
||||
return;
|
||||
}
|
||||
QTimer::singleShot(120, this, [this]() {
|
||||
QTimer::singleShot(80, this, [this]() {
|
||||
if (m_dragging && m_model->isEmpty()) {
|
||||
showPanel();
|
||||
if (m_panel) {
|
||||
@@ -112,14 +112,19 @@ bool App::init(bool daemon, bool forceShow, bool showTodo, bool showChat)
|
||||
m_panel->stopPortalSuppression();
|
||||
m_panel->setDragging(false);
|
||||
}
|
||||
if (m_model->isEmpty() && m_panel && m_panel->isVisible()) {
|
||||
if (m_model->isEmpty() && !m_panelPinned && m_panel && m_panel->isVisible()) {
|
||||
m_panel->dismiss();
|
||||
}
|
||||
});
|
||||
|
||||
connect(m_model, &StagingModel::countChanged, this, [this]() {
|
||||
if (!m_daemon && !m_model->isEmpty() && m_panel) {
|
||||
if (!m_model->isEmpty() && m_panel) {
|
||||
m_panel->popup();
|
||||
return;
|
||||
}
|
||||
/* cleared: the panel exists for the staging list, hide it again */
|
||||
if (!m_dragging && !m_panelPinned && m_panel && m_panel->isVisible()) {
|
||||
m_panel->dismiss();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -131,6 +136,7 @@ bool App::init(bool daemon, bool forceShow, bool showTodo, bool showChat)
|
||||
}
|
||||
if (!daemon || forceShow || !m_model->isEmpty()) {
|
||||
showPanel();
|
||||
m_panelPinned = forceShow;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -144,6 +150,11 @@ void App::ensurePanel()
|
||||
|
||||
m_panel = new PortalPanel(m_model);
|
||||
connect(m_panel, &PortalPanel::openChatRequested, this, &App::showChatWindow);
|
||||
connect(m_panel, &PortalPanel::hideRequested, this, [this]() {
|
||||
m_panelPinned = false;
|
||||
m_panel->dismiss();
|
||||
});
|
||||
connect(m_panel, &PortalPanel::todoAdded, this, &App::showTodoWindow);
|
||||
connect(m_panel, &PortalPanel::askWithContent, this,
|
||||
[this](const QString &text, const QStringList &files) {
|
||||
showChatWindow();
|
||||
@@ -159,6 +170,18 @@ void App::showPanel()
|
||||
m_panel->popup();
|
||||
}
|
||||
|
||||
void App::togglePanel()
|
||||
{
|
||||
ensurePanel();
|
||||
if (m_panel->isVisible() && m_panelPinned) {
|
||||
m_panel->dismiss();
|
||||
m_panelPinned = false;
|
||||
return;
|
||||
}
|
||||
m_panelPinned = true;
|
||||
m_panel->popup();
|
||||
}
|
||||
|
||||
void App::showChatWindow()
|
||||
{
|
||||
if (!m_chatWindow) {
|
||||
|
||||
@@ -32,6 +32,7 @@ public:
|
||||
private:
|
||||
void ensurePanel();
|
||||
void showPanel();
|
||||
void togglePanel();
|
||||
void showTodoWindow();
|
||||
void showChatWindow();
|
||||
|
||||
@@ -42,4 +43,5 @@ private:
|
||||
QLocalServer *m_server = nullptr;
|
||||
bool m_daemon = false;
|
||||
bool m_dragging = false;
|
||||
bool m_panelPinned = false;
|
||||
};
|
||||
|
||||
@@ -182,15 +182,16 @@ public:
|
||||
connect(fork, &QToolButton::clicked, this,
|
||||
[this]() { Q_EMIT forkRequested(m_index); });
|
||||
actions->addWidget(fork);
|
||||
if (m_isLast) {
|
||||
auto *retry = actionButton(tr("重试"), m_actions);
|
||||
connect(retry, &QToolButton::clicked, this,
|
||||
[this]() { Q_EMIT retryRequested(); });
|
||||
actions->addWidget(retry);
|
||||
}
|
||||
auto *edit = actionButton(tr("编辑"), m_actions);
|
||||
connect(edit, &QToolButton::clicked, this,
|
||||
[this]() { Q_EMIT editRequested(m_index); });
|
||||
actions->addWidget(edit);
|
||||
auto *retry = actionButton(tr("重试"), m_actions);
|
||||
connect(retry, &QToolButton::clicked, this,
|
||||
[this]() { Q_EMIT retryRequested(m_index); });
|
||||
actions->addWidget(retry);
|
||||
}
|
||||
actions->addStretch();
|
||||
m_actions->hide();
|
||||
column->addWidget(m_actions);
|
||||
|
||||
if (m_isUser) {
|
||||
@@ -220,21 +221,10 @@ signals:
|
||||
void copyRequested(int index);
|
||||
void editRequested(int index);
|
||||
void forkRequested(int index);
|
||||
void retryRequested();
|
||||
void retryRequested(int index);
|
||||
void linkActivated(const QUrl &url);
|
||||
|
||||
protected:
|
||||
void enterEvent(QEnterEvent *event) override
|
||||
{
|
||||
m_actions->show();
|
||||
QWidget::enterEvent(event);
|
||||
}
|
||||
|
||||
void leaveEvent(QEvent *event) override
|
||||
{
|
||||
m_actions->hide();
|
||||
QWidget::leaveEvent(event);
|
||||
}
|
||||
|
||||
private:
|
||||
ChatMessage m_message;
|
||||
|
||||
@@ -36,7 +36,7 @@ signals:
|
||||
void copyRequested(int index);
|
||||
void editRequested(int index);
|
||||
void forkRequested(int index);
|
||||
void retryRequested();
|
||||
void retryRequested(int index);
|
||||
void linkActivated(const QUrl &url);
|
||||
|
||||
protected:
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
#include "config.h"
|
||||
#include "chatview.h"
|
||||
|
||||
#include "../ui/aboutdialog.h"
|
||||
#include "markdown.h"
|
||||
#include "mcpclient.h"
|
||||
|
||||
@@ -555,7 +557,9 @@ void ChatWindow::setupUi()
|
||||
});
|
||||
connect(m_messages, &ChatMessageList::editRequested, this, &ChatWindow::editMessage);
|
||||
connect(m_messages, &ChatMessageList::forkRequested, this, &ChatWindow::forkFrom);
|
||||
connect(m_messages, &ChatMessageList::retryRequested, this, &ChatWindow::retryLast);
|
||||
connect(m_messages, &ChatMessageList::retryRequested, this, [this](int index) {
|
||||
retryFrom(index);
|
||||
});
|
||||
|
||||
auto *menu = new QMenu(menuButton);
|
||||
menu->addAction(tr("压缩上下文"), this, [this]() { beginCompression(false); });
|
||||
@@ -925,9 +929,6 @@ void ChatWindow::editMessage(int index)
|
||||
if (index < 0 || index >= conversation.messages.size()) {
|
||||
return;
|
||||
}
|
||||
if (conversation.messages.at(index).role != QStringLiteral("user")) {
|
||||
return;
|
||||
}
|
||||
|
||||
QDialog dialog(this);
|
||||
dialog.setWindowTitle(tr("编辑消息"));
|
||||
@@ -959,6 +960,27 @@ void ChatWindow::editMessage(int index)
|
||||
requestCompletion();
|
||||
}
|
||||
|
||||
void ChatWindow::retryFrom(int index)
|
||||
{
|
||||
if (m_client->busy() || m_compressing || m_current < 0) {
|
||||
return;
|
||||
}
|
||||
ChatConversation &conversation = m_conversations[m_current];
|
||||
if (index < 0 || index >= conversation.messages.size()) {
|
||||
return;
|
||||
}
|
||||
if (conversation.messages.at(index).role != QStringLiteral("assistant")) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* drop everything from the assistant answer on, then regenerate it */
|
||||
conversation.messages.resize(index);
|
||||
conversation.updatedAt = QDateTime::currentMSecsSinceEpoch();
|
||||
save();
|
||||
renderConversation();
|
||||
requestCompletion();
|
||||
}
|
||||
|
||||
void ChatWindow::retryLast()
|
||||
{
|
||||
if (m_client->busy() || m_compressing || m_current < 0) {
|
||||
@@ -1028,15 +1050,7 @@ void ChatWindow::handleAnchor(const QUrl &url)
|
||||
|
||||
void ChatWindow::showAbout()
|
||||
{
|
||||
QMessageBox::about(
|
||||
this, tr("关于小埃同学"),
|
||||
tr("<h3>GXDE 小埃同学</h3>"
|
||||
"<p>版本 %1</p>"
|
||||
"<p>GXDE 桌面的 AI 助手:传送门、内容工厂、待办与对话。</p>"
|
||||
"<p>许可证:GPL-3.0-or-later<br/>"
|
||||
"仓库:<a href='https://gitee.com/GXDE-OS/gxde-ai-see'>"
|
||||
"gitee.com/GXDE-OS/gxde-ai-see</a></p>")
|
||||
.arg(QCoreApplication::applicationVersion()));
|
||||
showAboutDialog(this, tr("GXDE 桌面的 AI 助手:传送门、内容工厂、待办与对话。"));
|
||||
}
|
||||
|
||||
void ChatWindow::appendError(const QString &message)
|
||||
|
||||
@@ -68,6 +68,7 @@ private:
|
||||
void sendPayload();
|
||||
void editMessage(int index);
|
||||
void retryLast();
|
||||
void retryFrom(int index);
|
||||
void forkFrom(int index);
|
||||
void beginCompression(bool thenSend);
|
||||
void handleAnchor(const QUrl &url);
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "flowlayout.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QPalette>
|
||||
#include <QClipboard>
|
||||
#include <QEvent>
|
||||
#include <QLabel>
|
||||
@@ -21,12 +22,17 @@ SegmentDialog::SegmentDialog(const QStringList &words, QWidget *parent)
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
resize(560, 420);
|
||||
|
||||
const bool dark = palette().color(QPalette::Window).lightness() < 128;
|
||||
const QColor capsuleBg = dark ? QColor(255, 255, 255, 20) : QColor(0, 0, 0, 18);
|
||||
const QColor capsuleBorder = dark ? QColor(255, 255, 255, 30) : QColor(0, 0, 0, 28);
|
||||
const QColor textColor = palette().color(QPalette::WindowText);
|
||||
|
||||
auto *layout = new QVBoxLayout(this);
|
||||
layout->setContentsMargins(16, 14, 16, 14);
|
||||
layout->setSpacing(10);
|
||||
|
||||
auto *hint = new QLabel(tr("按住并滑过词语可以连续选择,选中后复制。"), this);
|
||||
hint->setStyleSheet(QStringLiteral("color: rgba(255,255,255,0.55); font-size: 12px;"));
|
||||
hint->setStyleSheet(QStringLiteral("font-size: 12px;"));
|
||||
layout->addWidget(hint);
|
||||
|
||||
auto *scroll = new QScrollArea(this);
|
||||
@@ -44,12 +50,20 @@ SegmentDialog::SegmentDialog(const QStringList &words, QWidget *parent)
|
||||
capsule->setCursor(Qt::PointingHandCursor);
|
||||
capsule->setFocusPolicy(Qt::NoFocus);
|
||||
capsule->setStyleSheet(QStringLiteral(
|
||||
"QPushButton { color: rgba(255,255,255,0.85); background: rgba(255,255,255,0.07);"
|
||||
" border: 1px solid rgba(255,255,255,0.10); border-radius: 11px;"
|
||||
" padding: 3px 10px; font-size: 13px; }"
|
||||
"QPushButton:hover { background: rgba(255,255,255,0.14); }"
|
||||
"QPushButton:checked { background: rgba(74,144,255,0.55);"
|
||||
" border-color: rgba(160,205,255,0.9); color: white; }"));
|
||||
"QPushButton { color: %1; background: %2; border: 1px solid %3;"
|
||||
" border-radius: 11px; padding: 3px 10px; font-size: 13px; }"
|
||||
"QPushButton:hover { background: %4; }"
|
||||
"QPushButton:checked { background: rgba(74,144,255,0.85);"
|
||||
" border-color: rgba(74,144,255,1.0); color: white; }")
|
||||
.arg(textColor.name(),
|
||||
QStringLiteral("rgba(%1,%2,%3,%4)")
|
||||
.arg(capsuleBg.red())
|
||||
.arg(capsuleBg.green())
|
||||
.arg(capsuleBg.blue())
|
||||
.arg(capsuleBg.alpha()),
|
||||
capsuleBorder.name(),
|
||||
dark ? QStringLiteral("rgba(255,255,255,30)")
|
||||
: QStringLiteral("rgba(0,0,0,28)")));
|
||||
capsule->installEventFilter(this);
|
||||
connect(capsule, &QPushButton::released, this, [this]() { m_selecting = false; });
|
||||
flow->addWidget(capsule);
|
||||
@@ -60,7 +74,7 @@ SegmentDialog::SegmentDialog(const QStringList &words, QWidget *parent)
|
||||
auto *all = new QPushButton(tr("全选"), this);
|
||||
auto *none = new QPushButton(tr("清空"), this);
|
||||
m_count = new QLabel(tr("已选 0 个"), this);
|
||||
m_count->setStyleSheet(QStringLiteral("color: rgba(255,255,255,0.55); font-size: 12px;"));
|
||||
m_count->setStyleSheet(QStringLiteral("font-size: 12px;"));
|
||||
auto *copy = new QPushButton(tr("复制选中"), this);
|
||||
copy->setStyleSheet(QStringLiteral(
|
||||
"QPushButton { color: white; background: rgba(74,144,255,0.85); border: none;"
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <QDialogButtonBox>
|
||||
#include <QGuiApplication>
|
||||
#include <QHBoxLayout>
|
||||
#include <QIcon>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QListView>
|
||||
@@ -145,6 +146,9 @@ TodoWindow::TodoWindow(TodoModel *model, QWidget *parent)
|
||||
, m_model(model)
|
||||
{
|
||||
setWindowTitle(tr("待办"));
|
||||
setWindowIcon(QIcon::fromTheme(
|
||||
QStringLiteral("gxde-ai-see-todo"),
|
||||
QIcon(QStringLiteral("/usr/share/icons/hicolor/256x256/apps/gxde-ai-see-todo.png"))));
|
||||
setWindowFlags(Qt::FramelessWindowHint | Qt::Tool);
|
||||
setAttribute(Qt::WA_TranslucentBackground);
|
||||
setAttribute(Qt::WA_ShowWithoutActivating);
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
// SPDX-FileCopyrightText: 2026 GXDE OS Team
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "aboutdialog.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QCoreApplication>
|
||||
#include <QIcon>
|
||||
|
||||
#include <DAboutDialog>
|
||||
|
||||
void showAboutDialog(QWidget *parent, const QString &description)
|
||||
{
|
||||
auto *dialog = new Dtk::Widget::DAboutDialog(parent);
|
||||
dialog->setAttribute(Qt::WA_DeleteOnClose);
|
||||
dialog->setWindowTitle(QObject::tr("关于小埃同学"));
|
||||
dialog->setProductIcon(QApplication::windowIcon());
|
||||
dialog->setProductName(QObject::tr("GXDE 小埃同学"));
|
||||
dialog->setVersion(QCoreApplication::applicationVersion());
|
||||
dialog->setDescription(description);
|
||||
dialog->setLicense(QStringLiteral("GPL-3.0-or-later"));
|
||||
dialog->setWebsiteName(QStringLiteral("gitee.com/GXDE-OS/gxde-ai-see"));
|
||||
dialog->setWebsiteLink(QStringLiteral("https://gitee.com/GXDE-OS/gxde-ai-see"));
|
||||
dialog->show();
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// SPDX-FileCopyrightText: 2026 GXDE OS Team
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
|
||||
class QWidget;
|
||||
|
||||
/** DTK native about dialog shared by the panel and the chat window. */
|
||||
void showAboutDialog(QWidget *parent, const QString &description);
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "factory/contentclassifier.h"
|
||||
#include "factory/contentfactory.h"
|
||||
|
||||
#include "aboutdialog.h"
|
||||
#include "dialogs/segmentdialog.h"
|
||||
#include "dialogs/speechdialog.h"
|
||||
#include "todo/todomodel.h"
|
||||
@@ -580,7 +581,7 @@ public:
|
||||
setMinimumHeight(190);
|
||||
|
||||
m_portalTimer.setSingleShot(true);
|
||||
m_portalTimer.setInterval(150);
|
||||
m_portalTimer.setInterval(80);
|
||||
connect(&m_portalTimer, &QTimer::timeout, this, [this]() {
|
||||
if (m_active && !m_suppressed) {
|
||||
Q_EMIT portalRequested();
|
||||
@@ -1203,6 +1204,7 @@ void PortalPanel::runFactoryAction(const QString &id, const DropContent &content
|
||||
: content.summary();
|
||||
TodoModel::instance()->add(text);
|
||||
toast(tr("已添加到待办"));
|
||||
Q_EMIT todoAdded();
|
||||
return;
|
||||
}
|
||||
if (id == QLatin1String("save_image")) {
|
||||
@@ -1401,6 +1403,7 @@ void PortalPanel::showContextMenu(const QPoint &globalPos)
|
||||
|
||||
menu.addSeparator();
|
||||
QAction *clear = menu.addAction(tr("清空暂存区"));
|
||||
QAction *hide = menu.addAction(tr("隐藏"));
|
||||
QAction *about = menu.addAction(tr("关于"));
|
||||
QAction *quit = menu.addAction(tr("退出"));
|
||||
|
||||
@@ -1420,11 +1423,22 @@ void PortalPanel::showContextMenu(const QPoint &globalPos)
|
||||
menuPos.setX(available.right() - menuSize.width());
|
||||
}
|
||||
}
|
||||
menuPos.setX(globalPos.x() - menuSize.width() - 10);
|
||||
if (scr) {
|
||||
const QRect available = scr->availableGeometry();
|
||||
if (menuPos.x() < available.left()) {
|
||||
menuPos.setX(available.left());
|
||||
}
|
||||
}
|
||||
QAction *chosen = menu.exec(menuPos);
|
||||
if (!chosen) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (chosen == hide) {
|
||||
Q_EMIT hideRequested();
|
||||
return;
|
||||
}
|
||||
if (chosen == fixedPosition) {
|
||||
m_fixedPosition = fixedPosition->isChecked();
|
||||
saveWindowState();
|
||||
@@ -1469,13 +1483,7 @@ void PortalPanel::showContextMenu(const QPoint &globalPos)
|
||||
} else if (chosen == clear) {
|
||||
m_model->clearAll();
|
||||
} else if (chosen == about) {
|
||||
QMessageBox::about(
|
||||
this, tr("关于小埃同学"),
|
||||
tr("<h3>GXDE 小埃同学</h3><p>版本 %1</p>"
|
||||
"<p>传送门、内容工厂、待办与小埃同学对话。</p>"
|
||||
"<p>GPL-3.0-or-later · "
|
||||
"<a href='https://gitee.com/GXDE-OS/gxde-ai-see'>gxde-ai-see</a></p>")
|
||||
.arg(QCoreApplication::applicationVersion()));
|
||||
showAboutDialog(nullptr, tr("传送门、内容工厂、待办与小埃同学对话。"));
|
||||
} else if (chosen == quit) {
|
||||
QCoreApplication::quit();
|
||||
}
|
||||
|
||||
@@ -41,6 +41,8 @@ public:
|
||||
signals:
|
||||
void openChatRequested();
|
||||
void askWithContent(const QString &text, const QStringList &files);
|
||||
void hideRequested();
|
||||
void todoAdded();
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent *event) override;
|
||||
|
||||