Skip to content

Releases: TinyNano/TinyChat

TinyChat v2.1.1 · 修复新增界面的输入控件样式

Choose a tag to compare

@TinyNano TinyNano released this 10 Oct 18:38

[2.1.1] - 2026-10-11

修 2.1.0 新增界面里「原生裸奔」的输入控件:记忆面板的新增输入框、设置里的自定义指令文本域、以及动态输入弹窗(UI.prompt / TOTP 验证码)共用的输入框样式。

修复

  • 记忆面板新增输入框:此前是没有任何样式的裸 <input>(浏览器原生外观:白底、细灰边、直角),与站内表单格格不入;现改走 label.field 体系,带标题、圆角、描边与聚焦光环,方块主题下自动获得浅灰底覆盖。
  • 自定义指令文本域:同样改为 label.field 包裹,不再是一块裸 textarea。
  • 动态输入弹窗的输入框没有样式规则:UI.prompt 与 TOTP 验证码弹窗共用的 .oc-prompt-input 类此前在全部样式表里没有定义,一直按浏览器原生外观渲染(这个问题先于本版本存在,TOTP 弹窗让它显形了);现补上与 .field input 同一材质的基础样式,并加入方块主题的浅灰底 + 聚焦光环覆盖(白卡片上不能白底白字)。
  • .field textarea 背景一直落成透明:样式里引用的 --bg 变量从未定义过,所有走 .field 的多行输入框背景都是透明的;换成既有令牌 var(--bg-input),与单行输入框同材质。

测试

  • tests/new-features-gui.mjs 新增两条材质断言(记忆输入框 / 自定义指令 textarea 的 computed style 必须有圆角、描边与非透明底色),防回归。
  • tests/new-features-contracts.js 新增两条源码契约(chrome.css 必须带 .oc-prompt-input 规则、方块主题必须覆盖它;记忆输入框与自定义指令必须走 .field 体系)。

TinyChat v2.1.0 · 跨对话记忆 + TOTP 两步验证 + 主题升级

Choose a tag to compare

@TinyNano TinyNano released this 10 Oct 18:13

[2.1.0] - 2026-10-11

一批以「对话体验 / 账号安全 / 运营可见性」为主线的功能更新,外加方块主题的 Apple 官网化改版。

新增

对话体验

  • 跨对话记忆:对话结束后自动提取值得长期记住的用户信息(身份、偏好、进行中的项目),注入之后的每一次对话(服务端在 system 提示词中拼接,对话接口与 API 均生效);设置 → 对话里可一键开关、手动增删、清空全部。提取由前端在对话间隙用辅助模型完成,服务端只做去重 / 上限(默认 50 条)/ 注入,不占写事务。后台可整体关闭。
  • 全局自定义指令:设置 → 对话里的长效偏好(如「永远用中文回答」),随设置云同步,注入每次对话;会话内助手提示词仍然优先。
  • 回复朗读(TTS):AI 回复操作栏新增喇叭按钮,用浏览器自带语音引擎朗读,再点一次停止;零服务器成本,不支持的浏览器自动隐藏。
  • 语音输入(STT):输入框新增麦克风按钮(Web Speech API),边说边出字,识别结果进输入框可再编辑;浏览器不支持时按钮隐藏。
  • 斜杠指令:输入框以 / 开头浮出指令菜单(翻译 / 总结 / 润色 / 扩写 / 解释 / 代码审查 / 续写 / 大纲 / 优化提示词),支持关键字过滤与 ↑↓ 选择,回车回填模板。
  • 提示词优化器:把口语化提问用 AI 改写成结构化提示词,先弹「修改前 / 修改后」对照,确认才替换输入框,随时可放弃。
  • 截断续写:回复因达到输出上限被截断(finish_reason=length)时,消息操作栏出现「继续生成」,从断点接着写进同一条消息(OpenAI / Anthropic / Responses 三种格式都识别)。
  • 消息收藏:AI 回复操作栏新增 ★,收藏随账号云同步;用户菜单「我的收藏」可查看、复制、跳回原对话、删除(上限 200 条)。
  • 每日摘要:每天首次打开时展示一张「昨日概要」卡片(调用量 / 消耗 / 昨天聊过的会话,点击直达),服务端惰性聚合,不需要常驻进程。

