# 数学公式、头像、对话框与受限交互卡 卡片运行、静态回退及照片错误提示显示在消息气泡外的独立黄色区域,可逐条关闭。关闭只影响当前提示的显示,不改变脚本权限或执行结果;新的错误会重新显示。 [English](CONVERSATION_PRESENTATION_en.md) · [使用](USAGE_zh-CN.md) · [API](API.md) · [安全](../SECURITY.md) 本合同适用于 Tavern 3.1.5,目标 DSH `0.2.1-rc.2` / `0.2.1-rc.3`。通过官方 UI 服务和 slots 嵌入 Web/桌面主页面,不需要另开浏览器。DSH 历史仍为消息权威,以下数据都只是显示元数据。 RP 页面直接显示 DSH 会话/轮次错误的具体信息。若提示活动写入句柄占用,同一数据目录中的会话可能正由另一个网页端或桌面端实例持有;结束其操作并关闭该实例后,再重新打开会话。静态消息的样式表和行内样式均受 Shadow DOM 与外层绘制边界限制,固定定位内容不能覆盖主界面。 ## 渲染顺序 这是 RP 显示链路,与发送给模型的提示词装配顺序分开: 1. **读取显示来源与姓名**:读取 DSH 历史、开场白或当前流式文本,解析当前绑定的用户和角色姓名。手动保存的显示覆盖正文跳过下面的源码宏和正则步骤。 2. **展开消息源码中的姓名宏**:先处理 `{{user}}` / `{{char}}`,以及符合下节条件的正文 ``。此时尚未运行卡片脚本,也尚未把 MVU 变量中的字符串读入状态栏。 3. **运行显示正则**:按全局 → 当前预设 → 当前角色的资源顺序,再按各资源内的规则顺序执行;过滤关闭、不匹配消息身份或深度的规则。后续规则读取前一条的结果,替换结果不会再次从头运行姓名宏或整组正则。 4. **分流排版与卡片**:普通文本走 Markdown → KaTeX → HTML 净化及样式隔离;识别出的交互 HTML 卡片先解析源码、核实已启用依赖并提取脚本,显示无脚本的净化页面。 5. **启动卡片与读取变量**:满足脚本开关、消息完成和历史绑定条件后,读取本消息的 MVU 快照,再启动受限运行时。Worker 路径先运行已启用的 Helper,再按卡片脚本顺序执行;脚本从原始变量生成虚拟 DOM,变量更新后可再次生成。 6. **投影 Worker 的动态画面**:Worker 输出 HTML 经净化、节点数量与身份检查后,在独立显示副本的普通文本节点中展开孤立的 ``,最后显示到无脚本 iframe。只写文本节点,不把姓名当成 HTML;不处理属性、表单值、可编辑区域、代码、样式、模板数据或成对的角色包装文本。这一步不重跑显示正则,不改 Worker 内的 DOM、脚本读取的变量或持久 MVU 值。 例如,MVU 中的 `邀请 参加会议` 在脚本运行时仍保持原值;用户姓名为“小明”时,Worker 状态栏显示副本呈现 `邀请 小明 参加会议`。源码的宏步骤和运行后的文本投影是两个阶段,不把 MVU 值提前替换后再写回。内嵌简化 DOM 桥不经过 Worker 画面投影;静态 HTML 导出也不执行脚本、不导出动态 MVU 画面。 ## 姓名占位符 普通正文、开场白和流式回复沿用当前绑定用户/角色资源的姓名来展开 `{{user}}` / `{{char}}`;正文中孤立的 `` 也可表示用户姓名。重新读取同一历史对话时采用当前名称,不逐条保存旧姓名。手动编辑后保存的显示正文沿用现有固定结果,不再次运行姓名宏或显示正则。 消息源码阶段的 `` 写法不处理代码围栏、缩进代码、行内代码、转义文本、HTML 属性/注释、`pre`/`code`/脚本/样式内容、完整 HTML 文档,或成对的 `…` 结构;需要原样展示时使用代码或 `\`。既有双花括号姓名宏的处理规则保持不变。静态 HTML 导出使用相同的当前名称解析;DSH 原始消息和提示词装配不受此显示兼容影响,JSONL 的对话正文保留原文,开场白仍按既有导出规则展开姓名。 ## 数学公式 RP 正文、开场白、显示编辑和静态 HTML 导出共用 Markdown → KaTeX MathML → DOMPurify 路径。公式默认开启,不增加 conversation-settings 字段或专门控制按钮,也不改写 DSH 原文、提示词和 JSONL。行内用 `$…$` / `\(…\)`,独立公式用 `$$…$$` / `\[…\]`;多行分隔符各占一行。分数、根号、积分、矩阵和对齐公式使用 KaTeX 语法,浏览器负责原生 MathML 排版,不加载远程字体或脚本。 仅识别 Markdown 文字中的分隔符;代码、HTML 属性/注释、原始 HTML 块、完整文档模板和交互卡片内部保持原语义。行内 HTML 文字与 `
` 的 Markdown 正文/标题可使用公式。常见金额不会被当成公式,但字面 `$x$` 必须转义或放进代码。Markdown 强调不会消耗公式内部的 `*` / `_`;公式样式限于 Tavern 内容,普通公式不会为整条消息引入 Shadow DOM。独立长公式横向滚动,流式内容闭合后才排版,未变化的历史消息复用 DOM。 单个公式限制为 4096 字符、200 次宏展开与用户指定尺寸上限 10 em;宏定义不跨公式共享,错误或不支持的 TeX 回退源码。KaTeX 使用 `trust: false`,禁用公式中需要显式授权的外部资源和 HTML 扩展命令;此设置不影响普通 Markdown 链接、图片或现有 HTML 渲染。公式生成内容仍须净化;允许 MathML 及纯文本 `annotation`,禁止 `annotation-xml`。这不是完整 LaTeX 文档运行环境,浏览器布局仍有资源成本;需要支持 MathML 的现代浏览器。详细转义与混排规则见[使用说明](USAGE_zh-CN.md#markdownhtml-与模板样式)。 ## 头像 在 **DT → 用户** 上传 PNG/JPEG/WebP,然后保存资源。浏览器接受最多 8 MiB、边长不超过 8192 的图片,中心裁成 256×256 WebP。API 接受至多 128 KiB 的 raster data URI,校验媒体类型、base64 与文件签名;不接受 SVG、外部 URL 或文件路径。 默认用户图取自周目根 session 所绑定的用户资源;角色图使用当前绑定角色卡的现有 PNG 接口,缺图使用占位。名称/描述参与提示词装配,头像不进入提示词。 消息头像位于输入栏外沿两侧:角色在左,用户在右;正文和操作按钮位于中间。开场和助手气泡默认占满中间列的可用最大宽度,不随初始内容长度收缩;用户气泡默认随内容收缩,正文仍右对齐。可在外观设置中分别选择两种角色的“随内容收缩”或“占满消息栏”。交互卡 iframe 随父容器收缩,后续动态内容在既有宽度内换行。布局使用 Host 的 composer 宽度变量,在显示 Tavern 的 Session 内通过公开 slot 限定 composer 两侧留白;离开 Tavern 视图后恢复 Host 默认留白。设置预览使用相同的气泡布局,按钮尺寸随草稿实时变化,点击“应用并保存”后生效。 点击对话头像可以上传替换图,选择“仅此条”或“此周目所有用户/角色头像”。优先级如下: ```mermaid flowchart LR A[单条显示覆盖] -->|无| B[本周目该角色覆盖] B -->|无| C[绑定用户头像或角色卡图] C -->|无| D[占位头像] ``` - **仅此条**:用户按 QA node ID,角色按 QA node ID + variant ID + 输出段序号保存。开场白按开场序号,导入正文按导入位置保存。流式临时消息不可编辑头像,终态再编辑。 - **整周目替换**:覆盖该角色已有及后续消息,并清除该角色所有单条覆盖。另一个角色保持原样。 - **此条恢复继承**:只删除单条覆盖,继续继承本周目覆盖或资源默认值。 - **此周目全部恢复资源默认图**:清除该角色整周目覆盖及所有单条覆盖。 - 在用户面板修改资源头像,会改变所有继承该资源默认图的周目;对话内编辑不会修改用户资源、角色卡源图或其他周目。 - 新建普通周目从资源默认图开始。分支为新周目时复制当时的显示元数据;此后独立保存,不与原周目联动。更换导入记录后,导入位置覆盖仍跟随该位置;需要时恢复默认。 资源 `avatar` 随用户 JSON 导入/导出。覆盖保存在已有 timeline 的 `ext.pmpDshTavern.appearance`,`schemaVersion:1`,字段为可选 `user`、`assistant` 和 `messages`。图片是有界 data URI,`messages` 最多 500 个键,appearance 总计不超过 512K 字符(JSON 序列化后);timeline 仍受原来的 1 MiB 总限制。更新使用现有 workspace 文件 API 的 revision/CAS,不写 DSH 日志;超限或冲突失败不会覆盖原文件。 ## 对话框样式 v1 进入 **DT → 对话设置**,选择柔和、书页或夜色,或使用颜色、圆角、边距和字体控件编辑;预览后点“应用并保存”。也可展开 JSON 编辑、导入或导出。导入只更新预览,应用才保存。一个自定义样式随当前全局对话设置保存;可导出多个文件作为自己的样式库。 [完整示例](examples/bubble-paper.json): ```json { "format": "tavern-bubble", "version": 1, "name": "Paper", "radius": 4, "padding": 20, "borderWidth": 1, "font": "serif", "user": {"background":"#f4ecd9","text":"#483923","border":"#d5c6a9","widthMode":"content"}, "assistant": {"background":"#fffaf0","text":"#40392d","border":"#ded3bd","widthMode":"full"} } ``` `user`、`assistant` 分别支持可选 `widthMode`,取值为 `content`(随内容收缩)或 `full`(占满消息栏)。缺省时用户为 `content`、助手为 `full`,旧 v1 样式无需迁移;预览、保存及 JSON 导入导出使用同一规则。尚不认识该字段的旧客户端会按未知字段拒绝包含它的样式。 名称 1–80 字符;颜色必须是六位 `#RRGGBB`;圆角 0–32、边距 8–24、边框 0–3(整数像素);字体为 `sans|serif|mono`。文件最多 16 KiB,未知字段、版本或非法值明确拒绝。没有 CSS、HTML、JS、资源 URL 或任意代码字段。 `PUT /v1/conversation-settings` 在既有 `textScale/actionScale` 外接受可选 `bubbleStyle`(上述完整文档)和 `interactiveCards`(boolean)。省略恢复默认,DELETE 恢复全部默认。设置存于原有 `conversation-settings.json`;不绑定周目,不改变 DSH 原生样式。 ## 三种边界与方案选择 1. **静态内容**:Markdown → DOMPurify;样式在 Shadow DOM 与绘制边界内隔离。实时视图的 `img` 与 CSS `background` / `background-image`(包括图片自定义变量)经过下述按需媒体控制器;导入不下载图库,静态 HTML 导出不加载远程图片。移除 srcset、poster 等;字体、其他 CSS 外部资源、`@import`、image/image-set/src 函数和转义仍拒绝。正常布局、颜色、渐变、变量、媒体查询和动画可用。外链需用户主动点击,带 `noopener noreferrer`。 2. **脚本环境**:识别已闭合 `html` 围栏、无语言但以 ``/`` 开头或包含完整 `……` 文档的围栏,以及完整 `…`/`…` 中的控件或脚本。无语言的完整文档可带 HTML doctype 和注释;不完整的 head/body 片段和明确标为其他代码语言的围栏仍按文本呈现。静态 DOM 在 `sandbox="allow-same-origin"`、无 `allow-scripts` 的 iframe 内呈现,CSP 禁止连接、外部图片、脚本、子 frame、表单提交等。卡片 JS 在独立 QuickJS WASM 中运行,不在 iframe 或父页面执行。iframe/Shadow DOM 本身不承担完整权限保证。 3. **能力接口**:唯一的 JSON bridge 白名单提供卡片内部 DOM 与只读姓名上下文,以及“建议消息”。没有通用 RPC、Host API、凭据、文件、网络、父页面或 native eval 入口;模块仅从资源限定的已装配内容映射解析。建议在卡片外显示,由用户点击 Tavern 按钮提交;下述受控 ST 输入适配也允许直接模式中的卡片内真实用户点击,经既有 `user-message` API 请求提交。 [DSH sandbox 固定版文档](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.1-rc.2/packages/sandbox/sandbox/README.md) 隔离的是子进程与文件访问,不能复用为消息 JS 的浏览器隔离。[官方桌面转发](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.2.1-rc.2/apps/desktop/src/web-document.ts) 会剥离 Origin;[桌面请求令牌](API.md#桌面请求令牌) 解决此差异,没有关闭 webSecurity。 [酒馆助手渲染器文档](https://n0vi028.github.io/JS-Slash-Runner-Doc/guide/基本用法/渲染器.html) 与 [固定源码](https://github.com/N0VI028/JS-Slash-Runner/blob/519599bc68247d8e759cc844a983f8f5252941a8/src/panel/render/Iframe.vue) 使用 srcdoc/blob iframe;其构建函数注入公共接口、外部库和头像路径,部分工具桥接父页面。因此复用其显示思路,不能照搬权限模型或声明全量兼容。实现为 Tavern 内可关闭模块,复用已有周目与组件销毁边界;额外插件目前没有独立宿主能力需求,反而需要跨插件生命周期和权限协商。后续若增加通用、独立授权能力,再评估拆分。 ```mermaid flowchart LR A[闭合消息 HTML] --> B[净化与禁脚本 iframe] A --> C[QuickJS 配额解释器] C --> D[有界 JSON 白名单] D --> B D --> E[姓名上下文] D --> H[受控输入请求] H -->|真实点击与作用域校验| G D --> F[卡片外消息建议] F -->|用户点击| G[已有 Tavern API / DSH 历史] A --> H[惰性图片来源] H --> I[可信宿主按可见性获取 / 校验 / 缓存] I --> B ``` ### 远程图片按需加载 媒体不计入代码的 script/import/`.loadHTML` 依赖图或下载进度。可信父页根据实际视口、折叠、隐藏、iframe 和页面可见性获取图片;图片状态单独显示可见、已显示、加载中与不可用数量。关闭、禁用、变体/会话切换及卸载会释放 DOM 图片并取消无人使用的队列/请求,旧代次结果不能覆盖新界面。同一 URL 去重;页面内存缓存采用 LRU,不写 Host、资源文件、浏览器持久存储或图片 URL 日志。 支持静态 PNG/JPEG/WebP/GIF,以及同类有界 data URI;拒绝 SVG、APNG/动画 WebP/多帧 GIF、音视频、字体与通用文件/canvas 访问。远程来源必须为无凭据、无非默认端口的绝对 HTTPS 命名 URL;拒绝 IP、本机/私网形式的主机名、相对 URL及其他协议。URL 形式检查不核验 DNS 结果,不能保证阻止 DNS 重绑定。浏览器 CORS、`credentials: omit`、`referrerPolicy: no-referrer`、`redirect: error`、`cache: no-store` 保持生效;不支持需要登录、重定向或缺少 CORS 的图片服务。图片服务器仍可看到客户端 IP 和 URL,URL 中的查询参数也会发送给该服务器。 页面共享最多 4 个并发请求、32 个可见图片租约、64 个缓存项与 32 MiB(数据字符串和解码像素合计);单图源最多 3 MiB、4194304 像素、边长 8192 与 15 秒。先校验流式字节与文件头/尺寸,再解码、复核并重编码为 PNG;输出与 CSS 变量文本限 1 MiB 字符,超限时有界等比缩图并保留透明度,避免浏览器静默拒绝大 CSS 值。背景只有在原生 CSS 写入回读成功、实际计算背景包含该栅格后才计为已显示;写入或级联失败报告 `IMAGE_CSS` 并释放租约。超限/格式/网络错误显示占位,并在 `data-dtv-image-error` 提供无 URL 的固定原因码,等待全局额度的可见图片会在额度释放后重试。尚无尺寸声明的 img 使用 160×90 占位布局。浏览器临时解码、布局及引擎漏洞不由这些预算完全约束。 卡片可以通过受限 DOM 表达要显示的图片,不能调用通用 fetch 或读取 HTTP 响应。iframe 仍仅允许 `img-src data:`、`connect-src 'none'`,QuickJS/Worker 没有任意网络、Host 或凭据权限;只有可信宿主把校验后的栅格注入实际视图。CSS 图片 URL 转为惰性占位,只有在实际背景中使用才获取;未使用的自定义变量不会下载;html/body 根背景也纳入发现。伪元素只支持无子内容和其他生成盒的空宿主,须有生成内容、可见 display/visibility/opacity,且可确认其无变换的有界盒位于完整可见宿主内,未知几何不加载;隐藏、生成内容和视口变化会释放租约。惰性 `template` 可保留净化后的 SUOT 数据标记,标记在模板外会被展开为内容;模板脚本和事件属性不执行。 ### 选择一张照片 开启卡片脚本后,净化后的 `input[type=file]` 可接收用户主动选择的一张静态 PNG/JPEG/WebP。卡片按钮只有在新的可信点击与浏览器用户激活期内才能打开该输入;合成点击/变更不获权限。可信父页只读取选定 File,解码前检查 8 MiB、8388608 像素与单边 8192,去除原始元数据并输出最长边不超过 640、data URI 不超过 65536 字符的 JPEG。解码串行执行,最多排队四项;新选择、禁用、源码变化或卸载使旧结果失效,格式/额度错误单独显示照片提示。 VM 只收到合成的 `selected-photo.jpg` 描述与有界 JPEG。窄 FileReader/Image/canvas 门面支持读取此次选择、自然尺寸,以及既有 `drawImage(image,0,0,width,height)` → `toDataURL('image/jpeg',0.82)` 流程,返回宿主已处理的 JPEG;没有通用 canvas、真实 File、路径、原文件名、任意图片获取或 Host 入口。照片变更不携带输入框发送权限。持久化仍遵守调用者已有预算及独立授权,选图不扩大存储额度或自动授予写入。输入保持单文件及栅格 accept 白名单;`hidden` 和明确的 `data-opening-choice`/`data-opening-perk` 属性可通过净化。 ## 受控 ST 输入兼容 SUOT 选项卡使用窄范围的虚拟 `window.parent` / `window.top` facade。这些对象只含解释器自有代理,不包含宿主页面或真实 Window。惰性 `