import type { Bot, ThreadSnapshot } from "@rakazo/contracts"; import { useEffect, useRef } from "react"; import { wireOoAudioVoiceDucking } from "../../lib/oo-audio-voice-wire"; import { ooSound } from "../../lib/oo-sound"; import { ooVocals } from "../../lib/oo-vocals"; import { ooVoiceMark } from "../../lib/oo-voice-mark"; export type ShellSoundsInput = { bots: Bot[]; transcriptRunning: boolean; callOpen: boolean; displayedRunErrorId: string | null; activeSnapshot: ThreadSnapshot | null; inGroup: boolean; }; export function useShellSounds({ bots, transcriptRunning, callOpen, displayedRunErrorId, activeSnapshot, inGroup, }: ShellSoundsInput): void { const prevTranscriptRunning = useRef(false); const prevRunErrorId = useRef(null); const seenHandoffKeys = useRef(new Set()); const appSoundStarted = useRef(false); const prevBotStatuses = useRef(new Map()); useEffect(() => wireOoAudioVoiceDucking(), []); useEffect(() => { if (appSoundStarted.current) return; appSoundStarted.current = true; ooSound.play("app-start"); window.setTimeout(() => ooVoiceMark.maybePlayColdOpen(), 280); }, []); useEffect(() => { for (const bot of bots) { const prev = prevBotStatuses.current.get(bot.id); if ( prev !== bot.status && (bot.status === "waiting_input" || bot.status === "waiting_takeover") ) { ooSound.play("need-you"); ooVocals.reactForEvent("need-you"); } prevBotStatuses.current.set(bot.id, bot.status); } }, [bots]); useEffect(() => { if (prevTranscriptRunning.current && !transcriptRunning && !callOpen) { ooSound.play("success-resolve"); ooVocals.reactForEvent("success"); } prevTranscriptRunning.current = transcriptRunning; }, [transcriptRunning, callOpen]); useEffect(() => { const runId = displayedRunErrorId; if (runId && runId !== prevRunErrorId.current) { ooSound.play("attention"); } prevRunErrorId.current = runId; }, [displayedRunErrorId]); const seenGroupSparkKeys = useRef(new Set()); useEffect(() => { for (const message of activeSnapshot?.messages ?? []) { for (const block of message.blocks) { if (block.kind === "handoff") { const key = `${message.id}:handoff:${block.toBotId}`; if (seenHandoffKeys.current.has(key)) continue; seenHandoffKeys.current.add(key); if (seenHandoffKeys.current.size > 80) { const oldest = seenHandoffKeys.current.values().next().value; if (oldest !== undefined) seenHandoffKeys.current.delete(oldest); } ooSound.play("handoff"); continue; } if (inGroup && block.kind === "bot_message_received") { const key = `${message.id}:peer:${block.fromBotId}`; if (seenGroupSparkKeys.current.has(key)) continue; seenGroupSparkKeys.current.add(key); if (seenGroupSparkKeys.current.size > 80) { const oldest = seenGroupSparkKeys.current.values().next().value; if (oldest !== undefined) seenGroupSparkKeys.current.delete(oldest); } ooSound.play("group-spark"); } } } }, [activeSnapshot?.messages, inGroup]); }