diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e3762f..29b3e19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # 更新日志 +## 1.0.0~beta2 — 2026-10-30 + +- 会话列表:拖拽排序、多选、批量删除 +- 上下文压缩支持 128k / 1024k token 上限 +- 待办取消置顶(普通窗口) +- 全屏时隐藏联动合成器信号 +- 非中文环境回退英文;英文翻译补齐 +- 新应用图标、DTK 关于窗口 + ## 1.0.0~beta — 2026-10-30 首个 beta 版本。 diff --git a/data/icons/hicolor/128x128/apps/gxde-ai-see.png b/data/icons/hicolor/128x128/apps/gxde-ai-see.png index f116a65..9308bca 100644 Binary files a/data/icons/hicolor/128x128/apps/gxde-ai-see.png and b/data/icons/hicolor/128x128/apps/gxde-ai-see.png differ diff --git a/data/icons/hicolor/16x16/apps/gxde-ai-see.png b/data/icons/hicolor/16x16/apps/gxde-ai-see.png index 5dd1e04..6b464ae 100644 Binary files a/data/icons/hicolor/16x16/apps/gxde-ai-see.png and b/data/icons/hicolor/16x16/apps/gxde-ai-see.png differ diff --git a/data/icons/hicolor/24x24/apps/gxde-ai-see.png b/data/icons/hicolor/24x24/apps/gxde-ai-see.png index 0b134f6..f16c49b 100644 Binary files a/data/icons/hicolor/24x24/apps/gxde-ai-see.png and b/data/icons/hicolor/24x24/apps/gxde-ai-see.png differ diff --git a/data/icons/hicolor/256x256/apps/gxde-ai-see.png b/data/icons/hicolor/256x256/apps/gxde-ai-see.png index 2774644..44022f8 100644 Binary files a/data/icons/hicolor/256x256/apps/gxde-ai-see.png and b/data/icons/hicolor/256x256/apps/gxde-ai-see.png differ diff --git a/data/icons/hicolor/32x32/apps/gxde-ai-see.png b/data/icons/hicolor/32x32/apps/gxde-ai-see.png index d6a86bd..023aa4f 100644 Binary files a/data/icons/hicolor/32x32/apps/gxde-ai-see.png and b/data/icons/hicolor/32x32/apps/gxde-ai-see.png differ diff --git a/data/icons/hicolor/48x48/apps/gxde-ai-see.png b/data/icons/hicolor/48x48/apps/gxde-ai-see.png index 8695fc0..438b586 100644 Binary files a/data/icons/hicolor/48x48/apps/gxde-ai-see.png and b/data/icons/hicolor/48x48/apps/gxde-ai-see.png differ diff --git a/data/icons/hicolor/64x64/apps/gxde-ai-see.png b/data/icons/hicolor/64x64/apps/gxde-ai-see.png index d362848..e48cee2 100644 Binary files a/data/icons/hicolor/64x64/apps/gxde-ai-see.png and b/data/icons/hicolor/64x64/apps/gxde-ai-see.png differ diff --git a/debian/changelog b/debian/changelog index 5ec64f3..8d515bd 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,15 @@ +gxde-ai-see (1.0.0~beta2) unstable; urgency=medium + + * 会话列表支持拖拽排序、多选、批量删除 + * 上下文压缩支持 128k / 1024k token 上限 + * 待办窗口可取消置顶(普通窗口) + * 全屏时隐藏:合成器 FullscreenChanged 信号联动 + * 非中文环境回退英文翻译;补齐全部英文翻译 + * 全新应用图标(清理背景、圆角渐变底) + * DTK 关于窗口;分词/朗读对话框;消息下方常驻操作 + + -- shenmo Fri, 30 Oct 2026 23:30:00 +0800 + gxde-ai-see (1.0.0~beta) unstable; urgency=medium * 首个 beta:传送门原生 drop、暂存区、内容工厂、待办、小埃同学对话 diff --git a/docs/TODO.md b/docs/TODO.md index bc325c7..2fc88df 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -1,35 +1,14 @@ -# 1.0.0~beta 之后待办 +# 1.0.0~beta2 之后待办 -按优先级排列,部分项需要单独一轮实现与测试。 +已完成:会话列表拖拽排序/多选/批量删除(可复用控件已加入 dtk2widget-qt6)、 +token 压缩上限、待办取消置顶、全屏隐藏联动、英文回退翻译、新图标、DTK 关于窗口。 -## 对话 +## 仍需单独排期 -- [ ] 会话列表支持拖拽排序、右键删除、批量多选;先在本仓库实现,随后抽成 - `dtk2widget-qt6` 的可复用列表控件(避免每个项目重写) -- [ ] 浅色/深色主题跟随 DTK,默认浅色 -- [ ] 消息版本树:重试/编辑保留历史,可在旧版本之间左右切换 -- [ ] 每条消息下方常驻 重试 / 编辑 / 复制(已做基础版,版本树未做) - -## 国际化 - -- [ ] 源码中的 `tr()` 文案改为英文,中文放进 `zh_CN` 翻译;保证非中文环境 - 显示英文而不是中文 -- [ ] 更新 `translations/*.ts` 并补齐中文翻译 - -## 系统能力 - -- [ ] OCR / TTS / 翻译对接 GXDE Daemon 的接口(当前为占位) -- [ ] 全屏时隐藏:合成器侧判断全屏窗口,面板自动隐藏 -- [ ] 待办窗口「取消置顶」:可切换为普通窗口 -- [ ] 上下文压缩支持按 token(128k / 1024k)而不是只按条数 - -## 兼容性 - -- [ ] X11 / 非 GXDE 合成器的降级方案:传送门旁展示窗口图标网格, - 悬停最小化其他窗口并激活目标,松手完成 drop -- [ ] DTK Wayland 应用拖拽文本时合成器侧生成文本预览(当前无 drag icon) -- [ ] 更多窗口改用 DTK 控件与原生样式 - -## 图标 - -- [ ] 清理小埃同学 logo 背景并重新生成应用图标,改善小尺寸清晰度 +- [ ] 对话浅色/深色主题跟随 DTK(当前仍是深色固定配色) +- [ ] 消息版本树:重试/编辑保留历史并可左右切换 +- [ ] OCR / TTS / 翻译对接 GXDE Daemon(当前仓库未找到对应 D-Bus 接口,需先定义) +- [ ] X11 / 非 GXDE 合成器窗口图标网格降级方案 +- [ ] DTK Wayland 文本拖拽预览(合成器侧渲染,工作量大) +- [ ] 更多窗口改用 DTK 控件(聊天设置、待办窗口等) +- [ ] dtk2widget-qt6 的 DReorderListWidget 需要随库发布后,应用改为链接该控件 diff --git a/src/app.cpp b/src/app.cpp index b03aaf9..89ade33 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -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]() { if (!m_model->isEmpty() && m_panel) { m_panel->popup(); @@ -194,6 +208,14 @@ void App::showTodoWindow() { if (!m_todoWindow) { 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(); } diff --git a/src/chat/chatwindow.cpp b/src/chat/chatwindow.cpp index 59bff17..6b42c72 100644 --- a/src/chat/chatwindow.cpp +++ b/src/chat/chatwindow.cpp @@ -75,11 +75,20 @@ public: : QListWidget(parent) { setAcceptDrops(true); + setDragEnabled(true); + setDragDropMode(QAbstractItemView::DragDrop); + setDefaultDropAction(Qt::MoveAction); + setDropIndicatorShown(true); + setDragDropOverwriteMode(false); + setSelectionMode(QAbstractItemView::ExtendedSelection); } signals: void droppedAt(int index, const QMimeData *mime); void left(); + void reordered(); + void deleteRequested(const QList &rows); + void renameRequested(int row); protected: void leaveEvent(QEvent *event) override @@ -88,10 +97,9 @@ protected: QListWidget::leaveEvent(event); } -protected: void dragEnterEvent(QDragEnterEvent *event) override { - if (mimeOk(event->mimeData())) { + if (event->source() == this || mimeOk(event->mimeData())) { event->acceptProposedAction(); } else { event->ignore(); @@ -100,13 +108,27 @@ protected: void dragMoveEvent(QDragMoveEvent *event) override { - if (mimeOk(event->mimeData())) { + if (event->source() == this || mimeOk(event->mimeData())) { event->acceptProposedAction(); } } 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())) { event->ignore(); return; @@ -115,6 +137,37 @@ protected: Q_EMIT droppedAt(index.isValid() ? index.row() : -1, event->mimeData()); 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 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 @@ -546,6 +599,12 @@ void ChatWindow::setupUi() connect(m_sidebar, &QListWidget::currentRowChanged, this, &ChatWindow::selectConversation); connect(static_cast(m_sidebar), &ChatSidebar::droppedAt, this, &ChatWindow::handleDropOnConversation); + connect(static_cast(m_sidebar), &ChatSidebar::reordered, this, + &ChatWindow::reorderConversations); + connect(static_cast(m_sidebar), &ChatSidebar::deleteRequested, this, + &ChatWindow::deleteConversations); + connect(static_cast(m_sidebar), &ChatSidebar::renameRequested, this, + &ChatWindow::renameConversation); connect(m_messages, &ChatMessageList::dropped, this, &ChatWindow::handleDroppedMime); connect(m_messages, &ChatMessageList::linkActivated, this, &ChatWindow::handleAnchor); connect(m_messages, &ChatMessageList::copyRequested, this, [this](int index) { @@ -681,12 +740,83 @@ void ChatWindow::selectConversation(int index) renderConversation(); } +void ChatWindow::reorderConversations() +{ + if (m_conversations.isEmpty()) { + return; + } + QHash byId; + for (const ChatConversation &conversation : m_conversations) { + byId.insert(conversation.id, conversation); + } + QVector 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 &rows) +{ + if (rows.isEmpty()) { + return; + } + if (m_client->busy()) { + m_client->cancel(); + m_streamingIndex = -1; + } + QList sorted = rows; + std::sort(sorted.begin(), sorted.end(), std::greater()); + 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() { const QSignalBlocker blocker(m_sidebar); m_sidebar->clear(); for (const ChatConversation &conversation : m_conversations) { auto *item = new QListWidgetItem(conversation.title, m_sidebar); + item->setData(Qt::UserRole, conversation.id); QString preview = tr("新对话"); for (int i = conversation.messages.size() - 1; i >= 0; --i) { const ChatMessage &message = conversation.messages.at(i); @@ -874,8 +1004,26 @@ bool ChatWindow::needsCompression() const if (!Config::instance()->getBool(QStringLiteral("chat/autoCompress"), true)) { return false; } + const QVector &messages = m_conversations.at(m_current).messages; + 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) @@ -1153,6 +1301,17 @@ void ChatWindow::openSettings() Config::instance()->getInt(QStringLiteral("chat/contextLength"), 40)); 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); autoCompressBox->setChecked( 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/autoCompress"), autoCompressBox->isChecked()); + Config::instance()->set(QStringLiteral("chat/contextTokens"), + tokenBox->currentData().toInt()); const QJsonDocument mcpDocument = QJsonDocument::fromJson(mcpEdit->toPlainText().toUtf8()); if (mcpDocument.isArray() || mcpEdit->toPlainText().trimmed().isEmpty()) { diff --git a/src/chat/chatwindow.h b/src/chat/chatwindow.h index eda8d7d..4340638 100644 --- a/src/chat/chatwindow.h +++ b/src/chat/chatwindow.h @@ -62,6 +62,9 @@ private: void renderConversation(); void updateHeader(); void updateSidebar(); + void reorderConversations(); + void deleteConversations(const QList &rows); + void renameConversation(int row); void sendMessage(); void requestCompletion(); void runToolCalls(const QVector &calls, int index, const QJsonArray &results); diff --git a/src/main.cpp b/src/main.cpp index f7581b4..dd50d56 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,7 +19,7 @@ int main(int argc, char *argv[]) app.setOrganizationName(QStringLiteral("GXDE")); app.setApplicationName(QStringLiteral("gxde-ai-see")); 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")); if (appIcon.isNull()) { 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/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) { - if (translator->load(QStringLiteral("gxde-ai-see_") + locale, dir) || - translator->load(QStringLiteral("gxde-ai-see_") + locale.section(QLatin1Char('_'), 0, 0), - dir)) { - app.installTranslator(translator); - translated = true; + bool loaded = false; + for (const QString &candidate : candidates) { + if (translator->load(QStringLiteral("gxde-ai-see_") + candidate, dir)) { + app.installTranslator(translator); + loaded = true; + break; + } + } + if (loaded) { break; } } - Q_UNUSED(translated) QCommandLineParser parser; parser.setApplicationDescription(QStringLiteral("GXDE 小埃同学")); diff --git a/src/portal/portalclient.cpp b/src/portal/portalclient.cpp index 995e83a..5bb1545 100644 --- a/src/portal/portalclient.cpp +++ b/src/portal/portalclient.cpp @@ -53,6 +53,9 @@ PortalClient::PortalClient(QObject *parent) bus.connect(QString::fromLatin1(kService), QString::fromLatin1(kPath), QString::fromLatin1(kInterface), QStringLiteral("PortalClosed"), this, 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) @@ -96,6 +99,11 @@ void PortalClient::onDragStarted(const QString &mimeTypes) Q_EMIT dragStarted(mimeTypes); } +void PortalClient::onFullscreenChanged(bool fullscreen) +{ + Q_EMIT fullscreenChanged(fullscreen); +} + void PortalClient::onPortalClosed() { Q_EMIT portalClosed(); diff --git a/src/portal/portalclient.h b/src/portal/portalclient.h index 7f3892d..c047a62 100644 --- a/src/portal/portalclient.h +++ b/src/portal/portalclient.h @@ -34,12 +34,14 @@ signals: void dragStarted(const QString &mimeTypes); void dragEnded(); void portalClosed(); + void fullscreenChanged(bool fullscreen); void dropped(const QString &uuid, bool accepted); private slots: void onDragStarted(const QString &mimeTypes); void onDragEnded(); void onPortalClosed(); + void onFullscreenChanged(bool fullscreen); void onDropped(const QString &uuid, bool accepted); private: diff --git a/src/todo/todowindow.cpp b/src/todo/todowindow.cpp index f7ad43a..9a51824 100644 --- a/src/todo/todowindow.cpp +++ b/src/todo/todowindow.cpp @@ -7,6 +7,9 @@ #include "config.h" +#include +#include + #include #include #include @@ -159,6 +162,7 @@ TodoWindow::TodoWindow(TodoModel *model, QWidget *parent) m_rightMargin = Config::instance()->getInt(QStringLiteral("todo/rightMargin"), 16); m_topMargin = Config::instance()->getInt(QStringLiteral("todo/topMargin"), 72); + m_keepAbove = Config::instance()->getBool(QStringLiteral("todo/keepAbove"), true); setupWindow(); setupUi(); @@ -172,6 +176,11 @@ TodoWindow::TodoWindow(TodoModel *model, QWidget *parent) 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 if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"))) { createWinId(); @@ -308,6 +317,9 @@ void TodoWindow::addEntry() void TodoWindow::applyMargins() { + if (!m_keepAbove) { + return; + } #ifdef GXDE_HAVE_LAYERSHELLQT if (QGuiApplication::platformName().startsWith(QLatin1String("wayland"))) { 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() { Config::instance()->set(QStringLiteral("todo/rightMargin"), m_rightMargin); @@ -332,6 +364,13 @@ void TodoWindow::saveMargins() void TodoWindow::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton && event->position().y() < 48) { + if (!m_keepAbove) { + if (windowHandle()) { + windowHandle()->startSystemMove(); + } + event->accept(); + return; + } m_dragging = true; m_dragStart = event->position().toPoint(); m_dragStartRight = m_rightMargin; diff --git a/src/todo/todowindow.h b/src/todo/todowindow.h index 5699993..ac3cfd3 100644 --- a/src/todo/todowindow.h +++ b/src/todo/todowindow.h @@ -28,11 +28,15 @@ public: void refreshLayout(); protected: + void contextMenuEvent(QContextMenuEvent *event) override; void showEvent(QShowEvent *event) override; void mousePressEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; +signals: + void recreateRequested(); + private: void setupWindow(); void setupUi(); @@ -45,6 +49,7 @@ private: QLabel *m_countLabel = nullptr; int m_rightMargin = 16; int m_topMargin = 72; + bool m_keepAbove = true; bool m_dragging = false; QPoint m_dragStart; int m_dragStartRight = 0; diff --git a/translations/gxde-ai-see_en.ts b/translations/gxde-ai-see_en.ts index 4a1b1bc..10ad898 100644 --- a/translations/gxde-ai-see_en.ts +++ b/translations/gxde-ai-see_en.ts @@ -1,6 +1,6 @@ - + App @@ -14,6 +14,21 @@ 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. + + AskTile + + + 问问 +小埃同学 + Ask +AI See + + + + 把内容放到这里:新开一个对话并填入内容 + Drop here to start a new conversation with this content + + ChatClient @@ -32,168 +47,224 @@ The API key is invalid or expired + + ChatMessageList + + + 埃 + AI + + + + 你好,我是小埃同学 + Hi, I'm AI See + + + + 把文件、链接或文字拖进窗口,或者直接输入问题。 +第一次使用请在左下角「设置」里填入 DeepSeek API Key。 + 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. + + + + ChatSidebar + + + 重命名对话 + Rename conversation + + + + 删除选中 + Delete selected + + ChatWindow - - + + + + + 小埃同学 AI See - + 工具调用次数过多,已停止 Too many tool calls, stopped - + ⚠️ %1 ⚠️ %1 - - 把内容拖进对话即可提问 - Drop content into a conversation to ask about it - - - + + 新建对话 + New conversation - - ⋯ 更多 - ⋯ More - - - + 压缩上下文 Compress context - + 重试上一条 Retry last message - + 重新加载 MCP Reload MCP - + 没有配置 MCP 服务器 No MCP server configured - + 关于 About - - 设置 - Settings - - - + 输入消息,Ctrl+Enter 发送;也可以把文件/文字拖进窗口 Type a message, Ctrl+Enter to send; you can also drop files or text here - + 发送 Send - - + + + 新对话 New conversation - - 你 - You - - - - - 小埃 - AI See - - - + 正在压缩上下文… Compressing context… - + 编辑消息 Edit message - + %1 · 分支 %1 · branch - - 关于小埃同学 - About AI See + + ⚙ 设置 + ⚙ Settings - - <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> - <h3>GXDE AI See</h3><p>Version %1</p><p>The AI assistant for the GXDE desktop: portal, content factory, todo and chat.</p><p>License: GPL-3.0-or-later<br/>Repository: <a href='https://gitee.com/GXDE-OS/gxde-ai-see'>gitee.com/GXDE-OS/gxde-ai-see</a></p> + + Enter 换行 · Ctrl+Enter 发送 · 支持拖入文件/链接/图片 + Enter for newline · Ctrl+Enter to send · files, links and images can be dropped here - + + 重命名对话 + Rename conversation + + + + 名称 + Name + + + + GXDE 桌面的 AI 助手:传送门、内容工厂、待办与对话。 + The AI assistant for the GXDE desktop: portal, content factory, todo and chat. + + + 小埃同学设置 AI See settings - + DeepSeek API Key DeepSeek API key - + API 地址 API endpoint - + + 获取模型列表 + Fetch models + + + 模型 Model - + + 获取失败 + Fetch failed + + + + 已更新 + Updated + + + 上下文条数 Context messages - + + 128k + 128k + + + + 1024k + 1024k + + + + 上下文长度 + Context window + + + 超出后自动压缩旧消息 Automatically compress older messages when exceeded - + MCP 服务器(JSON) MCP servers (JSON) - + 系统提示 System prompt - + 还没有账号?前往 <a href='https://platform.deepseek.com'>platform.deepseek.com</a> 开通并创建 API Key。Key 只保存在本机配置文件中。 No account yet? Sign up and create an API key at <a href='https://platform.deepseek.com'>platform.deepseek.com</a>. The key is stored only in the local configuration file. - + MCP 配置不是合法的 JSON 数组,已忽略 The MCP configuration is not a valid JSON array; ignored @@ -201,13 +272,13 @@ ContentFactory - - + + 拖到这里,按类型处理 Drop here to process by type - + 拖到动作上松手 Drop onto an action @@ -241,211 +312,231 @@ - PortalPanel + MessageRow - - GXDE 小埃同学 - GXDE AI See + + + 你 + You - - - %1 项 - %1 items + + 埃 + AI - - 移动到目标窗口上松手 - Move onto a target window and release + + 小埃 + AI See - - 当前无已打开的窗口 - No open windows - - - - 当前环境不支持传送门 - The portal is not supported in this environment - - - - 暂存区 - Staging - - - - 全选 - Select all - - - - 反选 - Invert - - - - 删除选中 - Delete selected - - - + 复制 Copy - - + + + 编辑 + Edit + + + + 分支 + Branch + + + + 重试 + Retry + + + + PortalPanel + + + GXDE 小埃同学 + GXDE AI See + + + + + %1 项 + %1 items + + + + 移动到目标窗口上松手 + Move onto a target window and release + + + + 当前无已打开的窗口 + No open windows + + + + 当前环境不支持传送门 + The portal is not supported in this environment + + + + 暂存区 + Staging + + + + 全选 + Select all + + + + 反选 + Invert + + + + 删除选中 + Delete selected + + + + 复制 + Copy + + + + 已复制 Copied - + 真实地址 Real address - - 分词(%1) - Segmented (%1) - - - + 提取链接(%1) Links (%1) - - 未找到语音朗读程序 - No speech program found - - - - 开始朗读 - Reading aloud - - - + 已添加到待办 Added to todo - + 没有可保存的图片 No image to save - + 保存图片 Save image - + 图片 (*.png *.jpg) Images (*.png *.jpg) - + 已保存:%1 Saved: %1 - + 翻译接口将在小埃同学接入后可用 Translation will be available once the AI backend is wired up - + 该能力尚未接入 GXDE Daemon This capability is not wired to GXDE Daemon yet - + 未知操作:%1 Unknown action: %1 - + 固定位置 Pin position - + 恢复默认位置 Restore default position - + 固定大小 Pin size - + 全屏时隐藏 Hide on fullscreen - + 开机自启与后台保活 Start at login and keep alive - + 清空暂存区 Clear staging area - + + 隐藏 + Hide + + + 关于 About - + 退出 Quit - + 已开启开机自启与保活 Startup and keep-alive enabled - + 已关闭开机自启与保活 Startup and keep-alive disabled - - 关于小埃同学 - About AI See - - - - <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> - <h3>GXDE AI See</h3><p>Version %1</p><p>Portal, content factory, todo and AI See chat.</p><p>GPL-3.0-or-later · <a href='https://gitee.com/GXDE-OS/gxde-ai-see'>gxde-ai-see</a></p> + + 传送门、内容工厂、待办与小埃同学对话。 + Portal, content factory, todo and AI See chat. PortalZone - - 问问小埃 → - Ask AI See → - - - + 传送门 Portal - + 拖到这里 选择窗口跃迁 Drop here to choose a window - + 悬停在「传送门」上,再移动到目标窗口 Hover the portal, then move onto a target window @@ -458,25 +549,141 @@ to choose a window No API key configured yet. Open Settings and enter your DeepSeek API key. - + 文件 File - + 图片 Image - + 文字 · %1 字 Text · %1 chars + + + 关于小埃同学 + About AI See + + + + GXDE 小埃同学 + GXDE AI See + + + + SegmentDialog + + + 分词 + Segment + + + + 按住并滑过词语可以连续选择,选中后复制。 + Press and slide across words to select several, then copy. + + + + 全选 + Select all + + + + 清空 + Clear + + + + 已选 0 个 + 0 selected + + + + 复制选中 + Copy selected + + + + 关闭 + Close + + + + 已选 %1 个 + %1 selected + + + + 还没有选中词语 + No words selected + + + + 已复制 %1 个 + Copied %1 + + + + SpeechDialog + + + 朗读 + Read aloud + + + + 语速 + Speed + + + + 开始朗读 + Start + + + + 停止 + Stop + + + + 关闭 + Close + + + + 未找到 spd-say 或 espeak-ng + spd-say or espeak-ng not found + + + + 朗读失败:%1 + Speech failed: %1 + + + + 朗读结束 + Finished + + + + 正在朗读… + Speaking… + + + + 已停止 + Stopped + StagingView - + 把文件、文字或图片拖到这里 Drop files, text or images here @@ -484,42 +691,48 @@ to choose a window TodoWindow - - + + 待办 Todo - - + + %1 项 %1 items - + + + - - + + 新建待办 New todo - + × × - + + 隐藏 Hide - + 要做的事… Something to do… + + + 置顶 + Always on top +