ultron1 / website /src /pages /Memory.jsx
ghostdrive1's picture
v26: Chat page (cross-project persistent), Memory live, brain/__init__.py, Sidebar updated
9a87c53
Raw
History Blame Contribute Delete
11.6 kB
import { useState, useEffect, useCallback } from 'react';
import { api } from '../lib/api';
import { relativeTime } from '../lib/utils';
import Card from '../components/Card';
import Badge from '../components/Badge';
// Channel lookup: tab-name → Redis key prefix used by discord_bot.py
const TABS = ['STM', 'Lifecycle', 'LPM', 'MAGMA', 'Foresight'];
const SHARDS = Array.from({ length: 15 }, (_, i) => ({ id: i, fillPct: 0, vectors: 0 }));
// STM tab: reads live from /memory/stm/:channelId
function STMTab() {
const [channelId, setChannelId] = useState('');
const [inputId, setInputId] = useState('');
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const fetchSTM = useCallback(async (cid) => {
if (!cid) return;
setLoading(true); setError(null);
try { const d = await api.memory.stm(cid); setData(d); }
catch (e) { setError(e.message); }
finally { setLoading(false); }
}, []);
return (
<Card title="Short-Term Memory — Redis">
<div className="px-5 py-5 space-y-4">
<div className="flex gap-8">
{[['Window', '20 msgs'], ['TTL', '7200s'], ['Scope', 'per channel_id']].map(([k, v]) => (
<div key={k}>
<p className="text-[11px] font-mono text-muted uppercase tracking-wider">{k}</p>
<p className="text-sm font-medium text-ink mt-0.5">{v}</p>
</div>
))}
</div>
<div className="flex gap-2">
<input
className="flex-1 text-xs font-mono border border-border rounded px-3 py-2 bg-surface text-ink focus:outline-none focus:ring-1 focus:ring-ink/20"
placeholder="Discord channel ID"
value={inputId}
onChange={(e) => setInputId(e.target.value)}
/>
<button
onClick={() => { setChannelId(inputId.trim()); fetchSTM(inputId.trim()); }}
className="text-xs font-mono px-4 py-2 border border-border rounded hover:bg-bg transition-colors"
>
Load
</button>
</div>
{error && <p className="text-xs font-mono text-status-red">✗ {error}</p>}
{loading && <p className="text-xs font-mono text-muted">Loading...</p>}
{data && !loading && (
<div className="space-y-3">
<div className="flex gap-6">
<div>
<p className="text-[11px] font-mono text-muted">CONTEXT WINDOW</p>
<p className="text-sm font-bold text-ink">{data.context_window_count}</p>
</div>
<div>
<p className="text-[11px] font-mono text-muted">LIFECYCLE CELLS</p>
<p className="text-sm font-bold text-ink">{data.lifecycle_cell_count}</p>
</div>
</div>
{data.context_window?.length > 0 ? (
<div className="border border-border rounded-lg overflow-hidden">
{data.context_window.map((msg, i) => (
<div key={i} className="px-4 py-2.5 border-b border-border last:border-0 text-xs font-mono text-ink">
{msg}
</div>
))}
</div>
) : (
<div className="border border-dashed border-border rounded-lg py-6 text-center">
<p className="text-xs text-muted font-mono">No context. Send a Discord message to populate.</p>
</div>
)}
{data.lifecycle_cells?.length > 0 && (
<div>
<p className="text-[11px] font-mono text-muted uppercase tracking-widest mb-2">Lifecycle Cells</p>
{data.lifecycle_cells.map((cell, i) => (
<div key={i} className="border-b border-border last:border-0 py-2.5">
<div className="flex items-center gap-2">
<Badge variant="blue" dot>{cell.tier ?? 'stm'}</Badge>
<span className="text-[11px] font-mono text-muted">{cell.created_at ? relativeTime(cell.created_at) : ''}</span>
</div>
<p className="text-xs text-ink mt-1 leading-relaxed">{cell.content ?? ''}</p>
</div>
))}
</div>
)}
</div>
)}
{!data && !loading && !error && (
<div className="border border-dashed border-border rounded-lg py-8 text-center">
<p className="text-xs text-muted font-mono">Enter a Discord channel ID above to view STM.</p>
</div>
)}
</div>
</Card>
);
}
export default function Memory() {
const [tab, setTab] = useState('STM');
return (
<div className="space-y-6">
<div>
<h1 className="text-2xl font-bold tracking-tight">Memory</h1>
<p className="text-sm text-muted mt-0.5">STM (Redis) → Lifecycle → RAPTOR → Zilliz x15 unified pipeline.</p>
</div>
<div className="flex gap-0.5 p-1 bg-bg border border-border rounded-lg w-fit">
{TABS.map((t) => (
<button
key={t}
onClick={() => setTab(t)}
className={`px-4 py-1.5 text-sm rounded transition-colors font-mono ${
tab === t ? 'bg-surface text-ink shadow-sm font-medium border border-border' : 'text-muted hover:text-ink'
}`}
>
{t}
</button>
))}
</div>
{tab === 'STM' && <STMTab />}
{tab === 'Lifecycle' && (
<Card title="Lifecycle Engine — EverMemOS MemCell/MemScene/Foresight">
<div className="px-5 py-5 space-y-4">
<div className="flex gap-8">
{[['Status', 'Active'], ['Heat TTL', '6h'], ['Tiers', 'STM→MTM→LPM']].map(([k, v]) => (
<div key={k}>
<p className="text-[11px] font-mono text-muted uppercase tracking-wider">{k}</p>
<p className="text-sm font-medium text-ink mt-0.5">{v}</p>
</div>
))}
</div>
<div className="flex items-center gap-2 py-3 px-4 bg-bg rounded-lg">
{['MemCell', 'heat ↑', 'MemScene', 'abstract', 'Foresight'].map((label, i, arr) => (
<span key={label} className="flex items-center gap-2">
<span className={`text-xs font-mono ${
i % 2 === 0 ? 'border border-border rounded px-2.5 py-1 text-ink' : 'text-muted'
}`}>{label}</span>
{i < arr.length - 1 && i % 2 === 0 && <span className="text-muted text-xs"></span>}
</span>
))}
</div>
<p className="text-xs text-muted leading-relaxed">
lifecycle.py active. Ingests every Discord message + /infer call. MemCells promoted to MemScene
after heat threshold (6h TTL). Foresight signal feeds rd_loop.py proposal generation.
</p>
</div>
</Card>
)}
{tab === 'LPM' && (
<Card title="Long-Term Memory — Zilliz x15 + RAPTOR">
<div className="px-5 py-5">
<div className="flex gap-8 mb-5">
{[['Shards', '15'], ['Embedding', '384-dim'], ['Metric', 'COSINE'], ['Model', 'all-MiniLM-L6-v2']].map(([k, v]) => (
<div key={k}>
<p className="text-[11px] font-mono text-muted uppercase tracking-wider">{k}</p>
<p className="text-sm font-medium text-ink mt-0.5">{v}</p>
</div>
))}
</div>
<div className="grid grid-cols-5 gap-2">
{SHARDS.map((shard) => (
<div key={shard.id} className="border border-border rounded-lg p-3 text-center">
<div className="text-[10px] font-mono text-muted mb-2">shard {shard.id}</div>
<div className="h-10 bg-bg rounded overflow-hidden relative">
<div className="absolute bottom-0 left-0 right-0 bg-status-blue/20" style={{ height: `${shard.fillPct}%` }} />
<div className="absolute inset-0 flex items-center justify-center">
<span className="text-[10px] font-mono text-muted">{shard.fillPct}%</span>
</div>
</div>
</div>
))}
</div>
<p className="text-xs text-muted font-mono mt-3">Shard: hash(user_id) % 15. RAPTOR tree summarises clusters before upsert. Live fill % available after Zilliz stats endpoint added.</p>
</div>
</Card>
)}
{tab === 'MAGMA' && (
<Card title="MAGMA Graph — 4-Graph Layer">
<div className="px-5 py-5 space-y-4">
<div className="flex gap-8">
{[['Status', 'Active'], ['Backend', 'NetworkX + Redis'], ['Graphs', '4']].map(([k, v]) => (
<div key={k}>
<p className="text-[11px] font-mono text-muted uppercase tracking-wider">{k}</p>
<p className="text-sm font-medium text-ink mt-0.5">{v}</p>
</div>
))}
</div>
<div className="grid grid-cols-4 gap-3">
{[
{ name: 'SEMANTIC', desc: 'concept ↔ concept edges' },
{ name: 'TEMPORAL', desc: 'event ordering + time' },
{ name: 'CAUSAL', desc: 'cause → effect chains' },
{ name: 'ENTITY', desc: 'named entities + relations' },
].map(({ name, desc }) => (
<div key={name} className="border border-border rounded-lg p-4 text-center">
<p className="text-[11px] font-mono text-ink font-medium">{name}</p>
<p className="text-[10px] text-muted mt-1 leading-snug">{desc}</p>
</div>
))}
</div>
<p className="text-xs text-muted leading-relaxed">
magma_graph.py active. NetworkX in-memory + Redis persistence. D3 force-directed visualisation
available in next phase when graph query endpoint is added.
+45.5% reasoning accuracy (arXiv:2601.03236).
</p>
</div>
</Card>
)}
{tab === 'Foresight' && (
<Card title="Foresight — Lifecycle Prediction">
<div className="px-5 py-5 space-y-4">
<div className="flex gap-8">
{[['Status', 'Active'], ['Source', 'lifecycle.py'], ['Feeds', 'rd_loop.py']].map(([k, v]) => (
<div key={k}>
<p className="text-[11px] font-mono text-muted uppercase tracking-wider">{k}</p>
<p className="text-sm font-medium text-ink mt-0.5">{v}</p>
</div>
))}
</div>
<div className="flex items-center gap-2 py-3 px-4 bg-bg rounded-lg">
{['MemScene heat', '→', 'Foresight signal', '→', 'RDLoop.propose()', '→', 'Council debate', '→', 'Implement'].map((label, i) => (
<span key={i} className={`text-xs font-mono ${
label === '→' ? 'text-muted' : 'border border-border rounded px-2 py-1 text-ink'
}`}>{label}</span>
))}
</div>
<p className="text-xs text-muted leading-relaxed">
Foresight predicts what Ghost will need before asking. Drives autonomous R&D proposals
visible in Projects tab. Signal strength determined by MemScene heat score over 6h window.
</p>
</div>
</Card>
)}
</div>
);
}