feat(changelog): 用户版更新日志 CHANGELOG.md + /v1/changelog + 点版本号弹层(bump 0.55.0)

- 新增仓库根 CHANGELOG.md:面向用户口径(只记可感知变化),回填至 0.30.0 共 25 条
- GET /v1/changelog:公开无鉴权,`## <版本> — <日期>` 正则切段,mtime 缓存热改即生效,无 DB 零 migration
- 前端 changelog.js:右栏底部版本号可点弹更新日志;localStorage 记 last-seen 版本,
  与 /healthz 版本不一致亮红点提示,打开即清;接入 Esc/遮罩关闭栈
- 单测 tests/test_changelog.py 3 项(含真文件格式校验);全量 201 过
- CLAUDE.md 加维护约定:bump 版本若用户可感知顺手补 CHANGELOG 条目

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
caoqianming 2026-07-09 16:49:26 +08:00
parent 8aa6bed79c
commit 5b6adb3746
9 changed files with 330 additions and 6 deletions

108
CHANGELOG.md Normal file
View File

@ -0,0 +1,108 @@
# 更新日志
> 面向用户的更新说明,由 `GET /v1/changelog` 解析后在前端「点版本号」弹层展示。
> 维护口径:只记**用户可感知**的变化("能干什么了"而不是"改了哪个模块"),纯内部重构 / 修复不记,
> 所以不是每个版本号都有条目。条目格式 `## <版本> — <日期>`,新条目加在最上面。
> 工程口径的完整记录见 `PROGRESS.md` / git log。
## 0.55.0 — 2026-07-09
- 点击右下角版本号可查看更新日志(本弹层);有新版本未看时版本号旁会亮红点提示。
## 0.54.1 — 2026-07-09
- 对话顶部新增上下文用量指示环:随时看到当前对话"装了多少",接近上限会变色提醒。
- 超长对话自动整理:早期内容压缩为摘要,目标、结论和关键数据保留,长对话不再因过长而失效。
## 0.53.1 — 2026-07-08
- 新增「审稿回复」能力:上传决定信 / 审稿意见,协助逐条起草回复信、稿件修改清单和修回 cover letter(中英双语)。
- 论文能力增强:新增图表设计规范与投稿前全文审计;英文 SCI 润色新增分层诊断模式。
## 0.53.0 — 2026-07-08
- 支持上传录音文件转文字:单个文件最长 5 小时,自动区分说话人,适合会议 / 访谈记录整理与总结。
## 0.52.0 — 2026-07-08
- 微信 / 企业微信里可以直接发语音消息对话:自动识别为文字并回显「已识别」,再进入对话。
## 0.49.0 — 2026-07-07
- 语音输入升级为流式识别:边说边出字,松开即得到完整文本,不再等待。
## 0.48.0 — 2026-07-07
- 语音按钮支持「按住说话」:按住录音、松开转写、上滑取消(手机 / 电脑通用);快速点一下则进入免持连续录音。
## 0.47.0 — 2026-07-07
- 网页端新增语音输入:点麦克风按钮说话,识别结果插入输入框(不自动发送,可先编辑)。
## 0.46.2 — 2026-07-07
- 历史消息浏览优化:翻页与目录跳转按提问轮次对齐,不再出现半截的工具消息。
## 0.46.0 — 2026-07-07
- 企业微信新成员未绑定账号时发消息,会自动收到绑定指引,不再无响应。
## 0.45.0 — 2026-07-07
- 手机端显示优化:弹出菜单不再超出屏幕、按钮触控目标加大、管理入口位置统一。
## 0.44.0 — 2026-07-07
- 企业微信内点开应用即自动登录进入,无需再输账号密码。
## 0.43.0 — 2026-07-07
- 对话过程可视化:显示模型的思考过程(可折叠),各阶段实时计时,工具执行显示进度与耗时,不再长时间"静止的思考中"。
## 0.41.0 — 2026-07-06
- 服务更新升级不再中断使用:更新期间正在进行的对话继续完成,无需重来。
## 0.38.4 — 2026-07-03
- 任务列表显示运行状态:运行中的任务带绿色脉冲点,在别处(手机 / 定时任务)启动的运行也实时反映,无需刷新页面。
## 0.37.2 — 2026-07-03
- 对话直播中的文字按轮次分段展示,工具连续执行时不再把正文挤出视野。
## 0.37.0 — 2026-07-03
- PPT 生成质量强化(0.360.38 系列):自动质检对齐网格、图表退化、图标缺失、文字重叠、内容越界等问题,发现即自动返工。
## 0.35.0 — 2026-07-01
- 新增快捷指令:在「记忆」中定义触发词(如"日报"),发送时自动展开为完整指令。
## 0.34.0 — 2026-07-01
- PPT 工作目录精简:中间构建产物收进隐藏目录,文件面板里只看到源文件和交付物。
## 0.33.5 — 2026-06-30
- PPT 新增「商务红」政企风配色预设;素材没有配图时会主动提议 AI 配图(确认后才生成)。
## 0.33.0 — 2026-06-29
- PPT 能力全面重构:逐页设计生成,导出为**原生可编辑**的 PPT(文字、图形、图标均可在 Office 中直接修改),支持模板与品牌配色。
## 0.32.0 — 2026-06-29
- 微信 / 企业微信长会话自动分段:间隔超过 6 小时自动开启新话题(历史仍保留),也可发送「新话题」手动重置。
## 0.31.3 — 2026-06-26
- 消息输入框支持直接拖拽文件;多个附件并列展示,不再互相顶掉。
## 0.31.0 — 2026-06-26
- 模型访问按账户档位开放:不同账户可用的模型清单由管理员按档位配置。
## 0.30.0 — 2026-06-26
- 新增可选模型:豆包 Seed 2.1(turbo / pro / evolving)与 GLM 5.2。

