diff --git a/CMakeLists.txt b/CMakeLists.txt index 16ac4bb..7e76d47 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/data/gxde-ai-see-portal.desktop b/data/gxde-ai-see-portal.desktop new file mode 100644 index 0000000..b71cc14 --- /dev/null +++ b/data/gxde-ai-see-portal.desktop @@ -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 diff --git a/data/gxde-ai-see-todo.desktop b/data/gxde-ai-see-todo.desktop index 1aaed5d..848bd69 100644 --- a/data/gxde-ai-see-todo.desktop +++ b/data/gxde-ai-see-todo.desktop @@ -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 diff --git a/data/icons-src/gxde-ai-see-todo.svg b/data/icons-src/gxde-ai-see-todo.svg new file mode 100644 index 0000000..76b3109 --- /dev/null +++ b/data/icons-src/gxde-ai-see-todo.svg @@ -0,0 +1,17 @@ + diff --git a/data/icons/hicolor/128x128/apps/gxde-ai-see-todo.png b/data/icons/hicolor/128x128/apps/gxde-ai-see-todo.png new file mode 100644 index 0000000..0f15252 Binary files /dev/null and b/data/icons/hicolor/128x128/apps/gxde-ai-see-todo.png differ diff --git a/data/icons/hicolor/16x16/apps/gxde-ai-see-todo.png b/data/icons/hicolor/16x16/apps/gxde-ai-see-todo.png new file mode 100644 index 0000000..a93a2c3 Binary files /dev/null and b/data/icons/hicolor/16x16/apps/gxde-ai-see-todo.png differ diff --git a/data/icons/hicolor/24x24/apps/gxde-ai-see-todo.png b/data/icons/hicolor/24x24/apps/gxde-ai-see-todo.png new file mode 100644 index 0000000..af1a8bc Binary files /dev/null and b/data/icons/hicolor/24x24/apps/gxde-ai-see-todo.png differ diff --git a/data/icons/hicolor/256x256/apps/gxde-ai-see-todo.png b/data/icons/hicolor/256x256/apps/gxde-ai-see-todo.png new file mode 100644 index 0000000..db14033 Binary files /dev/null and b/data/icons/hicolor/256x256/apps/gxde-ai-see-todo.png differ diff --git a/data/icons/hicolor/32x32/apps/gxde-ai-see-todo.png b/data/icons/hicolor/32x32/apps/gxde-ai-see-todo.png new file mode 100644 index 0000000..2dc71be Binary files /dev/null and b/data/icons/hicolor/32x32/apps/gxde-ai-see-todo.png differ diff --git a/data/icons/hicolor/48x48/apps/gxde-ai-see-todo.png b/data/icons/hicolor/48x48/apps/gxde-ai-see-todo.png new file mode 100644 index 0000000..053140a Binary files /dev/null and b/data/icons/hicolor/48x48/apps/gxde-ai-see-todo.png differ diff --git a/data/icons/hicolor/64x64/apps/gxde-ai-see-todo.png b/data/icons/hicolor/64x64/apps/gxde-ai-see-todo.png new file mode 100644 index 0000000..02d4bc3 Binary files /dev/null and b/data/icons/hicolor/64x64/apps/gxde-ai-see-todo.png differ diff --git a/src/app.cpp b/src/app.cpp index d8bc94b..b03aaf9 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -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) { diff --git a/src/app.h b/src/app.h index faa3295..c7fc553 100644 --- a/src/app.h +++ b/src/app.h @@ -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; }; diff --git a/src/chat/chatview.cpp b/src/chat/chatview.cpp index 3cef516..03d857d 100644 --- a/src/chat/chatview.cpp +++ b/src/chat/chatview.cpp @@ -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; diff --git a/src/chat/chatview.h b/src/chat/chatview.h index 967801f..616829f 100644 --- a/src/chat/chatview.h +++ b/src/chat/chatview.h @@ -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: diff --git a/src/chat/chatwindow.cpp b/src/chat/chatwindow.cpp index 9f1831d..59bff17 100644 --- a/src/chat/chatwindow.cpp +++ b/src/chat/chatwindow.cpp @@ -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("
版本 %1
" - "GXDE 桌面的 AI 助手:传送门、内容工厂、待办与对话。
" - "许可证:GPL-3.0-or-later
"
- "仓库:"
- "gitee.com/GXDE-OS/gxde-ai-see
版本 %1
" - "传送门、内容工厂、待办与小埃同学对话。
" - "GPL-3.0-or-later · " - "gxde-ai-see
") - .arg(QCoreApplication::applicationVersion())); + showAboutDialog(nullptr, tr("传送门、内容工厂、待办与小埃同学对话。")); } else if (chosen == quit) { QCoreApplication::quit(); } diff --git a/src/ui/portalpanel.h b/src/ui/portalpanel.h index dabcd53..37e7edd 100644 --- a/src/ui/portalpanel.h +++ b/src/ui/portalpanel.h @@ -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;