账号与安全

  • 两步验证(TOTP):设置 → 账户里可绑定验证器(纯 PHP 实现 RFC 6238,兼容 Google / Microsoft Authenticator 等);开启后登录密码正确也不发令牌,改发 5 分钟有效的中间票据,输验证码换正式会话。验证码错误走同一套登录失败锁。后台可关闭「新绑定」(已开启者不受影响)。
  • 新设备登录提醒:登录成功且设备指纹(UA)与上次不同时发提醒邮件;同一设备重复登录不触发。邮件走新增的提醒队列(data/mailq/),在 shutdown 阶段事务外投递,SMTP 卡死不影响任何写事务,失败自动重试 3 次。

管理端与运营

  • 管理员操作审计日志:保存平台设置 / 用户增删改 / 调额度 / 备份恢复 / 强制下线 / 清日志 / 套餐增删 / 在线更新等管理动作全部记入日志(kind=audit),后台「日志」新增「审计」筛选。
  • 额度预警:后台设置阈值后,用户剩余额度低于该值时自动发提醒邮件(24 小时最多一次),在结算扣费路径里检查。
  • 用户用量页 14 天趋势:设置 → 用量新增逐日调用柱状趋势条(纯 CSS,与既有消耗总结 / 模型占比同屏)。

笔记

  • 笔记云端版本历史:编辑保存时把快照同步一份到云端(每笔记最多 5 份、单份 40KB、5 分钟内连续保存合并),「历史版本」弹窗新增「云端版本」区,换设备也能恢复。
  • 分享页留言:分享对话框新增「允许访客留言」开关;/n/<token> 分享页出现留言列表与发表框(按 IP 限流、上限 100 条),「已分享管理」里可查看与清空。

主题

  • 方块主题 Apple 官网化:SF 风格字体栈与抗锯齿排版、侧栏 / 输入区 / 弹窗改用饱和度提升 + 高斯模糊的半透明材质(apple.com 导航同款配方)、按钮全部胶囊化(980px 圆角)与顺滑按压反馈、空态大标题用产品页级字号与字距。原有「圆角矩形分块 / 不画任何分割线」的立论与全部几何断言保持不变。
  • 后台可设置站点默认主题:平台设置新增下拉(TinyChat 默认 / ChatGPT 风格 / 方块 / Claude 风格),对新用户与从未自选过主题的用户生效(登录页与主应用都会应用),用户自己选过主题则以用户为准。

对话效率

  • 快捷键扩充:Ctrl/⌘+K 改为聚焦会话搜索(原「新建对话」移到 Ctrl/⌘+Shift+O)、Ctrl/⌘+, 打开设置、?(非输入态)呼出快捷键速查表。
  • 会话右键菜单新增「导出 Markdown」:不用进「设置 → 数据」也能把任意一条对话存为 .md。

测试

  • 新增 tests/new-features.php(TOTP RFC 6238 已知向量 / 邮件队列 / 记忆去重与上限 / 收藏分片 / 笔记云端版本分片往返 / 额度预警阈值与冷却 / 审计条目 / 新设置项归一化)接入 CI。
  • 新增 tests/new-features-contracts.js(mfa 票据两端消费 / 记忆注入走 tc_append_system_text / 收藏与续写接线 / 默认主题应用条件 / extras.js 挂载)接入 CI。
  • 新增 tests/new-features-gui.mjs(真 Chromium:TOTP 绑定走真端点 / 收藏亮星与面板 / 斜杠指令回填 / 快捷键速查 / 继续生成按钮 / 默认主题不破坏外观)接入 CI。

TinyChat v2.0.164 供应商密钥填错不再把用户踢出登录

Choose a tag to compare

@TinyNano TinyNano released this 10 Oct 11:28

修一个会直接把用户踢出登录的缺陷。

