Feat: Discover zukunftssicher (Recency-Score) + Agent-Hirn (Hermes) im Modell-Manager
- discover.rank_runnable: Score aus Fit + Recency (lastModified, Halbwertszeit ~9 Mon) + Capability (params, log) + Popularity (downloads, log) -> neuere Generationen bevorzugt. - Agent-Hirn: neuer GET /api/agent/brain (aktuelles hermes-Modell + bestes NousResearch- Hermes-Update, versions-aware via Hermes-X.Y-Parsing). Cockpit zeigt "Agent-Hirn (Hermes)"-Karte mit aktuellem Brain + Aktualisieren-Button, wenn NousResearch eine neuere Generation hat (z.B. Hermes-4-14B -> Hermes-4.3-36B). Update installiert mit Rolle hermes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { useState, useRef, useCallback } from "react"
|
||||
import { Download, Trash2, Edit3, Activity, HardDrive, X, Check, Copy, Zap } from "lucide-react"
|
||||
import { Download, Trash2, Edit3, Activity, HardDrive, X, Check, Copy, Zap, Bot } from "lucide-react"
|
||||
import { api, type ModelInfo } from "@/lib/api"
|
||||
import { useModels, useRouting, useConnect, useUpdates, useQueryClient, qk } from "@/lib/queries"
|
||||
import { useModels, useRouting, useConnect, useUpdates, useHermesBrain, useQueryClient, qk } from "@/lib/queries"
|
||||
import { useDialog } from "@/lib/useDialog"
|
||||
import { CapsChips } from "@/components/CapsChips"
|
||||
import { cn } from "@/lib/utils"
|
||||
@@ -15,6 +15,7 @@ export function Cockpit() {
|
||||
const { data: routing } = useRouting(4_000)
|
||||
const { data: connectData } = useConnect()
|
||||
const { data: updates } = useUpdates(4_000)
|
||||
const { data: brain } = useHermesBrain()
|
||||
const { showAlert, showConfirm, showPrompt, dialogElement } = useDialog()
|
||||
const models = modelsResp?.models ?? []
|
||||
const running = modelsResp?.running ?? []
|
||||
@@ -175,6 +176,25 @@ export function Cockpit() {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleBrainUpdate(repo: string) {
|
||||
showConfirm(
|
||||
"Agent-Hirn aktualisieren?",
|
||||
`Neues Hermes-Modell '${repo.split("/").pop()}' herunterladen und als Agent-Hirn (Rolle hermes) setzen? Hermes nutzt danach automatisch das neue Modell.`,
|
||||
async () => {
|
||||
try {
|
||||
await api("/api/models/install", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ repo, role: "hermes", quant: "Q4_K_M", jinja: true }),
|
||||
})
|
||||
showAlert("Download gestartet", "Das neue Agent-Hirn wird geladen. Fortschritt oben.")
|
||||
reload()
|
||||
} catch (e: any) {
|
||||
showAlert("Fehler", `Update fehlgeschlagen: ${e.message || e}`)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
async function copySnippet(snippet?: string) {
|
||||
if (!snippet) return
|
||||
await navigator.clipboard.writeText(snippet)
|
||||
@@ -628,6 +648,55 @@ export function Cockpit() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ZONE B.6: Agent-Hirn (Hermes) — sichtbar + updatebar (NousResearch) */}
|
||||
{brain?.current && (
|
||||
<div className="rounded-2xl border border-indigo-500/30 bg-indigo-500/5 backdrop-blur-md p-5 shadow-lg shadow-black/10 space-y-3.5">
|
||||
<div className="flex items-center justify-between flex-wrap gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Bot className="h-4.5 w-4.5 text-indigo-400" />
|
||||
<span className="text-xs font-bold uppercase tracking-wider text-foreground">Agent-Hirn (Hermes)</span>
|
||||
{brain.current.version != null && (
|
||||
<span className="text-[9px] font-mono px-1.5 py-0.5 rounded bg-indigo-500/15 text-indigo-300 border border-indigo-500/25">v{brain.current.version}</span>
|
||||
)}
|
||||
</div>
|
||||
<span className="text-[10px] font-mono text-muted-foreground hidden sm:block">Modell, das der Hermes-Agent als Gehirn nutzt</span>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3 rounded-xl bg-background/30 border border-border/30 p-3.5">
|
||||
<div className="min-w-0">
|
||||
<div className="text-xs font-mono font-bold text-foreground truncate" title={brain.current.name}>
|
||||
{brain.current.name.split("/").pop()}
|
||||
</div>
|
||||
<div className="text-[10px] text-muted-foreground/80 flex items-center gap-2 mt-0.5">
|
||||
<span>{brain.current.params_b ? `${brain.current.params_b}B` : "—"}</span>
|
||||
<span>•</span><span>{brain.current.quant || "GGUF"}</span>
|
||||
<span>•</span><span>{fmtSize(brain.current.size_bytes || 0)}</span>
|
||||
</div>
|
||||
</div>
|
||||
{brain.update_available && brain.recommended ? (
|
||||
<button
|
||||
onClick={() => handleBrainUpdate(brain.recommended!.repo)}
|
||||
className="h-8 px-3 shrink-0 flex items-center justify-center gap-1.5 rounded-lg bg-amber-500 text-black text-[10px] font-bold uppercase hover:bg-amber-400 transition-all cursor-pointer shadow-md shadow-amber-500/10"
|
||||
>
|
||||
<Download className="h-3.5 w-3.5" /> Aktualisieren
|
||||
</button>
|
||||
) : (
|
||||
<span className="h-8 px-3 shrink-0 flex items-center gap-1.5 text-[10px] font-bold text-emerald-400 uppercase tracking-wide bg-emerald-500/5 border border-emerald-500/15 rounded-lg select-none">
|
||||
<Check className="h-4 w-4" /> Neueste Generation
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{brain.update_available && brain.recommended && (
|
||||
<div className="text-[10px] text-amber-400/90 flex items-center gap-1.5">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-amber-400 animate-ping shrink-0" />
|
||||
Neuere Generation verfügbar: <span className="font-mono font-bold">{brain.recommended.name.replace(/-GGUF$/i, "")}</span>
|
||||
(v{brain.recommended.version}, {brain.recommended.params_b}B) — von NousResearch.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ZONE C: Library list cards & Upgrade Radar */}
|
||||
<div className="space-y-4">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3 px-1">
|
||||
|
||||
Reference in New Issue
Block a user