chore(release): 1.0.0~beta2
Building GXDE Arch Package / detect (push) Skipped
Building GXDE Arch Package / aarch64-arch (push) Skipped
Building GXDE Arch Package / x86_64-arch (push) Skipped
Building GXDE Testing Package / call-arch (push) Skipped
Building Program / call (push) Skipped
Building GXDE Testing Package / call-zhuangzhuang (push) Skipped
Building GXDE Testing Package (zhuangzhuang) / call-i386-zhuangzhuang (push) Skipped
Building GXDE Testing Package (zhuangzhuang) / call-amd64-zhuangzhuang (push) Skipped
Building GXDE Testing Package (zhuangzhuang) / call-arm64-zhuangzhuang (push) Skipped
Building GXDE Testing Package (zhuangzhuang) / call-riscv64-zhuangzhuang (push) Skipped
Building GXDE Testing Package (zhuangzhuang) / call-loong64-zhuangzhuang (push) Skipped
Building GXDE Package / ${{ inputs.display-name || 'Build Job' }} (push) Skipped

- 会话列表拖拽排序/多选/批量删除
- 128k/1024k token 上下文上限
- 待办取消置顶;全屏隐藏联动
- 英文回退翻译补齐;新应用图标
- DTK 关于窗口、分词/朗读对话框、消息常驻操作
This commit is contained in:
shenmo
2026-10-30 22:37:05 +08:00
parent c8a0391abe
commit 4291ee286c
19 changed files with 677 additions and 216 deletions
+9
View File
@@ -1,5 +1,14 @@
# 更新日志 # 更新日志
## 1.0.0~beta2 — 2026-10-30
- 会话列表:拖拽排序、多选、批量删除
- 上下文压缩支持 128k / 1024k token 上限
- 待办取消置顶(普通窗口)
- 全屏时隐藏联动合成器信号
- 非中文环境回退英文;英文翻译补齐
- 新应用图标、DTK 关于窗口
## 1.0.0~beta — 2026-10-30 ## 1.0.0~beta — 2026-10-30
首个 beta 版本。 首个 beta 版本。
Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 17 KiB

