"use client" import { useState, useEffect, useMemo } from "react" import { useRouter } from "next/navigation" import { Input } from "@/components/ui/input" import { Button } from "@/components/ui/button" import { Card, CardContent } from "@/components/ui/card" import { Separator } from "@/components/ui/separator" import { Search, Zap, BookOpen, BrainCircuit, Cpu, Globe2, Eye, Cloud, ArrowRight, Lock, Loader2, XCircle, Filter, Trophy, Users, Clock, BarChart3, } from "lucide-react" import { cn } from "@/lib/utils" import { formatDistanceToNow } from "date-fns" const API_URL = process.env.NEXT_PUBLIC_API_URL ?? "http://localhost:8000" // ────────────────────────────────────────────────────────────────────────────── // Types // ────────────────────────────────────────────────────────────────────────────── type View = "catalog" | "leaderboard" type Category = "all" | "encoder" | "seq2seq" | "decoder" | "embedding" | "vision" | "api" type Provider = "all" | "huggingface" | "openai" | "cohere" | "google" type QualityTier = "excellent" | "good" | "balanced" | "fast" export interface CatalogModel { model_id: string display_name: string category: string provider: string param_count: string param_count_m: number task_types: string[] quality_tier: QualityTier inference_speed: string lora_compatible: boolean qlora_compatible: boolean languages: string[] description: string best_for: string tags: string[] requires_token: boolean license: string } interface LeaderboardEntry { rank: number model_id: string display_name: string category: string provider: string param_count: string quality_tier: string lora_compatible: boolean run_count: number best_f1: number | null avg_f1: number | null avg_accuracy: number | null task_types: string[] last_run_at: string | null } // ────────────────────────────────────────────────────────────────────────────── // Constants // ────────────────────────────────────────────────────────────────────────────── const CATEGORY_TABS: { key: Category; label: string; icon: React.ElementType }[] = [ { key: "all", label: "All Models", icon: BookOpen }, { key: "encoder", label: "Encoder", icon: BrainCircuit }, { key: "seq2seq", label: "Seq2Seq", icon: Filter }, { key: "decoder", label: "Decoder/LLM", icon: Cpu }, { key: "embedding", label: "Embedding", icon: Zap }, { key: "vision", label: "Vision", icon: Eye }, { key: "api", label: "Cloud API", icon: Cloud }, ] const QUALITY_STYLES: Record = { excellent: "bg-violet-500/10 text-violet-500 border-violet-500/25", good: "bg-blue-500/10 text-blue-500 border-blue-500/25", balanced: "bg-teal-500/10 text-teal-500 border-teal-500/25", fast: "bg-amber-500/10 text-amber-500 border-amber-500/25", } const PROVIDER_STYLES: Record = { huggingface: "bg-orange-500/10 text-orange-500 border-orange-500/25", openai: "bg-green-500/10 text-green-500 border-green-500/25", cohere: "bg-purple-500/10 text-purple-500 border-purple-500/25", google: "bg-blue-500/10 text-blue-500 border-blue-500/25", } const PROVIDER_LABELS: Record = { huggingface: "HuggingFace", openai: "OpenAI", cohere: "Cohere", google: "Google", } // ────────────────────────────────────────────────────────────────────────────── // Main component // ────────────────────────────────────────────────────────────────────────────── export function ModelsClient() { const router = useRouter() // View toggle const [view, setView] = useState("catalog") // Catalog state const [catalog, setCatalog] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) // Catalog filters const [q, setQ] = useState("") const [category, setCategory] = useState("all") const [provider, setProvider] = useState("all") const [loraOnly, setLoraOnly] = useState(false) // Leaderboard state const [leaderboard, setLeaderboard] = useState([]) const [lbLoading, setLbLoading] = useState(false) const [lbError, setLbError] = useState(null) // Fetch catalog once on mount useEffect(() => { fetch(`${API_URL}/models`) .then(r => { if (!r.ok) throw new Error(`API returned ${r.status}`) return r.json() as Promise }) .then(data => { setCatalog(data); setLoading(false) }) .catch(e => { setError(String(e)); setLoading(false) }) }, []) // Fetch leaderboard when tab is active; poll every 30s useEffect(() => { if (view !== "leaderboard") return let alive = true const load = async () => { if (!alive) return setLbLoading(prev => leaderboard.length === 0 ? true : prev) try { const r = await fetch(`${API_URL}/leaderboard`) if (!r.ok) throw new Error(`API returned ${r.status}`) const data = await r.json() as LeaderboardEntry[] if (alive) { setLeaderboard(data); setLbError(null) } } catch (e) { if (alive) setLbError(String(e)) } finally { if (alive) setLbLoading(false) } } load() const iv = setInterval(load, 30_000) return () => { alive = false; clearInterval(iv) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [view]) const filtered = useMemo(() => { let results = catalog if (category !== "all") results = results.filter(m => m.category === category) if (provider !== "all") results = results.filter(m => m.provider === provider) if (loraOnly) results = results.filter(m => m.lora_compatible) if (q.trim()) { const ql = q.toLowerCase() results = results.filter(m => m.display_name.toLowerCase().includes(ql) || m.description.toLowerCase().includes(ql) || m.best_for.toLowerCase().includes(ql) || m.model_id.toLowerCase().includes(ql) || m.tags.some(t => t.includes(ql)) ) } return results }, [catalog, category, provider, loraOnly, q]) // Build a CatalogModel from leaderboard entry + catalog lookup for trainWithModel function trainWithLeaderboardEntry(entry: LeaderboardEntry) { const catalogMatch = catalog.find(m => m.model_id === entry.model_id) const synthetic: CatalogModel = catalogMatch ?? { model_id: entry.model_id, display_name: entry.display_name, category: entry.category, provider: entry.provider, param_count: entry.param_count, param_count_m: 0, task_types: entry.task_types, quality_tier: (entry.quality_tier as QualityTier) || "balanced", inference_speed: "medium", lora_compatible: entry.lora_compatible, qlora_compatible: false, languages: ["en"], description: "", best_for: "", tags: [], requires_token: false, license: "", } localStorage.setItem("modelforge_preselect_model", JSON.stringify(synthetic)) router.push("/train") } function trainWithModel(model: CatalogModel) { localStorage.setItem("modelforge_preselect_model", JSON.stringify(model)) router.push("/train") } // ── Global catalog loading/error states ──────────────────────────────────── if (loading) { return (
) } if (error) { return (

Could not load catalog. Make sure the backend is running.
{error}

) } return (
{/* Header + view toggle */}

{view === "catalog" ? "Model Catalog" : "Community Leaderboard"}

{view === "catalog" ? `${catalog.length} models — encoders, LLMs, embedding, vision, and cloud APIs.` : "Base models ranked by best F1 across all platform training runs · refreshes every 30s"}

{/* View toggle pills */}
{/* ── CATALOG VIEW ─────────────────────────────────────────────────────── */} {view === "catalog" && ( <> {/* Search */}
setQ(e.target.value)} className="pl-9" />
{/* Category tabs */}
{CATEGORY_TABS.map(tab => { const Icon = tab.icon const active = category === tab.key return ( ) })}
{/* Sub-filters */}
{(["all", "huggingface", "openai", "cohere", "google"] as Provider[]).map(p => ( ))} {filtered.length} model{filtered.length !== 1 ? "s" : ""}
{/* Model grid */} {filtered.length === 0 ? (

No models match your filters.

) : (
{filtered.map(model => ( ))}
)} )} {/* ── LEADERBOARD VIEW ─────────────────────────────────────────────────── */} {view === "leaderboard" && ( )}
) } // ────────────────────────────────────────────────────────────────────────────── // Leaderboard view // ────────────────────────────────────────────────────────────────────────────── function LeaderboardView({ entries, loading, error, onTrain, }: { entries: LeaderboardEntry[] loading: boolean error: string | null onTrain: (e: LeaderboardEntry) => void }) { if (loading && entries.length === 0) { return (
{Array.from({ length: 5 }).map((_, i) => (
))}
) } if (error) { return (

Could not load leaderboard. Make sure the backend is running.
{error}

) } if (entries.length === 0) { return (

No completed runs yet

Train your first model to claim the #1 spot.

) } return (
{/* Column header */}
Rank Model F1 / Accuracy
{entries.map(entry => ( ))} {/* Staleness hint */}

Auto-refreshes every 30 seconds

) } // ────────────────────────────────────────────────────────────────────────────── // Leaderboard row // ────────────────────────────────────────────────────────────────────────────── const RANK_STYLES: Record = { 1: { bg: "bg-amber-500/15 border-amber-500/40", text: "text-amber-400 font-bold", label: "#1" }, 2: { bg: "bg-slate-400/10 border-slate-400/30", text: "text-slate-300 font-bold", label: "#2" }, 3: { bg: "bg-orange-700/15 border-orange-700/40", text: "text-orange-500 font-bold", label: "#3" }, } function fmtF1(v: number | null): string { if (v === null) return "—" return (v * 100).toFixed(1) + "%" } function LeaderboardRow({ entry, onTrain, }: { entry: LeaderboardEntry onTrain: (e: LeaderboardEntry) => void }) { const rank = RANK_STYLES[entry.rank] const qualStyle = QUALITY_STYLES[entry.quality_tier] ?? "bg-secondary text-muted-foreground border-border" const provStyle = PROVIDER_STYLES[entry.provider] ?? "bg-secondary text-muted-foreground border-border" return (
{/* Rank badge */}
{rank ? rank.label : `#${entry.rank}`}
{/* Model info */}

