Xenova's picture
Xenova HF Staff
Upload application
59c3ada verified
import { useState, useEffect, useRef, useCallback } from "react";
import { Volume2, VolumeX } from "lucide-react";
import { useLLM } from "./hooks/useLLM";
import { useTTS } from "./hooks/useTTS";
import useAudioPlayer from "./hooks/useAudioPlayer";
import LandingScreen from "./components/LandingScreen";
import ProgressScreen from "./components/ProgressScreen";
import ErrorScreen from "./components/ErrorScreen";
import WORKLET from "./play-worklet.js?raw";
import MainApplication from "./components/MainApplication";
export default function App() {
const llm = useLLM();
const tts = useTTS();
const { initAudio, playPopSound, playHoverSound, toggleMusic, playMusic, isMusicPlaying, isAudioReady } =
useAudioPlayer();
const [appState, setAppState] = useState<"landing" | "loading" | "main" | "error">(
navigator.gpu ? "landing" : "error",
);
const [error, setError] = useState<string | null>(null);
const audioWorkletNodeRef = useRef<AudioWorkletNode | null>(null);
const audioContextRef = useRef<AudioContext | null>(null);
const audioGlobal = (globalThis as any).__AUDIO__ || {
ctx: null as AudioContext | null,
node: null as AudioWorkletNode | null,
loaded: false as boolean,
};
(globalThis as any).__AUDIO__ = audioGlobal;
const allowAutoplayRef = useRef(true);
const handleToggleMusic = useCallback(() => {
if (isMusicPlaying) allowAutoplayRef.current = false;
toggleMusic();
}, [isMusicPlaying, toggleMusic]);
const handleLoadApp = async () => {
setAppState("loading");
initAudio();
if (audioGlobal.ctx && audioGlobal.node) {
audioContextRef.current = audioGlobal.ctx;
audioWorkletNodeRef.current = audioGlobal.node;
await audioContextRef.current?.resume();
} else {
try {
const audioContext = new AudioContext({ sampleRate: 24000 });
audioContextRef.current = audioContext;
await audioContext.resume();
if (!audioGlobal.loaded) {
const blob = new Blob([WORKLET], { type: "application/javascript" });
const url = URL.createObjectURL(blob);
await audioContext.audioWorklet.addModule(url);
URL.revokeObjectURL(url);
audioGlobal.loaded = true;
}
const workletNode = new AudioWorkletNode(audioContext, "buffered-audio-worklet-processor");
workletNode.connect(audioContext.destination);
audioWorkletNodeRef.current = workletNode;
audioGlobal.ctx = audioContext;
audioGlobal.node = workletNode;
} catch {}
}
await audioContextRef.current?.resume();
llm.load();
tts.load();
};
const handleLoadingComplete = useCallback(() => {
setAppState("main");
if (allowAutoplayRef.current && !isMusicPlaying) playMusic();
}, [playMusic, isMusicPlaying]);
const handleRetry = () => {
setError(null);
handleLoadApp();
};
useEffect(() => {
if (llm.error) {
setError(`LLM Error: ${llm.error}`);
setAppState("error");
} else if (tts.error) {
setError(`TTS Error: ${tts.error}`);
setAppState("error");
} else if (llm.isReady && tts.isReady) {
handleLoadingComplete();
}
}, [llm.isReady, tts.isReady, llm.error, tts.error, handleLoadingComplete]);
useEffect(() => {
if (!navigator.gpu) {
setError("WebGPU is not supported in this browser.");
setAppState("error");
return;
}
return () => {
audioWorkletNodeRef.current?.disconnect();
};
}, []);
return (
<>
<div className="bg-pattern h-screen text-black relative overflow-hidden">
{isAudioReady && (
<button
onClick={handleToggleMusic}
className="absolute top-4 right-4 z-20 p-2 bg-white/50 border-2 border-black rounded-full shadow-[2px_2px_0px_#000] hover:bg-white/80 transition-colors"
>
{isMusicPlaying ? <Volume2 /> : <VolumeX />}
</button>
)}
<LandingScreen isVisible={appState === "landing"} onLoad={handleLoadApp} playHoverSound={playHoverSound} />
<ProgressScreen isVisible={appState === "loading"} progress={(llm.progress + tts.progress) / 2} />
<ErrorScreen isVisible={appState === "error"} error={error} onRetry={handleRetry} />
<MainApplication
isVisible={appState === "main"}
playPopSound={playPopSound}
playHoverSound={playHoverSound}
generate={llm.generate}
streamTTS={tts.stream}
isTTSReady={tts.isReady}
audioWorkletNode={audioWorkletNodeRef.current}
toggleMusic={handleToggleMusic}
isMusicPlaying={isMusicPlaying}
/>
</div>
</>
);
}