View File

@ -56,6 +56,8 @@ PowerShell here-string `@'...'@` **只在 PowerShell 工具里有效**;用 Bash
- major(`x.0.0`):1.0 正式发版 / 不兼容大重构
- 当前 `0.x` **公测期**,1.0 留给"对外冻结行为 / 正式 GA"那一刻;公测中保持 `0.x` 迭代,minor 走新功能、patch 走修复
**bump 版本时若改动用户可感知 → 顺手在 `CHANGELOG.md` 顶部加条目**(格式 `## <版本> — <日期>` + bullet):口径面向用户("能干什么了"而不是"改了哪个模块"),纯内部重构 / 修复不记——不是每个版本都要有条目。这份由 `GET /v1/changelog` 解析、前端点版本号展示,别写内部模块名 / env 变量 / 部署细节。
**只有以下情况才动 `DESIGN.md`**(避免把工程笔记沉淀成设计):
- 架构 / 心智模型变化(如 §7.1 task-primary 重写)
- 取舍决策推翻或新增(§5 / §7.9 类内容)

View File

@ -2,7 +2,7 @@
> 配合 `DESIGN.md`。本文件只记 phase 状态、决策偏差、文件量、下一步。每条 1-2 句:做了啥 + 关键判断;细节查 `git log` / `git diff` / `DESIGN §7.9`
最后更新:2026-07-09(清理 deepseek-chat/reasoner 下线残留描述,bump 0.54.2)
最后更新:2026-07-09(用户版更新日志:CHANGELOG.md + /v1/changelog + 点版本号弹层,bump 0.55.0)
---
@ -23,6 +23,7 @@
### 2026-07
- **07-09 / 0.55.0**:**用户版更新日志**(点版本号看"更新了什么"):新增仓库根 `CHANGELOG.md`(面向用户口径,只记可感知变化,回填至 0.30;与工程笔记 PROGRESS 分离——PROGRESS 满是内部模块/env/部署细节不宜外露);`GET /v1/changelog`(公开无鉴权,`## <版本><日期>` 正则切段,mtime 缓存热改即生效,无 DB 零 migration);前端右栏底部版本号变可点 → 更新日志弹层(`changelog.js`,复用 marked+purify 渲染),localStorage 记 last-seen 版本、与 /healthz 版本不一致时版本号旁亮红点、打开即清(公测用户发现更新的唯一入口)。CLAUDE.md 加维护约定:bump 版本若用户可感知顺手补 CHANGELOG 条目。
- **07-09 / 0.54.2**:清理 `deepseek-chat/reasoner` 下线残留描述(旧模型 2026-07 已下线):删 `config/models/deepseek_v4.yaml` 里"可退回 deepseek-chat"的误导注释(照做会打不通),DESIGN 附录迁移提醒改为已完成陈述。全库 grep 确认代码/配置本体无旧 model id 引用。
- **07-09 / 0.54.1**:**对话头部压缩指示环**(用户提出:金额撤下明面,换编辑器式上下文环)——头部用量区改为 `N tok + 环形指示器`:环 = 窗口体量 / 模型容量(`reliable_context×2.5` chars),色阶映射三段机制(绿 <50% 未压缩 / 琥珀 50-85% 压缩区 / 85% 达折叠阈值),折叠过附 `已整理×N`;金额/缓存命中率挪 hover tooltip(taskUsageTooltip 本就有,信息不丢)。数据链: `GET /v1/tasks/{id}` 详情端点(列表不加, 100 ×聚合)新增 `context_window_chars/limit_chars/pressure/folds` 四个 additive 字段(窗口体量 = `sum(length(payload::text)) where idx>=base` 单聚合;容量按 task 模型档案折算,读不出置 None 前端画灰环);② 折叠计费 kind `"chat"→"context_fold"`(`record_chat_usage` kind 参数,prompt_optimize 同范式;折叠次数 = count kind, migration,admin kind 对账折叠成本)——0.54.0 未部署无存量,改名零成本;③ `llm_start` 事件补 `context_limit_chars`,run 中每轮实时刷环,`context_fold` done 事件折叠计数 +1 + 体量骤降环回绿;④ PATCH 模型/清空对话两处 `taskMeta` 整体覆盖点改 merge,保住环数据(清空顺带归零)。`format.js` SVG donut(14px,stroke-dasharray)无依赖。**§8.8 Phase 2 长会话中段折叠摘要**(补齐 IM 常驻会话"单段超长"缺口;Phase 1 gap 分段只砍跨话题累积)——run 起点窗口体量达 `reliable_context×85%`(chars×2.5 折算, 50% 压缩门槛同源常量, `core/context.py`)→ 新模块 `core/context_fold.py` 把窗口中段(保留尾部 ~30%,切点必落 user 消息不劈 tool 配对)折叠为固定模板结构化摘要(目标/约束决定/进展/待办/关键路径·ID·数值原文保留), `tasks.context_summary`(migration 0021)+ 推进 `context_base_idx`,`Session.load` system 后注入仅内存的「[前情摘要]」消息(`_n_head/_base_idx` 元数据维护 idx 映射不变量)。**前缀缓存友好**:摘要调用复用会话自身 prepare 后的消息前缀 + 末尾追加指令(与上一轮 chat 缓存字节一致,近全程 cache hit;切点跨列表用" k user"对齐);增量更新 = 旧摘要本在被折前缀里,指令要求合并。失败零阻塞(warn + 跳过);计费入 usage_events(message_id=NULL);「新话题」硬重置/web 清空对话顺带清摘要,gap 软重置保留前端:新 SSE `context_fold` 事件(start 状态行提示 / done 流内分隔条),顺手补 `warn` 事件渲染(此前 loop 熔断/重复拦截提示在 web 端被静默丢弃)。单测 `tests/test_context_fold.py` 12 (切点/映射/apply_fold/maybe_fold 打桩),全量 198
- **07-08 / 0.53.1**:**新增 rebuttal skill(审稿意见回复 / 修回)+ paper·review 增强**——调研 GitHub nature skill 生态(Yuan1z0825/nature-skills 26.9k★ Apache-2.0、Boom5426/Nature-Paper-Skills MIT)后移植三块:① 新 skill `rebuttal`(填投稿后环节空白):决定信 / 审稿意见 → 逐点回复信 + 稿件修改清单 + 修回 cover letter,拆条分诊(E.x/Rn.m 编号 → 8 类 × 4 档严重度 × 12 动作标签 tracker)→ 逐条策略确认 → **先改稿后写信** → QA 渲染(复用 --profile paper,中英双语);零编造铁律(不虚构实验/行号/图号/"已改"声明,含糊腹稿一律 AUTHOR_INPUT_NEEDED 追问,中文备注九种含糊转换表)、语气纪律(不同意五步窄口径、拒实验只用范围边界不用时间经费)、疑难案例库(两审稿人打架/统计大修/合规 blocking/appeal 识别)、readiness 四档诚实标注;② paper 补 `figure_discipline.md`(一图一论断/panel 角色/锚 panel/主图 vs 补充材料/图例四规则,阶段三必读)+ `submission_audit.md`(六步通读审计抓 quality_check 机检不到的跨章漂移 + 数据可用性声明模式含企业受限场景,阶段六必读);③ review 补 `sci_polish.md` 英文 SCI 润色子模式(四层递进诊断:全文策略→章节职责→段落逻辑→句子润色,章节职责错了不做句子润色;claim/evidence/boundary;中译英先抽命题不逐句翻)。未装:nature 系文献检索/下载类(自有 research/documents/brief 后端对不上)、paper2ppt/paper-to-patent/proposal-writer(已有国内定制版更强)。

