import { useState } from "react" import { Download, Search, Star, Layers, Check, Zap, Eye, Code, Brain, Compass, ChevronDown, ChevronUp } from "lucide-react" import { api } from "@/lib/api" import { useModels, useUpdates, useDiscover } from "@/lib/queries" import { cn } from "@/lib/utils" import { fmtBytes } from "@/lib/format" import { FitBadge } from "@/components/models/ModelBadges" import { AddModel } from "./AddModel" // Die 5 kanonischen Rollen (identisch zu sources.py / ModelBadges.ROLES). const ROLE_METADATA: Record = { fast: { title: "Schnelles Alltags-Hirn", desc: "Schnelle MoE-Antworten für Chat, Zusammenfassungen und alltägliche Aufgaben.", icon: Zap }, heavy: { title: "Schweres Reasoning", desc: "Große Modelle für komplexe Logik, Mathematik und tiefgründiges Planen.", icon: Brain }, coder: { title: "Coden & Entwicklung", desc: "Autovervollständigung, Refactoring und Codegenerierung direkt in deiner IDE.", icon: Code }, vision: { title: "Bilder & Vision", desc: "Verarbeitet Bilder, Screenshots und visuelle Diagramme in multimodalen Chats.", icon: Eye }, scout: { title: "Multimodal-Allrounder", desc: "Multimodaler Allrounder für schnelle Antworten, Übersetzungen und Alltag.", icon: Compass } } export function Discover() { const { data, isLoading: loading, error: loadErr } = useDiscover() const { data: modelsResp } = useModels() const { data: updates } = useUpdates() const models = modelsResp?.models ?? [] const error = loadErr ? String(loadErr) : "" const [installing, setInstalling] = useState>({}) const [expandedAlternatives, setExpandedAlternatives] = useState>({}) const [showExpert, setShowExpert] = useState(false) async function install(repo: string, role: string, quant: string, toolCapable: boolean) { setInstalling((s) => ({ ...s, [repo]: "Starte..." })) try { await api("/api/models/install", { method: "POST", body: JSON.stringify({ repo, role, quant, jinja: toolCapable }), }) setInstalling((s) => ({ ...s, [repo]: "Download läuft" })) } catch (e) { setInstalling((s) => ({ ...s, [repo]: "Fehler" })) } } if (loading) return
Analysiere Hardware und suche passende GGUF-Empfehlungen…
if (error || !data) return (
Empfehlungsdienst temporär nicht erreichbar ({error}).
) return (
{/* Informational Header */}
Modell-Registry geladen für {data.sys_ram_gb} GB System-RAM.
Empfehlungen sind automatisch auf deine Box-Hardware optimiert.
{/* Sockets/Slots Grid */}
{data.categories.map((cat) => { const meta = ROLE_METADATA[cat.role] || { title: cat.title || cat.role, desc: "Spezifisches Modell für diese Systemrolle.", icon: Layers } const IconComponent = meta.icon // Check if a model is installed for this role const installedModel = models.find((m) => m.role === cat.role) // Check if an upgrade is available for this role const hasUpgrade = updates?.model_list.find((u) => u.role === cat.role) // Get the primary recommended model const recommendedModel = cat.models.find((m) => m.repo === cat.recommended) || cat.models[0] if (!recommendedModel) return null const isInstallingRecommended = installing[recommendedModel.repo] const alternativeModels = cat.models.filter((m) => m.repo !== cat.recommended) const isExpanded = !!expandedAlternatives[cat.role] return (
{/* Socket Header */}

{meta.title}

Rolle: {cat.role}
{/* Status Badges */} {installedModel ? ( Aktiviert ) : ( Frei )}
{/* Role Description */}

{meta.desc}

{/* Current vs Recommended model card */}
{installedModel ? (
Aktive GGUF-Belegung
{installedModel.name.split("/").pop()}
Größe: {fmtBytes(installedModel.size_bytes || 0)} Quant: {installedModel.quant || "GGUF"}
) : (
Empfohlenes Modell
{recommendedModel.name}
Ersteller: {recommendedModel.author} Quant: {recommendedModel.quant}
)}
{/* Main Action Button */}
{installedModel ? ( hasUpgrade ? (
Bessere Version in der Registry: {hasUpgrade.repo.split("/").pop()}
) : (
Auf neuestem Stand
) ) : ( )}
{/* Collapsible alternatives list */} {alternativeModels.length > 0 && (
{isExpanded && (
{alternativeModels.map((alt) => (
{alt.name}
Quant: {alt.quant} {alt.fit.text}
))}
)}
)}
) })}
{/* Collapsible Custom Hugging Face Downloader */}
{showExpert && (
)}
) }