File size: 4,707 Bytes
59c3ada |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
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>
</>
);
}
|