Feat: Add Hermes brain change buttons and skills.sh guide source
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
import { useEffect, useState } from "react"
|
||||
import { Bot, Cpu, ExternalLink, Brain, Layers, Plus, ShieldAlert, X, Power, Shield, Download, RefreshCw } from "lucide-react"
|
||||
import { Bot, Cpu, ExternalLink, Brain, Layers, Plus, ShieldAlert, X, Power, Shield, Download, RefreshCw, Check } from "lucide-react"
|
||||
import { api, type SystemStatus, type AgentStatus, type ModelInfo, type Memory, type UpdatesResp, type Job } from "@/lib/api"
|
||||
import { cn, resolveExternalUrl } from "@/lib/utils"
|
||||
import { CustomDialog } from "@/components/CustomDialog"
|
||||
|
||||
|
||||
function gb(b: number) {
|
||||
return (b / 1024 ** 3).toFixed(1)
|
||||
@@ -55,6 +57,55 @@ export function DashboardView() {
|
||||
error?: string
|
||||
}>({ open: false, actionPath: "", actionLabel: "" })
|
||||
|
||||
// Custom Dialog State
|
||||
const [dialog, setDialog] = useState<{
|
||||
type: "alert" | "confirm"
|
||||
title: string
|
||||
message: string
|
||||
onConfirm: () => void
|
||||
onCancel?: () => void
|
||||
} | null>(null)
|
||||
|
||||
const [showBrainSelect, setShowBrainSelect] = useState(false)
|
||||
|
||||
async function changeBrainModel(model: string) {
|
||||
try {
|
||||
await api("/api/agent/brain", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ model })
|
||||
})
|
||||
setDialog({
|
||||
type: "alert",
|
||||
title: "Erfolgreich",
|
||||
message: `Hermes-Gehirn wurde auf '${model}' geändert. Der Gateway-Dienst wurde neu gestartet.`,
|
||||
onConfirm: () => setDialog(null)
|
||||
})
|
||||
loadData()
|
||||
setShowBrainSelect(false)
|
||||
} catch (e: any) {
|
||||
setDialog({
|
||||
type: "alert",
|
||||
title: "Fehler",
|
||||
message: `Fehler beim Wechseln des Gehirns: ${e.message}`,
|
||||
onConfirm: () => setDialog(null)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function showConfirm(title: string, message: string, onConfirm: () => void) {
|
||||
setDialog({
|
||||
type: "confirm",
|
||||
title,
|
||||
message,
|
||||
onConfirm: () => {
|
||||
setDialog(null)
|
||||
onConfirm()
|
||||
},
|
||||
onCancel: () => setDialog(null)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// Quick Memory Form State
|
||||
const [memContent, setMemContent] = useState("")
|
||||
const [memCat, setMemCat] = useState("stable")
|
||||
@@ -385,7 +436,7 @@ export function DashboardView() {
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => { if (confirm("Bist du sicher, dass du das Host-System neu starten willst?")) postAction("/api/maintenance/reboot", "Reboot") }}
|
||||
onClick={() => { showConfirm("Host-System neu starten?", "Bist du sicher, dass du das Host-System neu starten willst?", () => postAction("/api/maintenance/reboot", "Reboot")) }}
|
||||
disabled={loading}
|
||||
className="w-full h-8 flex items-center justify-center gap-1.5 rounded-lg border border-red-500/30 bg-red-500/5 text-red-400 text-[10px] font-bold uppercase hover:bg-red-500/10 transition-all cursor-pointer disabled:opacity-50"
|
||||
>
|
||||
@@ -490,8 +541,18 @@ export function DashboardView() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-3 bg-background/20 rounded-xl border border-border/40">
|
||||
<div className="text-[10px] text-muted-foreground uppercase font-semibold">Aktives Gehirn</div>
|
||||
<div
|
||||
onClick={() => setShowBrainSelect(true)}
|
||||
className="p-3 bg-background/20 rounded-xl border border-border/40 hover:border-primary/45 hover:bg-background/30 transition-all duration-300 cursor-pointer group"
|
||||
>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-[10px] text-muted-foreground uppercase font-semibold">Aktives Gehirn</span>
|
||||
<button
|
||||
className="flex items-center gap-1 text-[9px] font-bold uppercase tracking-wider text-primary border border-primary/20 bg-primary/10 hover:bg-primary/20 px-1.5 py-0.5 rounded transition-all cursor-pointer font-space"
|
||||
>
|
||||
<Cpu className="h-3 w-3" /> Ändern
|
||||
</button>
|
||||
</div>
|
||||
<div className="text-xs font-medium mt-1 font-mono text-primary flex items-center gap-1.5">
|
||||
<Layers className="h-3.5 w-3.5" />
|
||||
{agent.brain_model ? `model: ${agent.brain_model}` : "model: auto"}
|
||||
@@ -628,6 +689,68 @@ export function DashboardView() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{agent && showBrainSelect && (
|
||||
<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">
|
||||
<Cpu className="h-4 w-4" />
|
||||
<span>Hermes-Gehirn konfigurieren</span>
|
||||
</h3>
|
||||
<button
|
||||
onClick={() => setShowBrainSelect(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-xs text-muted-foreground leading-relaxed">
|
||||
Das „Gehirn" ist das primäre LLM für Hermes. Wähle ein Modell aus deiner Bibliothek oder nutze ein Gateway-Alias (<code className="text-primary font-semibold">auto</code> / <code className="text-primary font-semibold">fast</code> / <code className="text-primary font-semibold">heavy</code>):
|
||||
</p>
|
||||
|
||||
<div className="space-y-1.5 max-h-60 overflow-y-auto pr-1">
|
||||
{(["auto", "fast", "heavy", ...models.map(m => m.name.split("/").pop()?.replace(".gguf", "") || m.name)]).map((m) => {
|
||||
const isAlias = ["auto", "fast", "heavy"].includes(m);
|
||||
return (
|
||||
<button
|
||||
key={m}
|
||||
onClick={() => changeBrainModel(m)}
|
||||
className={cn(
|
||||
"w-full text-left px-3 py-2.5 rounded-lg text-xs hover:bg-accent flex items-center justify-between font-mono cursor-pointer border border-border/30",
|
||||
agent.brain_model === m || (!agent.brain_model && m === "auto")
|
||||
? "text-primary font-bold bg-primary/10 border-primary/30"
|
||||
: "text-foreground bg-background/20"
|
||||
)}
|
||||
>
|
||||
<div className="flex flex-col text-left">
|
||||
<span className="font-semibold truncate max-w-[280px]">
|
||||
{m}
|
||||
</span>
|
||||
<span className="text-[9px] text-muted-foreground mt-0.5">
|
||||
{isAlias ? "Gateway Routing Alias" : "Installiertes GGUF Modell"}
|
||||
</span>
|
||||
</div>
|
||||
{(agent.brain_model === m || (!agent.brain_model && m === "auto")) && (
|
||||
<Check className="h-4 w-4 shrink-0 text-primary" />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{dialog && (
|
||||
<CustomDialog
|
||||
type={dialog.type}
|
||||
title={dialog.title}
|
||||
message={dialog.message}
|
||||
onConfirm={() => dialog.onConfirm()}
|
||||
onCancel={dialog.onCancel}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user