LFM2-MoE-WebGPU / src /components /AppShell.tsx
mlabonne's picture
Add demo files (#1)
4755edd
import { useState, useEffect } from "react";
import { useLLM } from "../hooks/useLLM";
import { BrandMark } from "./BrandMark";
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]);
const progressPercent = status.state === "ready" ? 100 : status.state === "loading" ? (status.progress ?? 0) : 0;
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" : ""
}`}
>
<main className="w-full min-h-screen p-7 flex flex-col justify-center items-center gap-7 relative z-[1]">
<BrandMark />
<section className="w-[min(520px,100%)] p-[30px] border border-line rounded-[32px] bg-panel shadow-ambient backdrop-blur-[24px]">
<span className="inline-block text-accent font-mono text-[0.73rem] tracking-[0.22em] uppercase">
Loading Model
</span>
<div
aria-hidden="true"
className="w-full h-3 mt-6 overflow-hidden rounded-full bg-[rgba(255,255,255,0.08)]"
>
<div className="progress-fill" style={{ width: `${progressPercent}%` }} />
</div>
<p className="mt-4 text-text-soft font-mono text-[0.9rem]">
{status.state === "loading" && status.message
? status.message
: progressPercent === 100
? "Ready!"
: "..."}
</p>
{status.state === "error" && (
<div
className="mt-[18px] py-3.5 px-4 border border-[rgba(255,160,160,0.3)] rounded-[18px] bg-[rgba(255,120,120,0.08)] text-[#ffd9d9] leading-[1.5]"
role="alert"
>
{status.error}
</div>
)}
</section>
</main>
</div>
<div
className={`absolute inset-0 z-30 bg-bg transition-opacity duration-1000 ease-in-out ${
showChat ? "" : "opacity-0 pointer-events-none"
}`}
>
<ChatApp />
</div>
</>
);
}