import { useState, useCallback, useRef } from "react"; import { tts } from "@mintplex-labs/piper-tts-web"; const DEFAULT_VOICE = "en_US-hfc_female-medium"; export type TtsStatus = "idle" | "downloading" | "ready" | "speaking" | "error"; export function usePiperTts() { const [status, setStatus] = useState("idle"); const [progress, setProgress] = useState(0); const audioRef = useRef(null); const prewarm = useCallback(async () => { if (status === "ready" || status === "downloading") return; setStatus("downloading"); setProgress(0); try { const stored = await tts.stored(); if (!stored.includes(DEFAULT_VOICE)) { await tts.download(DEFAULT_VOICE, (p: { loaded: number; total: number }) => { setProgress(Math.round((p.loaded / p.total) * 100)); }); } setStatus("ready"); setProgress(100); } catch { setStatus("error"); } }, [status]); const speak = useCallback(async (text: string) => { if (status !== "ready") return; // Stop any currently playing audio if (audioRef.current) { audioRef.current.pause(); audioRef.current = null; } setStatus("speaking"); try { const wav = await tts.predict({ text, voiceId: DEFAULT_VOICE }); const audio = new Audio(wav); audioRef.current = audio; audio.onended = () => { audioRef.current = null; setStatus("ready"); }; audio.onerror = () => { audioRef.current = null; setStatus("ready"); }; await audio.play(); } catch { setStatus("ready"); } }, [status]); const stop = useCallback(() => { if (audioRef.current) { audioRef.current.pause(); audioRef.current = null; } if (status === "speaking") setStatus("ready"); }, [status]); return { status, progress, prewarm, speak, stop }; }