Repository navigation
Releases: dpkg-s/ai-memory-web
Release list
v1.11.3 — 本版无代码变更(源码包与安装器随 Release 提供)
本版没有代码变更 —— serve_web.py、web_console.py、web/ 与 v1.11.2 完全相同。v1.11.1 修的启动守卫与 MCP 报错可读性,本版原样有效。
现在能直接下载到什么
| 附件 | 用途 |
|---|---|
web-console-v1.11.3.tgz |
控制台源码包,解压即用(不含测试与 CI 配置) |
ai-memory-setup.exe |
Windows 快捷安装器,与核心仓提供的是同一个文件 |
此前这两个附件要手工挂,v1.11.2 及更早的几个版本在下载页上干脆没有。
升级方式
代码没变,正在用 v1.11.2 的不需要升级。Docker 部署请跟随 ai-memory-docker 的镜像 tag。
破坏性变更
无。
校验
744414078fa6de1eae7255fb8e51e9c12b7c9a4ed014e9448fd86b72bf43df4c web-console-v1.11.3.tgz
a52ca45373f8bfe21bfdbebc9b21afe91f40d7fe3f9c14f4961e8d2a7a3e3b1f ai-memory-setup.exe
本版尚未提供 .sha256 文件,下一版起自动附带。
v1.11.2 — 本版无功能变更(Docker 构建定义迁往 ai-memory-docker)
本版控制台源码一行未改,变化只在「怎么跑容器」这件事由谁负责。
用 Docker 部署的:构建入口变了
Dockerfile / docker-compose.yml / .dockerignore 已移出,改由 ai-memory-docker 的 Dockerfile.web 构建:
git clone --recurse-submodules https://github.com/dpkg-s/ai-memory-docker.git
cd ai-memory-docker
docker compose up -d --build # ai-memory(8700) + ai-memory-web(8811)
镜像内容不变,原来跑着的容器不用动。
不用 Docker 的:什么都不用做
README 现在讲什么
不再教 docker build,改为列控制台对镜像的运行时契约 —— 入口 serve_web.py、端口 AI_MEMORY_WEB_PORT、状态目录 AI_MEMORY_WEB_CONFIG_DIR、上游 MCP AI_MEMORY_WEB_MCP。怎么构建归容器仓,控制台只承诺这几个接口,两边不再各写一半。
不受影响
serve_web.py / web_console.py / web/ 的资源与行为一字未改,v1.11.1 的守卫修复与 _mcp_error_text() 依旧有效。
破坏性变更
无。唯一需要动作的是自建镜像的人,改一下构建入口即可。
校验
| 附件 | 用途 |
|---|---|
web-console-v1.11.2.tgz |
控制台源码包,解压即用 |
ai-memory-setup.exe |
Windows 快捷安装器,与核心仓同一个文件 |
4a578040377fe50fa8ca144ae3f02255d350461b95f053358ad66d56ec0850ab web-console-v1.11.2.tgz
a52ca45373f8bfe21bfdbebc9b21afe91f40d7fe3f9c14f4961e8d2a7a3e3b1f ai-memory-setup.exe
v1.11.1 — 非回环守卫不再打回自己人,MCP 报错能看见真正原因
这一版全部是控制台自身的修复:一个把容器挡在门外的认证守卫,一个只会显示
ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception) 的错误包装。
Fixed
非回环监听 + 账号登录,被自家守卫打回 exit 2
非回环地址的认证守卫只看 AI_MEMORY_WEB_TOKEN,而且写在登录配置解析之前。
可服务的标准形态恰恰是「HOST=0.0.0.0 + 账号登录、不给 token」(令牌是给脚本用的,
不该塞给浏览器用户)—— 于是进程一起来就被自己打回:
[web] 配置错误:监听非回环地址 '0.0.0.0' 时必须设置 AI_MEMORY_WEB_TOKEN … → exit 2
改法:守卫移到登录配置解析之后,判据变成「token 或账号登录至少一道」。
守卫要防的是「裸奔:局域网内谁都能打开调试台写记忆库」,两道认证任一成立都不算裸奔;
只配半截(给了 USER 没给口令)依旧 rc=2,不放水。
启动提示语与事实相反
serve_web.py 只判 auth_enabled(token),在「0.0.0.0 + 账号登录」下会打印
「未设 token:仅回环可访问」,恰好说反。改为按两道认证分别措辞。
Added
_mcp_error_text() —— 把多层异常下钻到叶子
streamablehttp_client 抛的常是 ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception),
真正原因藏在子异常里,界面显示这句等于没提示。现在逐层下钻到叶子异常再显示;
碰到 421 时追加一句「把 host:port 写进 MCP 侧的 AI_MEMORY_HTTP_ALLOWED_HOSTS,
该变量是替换语义」。已接入 health / tools / overview / 阈值 / 调用共 9 处。
回归 4 条(tests/test_web.py A3b~A3e)
| 用例 | 断言 |
|---|---|
| A3b | 非回环 + 账号登录 ⇒ 放行 |
| A3c | 首次启动把初始 admin 落盘,且磁盘上没有明文口令 |
| A3d | 二次启动不重建账号(改过的口令不被环境变量打回) |
| A3e | 非回环 + 只给 USER ⇒ 仍拒绝 |
测试里把 config / users / tokens 全钉到临时目录,不碰宿主机 ~/.ai-memory-web。
Note
- 421 Invalid Host header 的成因在 MCP 侧 —— MCP 的 streamable HTTP transport 默认只放行
127.0.0.1/localhost/[::1]作为Host(防 DNS rebinding)。实测同一 IP:端口只换
Host 头:127.0.0.1:8700→ 200,host.docker.internal:8700→ 421。放行方式在 MCP 侧,
完整说明见 docker 仓 README。 - 源码侧接口未变:
AI_MEMORY_WEB_HOST/PORT/TOKEN/USER/PASSWORD、AI_MEMORY_WEB_MCP、
AI_MEMORY_WEB_CONFIG_DIR语义一字未动,升级无迁移成本。
v1.11.0 — 容器定义进仓库:Dockerfile + compose + 开箱即可登录
主题:把「跑起来」这件事收进仓库。此前容器镜像是在设备上手工
docker build -f Dockerfile.offline拼出来的,仓库里没有任何容器定义 —— 换台机器就得重新摸一遍。这一版补上Dockerfile/docker-compose.yml/.dockerignore,并把默认账号定成 admin / password,开箱即可登录。
Added
Dockerfile——python:3.13-slim基座,pip install -r requirements.txt单独成层(改源码不重装 mcp);非 root 运行(uid 10001);/config为唯一可写目录并声明为卷;EXPOSE 8811。- 默认账号:
AI_MEMORY_WEB_USER=admin/AI_MEMORY_WEB_PASSWORD=password写进镜像 ENV。它只是首次启动的种子 ——web_console只在/config/users.json为空时用它建初始管理员,落盘为 scrypt 哈希;一旦改过口令,环境变量即失效(否则每次重启都会把口令打回默认值)。 - 探活打
/api/session,不是/api/health:开启登录后后者要 401,拿它做 healthcheck 会让容器被反复判死。
- 默认账号:
docker-compose.yml——docker compose up -d --build即起;/config落命名卷,/vault只读挂载留成注释(按需打开);上游 MCP 用AI_MEMORY_WEB_MCP指到容器外(容器内的127.0.0.1是它自己)。.dockerignore—— 排除.git/tests/__pycache__/*.tgz,镜像里不放测试与发布件。- README「容器运行(Docker)」一节 —— 构建/运行命令、默认账号表、两个挂载点的作用、默认口令的失效语义,以及「口令以 ENV 形式进镜像层,
docker inspect能看到」这条风险提示。
Note
- 离线设备(h29k)仍用设备侧
Dockerfile.offline(基座镜像已装好 mcp):本文件的pip install需要网络,离线构建会在那一步失败。
完整变更:CHANGELOG.md
v1.10.0 — 圆形扩散回来了:自研「底片挖孔」
主题:圆形扩散回来了,而且和开关真正同时。上一轮把扩散撤掉是因为浏览器 View Transition 会冻结整页快照;这一轮不再用它 —— 自己造一张「旧主题底片」盖在页面上,在底片中央挖一个不断扩大的圆孔,底片下面是实时页面。于是扩散扫到哪里,那里的开关就在演。
Added
- 自研圆形揭示(底片挖孔)(
core/theme.js+6-anim.css末段)—— 点击瞬间:- 克隆
body子树成一张底片,把旧主题的 token 逐个写进行内样式(html.theming那条降级路径不再参与主流程); - 底片
position:fixed盖满视口,mask: radial-gradient(...)在开关圆心留半径--vt-r的透明孔; - 提交新主题 → 底层是活页面,开关的 GSAP timeline 从第 0 帧起跑;
- 圆孔 0 → 视口外接圆,0.45s。
- 开孔与滑块同一帧:
startReveal()挂在 timeline 的onStart上 —— 那一 tick 正是滑块被画到第一帧的时刻。theme.js 里另留 150ms 兜底(拿不到 gsap / 组件没挂时自己开孔)。 - 为什么不再用
document.startViewTransition:VT 的快照是位图且冻结,页面内元素在那段时间不可见也不可动 —— 「同时」它在物理上做不到(2.x 跳过帧、2.1 试过错后 671ms、4.0 试过顺序化)。
- 克隆
Fixed
- 底片取不到 token(本轮实测抓到的坑)—— 用
getComputedStyle(documentElement)遍历自定义属性一个都拿不到(实现上不在枚举列表里),克隆体于是继承到刚提交的新值,底片变成新主题,圆孔挖开等于什么都没变。改法:遍历document.styleSheets,把html[data-theme=...]规则声明过的--*名字抠出来再逐个取计算值(实测拿到 58 个,底片--bg=#0b0c0d、卡片rgb(16,17,18),与旧主题一致)。 - 滚动位置与 canvas:克隆体默认回到顶部,逐节点同步
scrollTop/scrollLeft;canvas 用drawImage补像素(跨域污染的跳过)。
完整变更:CHANGELOG.md
v1.9.1 — 同一帧起跑,并给连点上锁
主题:把「同一帧起跑」写死,并给连点上锁。三条要求来自主人:页面换色与开关动画必须同帧开始、这期间不可再点、开关演完后等 0.3s 才收窄。
Changed
- 门控点 0.45s → 0.95s、摘锁延迟 150ms → 300ms(
ThemeSwitch.js)—— 0.95s 是滑块回弹段结束(0→66 用 0.45s、66→63 用 0.5s),也就是肉眼上「开关演完了」的时刻;再等 300ms 才摘data-anim,胶囊开始收窄。点击到可收纳 0.6s → 1.25s。- 不等星星闪烁的尾巴(到 2.55s):星星在画面上方、滑块在右侧,收窄后窗口覆盖 59~101,月亮始终在窗内,等它只是白让开关「大着不动」。
Added
- 动画期间互斥(
ThemeSwitch.js)——_busy在 timeline 建立时置位、在主段结束(0.95s)清位;toggle()与followSystem()先判_busy早返回。连点不再「kill 旧 timeline → 门控重新加锁 → 滑块抖一下重来」,右键恢复跟随系统同样被拦。
完整变更:CHANGELOG.md
v1.9.0 — 撤掉圆形扩散,改回单时钟
主题:撤掉圆形扩散,回到单时钟。1.8.0 的扩散本身好看,但它与开关动画在物理上无法同步 —— View Transition 期间整页是一张静态快照,开关被冻在快照里,扩散期间不存在任何可见的活体动画。于是只剩两条路:跳帧(1.8.0 之前)或错后(1.8.0)。实测错后 671ms,比跳帧更像不同步。这一次把页面换色和开关放回同一条时钟:同帧起跑、同帧收尾。
Changed
- 主题切换改回单时钟渐变(
core/theme.js重写 +6-anim.css末段重写)—— 页面换色 0.45s、缓动--ease-in-out,与开关 timeline 的滑块首段(0.45s、power2.inOut)完全一致,两者同一帧起跑。没有快照 ⇒ 没有冻结 ⇒ 既无跳帧也无滞后。- 为什么放弃 View Transitions:VT 期间原 DOM 不可见。让 timeline 照常跑 ⇒ 揭示完成瞬间画面从第 0 帧跳到第 0.55s 帧(滑块像被抽了一下);让 timeline 暂停等 ⇒ 页面先换完、开关 0.55s 后才补演。探针实测(真实时钟):页面
t≈108ms已换、滑块t≈779ms才起跑,错位 671ms。整页快照下「有扩散」与「有同步」不可兼得。 html.theming *而非容器白名单:只过渡容器的话,文字色会先在旧底色上跳到新色,那 0.2s 里白底白字。代价是 0.51s 内全站元素的 transition 属性列表被改写 —— 因此门控.tsw[data-anim="1"](特异性 0,2,0 > 0,1,1)必须压住本规则,否则开关收窄的width过渡被顶掉、变成瞬跳。- 一切延迟归零:1.8.0 降级路径的三层错峰(0/60/80/140ms)已删除 —— 错峰本身就是「不同步」。
- 为什么放弃 View Transitions:VT 期间原 DOM 不可见。让 timeline 照常跑 ⇒ 揭示完成瞬间画面从第 0 帧跳到第 0.55s 帧(滑块像被抽了一下);让 timeline 暂停等 ⇒ 页面先换完、开关 0.55s 后才补演。探针实测(真实时钟):页面
- 门控点 0.65s → 0.45s(
ThemeSwitch.js)—— 0.65s 里的 0.2s 余量是留给 VT 的,VT 撤掉后一并去掉。点击到可收纳 1.35s → 0.6s。
Removed
core/theme.js的vtBusy()/whenVtDone()/setOrigin()/resolveOrigin()与--vt-x/--vt-y/--vt-r三个 CSS 变量、::view-transition-*与@keyframes theme-reveal—— 一并删除,不留死代码。applyTheme(pref, origin, silent)的origin参数(圆形扩散的圆心,已无用途);ThemeSwitch.toggle()/followSystem()不再计算按钮矩形。
完整变更:CHANGELOG.md
v1.8.0 — 主题开关与切换串行 + 从开关圆心扩散揭示
这一版解决什么
主题开关上有两个独立动效:悬停收窄(收纳)和点击切换。它们各跑各的时钟 ——
收窄是 CSS 过渡(width / left,0.5s),切换是 GSAP timeline。更麻烦的是
.tsw.night .tsw-box { left: -59px } 是类切换即生效的终态值,不看 timeline
播到第几帧。
于是点击后稍一移开鼠标:窗口先左移 42.48px,滑块还在半路,收窄后那 30.24px
窗口里只剩空天空 —— 月亮被切成一半再滑出来,连点两次还会错位叠加。
v1.8.0 把两条时钟串成一条(门控),把主题切换改成从开关圆心扩散,并让扩散与
开关动画串行而不是各演各的。
变化
门控串行
- timeline 播放期间锁
data-anim="1":窗口锁定为全展开、禁用过渡; - 滑块第一段落定(0.65s)后解禁,再延 150ms 摘锁;
- 为什么不等第二段(0.45→0.95 的 66→63 微调):门控要防的是「窗口按终态左移
42.48px 而滑块还在半路」,0.45s 后滑块已在 66、占 66102,收窄窗口覆盖101,月亮完整在窗内,3px 微调不会跑出窗口;
59 - 收窄 0.5s → 0.62s、
ease-in-out→ease-expo。
主题圆形扩散
- View Transitions:
clip-path: circle()以开关中心为圆心扩张揭示,0.55s; - 与开关动画串行:VT 期间整页是静态快照,原 DOM 不可见。timeline 若照常起跑,
观众看到的是「定格 → 揭示完成瞬间跳到第 0.55s 帧」,滑块像被抽了一下 —— 这是
「两段动画不融洽」的真正来源。现在扩散在跑时 timeline 以paused创建,扩散
结束(含 rejected / 超时兜底)后由whenVtDone()放行; - 0.55s 与滑块首段 0.45s 同频、缓动同族(
power2.inOut≈
cubic-bezier(.65,0,.35,1))—— 一快一慢会让「页面已换完、开关还在爬」; - 不支持 VT(或
prefers-reduced-motion)时回落三层错峰:顶栏 0ms /
侧栏 60ms / 状态栏 80ms / 主区 140ms 颜色过渡;两套互斥、绝不并行; - 首屏不演,系统主题自动跟随也不演。
时间轴整体压缩
滑块 0.6+0.9 → 0.45+0.5,天空 0.6→0.5,云/星/月同步收紧,光晕 0.6+0.7 → 0.5+0.5。
原时序是 WPF 原文 1:1 移植,本次按需求加快;首段/回弹段的比例关系不变。
点击后可开始收纳:约 1.35s(0.55 扩散 + 0.65 门控 + 0.15 摘锁),改前是 2.05s
且带跳帧。
修掉一个 Vue 反模式
lock() 加的类会被 Vue 抹掉。原因:模板是 class="tsw" :class="{night: night}",
Vue 3 的 options watch 默认 flush:'pre' —— watch 先跑(加类成功),紧接着本组件
的 patch 重算 :class,把整个 class 属性覆写回去。探针证据:classList.add("animating")
调用过 1 次,60ms 后 classList 里没有它,MutationObserver 只看到 tsw > tsw > tsw。
改用 data-anim 属性(不在 diff 目标里)后,同一探针读到 w=72px / left=0px。
Breaking Change
网关鉴权从「比对单枚 env token」改为 auth_request 回调控制台
/api/internal/token/verify,两侧需同时设置 AI_MEMORY_WEB_INTERNAL_KEY。
容器部署必须重建容器 —— docker restart 不会应用新的 -e。
升级
# 源码部署
git pull && 重启服务(后端改动需重启进程;web/ 静态资源保存即生效)
# 容器部署
docker build -t web-console:1.8.0 .
docker rm -f ai-memory-web && docker run …(按旧容器 inspect 复原,替换镜像 tag)校验
- 本地回归:
tests/test_web.py87 passed / 0 failed; - 设备实测:镜像
fafa1c007f8a(186MB),启动日志[web] ai-memory 控制台 v1.8.0,
/api/config返回version: 1.8.0,/api/health200,四容器全 Up; - 静态资源 sha256:
theme.js/ThemeSwitch.js/6-anim.css/3-components.css
4/4 与本地一致; - 动效探针(mock 后端 + 无头 Chrome):
timeline以paused创建、扩散结束后
play()被调用 1 次、门控期间宽度锁在 72px 且窗口left=0。
v1.2.0 — 进控制台自动检查更新,有新版会提示
新增
- 进控制台自动检查更新 —— 后台跑一次,不阻塞首屏,失败静默;一次页面会话最多联网一次。有新版时弹一条提示(带版本号),并在侧栏「配置」右上角留一个圆点,直到更新完成或再检测已是最新。
- 同时检查两个目标 —— 控制台本身与核心程序各查各的、各更各的,两块卡片分开显示;某个目标定位不到时也留一张卡说明原因,不静默少一块。
修复
- 刚联网查到的版本会瞬间退回「未检查」 —— 进「关于」页或点「只读本地」时,本地状态的更新覆盖了刚拿到的远端结论。现在远端结论会保留,本地字段照常更新。
- 「远端版本」分不清「没联网」和「联网失败」 —— 原来两者都显示「(未检查)」。现在分三态:
(未检查)/检查失败(原因见下方)/已检查,未取到提交号;拿到提交号时正常显示vX.Y.Z <短提交号>。
升级方式
覆盖安装或替换源码后重启控制台。已在界面上配过的记忆库路径不受影响。
破坏性变更
无。
v1.1.0 — 阈值改由 MCP 读写;连核心程序不再靠猜路径
变化
- 阈值读写改走 MCP 工具 —— 控制台不再自己读改
memory.toml,改为调用核心程序的memory_config工具。校验、写前备份(同目录memory.toml.bak.<时间戳>,保留最近 10 份)、原子替换都交给服务端统一完成,界面契约没变。- 需要核心程序为 v3.0.0 或更高。
--with-mcp必须显式指定核心程序目录 —— 不再自动猜路径(猜错时的症状是连上一个空库、还一切正常)。用--core-dir <路径>或环境变量AI_MEMORY_CORE_DIR;不给会直接报错并打印可照抄的命令。- 只读开关语义明确 ——
AI_MEMORY_WEB_CONFIG_WRITE=0时界面仍能看阈值,但不能保存。
升级方式
serve_web.py --with-mcp --core-dir <核心程序目录>
从安装器装的,重新跑一遍安装器即可,它会写对路径。
破坏性变更
--with-mcp 不再自动定位核心程序,需要补 --core-dir 或 AI_MEMORY_CORE_DIR。