Files
next-tjwater-frontend/docs/agent-persona-design.md
T
jiang a617aa95e9 fix(workbench): stabilize investigation presentation rendering
Keep investigation graphs stable during updates, defer expensive chart rendering, and prevent stale results when switching tasks. Adopt reference annotations and consolidate frontend documentation.
2026-11-11 19:10:41 +08:00

19 KiB
Raw Permalink Blame History

Agent Persona 设计

整理日期:2026-11-10。本文保存供水前端当前的 Persona 视觉与交互规格,作为后续调整的基线。资源解析、再生成及缓存实现见 动画资源说明;通用控件和语义表面继续遵循 样式设计指南。

1. 形象与材质

采用 Obsidian 原生 Rive 水纹球,保留柔和羽化、流动反射、不规则边缘水纹和状态机。配色采用已选定的 02「冰水蓝」:浅色水体承载白色高光,暗部保持蓝色,外围水纹提供地图上的辨识度。主体底色具有较高遮蔽度,减少地图道路穿过球体的现象。

角色 当前值 用途
球面底色 #DCEFFA 浅冰蓝底层,独立于流动反射
底色不透明度 待机 94%,活动 98% 保持主体清楚,避免背景纹理干扰
水体反射 #299FDF 提供蓝色层次与流动感
同色相暗部 #1482BC 保留体积,避免灰黑混色
高光 #FFFFFF 保留清晰、干净的反射
外围水纹 #4C9ECC 提高浅色地图上的辨识度

颜色直接写入 Rive 资源,不使用 CSS 色相、亮度或对比度滤镜。通用 UI 仍引用共享语义颜色;上表仅定义动画资源本身的材质。参数源为 obsidian-material.json。

以下为当前资源的实际静态帧,用于辨认在线与离线外观;运动行为以第 4 节为准。

在线待机 离线静水待机
冰水蓝在线 Persona,有外围水纹 冰水蓝离线 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。

5. 悬停与点击

悬停沿用原版三层反射的尺寸、渐变和亮度。进入以约 150ms 渐显;白色高光以约 80ms 的跟随时间常数趋近鼠标位置,保留轻微惯性。鼠标移出后先停留 100ms,再以约 600ms 淡出,退场期间保留最后的跟随位置。不能只打开 hover 布尔值而遗漏坐标,否则原生指针目标停在默认中心,造成球体中间大片变白。球体、点击范围和常态外围水纹尺寸均保持原定义。

共享 Persona 保留 Rive 原生指针监听,经由 persona-hover.ts 平滑 CSS 坐标后,将主指针的进入、移动、离开和取消转交给画布;坐标转换与资源内的跟随约束由 Rive 完成。150ms / 600ms 渐变由 Rive 时间轴负责,进出切换以 100ms 状态混合衔接;所有时间参数集中在 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 带版本参数,使旧缓存自然更新。缓存范围与校验逻辑见 动画资源说明。

7. 实现与维护

入口 文件
业务形象与降级 AgentPersona
原生状态机接入 Persona
桌面悬浮按钮 AgentCollapsedRail
标题区 AgentCommandPanel
资源生成与原版还原 build-agent-persona.mjs
资源版本与缓存 persona-resource.ts

材质与离线参数以对应 JSON 为准,悬停沿用原始资源中的几何与指针约束,仅调整时间轴与指针跟随节奏;改动时同步本文的设计规格与资源维护章节。原始解析数据保留以便逐字节还原 .riv;该数据不等于作者完整的 .rev 编辑器工程。

验收时检查 64px 桌面入口、64px 标题形象与 40px 手机入口,覆盖默认、悬停进入与退出、快速切换、离线和恢复在线。桌面检查 1024/1440px 视口及 400/640px 面板,手机检查 320/375px。确认左上间距一致、按压无缩放、首次加载无画面硬切、底色能遮蔽地图、离线无外围光圈、缓存刷新后仍加载当前版本。

