Neue Ansicht 'Von allein': Skills + Vorschlags-Bilanz der Box (+ schlafende P1-Basis)

User-Wunsch 15.07.: EIN Viewpoint, was die Box selbst geleistet hat -
Chronik ist zu kompliziert. Neue Operativ-Ansicht #eigenleben:
- Skills aus ~/.hermes/skills (= Lucys Satz) mit Klartext-Beschreibung,
  Nutzungszaehler (.usage.json) und Herkunft (selbst erstellt / von uns
  gebaut / mitgeliefert), aufklappbar mit vollem SKILL.md.
- Vorschlags-Bilanz: angenommen (Auftragsbuch-Chronik) + abgelehnt mit
  Grund (Lern-Journal) als eine Timeline + Zaehler.
Backend: services/eigenleben.py + routers/eigenleben.py (read-only).

Mit an Bord (User-Ja, SCHLAFEND): config.V1_UPSTREAM + app.py-Weiche +
gateway_forward aus dem P1-Gateway-Auszug - ohne MC_V1_UPSTREAM in der
Unit exakt altes Verhalten. Der aktive Rest von P1 kommt separat ueber
Branch umbau/p1-gateway-auszug (dort liegt auch diese View schon).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-07-15 10:45:04 +02:00
parent 734cafd98f
commit 86bd72838a
31 changed files with 645 additions and 108 deletions
@@ -1,4 +1,4 @@
import{c as k,R as _,m as S,b as C,u as H,r as E,j as e,U as A,e as a,V as u,W as p,Y as B,s as h,Z as D,Q as P,X as M,C as g,g as b,q as i}from"./index-CgQUayy8.js";/**
import{c as k,R as _,m as S,b as C,u as H,r as E,j as e,U as A,e as a,V as u,W as p,Y as B,s as h,Z as D,Q as P,X as M,C as g,g as b,q as i}from"./index-KOW4xETl.js";/**
* @license lucide-react v0.460.0 - ISC
*
* This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
import{c as O,a4 as X,u as _,b as Q,r as h,j as e,a5 as Y,e as S,L as g,C as q,N as U,a6 as ee,a7 as te,O as V,X as I,w as R,F,a8 as re,T as z,g as B,a9 as se,q as J}from"./index-CgQUayy8.js";import{L as ae}from"./lightbulb-Dj1hbza2.js";import{S as G}from"./send-BjlsoGEB.js";/**
import{c as O,a4 as X,u as _,b as Q,r as h,j as e,a5 as Y,e as S,L as g,C as q,N as U,a6 as ee,a7 as te,O as V,X as I,w as R,F,a8 as re,T as z,g as B,a9 as se,q as J}from"./index-KOW4xETl.js";import{L as ae}from"./lightbulb-NhH1RZ1n.js";import{S as G}from"./send-Ca4K5Qji.js";/**
* @license lucide-react v0.460.0 - ISC
*
* This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
import{c as h,aa as S,r as c,j as e,ab as z,L as g,N as M,ac as C,a9 as D,ad as A,ae as B,af as Z,e as E,ag as L,u as q,b as R,g as k,q as T}from"./index-CgQUayy8.js";/**
import{c as h,aa as S,r as c,j as e,ab as z,L as g,N as M,ac as C,a9 as D,ad as A,ae as B,af as Z,e as E,ag as L,u as q,b as R,g as k,q as T}from"./index-KOW4xETl.js";/**
* @license lucide-react v0.460.0 - ISC
*
* This source code is licensed under the ISC license.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,4 +1,4 @@
import{c as p,j as e,r as M,s as C,a0 as z,a1 as k,a2 as y,W as v,Q as L,a3 as G,Z as T,e as f,N as D}from"./index-CgQUayy8.js";import{B as w}from"./book-open-ClD73ijg.js";import{a as N,b as B,L as P,C as E,Z as R}from"./zap-B7eYUqmc.js";import{L as I}from"./lightbulb-Dj1hbza2.js";/**
import{c as p,j as e,r as M,s as C,a0 as z,a1 as k,a2 as y,W as v,Q as L,a3 as G,Z as T,e as f,N as D}from"./index-KOW4xETl.js";import{B as w}from"./book-open-DIahvumE.js";import{a as N,b as B,L as P,C as E,Z as R}from"./zap-D4PbbO5d.js";import{L as I}from"./lightbulb-NhH1RZ1n.js";/**
* @license lucide-react v0.460.0 - ISC
*
* This source code is licensed under the ISC license.
@@ -1 +1 @@
import{R as l,V as o,j as e,e as a,U as n,T as d,$ as i}from"./index-CgQUayy8.js";function c(){const{data:s}=l(),r=s!=null&&s.box_console_url?o(s.box_console_url):void 0,t=s==null?void 0:s.box_console_reachable;return e.jsxs("div",{className:"flex h-full flex-col gap-4",children:[e.jsxs("div",{className:"flex flex-wrap items-end justify-between gap-3",children:[e.jsxs("div",{children:[e.jsx("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",children:"Konsole"}),e.jsx("p",{className:"flex items-center gap-2 text-sm text-muted-foreground",children:"Direkte Shell auf der Box — wie ein SSH-Fenster, mitten im Browser."})]}),e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsxs("span",{className:"flex items-center gap-1.5 text-[11px] font-medium text-muted-foreground",children:[e.jsx("span",{className:a("h-2 w-2 rounded-full",t?"bg-emerald-500 animate-pulse":"bg-amber-500")}),t?"online":"offline"]}),r&&e.jsxs("a",{href:r,target:"_blank",rel:"noopener",className:"flex h-8 items-center gap-1.5 rounded-lg border border-border/60 bg-background/20 px-3 text-xs font-semibold text-muted-foreground transition-all hover:border-primary/50 hover:text-foreground",children:[e.jsx(n,{className:"h-3.5 w-3.5"})," In neuem Tab"]})]})]}),r?e.jsxs("div",{className:"relative min-h-[58vh] flex-1 overflow-hidden rounded-2xl border border-border/60 bg-black/50 shadow-lg shadow-black/25",children:[t===!1&&e.jsxs("div",{className:"absolute inset-0 z-10 flex flex-col items-center justify-center gap-3 bg-black/70 text-center",children:[e.jsx(d,{className:"h-8 w-8 text-amber-400"}),e.jsx("div",{className:"text-sm font-semibold text-amber-300",children:"Konsole nicht erreichbar"}),e.jsxs("p",{className:"max-w-sm text-[11px] leading-normal text-muted-foreground",children:["Der ",e.jsx("code",{className:"font-mono text-primary",children:"box-console"}),"-Dienst (ttyd) läuft nicht. Auf der Box: ",e.jsx("code",{className:"font-mono",children:"systemctl --user restart box-console"}),"."]})]}),e.jsx("iframe",{src:r,title:"Box-Konsole",className:"h-full w-full border-0",style:{minHeight:"58vh"}})]}):e.jsxs("div",{className:"flex min-h-[58vh] flex-1 items-center justify-center rounded-2xl border border-border/60 bg-background/20 text-xs text-muted-foreground",children:[e.jsx(i,{className:"mr-2 h-4 w-4"})," Lade Konsole…"]})]})}export{c as KonsoleView};
import{R as l,V as o,j as e,e as a,U as n,T as d,$ as i}from"./index-KOW4xETl.js";function c(){const{data:s}=l(),r=s!=null&&s.box_console_url?o(s.box_console_url):void 0,t=s==null?void 0:s.box_console_reachable;return e.jsxs("div",{className:"flex h-full flex-col gap-4",children:[e.jsxs("div",{className:"flex flex-wrap items-end justify-between gap-3",children:[e.jsxs("div",{children:[e.jsx("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",children:"Konsole"}),e.jsx("p",{className:"flex items-center gap-2 text-sm text-muted-foreground",children:"Direkte Shell auf der Box — wie ein SSH-Fenster, mitten im Browser."})]}),e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsxs("span",{className:"flex items-center gap-1.5 text-[11px] font-medium text-muted-foreground",children:[e.jsx("span",{className:a("h-2 w-2 rounded-full",t?"bg-emerald-500 animate-pulse":"bg-amber-500")}),t?"online":"offline"]}),r&&e.jsxs("a",{href:r,target:"_blank",rel:"noopener",className:"flex h-8 items-center gap-1.5 rounded-lg border border-border/60 bg-background/20 px-3 text-xs font-semibold text-muted-foreground transition-all hover:border-primary/50 hover:text-foreground",children:[e.jsx(n,{className:"h-3.5 w-3.5"})," In neuem Tab"]})]})]}),r?e.jsxs("div",{className:"relative min-h-[58vh] flex-1 overflow-hidden rounded-2xl border border-border/60 bg-black/50 shadow-lg shadow-black/25",children:[t===!1&&e.jsxs("div",{className:"absolute inset-0 z-10 flex flex-col items-center justify-center gap-3 bg-black/70 text-center",children:[e.jsx(d,{className:"h-8 w-8 text-amber-400"}),e.jsx("div",{className:"text-sm font-semibold text-amber-300",children:"Konsole nicht erreichbar"}),e.jsxs("p",{className:"max-w-sm text-[11px] leading-normal text-muted-foreground",children:["Der ",e.jsx("code",{className:"font-mono text-primary",children:"box-console"}),"-Dienst (ttyd) läuft nicht. Auf der Box: ",e.jsx("code",{className:"font-mono",children:"systemctl --user restart box-console"}),"."]})]}),e.jsx("iframe",{src:r,title:"Box-Konsole",className:"h-full w-full border-0",style:{minHeight:"58vh"}})]}):e.jsxs("div",{className:"flex min-h-[58vh] flex-1 items-center justify-center rounded-2xl border border-border/60 bg-background/20 text-xs text-muted-foreground",children:[e.jsx(i,{className:"mr-2 h-4 w-4"})," Lade Konsole…"]})]})}export{c as KonsoleView};
@@ -1,5 +1,5 @@
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/GraphView-BrCeW1FA.js","assets/index-CgQUayy8.js","assets/index-Bpukq9Y7.css"])))=>i.map(i=>d[i]);
var ke=Object.defineProperty;var ve=(d,i,c)=>i in d?ke(d,i,{enumerable:!0,configurable:!0,writable:!0,value:c}):d[i]=c;var U=(d,i,c)=>ve(d,typeof i!="symbol"?i+"":i,c);import{c as h,r as s,j as e,u as Ne,b as Se,I as J,J as Ce,C as W,K as Ee,S as Me,M as ze,N as _,e as x,O as _e,Q as Oe,X,v as Z,_ as Te,g as j}from"./index-CgQUayy8.js";import{C as Y}from"./copy-B1SWMsx4.js";import{S as Ae}from"./send-BjlsoGEB.js";import{B as De}from"./book-open-ClD73ijg.js";/**
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/GraphView-Dqr19v9r.js","assets/index-KOW4xETl.js","assets/index-BAToQqxX.css"])))=>i.map(i=>d[i]);
var ke=Object.defineProperty;var ve=(d,i,c)=>i in d?ke(d,i,{enumerable:!0,configurable:!0,writable:!0,value:c}):d[i]=c;var U=(d,i,c)=>ve(d,typeof i!="symbol"?i+"":i,c);import{c as h,r as s,j as e,u as Ne,b as Se,I as J,J as Ce,C as W,K as Ee,S as Me,M as ze,N as _,e as x,O as _e,Q as Oe,X,v as Z,_ as Te,g as j}from"./index-KOW4xETl.js";import{C as Y}from"./copy-DZwsKvPS.js";import{S as Ae}from"./send-Ca4K5Qji.js";import{B as De}from"./book-open-DIahvumE.js";/**
* @license lucide-react v0.460.0 - ISC
*
* This source code is licensed under the ISC license.
@@ -34,7 +34,7 @@ var ke=Object.defineProperty;var ve=(d,i,c)=>i in d?ke(d,i,{enumerable:!0,config
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const Re=h("User",[["path",{d:"M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2",key:"975kel"}],["circle",{cx:"12",cy:"7",r:"4",key:"17ys0d"}]]);class re extends s.Component{constructor(){super(...arguments);U(this,"state",{error:null})}static getDerivedStateFromError(c){return{error:c}}render(){return this.state.error?e.jsxs("div",{className:"h-[480px] rounded-2xl border border-red-500/20 bg-red-500/5 p-6 text-xs text-red-400 overflow-auto",children:[e.jsx("div",{className:"font-semibold mb-2",children:"Graph konnte nicht gerendert werden"}),e.jsx("pre",{className:"whitespace-pre-wrap break-words text-[11px] leading-relaxed",children:this.state.error.message})]}):this.props.children}}const te=s.lazy(()=>Te(()=>import("./GraphView-BrCeW1FA.js"),__vite__mapDeps([0,1,2])).then(d=>({default:d.GraphView}))),k=["identity","knowledge","rules","events"],se=new Set(["auto","agent","hermes"]),O={identity:{label:"Identität",icon:Re,bg:"bg-cyan-500/10",text:"text-cyan-400"},knowledge:{label:"Wissen",icon:Oe,bg:"bg-indigo-500/10",text:"text-indigo-400"},rules:{label:"Regeln",icon:Pe,bg:"bg-violet-500/10",text:"text-violet-400"},events:{label:"Ereignisse",icon:_e,bg:"bg-amber-500/10",text:"text-amber-400"}},ne={label:"Gedächtnis",icon:De,text:"text-muted-foreground"},Ve={identity:"border-l-cyan-500/80",knowledge:"border-l-indigo-500/80",rules:"border-l-violet-500/80",events:"border-l-amber-500/80"},ae=`Hi Hermes! Lass uns ein kurzes Onboarding machen, damit du dich künftig an mich erinnerst. Stell mir nacheinander ein paar kurze Fragen zu:
*/const Re=h("User",[["path",{d:"M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2",key:"975kel"}],["circle",{cx:"12",cy:"7",r:"4",key:"17ys0d"}]]);class re extends s.Component{constructor(){super(...arguments);U(this,"state",{error:null})}static getDerivedStateFromError(c){return{error:c}}render(){return this.state.error?e.jsxs("div",{className:"h-[480px] rounded-2xl border border-red-500/20 bg-red-500/5 p-6 text-xs text-red-400 overflow-auto",children:[e.jsx("div",{className:"font-semibold mb-2",children:"Graph konnte nicht gerendert werden"}),e.jsx("pre",{className:"whitespace-pre-wrap break-words text-[11px] leading-relaxed",children:this.state.error.message})]}):this.props.children}}const te=s.lazy(()=>Te(()=>import("./GraphView-Dqr19v9r.js"),__vite__mapDeps([0,1,2])).then(d=>({default:d.GraphView}))),k=["identity","knowledge","rules","events"],se=new Set(["auto","agent","hermes"]),O={identity:{label:"Identität",icon:Re,bg:"bg-cyan-500/10",text:"text-cyan-400"},knowledge:{label:"Wissen",icon:Oe,bg:"bg-indigo-500/10",text:"text-indigo-400"},rules:{label:"Regeln",icon:Pe,bg:"bg-violet-500/10",text:"text-violet-400"},events:{label:"Ereignisse",icon:_e,bg:"bg-amber-500/10",text:"text-amber-400"}},ne={label:"Gedächtnis",icon:De,text:"text-muted-foreground"},Ve={identity:"border-l-cyan-500/80",knowledge:"border-l-indigo-500/80",rules:"border-l-violet-500/80",events:"border-l-amber-500/80"},ae=`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,
2) wie ich angesprochen werden möchte,
3) meine bevorzugten Tools, Sprachen und Arbeitsweise,
@@ -1,4 +1,4 @@
import{c as D,u as Ce,a as Ee,b as Se,j as e,f as ce,d as De,e as g,g as B,q as te,B as ae,h as qe,E as _e,i as Ge,r as y,X as Re,C as J,T as re,k as Z,H as Ae,l as P,m as me,n as Fe,o as Ke,p as Oe,S as pe,s as Ie,P as Te,t as Ue,v as He,w as Le,x as Pe,y as Qe}from"./index-CgQUayy8.js";import{C as xe,Z as ue,L as Ve,a as We,b as Je}from"./zap-B7eYUqmc.js";/**
import{c as D,u as Ce,a as Ee,b as Se,j as e,f as ce,d as De,e as g,g as B,q as te,B as ae,h as qe,E as _e,i as Ge,r as y,X as Re,C as J,T as re,k as Z,H as Ae,l as P,m as me,n as Fe,o as Ke,p as Oe,S as pe,s as Ie,P as Te,t as Ue,v as He,w as Le,x as Pe,y as Qe}from"./index-KOW4xETl.js";import{C as xe,Z as ue,L as Ve,a as We,b as Je}from"./zap-D4PbbO5d.js";/**
* @license lucide-react v0.460.0 - ISC
*
* This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
import{ah as y,r as x,g as L,j as e,L as v,S,ai as W,N as C,e as w,aj as E}from"./index-CgQUayy8.js";import{F as M}from"./folder-open-tYLNcEQ3.js";const D={"":"Allgemein",traeume:"Träume",muster:"Muster","skill-kandidaten":"Skill-Kandidaten","skill-kandidaten/beauftragt":"Skill-Kandidaten · beauftragt","skill-kandidaten/verworfen":"Skill-Kandidaten · verworfen"};function T(){const{data:o,isLoading:h}=y(),[l,f]=x.useState(null),[d,r]=x.useState(null),[u,i]=x.useState(!1),[a,p]=x.useState(""),t=x.useMemo(()=>(o==null?void 0:o.files)??[],[o]),m=x.useMemo(()=>{const s=new Map;for(const n of t)s.set(n.name.toLowerCase(),n.path);return s},[t]),g=x.useMemo(()=>{if(!a.trim())return t;const s=a.toLowerCase();return t.filter(n=>n.title.toLowerCase().includes(s)||n.path.toLowerCase().includes(s))},[t,a]),b=x.useMemo(()=>{const s=[];for(const n of g){const c=s.find(k=>k.dir===n.dir);c?c.files.push(n):s.push({dir:n.dir,files:[n]})}return s.sort((n,c)=>n.dir===""?-1:c.dir===""?1:n.dir.localeCompare(c.dir))},[g]);x.useEffect(()=>{if(!l&&t.length){const s=t.find(n=>n.path.toLowerCase()==="index.md");f((s==null?void 0:s.path)??t[0].path)}},[t,l]),x.useEffect(()=>{l&&(i(!0),L(`/api/wissen/datei?pfad=${encodeURIComponent(l)}`).then(r).catch(()=>r({path:l,content:"*(Notiz nicht ladbar)*",mtime:0})).finally(()=>i(!1)))},[l]);const j=s=>{const n=m.get(s.toLowerCase());n&&f(n)};return e.jsxs("div",{className:"space-y-5",children:[e.jsxs("div",{children:[e.jsx("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",children:"Wissen"}),e.jsx("p",{className:"text-sm text-muted-foreground",children:"Was sich die Box nachts erschließt — Traum-Notizen, Muster und Skill-Ideen aus dem Wissens-Vault, verlinkt wie ein Wiki."})]}),o&&!o.available&&e.jsx("div",{className:"rounded-2xl border border-amber-500/30 bg-amber-500/5 p-4 text-xs text-amber-300",children:"Der Wissens-Vault liegt auf der Box (~/wissens-vault) — im lokalen Dev-Modus nicht verfügbar."}),h&&e.jsxs("div",{className:"flex items-center gap-2 rounded-2xl border border-border/60 bg-card/30 p-6 text-xs text-muted-foreground",children:[e.jsx(v,{className:"h-4 w-4 animate-spin"})," Vault wird geladen …"]}),(o==null?void 0:o.available)&&t.length===0&&e.jsx("div",{className:"rounded-2xl border border-dashed border-border/60 bg-card/20 p-8 text-center text-xs text-muted-foreground",children:"Noch keine Notizen. Der nächtliche Traum (03:15) legt sie an — morgen früh steht hier die erste."}),t.length>0&&e.jsxs("div",{className:"flex flex-col gap-4 lg:flex-row",children:[e.jsxs("aside",{className:"w-full shrink-0 space-y-3 lg:w-72",children:[e.jsxs("div",{className:"relative",children:[e.jsx("input",{value:a,onChange:s=>p(s.target.value),type:"search",placeholder:"Notizen durchsuchen…","aria-label":"Notizen durchsuchen",className:"h-9 w-full rounded-lg border border-border/60 bg-card/45 pl-8 pr-3 text-xs text-foreground outline-none focus:ring-1 focus:ring-primary/50"}),e.jsx(S,{className:"absolute left-2.5 top-2.5 h-3.5 w-3.5 text-muted-foreground"})]}),e.jsx("div",{className:"max-h-[70vh] space-y-3 overflow-y-auto pr-1 scrollbar-thin",children:b.map(({dir:s,files:n})=>e.jsxs("div",{children:[e.jsxs("p",{className:"mb-1 flex items-center gap-1 text-[10px] font-bold uppercase tracking-wider text-muted-foreground/60",children:[e.jsx(M,{className:"h-3 w-3"})," ",D[s]??s]}),e.jsx("div",{className:"space-y-0.5",children:n.map(c=>e.jsxs("button",{onClick:()=>f(c.path),className:w("flex w-full items-center gap-1.5 rounded-lg px-2 py-1.5 text-left text-xs transition-all cursor-pointer",l===c.path?"bg-primary/15 text-primary":"text-muted-foreground hover:bg-accent hover:text-foreground"),children:[e.jsx(W,{className:"h-3.5 w-3.5 shrink-0"}),e.jsx("span",{className:"truncate",title:c.title,children:c.name}),c.neu&&e.jsxs("span",{className:"ml-auto flex items-center gap-0.5 rounded bg-violet-500/15 px-1 py-0.5 text-[8px] font-bold uppercase text-violet-300",children:[e.jsx(C,{className:"h-2.5 w-2.5"}),"neu"]})]},c.path))})]},s||"__root"))})]}),e.jsx("div",{className:"min-w-0 flex-1 mc-card p-5",children:u?e.jsxs("div",{className:"flex items-center gap-2 text-xs text-muted-foreground",children:[e.jsx(v,{className:"h-4 w-4 animate-spin"})," Notiz wird geladen …"]}):d?e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"mb-3 flex items-center justify-between border-b border-border/40 pb-2",children:[e.jsxs("span",{className:"flex items-center gap-1.5 font-mono text-[11px] text-muted-foreground",children:[e.jsx(E,{className:"h-3.5 w-3.5"})," ",d.path]}),d.mtime>0&&e.jsxs("span",{className:"text-[10px] text-muted-foreground/60",children:["Stand ",new Date(d.mtime*1e3).toLocaleString("de-DE",{day:"2-digit",month:"2-digit",hour:"2-digit",minute:"2-digit"})]})]}),e.jsx(z,{text:d.content,onWikiLink:j,known:m})]}):null})]})]})}function z({text:o,onWikiLink:h,known:l}){const f=o.split(`
import{aj as y,r as x,g as L,j as e,L as v,S,ak as W,N as C,e as w,al as E}from"./index-KOW4xETl.js";import{F as M}from"./folder-open-C6effGop.js";const D={"":"Allgemein",traeume:"Träume",muster:"Muster","skill-kandidaten":"Skill-Kandidaten","skill-kandidaten/beauftragt":"Skill-Kandidaten · beauftragt","skill-kandidaten/verworfen":"Skill-Kandidaten · verworfen"};function T(){const{data:o,isLoading:h}=y(),[l,f]=x.useState(null),[d,r]=x.useState(null),[u,i]=x.useState(!1),[a,p]=x.useState(""),t=x.useMemo(()=>(o==null?void 0:o.files)??[],[o]),m=x.useMemo(()=>{const s=new Map;for(const n of t)s.set(n.name.toLowerCase(),n.path);return s},[t]),g=x.useMemo(()=>{if(!a.trim())return t;const s=a.toLowerCase();return t.filter(n=>n.title.toLowerCase().includes(s)||n.path.toLowerCase().includes(s))},[t,a]),b=x.useMemo(()=>{const s=[];for(const n of g){const c=s.find(k=>k.dir===n.dir);c?c.files.push(n):s.push({dir:n.dir,files:[n]})}return s.sort((n,c)=>n.dir===""?-1:c.dir===""?1:n.dir.localeCompare(c.dir))},[g]);x.useEffect(()=>{if(!l&&t.length){const s=t.find(n=>n.path.toLowerCase()==="index.md");f((s==null?void 0:s.path)??t[0].path)}},[t,l]),x.useEffect(()=>{l&&(i(!0),L(`/api/wissen/datei?pfad=${encodeURIComponent(l)}`).then(r).catch(()=>r({path:l,content:"*(Notiz nicht ladbar)*",mtime:0})).finally(()=>i(!1)))},[l]);const j=s=>{const n=m.get(s.toLowerCase());n&&f(n)};return e.jsxs("div",{className:"space-y-5",children:[e.jsxs("div",{children:[e.jsx("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",children:"Wissen"}),e.jsx("p",{className:"text-sm text-muted-foreground",children:"Was sich die Box nachts erschließt — Traum-Notizen, Muster und Skill-Ideen aus dem Wissens-Vault, verlinkt wie ein Wiki."})]}),o&&!o.available&&e.jsx("div",{className:"rounded-2xl border border-amber-500/30 bg-amber-500/5 p-4 text-xs text-amber-300",children:"Der Wissens-Vault liegt auf der Box (~/wissens-vault) — im lokalen Dev-Modus nicht verfügbar."}),h&&e.jsxs("div",{className:"flex items-center gap-2 rounded-2xl border border-border/60 bg-card/30 p-6 text-xs text-muted-foreground",children:[e.jsx(v,{className:"h-4 w-4 animate-spin"})," Vault wird geladen …"]}),(o==null?void 0:o.available)&&t.length===0&&e.jsx("div",{className:"rounded-2xl border border-dashed border-border/60 bg-card/20 p-8 text-center text-xs text-muted-foreground",children:"Noch keine Notizen. Der nächtliche Traum (03:15) legt sie an — morgen früh steht hier die erste."}),t.length>0&&e.jsxs("div",{className:"flex flex-col gap-4 lg:flex-row",children:[e.jsxs("aside",{className:"w-full shrink-0 space-y-3 lg:w-72",children:[e.jsxs("div",{className:"relative",children:[e.jsx("input",{value:a,onChange:s=>p(s.target.value),type:"search",placeholder:"Notizen durchsuchen…","aria-label":"Notizen durchsuchen",className:"h-9 w-full rounded-lg border border-border/60 bg-card/45 pl-8 pr-3 text-xs text-foreground outline-none focus:ring-1 focus:ring-primary/50"}),e.jsx(S,{className:"absolute left-2.5 top-2.5 h-3.5 w-3.5 text-muted-foreground"})]}),e.jsx("div",{className:"max-h-[70vh] space-y-3 overflow-y-auto pr-1 scrollbar-thin",children:b.map(({dir:s,files:n})=>e.jsxs("div",{children:[e.jsxs("p",{className:"mb-1 flex items-center gap-1 text-[10px] font-bold uppercase tracking-wider text-muted-foreground/60",children:[e.jsx(M,{className:"h-3 w-3"})," ",D[s]??s]}),e.jsx("div",{className:"space-y-0.5",children:n.map(c=>e.jsxs("button",{onClick:()=>f(c.path),className:w("flex w-full items-center gap-1.5 rounded-lg px-2 py-1.5 text-left text-xs transition-all cursor-pointer",l===c.path?"bg-primary/15 text-primary":"text-muted-foreground hover:bg-accent hover:text-foreground"),children:[e.jsx(W,{className:"h-3.5 w-3.5 shrink-0"}),e.jsx("span",{className:"truncate",title:c.title,children:c.name}),c.neu&&e.jsxs("span",{className:"ml-auto flex items-center gap-0.5 rounded bg-violet-500/15 px-1 py-0.5 text-[8px] font-bold uppercase text-violet-300",children:[e.jsx(C,{className:"h-2.5 w-2.5"}),"neu"]})]},c.path))})]},s||"__root"))})]}),e.jsx("div",{className:"min-w-0 flex-1 mc-card p-5",children:u?e.jsxs("div",{className:"flex items-center gap-2 text-xs text-muted-foreground",children:[e.jsx(v,{className:"h-4 w-4 animate-spin"})," Notiz wird geladen …"]}):d?e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"mb-3 flex items-center justify-between border-b border-border/40 pb-2",children:[e.jsxs("span",{className:"flex items-center gap-1.5 font-mono text-[11px] text-muted-foreground",children:[e.jsx(E,{className:"h-3.5 w-3.5"})," ",d.path]}),d.mtime>0&&e.jsxs("span",{className:"text-[10px] text-muted-foreground/60",children:["Stand ",new Date(d.mtime*1e3).toLocaleString("de-DE",{day:"2-digit",month:"2-digit",hour:"2-digit",minute:"2-digit"})]})]}),e.jsx(z,{text:d.content,onWikiLink:j,known:m})]}):null})]})]})}function z({text:o,onWikiLink:h,known:l}){const f=o.split(`
`),d=[];let r=[],u=null;const i=a=>{r.length&&(d.push(e.jsx("ul",{className:"mb-3 ml-4 list-disc space-y-1",children:r},a)),r=[])};return f.forEach((a,p)=>{const t=`l${p}`;if(u!==null){a.trimEnd()==="```"?(d.push(e.jsx("pre",{className:"mb-3 overflow-x-auto rounded-xl border border-border/50 bg-background/50 p-3 font-mono text-[11px] leading-relaxed text-foreground/85",children:u.join(`
`)},t)),u=null):u.push(a);return}if(a.trimStart().startsWith("```")){i(t),u=[];return}const m=a.trimEnd();if(!m.trim()){i(t);return}const g=m.match(/^(#{1,4})\s+(.*)$/);if(g){i(t);const j=g[1].length,s=j===1?"text-lg font-bold mt-1 mb-3":j===2?"text-base font-bold mt-4 mb-2":"text-sm font-bold mt-3 mb-1.5";d.push(e.jsx("p",{className:w(s,"font-space text-foreground"),children:N(g[2],h,l,t)},t));return}const b=m.match(/^\s*[-*•]\s+(.*)$/);if(b){r.push(e.jsx("li",{className:"text-xs leading-relaxed text-foreground/85",children:N(b[1],h,l,t)},t));return}i(t),d.push(e.jsx("p",{className:"mb-2 text-xs leading-relaxed text-foreground/85",children:N(m,h,l,t)},t))}),i("end"),u!==null&&d.push(e.jsx("pre",{className:"mb-3 overflow-x-auto rounded-xl border border-border/50 bg-background/50 p-3 font-mono text-[11px]",children:u.join(`
`)},"code-end")),e.jsx("div",{className:"max-w-3xl",children:d})}function N(o,h,l,f){return o.split(/(\[\[[^\]]+\]\]|\*\*[^*]+\*\*|\*[^*]+\*|`[^`]+`)/g).map((r,u)=>{const i=`${f}-${u}`,a=r.match(/^\[\[([^\]|]+)(?:\|([^\]]+))?\]\]$/);if(a){const p=a[1].trim(),t=(a[2]??a[1]).trim();return l.has(p.toLowerCase())?e.jsx("button",{onClick:()=>h(p),className:"rounded bg-primary/10 px-1 text-primary underline decoration-primary/40 underline-offset-2 hover:bg-primary/20 cursor-pointer",children:t},i):e.jsx("span",{className:"rounded bg-background/40 px-1 text-muted-foreground",title:"Notiz existiert (noch) nicht",children:t},i)}return r.startsWith("**")&&r.endsWith("**")?e.jsx("b",{className:"font-semibold text-foreground",children:r.slice(2,-2)},i):r.startsWith("*")&&r.endsWith("*")&&r.length>2?e.jsx("i",{children:r.slice(1,-1)},i):r.startsWith("`")&&r.endsWith("`")?e.jsx("code",{className:"rounded bg-background/50 px-1 font-mono text-[11px] text-teal-300",children:r.slice(1,-1)},i):e.jsx("span",{children:r},i)})}export{T as WissenView};
@@ -1,4 +1,4 @@
import{c as a}from"./index-CgQUayy8.js";/**
import{c as a}from"./index-KOW4xETl.js";/**
* @license lucide-react v0.460.0 - ISC
*
* This source code is licensed under the ISC license.
+6
View File
@@ -0,0 +1,6 @@
import{c}from"./index-KOW4xETl.js";/**
* @license lucide-react v0.460.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/const r=c("CircleX",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m15 9-6 6",key:"1uzhvr"}],["path",{d:"m9 9 6 6",key:"z0biqf"}]]);export{r as C};
@@ -1,4 +1,4 @@
import{c}from"./index-CgQUayy8.js";/**
import{c}from"./index-KOW4xETl.js";/**
* @license lucide-react v0.460.0 - ISC
*
* This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
import{c as a}from"./index-CgQUayy8.js";/**
import{c as a}from"./index-KOW4xETl.js";/**
* @license lucide-react v0.460.0 - ISC
*
* This source code is licensed under the ISC license.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,4 +1,4 @@
import{c as t}from"./index-CgQUayy8.js";/**
import{c as t}from"./index-KOW4xETl.js";/**
* @license lucide-react v0.460.0 - ISC
*
* This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
import{c as a}from"./index-CgQUayy8.js";/**
import{c as a}from"./index-KOW4xETl.js";/**
* @license lucide-react v0.460.0 - ISC
*
* This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
import{c as a}from"./index-CgQUayy8.js";/**
import{c as a}from"./index-KOW4xETl.js";/**
* @license lucide-react v0.460.0 - ISC
*
* This source code is licensed under the ISC license.
+2 -2
View File
@@ -7,8 +7,8 @@
<link rel="manifest" href="/manifest.webmanifest" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<title>Mission Control 2.0</title>
<script type="module" crossorigin src="/assets/index-CgQUayy8.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-Bpukq9Y7.css">
<script type="module" crossorigin src="/assets/index-KOW4xETl.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BAToQqxX.css">
</head>
<body>
<div id="root"></div>
+3 -1
View File
@@ -21,6 +21,7 @@ const KonsoleView = lazy(() => import("@/views/KonsoleView").then((m) => ({ defa
const GuideView = lazy(() => import("@/views/GuideView").then((m) => ({ default: m.GuideView })))
const AuftragsbuchView = lazy(() => import("@/views/AuftragsbuchView").then((m) => ({ default: m.AuftragsbuchView })))
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).
@@ -201,10 +202,11 @@ 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 />}
{!["dashboard", "auftraege", "models", "connect", "memory", "wissen", "chronik", "agent", "konsole", "guide"].includes(view) && (
{!["dashboard", "auftraege", "eigenleben", "models", "connect", "memory", "wissen", "chronik", "agent", "konsole", "guide"].includes(view) && (
<Placeholder title={active.label} hint={active.hint} />
)}
</Suspense>
+28
View File
@@ -520,6 +520,34 @@ export interface ChronikResp {
items: ChronikItem[]
}
// ── Eigenleben („Von allein": Skills + Vorschlags-Bilanz der Box) ────────────
export interface EigenlebenSkill {
id: string
name: string
beschreibung: string
version: string
autor: string
herkunft: "selbst" | "repo" | "bundled"
herkunft_label: string
genutzt: number
zuletzt_genutzt: string | null
erstellt: string | null
status: string
geaendert: number
}
export interface EigenlebenEintrag {
art: string // "angenommen" | "abgelehnt" | sonstiger Chronik-State
titel: string
detail: string
ts: number
}
export interface EigenlebenResp {
available: boolean
skills: EigenlebenSkill[]
selbst_erstellt: number
bilanz: { eintraege: EigenlebenEintrag[]; angenommen: number; abgelehnt: number }
}
// ── Wissens-Vault (Traum-Notizen, read-only) ─────────────────────────────────
export interface WissenFile {
path: string
+9
View File
@@ -12,6 +12,7 @@ import {
type ConnectHealth,
type DiscoverResp,
type DraftsResp,
type EigenlebenResp,
type GroupsResp,
type HermesBrainResp,
type Health,
@@ -57,6 +58,7 @@ export const qk = {
auftragsbuch: ["auftragsbuch"] as const,
ideen: ["ideen"] as const,
chronik: ["chronik"] as const,
eigenleben: ["eigenleben"] as const,
wissen: ["wissen"] as const,
zeitmaschine: ["zeitmaschine"] as const,
reminders: ["reminders"] as const,
@@ -113,6 +115,13 @@ export const useChronik = (limit = 150, refetchInterval: number = TAKT.gemuetlic
export const useWissen = () =>
useQuery({ queryKey: qk.wissen, queryFn: () => api<WissenResp>("/api/wissen") })
export const useEigenleben = (refetchInterval: number = TAKT.traege) =>
useQuery({
queryKey: qk.eigenleben,
queryFn: () => api<EigenlebenResp>("/api/eigenleben"),
refetchInterval,
})
export const useZeitmaschine = (refetchInterval: number = TAKT.traege) =>
useQuery({
queryKey: qk.zeitmaschine,
+3 -1
View File
@@ -7,12 +7,13 @@ import {
Library,
Plug,
Bot,
Sparkles,
SquareTerminal,
HelpCircle,
type LucideIcon,
} from "lucide-react"
export type ViewId = "dashboard" | "auftraege" | "models" | "memory" | "wissen" | "chronik" | "connect" | "agent" | "konsole" | "guide"
export type ViewId = "dashboard" | "auftraege" | "eigenleben" | "models" | "memory" | "wissen" | "chronik" | "connect" | "agent" | "konsole" | "guide"
export type NavGroup = "Operativ" | "Wissen" | "Werkzeuge" | "Wartung"
@@ -28,6 +29,7 @@ export interface NavItem {
export const NAV: NavItem[] = [
{ id: "dashboard", label: "Cockpit", hint: "Deine Box auf einen Blick", icon: LayoutDashboard, group: "Operativ" },
{ id: "auftraege", label: "Auftragsbuch", hint: "Vorschläge der Box — annehmen oder ablehnen", icon: Inbox, group: "Operativ" },
{ id: "eigenleben", label: "Von allein", hint: "Was die Box selbst gebaut hat — Skills & 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" },
+223
View File
@@ -0,0 +1,223 @@
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.",
},
}
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">
Von allein
</h1>
<p className="text-sm text-muted-foreground">
Was deine Box selbst geleistet hat: ihre Fähigkeiten (Skills) und was aus ihren Vorschlägen wurde.
Das ist derselbe Skill-Satz, mit dem Lucy spricht und die Nacht-Worker arbeiten.
</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.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">{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>
)
}