View File

@ -1,3 +1,3 @@
# zcbot 版本号单一事实源:web/app.py 的 FastAPI version、/healthz 返回、前端展示都引这里。
# 改版本只动这一行。
__version__ = "0.54.2"
__version__ = "0.55.0"

50
tests/test_changelog.py Normal file
View File

@ -0,0 +1,50 @@
"""用户版更新日志:CHANGELOG.md 解析器(web/app.py::_parse_changelog)。"""
import unittest
from web.app import _CHANGELOG_PATH, _parse_changelog
SAMPLE = """# 更新日志
> 导语在首个条目前,应被丢弃
## 0.55.0 — 2026-07-09
- 条目 A
## v0.54.1 - 2026-07-09
- 条目 B
- 条目 C
## 0.53.0
- 无日期条目
"""
class ChangelogParseTests(unittest.TestCase):
def test_sample_parses_versions_dates_and_bodies(self) -> None:
es = _parse_changelog(SAMPLE)
self.assertEqual([e["version"] for e in es], ["0.55.0", "0.54.1", "0.53.0"])
self.assertEqual(es[0]["date"], "2026-07-09")
self.assertEqual(es[1]["date"], "2026-07-09") # v 前缀 + 单横线也认
self.assertIsNone(es[2]["date"]) # 日期可省
self.assertEqual(es[0]["body_md"], "- 条目 A")
self.assertIn("条目 C", es[1]["body_md"])
def test_preamble_before_first_entry_is_dropped(self) -> None:
es = _parse_changelog(SAMPLE)
self.assertNotIn("导语", es[0]["body_md"])
def test_real_changelog_file_parses(self) -> None:
# 仓库里的真文件:格式坏了(条目头打错 / 空正文)在 CI 就翻车,不等用户点开才发现
es = _parse_changelog(_CHANGELOG_PATH.read_text(encoding="utf-8"))
self.assertGreater(len(es), 0)
for e in es:
self.assertRegex(e["version"], r"^\d+\.\d+")
self.assertTrue(e["body_md"], f"empty body for {e['version']}")
if __name__ == "__main__":
unittest.main()