资源级检查使用固定时间步对照原版与修改版:在线状态保持相同材质下的逐像素一致;悬停检查左右移动时局部高光跟随指针,移出后完全恢复。工作台检查鼠标坐标实际到达悬浮入口和标题形象的 Rive 监听器,不能仅断言 hover 开关变化。离线检查完整球体、持续变化和光圈关闭。运行命令见 资源说明,各次实际验证结果记录在 实施记录。

8. 资源生成与维护

文件与再生成

文件 用途
运行时源数据 从原版解析的 775 个对象,包含图形、属性、关键帧、状态机和绑定数据
冰水蓝材质参数 已确认的水体、暗部、白色高光和水纹颜色,以及球面底色遮蔽度
离线设计参数 可编辑的播放速度、振幅、混合时长和三层柔光/反射强度
悬停时间参数 150ms 渐显、80ms 跟随、100ms 停留、600ms 淡出及 100ms 状态混合
生成脚本 验证原版重编码、修改离线动画,生成本地资源
应用资源 前端加载的 9,929 字节资源
缓存版本清单 生成脚本同步输出的路径、SHA-256 和字节数,用于客户端缓存更新
静态帧生成脚本 从实际资源渲染在线、离线 PNG,供首次加载、减少动态效果和失败兜底使用
资源对照测试 真实 WebGL2 运行时定时推进,比较原版与修改版画面

在前端仓库运行:

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 说明。缓存测试覆盖真实页面刷新,并阻断资源网络路径验证动画仍能播放。

Agent 服务不可用不妨碍前端读取该资源。这项缓存仅覆盖 .riv,Rive WASM、应用代码与其他资源沿用各自的加载策略;不构成整站离线支持。浏览器完全断网且应用或运行时尚未加载时,仍可能无法播放动画。

在线与离线切换不卸载可见的 Rive 实例,由状态机处理过渡。静态帧仅用于首次加载、资源失败、减少动态效果及未挂载动画的情况:在线使用 obsidian-water-idle.png,离线使用无外围光圈的 obsidian-water-offline.png。两张 192px PNG 均由当前 Rive 材质直接渲染,URL 携带资源版本;首次加载维持 180ms 淡入,不再出现银灰占位图。保留可见性卸载、Strict Mode 延迟初始化和原版悬停;没有重新加入 Motion 按压效果或 View Model 绑定。历史约束见 Rive 内存记录。

验证边界与来源

运行时 JSON 能恢复当前 .riv,但不等于作者的 .rev 编辑器工程;导出时省略的编辑信息没有凭空恢复。原作者提供可编辑 .rev 的说明见 资源定制文档,格式差异见 Rive 备份说明。

原始图形来源为 Surge Obsidian 动画。解析时参考了 rive-rs-cli 的格式读取实现及其从 Rive 定义生成的类型/属性表;应用不依赖或包含该工具。原图形的作者归属不因解析和局部修改而改变。

对照测试在同一 WebGL2 运行时以固定时间步推进。就绪、思考、待回复、输出和稳定悬停状态要求与“仅应用相同冰水蓝材质的原版”采样画面逐像素一致,并验证主色由灰变蓝、合成到白底后仍保有蓝色,球体中心采样区域的不透明度不低于 90%。悬停另外比较左右指针位置对应的高光重心,要求局部反射随指针移动且移出后完全恢复。时间采样另外验证 150ms 渐显与 600ms 淡出,中途保持可见且最终恢复常态。指针定时测试覆盖惯性跟随、停留、快速进出与卸载清理;工作台测试验证平滑后的实际坐标最终到达原生监听器。离线检查完整球体、持续变化、初始离线和反复恢复;通过外围像素计数验证光圈完全消失,悬停不会重新出现,恢复在线后再次可见。另用相同运动轨迹、取消离线光照变化的对照资源,验证快速切换后在线明暗完整恢复。该检查覆盖固定采样时刻,不是所有设备、运行时间和状态序列的穷举验证。