fix: remove unused EigenlebenView imports and rebuild frontend
Ampel / ampel (push) Successful in 22s
Ampel / ampel (push) Successful in 22s
This commit is contained in:
@@ -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
@@ -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" },
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user