View File

@ -15,6 +15,7 @@ import asyncio
import json
import mimetypes
import os
import re
import tempfile
from concurrent.futures import ThreadPoolExecutor
from contextlib import asynccontextmanager
@ -46,7 +47,7 @@ from starlette.background import BackgroundTask
from core import __version__
from core.branding import brand_name
from core.paths import from_db_path, to_db_path
from core.paths import ROOT, from_db_path, to_db_path
from core.storage import (
NoSubtaskError,
check_no_subtask,
@ -106,6 +107,29 @@ def _pptx_lock_for(abs_path: str) -> asyncio.Lock:
# ─────────────────────────── helpers ───────────────────────────
# 用户版更新日志:仓库根 CHANGELOG.md(面向用户的口径,区别于工程笔记 PROGRESS.md)。
# 按文件 mtime 缓存解析结果 —— 热改文件即生效,不用重启。条目头格式 `## <版本> — <日期>`。
_CHANGELOG_PATH = ROOT / "CHANGELOG.md"
_CHANGELOG_HEAD = re.compile(r"^##\s+v?(\d[\w.\-]*)\s*(?:[—–-]+\s*(\S+))?\s*$")
_changelog_cache: dict[str, Any] = {"mtime": None, "entries": []}
def _parse_changelog(text: str) -> list[dict[str, Any]]:
"""CHANGELOG.md → [{version, date, body_md}](文件序,即新在前);首个 `## ` 前的导语丢弃。"""
entries: list[dict[str, Any]] = []
body: list[str] = []
for line in text.splitlines():
m = _CHANGELOG_HEAD.match(line)
if m:
body = []
entries.append({"version": m.group(1), "date": m.group(2), "body_md": body})
elif entries:
body.append(line)
for e in entries:
e["body_md"] = "\n".join(e["body_md"]).strip()
return entries
def _norm_path(p: str) -> str:
"""跨 OS 显示归一:backslash → forward slash。"""
return (p or "").replace("\\", "/")
@ -1334,13 +1358,31 @@ def create_app() -> FastAPI:
return {"status": "ok", "version": __version__, "instance": INSTANCE or None,
"brand": BRAND}
@app.get("/v1/changelog", tags=["misc"])
def changelog(limit: int = 20):
"""用户版更新日志(仓库根 CHANGELOG.md 解析,新在前)。
公开端点(无鉴权):内容本就是写给用户看的,登录页脚也展示版本号
只记用户可感知的变化,当前版本可能没有专属条目(纯内部改动不记)
前端:点左栏底部版本号弹层展示, localStorage 红点提示新版本
"""
limit = max(1, min(limit, 100))
try:
mtime = _CHANGELOG_PATH.stat().st_mtime
except OSError:
return {"current": __version__, "entries": []}
if _changelog_cache["mtime"] != mtime:
_changelog_cache["entries"] = _parse_changelog(
_CHANGELOG_PATH.read_text(encoding="utf-8"))
_changelog_cache["mtime"] = mtime
return {"current": __version__, "entries": _changelog_cache["entries"][:limit]}
@app.get("/WW_verify_{token}.txt", include_in_schema=False)
def wecom_domain_verify(token: str):
"""企业微信「网页授权可信域名」归属校验文件(公开,无需登录)。
把企业微信下载的 WW_verify_<token>.txt 放到 ZCBOT_WECOM_VERIFY_DIR
(默认 repo ),本路由按文件名在域名根 serve"""
from fastapi.responses import PlainTextResponse
from core.paths import ROOT
if not token.isalnum(): # 防路径穿越
raise HTTPException(404, "not found")
vdir = Path(os.getenv("ZCBOT_WECOM_VERIFY_DIR", "").strip() or str(ROOT))

View File

@ -210,12 +210,50 @@
display: inline-flex; align-items: center; justify-content: center; gap: 6px;
}
#rail-resources > button svg { flex-shrink: 0; opacity: .85; }
/* 版本号:钉在右侧文件面板底部存储条最左,带细分隔线,纯展示
/* 版本号:钉在右侧文件面板底部存储条最左,带细分隔线;点击弹更新日志,
有新版本未看时右上亮红点(changelog.js 维护 .hasnew)
(垂直居中由 .storage-foot 的 align-items:center 提供;随存储条一起显隐) */
#app-version {
flex-shrink: 0; font-size: 11px; color: var(--muted);
font-family: var(--mono); white-space: nowrap; cursor: default;
font-family: var(--mono); white-space: nowrap; cursor: pointer;
padding-right: 8px; border-right: 1px solid var(--border-soft);
position: relative;
}
#app-version:hover { color: var(--text); text-decoration: underline; }
#app-version.hasnew::after {
content: ""; position: absolute; top: -3px; right: 3px;
width: 6px; height: 6px; border-radius: 50%; background: #e5484d;
}
/* ───── 更新日志 modal(点版本号弹出)───── */
#changelog-modal { z-index: 112; }
#changelog-modal .card {
width: 640px; max-width: 94vw; max-height: 80vh;
display: flex; flex-direction: column;
}
#changelog-modal h3 {
margin: 0; padding: 12px 16px; font-size: 16px;
border-bottom: 1px solid var(--border);
display: flex; align-items: center; gap: 8px;
}
#changelog-modal h3 .spacer { flex: 1; }
#changelog-modal h3 svg { opacity: .85; }
#cl-cur { font-size: 12px; color: var(--muted); font-weight: normal; font-family: var(--mono); }
#cl-body { flex: 1; min-height: 0; overflow: auto; padding: 4px 20px 16px; }
.cl-entry { padding: 12px 0; border-bottom: 1px solid var(--border-soft); }
.cl-entry:last-child { border-bottom: none; }
.cl-head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 2px; }
.cl-ver { font-family: var(--mono); font-weight: 600; font-size: 14px; }
.cl-badge {
font-size: 11px; color: #fff; background: #2f8a4c;
border-radius: 8px; padding: 1px 7px;
}
.cl-date { font-size: 12px; color: var(--muted); }
.cl-md { font-size: 13px; }
.cl-md ul { margin: 4px 0; padding-left: 18px; }
.cl-md li { margin: 2px 0; }
@media (max-width: 760px) {
#changelog-modal .card { width: 96vw; max-height: 88vh; }
}
/* ───── 技能查看 modal(两栏 master-detail)───── */
@ -1459,6 +1497,20 @@
</div>
</div>
<!-- ───── 更新日志 modal(点右栏底部版本号弹出)───── -->
<div id="changelog-modal" class="modal">
<div class="card">
<h3>
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 11l18-5v12L3 14v-3z"></path><path d="M11.6 16.8a3 3 0 1 1-5.8-1.6"></path></svg>
<span>更新日志</span>
<span id="cl-cur"></span>
<span class="spacer"></span>
<button id="cl-close" class="sk-x" title="关闭"></button>
</h3>
<div id="cl-body"></div>
</div>
</div>
<!-- ───── 记忆查看 modal(只读;改记忆走对话)───── -->
<div id="memory-modal" class="modal">
<div class="card">

