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:
@@ -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 & 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>.
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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")}>
|
||||
|
||||
@@ -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" }} />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user