import { useState } from "react" import { Zap, Plus, X, Check, HardDrive, AlertTriangle } from "lucide-react" import { setGroup, type ModelInfo } from "@/lib/api" import { useModels, useGroups, useSystemStatus, useHermesBrain, useQueryClient, qk } from "@/lib/queries" import { useDialog } from "@/lib/useDialog" import { RoleLabel, roleMeta } from "@/components/models/ModelBadges" import { fmtSize } from "@/lib/format" import { cn } from "@/lib/utils" // „Immer-bereit-Set" = die ko-residente llama-swap-Gruppe `brains`. Ihre Mitglieder // bleiben gemeinsam warm (verdrängen sich nicht). Hier in Klartext verwaltbar, // mit Speicher-Budget und Geländer (Hirn/Gedächtnis nicht entfernbar). export function WarmSetManager() { const qc = useQueryClient() const { data: modelsResp } = useModels(4_000) const { data: groupsResp } = useGroups() const { data: sysStatus } = useSystemStatus() const { data: brain } = useHermesBrain() const { showAlert, dialogElement } = useDialog() const [picking, setPicking] = useState(false) const [busy, setBusy] = useState(false) const models = modelsResp?.models ?? [] const running = modelsResp?.running ?? [] const group = groupsResp?.groups?.brains const members = group?.members ?? [] const byName = (name: string) => models.find((m) => m.name === name) const shortName = (name: string) => name.split("/").pop()?.replace(/\.gguf$/i, "") || name const memberModels = members.map(byName).filter(Boolean) as ModelInfo[] const B = 1024 ** 3 const bud = brain?.budget const weightsGb = memberModels.reduce((a, m) => a + (m.size_bytes || 0), 0) / B const gttTotal = sysStatus?.gpu?.gtt_total || sysStatus?.gpu?.vram_total || 0 const totalGb = bud?.gtt_gb || (gttTotal ? gttTotal / B : 0) // Ehrlicher Fußabdruck inkl. Arbeitsspeicher (KV-Cache) aus dem Budget; sonst nur Gewichte. const reservedGb = bud?.warm_projected_gb ?? weightsGb const pct = totalGb > 0 ? Math.min(100, (reservedGb / totalGb) * 100) : 0 const eligible = models.filter((m) => !members.includes(m.name) && !m.incomplete) async function save(next: string[]) { setBusy(true) try { await setGroup("brains", next, group?.swap ?? false, group?.persist ?? true) qc.invalidateQueries({ queryKey: qk.groups }) qc.invalidateQueries({ queryKey: qk.models }) qc.invalidateQueries({ queryKey: qk.hermesBrain }) } catch (e: any) { showAlert("Fehler", `Immer-bereit-Set konnte nicht geändert werden: ${e?.message || e}`) } finally { setBusy(false) } } function remove(name: string) { const m = byName(name) if (m && roleMeta(m.role).protected) { showAlert("Geschützt", `„${roleMeta(m.role).label}" ist lebenswichtig und bleibt immer bereit.`) return } void save(members.filter((x) => x !== name)) } function add(name: string) { setPicking(false) void save([...members, name]) } if (!group) { return (
Es gibt noch kein Immer-bereit-Set. Es entsteht automatisch, sobald Lucys Hirn zugewiesen ist.
{dialogElement}
Wähle ein Modell, das ohne Ladezeit bereitstehen soll:
Diese Modelle hält Lucy immer bereit — sie antworten ohne Ladezeit.