- Install @mintplex-labs/piper-tts-web as UI dependency - Create usePiperTts hook with prewarm/speak/stop/status/progress (VOICE-01, VOICE-02) - tts.stored() checks IndexedDB cache to skip re-download - tts.download() with progress callback for visible download progress - tts.predict() returns WAV blob URL for CPU-safe WASM synthesis - Create TtsButton component showing download progress during prewarm - TtsButton shows Volume2/VolumeX icons for idle/speaking states
62 lines
1.7 KiB
TypeScript
62 lines
1.7 KiB
TypeScript
import { Volume2, VolumeX, Loader2 } from "lucide-react";
|
|
import { Button } from "./ui/button";
|
|
import type { TtsStatus } from "../hooks/usePiperTts";
|
|
|
|
interface TtsButtonProps {
|
|
status: TtsStatus;
|
|
progress: number;
|
|
onSpeak: () => void;
|
|
onStop: () => void;
|
|
onPrewarm: () => void;
|
|
disabled?: boolean;
|
|
}
|
|
|
|
export function TtsButton({ status, progress, onSpeak, onStop, onPrewarm, disabled }: TtsButtonProps) {
|
|
if (status === "downloading") {
|
|
return (
|
|
<Button variant="ghost" size="icon" className="h-8 w-8 relative" disabled title={`Downloading voice model: ${progress}%`}>
|
|
<Loader2 className="h-4 w-4 animate-spin" />
|
|
<span className="absolute -bottom-1 text-[10px] text-muted-foreground">{progress}%</span>
|
|
</Button>
|
|
);
|
|
}
|
|
|
|
if (status === "speaking") {
|
|
return (
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
className="h-8 w-8 text-primary"
|
|
onClick={onStop}
|
|
aria-label="Stop speaking"
|
|
title="Stop speaking"
|
|
>
|
|
<VolumeX className="h-4 w-4" />
|
|
</Button>
|
|
);
|
|
}
|
|
|
|
// idle or error: clicking triggers prewarm then speak
|
|
// ready: clicking triggers speak directly
|
|
const handleClick = () => {
|
|
if (status === "ready") {
|
|
onSpeak();
|
|
} else {
|
|
onPrewarm();
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
className="h-8 w-8"
|
|
onClick={handleClick}
|
|
disabled={disabled || status === "error"}
|
|
aria-label="Read aloud"
|
|
title={status === "error" ? "TTS unavailable" : status === "idle" ? "Download voice model and read aloud" : "Read aloud"}
|
|
>
|
|
<Volume2 className="h-4 w-4" />
|
|
</Button>
|
|
);
|
|
}
|