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:
+397
File diff suppressed because one or more lines are too long
-1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
-397
File diff suppressed because one or more lines are too long
Vendored
+2
-2
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user