修复

  • 供应商密钥填错不再导致前台自动退出登录:后台添加供应商时 API Key 填错,前端发消息会立刻被登出并跳回登录页。原因是上游对错误密钥返回 401,而本站的供应商代理把上游状态码原样透传;前端拿到 401 会按「登录已过期」处理 —— 清空本地令牌、吊销会话。现在所有来自上游的认证 / 权限 / 配额类状态(401/402/403)在回传前统一归一为 502(上游网关失败),错误原文仍然保留在提示里供排查;本站自身的「未登录」「额度不足」「无权限」判定不受影响,真正需要重新登录时该 401 依旧会发出。涉及对话(含流式)、生图、生视频与文档解析各条出站路径。

测试

  • 新增 tests/upstream-status.php(上游 401/402/403 归一为 502、429/5xx 保持原样、字符串状态同样按整数判定)并接入 CI。
  • tests/e2e.sh 新增「密钥错误的响应状态」一组:坏 Key 的对话 / 流式对话 / 生图都必须返回 502 而非 401,同时回归断言未登录与无效令牌仍是 401。

TinyChat v2.0.163 AI 改写对照面板增加「在选中后追加」+ AI 处理进度条

Choose a tag to compare

@TinyNano TinyNano released this 10 Oct 08:07

[2.0.163]

承接 2.0.162 的 AI 改写对照预览,补两处使用中暴露的问题:改写结果只能替换、不能保留原文;以及右键跑动作时等模型返回那几秒没有任何反馈,像「点了没反应」。

新增

  • 对照预览新增「在选中后追加」:选中文字跑 AI 编辑后,对照面板底部现在是三个按钮 —— 放弃 / 在选中后追加 / 替换选中内容(主按钮)。「在选中后追加」把模型结果插到选中文字之后、原文照旧保留,适合「扩写 / 总结」这类想留底稿的场景;追加位置同样先校验选区未变,定位失效时退回光标处,不会扔到文末。
  • AI 处理进度条:所有 AI 动作(选区编辑、全文动作、列表页生成标题 / 标签 / 摘要、续写、日报)执行期间,笔记页顶部居中显示一张进度卡片 —— 转圈 + 「AI 正在<动作>…」文案 + 不确定进度条,模型返回后自动消失。此前只有右下角一枚小胶囊,等模型那几秒几乎看不见,用户会以为菜单点了没反应。

文档

  • 修正 2.0.162 更新说明里「笔记列表页右键菜单」的措辞:列表页那组 AI 动作实际挂在笔记行尾的「⋯」按钮上(悬停笔记行才浮现),正文选区的 AI 编辑才是右键;README 同步订正。

测试

  • tests/notes-ai-interactions-gui.mjs 扩充至 37 项:新增「执行 AI 动作后立即出现进度条、带不确定动画条、文案标明动作、返回后自动消失」,以及「在选中后追加保留原文、正文变长、结果落在选中文字之后」两组断言。

TinyChat v2.0.162 笔记列表页 AI 动作 + AI 改写「改前改后」对照预览

Choose a tag to compare

@TinyNano TinyNano released this 10 Oct 07:11

[2.0.162]

笔记页的 AI 交互从「先打开笔记才能用」扩到「在列表上直接用」,并给破坏性的 AI 改写补上「改前改后对照」这一关。

新增

  • 笔记列表页右键菜单新增「AI」分组:不必先打开笔记,直接在列表项上跑 生成标题(依正文起名,确认后才替换)、推荐标签(自动补充标签,与已有标签合并去重)、生成摘要(写入正文开头)。三项与编辑器内的同名动作共用提示词口径与每日配额。
  • AI 改写统一走「修改前 / 修改后」对照预览:选中文字跑 AI 编辑(扩写 / 谬误检查 / 总结 / 翻译 / 降低重复率)、以及全文的 写入摘要 / 生成大纲 / 提取待办 / 生成双链图谱,结果先弹左右对照面板,点「应用」才写回正文,放弃 则一字不动。此前这些动作会直接改写正文,选错动作只能靠 Ctrl+Z 挽回。

变更

  • 选区 AI 编辑由「结果插到选中文字之后、原内容保留」改为「预览后替换选中文字」。改写类动作的本意是替换,保留原文再追加实际上让正文越改越长;现在替换前会校验选区在预览期间未被改动,极端情况下退回光标处插入而不是错位覆盖。
  • showTidyPreview(原只服务「自动整理」)泛化为通用的 showAiDiff(opts),上面所有预览入口共用一套弹窗与 settled 关闭守卫。

