mirror of
https://github.com/samvallad33/vestige.git
synced 2026-05-05 05:42:36 +02:00
Closes Agent 1's audit gap #4: FSRS memory state (Active / Dormant / Silent / Unavailable) was computed server-side per query but never rendered in the 3D graph. Spheres always tinted by node type. The new colour mode adds a second channel that users can toggle between at runtime — Type (default, existing behaviour) and State (new). The toggle is a radio-pair pill in the graph page's top-right control bar next to the node-count selector + Dream button. Buckets + palette: - Active ≥ 70% emerald #10b981 easily retrievable - Dormant 40-70% amber #f59e0b retrievable with effort - Silent 10-40% violet #8b5cf6 difficult, needs cues - Unavail. < 10% slate #6b7280 needs reinforcement Thresholds match `execute_system_status` at the backend so the graph colour bands line up exactly with what the Stats page reports in its stateDistribution block. Using retention as the proxy for the full accessibility formula (retention × 0.5 + retrieval × 0.3 + storage × 0.2) is an approximation — retention is the dominant 0.5 weight and it is the only FSRS channel the current GraphNode DTO carries. Swap to the full formula in a future release if the DTO grows. Implementation: - `apps/dashboard/src/lib/graph/nodes.ts` — new `MemoryState` type, `getMemoryState(retention)`, `MEMORY_STATE_COLORS`, `MEMORY_STATE_DESCRIPTIONS`, `ColorMode`, `getNodeColor(node, mode)`. - `NodeManager.colorMode` field (default `'type'`). `createNodeMeshes` now calls `getNodeColor(node, this.colorMode)` so newly-added nodes during the session follow the toggled mode. - New `NodeManager.setColorMode(mode)` mutates every live mesh's material + glow sprite colour in place. Idempotent; cheap. Does NOT touch opacity/emissive-intensity so the v2.0.5 suppression dimming layer keeps working unchanged. - New `MemoryStateLegend.svelte` floating overlay in the bottom-right when state mode is active (hidden in type mode so the legend doesn't compete with the node-type palette). - `Graph3D.svelte` accepts a new `colorMode` prop (default `'type'`) and runs a `$effect` that calls `setColorMode` on every toggle. - Dashboard rebuild picks up the new component + wiring. Tests: 171 vitest, svelte-check 581 files / 0 errors. No backend changes; this is pure dashboard code.
1 line
5.6 KiB
JavaScript
1 line
5.6 KiB
JavaScript
import"../chunks/Bzak7iHL.js";import{o as Ft}from"../chunks/DWVWfZUn.js";import{p as $t,a as Ct,d as s,e,j as W,h as y,g as t,r as a,s as E,f as dt,O,t as B,u as P}from"../chunks/VE8Jor13.js";import{d as Rt,s as i,a as At}from"../chunks/DHnEMX8z.js";import{i as X}from"../chunks/JkhlGLjU.js";import{e as U,i as q}from"../chunks/ByItJEsC.js";import{a as p,f as u}from"../chunks/7UNxJI5L.js";import{s as A}from"../chunks/ussr1V5_.js";import{a as w}from"../chunks/DcQGRi49.js";var Dt=u('<div class="h-24 glass-subtle rounded-xl animate-pulse"></div>'),Mt=u('<div class="grid grid-cols-2 lg:grid-cols-4 gap-4"></div>'),kt=u('<div class="flex-1 flex flex-col items-center gap-1"><span class="text-xs text-dim"> </span> <div class="w-full rounded-t transition-all duration-500"></div> <span class="text-xs text-muted"> </span></div>'),Bt=u('<div class="flex items-center gap-2 text-sm"><div class="w-3 h-3 rounded-full"></div> <span class="text-dim"> </span> <span class="text-muted ml-auto"> </span></div>'),St=u('<div class="flex items-center gap-3 text-sm"><span class="text-xs text-decay"> </span> <span class="text-dim truncate"> </span></div>'),Tt=u('<div class="p-6 glass rounded-xl !border-decay/20"><h2 class="text-sm text-decay font-semibold mb-3"> </h2> <div class="space-y-2 max-h-48 overflow-y-auto"></div></div>'),jt=u('<div class="p-6 glass rounded-xl"><h2 class="text-sm text-bright font-semibold mb-4">Retention Distribution</h2> <div class="flex items-end gap-1 h-40"></div></div> <div class="p-6 glass-subtle rounded-xl"><h2 class="text-sm text-bright font-semibold mb-4">Memory Types</h2> <div class="grid grid-cols-2 lg:grid-cols-4 gap-3"></div></div> <!>',1),Et=u('<div class="flex items-center gap-3 p-4 glass rounded-xl"><div class="w-3 h-3 rounded-full animate-pulse-glow"></div> <span class="text-sm font-medium"> </span> <span class="text-xs text-dim"> </span></div> <div class="grid grid-cols-2 lg:grid-cols-4 gap-4"><div class="p-4 glass rounded-xl"><div class="text-2xl text-bright font-bold"> </div> <div class="text-xs text-dim mt-1">Total Memories</div></div> <div class="p-4 glass rounded-xl"><div class="text-2xl font-bold"> </div> <div class="text-xs text-dim mt-1">Avg Retention</div></div> <div class="p-4 glass rounded-xl"><div class="text-2xl text-bright font-bold"> </div> <div class="text-xs text-dim mt-1">Due for Review</div></div> <div class="p-4 glass rounded-xl"><div class="text-2xl text-bright font-bold"> </div> <div class="text-xs text-dim mt-1">Embedding Coverage</div></div></div> <!> <div class="flex gap-3"><button class="px-4 py-2 bg-warning/20 border border-warning/40 text-warning text-sm rounded-xl hover:bg-warning/30 transition">Run Consolidation</button></div>',1),Ot=u('<div class="p-6 max-w-5xl mx-auto space-y-6"><h1 class="text-xl text-bright font-semibold">System Stats</h1> <!></div>');function Lt(ot,vt){$t(vt,!0);let n=E(null),m=E(null),l=E(null),Y=E(!0);Ft(async()=>{try{await(async d=>{var r=W(d,3);y(n,r[0],!0),y(m,r[1],!0),y(l,r[2],!0)})(await Promise.all([w.stats(),w.health(),w.retentionDistribution()]))}catch{}finally{y(Y,!1)}});function z(d){return{healthy:"#10b981",degraded:"#f59e0b",critical:"#ef4444",empty:"#6b7280"}[d]||"#6b7280"}async function nt(){try{await w.consolidate(),await(async d=>{var r=W(d,3);y(n,r[0],!0),y(m,r[1],!0),y(l,r[2],!0)})(await Promise.all([w.stats(),w.health(),w.retentionDistribution()]))}catch{}}var G=Ot(),lt=s(e(G),2);{var ct=d=>{var r=Mt();U(r,20,()=>Array(8),q,(F,H)=>{var $=Dt();p(F,$)}),a(r),p(d,r)},xt=d=>{var r=Et(),F=dt(r),H=e(F),$=s(H,2),pt=e($,!0);a($);var Z=s($,2),ut=e(Z);a(Z),a(F);var I=s(F,2),J=e(I),tt=e(J),mt=e(tt,!0);a(tt),O(2),a(J);var K=s(J,2),L=e(K),gt=e(L);a(L),O(2),a(K);var N=s(K,2),at=e(N),_t=e(at,!0);a(at),O(2),a(N);var et=s(N,2),st=e(et),ft=e(st);a(st),O(2),a(et),a(I);var rt=s(I,2);{var bt=D=>{var S=jt(),M=dt(S),T=s(e(M),2);U(T,21,()=>t(l).distribution,q,(g,c,v)=>{const C=P(()=>Math.max(...t(l).distribution.map(V=>V.count),1)),R=P(()=>t(c).count/t(C)*100),_=P(()=>v<3?"#ef4444":v<5?"#f59e0b":v<7?"#10b981":"#6366f1");var x=kt(),o=e(x),f=e(o,!0);a(o);var b=s(o,2),h=s(b,2),Q=e(h,!0);a(h),a(x),B(()=>{i(f,t(c).count),A(b,`height: ${t(R)??""}%; background: ${t(_)??""}; opacity: 0.7; min-height: 2px`),i(Q,t(c).range)}),p(g,x)}),a(T),a(M);var k=s(M,2),j=s(e(k),2);U(j,21,()=>Object.entries(t(l).byType),q,(g,c)=>{var v=P(()=>W(t(c),2));let C=()=>t(v)[0],R=()=>t(v)[1];var _=Bt(),x=e(_),o=s(x,2),f=e(o,!0);a(o);var b=s(o,2),h=e(b,!0);a(b),a(_),B(()=>{A(x,`background: ${({fact:"#00A8FF",concept:"#9D00FF",event:"#FFB800",person:"#00FFD1",note:"#8B95A5",pattern:"#FF3CAC",decision:"#FF4757"}[C()]||"#8B95A5")??""}`),i(f,C()),i(h,R())}),p(g,_)}),a(j),a(k);var yt=s(k,2);{var wt=g=>{var c=Tt(),v=e(c),C=e(v);a(v);var R=s(v,2);U(R,21,()=>t(l).endangered.slice(0,20),q,(_,x)=>{var o=St(),f=e(o),b=e(f);a(f);var h=s(f,2),Q=e(h,!0);a(h),a(o),B(V=>{i(b,`${V??""}%`),i(Q,t(x).content)},[()=>(t(x).retentionStrength*100).toFixed(0)]),p(_,o)}),a(R),a(c),B(()=>i(C,`Endangered Memories (${t(l).endangered.length??""})`)),p(g,c)};X(yt,g=>{t(l).endangered.length>0&&g(wt)})}p(D,S)};X(rt,D=>{t(l)&&D(bt)})}var it=s(rt,2),ht=e(it);a(it),B((D,S,M,T,k,j)=>{A(F,`border-color: ${D??""}30`),A(H,`background: ${S??""}`),A($,`color: ${M??""}`),i(pt,T),i(ut,`v${t(m).version??""}`),i(mt,t(n).totalMemories),A(L,`color: ${t(n).averageRetention>.7?"#10b981":t(n).averageRetention>.4?"#f59e0b":"#ef4444"}`),i(gt,`${k??""}%`),i(_t,t(n).dueForReview),i(ft,`${j??""}%`)},[()=>z(t(m).status),()=>z(t(m).status),()=>z(t(m).status),()=>t(m).status.toUpperCase(),()=>(t(n).averageRetention*100).toFixed(1),()=>t(n).embeddingCoverage.toFixed(0)]),At("click",ht,nt),p(d,r)};X(lt,d=>{t(Y)?d(ct):t(n)&&t(m)&&d(xt,1)})}a(G),p(ot,G),Ct()}Rt(["click"]);export{Lt as component};
|