mirror of
https://github.com/samvallad33/vestige.git
synced 2026-05-01 20:03:19 +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
6.6 KiB
JavaScript
1 line
6.6 KiB
JavaScript
import"../chunks/Bzak7iHL.js";import{o as gt}from"../chunks/DWVWfZUn.js";import{p as yt,s as D,c as Q,t as u,a as bt,d as o,e as n,h as N,g as r,r as a,O as ht}from"../chunks/VE8Jor13.js";import{d as wt,s as d,a as Rt}from"../chunks/DHnEMX8z.js";import{i as R}from"../chunks/JkhlGLjU.js";import{e as L,i as U}from"../chunks/ByItJEsC.js";import{a as l,f as v}from"../chunks/7UNxJI5L.js";import{s as W}from"../chunks/BR2EHpd7.js";import{a as Z}from"../chunks/DcQGRi49.js";var St=v("<button> </button>"),$t=v('<div class="h-16 glass-subtle rounded-xl animate-pulse"></div>'),Ot=v('<div class="space-y-2"></div>'),Pt=v('<div class="text-center py-12 text-dim"><div class="text-4xl mb-3 opacity-20">◇</div> <p> </p> <p class="text-xs text-muted mt-1">Use "Remind me..." in conversation to create intentions.</p></div>'),Nt=v('<span class="text-[10px] text-dream-glow"> </span>'),It=v('<span class="text-[10px] text-muted"> </span>'),Tt=v('<div class="p-4 glass-subtle rounded-xl"><div class="flex items-start gap-3"><div class="w-8 h-8 rounded-lg bg-white/[0.04] flex items-center justify-center text-lg flex-shrink-0"> </div> <div class="flex-1 min-w-0"><p class="text-sm text-text"> </p> <div class="flex flex-wrap gap-2 mt-2"><span> </span> <span> </span> <span class="text-[10px] text-muted"> </span> <!> <!></div></div> <span class="text-[10px] text-muted flex-shrink-0"> </span></div></div>'),kt=v('<div class="space-y-2"></div>'),zt=v('<div class="text-center py-8 text-dim"><div class="text-3xl mb-3 opacity-20">◬</div> <p class="text-sm">No predictions yet. Use Vestige more to train the predictive model.</p></div>'),Dt=v("<span> </span>"),Lt=v('<span class="text-dream-glow"> </span>'),Ut=v('<div class="p-3 glass-subtle rounded-xl flex items-start gap-3"><div class="w-6 h-6 rounded-full bg-dream/20 text-dream-glow text-xs flex items-center justify-center flex-shrink-0 mt-0.5"></div> <div class="flex-1 min-w-0"><p class="text-sm text-text line-clamp-2"> </p> <div class="flex gap-3 mt-1 text-xs text-muted"><span> </span> <!> <!></div></div></div>'),Ct=v('<div class="space-y-2"></div>'),At=v('<div class="p-6 max-w-5xl mx-auto space-y-8"><div class="flex items-center justify-between"><h1 class="text-xl text-bright font-semibold">Intentions & Predictions</h1> <span class="text-xs text-muted"> </span></div> <div class="space-y-4"><div class="flex items-center gap-2"><h2 class="text-sm text-bright font-semibold">Prospective Memory</h2> <span class="text-xs text-muted">"Remember to do X when Y happens"</span></div> <div class="flex gap-1.5"></div> <!></div> <div class="pt-6 border-t border-synapse/10 space-y-4"><div class="flex items-center gap-2"><h2 class="text-sm text-bright font-semibold">Predicted Needs</h2> <span class="text-xs text-muted">What you might need next</span></div> <!></div></div>');function Wt(tt,et){yt(et,!0);let I=D(Q([])),C=D(Q([])),A=D(!0),S=D("active");const at={active:"text-synapse-glow bg-synapse/10 border-synapse/30",fulfilled:"text-recall bg-recall/10 border-recall/30",cancelled:"text-dim bg-white/[0.03] border-subtle/20",snoozed:"text-dream-glow bg-dream/10 border-dream/30"},st={4:"critical",3:"high",2:"normal",1:"low"},rt={4:"text-decay",3:"text-amber-400",2:"text-dim",1:"text-muted"},it={time:"⏰",context:"◎",event:"⚡",manual:"◇"};function nt(s){let t;try{const e=JSON.parse(s.trigger_data||"{}");if(typeof e.condition=="string"&&e.condition)t=e.condition;else if(typeof e.topic=="string"&&e.topic)t=e.topic;else if(typeof e.at=="string"&&e.at)try{t=new Date(e.at).toLocaleDateString("en-US",{month:"short",day:"numeric"})}catch{t=e.at}else if(typeof e.in_minutes=="number")t=`in ${e.in_minutes} min`;else if(typeof e.inMinutes=="number")t=`in ${e.inMinutes} min`;else if(typeof e.codebase=="string"&&e.codebase){const i=typeof e.filePattern=="string"&&e.filePattern?`/${e.filePattern}`:"";t=`${e.codebase}${i}`}else t=s.trigger_type}catch{t=s.trigger_type}return t.length>40?t.slice(0,37)+"...":t}gt(async()=>{await X()});async function X(){N(A,!0);try{const[s,t]=await Promise.all([Z.intentions(r(S)),Z.predict()]);N(I,s.intentions||[],!0),N(C,t.predictions||[],!0)}catch{}finally{N(A,!1)}}async function ot(s){N(S,s,!0),await X()}function M(s){if(!s)return"";try{return new Date(s).toLocaleDateString("en-US",{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"})}catch{return s}}var j=At(),F=n(j),q=o(n(F),2),dt=n(q);a(q),a(F);var Y=o(F,2),E=o(n(Y),2);L(E,20,()=>["active","fulfilled","snoozed","cancelled","all"],U,(s,t)=>{var e=St(),i=n(e,!0);a(e),u(p=>{W(e,1,`px-3 py-1.5 rounded-xl text-xs transition ${r(S)===t?"bg-synapse/20 text-synapse-glow border border-synapse/40":"glass-subtle text-dim hover:bg-white/[0.03]"}`),d(i,p)},[()=>t.charAt(0).toUpperCase()+t.slice(1)]),Rt("click",e,()=>ot(t)),l(s,e)}),a(E);var lt=o(E,2);{var vt=s=>{var t=Ot();L(t,20,()=>Array(4),U,(e,i)=>{var p=$t();l(e,p)}),a(t),l(s,t)},ct=s=>{var t=Pt(),e=o(n(t),2),i=n(e);a(e),ht(2),a(t),u(()=>d(i,`No ${r(S)==="all"?"":r(S)+" "}intentions.`)),l(s,t)},pt=s=>{var t=kt();L(t,21,()=>r(I),U,(e,i)=>{var p=Tt(),g=n(p),y=n(g),T=n(y,!0);a(y);var f=o(y,2),$=n(f),k=n($,!0);a($);var b=o($,2),h=n(b),z=n(h,!0);a(h);var w=o(h,2),G=n(w);a(w);var O=o(w,2),x=n(O);a(O);var c=o(O,2);{var P=m=>{var _=Nt(),J=n(_);a(_),u(V=>d(J,`deadline: ${V??""}`),[()=>M(r(i).deadline)]),l(m,_)};R(c,m=>{r(i).deadline&&m(P)})}var B=o(c,2);{var ut=m=>{var _=It(),J=n(_);a(_),u(V=>d(J,`snoozed until ${V??""}`),[()=>M(r(i).snoozed_until)]),l(m,_)};R(B,m=>{r(i).snoozed_until&&m(ut)})}a(b),a(f);var K=o(f,2),ft=n(K,!0);a(K),a(g),a(p),u((m,_)=>{d(T,it[r(i).trigger_type]||"◇"),d(k,r(i).content),W(h,1,`px-2 py-0.5 text-[10px] rounded-lg border ${(at[r(i).status]||"text-dim bg-white/[0.03] border-subtle/20")??""}`),d(z,r(i).status),W(w,1,`text-[10px] ${(rt[r(i).priority]||"text-muted")??""}`),d(G,`${(st[r(i).priority]||"normal")??""} priority`),d(x,`${r(i).trigger_type??""}: ${m??""}`),d(ft,_)},[()=>nt(r(i)),()=>M(r(i).created_at)]),l(e,p)}),a(t),l(s,t)};R(lt,s=>{r(A)?s(vt):r(I).length===0?s(ct,1):s(pt,!1)})}a(Y);var H=o(Y,2),xt=o(n(H),2);{var mt=s=>{var t=zt();l(s,t)},_t=s=>{var t=Ct();L(t,21,()=>r(C),U,(e,i,p)=>{var g=Ut(),y=n(g);y.textContent=p+1;var T=o(y,2),f=n(T),$=n(f,!0);a(f);var k=o(f,2),b=n(k),h=n(b,!0);a(b);var z=o(b,2);{var w=x=>{var c=Dt(),P=n(c);a(c),u(B=>d(P,`${B??""}% retention`),[()=>(Number(r(i).retention)*100).toFixed(0)]),l(x,c)};R(z,x=>{r(i).retention&&x(w)})}var G=o(z,2);{var O=x=>{var c=Lt(),P=n(c);a(c),u(()=>d(P,`${r(i).predictedNeed??""} need`)),l(x,c)};R(G,x=>{r(i).predictedNeed&&x(O)})}a(k),a(T),a(g),u(()=>{d($,r(i).content),d(h,r(i).nodeType)}),l(e,g)}),a(t),l(s,t)};R(xt,s=>{r(C).length===0?s(mt):s(_t,!1)})}a(H),a(j),u(()=>d(dt,`${r(I).length??""} intentions`)),l(tt,j),bt()}wt(["click"]);export{Wt as component};
|