| import { useEffect, useState } from "react"; | |
| import { LandingPage } from "./components/LandingPage"; | |
| import { ChatApp } from "./components/ChatApp"; | |
| import { useLLM } from "./hooks/useLLM"; | |
| import { Loader2 } from "lucide-react"; | |
| import "katex/dist/katex.min.css"; | |
| function App() { | |
| const { status, loadModel, selectedModel, setSelectedModel, loadedModelId } = | |
| useLLM(); | |
| const [hasStarted, setHasStarted] = useState(false); | |
| const [showChat, setShowChat] = useState(false); | |
| const isReady = | |
| status.state === "ready" && loadedModelId === selectedModel.id; | |
| const isLoading = hasStarted && !isReady && status.state !== "error"; | |
| const handleStart = () => { | |
| setHasStarted(true); | |
| loadModel(); | |
| }; | |
| const handleGoHome = () => { | |
| setShowChat(false); | |
| setTimeout(() => setHasStarted(false), 700); | |
| }; | |
| useEffect(() => { | |
| if (isReady && hasStarted) { | |
| setShowChat(true); | |
| } | |
| }, [isReady, hasStarted]); | |
| return ( | |
| <div className="relative h-screen w-screen bg-[#0A3235]"> | |
| {/* Landing page — hidden once loading starts */} | |
| <div | |
| className={`absolute inset-0 z-10 transition-opacity duration-700 ${ | |
| hasStarted ? "opacity-0 pointer-events-none" : "opacity-100" | |
| }`} | |
| > | |
| <LandingPage | |
| onStart={handleStart} | |
| isLoading={isLoading} | |
| showChat={showChat} | |
| selectedModel={selectedModel} | |
| onSelectModel={setSelectedModel} | |
| loadedModelId={loadedModelId} | |
| /> | |
| </div> | |
| {/* Chat page — fades in when ready */} | |
| <div | |
| className={`absolute inset-0 z-10 transition-opacity duration-700 ${ | |
| showChat ? "opacity-100" : "opacity-0 pointer-events-none" | |
| }`} | |
| > | |
| {hasStarted && <ChatApp onGoHome={handleGoHome} />} | |
| </div> | |
| {/* Loading overlay — sits on top, fades from loading screen directly to chat */} | |
| <div | |
| className={`absolute inset-0 z-30 flex flex-col items-center justify-center transition-opacity duration-700 bg-[#0A3235] ${ | |
| isLoading ? "opacity-100" : "opacity-0 pointer-events-none" | |
| }`} | |
| > | |
| <div | |
| className={`flex w-full max-w-md flex-col items-center px-6 transition-all duration-700 ${isLoading ? "opacity-100 translate-y-0" : "opacity-0 translate-y-4"}`} | |
| > | |
| <img | |
| src="/ai2.svg" | |
| alt="AI2" | |
| className="mb-8 h-9 w-auto" | |
| draggable={false} | |
| /> | |
| <Loader2 className="h-10 w-10 animate-spin text-[#F0529C]" /> | |
| <p className="mt-4 text-sm tracking-wide text-[#FAF2E9b3]"> | |
| {status.state === "loading" | |
| ? (status.message ?? "Loading model…") | |
| : status.state === "error" | |
| ? "Error" | |
| : "Initializing…"} | |
| </p> | |
| <div className="mt-4 h-1.5 w-full rounded-full bg-[#FAF2E91a] overflow-hidden"> | |
| <div | |
| className="h-full rounded-full bg-[linear-gradient(90deg,#105257_0%,#F0529C_60%,#B11BE8_100%)] transition-[width] ease-out" | |
| style={{ | |
| width: `${status.state === "ready" ? 100 : status.state === "loading" && status.progress != null ? status.progress : 0}%`, | |
| }} | |
| /> | |
| </div> | |
| {status.state === "error" && ( | |
| <p className="mt-3 text-sm text-red-400">{status.error}</p> | |
| )} | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| export default App; | |