+12
View File
@@ -1,3 +1,15 @@
gxde-ai-see (1.0.0~beta2) unstable; urgency=medium
* 会话列表支持拖拽排序、多选、批量删除
* 上下文压缩支持 128k / 1024k token 上限
* 待办窗口可取消置顶(普通窗口)
* 全屏时隐藏:合成器 FullscreenChanged 信号联动
* 非中文环境回退英文翻译;补齐全部英文翻译
* 全新应用图标(清理背景、圆角渐变底)
* DTK 关于窗口;分词/朗读对话框;消息下方常驻操作
-- shenmo <shenmo@spark-app.store> Fri, 30 Oct 2026 23:30:00 +0800
gxde-ai-see (1.0.0~beta) unstable; urgency=medium gxde-ai-see (1.0.0~beta) unstable; urgency=medium
* 首个 beta:传送门原生 drop、暂存区、内容工厂、待办、小埃同学对话 * 首个 beta:传送门原生 drop、暂存区、内容工厂、待办、小埃同学对话
+11 -32
View File
@@ -1,35 +1,14 @@
# 1.0.0~beta 之后待办 # 1.0.0~beta2 之后待办
按优先级排列,部分项需要单独一轮实现与测试。 已完成:会话列表拖拽排序/多选/批量删除(可复用控件已加入 dtk2widget-qt6)、
token 压缩上限、待办取消置顶、全屏隐藏联动、英文回退翻译、新图标、DTK 关于窗口。
## 对话 ## 仍需单独排期
- [ ] 会话列表支持拖拽排序、右键删除、批量多选;先在本仓库实现,随后抽成 - [ ] 对话浅色/深色主题跟随 DTK(当前仍是深色固定配色)
`dtk2widget-qt6` 的可复用列表控件(避免每个项目重写) - [ ] 消息版本树:重试/编辑保留历史并可左右切换
- [ ] 浅色/深色主题跟随 DTK,默认浅色 - [ ] OCR / TTS / 翻译对接 GXDE Daemon(当前仓库未找到对应 D-Bus 接口,需先定义)
- [ ] 消息版本树:重试/编辑保留历史,可在旧版本之间左右切换 - [ ] X11 / 非 GXDE 合成器窗口图标网格降级方案
- [ ] 每条消息下方常驻 重试 / 编辑 / 复制(已做基础版,版本树未做) - [ ] DTK Wayland 文本拖拽预览(合成器侧渲染,工作量大)
- [ ] 更多窗口改用 DTK 控件(聊天设置、待办窗口等)
## 国际化 - [ ] dtk2widget-qt6 的 DReorderListWidget 需要随库发布后,应用改为链接该控件
- [ ] 源码中的 `tr()` 文案改为英文,中文放进 `zh_CN` 翻译;保证非中文环境
显示英文而不是中文
- [ ] 更新 `translations/*.ts` 并补齐中文翻译
## 系统能力
- [ ] OCR / TTS / 翻译对接 GXDE Daemon 的接口(当前为占位)
- [ ] 全屏时隐藏:合成器侧判断全屏窗口,面板自动隐藏
- [ ] 待办窗口「取消置顶」:可切换为普通窗口
- [ ] 上下文压缩支持按 token(128k / 1024k)而不是只按条数
## 兼容性
- [ ] X11 / 非 GXDE 合成器的降级方案:传送门旁展示窗口图标网格,
悬停最小化其他窗口并激活目标,松手完成 drop
- [ ] DTK Wayland 应用拖拽文本时合成器侧生成文本预览(当前无 drag icon)
- [ ] 更多窗口改用 DTK 控件与原生样式
## 图标
- [ ] 清理小埃同学 logo 背景并重新生成应用图标,改善小尺寸清晰度
+22
View File
@@ -117,6 +117,20 @@ bool App::init(bool daemon, bool forceShow, bool showTodo, bool showChat)
} }
}); });
connect(portal, &PortalClient::fullscreenChanged, this, [this](bool fullscreen) {
if (!Config::instance()->getBool(QStringLiteral("panel/hideOnFullscreen"), false)) {
return;
}
if (fullscreen) {
if (m_panel && m_panel->isVisible()) {
m_panel->dismiss();
}
} else if (m_panelPinned || !m_model->isEmpty()) {
ensurePanel();
m_panel->popup();
}
});
connect(m_model, &StagingModel::countChanged, this, [this]() { connect(m_model, &StagingModel::countChanged, this, [this]() {
if (!m_model->isEmpty() && m_panel) { if (!m_model->isEmpty() && m_panel) {
m_panel->popup(); m_panel->popup();
@@ -194,6 +208,14 @@ void App::showTodoWindow()
{ {
if (!m_todoWindow) { if (!m_todoWindow) {
m_todoWindow = new TodoWindow(TodoModel::instance()); m_todoWindow = new TodoWindow(TodoModel::instance());
connect(m_todoWindow, &TodoWindow::recreateRequested, this, [this]() {
if (m_todoWindow) {
m_todoWindow->close();
m_todoWindow->deleteLater();
m_todoWindow = nullptr;
}
showTodoWindow();
});
} }
m_todoWindow->popup(); m_todoWindow->popup();
} }
+165 -4
View File
@@ -75,11 +75,20 @@ public:
: QListWidget(parent) : QListWidget(parent)
{ {
setAcceptDrops(true); setAcceptDrops(true);
setDragEnabled(true);
setDragDropMode(QAbstractItemView::DragDrop);
setDefaultDropAction(Qt::MoveAction);
setDropIndicatorShown(true);
setDragDropOverwriteMode(false);
setSelectionMode(QAbstractItemView::ExtendedSelection);
} }
signals: signals:
void droppedAt(int index, const QMimeData *mime); void droppedAt(int index, const QMimeData *mime);
void left(); void left();
void reordered();
void deleteRequested(const QList<int> &rows);
void renameRequested(int row);
protected: protected:
void leaveEvent(QEvent *event) override void leaveEvent(QEvent *event) override
@@ -88,10 +97,9 @@ protected:
QListWidget::leaveEvent(event); QListWidget::leaveEvent(event);
} }
protected:
void dragEnterEvent(QDragEnterEvent *event) override void dragEnterEvent(QDragEnterEvent *event) override
{ {
if (mimeOk(event->mimeData())) { if (event->source() == this || mimeOk(event->mimeData())) {
event->acceptProposedAction(); event->acceptProposedAction();
} else { } else {
event->ignore(); event->ignore();
@@ -100,13 +108,27 @@ protected:
void dragMoveEvent(QDragMoveEvent *event) override void dragMoveEvent(QDragMoveEvent *event) override
{ {
if (mimeOk(event->mimeData())) { if (event->source() == this || mimeOk(event->mimeData())) {
event->acceptProposedAction(); event->acceptProposedAction();
} }
} }
void dropEvent(QDropEvent *event) override void dropEvent(QDropEvent *event) override
{ {
if (event->source() == this) {
const QModelIndex dropIndex = indexAt(event->position().toPoint());
const int to = dropIndex.isValid() ? dropIndex.row() : count();
const int from = currentRow();
if (from >= 0 && to >= 0 && to != from && to != from + 1) {
QListWidgetItem *item = takeItem(from);
const int target = to > from ? to - 1 : to;
insertItem(target, item);
setCurrentRow(target);
Q_EMIT reordered();
}
event->acceptProposedAction();
return;
}
if (!mimeOk(event->mimeData())) { if (!mimeOk(event->mimeData())) {
event->ignore(); event->ignore();
return; return;
@@ -115,6 +137,37 @@ protected:
Q_EMIT droppedAt(index.isValid() ? index.row() : -1, event->mimeData()); Q_EMIT droppedAt(index.isValid() ? index.row() : -1, event->mimeData());
event->acceptProposedAction(); event->acceptProposedAction();
} }
void contextMenuEvent(QContextMenuEvent *event) override
{
const QModelIndex index = indexAt(event->pos());
QMenu menu(this);
QAction *rename = nullptr;
if (index.isValid()) {
rename = menu.addAction(tr("重命名对话"));
}
QAction *remove = menu.addAction(tr("删除选中"));
QAction *chosen = menu.exec(event->globalPos());
if (!chosen) {
return;
}
if (chosen == rename) {
Q_EMIT renameRequested(index.row());
return;
}
if (chosen == remove) {
QList<int> rows;
for (const QModelIndex &selected : selectionModel()->selectedRows()) {
rows.append(selected.row());
}
if (rows.isEmpty() && index.isValid()) {
rows.append(index.row());
}
if (!rows.isEmpty()) {
Q_EMIT deleteRequested(rows);
}
}
}
}; };
class ChatView : public QTextBrowser class ChatView : public QTextBrowser
@@ -546,6 +599,12 @@ void ChatWindow::setupUi()
connect(m_sidebar, &QListWidget::currentRowChanged, this, &ChatWindow::selectConversation); connect(m_sidebar, &QListWidget::currentRowChanged, this, &ChatWindow::selectConversation);
connect(static_cast<ChatSidebar *>(m_sidebar), &ChatSidebar::droppedAt, this, connect(static_cast<ChatSidebar *>(m_sidebar), &ChatSidebar::droppedAt, this,
&ChatWindow::handleDropOnConversation); &ChatWindow::handleDropOnConversation);
connect(static_cast<ChatSidebar *>(m_sidebar), &ChatSidebar::reordered, this,
&ChatWindow::reorderConversations);
connect(static_cast<ChatSidebar *>(m_sidebar), &ChatSidebar::deleteRequested, this,
&ChatWindow::deleteConversations);
connect(static_cast<ChatSidebar *>(m_sidebar), &ChatSidebar::renameRequested, this,
&ChatWindow::renameConversation);
connect(m_messages, &ChatMessageList::dropped, this, &ChatWindow::handleDroppedMime); connect(m_messages, &ChatMessageList::dropped, this, &ChatWindow::handleDroppedMime);
connect(m_messages, &ChatMessageList::linkActivated, this, &ChatWindow::handleAnchor); connect(m_messages, &ChatMessageList::linkActivated, this, &ChatWindow::handleAnchor);
connect(m_messages, &ChatMessageList::copyRequested, this, [this](int index) { connect(m_messages, &ChatMessageList::copyRequested, this, [this](int index) {
@@ -681,12 +740,83 @@ void ChatWindow::selectConversation(int index)
renderConversation(); renderConversation();
} }
void ChatWindow::reorderConversations()
{
if (m_conversations.isEmpty()) {
return;
}
QHash<QString, ChatConversation> byId;
for (const ChatConversation &conversation : m_conversations) {
byId.insert(conversation.id, conversation);
}
QVector<ChatConversation> ordered;
for (int i = 0; i < m_sidebar->count(); ++i) {
const QString id = m_sidebar->item(i)->data(Qt::UserRole).toString();
auto it = byId.find(id);
if (it != byId.end()) {
ordered.append(it.value());
byId.erase(it);
}
}
for (auto it = byId.begin(); it != byId.end(); ++it) {
ordered.append(it.value());
}
m_conversations = ordered;
const int row = m_sidebar->currentRow();
if (row >= 0 && row < m_conversations.size()) {
m_current = row;
}
save();
updateSidebar();
}
void ChatWindow::deleteConversations(const QList<int> &rows)
{
if (rows.isEmpty()) {
return;
}
if (m_client->busy()) {
m_client->cancel();
m_streamingIndex = -1;
}
QList<int> sorted = rows;
std::sort(sorted.begin(), sorted.end(), std::greater<int>());
for (int row : sorted) {
if (row >= 0 && row < m_conversations.size()) {
m_conversations.removeAt(row);
}
}
m_current = m_conversations.isEmpty() ? -1
: qBound(0, m_current, m_conversations.size() - 1);
save();
updateSidebar();
renderConversation();
}
void ChatWindow::renameConversation(int row)
{
if (row < 0 || row >= m_conversations.size()) {
return;
}
bool ok = false;
const QString title = QInputDialog::getText(nullptr, tr("重命名对话"), tr("名称"),
QLineEdit::Normal,
m_conversations.at(row).title, &ok);
if (ok && !title.trimmed().isEmpty()) {
m_conversations[row].title = title.trimmed();
save();
updateSidebar();
renderConversation();
}
}
void ChatWindow::updateSidebar() void ChatWindow::updateSidebar()
{ {
const QSignalBlocker blocker(m_sidebar); const QSignalBlocker blocker(m_sidebar);
m_sidebar->clear(); m_sidebar->clear();
for (const ChatConversation &conversation : m_conversations) { for (const ChatConversation &conversation : m_conversations) {
auto *item = new QListWidgetItem(conversation.title, m_sidebar); auto *item = new QListWidgetItem(conversation.title, m_sidebar);
item->setData(Qt::UserRole, conversation.id);
QString preview = tr("新对话"); QString preview = tr("新对话");
for (int i = conversation.messages.size() - 1; i >= 0; --i) { for (int i = conversation.messages.size() - 1; i >= 0; --i) {
const ChatMessage &message = conversation.messages.at(i); const ChatMessage &message = conversation.messages.at(i);
@@ -874,8 +1004,26 @@ bool ChatWindow::needsCompression() const
if (!Config::instance()->getBool(QStringLiteral("chat/autoCompress"), true)) { if (!Config::instance()->getBool(QStringLiteral("chat/autoCompress"), true)) {
return false; return false;
} }
const QVector<ChatMessage> &messages = m_conversations.at(m_current).messages;
const int keep = Config::instance()->getInt(QStringLiteral("chat/contextLength"), 40); const int keep = Config::instance()->getInt(QStringLiteral("chat/contextLength"), 40);
return m_conversations.at(m_current).messages.size() > keep + 4; if (messages.size() > keep + 4) {
return true;
}
/* also honour a token budget (128k / 1024k); a rough estimate is enough */
const qint64 tokenLimit =
Config::instance()->getInt(QStringLiteral("chat/contextTokens"), 128000);
if (tokenLimit <= 0) {
return false;
}
qint64 tokens = 0;
for (const ChatMessage &message : messages) {
for (const QChar &ch : message.content) {
tokens += ch.unicode() < 128 ? 1 : 2;
}
}
return tokens / 4 > tokenLimit;
} }
void ChatWindow::beginCompression(bool thenSend) void ChatWindow::beginCompression(bool thenSend)
@@ -1153,6 +1301,17 @@ void ChatWindow::openSettings()
Config::instance()->getInt(QStringLiteral("chat/contextLength"), 40)); Config::instance()->getInt(QStringLiteral("chat/contextLength"), 40));
form->addRow(tr("上下文条数"), contextSpin); form->addRow(tr("上下文条数"), contextSpin);
auto *tokenBox = new QComboBox(&dialog);
tokenBox->addItem(tr("128k"), 128000);
tokenBox->addItem(tr("1024k"), 1024000);
const int storedTokens =
Config::instance()->getInt(QStringLiteral("chat/contextTokens"), 128000);
const int tokenIndex = tokenBox->findData(storedTokens);
if (tokenIndex >= 0) {
tokenBox->setCurrentIndex(tokenIndex);
}
form->addRow(tr("上下文长度"), tokenBox);
auto *autoCompressBox = new QCheckBox(tr("超出后自动压缩旧消息"), &dialog); auto *autoCompressBox = new QCheckBox(tr("超出后自动压缩旧消息"), &dialog);
autoCompressBox->setChecked( autoCompressBox->setChecked(
Config::instance()->getBool(QStringLiteral("chat/autoCompress"), true)); Config::instance()->getBool(QStringLiteral("chat/autoCompress"), true));
@@ -1201,6 +1360,8 @@ void ChatWindow::openSettings()
Config::instance()->set(QStringLiteral("chat/contextLength"), contextSpin->value()); Config::instance()->set(QStringLiteral("chat/contextLength"), contextSpin->value());
Config::instance()->set(QStringLiteral("chat/autoCompress"), Config::instance()->set(QStringLiteral("chat/autoCompress"),
autoCompressBox->isChecked()); autoCompressBox->isChecked());
Config::instance()->set(QStringLiteral("chat/contextTokens"),
tokenBox->currentData().toInt());
const QJsonDocument mcpDocument = const QJsonDocument mcpDocument =
QJsonDocument::fromJson(mcpEdit->toPlainText().toUtf8()); QJsonDocument::fromJson(mcpEdit->toPlainText().toUtf8());
if (mcpDocument.isArray() || mcpEdit->toPlainText().trimmed().isEmpty()) { if (mcpDocument.isArray() || mcpEdit->toPlainText().trimmed().isEmpty()) {
+3
View File
@@ -62,6 +62,9 @@ private:
void renderConversation(); void renderConversation();
void updateHeader(); void updateHeader();
void updateSidebar(); void updateSidebar();
void reorderConversations();
void deleteConversations(const QList<int> &rows);
void renameConversation(int row);
void sendMessage(); void sendMessage();
void requestCompletion(); void requestCompletion();
void runToolCalls(const QVector<ToolCall> &calls, int index, const QJsonArray &results); void runToolCalls(const QVector<ToolCall> &calls, int index, const QJsonArray &results);
+16 -8
View File
@@ -19,7 +19,7 @@ int main(int argc, char *argv[])
app.setOrganizationName(QStringLiteral("GXDE")); app.setOrganizationName(QStringLiteral("GXDE"));
app.setApplicationName(QStringLiteral("gxde-ai-see")); app.setApplicationName(QStringLiteral("gxde-ai-see"));
app.setApplicationDisplayName(QStringLiteral("GXDE 小埃同学")); app.setApplicationDisplayName(QStringLiteral("GXDE 小埃同学"));
app.setApplicationVersion(QStringLiteral("1.0.0~beta")); app.setApplicationVersion(QStringLiteral("1.0.0~beta2"));
QIcon appIcon = QIcon::fromTheme(QStringLiteral("gxde-ai-see")); QIcon appIcon = QIcon::fromTheme(QStringLiteral("gxde-ai-see"));
if (appIcon.isNull()) { if (appIcon.isNull()) {
appIcon = QIcon(QStringLiteral("/usr/share/icons/hicolor/256x256/apps/gxde-ai-see.png")); appIcon = QIcon(QStringLiteral("/usr/share/icons/hicolor/256x256/apps/gxde-ai-see.png"));
@@ -35,17 +35,25 @@ int main(int argc, char *argv[])
QStringLiteral("/usr/share/gxde-ai-see/translations"), QStringLiteral("/usr/share/gxde-ai-see/translations"),
QStringLiteral("/usr/local/share/gxde-ai-see/translations"), QStringLiteral("/usr/local/share/gxde-ai-see/translations"),
}; };
bool translated = false; QStringList candidates{ locale, locale.section(QLatin1Char('_'), 0, 0) };
/* ensure unsupported locales fall back to English, never to the Chinese
* source strings */
if (!locale.startsWith(QLatin1String("zh"))) {
candidates.append(QStringLiteral("en"));
}
for (const QString &dir : translationDirs) { for (const QString &dir : translationDirs) {
if (translator->load(QStringLiteral("gxde-ai-see_") + locale, dir) || bool loaded = false;
translator->load(QStringLiteral("gxde-ai-see_") + locale.section(QLatin1Char('_'), 0, 0), for (const QString &candidate : candidates) {
dir)) { if (translator->load(QStringLiteral("gxde-ai-see_") + candidate, dir)) {
app.installTranslator(translator); app.installTranslator(translator);
translated = true; loaded = true;
break;
}
}
if (loaded) {
break; break;
} }
} }
Q_UNUSED(translated)
QCommandLineParser parser; QCommandLineParser parser;
parser.setApplicationDescription(QStringLiteral("GXDE 小埃同学")); parser.setApplicationDescription(QStringLiteral("GXDE 小埃同学"));
+8
View File
@@ -53,6 +53,9 @@ PortalClient::PortalClient(QObject *parent)
bus.connect(QString::fromLatin1(kService), QString::fromLatin1(kPath), bus.connect(QString::fromLatin1(kService), QString::fromLatin1(kPath),
QString::fromLatin1(kInterface), QStringLiteral("PortalClosed"), this, QString::fromLatin1(kInterface), QStringLiteral("PortalClosed"), this,
SLOT(onPortalClosed())); SLOT(onPortalClosed()));
bus.connect(QString::fromLatin1(kService), QString::fromLatin1(kPath),
QString::fromLatin1(kInterface), QStringLiteral("FullscreenChanged"), this,
SLOT(onFullscreenChanged(bool)));
} }
bool PortalClient::beginPortal(const QRect &panelRect) bool PortalClient::beginPortal(const QRect &panelRect)
@@ -96,6 +99,11 @@ void PortalClient::onDragStarted(const QString &mimeTypes)
Q_EMIT dragStarted(mimeTypes); Q_EMIT dragStarted(mimeTypes);
} }
void PortalClient::onFullscreenChanged(bool fullscreen)
{
Q_EMIT fullscreenChanged(fullscreen);
}
void PortalClient::onPortalClosed() void PortalClient::onPortalClosed()
{ {
Q_EMIT portalClosed(); Q_EMIT portalClosed();
+2
View File
@@ -34,12 +34,14 @@ signals:
void dragStarted(const QString &mimeTypes); void dragStarted(const QString &mimeTypes);
void dragEnded(); void dragEnded();
void portalClosed(); void portalClosed();
void fullscreenChanged(bool fullscreen);
void dropped(const QString &uuid, bool accepted); void dropped(const QString &uuid, bool accepted);
private slots: private slots:
void onDragStarted(const QString &mimeTypes); void onDragStarted(const QString &mimeTypes);
void onDragEnded(); void onDragEnded();
void onPortalClosed(); void onPortalClosed();
void onFullscreenChanged(bool fullscreen);
void onDropped(const QString &uuid, bool accepted); void onDropped(const QString &uuid, bool accepted);
private: private:
+39
View File
@@ -7,6 +7,9 @@
#include "config.h" #include "config.h"
#include <QContextMenuEvent>
#include <QMenu>
#include <QApplication> #include <QApplication>
#include <QDialog> #include <QDialog>
#include <QDialogButtonBox> #include <QDialogButtonBox>
@@ -159,6 +162,7 @@ TodoWindow::TodoWindow(TodoModel *model, QWidget *parent)
m_rightMargin = Config::instance()->getInt(QStringLiteral("todo/rightMargin"), 16); m_rightMargin = Config::instance()->getInt(QStringLiteral("todo/rightMargin"), 16);
m_topMargin = Config::instance()->getInt(QStringLiteral("todo/topMargin"), 72); m_topMargin = Config::instance()->getInt(QStringLiteral("todo/topMargin"), 72);
m_keepAbove = Config::instance()->getBool(QStringLiteral("todo/keepAbove"), true);
setupWindow(); setupWindow();
setupUi(); setupUi();
@@ -172,6 +176,11 @@ TodoWindow::TodoWindow(TodoModel *model, QWidget *parent)
void TodoWindow::setupWindow() void TodoWindow::setupWindow()
{ {
if (!m_keepAbove) {
/* plain toplevel: shows in the taskbar and can be covered/moved */
setWindowFlag(Qt::Tool, false);
return;
}
#ifdef GXDE_HAVE_LAYERSHELLQT #ifdef GXDE_HAVE_LAYERSHELLQT
if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"))) { if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"))) {
createWinId(); createWinId();
@@ -308,6 +317,9 @@ void TodoWindow::addEntry()
void TodoWindow::applyMargins() void TodoWindow::applyMargins()
{ {
if (!m_keepAbove) {
return;
}
#ifdef GXDE_HAVE_LAYERSHELLQT #ifdef GXDE_HAVE_LAYERSHELLQT
if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"))) { if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"))) {
if (auto *layer = LayerShellQt::Window::get(windowHandle())) { if (auto *layer = LayerShellQt::Window::get(windowHandle())) {
@@ -322,6 +334,26 @@ void TodoWindow::applyMargins()
} }
} }
void TodoWindow::contextMenuEvent(QContextMenuEvent *event)
{
QMenu menu(this);
QAction *pin = menu.addAction(tr("置顶"));
pin->setCheckable(true);
pin->setChecked(m_keepAbove);
QAction *hideAction = menu.addAction(tr("隐藏"));
QAction *chosen = menu.exec(event->globalPos());
if (chosen == hideAction) {
hide();
return;
}
if (chosen == pin) {
const bool keep = pin->isChecked();
Config::instance()->set(QStringLiteral("todo/keepAbove"), keep);
Config::instance()->sync();
Q_EMIT recreateRequested();
}
}
void TodoWindow::saveMargins() void TodoWindow::saveMargins()
{ {
Config::instance()->set(QStringLiteral("todo/rightMargin"), m_rightMargin); Config::instance()->set(QStringLiteral("todo/rightMargin"), m_rightMargin);
@@ -332,6 +364,13 @@ void TodoWindow::saveMargins()
void TodoWindow::mousePressEvent(QMouseEvent *event) void TodoWindow::mousePressEvent(QMouseEvent *event)
{ {
if (event->button() == Qt::LeftButton && event->position().y() < 48) { if (event->button() == Qt::LeftButton && event->position().y() < 48) {
if (!m_keepAbove) {
if (windowHandle()) {
windowHandle()->startSystemMove();
}
event->accept();
return;
}
m_dragging = true; m_dragging = true;
m_dragStart = event->position().toPoint(); m_dragStart = event->position().toPoint();
m_dragStartRight = m_rightMargin; m_dragStartRight = m_rightMargin;
+5
View File
@@ -28,11 +28,15 @@ public:
void refreshLayout(); void refreshLayout();
protected: protected:
void contextMenuEvent(QContextMenuEvent *event) override;
void showEvent(QShowEvent *event) override; void showEvent(QShowEvent *event) override;
void mousePressEvent(QMouseEvent *event) override; void mousePressEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override;
signals:
void recreateRequested();
private: private:
void setupWindow(); void setupWindow();
void setupUi(); void setupUi();
@@ -45,6 +49,7 @@ private:
QLabel *m_countLabel = nullptr; QLabel *m_countLabel = nullptr;
int m_rightMargin = 16; int m_rightMargin = 16;
int m_topMargin = 72; int m_topMargin = 72;
bool m_keepAbove = true;
bool m_dragging = false; bool m_dragging = false;
QPoint m_dragStart; QPoint m_dragStart;
int m_dragStartRight = 0; int m_dragStartRight = 0;
+385 -172
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS> <!DOCTYPE TS>
<TS version="2.1"> <TS version="2.1" language="en">
<context> <context>
<name>App</name> <name>App</name>
<message> <message>
@@ -14,6 +14,21 @@
<translation>This desktop does not support the portal (a GXDE Wayland compositor is required). AI See will stay idle in the background; staging, the content factory and chat remain available.</translation> <translation>This desktop does not support the portal (a GXDE Wayland compositor is required). AI See will stay idle in the background; staging, the content factory and chat remain available.</translation>
</message> </message>
</context> </context>
<context>
<name>AskTile</name>
<message>
<location filename="../src/factory/contentfactory.cpp" line="136"/>
<source>问问
小埃同学</source>
<translation>Ask
AI See</translation>
</message>
<message>
<location filename="../src/factory/contentfactory.cpp" line="137"/>
<source>把内容放到这里:新开一个对话并填入内容</source>
<translation>Drop here to start a new conversation with this content</translation>
</message>
</context>
<context> <context>
<name>ChatClient</name> <name>ChatClient</name>
<message> <message>
@@ -32,168 +47,224 @@
<translation>The API key is invalid or expired</translation> <translation>The API key is invalid or expired</translation>
</message> </message>
</context> </context>
<context>
<name>ChatMessageList</name>
<message>
<location filename="../src/chat/chatview.cpp" line="263"/>
<source>埃</source>
<translation>AI</translation>
</message>
<message>
<location filename="../src/chat/chatview.cpp" line="271"/>
<source>你好,我是小埃同学</source>
<translation>Hi, I&apos;m AI See</translation>
</message>
<message>
<location filename="../src/chat/chatview.cpp" line="280"/>
<source>把文件、链接或文字拖进窗口,或者直接输入问题。
第一次使用请在左下角「设置」里填入 DeepSeek API Key。</source>
<translation>Drop files, links or text here, or just type a question.
On first use open Settings at the bottom left and enter your DeepSeek API key.</translation>
</message>
</context>
<context>
<name>ChatSidebar</name>
<message>
<location filename="../src/chat/chatwindow.cpp" line="147"/>
<source>重命名对话</source>
<translation>Rename conversation</translation>
</message>
<message>
<location filename="../src/chat/chatwindow.cpp" line="149"/>
<source>删除选中</source>
<translation>Delete selected</translation>
</message>
</context>
<context> <context>
<name>ChatWindow</name> <name>ChatWindow</name>
<message> <message>
<location filename="../src/chat/chatwindow.cpp" line="211"/> <location filename="../src/chat/chatwindow.cpp" line="290"/>
<location filename="../src/chat/chatwindow.cpp" line="334"/> <location filename="../src/chat/chatwindow.cpp" line="291"/>
<location filename="../src/chat/chatwindow.cpp" line="446"/>
<location filename="../src/chat/chatwindow.cpp" line="523"/>
<location filename="../src/chat/chatwindow.cpp" line="853"/>
<source>小埃同学</source> <source>小埃同学</source>
<translation>AI See</translation> <translation>AI See</translation>
</message> </message>
<message> <message>
<location filename="../src/chat/chatwindow.cpp" line="250"/> <location filename="../src/chat/chatwindow.cpp" line="346"/>
<source>工具调用次数过多,已停止</source> <source>工具调用次数过多,已停止</source>
<translation>Too many tool calls, stopped</translation> <translation>Too many tool calls, stopped</translation>
</message> </message>
<message> <message>
<location filename="../src/chat/chatwindow.cpp" line="309"/> <location filename="../src/chat/chatwindow.cpp" line="405"/>
<source>⚠️ %1</source> <source>⚠️ %1</source>
<translation>⚠️ %1</translation> <translation>⚠️ %1</translation>
</message> </message>
<message> <message>
<location filename="../src/chat/chatwindow.cpp" line="342"/> <location filename="../src/chat/chatwindow.cpp" line="460"/>
<source>把内容拖进对话即可提问</source>
<translation>Drop content into a conversation to ask about it</translation>
</message>
<message>
<location filename="../src/chat/chatwindow.cpp" line="355"/>
<source>+ 新建对话</source> <source>+ 新建对话</source>
<translation>+ New conversation</translation> <translation>+ New conversation</translation>
</message> </message>
<message> <message>
<location filename="../src/chat/chatwindow.cpp" line="363"/> <location filename="../src/chat/chatwindow.cpp" line="624"/>
<source>⋯ 更多</source>
<translation>⋯ More</translation>
</message>
<message>
<location filename="../src/chat/chatwindow.cpp" line="370"/>
<source>压缩上下文</source> <source>压缩上下文</source>
<translation>Compress context</translation> <translation>Compress context</translation>
</message> </message>
<message> <message>
<location filename="../src/chat/chatwindow.cpp" line="371"/> <location filename="../src/chat/chatwindow.cpp" line="625"/>
<source>重试上一条</source> <source>重试上一条</source>
<translation>Retry last message</translation> <translation>Retry last message</translation>
</message> </message>
<message> <message>
<location filename="../src/chat/chatwindow.cpp" line="372"/> <location filename="../src/chat/chatwindow.cpp" line="626"/>
<source>重新加载 MCP</source> <source>重新加载 MCP</source>
<translation>Reload MCP</translation> <translation>Reload MCP</translation>
</message> </message>
<message> <message>
<location filename="../src/chat/chatwindow.cpp" line="375"/> <location filename="../src/chat/chatwindow.cpp" line="629"/>
<source>没有配置 MCP 服务器</source> <source>没有配置 MCP 服务器</source>
<translation>No MCP server configured</translation> <translation>No MCP server configured</translation>
</message> </message>
<message> <message>
<location filename="../src/chat/chatwindow.cpp" line="380"/> <location filename="../src/chat/chatwindow.cpp" line="634"/>
<source>关于</source> <source>关于</source>
<translation>About</translation> <translation>About</translation>
</message> </message>
<message> <message>
<location filename="../src/chat/chatwindow.cpp" line="384"/> <location filename="../src/chat/chatwindow.cpp" line="562"/>
<source>设置</source>
<translation>Settings</translation>
</message>
<message>
<location filename="../src/chat/chatwindow.cpp" line="416"/>
<source>输入消息,Ctrl+Enter 发送;也可以把文件/文字拖进窗口</source> <source>输入消息,Ctrl+Enter 发送;也可以把文件/文字拖进窗口</source>
<translation>Type a message, Ctrl+Enter to send; you can also drop files or text here</translation> <translation>Type a message, Ctrl+Enter to send; you can also drop files or text here</translation>
</message> </message>
<message> <message>
<location filename="../src/chat/chatwindow.cpp" line="424"/> <location filename="../src/chat/chatwindow.cpp" line="569"/>
<source>发送</source> <source>发送</source>
<translation>Send</translation> <translation>Send</translation>
</message> </message>
<message> <message>
<location filename="../src/chat/chatwindow.cpp" line="535"/> <location filename="../src/chat/chatwindow.cpp" line="719"/>
<location filename="../src/chat/chatwindow.cpp" line="652"/> <location filename="../src/chat/chatwindow.cpp" line="820"/>
<location filename="../src/chat/chatwindow.cpp" line="874"/>
<source>新对话</source> <source>新对话</source>
<translation>New conversation</translation> <translation>New conversation</translation>
</message> </message>
<message> <message>
<location filename="../src/chat/chatwindow.cpp" line="605"/> <location filename="../src/chat/chatwindow.cpp" line="1066"/>
<source>你</source>
<translation>You</translation>
</message>
<message>
<location filename="../src/chat/chatwindow.cpp" line="605"/>
<location filename="../src/chat/chatwindow.cpp" line="630"/>
<source>小埃</source>
<translation>AI See</translation>
</message>
<message>
<location filename="../src/chat/chatwindow.cpp" line="826"/>
<source>正在压缩上下文…</source> <source>正在压缩上下文…</source>
<translation>Compressing context…</translation> <translation>Compressing context…</translation>
</message> </message>
<message> <message>
<location filename="../src/chat/chatwindow.cpp" line="845"/> <location filename="../src/chat/chatwindow.cpp" line="1082"/>
<source>编辑消息</source> <source>编辑消息</source>
<translation>Edit message</translation> <translation>Edit message</translation>
</message> </message>
<message> <message>
<location filename="../src/chat/chatwindow.cpp" line="906"/> <location filename="../src/chat/chatwindow.cpp" line="1164"/>
<source>%1 · 分支</source> <source>%1 · 分支</source>
<translation>%1 · branch</translation> <translation>%1 · branch</translation>
</message> </message>
<message> <message>
<location filename="../src/chat/chatwindow.cpp" line="944"/> <location filename="../src/chat/chatwindow.cpp" line="490"/>
<source>关于小埃同学</source> <source>⚙ 设置</source>
<translation>About AI See</translation> <translation>⚙ Settings</translation>
</message> </message>
<message> <message>
<location filename="../src/chat/chatwindow.cpp" line="945"/> <location filename="../src/chat/chatwindow.cpp" line="581"/>
<source>&lt;h3&gt;GXDE 小埃同学&lt;/h3&gt;&lt;p&gt;版本 %1&lt;/p&gt;&lt;p&gt;GXDE 桌面的 AI 助手:传送门、内容工厂、待办与对话。&lt;/p&gt;&lt;p&gt;许可证:GPL-3.0-or-later&lt;br/&gt;仓库:&lt;a href=&apos;https://gitee.com/GXDE-OS/gxde-ai-see&apos;&gt;gitee.com/GXDE-OS/gxde-ai-see&lt;/a&gt;&lt;/p&gt;</source> <source>Enter 换行 · Ctrl+Enter 发送 · 支持拖入文件/链接/图片</source>
<translation>&lt;h3&gt;GXDE AI See&lt;/h3&gt;&lt;p&gt;Version %1&lt;/p&gt;&lt;p&gt;The AI assistant for the GXDE desktop: portal, content factory, todo and chat.&lt;/p&gt;&lt;p&gt;License: GPL-3.0-or-later&lt;br/&gt;Repository: &lt;a href=&apos;https://gitee.com/GXDE-OS/gxde-ai-see&apos;&gt;gitee.com/GXDE-OS/gxde-ai-see&lt;/a&gt;&lt;/p&gt;</translation> <translation>Enter for newline · Ctrl+Enter to send · files, links and images can be dropped here</translation>
</message> </message>
<message> <message>
<location filename="../src/chat/chatwindow.cpp" line="963"/> <location filename="../src/chat/chatwindow.cpp" line="802"/>
<source>重命名对话</source>
<translation>Rename conversation</translation>
</message>
<message>
<location filename="../src/chat/chatwindow.cpp" line="802"/>
<source>名称</source>
<translation>Name</translation>
</message>
<message>
<location filename="../src/chat/chatwindow.cpp" line="1201"/>
<source>GXDE 桌面的 AI 助手:传送门、内容工厂、待办与对话。</source>
<translation>The AI assistant for the GXDE desktop: portal, content factory, todo and chat.</translation>
</message>
<message>
<location filename="../src/chat/chatwindow.cpp" line="1213"/>
<source>小埃同学设置</source> <source>小埃同学设置</source>
<translation>AI See settings</translation> <translation>AI See settings</translation>
</message> </message>
<message> <message>
<location filename="../src/chat/chatwindow.cpp" line="975"/> <location filename="../src/chat/chatwindow.cpp" line="1225"/>
<source>DeepSeek API Key</source> <source>DeepSeek API Key</source>
<translation>DeepSeek API key</translation> <translation>DeepSeek API key</translation>
</message> </message>
<message> <message>
<location filename="../src/chat/chatwindow.cpp" line="982"/> <location filename="../src/chat/chatwindow.cpp" line="1232"/>
<source>API 地址</source> <source>API 地址</source>
<translation>API endpoint</translation> <translation>API endpoint</translation>
</message> </message>
<message> <message>
<location filename="../src/chat/chatwindow.cpp" line="992"/> <location filename="../src/chat/chatwindow.cpp" line="1245"/>
<source>获取模型列表</source>
<translation>Fetch models</translation>
</message>
<message>
<location filename="../src/chat/chatwindow.cpp" line="1247"/>
<source>模型</source> <source>模型</source>
<translation>Model</translation> <translation>Model</translation>
</message> </message>
<message> <message>
<location filename="../src/chat/chatwindow.cpp" line="998"/> <location filename="../src/chat/chatwindow.cpp" line="1270"/>
<source>获取失败</source>
<translation>Fetch failed</translation>
</message>
<message>
<location filename="../src/chat/chatwindow.cpp" line="1293"/>
<source>已更新</source>
<translation>Updated</translation>
</message>
<message>
<location filename="../src/chat/chatwindow.cpp" line="1302"/>
<source>上下文条数</source> <source>上下文条数</source>
<translation>Context messages</translation> <translation>Context messages</translation>
</message> </message>
<message> <message>
<location filename="../src/chat/chatwindow.cpp" line="1000"/> <location filename="../src/chat/chatwindow.cpp" line="1305"/>
<source>128k</source>
<translation>128k</translation>
</message>
<message>
<location filename="../src/chat/chatwindow.cpp" line="1306"/>
<source>1024k</source>
<translation>1024k</translation>
</message>
<message>
<location filename="../src/chat/chatwindow.cpp" line="1313"/>
<source>上下文长度</source>
<translation>Context window</translation>
</message>
<message>
<location filename="../src/chat/chatwindow.cpp" line="1315"/>
<source>超出后自动压缩旧消息</source> <source>超出后自动压缩旧消息</source>
<translation>Automatically compress older messages when exceeded</translation> <translation>Automatically compress older messages when exceeded</translation>
</message> </message>
<message> <message>
<location filename="../src/chat/chatwindow.cpp" line="1013"/> <location filename="../src/chat/chatwindow.cpp" line="1328"/>
<source>MCP 服务器(JSON)</source> <source>MCP 服务器(JSON)</source>
<translation>MCP servers (JSON)</translation> <translation>MCP servers (JSON)</translation>
</message> </message>
<message> <message>
<location filename="../src/chat/chatwindow.cpp" line="1020"/> <location filename="../src/chat/chatwindow.cpp" line="1335"/>
<source>系统提示</source> <source>系统提示</source>
<translation>System prompt</translation> <translation>System prompt</translation>
</message> </message>
<message> <message>
<location filename="../src/chat/chatwindow.cpp" line="1025"/> <location filename="../src/chat/chatwindow.cpp" line="1340"/>
<source>还没有账号?前往 &lt;a href=&apos;https://platform.deepseek.com&apos;&gt;platform.deepseek.com&lt;/a&gt; 开通并创建 API Key。Key 只保存在本机配置文件中。</source> <source>还没有账号?前往 &lt;a href=&apos;https://platform.deepseek.com&apos;&gt;platform.deepseek.com&lt;/a&gt; 开通并创建 API Key。Key 只保存在本机配置文件中。</source>
<translation>No account yet? Sign up and create an API key at &lt;a href=&apos;https://platform.deepseek.com&apos;&gt;platform.deepseek.com&lt;/a&gt;. The key is stored only in the local configuration file.</translation> <translation>No account yet? Sign up and create an API key at &lt;a href=&apos;https://platform.deepseek.com&apos;&gt;platform.deepseek.com&lt;/a&gt;. The key is stored only in the local configuration file.</translation>
</message> </message>
<message> <message>
<location filename="../src/chat/chatwindow.cpp" line="1055"/> <location filename="../src/chat/chatwindow.cpp" line="1372"/>
<source>MCP 配置不是合法的 JSON 数组,已忽略</source> <source>MCP 配置不是合法的 JSON 数组,已忽略</source>
<translation>The MCP configuration is not a valid JSON array; ignored</translation> <translation>The MCP configuration is not a valid JSON array; ignored</translation>
</message> </message>
@@ -201,13 +272,13 @@
<context> <context>
<name>ContentFactory</name> <name>ContentFactory</name>
<message> <message>
<location filename="../src/factory/contentfactory.cpp" line="151"/> <location filename="../src/factory/contentfactory.cpp" line="217"/>
<location filename="../src/factory/contentfactory.cpp" line="181"/> <location filename="../src/factory/contentfactory.cpp" line="259"/>
<source>拖到这里,按类型处理</source> <source>拖到这里,按类型处理</source>
<translation>Drop here to process by type</translation> <translation>Drop here to process by type</translation>
</message> </message>
<message> <message>
<location filename="../src/factory/contentfactory.cpp" line="154"/> <location filename="../src/factory/contentfactory.cpp" line="220"/>
<source>拖到动作上松手</source> <source>拖到动作上松手</source>
<translation>Drop onto an action</translation> <translation>Drop onto an action</translation>
</message> </message>
@@ -241,211 +312,231 @@
</message> </message>
</context> </context>
<context> <context>
<name>PortalPanel</name> <name>MessageRow</name>
<message> <message>
<location filename="../src/ui/portalpanel.cpp" line="761"/> <location filename="../src/chat/chatview.cpp" line="121"/>
<source>GXDE 小埃同学</source> <location filename="../src/chat/chatview.cpp" line="130"/>
<translation>GXDE AI See</translation> <source>你</source>
<translation>You</translation>
</message> </message>
<message> <message>
<location filename="../src/ui/portalpanel.cpp" line="781"/> <location filename="../src/chat/chatview.cpp" line="121"/>
<location filename="../src/ui/portalpanel.cpp" line="889"/> <source>埃</source>
<source>%1 项</source> <translation>AI</translation>
<translation>%1 items</translation>
</message> </message>
<message> <message>
<location filename="../src/ui/portalpanel.cpp" line="859"/> <location filename="../src/chat/chatview.cpp" line="130"/>
<source>移动到目标窗口上松手</source> <source>小埃</source>
<translation>Move onto a target window and release</translation> <translation>AI See</translation>
</message> </message>
<message> <message>
<location filename="../src/ui/portalpanel.cpp" line="861"/> <location filename="../src/chat/chatview.cpp" line="170"/>
<source>当前无已打开的窗口</source>
<translation>No open windows</translation>
</message>
<message>
<location filename="../src/ui/portalpanel.cpp" line="862"/>
<source>当前环境不支持传送门</source>
<translation>The portal is not supported in this environment</translation>
</message>
<message>
<location filename="../src/ui/portalpanel.cpp" line="880"/>
<source>暂存区</source>
<translation>Staging</translation>
</message>
<message>
<location filename="../src/ui/portalpanel.cpp" line="913"/>
<source>全选</source>
<translation>Select all</translation>
</message>
<message>
<location filename="../src/ui/portalpanel.cpp" line="914"/>
<source>反选</source>
<translation>Invert</translation>
</message>
<message>
<location filename="../src/ui/portalpanel.cpp" line="915"/>
<source>删除选中</source>
<translation>Delete selected</translation>
</message>
<message>
<location filename="../src/ui/portalpanel.cpp" line="1031"/>
<source>复制</source> <source>复制</source>
<translation>Copy</translation> <translation>Copy</translation>
</message> </message>
<message> <message>
<location filename="../src/ui/portalpanel.cpp" line="1035"/> <location filename="../src/chat/chatview.cpp" line="176"/>
<location filename="../src/ui/portalpanel.cpp" line="1043"/> <location filename="../src/chat/chatview.cpp" line="185"/>
<source>编辑</source>
<translation>Edit</translation>
</message>
<message>
<location filename="../src/chat/chatview.cpp" line="181"/>
<source>分支</source>
<translation>Branch</translation>
</message>
<message>
<location filename="../src/chat/chatview.cpp" line="189"/>
<source>重试</source>
<translation>Retry</translation>
</message>
</context>
<context>
<name>PortalPanel</name>
<message>
<location filename="../src/ui/portalpanel.cpp" line="766"/>
<source>GXDE 小埃同学</source>
<translation>GXDE AI See</translation>
</message>
<message>
<location filename="../src/ui/portalpanel.cpp" line="786"/>
<location filename="../src/ui/portalpanel.cpp" line="920"/>
<source>%1 项</source>
<translation>%1 items</translation>
</message>
<message>
<location filename="../src/ui/portalpanel.cpp" line="888"/>
<source>移动到目标窗口上松手</source>
<translation>Move onto a target window and release</translation>
</message>
<message>
<location filename="../src/ui/portalpanel.cpp" line="890"/>
<source>当前无已打开的窗口</source>
<translation>No open windows</translation>
</message>
<message>
<location filename="../src/ui/portalpanel.cpp" line="891"/>
<source>当前环境不支持传送门</source>
<translation>The portal is not supported in this environment</translation>
</message>
<message>
<location filename="../src/ui/portalpanel.cpp" line="911"/>
<source>暂存区</source>
<translation>Staging</translation>
</message>
<message>
<location filename="../src/ui/portalpanel.cpp" line="944"/>
<source>全选</source>
<translation>Select all</translation>
</message>
<message>
<location filename="../src/ui/portalpanel.cpp" line="945"/>
<source>反选</source>
<translation>Invert</translation>
</message>
<message>
<location filename="../src/ui/portalpanel.cpp" line="946"/>
<source>删除选中</source>
<translation>Delete selected</translation>
</message>
<message>
<location filename="../src/ui/portalpanel.cpp" line="1113"/>
<source>复制</source>
<translation>Copy</translation>
</message>
<message>
<location filename="../src/ui/portalpanel.cpp" line="1117"/>
<location filename="../src/ui/portalpanel.cpp" line="1125"/>
<source>已复制</source> <source>已复制</source>
<translation>Copied</translation> <translation>Copied</translation>
</message> </message>
<message> <message>
<location filename="../src/ui/portalpanel.cpp" line="1097"/> <location filename="../src/ui/portalpanel.cpp" line="1179"/>
<source>真实地址</source> <source>真实地址</source>
<translation>Real address</translation> <translation>Real address</translation>
</message> </message>
<message> <message>
<location filename="../src/ui/portalpanel.cpp" line="1102"/> <location filename="../src/ui/portalpanel.cpp" line="1192"/>
<source>分词(%1)</source>
<translation>Segmented (%1)</translation>
</message>
<message>
<location filename="../src/ui/portalpanel.cpp" line="1107"/>
<source>提取链接(%1)</source> <source>提取链接(%1)</source>
<translation>Links (%1)</translation> <translation>Links (%1)</translation>
</message> </message>
<message> <message>
<location filename="../src/ui/portalpanel.cpp" line="1113"/> <location filename="../src/ui/portalpanel.cpp" line="1206"/>
<source>未找到语音朗读程序</source>
<translation>No speech program found</translation>
</message>
<message>
<location filename="../src/ui/portalpanel.cpp" line="1117"/>
<source>开始朗读</source>
<translation>Reading aloud</translation>
</message>
<message>
<location filename="../src/ui/portalpanel.cpp" line="1124"/>
<source>已添加到待办</source> <source>已添加到待办</source>
<translation>Added to todo</translation> <translation>Added to todo</translation>
</message> </message>
<message> <message>
<location filename="../src/ui/portalpanel.cpp" line="1129"/> <location filename="../src/ui/portalpanel.cpp" line="1212"/>
<source>没有可保存的图片</source> <source>没有可保存的图片</source>
<translation>No image to save</translation> <translation>No image to save</translation>
</message> </message>
<message> <message>
<location filename="../src/ui/portalpanel.cpp" line="1133"/> <location filename="../src/ui/portalpanel.cpp" line="1216"/>
<source>保存图片</source> <source>保存图片</source>
<translation>Save image</translation> <translation>Save image</translation>
</message> </message>
<message> <message>
<location filename="../src/ui/portalpanel.cpp" line="1133"/> <location filename="../src/ui/portalpanel.cpp" line="1216"/>
<source>图片 (*.png *.jpg)</source> <source>图片 (*.png *.jpg)</source>
<translation>Images (*.png *.jpg)</translation> <translation>Images (*.png *.jpg)</translation>
</message> </message>
<message> <message>
<location filename="../src/ui/portalpanel.cpp" line="1136"/> <location filename="../src/ui/portalpanel.cpp" line="1219"/>
<source>已保存:%1</source> <source>已保存:%1</source>
<translation>Saved: %1</translation> <translation>Saved: %1</translation>
</message> </message>
<message> <message>
<location filename="../src/ui/portalpanel.cpp" line="1145"/> <location filename="../src/ui/portalpanel.cpp" line="1228"/>
<source>翻译接口将在小埃同学接入后可用</source> <source>翻译接口将在小埃同学接入后可用</source>
<translation>Translation will be available once the AI backend is wired up</translation> <translation>Translation will be available once the AI backend is wired up</translation>
</message> </message>
<message> <message>
<location filename="../src/ui/portalpanel.cpp" line="1150"/> <location filename="../src/ui/portalpanel.cpp" line="1233"/>
<source>该能力尚未接入 GXDE Daemon</source> <source>该能力尚未接入 GXDE Daemon</source>
<translation>This capability is not wired to GXDE Daemon yet</translation> <translation>This capability is not wired to GXDE Daemon yet</translation>
</message> </message>
<message> <message>
<location filename="../src/ui/portalpanel.cpp" line="1154"/> <location filename="../src/ui/portalpanel.cpp" line="1237"/>
<source>未知操作:%1</source> <source>未知操作:%1</source>
<translation>Unknown action: %1</translation> <translation>Unknown action: %1</translation>
</message> </message>
<message> <message>
<location filename="../src/ui/portalpanel.cpp" line="1297"/> <location filename="../src/ui/portalpanel.cpp" line="1381"/>
<source>固定位置</source> <source>固定位置</source>
<translation>Pin position</translation> <translation>Pin position</translation>
</message> </message>
<message> <message>
<location filename="../src/ui/portalpanel.cpp" line="1301"/> <location filename="../src/ui/portalpanel.cpp" line="1385"/>
<source>恢复默认位置</source> <source>恢复默认位置</source>
<translation>Restore default position</translation> <translation>Restore default position</translation>
</message> </message>
<message> <message>
<location filename="../src/ui/portalpanel.cpp" line="1303"/> <location filename="../src/ui/portalpanel.cpp" line="1387"/>
<source>固定大小</source> <source>固定大小</source>
<translation>Pin size</translation> <translation>Pin size</translation>
</message> </message>
<message> <message>
<location filename="../src/ui/portalpanel.cpp" line="1307"/> <location filename="../src/ui/portalpanel.cpp" line="1391"/>
<source>全屏时隐藏</source> <source>全屏时隐藏</source>
<translation>Hide on fullscreen</translation> <translation>Hide on fullscreen</translation>
</message> </message>
<message> <message>
<location filename="../src/ui/portalpanel.cpp" line="1316"/> <location filename="../src/ui/portalpanel.cpp" line="1400"/>
<source>开机自启与后台保活</source> <source>开机自启与后台保活</source>
<translation>Start at login and keep alive</translation> <translation>Start at login and keep alive</translation>
</message> </message>
<message> <message>
<location filename="../src/ui/portalpanel.cpp" line="1321"/> <location filename="../src/ui/portalpanel.cpp" line="1405"/>
<source>清空暂存区</source> <source>清空暂存区</source>
<translation>Clear staging area</translation> <translation>Clear staging area</translation>
</message> </message>
<message> <message>
<location filename="../src/ui/portalpanel.cpp" line="1322"/> <location filename="../src/ui/portalpanel.cpp" line="1406"/>
<source>隐藏</source>
<translation>Hide</translation>
</message>
<message>
<location filename="../src/ui/portalpanel.cpp" line="1407"/>
<source>关于</source> <source>关于</source>
<translation>About</translation> <translation>About</translation>
</message> </message>
<message> <message>
<location filename="../src/ui/portalpanel.cpp" line="1323"/> <location filename="../src/ui/portalpanel.cpp" line="1408"/>
<source>退出</source> <source>退出</source>
<translation>Quit</translation> <translation>Quit</translation>
</message> </message>
<message> <message>
<location filename="../src/ui/portalpanel.cpp" line="1370"/> <location filename="../src/ui/portalpanel.cpp" line="1482"/>
<source>已开启开机自启与保活</source> <source>已开启开机自启与保活</source>
<translation>Startup and keep-alive enabled</translation> <translation>Startup and keep-alive enabled</translation>
</message> </message>
<message> <message>
<location filename="../src/ui/portalpanel.cpp" line="1370"/> <location filename="../src/ui/portalpanel.cpp" line="1482"/>
<source>已关闭开机自启与保活</source> <source>已关闭开机自启与保活</source>
<translation>Startup and keep-alive disabled</translation> <translation>Startup and keep-alive disabled</translation>
</message> </message>
<message> <message>
<location filename="../src/ui/portalpanel.cpp" line="1375"/> <location filename="../src/ui/portalpanel.cpp" line="1486"/>
<source>关于小埃同学</source> <source>传送门、内容工厂、待办与小埃同学对话。</source>
<translation>About AI See</translation> <translation>Portal, content factory, todo and AI See chat.</translation>
</message>
<message>
<location filename="../src/ui/portalpanel.cpp" line="1376"/>
<source>&lt;h3&gt;GXDE 小埃同学&lt;/h3&gt;&lt;p&gt;版本 %1&lt;/p&gt;&lt;p&gt;传送门、内容工厂、待办与小埃同学对话。&lt;/p&gt;&lt;p&gt;GPL-3.0-or-later · &lt;a href=&apos;https://gitee.com/GXDE-OS/gxde-ai-see&apos;&gt;gxde-ai-see&lt;/a&gt;&lt;/p&gt;</source>
<translation>&lt;h3&gt;GXDE AI See&lt;/h3&gt;&lt;p&gt;Version %1&lt;/p&gt;&lt;p&gt;Portal, content factory, todo and AI See chat.&lt;/p&gt;&lt;p&gt;GPL-3.0-or-later · &lt;a href=&apos;https://gitee.com/GXDE-OS/gxde-ai-see&apos;&gt;gxde-ai-see&lt;/a&gt;&lt;/p&gt;</translation>
</message> </message>
</context> </context>
<context> <context>
<name>PortalZone</name> <name>PortalZone</name>
<message> <message>
<location filename="../src/ui/portalpanel.cpp" line="587"/> <location filename="../src/ui/portalpanel.cpp" line="669"/>
<source>问问小埃 →</source>
<translation>Ask AI See →</translation>
</message>
<message>
<location filename="../src/ui/portalpanel.cpp" line="664"/>
<source>传送门</source> <source>传送门</source>
<translation>Portal</translation> <translation>Portal</translation>
</message> </message>
<message> <message>
<location filename="../src/ui/portalpanel.cpp" line="672"/> <location filename="../src/ui/portalpanel.cpp" line="677"/>
<source>拖到这里 <source>拖到这里
选择窗口跃迁</source> 选择窗口跃迁</source>
<translation>Drop here <translation>Drop here
to choose a window</translation> to choose a window</translation>
</message> </message>
<message> <message>
<location filename="../src/ui/portalpanel.cpp" line="729"/> <location filename="../src/ui/portalpanel.cpp" line="734"/>
<source>悬停在「传送门」上,再移动到目标窗口</source> <source>悬停在「传送门」上,再移动到目标窗口</source>
<translation>Hover the portal, then move onto a target window</translation> <translation>Hover the portal, then move onto a target window</translation>
</message> </message>
@@ -458,25 +549,141 @@ to choose a window</translation>
<translation>No API key configured yet. Open Settings and enter your DeepSeek API key.</translation> <translation>No API key configured yet. Open Settings and enter your DeepSeek API key.</translation>
</message> </message>
<message> <message>
<location filename="../src/ui/portalpanel.cpp" line="118"/> <location filename="../src/ui/portalpanel.cpp" line="122"/>
<source>文件</source> <source>文件</source>
<translation>File</translation> <translation>File</translation>
</message> </message>
<message> <message>
<location filename="../src/ui/portalpanel.cpp" line="120"/> <location filename="../src/ui/portalpanel.cpp" line="124"/>
<source>图片</source> <source>图片</source>
<translation>Image</translation> <translation>Image</translation>
</message> </message>
<message> <message>
<location filename="../src/ui/portalpanel.cpp" line="122"/> <location filename="../src/ui/portalpanel.cpp" line="126"/>
<source>文字 · %1 字</source> <source>文字 · %1 字</source>
<translation>Text · %1 chars</translation> <translation>Text · %1 chars</translation>
</message> </message>
<message>
<location filename="../src/ui/aboutdialog.cpp" line="16"/>
<source>关于小埃同学</source>
<translation>About AI See</translation>
</message>
<message>
<location filename="../src/ui/aboutdialog.cpp" line="18"/>
<source>GXDE 小埃同学</source>
<translation>GXDE AI See</translation>
</message>
</context>
<context>
<name>SegmentDialog</name>
<message>
<location filename="../src/dialogs/segmentdialog.cpp" line="21"/>
<source>分词</source>
<translation>Segment</translation>
</message>
<message>
<location filename="../src/dialogs/segmentdialog.cpp" line="34"/>
<source>按住并滑过词语可以连续选择,选中后复制。</source>
<translation>Press and slide across words to select several, then copy.</translation>
</message>
<message>
<location filename="../src/dialogs/segmentdialog.cpp" line="74"/>
<source>全选</source>
<translation>Select all</translation>
</message>
<message>
<location filename="../src/dialogs/segmentdialog.cpp" line="75"/>
<source>清空</source>
<translation>Clear</translation>
</message>
<message>
<location filename="../src/dialogs/segmentdialog.cpp" line="76"/>
<source>已选 0 个</source>
<translation>0 selected</translation>
</message>
<message>
<location filename="../src/dialogs/segmentdialog.cpp" line="78"/>
<source>复制选中</source>
<translation>Copy selected</translation>
</message>
<message>
<location filename="../src/dialogs/segmentdialog.cpp" line="95"/>
<source>关闭</source>
<translation>Close</translation>
</message>
<message>
<location filename="../src/dialogs/segmentdialog.cpp" line="116"/>
<source>已选 %1 个</source>
<translation>%1 selected</translation>
</message>
<message>
<location filename="../src/dialogs/segmentdialog.cpp" line="128"/>
<source>还没有选中词语</source>
<translation>No words selected</translation>
</message>
<message>
<location filename="../src/dialogs/segmentdialog.cpp" line="132"/>
<source>已复制 %1 个</source>
<translation>Copied %1</translation>
</message>
</context>
<context>
<name>SpeechDialog</name>
<message>
<location filename="../src/dialogs/speechdialog.cpp" line="19"/>
<source>朗读</source>
<translation>Read aloud</translation>
</message>
<message>
<location filename="../src/dialogs/speechdialog.cpp" line="36"/>
<source>语速</source>
<translation>Speed</translation>
</message>
<message>
<location filename="../src/dialogs/speechdialog.cpp" line="48"/>
<source>开始朗读</source>
<translation>Start</translation>
</message>
<message>
<location filename="../src/dialogs/speechdialog.cpp" line="53"/>
<source>停止</source>
<translation>Stop</translation>
</message>
<message>
<location filename="../src/dialogs/speechdialog.cpp" line="54"/>
<source>关闭</source>
<translation>Close</translation>
</message>
<message>
<location filename="../src/dialogs/speechdialog.cpp" line="82"/>
<source>未找到 spd-say 或 espeak-ng</source>
<translation>spd-say or espeak-ng not found</translation>
</message>
<message>
<location filename="../src/dialogs/speechdialog.cpp" line="92"/>
<source>朗读失败:%1</source>
<translation>Speech failed: %1</translation>
</message>
<message>
<location filename="../src/dialogs/speechdialog.cpp" line="96"/>
<source>朗读结束</source>
<translation>Finished</translation>
</message>
<message>
<location filename="../src/dialogs/speechdialog.cpp" line="98"/>
<source>正在朗读…</source>
<translation>Speaking…</translation>
</message>
<message>
<location filename="../src/dialogs/speechdialog.cpp" line="109"/>
<source>已停止</source>
<translation>Stopped</translation>
</message>
</context> </context>
<context> <context>
<name>StagingView</name> <name>StagingView</name>
<message> <message>
<location filename="../src/ui/portalpanel.cpp" line="351"/> <location filename="../src/ui/portalpanel.cpp" line="355"/>
<source>把文件、文字或图片拖到这里</source> <source>把文件、文字或图片拖到这里</source>
<translation>Drop files, text or images here</translation> <translation>Drop files, text or images here</translation>
</message> </message>
@@ -484,42 +691,48 @@ to choose a window</translation>
<context> <context>
<name>TodoWindow</name> <name>TodoWindow</name>
<message> <message>
<location filename="../src/todo/todowindow.cpp" line="145"/> <location filename="../src/todo/todowindow.cpp" line="151"/>
<location filename="../src/todo/todowindow.cpp" line="195"/> <location filename="../src/todo/todowindow.cpp" line="213"/>
<source>待办</source> <source>待办</source>
<translation>Todo</translation> <translation>Todo</translation>
</message> </message>
<message> <message>
<location filename="../src/todo/todowindow.cpp" line="158"/> <location filename="../src/todo/todowindow.cpp" line="171"/>
<location filename="../src/todo/todowindow.cpp" line="203"/> <location filename="../src/todo/todowindow.cpp" line="221"/>
<source>%1 项</source> <source>%1 项</source>
<translation>%1 items</translation> <translation>%1 items</translation>
</message> </message>
<message> <message>
<location filename="../src/todo/todowindow.cpp" line="208"/> <location filename="../src/todo/todowindow.cpp" line="226"/>
<source>+</source> <source>+</source>
<translation>+</translation> <translation>+</translation>
</message> </message>
<message> <message>
<location filename="../src/todo/todowindow.cpp" line="212"/> <location filename="../src/todo/todowindow.cpp" line="230"/>
<location filename="../src/todo/todowindow.cpp" line="280"/> <location filename="../src/todo/todowindow.cpp" line="298"/>
<source>新建待办</source> <source>新建待办</source>
<translation>New todo</translation> <translation>New todo</translation>
</message> </message>
<message> <message>
<location filename="../src/todo/todowindow.cpp" line="220"/> <location filename="../src/todo/todowindow.cpp" line="238"/>
<source>×</source> <source>×</source>
<translation>×</translation> <translation>×</translation>
</message> </message>
<message> <message>
<location filename="../src/todo/todowindow.cpp" line="224"/> <location filename="../src/todo/todowindow.cpp" line="242"/>
<location filename="../src/todo/todowindow.cpp" line="343"/>
<source>隐藏</source> <source>隐藏</source>
<translation>Hide</translation> <translation>Hide</translation>
</message> </message>
<message> <message>
<location filename="../src/todo/todowindow.cpp" line="285"/> <location filename="../src/todo/todowindow.cpp" line="303"/>
<source>要做的事…</source> <source>要做的事…</source>
<translation>Something to do…</translation> <translation>Something to do…</translation>
</message> </message>
<message>
<location filename="../src/todo/todowindow.cpp" line="340"/>
<source>置顶</source>
<translation>Always on top</translation>
</message>
</context> </context>
</TS> </TS>