{entry.display_name}

{entry.model_id}

{entry.quality_tier && ( {entry.quality_tier} )} {entry.provider && ( {PROVIDER_LABELS[entry.provider] ?? entry.provider} )} {entry.param_count && ( {entry.param_count} )} {entry.lora_compatible && ( LoRA )}
{/* Task type chips */}
{entry.task_types.slice(0, 3).map(t => ( {t.replace(/_/g, " ")} ))} {entry.task_types.length > 3 && ( +{entry.task_types.length - 3} )}
{/* Run count */} {entry.run_count} run{entry.run_count !== 1 ? "s" : ""} {/* Last trained */} {entry.last_run_at && ( {formatDistanceToNow(new Date(entry.last_run_at), { addSuffix: true })} )}
{/* Metrics column (md+) */}
{fmtF1(entry.best_f1)}
avg F1 {fmtF1(entry.avg_f1)} · acc {entry.avg_accuracy !== null ? (entry.avg_accuracy * 100).toFixed(1) + "%" : "—"}
{/* CTA */}
{/* Metrics row on mobile */}
Best F1 {fmtF1(entry.best_f1)}
avg {fmtF1(entry.avg_f1)} · acc {entry.avg_accuracy !== null ? (entry.avg_accuracy * 100).toFixed(1) + "%" : "—"}
) } // ────────────────────────────────────────────────────────────────────────────── // Model card (catalog view) // ────────────────────────────────────────────────────────────────────────────── function ModelCard({ model, onTrain }: { model: CatalogModel; onTrain: (m: CatalogModel) => void }) { const qualityStyle = QUALITY_STYLES[model.quality_tier] ?? "bg-secondary text-muted-foreground border-border" const providerStyle = PROVIDER_STYLES[model.provider] ?? "bg-secondary text-muted-foreground border-border" return ( {/* Name + provider */}

{model.display_name}

{model.model_id}

{PROVIDER_LABELS[model.provider] ?? model.provider}
{/* Badge row */}
{model.quality_tier} {model.param_count} {model.category} {model.lora_compatible && ( LoRA )} {model.requires_token && ( Gated )} {model.languages.includes("multilingual") && ( Multilingual )}
{/* Description */}

{model.description}

{/* Best for */}
Best for: {model.best_for}
{/* Task types */}
{model.task_types.slice(0, 3).map(t => ( {t.replace(/_/g, " ")} ))} {model.task_types.length > 3 && ( +{model.task_types.length - 3} )}
{/* CTA */}
) }