Tab-Review-Fixes: toter Gedaechtnis-Knopf, Konsole-Restart, Cockpit-Dopplung

- Gedaechtnis: 'Im Terminal starten' navigierte zu nicht existenter View 'terminal'
  -> 'konsole'; interne Duplikate (AUTO-Set, CAT_LABEL_MAP) entfernt
- Konsole: box-console jetzt in Dienste-Liste + Restart-Allowlist; Offline-Overlay
  bekommt Ein-Klick-'Dienst neu starten' statt SSH-Anweisung
- Cockpit Werkzeuge: Doppel-Eintrag ('Box-Shell' + 'Interaktives Terminal', beide
  -> Konsole) zu EINEM Eintrag 'Konsole (Box-Shell)' zusammengelegt
- Auftragsbuch: 'Nichts zu entscheiden'-Banner beruecksichtigt blockierte
  Ideen-Karten; Abschnitt heisst neutral 'Patch-Vorschlaege'
- Hermes-Diagnose: Klickweg (Dienste-Schublade oeffnen) statt veraltetem
  Drawer-Namen + systemctl-Kommandos
- Entdecken: Upgrade-Knopf uebernimmt fremde Quant nicht mehr blind
- Skills: Status-Badge zeigt Klartext (abgeschaltet/defekt/...) statt rohem State

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-07-16 00:42:04 +02:00
parent bc60f9254b
commit 26224804ed
34 changed files with 185 additions and 145 deletions
+7 -8
View File
@@ -236,15 +236,14 @@ export function AgentView() {
Der Hermes-Dienst ist auf der Box derzeit offline oder kann sich nicht mit dem lokalen llama-swap verbinden.
</p>
<p>
Stelle sicher, dass die systemd-Dienste auf der Box laufen. Du kannst den Status im <strong>OS &amp; Updates Drawer</strong> prüfen und die Dienste bei Bedarf neu starten.
Am schnellsten: In der System-Schublade unter <strong>Dienste</strong> den Eintrag{" "}
<strong>Hermes-Gateway</strong> mit einem Klick neu starten (dort gibt es auch die Logs dazu).
</p>
<div className="p-3.5 bg-background/25 rounded-xl border border-border/30 font-mono text-[10px] space-y-1">
<div className="text-muted-foreground/60"># Dienste manuell auf der Box prüfen:</div>
<div className="text-primary">systemctl --user status hermes-gateway</div>
<div className="text-primary">systemctl --user status hermes-builtin-ui</div>
</div>
<button
onClick={() => window.dispatchEvent(new CustomEvent("open-system-drawer", { detail: { tab: "maintenance" } }))}
className="flex h-9 items-center gap-1.5 rounded-lg border border-primary/40 bg-primary/10 px-4 text-[11px] font-bold uppercase tracking-wide text-primary transition-all hover:bg-primary/20 cursor-pointer">
<Wrench className="h-3.5 w-3.5" /> Dienste-Übersicht öffnen
</button>
<p>
Weitere Einrichtungsdetails (Brain-Konfiguration, MCP-Kopplungen, SSH-Tunneling und Such-Engines) findest du in der Dokumentationsdatei:
<code className="ml-1 px-1.5 py-0.5 rounded bg-muted/40 text-foreground border border-border/30">docs/HERMES_SETUP.md</code>.
+7 -3
View File
@@ -36,6 +36,9 @@ function fmtWhen(ts: number | null | undefined): string {
export function AuftragsbuchView() {
const { data, isLoading, refetch, isFetching } = useAuftragsbuch()
// Geteilter Cache mit der IdeenSection — hier nur fürs ehrliche "Nichts zu entscheiden"-
// Banner (eine hängende Karte direkt darunter widerspräche dem grünen Häkchen).
const { data: ideenData } = useIdeen()
const qc = useQueryClient()
const { showAlert, showConfirm, dialogElement } = useDialog()
const [busy, setBusy] = useState<Record<string, boolean>>({})
@@ -57,7 +60,8 @@ export function AuftragsbuchView() {
const items = data?.items ?? []
const kandidaten = data?.skill_kandidaten ?? []
const empty = items.length === 0 && kandidaten.length === 0
const blockedIdeen = ideenData?.available ? ideenData.items.filter((i) => i.status === "blocked").length : 0
const empty = items.length === 0 && kandidaten.length === 0 && blockedIdeen === 0
return (
<div className="space-y-6">
@@ -102,7 +106,7 @@ export function AuftragsbuchView() {
{items.length > 0 && (
<section className="space-y-3">
<SectionLabel icon={GitBranch}>Fertige Patches ({items.length})</SectionLabel>
<SectionLabel icon={GitBranch}>Patch-Vorschläge ({items.length})</SectionLabel>
{items.map((it) => (
<ProposalCard key={`${it.repo ?? "mc2"}:${it.branch}`} item={it} busy={busy} onAct={act} showConfirm={showConfirm} />
))}
@@ -243,7 +247,7 @@ function IdeenSection() {
</div>
<p className="text-[11px] text-muted-foreground">
Eine Queue, alle Türen: hier, per Telegram oder im Gespräch mit Lucy. Die Box arbeitet Ideen selbst
aus und baut Code-Ergebnisse erscheinen als Karte unter Fertige Patches, nichts davon geht ohne
aus und baut Code-Ergebnisse erscheinen als Karte unter Patch-Vorschläge, nichts davon geht ohne
deinen Klick live. Nur reine Doku-Bagatellen zieht die Box nachts selbst ein (mit Fangnetz und Meldung).
</p>
+11 -1
View File
@@ -30,6 +30,14 @@ const HERKUNFT_UI: Record<EigenlebenSkill["herkunft"], { icon: LucideIcon; cls:
},
}
// Hermes-Skill-States in Klartext (Quelle: banner/use_count-Daten; unbekannte Werte roh anzeigen).
const STATUS_LABEL: Record<string, string> = {
disabled: "abgeschaltet",
deprecated: "veraltet",
broken: "defekt",
error: "Fehler",
}
function datumKurz(iso: string | null): string {
if (!iso) return "—"
const d = new Date(iso)
@@ -169,7 +177,9 @@ function SkillKarte({ skill }: { skill: EigenlebenSkill }) {
</span>
)}
{skill.status !== "active" && (
<span className="rounded bg-amber-500/10 px-1.5 py-0.5 text-[9px] font-bold text-amber-300">{skill.status}</span>
<span className="rounded bg-amber-500/10 px-1.5 py-0.5 text-[9px] font-bold text-amber-300">
{STATUS_LABEL[skill.status] ?? skill.status}
</span>
)}
</div>
<p className={cn("mt-1 text-xs leading-relaxed text-foreground/75", !offen && "line-clamp-2")}>
+30 -4
View File
@@ -1,5 +1,7 @@
import { SquareTerminal, ExternalLink, AlertTriangle } from "lucide-react"
import { useAgentStatus } from "@/lib/queries"
import { useState } from "react"
import { SquareTerminal, ExternalLink, AlertTriangle, RefreshCw } from "lucide-react"
import { api } from "@/lib/api"
import { useAgentStatus, useQueryClient, qk, invalidate } from "@/lib/queries"
import { resolveExternalUrl, cn } from "@/lib/utils"
// Konsole = direkter, SSH-artiger Box-Zugriff (ttyd → Login-Shell auf :7682), eingebettet
@@ -7,8 +9,28 @@ import { resolveExternalUrl, cn } from "@/lib/utils"
// Terminal selbst). Die Passwort-Verwaltung liegt in System-Wartung → Einstellungen.
export function KonsoleView() {
const { data: agent } = useAgentStatus()
const qc = useQueryClient()
const url = agent?.box_console_url ? resolveExternalUrl(agent.box_console_url) : undefined
const ok = agent?.box_console_reachable
const [restarting, setRestarting] = useState(false)
const [restartMsg, setRestartMsg] = useState("")
// Ein-Klick-Reparatur statt SSH-Anweisung: box-console ist ein normaler User-Dienst.
async function restartConsole() {
setRestarting(true)
setRestartMsg("")
try {
const r = await api<{ ok: boolean; err?: string }>("/api/maintenance/restart", {
method: "POST", body: JSON.stringify({ service: "box-console" }),
})
setRestartMsg(r.ok ? "Neu gestartet — einen Moment, dann lädt das Terminal." : `Fehlgeschlagen: ${r.err || "Unbekannter Fehler"}`)
invalidate(qc, qk.agentStatus, qk.services)
} catch (e: any) {
setRestartMsg(`Fehlgeschlagen: ${e?.message || e}`)
} finally {
setRestarting(false)
}
}
return (
<div className="flex h-full flex-col gap-4">
@@ -42,9 +64,13 @@ export function KonsoleView() {
<AlertTriangle className="h-8 w-8 text-amber-400" />
<div className="text-sm font-semibold text-amber-300">Konsole nicht erreichbar</div>
<p className="max-w-sm text-[11px] leading-normal text-muted-foreground">
Der <code className="font-mono text-primary">box-console</code>-Dienst (ttyd) läuft nicht.
Auf der Box: <code className="font-mono">systemctl --user restart box-console</code>.
Der Terminal-Dienst (<code className="font-mono text-primary">box-console</code>) läuft gerade nicht.
</p>
<button onClick={restartConsole} disabled={restarting}
className="flex h-9 items-center gap-1.5 rounded-lg border border-amber-500/40 bg-amber-500/10 px-4 text-[11px] font-bold uppercase tracking-wide text-amber-300 transition-all hover:bg-amber-500/20 cursor-pointer disabled:opacity-50">
<RefreshCw className={cn("h-3.5 w-3.5", restarting && "animate-spin")} /> Dienst neu starten
</button>
{restartMsg && <p className="max-w-sm text-[11px] text-muted-foreground">{restartMsg}</p>}
</div>
)}
<iframe src={url} title="Box-Konsole" className="h-full w-full border-0" style={{ minHeight: "58vh" }} />
+11 -14
View File
@@ -31,6 +31,12 @@ const BORDER_CLASSES: Record<string, string> = {
rules: "border-l-violet-500/80", events: "border-l-amber-500/80",
}
// Hex-Farben für Inline-Styles (Graph-Punkte, Detail-Panel) — Tailwind-Klassen aus
// CAT_CONFIG greifen dort nicht.
const CAT_COLOR_MAP: Record<string, string> = {
identity: "#00f5ff", knowledge: "#3b82f6", rules: "#d946ef", events: "#fbbf24",
}
// Onboarding-Prompt: der Nutzer startet damit ein Gespräch mit Hermes (Terminal/Telegram).
const ONBOARDING_PROMPT = `Hi Hermes! Lass uns ein kurzes Onboarding machen, damit du dich künftig an mich erinnerst. Stell mir nacheinander ein paar kurze Fragen zu:
1) wer ich bin und woran ich gerade arbeite,
@@ -142,7 +148,8 @@ export function MemoryView() {
function goTerminal() {
copyPrompt()
window.dispatchEvent(new CustomEvent("mc-navigate", { detail: { view: "terminal" } }))
// "konsole" ist die echte View-Id (nav.ts) — "terminal" existierte nie, der Knopf lief ins Leere.
window.dispatchEvent(new CustomEvent("mc-navigate", { detail: { view: "konsole" } }))
}
async function cleanup() {
@@ -179,16 +186,6 @@ export function MemoryView() {
return allItems.filter((m) => connectedIds.has(m.id))
}, [sel, graph, allItems])
const CAT_COLOR_MAP: Record<string, string> = {
identity: "#00f5ff", knowledge: "#3b82f6", rules: "#d946ef", events: "#fbbf24",
}
const CAT_LABEL_MAP: Record<string, string> = {
identity: "Identität", knowledge: "Wissen", rules: "Regeln", events: "Ereignisse",
}
const AUTO = new Set(["auto", "agent", "hermes"])
return (
<div className="space-y-6">
{empty ? (
@@ -437,10 +434,10 @@ export function MemoryView() {
<div className="flex items-center gap-2 mb-3 shrink-0">
<span className="w-2.5 h-2.5 rounded-full" style={{ background: CAT_COLOR_MAP[sel.category] }} />
<span className="text-[9px] font-bold uppercase tracking-wider" style={{ color: CAT_COLOR_MAP[sel.category] }}>
{CAT_LABEL_MAP[sel.category] || sel.category}
{CAT_CONFIG[sel.category]?.label ?? sel.category}
</span>
<span className={`ml-auto text-[9px] font-mono flex items-center gap-1 ${AUTO.has(sel.source) ? "text-emerald-400" : "text-muted-foreground/70"}`}>
{AUTO.has(sel.source) && <Sparkles className="h-2.5 w-2.5" />}
<span className={`ml-auto text-[9px] font-mono flex items-center gap-1 ${AUTO_SOURCES.has(sel.source) ? "text-emerald-400" : "text-muted-foreground/70"}`}>
{AUTO_SOURCES.has(sel.source) && <Sparkles className="h-2.5 w-2.5" />}
{sel.source}
</span>
<button onClick={() => setSelected(null)} className="text-muted-foreground hover:text-foreground ml-1.5 cursor-pointer"><X className="h-4 w-4" /></button>
+3 -7
View File
@@ -391,7 +391,9 @@ function ToolListCard({ agent, svcErrors, onNavigate }: {
action: () => onNavigate("agent"),
},
{
label: "Direkte Box-Shell (SSH)",
// EIN Eintrag für die Konsole (vorher standen "Direkte Box-Shell (SSH)" UND
// "Interaktives Terminal" in der Liste — zwei Namen, dasselbe Ziel).
label: "Konsole (Box-Shell)",
status: agent ? (agent.box_console_reachable ? "Bereit" : "Offline") : "…",
tone: !agent ? "loading" : agent.box_console_reachable ? "ok" : "warn",
action: () => onNavigate("konsole"),
@@ -402,12 +404,6 @@ function ToolListCard({ agent, svcErrors, onNavigate }: {
tone: svcErrors > 0 ? "alert" : "ok",
action: () => openDrawer("logs"),
},
{
label: "Interaktives Terminal",
status: "Öffnen",
tone: "muted",
action: () => onNavigate("konsole"),
},
{
label: "Wissens-Vault",
status: "Öffnen",
+5 -1
View File
@@ -224,7 +224,11 @@ export function Discover() {
<span>Bessere Version in der Registry: {hasUpgrade.repo.split("/").pop()}</span>
</div>
<button
onClick={() => install(hasUpgrade.repo, cat.role, recommendedModel.quant || "Q4_K_M", recommendedModel.caps.tools !== "no")}
// Quant des Empfehlungsmodells nur übernehmen, wenn das Upgrade DASSELBE
// Repo ist — sonst neutraler Default (fremde Quant könnte dort fehlen).
onClick={() => install(hasUpgrade.repo, cat.role,
hasUpgrade.repo === recommendedModel.repo ? (recommendedModel.quant || "Q4_K_M") : "Q4_K_M",
recommendedModel.caps.tools !== "no")}
disabled={!!installing[hasUpgrade.repo]}
className="h-8 w-full flex items-center justify-center gap-1.5 rounded-lg bg-amber-500 text-black text-xs font-bold hover:bg-amber-400 disabled:opacity-50 transition-all cursor-pointer shadow-md shadow-amber-500/10"
>