View File

@ -0,0 +1,66 @@
// 更新日志弹层:点右栏底部版本号打开。数据 GET /v1/changelog(公开端点,直接 fetch,
// 不走 api() 免绑登录态)。红点:localStorage 记 last seen 版本,与 /healthz 版本不一致
// → 版本号旁亮小红点提示"有更新",打开弹层即视为已看、红点清除。
import { $ } from "./dom.js";
import { escapeHtml } from "./format.js";
import { renderMd, highlightIn } from "./markdown.js";
const SEEN_KEY = "zcbot_seen_version";
let _current = ""; // /healthz 返回的当前版本(main.js loadVersion 灌入)
// main.js loadVersion 拿到版本号后调:决定红点亮不亮(localStorage 不可用时静默不亮)
export function markVersion(v) {
_current = v || "";
const el = $("app-version");
if (!el) return;
let seen = "";
try { seen = localStorage.getItem(SEEN_KEY) || ""; } catch (e) {}
el.classList.toggle("hasnew", !!_current && seen !== _current);
}
export function closeChangelogModal() {
$("changelog-modal").classList.remove("show");
}
async function openChangelogModal() {
const body = $("cl-body");
$("changelog-modal").classList.add("show");
body.innerHTML = '<div class="muted" style="padding:8px;">加载中…</div>';
// 打开即视为"看过":记住当前版本 + 清红点
if (_current) {
try { localStorage.setItem(SEEN_KEY, _current); } catch (e) {}
$("app-version").classList.remove("hasnew");
}
let d;
try {
const r = await fetch("/v1/changelog?limit=50");
if (!r.ok) throw new Error("HTTP " + r.status);
d = await r.json();
} catch (e) {
body.innerHTML = `<div class="err" style="padding:8px;">加载失败: ${escapeHtml(e.message)}</div>`;
return;
}
$("cl-cur").textContent = d.current ? "当前 v" + d.current : "";
const entries = d.entries || [];
if (!entries.length) {
body.innerHTML = '<div class="sk-empty">暂无更新说明。</div>';
return;
}
body.innerHTML = entries.map((e, i) => `
<div class="cl-entry">
<div class="cl-head">
<span class="cl-ver">v${escapeHtml(e.version)}</span>
${i === 0 ? '<span class="cl-badge">最新</span>' : ""}
${e.date ? `<span class="cl-date">${escapeHtml(e.date)}</span>` : ""}
</div>
<div class="cl-md">${renderMd(e.body_md)}</div>
</div>`).join("");
highlightIn(body);
}
// ───── 顶层绑定 ─────
$("app-version").onclick = openChangelogModal;
$("cl-close").onclick = closeChangelogModal;
$("changelog-modal").addEventListener("click", (e) => {
if (e.target.id === "changelog-modal") closeChangelogModal(); // 点遮罩关闭
});

