Feat: warm-bewusster Agent-Hirn-Wechsel (beliebiges installiertes Modell)

- agent.set_agent_brain(model_id): vergibt 'hermes'-Alias, tauscht das Modell in die
  residente brains-Gruppe (altes Hirn raus, fast/vision bleiben) und zeigt die Hermes-Config
  darauf (+ Gateway-Restart). Behebt: AgentView-Switch hielt das neue Hirn nicht warm.
- POST /api/agent/brain/set.
- Cockpit Agent-Hirn-Karte: Button "Hirn wechseln" + Modal mit allen installierten Modellen
  (Groesse/Params/Rolle/Tools), aktuelles markiert; Hinweis zugunsten Hermes (Tool-Calling).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-06-27 12:25:31 +02:00
parent 45e74a97bd
commit 6758bbfbd9
8 changed files with 518 additions and 402 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -7,8 +7,8 @@
<link rel="manifest" href="/manifest.webmanifest" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<title>Mission Control 2.0</title>
<script type="module" crossorigin src="/assets/index-lvQamTQ3.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-D4NZ6il2.css">
<script type="module" crossorigin src="/assets/index-Bb8BpQr4.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-bw6S4bF_.css">
</head>
<body>
<div id="root"></div>
+78 -1
View File
@@ -29,6 +29,7 @@ export function Cockpit() {
const [activeClient, setActiveClient] = useState<"roocode" | "cursor" | "opencode" | "zed" | "continue" | null>(null)
const [activeRoleForAssign, setActiveRoleForAssign] = useState<string | null>(null)
const [specModel, setSpecModel] = useState<ModelInfo | null>(null)
const [showBrainSwitch, setShowBrainSwitch] = useState(false)
const [copied, setCopied] = useState(false)
const [hoveredNode, setHoveredNode] = useState<string | null>(null)
const [viewMode, setViewMode] = useState<"grid" | "list">("grid")
@@ -199,6 +200,23 @@ export function Cockpit() {
)
}
async function handleSetBrain(modelName: string) {
showConfirm(
"Agent-Hirn wechseln?",
`'${modelName.split("/").pop()}' als Agent-Hirn (Alias hermes) setzen? Es wird warm gehalten (brains-Gruppe); Hermes nutzt es nach einem kurzen Gateway-Restart.`,
async () => {
try {
await api("/api/agent/brain/set", { method: "POST", body: JSON.stringify({ model_id: modelName }) })
showAlert("Erledigt", "Agent-Hirn gewechselt. Gateway wurde neugestartet.")
setShowBrainSwitch(false)
reload()
} catch (e: any) {
showAlert("Fehler", `Wechsel fehlgeschlagen: ${e.message || e}`)
}
}
)
}
async function copySnippet(snippet?: string) {
if (!snippet) return
await navigator.clipboard.writeText(snippet)
@@ -663,7 +681,12 @@ export function Cockpit() {
<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>
<button
onClick={() => setShowBrainSwitch(true)}
className="h-7 px-3 rounded-lg border border-indigo-500/30 bg-indigo-500/5 text-indigo-300 text-[9px] font-bold uppercase hover:bg-indigo-500/15 transition-all cursor-pointer"
>
Hirn wechseln
</button>
</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">
@@ -1145,6 +1168,60 @@ export function Cockpit() {
/>
)}
{/* Agent-Hirn wechseln */}
{showBrainSwitch && (
<div className="fixed inset-0 z-50 bg-black/60 backdrop-blur-sm flex items-center justify-center p-4">
<div className="w-full max-w-md rounded-2xl border border-border/80 bg-card p-6 shadow-2xl space-y-4 animate-in fade-in zoom-in-95 duration-200">
<div className="flex items-center justify-between border-b border-border/20 pb-2">
<h3 className="text-xs font-bold uppercase tracking-wider text-primary flex items-center gap-1.5">
<Bot className="h-4 w-4" /> Agent-Hirn wechseln
</h3>
<button onClick={() => setShowBrainSwitch(false)} className="p-1 rounded hover:bg-accent text-muted-foreground hover:text-foreground cursor-pointer">
<X className="h-4 w-4" />
</button>
</div>
<p className="text-[11px] text-muted-foreground leading-relaxed">
Wähle ein <strong className="text-foreground">installiertes</strong> Modell als Agent-Hirn. Es bekommt den
<code className="text-primary"> hermes</code>-Alias, wird warm gehalten (brains-Gruppe), und Hermes nutzt es nach
einem kurzen Gateway-Restart. Neues Modell (z.B. Hermes-4.3 oder Gemma-4)? Erst über Modelle finden" laden.
</p>
<div className="space-y-1.5 max-h-72 overflow-y-auto pr-1">
{models.map((m) => {
const isBrain = m.role === "hermes"
return (
<button
key={m.name}
onClick={() => !isBrain && handleSetBrain(m.name)}
disabled={isBrain || m.incomplete}
className={cn(
"w-full text-left px-3 py-2.5 rounded-lg text-xs flex items-center justify-between font-mono border transition-all",
isBrain ? "border-indigo-500/40 bg-indigo-500/10 text-indigo-300"
: m.incomplete ? "border-border/20 bg-background/10 text-muted-foreground/50 cursor-not-allowed"
: "border-border/30 bg-background/20 text-foreground hover:bg-accent cursor-pointer"
)}
>
<div className="flex flex-col min-w-0">
<span className="font-semibold truncate max-w-[280px]">{m.name.split("/").pop()?.replace(/\.gguf$/i, "")}</span>
<span className="text-[9px] text-muted-foreground mt-0.5">
{m.capabilities.params_b ? `${m.capabilities.params_b}B` : "?"} · {fmtSize(m.size_bytes)}
{m.role && ` · Rolle: ${m.role}`}{m.capabilities.tools !== "no" ? " · Tools " : " · ohne Tools"}
</span>
</div>
{isBrain
? <span className="text-[9px] font-bold uppercase text-indigo-300 shrink-0 flex items-center gap-1"><Check className="h-3.5 w-3.5" /> Aktiv</span>
: <span className="text-[9px] font-bold uppercase text-primary/70 shrink-0">Als Hirn setzen →</span>}
</button>
)
})}
</div>
<div className="text-[10px] text-amber-400/80 flex items-start gap-1.5 border-t border-border/20 pt-2.5">
<span>💡</span>
<span>Für einen <strong>Agenten</strong> sind Hermes-Modelle (natives Tool-Calling) meist die robustere Wahl als allgemeine Modelle.</span>
</div>
</div>
</div>
)}
{dialogElement}
</div>
)