Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions devel/0185.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# [0185] Chat 侧边栏欢迎内容居中偏上调整

## 相关文档

- [dddd.md](dddd.md) - 任务文档模板

## 任务相关的代码文件

- `src/Plugins/Qt/qt_chat_tab_widget.cpp`
- `src/Plugins/Qt/qt_chat_tab_widget.hpp`
- `tests/Plugins/Qt/qt_chat_tab_widget_test.cpp`

## 如何测试

### 确定性测试(单元测试)

编译并运行 Chat Tab Widget 单元测试:

```bash
xmake b qt_chat_tab_widget_test
xmake r qt_chat_tab_widget_test
```

预期结果:全部通过。

### 非确定性测试(UI 验证)

#### A. Chat 标签页欢迎页位置

1. 切换到 Chat 标签页(确保没有活跃会话,显示欢迎页)
2. 观察:"欢迎使用 Liii STEM" 和输入框应位于整个软件界面的竖直方向中心稍稍偏上
3. 调整窗口高度(放大/缩小)
4. 观察:欢迎内容应随窗口高度自适应,始终保持中心偏上

#### B. Chat 侧边栏(dock 模式)欢迎页位置

1. 打开一个 TMU 文档或 PDF 文档
2. 点击右上角 AI 对话浮动按钮,打开右侧 dock
3. 确保 dock 中显示的是欢迎页(无活跃会话或新建空白会话)
4. 观察:"欢迎使用 Liii STEM" 和输入框应位于整个软件界面的竖直方向中心稍稍偏上
5. 调整主窗口高度
6. 观察:欢迎内容应随窗口高度自适应,始终保持中心偏上

#### C. 会话模式不受影响

1. 在 Chat 标签页或 dock 中发送一条消息
2. 观察:消息区域应贴顶显示(仅有少量顶部偏移),输入框在底部
3. 观察:不应出现大片空白区域挤压消息区

## 如何提交

提交前编译项目:

```bash
xmake b stem
```

提交前运行单元测试:

```bash
xmake b qt_chat_tab_widget_test
xmake r qt_chat_tab_widget_test
```

## What

调整 `ChatConversationPanel` 欢迎模式的竖直布局策略,使其在 Chat 标签页和 Chat 侧边栏(dock)模式下都能将"欢迎使用 Liii STEM"和输入框置于整个软件界面的中心偏上位置。

具体修改:
1. 将 `contentLayout` 顶部固定偏移(240px)改为 0,消除硬编码偏移。
2. 重写 `ChatConversationPanel::resizeEvent()`,在欢迎模式下根据面板高度动态计算顶部 spacer 高度,使内容位于容器高度的 2/7 处(中心稍稍偏上)。
3. 进入会话模式时,顶部 spacer 恢复为固定小偏移(8px),不影响消息区域展开。

## Why

当前实现使用固定的 240px 顶部 spacer(`kWelcomeTopOffsetY`):
- 在 Chat 标签页模式下,由于标签页容器高度相对较小,240px 偏移使内容看起来偏上。
- 在 Chat 侧边栏(dock)模式下,dock 高度通常等于主窗口高度,240px 偏移不足以将内容推到偏上位置,导致内容落在 dock 的竖直方向中心,与 Chat 标签页的视觉效果不一致。

用户期望两种模式下的欢迎页具有统一的视觉位置:整个软件界面的中心稍稍偏上。

## How

### 布局策略调整

原方案:
```
contentLayout: [topSpacer_ 240px Fixed] -> [topPanel stretch 1, AlignTop]
```

新方案(欢迎模式):
```
contentLayout: [topSpacer_ height*2/7 Fixed] -> [topPanel stretch 1, AlignTop]
topLayout: [welcomeTitle_]
-> [sessionTitle_]
-> [messageFrame_ hidden]
-> [inputWrap]
```

新方案(会话模式):
```
contentLayout: [topSpacer_ 8px Fixed] -> [topPanel stretch 1, AlignTop]
topLayout: [sessionTitle_]
-> [messageFrame_ stretch 1 visible]
-> [inputWrap]
```

`resizeEvent` 中按 `height() * 2 / 7 - kContentMarginY` 计算顶部 spacer 高度,确保内容始终位于容器高度的 2/7 处(中心稍稍偏上)。窗口大小变化时自动重新计算,无需内部弹性空间。进入会话模式后,`topSpacer_` 收缩为固定小偏移,不影响消息区域展开。
22 changes: 17 additions & 5 deletions src/Plugins/Qt/qt_chat_tab_widget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,7 @@ ChatConversationPanel::setup_ui () {
contentLayout->setContentsMargins (0, DpiUtils::scaled (kContentMarginY), 0,
DpiUtils::scaled (kContentMarginY));
contentLayout->setSpacing (DpiUtils::scaled (kContentSpacing));
topSpacer_= new QSpacerItem (0, DpiUtils::scaled (kWelcomeTopOffsetY),
QSizePolicy::Minimum, QSizePolicy::Fixed);
topSpacer_= new QSpacerItem (0, 0, QSizePolicy::Minimum, QSizePolicy::Fixed);
contentLayout->addSpacerItem (topSpacer_);

QWidget* topPanel = new QWidget (this);
Expand Down Expand Up @@ -315,9 +314,8 @@ void
ChatConversationPanel::enterConversationMode () {
if (conversationMode_) return;

conversationMode_ = true;
const int startOffset= DpiUtils::scaled (kWelcomeTopOffsetY);
const int endOffset = DpiUtils::scaled (kConversationTopOffsetY);
conversationMode_ = true;
const int endOffset= DpiUtils::scaled (kConversationTopOffsetY);

if (messageFrame_) {
QGraphicsOpacityEffect* messageEffect=
Expand Down Expand Up @@ -359,6 +357,20 @@ ChatConversationPanel::enterConversationMode () {
}
}

void
ChatConversationPanel::resizeEvent (QResizeEvent* event) {
QWidget::resizeEvent (event);
if (conversationMode_ || !topSpacer_) return;
int targetOffset= height () * 2 / 7 - DpiUtils::scaled (kContentMarginY);
if (targetOffset < 0) targetOffset= 0;
topSpacer_->changeSize (0, targetOffset, QSizePolicy::Minimum,
QSizePolicy::Fixed);
if (layout ()) {
layout ()->invalidate ();
layout ()->activate ();
}
}

void
ChatConversationPanel::focusInput () {
url inBufUrl= ChatSessionManager::inputBufferUrl (sessionId_);
Expand Down
2 changes: 2 additions & 0 deletions src/Plugins/Qt/qt_chat_tab_widget.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ class ChatConversationPanel : public QWidget {
protected:
/// 事件过滤器:拦截 Enter 键触发发送
bool eventFilter (QObject* watched, QEvent* event) override;
/// 欢迎模式下按容器高度比例调整顶部偏移
void resizeEvent (QResizeEvent* event) override;

private:
/// 构建面板 UI 布局
Expand Down
2 changes: 2 additions & 0 deletions tests/Plugins/Qt/qt_chat_tab_widget_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@
#include "Qt/qt_utilities.hpp"
#include "base.hpp"
#include <QInputMethodEvent>
#include <QLabel>
#include <QLineEdit>
#include <QMenu>
#include <QMouseEvent>
#include <QPushButton>
#include <QSignalSpy>
#include <QWheelEvent>
#include <QWidget>
#include <QtTest/QtTest>

using namespace moebius;
Expand Down
Loading