import { motion, useMotionValue, useScroll, useSpring } from "framer-motion"; import type { Variants } from "framer-motion"; import { lazy, Suspense, useEffect, useState, type CSSProperties } from "react"; import { extractText, getTiers } from "./api/client"; import { GraphErrorBoundary } from "./components/GraphErrorBoundary"; import { TextInput } from "./components/TextInput"; import { TierSelector } from "./components/TierSelector"; import type { ExtractResponse, TierInfo, TierName } from "./types"; const initialText = "Havana Radio Reloj Network broadcast the interview from Cuba."; const GraphView = lazy(() => import("./components/GraphView")); const reveal: Variants = { hidden: { opacity: 0, y: 28 }, visible: (index: number) => ({ opacity: 1, y: 0, transition: { delay: 0.12 + index * 0.08, duration: 0.72, ease: [0.22, 1, 0.36, 1] as [number, number, number, number], }, }), }; export default function App() { const [text, setText] = useState(initialText); const [tiers, setTiers] = useState([]); const [selectedTier, setSelectedTier] = useState("balanced"); const [result, setResult] = useState(null); const [isLoading, setIsLoading] = useState(false); const [isTierLoading, setIsTierLoading] = useState(true); const [error, setError] = useState(null); const [signal, setSignal] = useState({ x: 50, y: 45 }); const cursorX = useMotionValue(-80); const cursorY = useMotionValue(-80); const cursorSpringX = useSpring(cursorX, { stiffness: 420, damping: 34, mass: 0.35, }); const cursorSpringY = useSpring(cursorY, { stiffness: 420, damping: 34, mass: 0.35, }); const { scrollYProgress } = useScroll(); const progress = useSpring(scrollYProgress, { stiffness: 120, damping: 26, mass: 0.2, }); useEffect(() => { const updateCursor = (event: PointerEvent) => { cursorX.set(event.clientX - 12); cursorY.set(event.clientY - 12); }; window.addEventListener("pointermove", updateCursor); return () => window.removeEventListener("pointermove", updateCursor); }, [cursorX, cursorY]); useEffect(() => { void getTiers() .then((availableTiers) => { setTiers(availableTiers); if (!availableTiers.some((tier) => tier.name === "balanced")) { setSelectedTier(availableTiers[0]?.name ?? "balanced"); } }) .catch((reason: unknown) => { setError( reason instanceof Error ? reason.message : "Unable to load model tiers.", ); }) .finally(() => { setIsTierLoading(false); }); }, []); const runExtraction = async () => { setError(null); setIsLoading(true); try { setResult(await extractText(text, selectedTier)); } catch (reason: unknown) { setError( reason instanceof Error ? reason.message : "Extraction could not be completed.", ); } finally { setIsLoading(false); } }; return (
); } function MagneticLink() { const x = useMotionValue(0); const y = useMotionValue(0); const springX = useSpring(x, { stiffness: 280, damping: 16 }); const springY = useSpring(y, { stiffness: 280, damping: 16 }); return ( { const bounds = event.currentTarget.getBoundingClientRect(); x.set((event.clientX - (bounds.left + bounds.width / 2)) * 0.18); y.set((event.clientY - (bounds.top + bounds.height / 2)) * 0.18); }} onPointerLeave={() => { x.set(0); y.set(0); }} > Open extraction studio ); } function GraphPlaceholder({ message }: { message: string }) { return (

{message}

); }