Feat: "Modelle finden" mit gleichwertigem Stöbern-&-Suchen-Modus
Profi-Suche raus aus dem versteckten Akkordeon → eigener Modus-Umschalter
(Empfohlen | Stöbern & Suchen) im "Modelle finden"-Tab.
- Stöbern & Suchen: immer sichtbare Suchleiste, Filter-Chips (Beliebt/Coder/
Vision/Reasoning/Klein), reiche Treffer (Autor · Downloads · Likes · installiert-
Badge), aufklappbar → Quant + Rolle + Fit-Ampel + Download (OOM-Gate), plus
Direkt-Eingabe für exaktes Repo/URL.
- Trending ohne Query: hf.search('') liefert jetzt Top-GGUF nach Downloads;
Route /api/hf/search?q wird optional.
- Empfohlen = unveränderte Rollen-Sockets (Default). AddModel.tsx in ModelBrowse
aufgegangen → gelöscht.
Verifiziert: npm run build (tsc strict) clean; Backend-Smoke (empty-q → Top-GGUF);
live gegen die Box (Toggle, Chips, Suche, installiert-Erkennung, Fit-Ampel).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
import { useState } from "react"
|
||||
import { Download, Search, Star, Layers, Check, Zap, Eye, Code, Brain, Compass, ChevronDown, ChevronUp } from "lucide-react"
|
||||
import { Download, 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"
|
||||
import { ModelBrowse } from "./ModelBrowse"
|
||||
|
||||
// Die 5 kanonischen Rollen (identisch zu sources.py / ModelBadges.ROLES).
|
||||
const ROLE_METADATA: Record<string, { title: string; desc: string; icon: any }> = {
|
||||
@@ -44,7 +44,7 @@ export function Discover() {
|
||||
const error = loadErr ? String(loadErr) : ""
|
||||
const [installing, setInstalling] = useState<Record<string, string>>({})
|
||||
const [expandedAlternatives, setExpandedAlternatives] = useState<Record<string, boolean>>({})
|
||||
const [showExpert, setShowExpert] = useState(false)
|
||||
const [mode, setMode] = useState<"recommended" | "browse">("recommended")
|
||||
|
||||
async function install(repo: string, role: string, quant: string, toolCapable: boolean) {
|
||||
setInstalling((s) => ({ ...s, [repo]: "Starte..." }))
|
||||
@@ -59,16 +59,34 @@ export function Discover() {
|
||||
}
|
||||
}
|
||||
|
||||
if (loading) return <div className="text-xs text-muted-foreground py-12 text-center">Analysiere Hardware und suche passende GGUF-Empfehlungen…</div>
|
||||
if (error || !data)
|
||||
return (
|
||||
<div className="rounded-xl border border-red-500/20 bg-red-500/5 p-4 text-xs text-red-400">
|
||||
Empfehlungsdienst temporär nicht erreichbar ({error}).
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<div className="space-y-6">
|
||||
{/* Modus-Umschalter: geführt (Empfohlen) vs. Stöbern & Suchen */}
|
||||
<div className="flex rounded-xl border border-border/60 bg-card/45 backdrop-blur-md p-1 w-fit">
|
||||
{([["recommended", "Empfohlen"], ["browse", "Stöbern & Suchen"]] as const).map(([m, label]) => (
|
||||
<button
|
||||
key={m}
|
||||
onClick={() => setMode(m)}
|
||||
className={cn(
|
||||
"rounded-lg px-4 py-1.5 text-xs font-semibold tracking-wide transition-all cursor-pointer",
|
||||
mode === m ? "bg-primary text-primary-foreground shadow-md shadow-primary/10" : "text-muted-foreground hover:text-foreground",
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{mode === "browse" ? (
|
||||
<ModelBrowse />
|
||||
) : loading ? (
|
||||
<div className="text-xs text-muted-foreground py-12 text-center">Analysiere Hardware und suche passende GGUF-Empfehlungen…</div>
|
||||
) : (error || !data) ? (
|
||||
<div className="rounded-xl border border-red-500/20 bg-red-500/5 p-4 text-xs text-red-400">
|
||||
Empfehlungsdienst temporär nicht erreichbar ({error}).
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-8">
|
||||
{/* Informational Header */}
|
||||
<div className="text-[10px] font-mono text-muted-foreground bg-background/25 border border-border/40 p-4 rounded-xl flex flex-col sm:flex-row sm:items-center justify-between gap-3 shadow-sm">
|
||||
<div>
|
||||
@@ -261,26 +279,8 @@ export function Discover() {
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Collapsible Custom Hugging Face Downloader */}
|
||||
<div className="border border-border/50 bg-card/20 rounded-2xl overflow-hidden shadow-md mt-4">
|
||||
<button
|
||||
onClick={() => setShowExpert(!showExpert)}
|
||||
className="w-full px-5 py-4 flex items-center justify-between text-xs font-bold uppercase tracking-wider text-muted-foreground hover:bg-card/30 transition-colors cursor-pointer"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<Search className="h-4 w-4 text-primary" />
|
||||
<span>Eigenes Modell von Hugging Face laden (Erweiterte Ansicht)</span>
|
||||
</div>
|
||||
<span className="text-[10px] text-primary hover:underline">
|
||||
{showExpert ? "Ausblenden ▲" : "Anzeigen ▼"}
|
||||
</span>
|
||||
</button>
|
||||
{showExpert && (
|
||||
<div className="p-5 border-t border-border/20 bg-card/10">
|
||||
<AddModel />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user