feat(chat): DTK 主窗口 + 新模型;fix(panel): 菜单位置/工厂挤压与误隐藏

- ChatWindow 改为 DMainWindow + DTitlebar,图标按 DPR 取图
- DeepSeek 模型改为 deepseek-flash / deepseek-v4-pro,设置里可拉取 /models
- 内容工厂右侧常驻「问问小埃同学」大按钮,移除传送门上的常驻入口
- 工厂出现时面板整体上移预留空间,不再挤压传送门
- 工厂隐藏改为检查指针是否仍在面板内,拖进工厂不再消失
- 右键菜单超出屏幕时向上弹出
- 移除 512 图标(由 293px 源图放大,发糊)
This commit is contained in:
shenmo
2026-10-30 21:03:25 +08:00
parent 798f5aa7ce
commit 4032de318c
12 changed files with 175 additions and 40 deletions
+88 -9
View File
@@ -39,6 +39,7 @@
#include <QMimeData>
#include <QPlainTextEdit>
#include <QPushButton>
#include <DTitlebar>
#include <QRegularExpression>
#include <QSaveFile>
#include <QScreen>
@@ -46,6 +47,10 @@
#include <QShortcut>
#include <QTextCursor>
#include <QTextBrowser>
#include <QIcon>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QUrl>
#include <QUuid>
#include <QVBoxLayout>
@@ -224,10 +229,13 @@ private:
};
ChatWindow::ChatWindow(QWidget *parent)
: QWidget(parent)
: Dtk::Widget::DMainWindow(parent)
, m_client(new ChatClient(this))
{
setWindowTitle(tr("小埃同学"));
titlebar()->setTitle(tr("小埃同学"));
titlebar()->setIcon(QApplication::windowIcon());
titlebar()->setAutoHideOnFullscreen(true);
setMinimumSize(760, 520);
resize(1000, 700);
m_storePath = Config::instance()->dataDir() + QStringLiteral("/chat.json");
@@ -235,6 +243,18 @@ ChatWindow::ChatWindow(QWidget *parent)
setupUi();
McpManager::instance()->reload();
load();
/* DeepSeek renamed the API models; map the old presets once. */
const QString storedModel =
Config::instance()->get(QStringLiteral("chat/model")).toString();
if (storedModel == QStringLiteral("deepseek-chat")) {
Config::instance()->set(QStringLiteral("chat/model"), QStringLiteral("deepseek-flash"));
Config::instance()->sync();
} else if (storedModel == QStringLiteral("deepseek-reasoner")) {
Config::instance()->set(QStringLiteral("chat/model"), QStringLiteral("deepseek-v4-pro"));
Config::instance()->sync();
}
m_current = m_conversations.isEmpty() ? -1 : qBound(0, m_current, m_conversations.size() - 1);
updateSidebar();
renderConversation();
@@ -337,7 +357,9 @@ ChatWindow::ChatWindow(QWidget *parent)
void ChatWindow::setupUi()
{
auto *root = new QHBoxLayout(this);
auto *central = new QWidget(this);
setCentralWidget(central);
auto *root = new QHBoxLayout(central);
root->setContentsMargins(0, 0, 0, 0);
root->setSpacing(0);
@@ -356,8 +378,11 @@ void ChatWindow::setupUi()
brandRow->setSpacing(10);
auto *logo = new QLabel(sidebarPane);
logo->setFixedSize(34, 34);
logo->setPixmap(QApplication::windowIcon().pixmap(34, 34));
logo->setScaledContents(true);
const qreal ratio = logo->devicePixelRatioF();
QPixmap logoPixmap = QApplication::windowIcon().pixmap(
QSize(qRound(34 * ratio), qRound(34 * ratio)));
logoPixmap.setDevicePixelRatio(ratio);
logo->setPixmap(logoPixmap);
logo->setStyleSheet(QStringLiteral("border-radius: 8px;"));
brandRow->addWidget(logo);
@@ -448,7 +473,7 @@ void ChatWindow::setupUi()
m_titleLabel->setStyleSheet(QStringLiteral("color: rgba(255,255,255,0.92);"));
headerLayout->addWidget(m_titleLabel);
m_modelLabel = new QLabel(QStringLiteral("deepseek-chat"), header);
m_modelLabel = new QLabel(QStringLiteral("deepseek-flash"), header);
m_modelLabel->setStyleSheet(QStringLiteral(
"QLabel { color: rgba(160,200,240,0.75); background: rgba(74,144,255,0.13);"
" border-radius: 8px; padding: 2px 8px; font-size: 10px; }"));
@@ -694,7 +719,7 @@ void ChatWindow::updateHeader()
m_titleLabel->setText(valid ? m_conversations.at(m_current).title : tr("小埃同学"));
m_modelLabel->setText(Config::instance()
->get(QStringLiteral("chat/model"),
QStringLiteral("deepseek-chat"))
QStringLiteral("deepseek-flash"))
.toString());
}
@@ -1046,13 +1071,67 @@ void ChatWindow::openSettings()
auto *modelBox = new QComboBox(&dialog);
modelBox->setEditable(true);
modelBox->addItems({ QStringLiteral("deepseek-chat"),
modelBox->addItems({ QStringLiteral("deepseek-flash"),
QStringLiteral("deepseek-reasoner") });
modelBox->setCurrentText(Config::instance()
->get(QStringLiteral("chat/model"),
QStringLiteral("deepseek-chat"))
QStringLiteral("deepseek-flash"))
.toString());
form->addRow(tr("模型"), modelBox);
auto *modelRow = new QHBoxLayout;
modelRow->setSpacing(6);
modelRow->addWidget(modelBox, 1);
auto *fetchModels = new QPushButton(tr("获取模型列表"), &dialog);
modelRow->addWidget(fetchModels);
form->addRow(tr("模型"), modelRow);
connect(fetchModels, &QPushButton::clicked, &dialog, [=, &dialog]() {
QString base = baseEdit->text().trimmed();
while (base.endsWith(QLatin1Char('/'))) {
base.chop(1);
}
if (base.endsWith(QStringLiteral("/chat/completions"))) {
base.chop(int(qstrlen("/chat/completions")));
}
QNetworkRequest request{QUrl(base + QStringLiteral("/models"))};
request.setRawHeader("Authorization",
"Bearer " + keyEdit->text().trimmed().toUtf8());
auto *manager = new QNetworkAccessManager(&dialog);
QNetworkReply *reply = manager->get(request);
fetchModels->setEnabled(false);
connect(reply, &QNetworkReply::finished, &dialog, [=]() {
fetchModels->setEnabled(true);
const QByteArray body = reply->readAll();
const QJsonDocument document = QJsonDocument::fromJson(body);
const QJsonArray models =
document.object().value(QStringLiteral("data")).toArray();
if (reply->error() != QNetworkReply::NoError || models.isEmpty()) {
fetchModels->setText(tr("获取失败"));
fetchModels->setToolTip(QString::fromUtf8(body).left(200));
reply->deleteLater();
return;
}
const QString current = modelBox->currentText();
modelBox->clear();
for (const QJsonValue &value : models) {
const QJsonObject object = value.toObject();
const QString id = object.value(QStringLiteral("id")).toString();
if (id.isEmpty()) {
continue;
}
modelBox->addItem(id);
const QString name = object.value(QStringLiteral("name")).toString();
if (!name.isEmpty()) {
modelBox->setItemData(modelBox->count() - 1, name, Qt::ToolTipRole);
}
}
const int index = modelBox->findText(current);
if (index >= 0) {
modelBox->setCurrentIndex(index);
}
fetchModels->setText(tr("已更新"));
reply->deleteLater();
});
});
auto *contextSpin = new QSpinBox(&dialog);
contextSpin->setRange(4, 200);