更新 Skim 演示资源
重新生成产品演示视频以及随落地页发布的明/暗两套界面截图。所有操作均由真实的 Skim UI 在模拟的 Tauri IPC 层 (demo/mock/) 上驱动完成,因此绝不会触及真实的账户、邮箱或模型。
输出文件全部发布到 docs/ 目录:
skim-demo.mp4— 脚本化的功能漫游视频 (1280×800)skim-light.jpg,skim-dark.jpg— 界面截图,宽度 800px
不要提交或推送
此技能仅更新磁盘上的文件。不要执行 git add、git commit 或 git push。用户自行审查重新生成的资源,并决定保留哪些内容——这些是二进制文件,会产生较大的 diff,且退出码为 0 的运行结果在视觉上仍可能是错误的。保持工作树脏乱是正确的输出结果。
仅在用户在同一请求中明确要求时才触碰 git(例如“重新生成并提交它们”)。否则,告知用户文件已更新但未暂存。
运行方式
在仓库根目录下执行:
npm run demo
该命令链式执行三个步骤:demo:record(Playwright 驱动真实 UI → demo/output/raw.webm)→ demo:encode(ffmpeg → MP4,复制到 docs/)→ demo:shots(两种主题 → docs/)。
如果仅 UI 部分发生变化,只运行必要的步骤——这样更快,并且能使无关资源保持字节一致:
npm run demo:shots— 仅截图(静态 UI、颜色、布局变化)npm run demo:record && npm run demo:encode— 仅视频(流程、AI 行为)
前置条件:已执行 npm install,并且 ffmpeg 在 PATH 中。完整运行需要几分钟;录制是最慢的部分。
npm run demo 以 demo:browser (playwright install chromium) 开始——当浏览器已存在时该步骤为空操作。如果运行失败并提示“Executable doesn't exist at ...chromium_headless_shell-NNNN”,这是因为 Playwright 的浏览器构建版本与库版本不一致(^ 版本提升会重新指向,且 headless shell 是与 Chromium 分开下载的)。执行 npm run demo:browser 或完整的 npm run demo 即可解决此问题,无需将其作为演示问题来调试。单独的 demo:record / demo:shots 脚本会跳过此检查。
在报告成功前进行验证
运行可能以退出码 0 结束,但仍产生损坏的视频。未处理的 mock 命令会返回 undefined 而非抛出异常,这会呈现为空窗格而非错误——所以请相信你的眼睛,而不是退出码。
- 确认
docs/中的三个文件具有最新的 mtime。 - 检查尺寸:截图宽度 800px,MP4 为 1280×800。
- 在整个 MP4 中抽取帧并实际查看它们。按比例抽样而非固定秒数——漫游视频的长度会在添加场景或调整节奏时发生变化,因此硬编码的时间戳会失效:
每一帧都应该显示一个填充好的窗格,AI 场景应该有滚动输出的文本。dur=$(ffprobe -v error -show_entries format=duration -of csv=p=0 docs/skim-demo.mp4) for k in 1 2 3 4 5 6 7; do ffmpeg -y -hide_banner -loglevel error \ -ss $(echo "$dur * $k / 8" | bc -l) -i docs/skim-demo.mp4 -frames:v 1 /tmp/f$k.png donedemo/output/progress.log会记录每个场景完成时的名称,这有助于将错误帧映射回产生它的步骤。 - 查看两张截图——尤其检查暗色主题那张(邮件正文在沙箱化的 iframe 中渲染,有自己的主题逻辑,可能出现暗色背景上的暗色文字)。
报告哪些文件发生了变化以及你看到了什么。如果某个场景看起来有问题,请修复它而不是直接交付。
当 UI 变更导致漫游中断时
测试平台依赖两个约定。失败消息会告诉你哪个约定发生了变动:
IPC 命令名称 (demo/mock/tauri-core.ts)。mock 通过一个 switch 语句实现 invoke(),该 switch 分支对应 src/lib/api.ts 中的命令名称。如果某个命令被重命名或新增,请添加相应的 case。症状:出现空窗格或缺失数据,而非崩溃。
选择器和英文标签 (demo/record.mjs)。漫游会点击诸如 .row、.quick-btn:has-text("Summarize")、.ai-input .instruction 等元素。重命名的 CSS 类或 src/lib/i18n/locales/en.json 中更改的字符串会导致匹配步骤失败,超时错误会指出失败的选择器。演示强制使用 locale: "en",因此英文标签才是关键。
测试数据同样如此:demo/mock/data.ts 会匹配 ai.prompt_* 字符串以决定快速提示对应哪个脚本化的回答,而漫游会等待这些回答中的关键单词(如 "sync"、"yours"、"4.2")。重写测试数据会导致依赖它的等待超时。
所有测试数据和每个脚本化的 AI 响应都位于 demo/mock/data.ts 中——当你希望演示展示不同内容(不同的邮件、不同的摘要、不同的草稿回复)时,这就是需要编辑的文件。
编辑 record.mjs 时非显而易见的陷阱:
- 编辑器正文是一个
<textarea>。其流式内容是它的 value,而不是它的 text——请使用waitValue而非waitText进行等待。 .compose-window textarea也会匹配到 AI 指令框(它在 DOM 中排在前面)。对于正文请使用.compose-window > textarea。- 编辑场景通过 hash 导航,并且必须在之后调用
page.reload()——仅 hash 的goto不会重新挂载 SPA,因此编辑器永远不会出现。 - 在
browser.close()之前 使用video.saveAs()保存视频,否则会失败并提示“target closed”。
节奏与主题
默认值针对可读的落地视频进行了调优。可通过环境变量(所有单位均为毫秒)覆盖:DEMO_READ(每屏停留时间)、DEMO_BEAT(动作间间隔)、DEMO_TYPE(按键延迟),以及 DEMO_AI_TYPING / DEMO_AI_THINK / DEMO_AI_STEP(AI 流式输出节奏)。当你在场景中添加内容后感觉节奏过快时,这些变量会很有用。
主题是两维的——"<cold|warm>-<light|dark>",例如 warm-light(应用默认值)。两个脚本都通过 localStorage(skimdemo.theme)强制设置主题,mock 会从 get_settings 返回该主题:
- 视频固定使用
warm-light;可通过DEMO_THEME=cold-dark npm run demo:record覆盖。 - 截图渲染
warm-light→docs/skim-light.jpg和warm-dark→docs/skim-dark.jpg(demo/screenshots.mjs中的SHOTS数组将主题映射为文件名)。
注意遗留的单维值:"light" 和 "dark" 仍然可以解析,但会映射到 cold 调色板,因此传入它们会静默地渲染出错误的色温而非失败。
DEMO_PREBUILT=1 会从 demo/dist-demo 提供静态构建版本,而非使用开发服务器——在受限或无头环境下很方便。
更多细节见 demo/README.md。