fix: remove unused EigenlebenView imports and rebuild frontend
Ampel / ampel (push) Successful in 22s

This commit is contained in:
Hitonabi
2026-08-07 11:02:49 +02:00
parent 1e68f62499
commit c2319739e2
30 changed files with 113 additions and 370 deletions
+2 -2
View File
@@ -22,7 +22,7 @@ const GuideView = lazy(() => import("@/views/GuideView").then((m) => ({ default:
const AuftragsbuchView = lazy(() => import("@/views/AuftragsbuchView").then((m) => ({ default: m.AuftragsbuchView })))
const IdeenView = lazy(() => import("@/views/IdeenView").then((m) => ({ default: m.IdeenView })))
const ChronikView = lazy(() => import("@/views/ChronikView").then((m) => ({ default: m.ChronikView })))
const EigenlebenView = lazy(() => import("@/views/EigenlebenView").then((m) => ({ default: m.EigenlebenView })))
const WissenView = lazy(() => import("@/views/WissenView").then((m) => ({ default: m.WissenView })))
// Dezenter Lade-Zustand während eine View nachgeladen wird (lokales Netz: kaum sichtbar).
@@ -209,7 +209,7 @@ export default function App() {
{view === "memory" && <MemoryView />}
{view === "wissen" && <WissenView />}
{view === "chronik" && <ChronikView />}
{view === "eigenleben" && <EigenlebenView />}
{view === "agent" && <AgentView />}
{view === "konsole" && <KonsoleView />}
{view === "guide" && <GuideView />}
+2 -3
View File
@@ -7,14 +7,13 @@ import {
Library,
Plug,
Bot,
Sparkles,
SquareTerminal,
HelpCircle,
Lightbulb,
type LucideIcon,
} from "lucide-react"
export type ViewId = "dashboard" | "ideen" | "auftraege" | "eigenleben" | "models" | "memory" | "wissen" | "chronik" | "connect" | "agent" | "konsole" | "guide"
export type ViewId = "dashboard" | "ideen" | "auftraege" | "models" | "memory" | "wissen" | "chronik" | "connect" | "agent" | "konsole" | "guide"
export type NavGroup = "Operativ" | "Wissen" | "Werkzeuge" | "Wartung"
@@ -35,7 +34,7 @@ export const NAV: NavItem[] = [
// Seitenüberschrift „Ideen" — ein langer Satz stand dort dreifach gedoppelt.
{ id: "ideen", label: "Ideen", hint: "Schreib hin, was entstehen soll", icon: Lightbulb, group: "Operativ" },
{ id: "auftraege", label: "Auftragsbuch", hint: "Vorschläge der Box — annehmen oder ablehnen", icon: Inbox, group: "Operativ" },
{ id: "eigenleben", label: "Skills", hint: "Fähigkeiten der Box — aktiv/inaktiv, Herkunft & Vorschlags-Bilanz", icon: Sparkles, group: "Operativ" },
{ id: "chronik", label: "Chronik", hint: "Was die Box von allein getan hat + Zeitmaschine", icon: History, group: "Operativ" },
{ id: "memory", label: "Gedächtnis", hint: "Geteiltes Memory verwalten", icon: Brain, group: "Wissen" },
{ id: "wissen", label: "Wissen", hint: "Lucys Wissens-Vault (Traum-Notizen)", icon: Library, group: "Wissen" },
-249
View File
@@ -1,249 +0,0 @@
import { useMemo, useState } from "react"
import {
Sparkles, Wrench, Package, Loader2, CheckCircle2, XCircle, CircleDot,
ChevronDown, ChevronRight, BookOpen, type LucideIcon,
} from "lucide-react"
import { api, type EigenlebenEintrag, type EigenlebenSkill } from "@/lib/api"
import { useEigenleben } from "@/lib/queries"
import { cn } from "@/lib/utils"
// „Von allein" — die Antwort auf die User-Frage vom 15.07.2026: EIN Ort, der zeigt,
// was die Box selbst geleistet hat. Oben die Skills (Lucys Fähigkeiten-Regal, selbst
// erstellte zuerst, mit Klartext-Beschreibung + Nutzungszähler), darunter die
// Vorschlags-Bilanz (angenommen/abgelehnt mit Grund) — ohne Chronik-Rauschen.
const HERKUNFT_UI: Record<EigenlebenSkill["herkunft"], { icon: LucideIcon; cls: string; erklaerung: string }> = {
selbst: {
icon: Sparkles,
cls: "text-emerald-300 bg-emerald-500/10 border-emerald-500/30",
erklaerung: "Diese Skills hat sich die Box selbst geschrieben — niemand hat sie ihr installiert.",
},
repo: {
icon: Wrench,
cls: "text-primary bg-primary/10 border-primary/25",
erklaerung: "Von uns gebaut und über das Repo installiert (deploy/skills).",
},
bundled: {
icon: Package,
cls: "text-muted-foreground bg-background/40 border-border/50",
erklaerung: "Kamen mit Hermes mit — Standard-Ausstattung.",
},
}
// 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)
return isNaN(d.getTime()) ? "—" : d.toLocaleDateString("de-DE", { day: "2-digit", month: "2-digit", year: "2-digit" })
}
export function EigenlebenView() {
const { data, isLoading } = useEigenleben()
const gruppen = useMemo(() => {
const g: { herkunft: EigenlebenSkill["herkunft"]; label: string; skills: EigenlebenSkill[] }[] = []
for (const s of data?.skills ?? []) {
const last = g[g.length - 1]
if (last && last.herkunft === s.herkunft) last.skills.push(s)
else g.push({ herkunft: s.herkunft, label: s.herkunft_label, skills: [s] })
}
return g
}, [data])
return (
<div className="space-y-7">
<div>
<h1 className="bg-gradient-to-r from-foreground via-foreground to-primary bg-clip-text font-space text-2xl font-bold tracking-tight text-transparent">
Skills
</h1>
<p className="text-sm text-muted-foreground">
Die Fähigkeiten deiner Box mit Herkunft, Nutzung und ob der Agent sie gerade laden kann
(aktiv/inaktiv). Das ist derselbe Skill-Satz, mit dem Lucy spricht und die Nacht-Worker
arbeiten. Darunter: was aus den Vorschlägen der Box wurde.
</p>
</div>
{isLoading && (
<div className="flex items-center gap-2 rounded-2xl border border-border/60 bg-card/30 p-6 text-xs text-muted-foreground">
<Loader2 className="h-4 w-4 animate-spin" /> Wird geladen
</div>
)}
{data && !data.available && (
<div className="rounded-2xl border border-amber-500/30 bg-amber-500/5 p-4 text-xs text-amber-300">
Die Skills liegen auf der Box (~/.hermes/skills) im lokalen Dev-Modus nicht verfügbar.
</div>
)}
{data?.available && (
<>
{/* Bilanz-Zähler */}
<div className="grid grid-cols-2 gap-2 sm:grid-cols-4">
<Zahl label="Skills gesamt" wert={data.skills.length} />
<Zahl label="davon selbst erstellt" wert={data.selbst_erstellt} betone />
<Zahl label="Vorschläge angenommen" wert={data.bilanz.angenommen} />
<Zahl label="Vorschläge abgelehnt" wert={data.bilanz.abgelehnt} />
</div>
{/* Skills nach Herkunft */}
{gruppen.map((g) => {
const ui = HERKUNFT_UI[g.herkunft]
const Icon = ui.icon
return (
<section key={g.herkunft} className="space-y-3">
<div>
<p className={cn("inline-flex items-center gap-1.5 rounded-md border px-2 py-1 text-[11px] font-bold", ui.cls)}>
<Icon className="h-3.5 w-3.5" /> {g.label} · {g.skills.length}
</p>
<p className="mt-1 text-[11px] text-muted-foreground/70">{ui.erklaerung}</p>
</div>
<div className="grid gap-2 md:grid-cols-2">
{g.skills.map((s) => <SkillKarte key={s.id} skill={s} />)}
</div>
</section>
)
})}
{/* Vorschlags-Bilanz */}
<Bilanz eintraege={data.bilanz.eintraege} />
</>
)}
</div>
)
}
function Zahl({ label, wert, betone }: { label: string; wert: number; betone?: boolean }) {
return (
<div className={cn("rounded-xl border p-3", betone ? "border-emerald-500/30 bg-emerald-500/5" : "border-border/50 bg-card/40")}>
<p className={cn("font-space text-xl font-bold", betone ? "text-emerald-300" : "text-foreground")}>{wert}</p>
<p className="text-[10px] font-semibold uppercase tracking-wide text-muted-foreground/70">{label}</p>
</div>
)
}
function SkillKarte({ skill }: { skill: EigenlebenSkill }) {
const [offen, setOffen] = useState(false)
const [text, setText] = useState<string | null>(null)
const [laedt, setLaedt] = useState(false)
async function toggle() {
const next = !offen
setOffen(next)
if (next && text === null && !laedt) {
setLaedt(true)
try {
const r = await api<{ id: string; text: string }>(`/api/eigenleben/skill/${encodeURIComponent(skill.id)}`)
setText(r.text)
} catch {
setText("(Konnte den Skill-Text nicht laden.)")
} finally {
setLaedt(false)
}
}
}
return (
<div className="rounded-xl border border-border/50 bg-card/40 p-3 backdrop-blur-sm">
<button onClick={toggle} className="flex w-full items-start gap-2 text-left cursor-pointer">
{offen ? <ChevronDown className="mt-0.5 h-3.5 w-3.5 shrink-0 text-muted-foreground" /> : <ChevronRight className="mt-0.5 h-3.5 w-3.5 shrink-0 text-muted-foreground" />}
<div className="min-w-0 flex-1">
<div className="flex flex-wrap items-center gap-2">
<span className="text-sm font-semibold text-foreground">{skill.name}</span>
{skill.aktiv === true && (
<span className="flex items-center gap-1 rounded bg-emerald-500/10 px-1.5 py-0.5 text-[9px] font-bold text-emerald-300"
title="Der Agent kann diesen Skill laden und nutzen.">
<span className="h-1.5 w-1.5 rounded-full bg-emerald-400" /> aktiv
</span>
)}
{skill.aktiv === false && (
<span className="flex items-center gap-1 rounded bg-zinc-500/10 px-1.5 py-0.5 text-[9px] font-bold text-zinc-400"
title="Der Agent kann diesen Skill gerade NICHT laden — Plattform passt nicht (z. B. Apple-Skills auf Linux), Voraussetzungen fehlen oder per Config abgeschaltet.">
<span className="h-1.5 w-1.5 rounded-full bg-zinc-500" /> inaktiv
</span>
)}
{skill.kategorie && (
<span className="rounded bg-background/60 px-1.5 py-0.5 text-[9px] font-semibold text-muted-foreground/80">{skill.kategorie}</span>
)}
{skill.genutzt > 0 && (
<span className="rounded bg-primary/10 px-1.5 py-0.5 text-[9px] font-bold text-primary" title={`zuletzt: ${datumKurz(skill.zuletzt_genutzt)}`}>
{skill.genutzt}× genutzt
</span>
)}
{skill.status !== "active" && (
<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")}>
{skill.beschreibung || "(keine Beschreibung im Skill hinterlegt)"}
</p>
</div>
</button>
{offen && (
<div className="mt-2 border-t border-border/40 pt-2">
<p className="mb-1.5 flex items-center gap-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground/60">
<BookOpen className="h-3 w-3" /> So funktioniert er (SKILL.md){skill.autor ? ` · Autor: ${skill.autor}` : ""}
</p>
{laedt
? <Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
: <pre className="max-h-80 overflow-y-auto whitespace-pre-wrap rounded-lg bg-background/50 p-2.5 font-mono text-[10.5px] leading-relaxed text-foreground/80 scrollbar-thin">{text ?? ""}</pre>}
</div>
)}
</div>
)
}
function Bilanz({ eintraege }: { eintraege: EigenlebenEintrag[] }) {
const [alle, setAlle] = useState(false)
const shown = alle ? eintraege : eintraege.slice(0, 12)
return (
<section className="space-y-3">
<p className="flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground/60">
<CircleDot className="h-3.5 w-3.5" /> Vorschlags-Bilanz was die Box vorschlug und was daraus wurde
</p>
{eintraege.length === 0 && (
<div className="rounded-2xl border border-dashed border-border/60 bg-card/20 p-6 text-center text-xs text-muted-foreground">
Noch keine Vorschläge verbucht. Sobald die Box etwas vorschlägt und du klickst, steht die Entscheidung hier.
</div>
)}
<div className="space-y-2">
{shown.map((e, i) => {
const angenommen = e.art === "angenommen"
const Icon = angenommen ? CheckCircle2 : e.art === "abgelehnt" ? XCircle : CircleDot
return (
<div key={`${e.titel}-${e.ts}-${i}`} className="flex items-start gap-2.5 rounded-xl border border-border/50 bg-card/40 p-3">
<Icon className={cn("mt-0.5 h-4 w-4 shrink-0",
angenommen ? "text-emerald-400" : e.art === "abgelehnt" ? "text-red-400" : "text-amber-300")} />
<div className="min-w-0 flex-1">
<div className="flex flex-wrap items-center gap-2">
<span className="font-mono text-xs font-semibold text-foreground">{e.titel}</span>
<span className={cn("rounded px-1.5 py-0.5 text-[9px] font-bold uppercase",
angenommen ? "bg-emerald-500/10 text-emerald-300" : e.art === "abgelehnt" ? "bg-red-500/10 text-red-300" : "bg-amber-500/10 text-amber-300")}>
{e.art}
</span>
<span className="ml-auto font-mono text-[10px] text-muted-foreground/60">
{e.ts ? new Date(e.ts * 1000).toLocaleDateString("de-DE", { day: "2-digit", month: "2-digit" }) : ""}
</span>
</div>
{e.detail && <p className="mt-1 text-xs leading-relaxed text-foreground/70">{e.detail}</p>}
</div>
</div>
)
})}
</div>
{!alle && eintraege.length > 12 && (
<button onClick={() => setAlle(true)} className="text-[11px] text-muted-foreground underline hover:text-foreground cursor-pointer">
Alle {eintraege.length} Einträge anzeigen
</button>
)}
</section>
)
}