测试

  • tests/notes-ai-interactions-gui.mjs(新增):真 Chromium + 真服务端锁住 6 组行为 —— 列表菜单含「AI」分组且原有项不被破坏;列表「推荐标签」真的落进笔记 tags;列表「生成标题」必须经确认框且确认后标题更新;选中文字 AI 编辑必须先弹对照预览、应用前正文不变、应用后选中处被替换;「写入摘要」整篇动作同样先预览再应用;全程无未捕获异常。

TinyChat v2.0.161 支持无 Key 上游 / 内网本地模型开关,出站代理补全 SOCKS

Choose a tag to compare

@TinyNano TinyNano released this 10 Oct 05:00

[2.0.161]

补上 2.0.160「API Key 可留空」未能真正打通本地 Ollama 的缺口:Key 那关过了,但发请求前的 SSRF 防线仍会挡住本地地址(127.0.0.1、192.168.x.x)与非白名单端口(Ollama 默认 11434)。现在提供后台开关。

新增

  • 后台「对话设置 → 允许供应商指向内网 / 本地地址」开关(默认关闭):开启后供应商 Base URL 可指向内网 / 本地地址与任意端口,用于对接本地 Ollama / LM Studio(http://127.0.0.1:11434)。关闭时行为与之前完全一致 —— 只允许公网地址且端口限 80 / 443 / 8080 / 8443。默认关闭,公网多租户部署无需改动、安全性不受影响。
    • 开关与出站代理同一手法:值在 tc_with_db 里随库带进全局(SSRF 判定在事务之外、发请求前执行),另有环境变量 TC_ALLOW_PRIVATE_UPSTREAM=1 作为部署级来源(两者任一为真即放行)。
    • 演示管理员不可修改该设置(回滚前已可用于探测内网),与 SMTP / 内容安全同一口径拒绝。
    • 开启后仍守底线:带 user:pass@ 的 URL、非 http(s) 协议、空主机照旧拒绝。

测试

  • tests/private-upstream.php(新增,已接入 CI):默认关闭时本地 / 内网 / 非白名单端口被拒、公网地址照常放行;开启后放行本地与任意端口,但仍拒绝带凭据 URL 与非 http(s) 协议;环境变量来源(子进程验证);设置归一化只认布尔真值、缺省为假。
  • tests/e2e.sh:新增开关默认关闭 / 可开启 / 可回读 / 可关回四项,以及「演示管理员不可改内网访问设置」一项。

文档

  • README / 上一版本的更新说明中「对接本地 Ollama」一句此前过度承诺(只去掉 Key 校验,SSRF 防线仍会挡),现已补上「需在后台开启内网放行」的前提。

[2.0.160]

两条来自使用的诉求:接入本地 Ollama / LM Studio 这类不需要 API Key 的上游时,保存供应商被「API Key 不能为空」挡住;以及出站代理想要 SOCKS 与更完整的代理类型支持。

新增

  • API Key 可留空(无鉴权上游):新建 / 编辑供应商时不再强制填写 Key。留空即表示上游不需要鉴权,请求不会发送 Authorization / x-api-key 头(而不是发一个空的 Bearer ,后者在部分网关会被判成「带了无效凭据」而拒绝)。适合本地 Ollama、LM Studio、或自建的无鉴权网关;填了 Key 的行为完全不变。注意:这只是去掉保存时的 Key 校验,要让供应商地址真正指向本机还需开启下方 2.0.161 的内网访问开关。
  • 出站代理支持完整类型:此前只显式支持 http / https / socks5 / socks5h,现在补上 socks4 与 socks4a,并把 scheme 到 curl 代理类型的映射收口到一个函数(tc_curl_apply_proxy),按 scheme 设置 CURLOPT_PROXYTYPE(socks4a / socks5h 由代理解析域名)。后台「对话设置 → 出站代理」的输入校验与提示文案同步更新。

变更

  • 出站代理覆盖面收敛到「全部出站请求」:代理此前只在统一助手 tc_http_request 里生效,另有 9 处直接 curl_init 的旁路(在线更新检查与下载、在线浏览器抓取与子资源预热、搜索结果并行抓页、SearXNG 探测、图片留存下载、图片代理、视频代理)不走代理 —— 表现为「配了代理,但某些功能仍直连、在国内网络卡死」。现在这些路径统一调用同一个 tc_curl_apply_proxy,配了代理就全都走代理。
  • 获取模型列表 / 测试连通性 / 网页 AI / IM AI 的空 Key 处理:这些路径此前对空 Key 直接拒绝(请先填写 API Key 等),现在允许空 Key 直连(地址仍必填),只在带了掩码占位符 •• 时才清空后按无 Key 处理。生成的图片 / 视频请求同理:空 Key 不再发送 Authorization 头。

测试

  • tests/outbound-proxy.php(新增):scheme 到 curl 代理类型的映射(含大小写不敏感、未知 scheme 返回 null、remoteDns 标记)、端到端断言实际写入的 CURLOPT_PROXY / CURLOPT_PROXYTYPE、非法代理串(ftp / file / javascript: / socks6)被拒、未配置时不写任何代理选项。已接入 CI。
  • tests/e2e.sh:新增「无 Key 供应商可保存」「hasKey 为假」「可正常对话」三项;原先「缺 Key 被拒」的断言改为「缺 Key 直连无鉴权上游成功」。

2.0.159

Choose a tag to compare

@TinyNano TinyNano released this 09 Oct 13:24

修复

  • 模型偏好跨设备同步后静默失效:默认生图 / 生视频模型、AI 工具判定模型、跟进建议模型、笔记辅助模型这些偏好,值都形如 providerId\nmodelId(换行分隔供应商与模型)。服务端的云同步清洗 tc_settings_text() 把换行当控制字符一并剔除,两段被粘成 providerIdmodelId —— 跨设备同步回来既找不到供应商也找不到模型,该偏好悄悄失效(本机不经清洗,所以只有换设备才暴露)。现在为这类偏好新增 tc_settings_model_ref():保留换行分隔符,其余控制字符照旧剔除,并归一化为「供应商 + 模型」至多两段(去空段、去首尾空白)。普通短文本的清洗行为不变。

测试

  • tests/settings-sync.php 新增 9 项:直接对清洗函数下探(保留换行、去首尾空白、丢空段、空值归空、普通短文本仍剔除换行未被波及),并在 sanitize 白名单用例里同时覆盖 prefs 与 ui 两侧的模型引用键。
  • tests/settings-sync-gui.mjs 新增 5 项:走真实云同步链路写入 providerId\nmodelId,断言云端落库带换行,再用全新浏览器上下文(新设备)登录同一账号,断言偏好与界面两侧的模型引用都带换行恢复。

2.0.158

Choose a tag to compare

@TinyNano TinyNano released this 09 Oct 13:24

四条来自使用的反馈:侧栏那个开关样式乱了、生图生视频按钮应该自动列出可用视频模型、后台更新日志要支持 Markdown、以及开关式地开启自动更新(默认开启)。

修复

  • 侧栏「简单对话 / 多角色」的开关样式错乱:style.css 为挡住 .field span{display:block} 给 .switch 钉了 min-height:26px,而 groupui.css 里的缩小版只覆写了 height:20px —— min-height 恒压 height,轨道被撑回 26px 高,旋钮却还是 14px,于是胶囊里空出一大截(用户截图里那个)。现在缩小版把 min-height 一并覆写成 20px,实测旋钮四周留白恒为 3px。
  • 生图 / 生视频入口的下拉只列当前供应商的模型:入口在「任意供应商有该类模型」时就会出现,而弹窗与下拉此前只扫当前供应商 —— 当前对话供应商恰好没有视频模型时,点开下拉是一片空白。现在候选跨供应商汇总,条目显示成「渠道@模型名」,选中后按所选模型的供应商路由(请求里的 providerId 不再固定为当前供应商)。没有可用视频模型时给出明确提示而不是空弹窗。

变更

  • 后台「版本更新」的发布说明渲染成 Markdown:发布说明是 GitHub Release 的 body,本身就是 Markdown,此前直接塞进 <pre> 显示一堆 # / * / 反引号。现在交给全站同一套渲染器(经 DOMPurify 消毒)渲染成标题、列表、加粗、行内码与链接,并补上后台语境下的排版样式。
  • 新增「自动更新」开关(默认开启):在后台「版本更新」面板可切换。开启后打开该面板时若发现新版本会自动执行更新(与「一键更新」同一条下载 / 校验 / 备份 / 加锁流程,更新前照常自动备份);关闭后仅提示,需手动点「一键更新」。只在打开面板时触发,手动点「检查更新」不会顺手升级。
  • 「≡」菜单里生图 / 生视频行右侧新增模型下拉:候选与弹窗、设置里的「默认生图模型」共用同一份偏好(生视频新增 videoModel 偏好并纳入云同步),选中即记住;未选中时展示自动挑出的模型,让用户一眼看到会用哪个。该行拆成「动作按钮 + 模型下拉」两个可点区域(下拉不能嵌在按钮里)。

测试

  • 新增 tests/media-toggle-gui.mjs(20 项):真浏览器量侧栏开关的轨道高度与旋钮留白,并构造「当前供应商只有对话/生图、视频模型在别的供应商」的场景,验生图/生视频下拉列出跨供应商的全部可用模型、不含对话模型、选中后落到正确供应商、偏好落库、弹窗默认选中同一个模型。
  • 新增 tests/update-panel-gui.mjs(17 项):预置检查缓存(不触网)验发布说明渲染成 <h2>/<li>/<strong>/<code>/<a> 且原始 Markdown 记号不漏出;验「自动更新」默认开启、切换后真的落库(重读接口仍是新值),而不是只改前端勾选。
  • 两者都已登记进 CI。

本次回归:19 套 PHP 自检、13 套 JS 自检、GUI 全量(含新增两套)、隔离端口端到端 630 项、模型汇总端到端 44 项通过;发版产物重复生成无差异。

2.0.157

Choose a tag to compare

@TinyNano TinyNano released this 08 Oct 14:41

修复

  • 中文字体切换往返即时生效:思源宋体与阿里巴巴普惠体的切片样式表共用同一个族名;此前两份样式表只增不减,切回已加载的字体仍被后插入的样式表覆盖。现在只保留当前选中的中文字体样式表,切到系统或本机字体时移除网页字体样式表。保留缓存与按需分片下载,未修改字体文件或字形。
  • 注册协议勾选框与文字同行:原来的协议行只有 flex-direction,没有 display:flex,实际仍是 .field 的块布局。两处注册入口改用专用横向布局、固定 16px 勾选框,文字可在自己的列内换行。
  • 纠正 2.0.156 对汇总标签位置的误解:用户要求的是模型选择器顶部的渠道标签,不是回答版本标签。所有汇总模型集中到「全部」后面的第一个渠道标签「自动切换」,使用平台 logo;列表与选中项显示 Auto@模型名。普通渠道和用户自己添加的模型照旧显示,底层 agg: 路由不变。撤回单个汇总回答强制显示回答版本标签的改动。

测试

  • 字体往返用实际选择器操作并核验 Chromium 真正渲染的中文字形,而不仅检查偏好或 CSS 字符串;覆盖思源宋体、普惠体、系统字体、本机字体和刷新后的恢复。
  • focus-ring-gui.mjs 共 151 项:字体实际渲染、两处注册入口、14/22px 字号、桌面/窄屏、标签点击与协议关闭后的隐藏。model-groups-gui.mjs 共 70 项:统一自动切换标签、平台 logo、多个汇总模型、搜索过滤、用户渠道独立及真实选中路由。

本次回归:19 套 PHP 自检、13 套 JS 自检、20 套 GUI 全部通过;隔离端口端到端 630 项、模型汇总端到端 44 项通过。发版产物重复生成无差异。

2.0.156

Choose a tag to compare

@TinyNano TinyNano released this 08 Oct 08:42

2.0.156

四条来自使用的反馈。最后一条查下去发现根源比预想的严重:后台「成员渠道」那个窗口的样式一条都没有。

后台「成员渠道」窗口:此前没有任何 CSS

JS 一直在生成 ma-pick / ma-pick-row / ma-pick-prov / ma-tag / ma-member / ma-card / ma-ops 这一整套类名,但样式表里查无此类(同一个后台的「模型元数据」mm-* 有 50 条规则,这边是 0 条)。于是勾选框和文字挤在一起、渠道分组与模型行没有层级、按钮堆成一团 —— 你看到的「乱七八糟、各种没对齐」就是这个。现在补齐了这一族样式:渠道分组吸顶 + 底色、每行统一 34px 行高、勾选框固定 16px 并真正垂直居中、成员选择器限高滚动、卡片与状态标签按站内设计系统(无描边,分层靠底色与 hairline 内阴影)。

开关白点溢出胶囊(截图里那个)

.switch 是 <span>,而 style.css 里有一条 .field span { display: block; font-size: .929rem } —— 开关一旦落在 .field 里就被压成行盒高度(height: 26px 失效,实际 20px),而绝对定位的旋钮仍是 20px + top: 3px,于是底部溢出 3px、右侧看起来也对不齐。现在把盒模型和尺寸钉死,实测旋钮四周留白恒为 3px。

同时修掉两处同源问题:

  • 生成汇总弹窗里的非法 HTML:那段是「<label class="field"> 里又套一层 <label class="user-form-admin">」的嵌套 label(模型元数据弹窗里也有同样写法)。嵌套 label 不是合法结构,浏览器会把关联关系打乱。外层已改成 div。
  • 勾选框与模型名错开 3px:同一条 .field span { margin-bottom: 6px } 漏进了选择器行内的 <span>,把文字整体上推。清掉后实测偏差 0px。

汇总不再收纳用户在前台自己添加的模型

汇总的成员池此前扫的是全部供应商,没有区分「平台渠道(管理员添加)」与「用户自建」。两个后果:用户自建的一条同名渠道就能把「只有 1 个平台成员」的模型顶成 ≥2,凭空造出一条全局汇总组(而该组对其它用户只有一个真实成员);而该用户的同名模型本就在他自己的列表里,再被汇总收纳一次,他反而找不到自己那条渠道了。

现在建组、成员解析、成员校验三处口径统一为「只认平台渠道」;后台弹窗把用户自建渠道单独列成只读清单(带渠道主),说明为什么它们不参与汇总。

其余两条

  • 生图留存文件与数据备份清单默认折叠 + 分页:这两份清单会随使用不断变长(留存按张、备份按天累积),此前平铺渲染会把整页撑得很长。现在默认收起(标题上直接写明「多少个 · 多大」,不展开也知道规模),展开后固定限高内滚动、每页 10 行并带翻页。
  • 汇总模型回答在对话里只占一个标签,名字是 Auto@模型名:汇总的意义就是「多个渠道一个 ID」,一轮回答只该有一条标签,而不是每个成员渠道各占一条。logo 由模型名匹配,同一个汇总 ID 背后可能是不同厂商的模型,图标据此自动切换;汇总只有一条回答时也会画出标签条(此前要 ≥2 个版本才画),否则看不出这一轮是哪条汇总 ID 答的。

测试

  • tests/model-groups-gui.mjs 39 → 52 项:新增排版几何断言(勾选框与文字中心偏差 ≤2px、行高一致、分组标题吸顶、选择器限高、旋钮不得溢出轨道)与汇总回答的标签断言(有且只有一条、名字是 Auto@agg-model、带 logo、不泄漏 agg: 前缀)。这几条抓出的正是上面那些渲染缺陷 —— 接口全对,界面却是错的。
  • 新增 tests/storage-panel-gui.mjs(22 项):写 25 个留存文件 + 14 个备份文件,真浏览器里验默认收起、标题写明规模、一页 10 行、翻页真的换内容、末页「下一页」禁用且正好装下余数、限高可滚动、再次收起不占高度。
  • 两者都已登记进 CI。

回归:PHP 20 套、JS 静态 13 套、GUI 全量 18 套零失败(含 model-groups-gui 52 / storage-panel-gui 22 / chat-api-group-gui 50 / local-store-gui 39 / settings-sync-gui 27 / toolbox-gui 187)、e2e.sh 630 项、model-groups-e2e.sh 44 项、release.mjs 幂等。

本版无数据库迁移,替换文件即可。