From 03b535f5a4698e94e7e7d33d1a67d259557c5a0f Mon Sep 17 00:00:00 2001
From: Hao19911125 <99091644+Hao19911125@users.noreply.github.com>
Date: Sun, 5 Apr 2026 14:36:50 +0800
Subject: [PATCH] Add compact notice display mode
---
index.js | 31 ++++
notice.js | 40 ++++-
panel.html | 24 +++
panel.js | 15 ++
plans/notice-display-mode-plan.md | 267 ++++++++++++++++++++++++++++++
5 files changed, 375 insertions(+), 2 deletions(-)
create mode 100644 plans/notice-display-mode-plan.md
diff --git a/index.js b/index.js
index 217d3fd..9c01258 100644
--- a/index.js
+++ b/index.js
@@ -496,6 +496,7 @@ const defaultSettings = {
compressionEveryN: 10,
// UI 面板
+ noticeDisplayMode: "normal", // normal|compact
panelTheme: "crimson", // 面板主题 crimson|cyan|amber|violet
};
@@ -1054,6 +1055,30 @@ function syncStageNoticeAbortAction(stage) {
});
}
+function getStageNoticeDisplayMode(level = "info") {
+ const configuredMode = getSettings()?.noticeDisplayMode;
+ if (
+ configuredMode === "compact" &&
+ level !== "warning" &&
+ level !== "error"
+ ) {
+ return "compact";
+ }
+ return "normal";
+}
+
+function refreshVisibleStageNotices() {
+ for (const stage of Object.keys(stageNoticeHandles)) {
+ const handle = stageNoticeHandles[stage];
+ if (!handle || handle.isClosed?.()) continue;
+ const status = getStageUiStatus(stage);
+ if (!status) continue;
+ updateStageNotice(stage, status.text, status.meta, status.level, {
+ title: getStageNoticeTitle(stage),
+ });
+ }
+}
+
function updateStageNotice(
stage,
text,
@@ -1069,6 +1094,7 @@ function updateStageNotice(
const input = {
title,
message,
+ displayMode: options.displayMode || getStageNoticeDisplayMode(noticeLevel),
level: noticeLevel,
busy,
persist,
@@ -5483,6 +5509,7 @@ function updateModuleSettings(patch = {}) {
"hideOldMessagesKeepLastN",
]);
const recallUiKeys = new Set(["recallCardUserInputDisplayMode"]);
+ const noticeUiKeys = new Set(["noticeDisplayMode"]);
const settings = getSettings();
Object.assign(settings, patch);
extension_settings[MODULE_NAME] = settings;
@@ -5543,6 +5570,10 @@ function updateModuleSettings(patch = {}) {
schedulePersistedRecallMessageUiRefresh(30);
}
+ if (Object.keys(patch).some((key) => noticeUiKeys.has(key))) {
+ refreshVisibleStageNotices();
+ }
+
scheduleServerSettingsSave();
return settings;
}
diff --git a/notice.js b/notice.js
index 19e7e20..87c472a 100644
--- a/notice.js
+++ b/notice.js
@@ -60,6 +60,16 @@ function ensureStyle(doc) {
-webkit-backdrop-filter: blur(10px) saturate(125%);
}
+ .st-bme-notice[data-layout="compact"] {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ width: fit-content;
+ max-width: 100%;
+ align-self: flex-end;
+ padding: 10px;
+ }
+
.st-bme-notice::after {
content: "";
position: absolute;
@@ -87,6 +97,7 @@ function ensureStyle(doc) {
background: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.14);
box-shadow: inset 0 1px 1px rgba(255, 255, 255, 0.16);
+ flex-shrink: 0;
}
.st-bme-notice[data-busy="true"] .st-bme-notice__icon {
@@ -97,6 +108,12 @@ function ensureStyle(doc) {
min-width: 0;
}
+ .st-bme-notice[data-layout="compact"] .st-bme-notice__content {
+ display: flex;
+ align-items: center;
+ min-width: 0;
+ }
+
.st-bme-notice__title {
margin: 0;
font-size: 17px;
@@ -106,6 +123,14 @@ function ensureStyle(doc) {
color: #f0f6ff;
}
+ .st-bme-notice[data-layout="compact"] .st-bme-notice__title {
+ font-size: 16px;
+ line-height: 1.2;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
.st-bme-notice__message {
margin: 4px 0 0;
font-size: 14px;
@@ -126,6 +151,12 @@ function ensureStyle(doc) {
-webkit-mask-image: linear-gradient(90deg, transparent 0%, black 6%, black 88%, transparent 100%);
}
+ .st-bme-notice[data-layout="compact"] .st-bme-notice__message,
+ .st-bme-notice[data-layout="compact"] .st-bme-notice__actions,
+ .st-bme-notice[data-layout="compact"] .st-bme-notice__progress {
+ display: none !important;
+ }
+
.st-bme-notice__actions {
display: flex;
gap: 8px;
@@ -170,6 +201,7 @@ function ensureStyle(doc) {
line-height: 1;
cursor: pointer;
transition: background 140ms ease;
+ flex-shrink: 0;
}
.st-bme-notice__close:hover,
@@ -280,8 +312,11 @@ function getIcon(level) {
function applyNoticeState(item, input, progress) {
const level = input.level || "info";
+ const displayMode = input.displayMode === "compact" ? "compact" : "normal";
+ const isCompact = displayMode === "compact";
item.dataset.level = level;
item.dataset.busy = input.busy ? "true" : "false";
+ item.dataset.layout = displayMode;
const icon = item.querySelector(".st-bme-notice__icon");
if (icon) {
@@ -296,6 +331,7 @@ function applyNoticeState(item, input, progress) {
const message = item.querySelector(".st-bme-notice__message");
if (message) {
message.textContent = input.message || "";
+ message.hidden = isCompact || !String(input.message || "").trim();
if (input.marquee) {
message.classList.add("st-bme-notice__message--marquee");
} else {
@@ -306,7 +342,7 @@ function applyNoticeState(item, input, progress) {
const actionWrap = item.querySelector(".st-bme-notice__actions");
const actionButton = item.querySelector(".st-bme-notice__action");
if (actionWrap && actionButton) {
- if (input.action?.label) {
+ if (!isCompact && input.action?.label) {
actionWrap.style.display = "";
actionButton.style.display = "";
actionButton.textContent = input.action.label;
@@ -319,7 +355,7 @@ function applyNoticeState(item, input, progress) {
}
}
- if (input.persist) {
+ if (input.persist || isCompact) {
progress.style.display = "none";
progress.style.animationDuration = "";
} else {
diff --git a/panel.html b/panel.html
index c43dc56..d1f0143 100644
--- a/panel.html
+++ b/panel.html
@@ -1055,6 +1055,30 @@
+
+
+
+
提示信息
+
+ 控制提取、召回等顶部通知的显示样式。
+
+
+
+
+
+
+
+
+ 精简模式会将工作中的提示压缩为标题卡片;错误和警告仍显示完整内容,避免关键信息被隐藏。
+
+
+
diff --git a/panel.js b/panel.js
index 45fb306..7cf9a60 100644
--- a/panel.js
+++ b/panel.js
@@ -1711,6 +1711,10 @@ function _refreshConfigTab() {
"bme-setting-recall-card-user-input-display-mode",
settings.recallCardUserInputDisplayMode ?? "beautify_only",
);
+ _setInputValue(
+ "bme-setting-notice-display-mode",
+ settings.noticeDisplayMode ?? "normal",
+ );
_setInputValue("bme-setting-extract-every", settings.extractEvery ?? 1);
_setInputValue(
@@ -2053,6 +2057,17 @@ function _bindConfigControls() {
});
recallCardUserInputDisplayModeEl.dataset.bmeBound = "true";
}
+ const noticeDisplayModeEl = document.getElementById(
+ "bme-setting-notice-display-mode",
+ );
+ if (noticeDisplayModeEl && noticeDisplayModeEl.dataset.bmeBound !== "true") {
+ noticeDisplayModeEl.addEventListener("change", () => {
+ _patchSettings({
+ noticeDisplayMode: noticeDisplayModeEl.value || "normal",
+ });
+ });
+ noticeDisplayModeEl.dataset.bmeBound = "true";
+ }
bindNumber("bme-setting-extract-every", 1, 1, 50, (value) =>
_patchSettings({ extractEvery: value }),
diff --git a/plans/notice-display-mode-plan.md b/plans/notice-display-mode-plan.md
new file mode 100644
index 0000000..bb51429
--- /dev/null
+++ b/plans/notice-display-mode-plan.md
@@ -0,0 +1,267 @@
+# ST-BME 提示信息“正常 / 精简”模式计划
+
+## 目标
+
+在“配置” -> “功能开关”里新增一个“提示信息”设置项,让用户选择通知展示模式:
+
+- `正常`:保持当前行为不变
+- `精简`:通知仅显示标题,例如 `ST-BME 提取`、`ST-BME 召回`
+
+默认值为 `正常`。该设置需要在 PC 端和手机端的设置入口都能看到并生效。
+
+## 现状梳理
+
+### 1. 提示信息的实际渲染位置
+
+- `notice.js`
+ - 负责创建和更新通知 DOM
+ - 当前结构固定包含:图标、标题、正文、动作按钮区域、关闭按钮、进度条
+ - 移动端只缩小了宿主宽度,没有缩小卡片内容结构
+
+### 2. 通知内容从哪里来
+
+- `index.js`
+ - `updateStageNotice(...)` 会把 `text + meta` 拼成 `message`
+ - `showManagedBmeNotice(...)` 统一负责显示提取 / 向量 / 召回 / 历史恢复通知
+- `ui-status.js`
+ - `getStageNoticeTitle(...)` 负责输出 `ST-BME 提取`、`ST-BME 召回` 等标题
+
+### 3. 设置从哪里定义和持久化
+
+- `index.js`
+ - `defaultSettings` 是设置默认值来源
+ - `mergePersistedSettings(...)` / `getPersistedSettingsSnapshot(...)` 会自动把新增设置纳入持久化
+- `panel.js`
+ - `_refreshConfigTab()` 负责把设置值回填到 UI
+ - `_bindConfigControls()` 负责把 UI 改动写回设置
+- `panel.html`
+ - “功能开关” section 是实际设置表单
+
+### 4. PC / 手机端设置入口的关系
+
+- `panel.html` 里桌面端和手机端各有一套“配置导航按钮”
+- 但它们切换的是同一套 `bme-config-section`
+- 也就是说:
+ - 真正的设置项表单只需要在“功能开关” section 加一次
+ - 该项天然会同时出现在 PC 和手机端
+
+## 关键发现
+
+仅仅把正文文本隐藏,还不足以让精简通知“变窄”。
+
+原因是 `notice.js` 里的通知宿主 `#st-bme-notice-host` 使用纵向 flex 布局,而子项默认会被拉伸到宿主宽度。现在宿主在移动端宽度是 `calc(100vw - 16px)`,所以即使正文为空,卡片依然可能占满整行。
+
+结论:
+
+- 精简模式不仅要隐藏正文和动作区
+- 还必须给通知卡片增加“按内容宽度收缩”的样式
+
+这是这个需求里最容易漏掉的点。
+
+## 推荐设计
+
+### 设置字段
+
+建议新增设置字段:
+
+- key: `noticeDisplayMode`
+- 可选值: `"normal"` / `"compact"`
+- 默认值: `"normal"`
+
+命名理由:
+
+- 语义直接,对应“提示信息显示模式”
+- 以后如果要扩展为更多通知布局,也容易继续演进
+
+### 设置 UI
+
+建议放在“配置” -> “功能开关”里,作为一个 `select`,而不是复选框。
+
+建议文案:
+
+- 标题:`提示信息`
+- 说明:`控制提取 / 召回等顶部通知的显示样式`
+- 选项:
+ - `normal` -> `正常`
+ - `compact` -> `精简(仅显示标题)`
+
+### 精简模式的推荐行为
+
+推荐在 `精简` 模式下:
+
+- 保留:标题
+- 保留:左侧状态图标
+- 保留:关闭按钮
+- 隐藏:正文文本
+- 隐藏:meta 信息
+- 隐藏:动作按钮区,例如“终止提取”“终止召回”
+- 隐藏:底部进度条
+
+这样可以最大化缩小高度,同时保留最基本的状态识别和手动关闭能力。
+
+## 一个需要先确认的设计点
+
+`warning` / `error` 通知是否也要强制进入“仅标题”模式?
+
+我的推荐方案:
+
+- `running` / `info` / `success`:遵循 `精简`,只显示标题
+- `warning` / `error`:仍显示正文,避免重要报错信息被吃掉
+
+理由:
+
+- 用户截图里最占空间的是“工作中”的持续通知
+- 真正需要完整文本的,通常是失败原因或警告信息
+
+如果你更想要“精简就是所有通知一律只显示标题”,也能做,但我认为可用性会更差一些。
+
+## 预期改动范围
+
+### 1. `index.js`
+
+- 在 `defaultSettings` 增加 `noticeDisplayMode: "normal"`
+- 在通知更新逻辑里读取该设置
+- 显式采用“Option A”:
+ - `index.js` 在 `updateStageNotice(...)` 里把 `displayMode` 放进传给 `showManagedBmeNotice(...)` 的 `input` 对象
+ - `notice.js` 只读取 `input.displayMode` 控制渲染和样式,不直接读取全局设置
+ - 保持 `notice.js` 为纯渲染模块,不和 settings 持久化层耦合
+- 若采用“错误/警告保留正文”的推荐方案,则在这里按 level 做分流
+- 当用户切换 `noticeDisplayMode` 时,补一条“刷新当前可见通知”的处理
+ - 目标:如果用户在通知仍可见时切换模式,现有通知也能立即切换,而不是等下次状态更新
+ - 实现可放在设置更新后,对当前 stage notice 进行一次统一 `update`
+
+### 2. `notice.js`
+
+- 扩展通知输入参数,支持 `displayMode` 或 `compact`
+- 渲染时根据模式:
+ - 正常模式:保持现状
+ - 精简模式:不渲染正文和动作区,或渲染后隐藏
+- 增加精简态 class / data attribute,例如:
+ - `data-layout="compact"`
+- 新增 compact 样式:
+ - 卡片宽度按内容收缩
+ - 卡片高度缩成单行或接近单行
+ - 维持移动端可点击性和可读性
+ - 明确让 compact 通知右对齐
+ - 首选:给 compact 卡片本身加 `align-self: flex-end`
+ - 备选:给 host 在 compact 态下加 `align-items: flex-end`
+ - compact 布局可直接切成更自然的横向紧凑结构
+ - 例如从当前 grid 调整为单行 `flex`
+ - 避免 `close` 按钮在极窄宽度下被 grid 挤得别扭
+ - compact 模式显式隐藏进度条,避免小卡片底部出现细长条影响观感
+
+### 3. `panel.html`
+
+- 在“功能开关” section 里新增“提示信息”配置卡或配置行
+- 位置建议放在较靠前区域,因为它属于明显的 UI 行为开关
+
+### 4. `panel.js`
+
+- `_refreshConfigTab()` 里回填 `noticeDisplayMode`
+- `_bindConfigControls()` 里监听该 `select` 的变更并调用 `_patchSettings(...)`
+
+## 样式实现思路
+
+推荐采用“精简卡片单独收缩”的方式,而不是修改整个通知宿主。
+
+建议方向:
+
+- 正常卡片:继续占用现有宽度逻辑
+- 精简卡片:
+ - `align-self: flex-end`
+ - `width: fit-content`
+ - `max-width: 100%`
+ - 更紧凑的 padding / gap
+ - 必要时直接切成单行 `flex` 布局,而不是沿用三列 grid
+
+这样可以避免:
+
+- 一条精简通知把整个通知堆栈的布局都改坏
+- 正常模式和精简模式互相影响
+
+补充说明:
+
+- 这里真正需要注意的是“host 是全宽,而 compact 卡片要在 host 内收缩并靠右”
+- 因此不能只写 `width: fit-content`
+- 还必须同时处理横向对齐
+- 纠正一下术语:这里如果要改 host,应该是 `align-items`,不是 `align-self`
+
+## 风险与兼容性
+
+### 风险 1:终止按钮被隐藏后,工作中通知无法直接中断
+
+这是“只显示标题”带来的天然代价。
+
+处理方式建议:
+
+- 先按用户描述执行精简:隐藏动作按钮
+- 在计划评审阶段明确接受这个取舍
+
+### 风险 2:只隐藏正文但不改宽度,视觉上仍然太大
+
+这是本需求最核心的实现风险。必须在样式上单独处理 compact 卡片宽度。
+
+### 风险 3:切换设置后,当前可见通知不立刻刷新
+
+如果只让后续新通知读取新模式,那么用户在设置页切换“正常 / 精简”时,已显示的通知会出现“要等下一次状态变更才切换”的延迟感。
+
+处理方式建议:
+
+- 把它当作一个小型 UI 刷新点纳入实现
+- 设置更新时,对当前仍存在的 stage notice 重新执行一次 `update`
+
+### 风险 4:旧配置兼容
+
+风险较低。因为默认设置合并机制已经存在,只要新增默认值即可让旧配置自动回退到 `normal`。
+
+## 建议验证点
+
+### 功能验证
+
+- 默认安装或旧配置升级后,提示信息模式应为 `正常`
+- 在 PC 端设置面板中能看到“提示信息”选项
+- 在手机端设置面板中也能看到同一个选项
+- 切到 `精简` 后,提取 / 召回通知只显示标题
+- 切回 `正常` 后,恢复当前完整通知样式
+- 通知可见时切换设置,已显示的通知也会同步切换样式
+
+### 视觉验证
+
+- 手机上精简通知高度明显缩小
+- 手机上精简通知宽度不再铺满整行
+- 手机上精简通知应靠右显示,而不是缩小后仍贴左
+- 多条通知叠加时,布局仍然稳定
+- PC 端正常模式无回归
+
+### 行为验证
+
+- 运行中通知仍能正常更新标题
+- 自动关闭逻辑不受影响
+- 关闭按钮不受影响
+- compact 模式下进度条已按预期隐藏
+- 若保留“错误/警告显示正文”策略,则需单独验证错误提示仍完整可见
+
+## 实施顺序
+
+1. 增加设置字段与默认值
+2. 在功能开关 UI 中加入“提示信息”选择器
+3. 将选择器和设置读写绑定起来
+4. 给通知渲染层增加 compact 模式
+5. 调整 compact 样式,确保真正按内容收缩
+6. 验证 PC / 手机端、正常 / 精简、运行 / 完成 / 异常几类通知
+
+## 我目前的结论
+
+这个需求本身不复杂,真正要小心的是两件事:
+
+- “PC 和手机端都要有设置”其实主要是配置入口问题,不是双份业务实现
+- “精简后通知真的变小”不能只靠删文本,必须同时改通知卡片的宽度收缩策略
+
+这次复审后,我会把实现重点再收敛成四个明确点:
+
+- `displayMode` 通过 `updateStageNotice(...) -> input.displayMode -> notice.js` 这条链路传递
+- compact 不仅隐藏正文,也隐藏进度条
+- compact 需要明确右对齐,不能只是 `fit-content`
+- 用户切换设置时,当前可见通知也应立即刷新
+
+如果后续开始实现,我会优先按这个计划走,并把“错误/警告是否保留正文”作为唯一需要最终拍板的交互点。