View File

@ -10,6 +10,7 @@ import { closeSkillsModal } from "./skills.js";
import { closeMemoryModal } from "./memory.js";
import { closeCronsModal } from "./crons.js";
import { closeWechatModal } from "./wechat.js";
import { closeChangelogModal, markVersion } from "./changelog.js";
import { closeFilePreview, closeMiniPreview } from "./preview.js";
import { closeSrcPicker, loadFiles } from "./files.js";
import { loadFolderSuggestions } from "./newtask.js";
@ -88,6 +89,7 @@ async function loadVersion() {
const d = await r.json();
const el = $("app-version");
if (el && d && d.version) el.textContent = "v" + d.version;
if (d && d.version) markVersion(d.version); // 与 localStorage 比对,决定新版本红点
if (d && d.brand) {
document.title = d.brand;
const hd = document.querySelector("header .brand .title");
@ -108,6 +110,7 @@ document.addEventListener("keydown", (e) => {
if ($("memory-modal").classList.contains("show")) { closeMemoryModal(); return; }
if ($("crons-modal").classList.contains("show")) { closeCronsModal(); return; }
if ($("wechat-modal").classList.contains("show")) { closeWechatModal(); return; }
if ($("changelog-modal").classList.contains("show")) { closeChangelogModal(); return; }
if ($("mini-preview-modal").classList.contains("show")) { closeMiniPreview(); return; }
if ($("src-picker-modal").classList.contains("show")) { closeSrcPicker(); return; }
if ($("file-preview-modal").classList.contains("show")) { closeFilePreview(); return; }