import { useRef, useState, useEffect } from "react"; import { motion } from "motion/react"; export default function App() { // Create a reference to the worker object. const worker = useRef(null); const [inputText, setInputText] = useState("Life is like a box of chocolates. You never know what you're gonna get."); const [selectedSpeaker, setSelectedSpeaker] = useState("af_heart"); const [voices, setVoices] = useState([]); const [status, setStatus] = useState(null); const [error, setError] = useState(null); const [loadingMessage, setLoadingMessage] = useState("Loading..."); const [results, setResults] = useState([]); // We use the `useEffect` hook to setup the worker as soon as the `App` component is mounted. useEffect(() => { // Create the worker if it does not yet exist. worker.current ??= new Worker(new URL("./worker.js", import.meta.url), { type: "module", }); // Create a callback function for messages from the worker thread. const onMessageReceived = (e) => { switch (e.data.status) { case "device": setLoadingMessage(`Loading model (device="${e.data.device}")`); break; case "ready": setStatus("ready"); setVoices(e.data.voices); break; case "error": setError(e.data.data); break; case "complete": const { audio, text } = e.data; // Generation complete: re-enable the "Generate" button setResults((prev) => [{ text, src: audio }, ...prev]); setStatus("ready"); break; } }; const onErrorReceived = (e) => { console.error("Worker error:", e); setError(e.message); }; // Attach the callback function as an event listener. worker.current.addEventListener("message", onMessageReceived); worker.current.addEventListener("error", onErrorReceived); // Define a cleanup function for when the component is unmounted. return () => { worker.current.removeEventListener("message", onMessageReceived); worker.current.removeEventListener("error", onErrorReceived); }; }, []); const handleSubmit = (e) => { e.preventDefault(); setStatus("running"); worker.current.postMessage({ type: "generate", text: inputText.trim(), voice: selectedSpeaker, }); }; return (
{error ?? loadingMessage}
Powered by
Kokoro
and
Transformers.js
{result.text}