Keep investigation graphs stable during updates, defer expensive chart rendering, and prevent stale results when switching tasks. Adopt reference annotations and consolidate frontend documentation.
177 lines
19 KiB
Markdown
177 lines
19 KiB
Markdown
# Agent Persona 设计
|
||
|
||
整理日期:2026-11-10。本文保存供水前端当前的 Persona 视觉与交互规格,作为后续调整的基线。资源解析、再生成及缓存实现见 [动画资源说明](agent-persona-design.md#8-资源生成与维护);通用控件和语义表面继续遵循 [样式设计指南](style-design-guide.md)。
|
||
|
||
## 1. 形象与材质
|
||
|
||
采用 Obsidian 原生 Rive 水纹球,保留柔和羽化、流动反射、不规则边缘水纹和状态机。配色采用已选定的 02「冰水蓝」:浅色水体承载白色高光,暗部保持蓝色,外围水纹提供地图上的辨识度。主体底色具有较高遮蔽度,减少地图道路穿过球体的现象。
|
||
|
||
| 角色 | 当前值 | 用途 |
|
||
| --- | --- | --- |
|
||
| 球面底色 | `#DCEFFA` | 浅冰蓝底层,独立于流动反射 |
|
||
| 底色不透明度 | 待机 94%,活动 98% | 保持主体清楚,避免背景纹理干扰 |
|
||
| 水体反射 | `#299FDF` | 提供蓝色层次与流动感 |
|
||
| 同色相暗部 | `#1482BC` | 保留体积,避免灰黑混色 |
|
||
| 高光 | `#FFFFFF` | 保留清晰、干净的反射 |
|
||
| 外围水纹 | `#4C9ECC` | 提高浅色地图上的辨识度 |
|
||
|
||
颜色直接写入 Rive 资源,不使用 CSS 色相、亮度或对比度滤镜。通用 UI 仍引用共享语义颜色;上表仅定义动画资源本身的材质。参数源为 [obsidian-material.json](../design/agent/obsidian-material.json)。
|
||
|
||
以下为当前资源的实际静态帧,用于辨认在线与离线外观;运动行为以第 4 节为准。
|
||
|
||
| 在线待机 | 离线静水待机 |
|
||
| --- | --- |
|
||
| <img src="../public/agent/obsidian-water-idle.png" width="64" height="64" alt="冰水蓝在线 Persona,有外围水纹"> | <img src="../public/agent/obsidian-water-offline.png" width="64" height="64" alt="冰水蓝离线 Persona,无外围光圈"> |
|
||
|
||
## 2. 尺寸与定位
|
||
|
||
| 使用位置 | 画布尺寸 | 布局要求 |
|
||
| --- | --- | --- |
|
||
| `AgentPersona` 默认 | 64 × 64px | 固定尺寸,不随状态变化 |
|
||
| 桌面收起入口 | 64 × 64px | 画布填满按钮命中范围 |
|
||
| 桌面与手机面板标题栏 | 64 × 64px | 与名称、状态共同形成身份区 |
|
||
| 手机地图入口 | 40 × 40px | 使用同一资源,适应地图工具栏 |
|
||
|
||
桌面收起入口距地图左侧 **12px**,距顶部导航下沿 **12px**。两个方向由同一定位层的内边距控制;当前导航高 56px,因此入口左上角为视口中的 `(12px, 68px)`。这里的顶部距离以导航下沿为起点。
|
||
|
||
尺寸指整个 Rive 画布,包含资源固有的羽化留白。放大时保留画板比例,不裁掉外围水纹。悬浮入口仅显示 Persona;状态通过按钮名称与悬停提示提供,避免增加卡片、状态点、箭头或底圈。
|
||
|
||
## 3. Agent 标题区
|
||
|
||
身份行高 **80px**,左右内边距 12px、上下 8px。左侧为 64px Persona,旁边上下排列名称“供水助手”和连接状态;右侧集中排列对话操作。
|
||
|
||
| 内容 | 规格与行为 |
|
||
| --- | --- |
|
||
| 名称 | 14px、半粗,与现有标题排版一致 |
|
||
| 连接状态 | 12px 辅助文字配共享 `StatusDot`;显示实际的“连接中 / 在线 / 离线” |
|
||
| 操作 | 新建对话、历史、折叠;手机末项为关闭,每项命中范围 40 × 40px |
|
||
| 上下文行 | 高 36px,保留会话标题与项目或个人范围切换 |
|
||
|
||
身份行与上下文行保持连续的浅色表面,在标题区底部使用分隔线。历史展开、键盘导航和关闭后焦点恢复沿用现有行为。320px 手机宽度下,名称、状态和三个操作均应完整显示且互不重叠。
|
||
|
||
## 4. 动画与状态
|
||
|
||
动画由 Rive WebGL2 的同一个 `default` 状态机驱动,业务层提供真实状态。在线水纹持续流动;离线保持完整球体,通过节奏与光圈变化表达连接不可用。
|
||
|
||
| 状态 | 呈现 |
|
||
| --- | --- |
|
||
| `idle` | 原生待机流动与外围水纹 |
|
||
| `thinking` | 原生处理状态动画 |
|
||
| `listening` | 原生等待输入动画,具体含义由业务映射决定,不单凭形象表示麦克风录音 |
|
||
| `speaking` | 原生输出状态动画 |
|
||
| `asleep` | 内部柔光低速、小幅流动,外围三层光圈透明度为 0 |
|
||
| `hover` | 鼠标附近的局部白色反射随指针移动,与当前业务状态叠加 |
|
||
|
||
离线设计称为“静水待机”。在线与离线以 **600ms** 混合切换,可见实例保持挂载;离线球体持续运动,恢复在线后恢复外围光圈。离线悬停也不重新打开外围光圈。
|
||
|
||
离线三层循环周期为 **15s / 19.25s / 31.375s**,位移与呼吸振幅为原来的 25%,完整旋转保持循环闭合。时间轴速率由 60 调至 24,约为原速 40%;此值不代表屏幕渲染帧率上限。详细图层参数见 [obsidian-offline.json](../design/agent/obsidian-offline.json)。
|
||
|
||
## 5. 悬停与点击
|
||
|
||
悬停沿用原版三层反射的尺寸、渐变和亮度。进入以约 150ms 渐显;白色高光以约 80ms 的跟随时间常数趋近鼠标位置,保留轻微惯性。鼠标移出后先停留 100ms,再以约 600ms 淡出,退场期间保留最后的跟随位置。不能只打开 `hover` 布尔值而遗漏坐标,否则原生指针目标停在默认中心,造成球体中间大片变白。球体、点击范围和常态外围水纹尺寸均保持原定义。
|
||
|
||
共享 `Persona` 保留 Rive 原生指针监听,经由 `persona-hover.ts` 平滑 CSS 坐标后,将主指针的进入、移动、离开和取消转交给画布;坐标转换与资源内的跟随约束由 Rive 完成。150ms / 600ms 渐变由 Rive 时间轴负责,进出切换以 100ms 状态混合衔接;所有时间参数集中在 [persona-hover-timing.json](../src/shared/ai-elements/persona-hover-timing.json)。100ms 停留内重新进入会取消退场,淡出过程中重新进入从当前坐标继续跟随;取消事件立即释放悬停,卸载时清理动画帧和计时器。触摸及按压不转交动画,不绑定 View Model。悬浮入口和标题形象共用这条通路,画布就绪后才接收交互。
|
||
|
||
点击、触摸轻点及 Enter / Space 展开对话,离线时同样可用。球体不添加 Motion 按压缩放、弹簧回弹或按下变暗;触摸按下不模拟 hover。外层面板的布局动画独立维护,Resizable 连续调宽保持直接响应。
|
||
|
||
## 6. 加载、降级与缓存
|
||
|
||
首次加载显示当前资源导出的静态帧。Rive 加载后先等待 600ms 完成原生进入动画,再以 **180ms CSS 透明度渐变**衔接,尺寸和位置保持固定。在线、离线分别使用相应的本地 PNG,避免银灰占位与蓝色动画之间的突变。
|
||
|
||
系统开启“减少动态效果”时显示静态帧,不初始化 Rive;资源失败时保留相应静态形象与按钮功能。强制颜色模式显示高对比轮廓,保留焦点和键盘操作。元素离开视口或页面隐藏时卸载渲染实例,可见后恢复。
|
||
|
||
客户端按页面内存、Cache Storage、网络的顺序取得资源。仅共享不可变 `.riv` 字节,每个可见入口独立创建动画实例;不共享实例或绑定 View Model。资源重建时同步生成 SHA-256 版本,URL 带版本参数,使旧缓存自然更新。缓存范围与校验逻辑见 [动画资源说明](agent-persona-design.md#8-资源生成与维护)。
|
||
|
||
## 7. 实现与维护
|
||
|
||
| 入口 | 文件 |
|
||
| --- | --- |
|
||
| 业务形象与降级 | [AgentPersona](../src/features/agent/components/agent-persona.tsx) |
|
||
| 原生状态机接入 | [Persona](../src/shared/ai-elements/persona.tsx) |
|
||
| 桌面悬浮按钮 | [AgentCollapsedRail](../src/features/agent/components/agent-collapsed-rail.tsx) |
|
||
| 标题区 | [AgentCommandPanel](../src/features/agent/components/agent-command-panel.tsx) |
|
||
| 资源生成与原版还原 | [build-agent-persona.mjs](../scripts/build-agent-persona.mjs) |
|
||
| 资源版本与缓存 | [persona-resource.ts](../src/shared/ai-elements/persona-resource.ts) |
|
||
|
||
材质与离线参数以对应 JSON 为准,悬停沿用原始资源中的几何与指针约束,仅调整时间轴与指针跟随节奏;改动时同步本文的设计规格与资源维护章节。原始解析数据保留以便逐字节还原 `.riv`;该数据不等于作者完整的 `.rev` 编辑器工程。
|
||
|
||
验收时检查 64px 桌面入口、64px 标题形象与 40px 手机入口,覆盖默认、悬停进入与退出、快速切换、离线和恢复在线。桌面检查 1024/1440px 视口及 400/640px 面板,手机检查 320/375px。确认左上间距一致、按压无缩放、首次加载无画面硬切、底色能遮蔽地图、离线无外围光圈、缓存刷新后仍加载当前版本。
|
||
|
||
资源级检查使用固定时间步对照原版与修改版:在线状态保持相同材质下的逐像素一致;悬停检查左右移动时局部高光跟随指针,移出后完全恢复。工作台检查鼠标坐标实际到达悬浮入口和标题形象的 Rive 监听器,不能仅断言 hover 开关变化。离线检查完整球体、持续变化和光圈关闭。运行命令见 [资源说明](agent-persona-design.md#8-资源生成与维护),各次实际验证结果记录在 [实施记录](history/agent-chat-ui-implementation-notes.md)。
|
||
|
||
## 8. 资源生成与维护
|
||
|
||
### 文件与再生成
|
||
|
||
| 文件 | 用途 |
|
||
| --- | --- |
|
||
| [运行时源数据](../design/agent/obsidian-runtime.json) | 从原版解析的 775 个对象,包含图形、属性、关键帧、状态机和绑定数据 |
|
||
| [冰水蓝材质参数](../design/agent/obsidian-material.json) | 已确认的水体、暗部、白色高光和水纹颜色,以及球面底色遮蔽度 |
|
||
| [离线设计参数](../design/agent/obsidian-offline.json) | 可编辑的播放速度、振幅、混合时长和三层柔光/反射强度 |
|
||
| [悬停时间参数](../src/shared/ai-elements/persona-hover-timing.json) | 150ms 渐显、80ms 跟随、100ms 停留、600ms 淡出及 100ms 状态混合 |
|
||
| [生成脚本](../scripts/build-agent-persona.mjs) | 验证原版重编码、修改离线动画,生成本地资源 |
|
||
| [应用资源](../public/agent/obsidian-water-v1.riv) | 前端加载的 9,929 字节资源 |
|
||
| [缓存版本清单](../src/shared/ai-elements/persona-resource-manifest.json) | 生成脚本同步输出的路径、SHA-256 和字节数,用于客户端缓存更新 |
|
||
| [静态帧生成脚本](../scripts/render-agent-persona-stills.mjs) | 从实际资源渲染在线、离线 PNG,供首次加载、减少动态效果和失败兜底使用 |
|
||
| [资源对照测试](../tests/browser/agent-persona-resource.e2e.ts) | 真实 WebGL2 运行时定时推进,比较原版与修改版画面 |
|
||
|
||
在前端仓库运行:
|
||
|
||
```bash
|
||
node scripts/build-agent-persona.mjs
|
||
node scripts/render-agent-persona-stills.mjs
|
||
node scripts/build-agent-persona.mjs --check
|
||
# 将原版还原到一个尚不存在的文件,应用资源不会被替换
|
||
node scripts/build-agent-persona.mjs --restore-original /tmp/obsidian-original.riv
|
||
pnpm test:browser tests/browser/agent-persona-resource.e2e.ts tests/browser/agent-orb.e2e.ts --workers=1
|
||
```
|
||
|
||
JSON 保存原始运行数据;脚本负责可审查的定制。`--check` 验证原始数据能逐字节恢复,并检查生成结果与应用资源一致。基线 SHA-256 为 `c6ae7d08f5787a560e38d3453ba155c28250acf2db75e6acf2b0d1a0cd6017db`。替换上游基线时需重新解析和验证,不应只修改散列值跳过检查。
|
||
|
||
配色编辑 `obsidian-material.json`,其中 `bodyColor` 只控制球面底色,`bodyOpacity` 控制它在待机/活动时的遮蔽度,其他蓝色反射沿用 `colors` 映射;离线动画编辑 `obsidian-offline.json`;重新生成 `.riv` 和静态帧后刷新前端。静态帧脚本使用已安装的 Playwright、esbuild 与 Rive WASM,无需访问外部动画服务。`fps` 控制离线时间轴速度,`amplitude` 控制位移与缩放的变化幅度,`transitionMs` 控制在线/离线的混合时长。各层 `opacity` 中的 `rest` 和 `crest` 是相对原始透明度的比例,分别对应循环开始/结束与中点。数值需满足 `0 ≤ rest ≤ crest ≤ 1`;保留循环端点一致,避免接缝。外围三个光圈均固定为 `0`。
|
||
|
||
`--restore-original` 使用只创建模式,目标文件已存在时会报错,避免覆盖已有编辑。原版还原文件与基线逐字节相同,包含原版离线消失的行为;前端继续加载带离线设计的应用资源。
|
||
|
||
### 离线图层参数
|
||
|
||
内部柔光的三层强度分别为 56%–68%、48%–62%、60%–72%,反射强度均为 0;周期与外观以第 4 节和 `obsidian-offline.json` 为准。参数中的图层名称是运行时结构的语义别名,不是作者原始命名。`objectId` 是画板组件编号,不能用 JSON 数组索引替代,绑定记录不占组件编号。
|
||
|
||
### 解析结果与修改
|
||
|
||
原版有 1 个 `Obsidian` 画板、20 段动画、13 个状态机层和 5 个布尔输入。`asleep` 动画在第 45 帧将节点 4 的透明度从 1 降至 0,并将节点 3、190、199、208 的缩放降至 0.6–0.8,因此离线时球体消失。
|
||
|
||
修改版保留原有图形、渐变结构、羽化、裁剪和在线运动,改变以下部分:
|
||
|
||
- 根据已选定的配色映射 RGB,并独立设置底色填充节点 189 的颜色、节点 186 的静止及活动透明度;原始 JSON 不改动,仍可完整还原银灰原版 `.riv`。
|
||
- `asleep` / `wake` 的透明度与缩放关键帧保持 1,避免隐藏或缩小球体。
|
||
- 在原有 20 段动画后追加 `offline_loop_1`、`offline_loop_2`、`offline_loop_3`,保持原有动画编号。
|
||
- 离线循环复用原版三层水纹,时间轴速率从 60 改为 24,约为原速的 40%;这是时间轴播放速度,不是将屏幕渲染限制为 24fps。
|
||
- 位移和呼吸振幅缩至原来的 25%;完整旋转保留首尾角度,避免循环接缝。
|
||
- 离线循环增加六个柔光/光圈节点的透明度轨道,其中节点 190、199、208 的光圈透明度固定为零;在线循环对应增加原始透明度常量轨道,确保恢复连接后完整恢复。
|
||
- 三个循环层根据既有 `asleep` 输入切换,使用 600ms 状态混合。
|
||
- 悬停保持原版反射几何、渐变与亮度,将 `hover_in` / `hover_out` 分别调整为 9 / 36 帧,即 150ms / 600ms,并使用原有贝塞尔插值器。退出时间轴持续保持节点 129 的指针约束,完全淡出后才释放;进出切换使用 100ms 混合。
|
||
|
||
生成结果包含 960 个对象、23 段动画。调整设计参数后需再生成并运行对照测试;无需修改前端组件或安装额外依赖。
|
||
|
||
### 前端接入与生命周期
|
||
|
||
悬停沿用原版节点 110 的反射尺寸,以及节点 129 对指针目标 1 的跟随约束。`Persona` 保留 Rive 监听器,通过外层 Pointer Events 与 `persona-hover.ts` 向画布转交鼠标的进入、移动和离开坐标;移动坐标以 80ms 时间常数平滑,移出延迟 100ms 后交给资源淡出,Rive 自行完成画布与画板坐标转换。画布内部不直接命中鼠标或触摸,只有就绪的外层接收并筛选主指针;不转交触摸和按压,因此手机点击不会触发白色高光。仅手动设置 `hover` 而禁用原生监听,会使目标停在中心,造成中央大片变白。这个问题由事件通路修复,不通过缩小光圈或改变材质补偿。
|
||
|
||
`persona-resource.ts` 加载同源的 `/agent/obsidian-water-v1.riv?v=<SHA-256>`。同一页面共用下载 Promise,跨刷新、再次打开页面使用浏览器 Cache Storage 的 `tjwater-agent-persona-v1` 缓存;只共享不可变字节,不共享 Rive 实例或 View Model。
|
||
|
||
读取顺序为页面内存、当前版本的持久缓存、网络。下载及缓存命中均检查字节数和 `RIVE` 文件头;支持 Web Crypto 时进一步验证 SHA-256。损坏缓存重新下载,错误页或不匹配的资源不写入缓存。新版本成功保存后,只删除本缓存中该资源路径的旧版本,不清理其他应用数据。重新生成动画时同步更新版本清单,`--check` 同时验证资源与清单。
|
||
|
||
持久缓存不可用、被禁用或空间不足时仍可下载并在当前页面复用。Cache Storage 需要安全上下文,生产环境使用 HTTPS,本机 localhost 开发可用;浏览器清理站点数据也可能移除缓存,见 [Cache API 说明](https://developer.mozilla.org/en-US/docs/Web/API/Cache)。缓存测试覆盖真实页面刷新,并阻断资源网络路径验证动画仍能播放。
|
||
|
||
Agent 服务不可用不妨碍前端读取该资源。这项缓存仅覆盖 `.riv`,Rive WASM、应用代码与其他资源沿用各自的加载策略;不构成整站离线支持。浏览器完全断网且应用或运行时尚未加载时,仍可能无法播放动画。
|
||
|
||
在线与离线切换不卸载可见的 Rive 实例,由状态机处理过渡。静态帧仅用于首次加载、资源失败、减少动态效果及未挂载动画的情况:在线使用 `obsidian-water-idle.png`,离线使用无外围光圈的 `obsidian-water-offline.png`。两张 192px PNG 均由当前 Rive 材质直接渲染,URL 携带资源版本;首次加载维持 180ms 淡入,不再出现银灰占位图。保留可见性卸载、Strict Mode 延迟初始化和原版悬停;没有重新加入 Motion 按压效果或 View Model 绑定。历史约束见 [Rive 内存记录](history/rive-persona-incident.md)。
|
||
|
||
### 验证边界与来源
|
||
|
||
运行时 JSON 能恢复当前 `.riv`,但不等于作者的 `.rev` 编辑器工程;导出时省略的编辑信息没有凭空恢复。原作者提供可编辑 `.rev` 的说明见 [资源定制文档](https://elements.surge.studio/docs/customization),格式差异见 [Rive 备份说明](https://rive.app/docs/editor/exporting/exporting-for-backup)。
|
||
|
||
原始图形来源为 [Surge Obsidian 动画](https://ejiidnob33g9ap1r.public.blob.vercel-storage.com/obsidian-2.0.riv)。解析时参考了 [rive-rs-cli 的格式读取实现](https://github.com/George-RD/rive-rs-cli/blob/main/src/validator/parser.rs)及其从 Rive 定义生成的类型/属性表;应用不依赖或包含该工具。原图形的作者归属不因解析和局部修改而改变。
|
||
|
||
对照测试在同一 WebGL2 运行时以固定时间步推进。就绪、思考、待回复、输出和稳定悬停状态要求与“仅应用相同冰水蓝材质的原版”采样画面逐像素一致,并验证主色由灰变蓝、合成到白底后仍保有蓝色,球体中心采样区域的不透明度不低于 90%。悬停另外比较左右指针位置对应的高光重心,要求局部反射随指针移动且移出后完全恢复。时间采样另外验证 150ms 渐显与 600ms 淡出,中途保持可见且最终恢复常态。指针定时测试覆盖惯性跟随、停留、快速进出与卸载清理;工作台测试验证平滑后的实际坐标最终到达原生监听器。离线检查完整球体、持续变化、初始离线和反复恢复;通过外围像素计数验证光圈完全消失,悬停不会重新出现,恢复在线后再次可见。另用相同运动轨迹、取消离线光照变化的对照资源,验证快速切换后在线明暗完整恢复。该检查覆盖固定采样时刻,不是所有设备、运行时间和状态序列的穷举验证。
|