Hermes 開場動畫
Hermes Desktop 已內建一段 22 秒的開場影片 — 包含七個節拍、即時立方體視埠、平行代理與 Nous 品牌標誌,並附有合成音效。該影片存在,但幾乎沒有人會看到第二次:播放條件為 guestOnboardingEnabled && !seen。
此技能讓影片在每次啟動時播放,並允許您在內建音效的基礎上疊加自訂語音旁白。
使用時機
- 「讓 Hermes 啟動動畫每次都播放。」
- 「將我的音訊放在開機動畫上 — 疊加,而非取代音效。」
- 針對
apps/desktop/src/components/intro-reveal/的任何修改。
現有影片結構
| 項目 | 路徑 |
|---|---|
| 閘門(播放時機) | apps/desktop/src/components/intro-reveal/index.tsx |
| 播放資格 + 階段儲存 | apps/desktop/src/store/intro-reveal.ts |
| 覆蓋層啟動 | src/components/intro-reveal/intro-root.tsx |
| 時鐘、計分、畫布 | use-intro-clock.ts、timeline.ts、viewport-cube.ts |
| 合成音效提示 | sound.ts |
| 原生視窗 | electron/intro-reveal-window.ts、electron/main.ts |
時間計算:INTRO_TOTAL_MS = 17 600 是樂譜時間;實際時間為 × INTRO_PACE (1.25) = 22 000 毫秒,再加上 INTRO_EXIT_MS 900 毫秒 → 總共 23 125 毫秒。
每次啟動都重播 — 三個編輯,缺一不可
src/store/intro-reveal.ts—isIntroRevealEnabled()必須不再要求訪客引導;shouldPlayFirstRunIntro()在非引導狀態下回傳!playedThisSession。必須使用模組層級的playedThisSession標誌:閘門的 effect 會在階段回到'hidden'時重新執行,而影片結束時恰好會觸發此情況 — 沒有該標誌就會無限循環。src/components/intro-reveal/intro-root.tsx— 覆蓋層視窗會檢查isOnboardingEnabled()並在符合條件前返回,導致無法顯示。將其改為與主渲染器相同的判斷條件。electron/main.ts—introRevealController建構時帶有enabled: GUEST_ONBOARDING,因此主程序會拒絕open。將其設為true;活躍性由渲染器決定。
引導流程的 beginOnboardingFlow() / queueGuideAfterIntro() / takeGuideShape() 皆會自我檢查 isOnboardingEnabled(),因此在引導關閉時不會執行 — 無需額外修改。
關閉開關:localStorage['hermes-intro-reveal-always-v1'] = '0',或使用 HERMES_SKIP_INTRO=1 / --skip-intro 啟動。
疊加音訊軌道
將檔案置於 apps/desktop/public/intro-voiceover.mp3(Vite 會將 public/ 原樣複製到 dist/),然後在 sound.ts 中:
export function startVoiceover(cutAtMs: number): { stop: () => void }
— 建立一個 Audio 元素,設定 volume,開始播放,並使用 setTimeout(stop, cutAtMs - 300) 使最後 300 毫秒淡出而非突然中斷。在 use-intro-clock.ts 中與 startPad() 相鄰處呼叫它,並在 effect 清理時停止。
請使用 <audio> 元素,切勿使用 fetch() + decodeAudioData()。 打包後的渲染器在 file:// 上執行,fetch 和 decodeAudioData 會因 CORS 而被阻擋;媒體元素則可正常載入 file:// 資產。
兩個音軌本質上互相獨立 — 元素音訊與 WebAudio 提示在作業系統層級混合,無需任何衰減處理。將 $hapticsMuted 連接至兩者,並訂閱而非僅讀取一次,否則靜音切換會不同步。
裁剪點:
const cutMs = Math.round((INTRO_TOTAL_MS + INTRO_EXIT_MS) * INTRO_PACE) // 23 125
發布至打包應用程式
僅修改原始碼不會生效,必須重新打包 asar。這裡有兩個值得檢查的陷阱:
找出使用者實際啟動的應用程式 — 切勿假設為 /Applications。
ps aux | grep '[H]ermes.app/Contents/MacOS/Hermes'
defaults read com.apple.dock persistent-apps | grep -o '/[^"']*Hermes\.app'
Hermes 更新會放置在 ~/.hermes/hermes-agent*/apps/desktop/release/mac-arm64/Hermes.app,而 Dock 通常指向該處;反之 /Applications/Hermes.app 可能是過時的 0.0.0 副本。請以 CFBundleShortVersionString 確認(0.17.x 為正式版本,0.0.0 則不是)。
也請重新整理 app.asar.unpacked/dist/。 打包構建會將 dist/** 列在 asarUnpack 中,而 resolveRendererIndex() 會優先使用該複本 — 僅重新打包 asar 會導致舊的 unpacked 樹狀結構遮蔽變更。
npm install --workspace apps/desktop --ignore-scripts \
--engine-strict=false --min-release-age=0
cd apps/desktop && node ../../node_modules/vite/bin/vite.js build
node scripts/bundle-electron-main.mjs # 主程序修改也需要此步驟
asar extract "$RES/app.asar" "$WORK"
rm -rf "$WORK/dist" && cp -R apps/desktop/dist "$WORK/dist"
asar pack "$WORK" "$WORK/app.asar"
mv -f "$WORK/app.asar" "$RES/app.asar"
cp -R apps/desktop/dist/. "$RES/app.asar.unpacked/dist/"
codesign --force --deep -s - "$APP" # 臨時簽章;Hermes 使用臨時簽章發布
在網路緩慢時,--ignore-scripts 很重要:下載 Electron 二進位檔並非建構或重新打包所需,僅在執行開發版本時才需要。
在不使用 Electron 的情況下驗證
在普通瀏覽器中渲染影片 — 無需打包,無需下載二進位檔:
cd apps/desktop && node ../../node_modules/vite/bin/vite.js --host 127.0.0.1 --port 5174
page.add_init_script("""
window.hermesDesktop = {
guestOnboardingEnabled: true, skipIntro: false,
introReveal: { ready(){}, skip(){},
open(){ return Promise.resolve() }, close(){ return Promise.resolve() },
onSkip(){ return () => {} }, onClosed(){ return () => {} } }
};
""")
page.goto("http://127.0.0.1:5174/?win=intro")
page.evaluate("import('/src/components/intro-reveal/intro-root.tsx').then(m => m.mountIntroReveal())")
路由自身的掛載呼叫會在沒有橋接時不執行;在模擬後手動匯入才是實際繪製的關鍵。若要檢查音訊軌道,請模擬 window.Audio 並記錄實例 — 確認 currentTime 隨著影片前進,且 paused === true, volume === 0 在裁剪點處成立。
值得保留在 src/store/intro-reveal.test.ts 中的合約測試:
- 在正常啟動時播放,即使已設定
hermes-intro-reveal-seen-v1 - 僅播放一次 —
finishIntroReveal()必須不能重新啟用 - 尊重 opt-out 鍵與
skipIntro啟動標誌 - 不影響引導流程的首次執行語意
陷阱
- 移除
playedThisSession會導致無限重播循環,而非無錯誤的啟動。 INTRO_TOTAL_MS是樂譜時間。忘記INTRO_PACE會使語音旁白提早 4.4 秒結束。- 主程序的
enabled標誌與覆蓋層自身的保護是獨立的閘門;遺漏任何一個都會導致沒有任何畫面渲染,且不顯示錯誤。 - 四個獨立層級確保畫面能重新顯示(正常關閉、Esc + 1.2 秒備援、本機 deadman、34 秒主程序看門狗)。在調整時間時請勿移除其中任何一個。
hermes update會將應用程式更換為官方版本。修改會保留在 Git 中;更新後請重新執行安裝程式。