| import { useState, useEffect } from "react"; |
| import { Loader2 } from "lucide-react"; |
|
|
| import { useLLM } from "../hooks/useLLM"; |
| import { ChatApp } from "./ChatApp"; |
|
|
| export function AppShell() { |
| const { status } = useLLM(); |
| const isReady = status.state === "ready"; |
| const [showChat, setShowChat] = useState(false); |
|
|
| useEffect(() => { |
| if (isReady) { |
| const timeoutId = setTimeout(() => setShowChat(true), 300); |
| return () => clearTimeout(timeoutId); |
| } |
| }, [isReady]); |
|
|
| return ( |
| <> |
| <div |
| className={`absolute inset-0 z-20 flex flex-col items-center justify-center transition-opacity duration-1000 ease-in-out ${ |
| showChat ? "opacity-0 pointer-events-none" : "opacity-100" |
| }`} |
| > |
| <Loader2 className="h-10 w-10 animate-spin text-[#76b900]" /> |
| <p className="mt-4 text-base text-white/80"> |
| {status.state === "loading" |
| ? (status.message ?? "Loading model…") |
| : status.state === "error" |
| ? "Error" |
| : "Preparing Nemotron-3-Nano…"} |
| </p> |
| <div className="mt-3 h-1.5 w-72 overflow-hidden rounded-full bg-white/20"> |
| <div |
| className="h-full rounded-full bg-[#76b900] transition-[width] duration-300 ease-out" |
| style={{ |
| width: `${ |
| status.state === "ready" |
| ? 100 |
| : status.state === "loading" |
| ? (status.progress ?? 0) |
| : 0 |
| }%`, |
| }} |
| /> |
| </div> |
| {status.state === "error" && ( |
| <p className="mt-2 text-sm text-red-300">{status.error}</p> |
| )} |
| </div> |
| |
| <div |
| className={`absolute inset-0 z-30 bg-[#0a0a0a] transition-opacity duration-1000 ease-in-out ${ |
| showChat ? "opacity-100" : "opacity-0 pointer-events-none" |
| }`} |
| > |
| <ChatApp /> |
| </div> |
| </> |
| ); |
| } |
|
|