feat: Vestige v2.0.0 "Cognitive Leap" — 3D dashboard, HyDE search, WebSocket events

The biggest release in Vestige history. Complete visual and cognitive overhaul.

Dashboard:
- SvelteKit 2 + Three.js 3D neural visualization at localhost:3927/dashboard
- 7 interactive pages: Graph, Memories, Timeline, Feed, Explore, Intentions, Stats
- WebSocket event bus with 16 event types, real-time 3D animations
- Bloom post-processing, GPU instanced rendering, force-directed layout
- Dream visualization mode, FSRS retention curves, command palette (Cmd+K)
- Keyboard shortcuts, responsive mobile layout, PWA installable
- Single binary deployment via include_dir! (22MB)

Engine:
- HyDE query expansion (intent classification + 3-5 semantic variants + centroid)
- fastembed 5.11 with optional Nomic v2 MoE + Qwen3 reranker + Metal GPU
- Emotional memory module (#29)
- Criterion benchmark suite

Backend:
- Axum WebSocket at /ws with heartbeat + event broadcast
- 7 new REST endpoints for cognitive operations
- Event emission from MCP tools via shared broadcast channel
- CORS for SvelteKit dev mode

Distribution:
- GitHub issue templates (bug report, feature request)
- CHANGELOG with comprehensive v2.0 release notes
- README updated with dashboard docs, architecture diagram, comparison table

734 tests passing, zero warnings, 22MB release binary.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Sam Valladares 2026-02-22 03:07:25 -06:00
parent 26cee040a5
commit c2d28f3433
321 changed files with 32695 additions and 4727 deletions

View file

@ -0,0 +1,44 @@
import { g as getContext, c as escape_html } from "../../chunks/index.js";
import "../../chunks/state.svelte.js";
import "@sveltejs/kit/internal";
import "../../chunks/exports.js";
import "../../chunks/utils2.js";
import { w as writable } from "../../chunks/index2.js";
import "@sveltejs/kit/internal/server";
import "../../chunks/root.js";
function create_updated_store() {
const { set, subscribe } = writable(false);
{
return {
subscribe,
// eslint-disable-next-line @typescript-eslint/require-await
check: async () => false
};
}
}
const stores = {
updated: /* @__PURE__ */ create_updated_store()
};
({
check: stores.updated.check
});
function context() {
return getContext("__request__");
}
const page$1 = {
get error() {
return context().page.error;
},
get status() {
return context().page.status;
}
};
const page = page$1;
function Error$1($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
$$renderer2.push(`<h1>${escape_html(page.status)}</h1> <p>${escape_html(page.error?.message)}</p>`);
});
}
export {
Error$1 as default
};

View file

@ -0,0 +1,8 @@
function _layout($$renderer, $$props) {
let { children } = $$props;
children($$renderer);
$$renderer.push(`<!---->`);
}
export {
_layout as default
};

View file

@ -0,0 +1,53 @@
import { e as ensure_array_like, b as attr_class, c as escape_html, a as attr, d as stringify } from "../../../../chunks/index.js";
function _page($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
let searchQuery = "";
let mode = "associations";
let importanceText = "";
const MODE_INFO = {
associations: {
icon: "◎",
desc: "Spreading activation — find related memories via graph traversal"
},
chains: {
icon: "⟿",
desc: "Build reasoning path from source to target memory"
},
bridges: {
icon: "⬡",
desc: "Find connecting memories between two concepts"
}
};
$$renderer2.push(`<div class="p-6 max-w-5xl mx-auto space-y-8"><h1 class="text-xl text-bright font-semibold">Explore Connections</h1> <div class="grid grid-cols-3 gap-2"><!--[-->`);
const each_array = ensure_array_like(["associations", "chains", "bridges"]);
for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
let m = each_array[$$index];
$$renderer2.push(`<button${attr_class(`flex flex-col items-center gap-1 p-3 rounded-lg text-sm transition ${stringify(mode === m ? "bg-synapse/15 text-synapse-glow border border-synapse/40" : "bg-surface/30 text-dim border border-subtle/20 hover:border-subtle/40")}`)}><span class="text-xl">${escape_html(MODE_INFO[m].icon)}</span> <span class="font-medium">${escape_html(m.charAt(0).toUpperCase() + m.slice(1))}</span> <span class="text-[10px] text-muted text-center">${escape_html(MODE_INFO[m].desc)}</span></button>`);
}
$$renderer2.push(`<!--]--></div> <div class="space-y-3"><label class="text-xs text-dim font-medium">Source Memory</label> <div class="flex gap-2"><input type="text" placeholder="Search for a memory to explore from..."${attr("value", searchQuery)} class="flex-1 px-4 py-2.5 bg-surface border border-subtle/40 rounded-lg text-text text-sm placeholder:text-muted focus:outline-none focus:border-synapse/60 transition"/> <button class="px-4 py-2.5 bg-synapse/20 border border-synapse/40 text-synapse-glow text-sm rounded-lg hover:bg-synapse/30 transition">Find</button></div></div> `);
{
$$renderer2.push("<!--[!-->");
}
$$renderer2.push(`<!--]--> `);
{
$$renderer2.push("<!--[!-->");
}
$$renderer2.push(`<!--]--> `);
{
$$renderer2.push("<!--[!-->");
}
$$renderer2.push(`<!--]--> <div class="pt-8 border-t border-subtle/20"><h2 class="text-lg text-bright font-semibold mb-4">Importance Scorer</h2> <p class="text-xs text-muted mb-3">4-channel neuroscience scoring: novelty, arousal, reward, attention</p> <textarea placeholder="Paste any text to score its importance..." class="w-full h-24 px-4 py-3 bg-surface border border-subtle/40 rounded-lg text-text text-sm placeholder:text-muted resize-none focus:outline-none focus:border-synapse/60 transition">`);
const $$body = escape_html(importanceText);
if ($$body) {
$$renderer2.push(`${$$body}`);
}
$$renderer2.push(`</textarea> <button class="mt-2 px-4 py-2 bg-dream/20 border border-dream/40 text-dream-glow text-sm rounded-lg hover:bg-dream/30 transition">Score</button> `);
{
$$renderer2.push("<!--[!-->");
}
$$renderer2.push(`<!--]--></div></div>`);
});
}
export {
_page as default
};

View file

@ -0,0 +1,85 @@
import { c as escape_html, s as store_get, e as ensure_array_like, ac as attr_style, d as stringify, f as unsubscribe_stores } from "../../../../chunks/index.js";
import { e as eventFeed } from "../../../../chunks/websocket.js";
import { E as EVENT_TYPE_COLORS } from "../../../../chunks/index3.js";
function _page($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
var $$store_subs;
function formatTime(ts) {
return new Date(ts).toLocaleTimeString();
}
function eventIcon(type) {
const icons = {
MemoryCreated: "+",
MemoryUpdated: "~",
MemoryDeleted: "×",
MemoryPromoted: "↑",
MemoryDemoted: "↓",
SearchPerformed: "◎",
DreamStarted: "◈",
DreamProgress: "◈",
DreamCompleted: "◈",
ConsolidationStarted: "◉",
ConsolidationCompleted: "◉",
RetentionDecayed: "↘",
ConnectionDiscovered: "━",
ActivationSpread: "◬",
ImportanceScored: "◫",
Heartbeat: "♡"
};
return icons[type] || "·";
}
function eventSummary(event) {
const d = event.data;
switch (event.type) {
case "MemoryCreated":
return `New ${d.node_type}: "${String(d.content_preview).slice(0, 60)}..."`;
case "SearchPerformed":
return `Searched "${d.query}" → ${d.result_count} results (${d.duration_ms}ms)`;
case "DreamStarted":
return `Dream started with ${d.memory_count} memories`;
case "DreamCompleted":
return `Dream complete: ${d.connections_found} connections, ${d.insights_generated} insights (${d.duration_ms}ms)`;
case "ConsolidationStarted":
return "Consolidation cycle started";
case "ConsolidationCompleted":
return `Consolidated ${d.nodes_processed} nodes, ${d.decay_applied} decayed (${d.duration_ms}ms)`;
case "ConnectionDiscovered":
return `Connection: ${String(d.connection_type)} (weight: ${Number(d.weight).toFixed(2)})`;
case "ImportanceScored":
return `Scored ${Number(d.composite_score).toFixed(2)}: "${String(d.content_preview).slice(0, 50)}..."`;
case "MemoryPromoted":
return `Promoted → ${(Number(d.new_retention) * 100).toFixed(0)}% retention`;
case "MemoryDemoted":
return `Demoted → ${(Number(d.new_retention) * 100).toFixed(0)}% retention`;
default:
return JSON.stringify(d).slice(0, 100);
}
}
$$renderer2.push(`<div class="p-6 max-w-4xl mx-auto space-y-6"><div class="flex items-center justify-between"><h1 class="text-xl text-bright font-semibold">Live Feed</h1> <div class="flex gap-3"><span class="text-dim text-sm">${escape_html(store_get($$store_subs ??= {}, "$eventFeed", eventFeed).length)} events</span> <button class="text-xs text-muted hover:text-text transition">Clear</button></div></div> `);
if (store_get($$store_subs ??= {}, "$eventFeed", eventFeed).length === 0) {
$$renderer2.push("<!--[-->");
$$renderer2.push(`<div class="text-center py-20 text-dim"><div class="text-4xl mb-4">◉</div> <p>Waiting for cognitive events...</p> <p class="text-sm text-muted mt-2">Events appear here in real-time as Vestige thinks.</p></div>`);
} else {
$$renderer2.push("<!--[!-->");
$$renderer2.push(`<div class="space-y-2"><!--[-->`);
const each_array = ensure_array_like(store_get($$store_subs ??= {}, "$eventFeed", eventFeed));
for (let i = 0, $$length = each_array.length; i < $$length; i++) {
let event = each_array[i];
$$renderer2.push(`<div class="flex items-start gap-3 p-3 bg-surface/40 border border-subtle/15 rounded-lg hover:border-subtle/30 transition-all duration-200"${attr_style(`border-left: 3px solid ${stringify(EVENT_TYPE_COLORS[event.type] || "#6b7280")}`)}><div class="w-6 h-6 rounded flex items-center justify-center text-xs flex-shrink-0"${attr_style(`background: ${stringify(EVENT_TYPE_COLORS[event.type] || "#6b7280")}20; color: ${stringify(EVENT_TYPE_COLORS[event.type] || "#6b7280")}`)}>${escape_html(eventIcon(event.type))}</div> <div class="flex-1 min-w-0"><div class="flex items-center gap-2 mb-0.5"><span class="text-xs font-medium"${attr_style(`color: ${stringify(EVENT_TYPE_COLORS[event.type] || "#6b7280")}`)}>${escape_html(event.type)}</span> `);
if (event.data.timestamp) {
$$renderer2.push("<!--[-->");
$$renderer2.push(`<span class="text-xs text-muted">${escape_html(formatTime(String(event.data.timestamp)))}</span>`);
} else {
$$renderer2.push("<!--[!-->");
}
$$renderer2.push(`<!--]--></div> <p class="text-sm text-dim">${escape_html(eventSummary(event))}</p></div></div>`);
}
$$renderer2.push(`<!--]--></div>`);
}
$$renderer2.push(`<!--]--></div>`);
if ($$store_subs) unsubscribe_stores($$store_subs);
});
}
export {
_page as default
};

View file

@ -0,0 +1,103 @@
import { ad as ssr_context, a as attr, b as attr_class, c as escape_html, s as store_get, f as unsubscribe_stores, d as stringify } from "../../../../chunks/index.js";
import { a as api } from "../../../../chunks/api.js";
import { e as eventFeed } from "../../../../chunks/websocket.js";
function onDestroy(fn) {
/** @type {SSRContext} */
ssr_context.r.on_destroy(fn);
}
function Graph3D($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
let animationId;
onDestroy(() => {
cancelAnimationFrame(animationId);
window.removeEventListener("resize", onResize);
});
function onResize() {
return;
}
$$renderer2.push(`<div class="w-full h-full"></div>`);
});
}
function _page($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
var $$store_subs;
let graphData = null;
let loading = true;
let error = "";
let isDreaming = false;
let searchQuery = "";
let maxNodes = 150;
async function loadGraph(query, centerId) {
loading = true;
error = "";
try {
graphData = await api.graph({
max_nodes: maxNodes,
depth: 3,
query: query || void 0,
center_id: centerId || void 0
});
} catch {
error = "No memories yet. Start using Vestige to populate your graph.";
} finally {
loading = false;
}
}
$$renderer2.push(`<div class="h-full relative">`);
if (loading) {
$$renderer2.push("<!--[-->");
$$renderer2.push(`<div class="h-full flex items-center justify-center"><div class="text-center space-y-4"><div class="w-16 h-16 mx-auto rounded-full border-2 border-synapse/30 border-t-synapse animate-spin"></div> <p class="text-dim text-sm">Loading memory graph...</p></div></div>`);
} else if (error) {
$$renderer2.push("<!--[1-->");
$$renderer2.push(`<div class="h-full flex items-center justify-center"><div class="text-center space-y-4 max-w-md px-8"><div class="text-5xl opacity-30">◎</div> <h2 class="text-xl text-bright">Your Mind Awaits</h2> <p class="text-dim text-sm">${escape_html(error)}</p></div></div>`);
} else if (graphData) {
$$renderer2.push("<!--[2-->");
Graph3D($$renderer2, {
nodes: graphData.nodes,
edges: graphData.edges,
centerId: graphData.center_id,
events: store_get($$store_subs ??= {}, "$eventFeed", eventFeed)
});
} else {
$$renderer2.push("<!--[!-->");
}
$$renderer2.push(`<!--]--> <div class="absolute top-4 left-4 right-4 z-10 flex items-center gap-3"><div class="flex gap-2 flex-1 max-w-md"><input type="text" placeholder="Center graph on..."${attr("value", searchQuery)} class="flex-1 px-3 py-2 bg-abyss/80 backdrop-blur-sm border border-subtle/30 rounded-lg text-text text-sm placeholder:text-muted focus:outline-none focus:border-synapse/50 transition"/> <button class="px-3 py-2 bg-synapse/20 border border-synapse/40 text-synapse-glow text-sm rounded-lg hover:bg-synapse/30 transition backdrop-blur-sm">Focus</button></div> <div class="flex gap-2 ml-auto">`);
$$renderer2.select(
{
value: maxNodes,
onchange: () => loadGraph(),
class: "px-2 py-2 bg-abyss/80 backdrop-blur-sm border border-subtle/30 rounded-lg text-dim text-xs"
},
($$renderer3) => {
$$renderer3.option({ value: 50 }, ($$renderer4) => {
$$renderer4.push(`50 nodes`);
});
$$renderer3.option({ value: 100 }, ($$renderer4) => {
$$renderer4.push(`100 nodes`);
});
$$renderer3.option({ value: 150 }, ($$renderer4) => {
$$renderer4.push(`150 nodes`);
});
$$renderer3.option({ value: 200 }, ($$renderer4) => {
$$renderer4.push(`200 nodes`);
});
}
);
$$renderer2.push(` <button${attr("disabled", isDreaming, true)}${attr_class(`px-4 py-2 rounded-lg bg-dream/20 border border-dream/40 text-dream-glow text-sm hover:bg-dream/30 transition-all backdrop-blur-sm disabled:opacity-50 ${stringify("")}`)}>${escape_html("◈ Dream")}</button> <button class="px-3 py-2 bg-abyss/80 backdrop-blur-sm border border-subtle/30 rounded-lg text-dim text-sm hover:text-text transition">↻</button></div></div> <div class="absolute bottom-4 left-4 z-10 text-xs text-dim backdrop-blur-sm bg-abyss/60 rounded-lg px-3 py-2 border border-subtle/20">`);
if (graphData) {
$$renderer2.push("<!--[-->");
$$renderer2.push(`<span>${escape_html(graphData.nodeCount)} nodes</span> <span class="mx-2 text-subtle">·</span> <span>${escape_html(graphData.edgeCount)} edges</span> <span class="mx-2 text-subtle">·</span> <span>depth ${escape_html(graphData.depth)}</span>`);
} else {
$$renderer2.push("<!--[!-->");
}
$$renderer2.push(`<!--]--></div> `);
{
$$renderer2.push("<!--[!-->");
}
$$renderer2.push(`<!--]--></div>`);
if ($$store_subs) unsubscribe_stores($$store_subs);
});
}
export {
_page as default
};

View file

@ -0,0 +1,57 @@
import { c as escape_html, e as ensure_array_like, b as attr_class, d as stringify } from "../../../../chunks/index.js";
function _page($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
let intentions = [];
let predictions = [];
let statusFilter = "active";
$$renderer2.push(`<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 &amp; Predictions</h1> <span class="text-xs text-muted">${escape_html(intentions.length)} intentions</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"><!--[-->`);
const each_array = ensure_array_like(["active", "fulfilled", "snoozed", "cancelled", "all"]);
for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
let status = each_array[$$index];
$$renderer2.push(`<button${attr_class(`px-3 py-1.5 rounded-lg text-xs transition ${stringify(statusFilter === status ? "bg-synapse/20 text-synapse-glow border border-synapse/40" : "bg-surface/40 text-dim border border-subtle/20 hover:border-subtle/40")}`)}>${escape_html(status.charAt(0).toUpperCase() + status.slice(1))}</button>`);
}
$$renderer2.push(`<!--]--></div> `);
{
$$renderer2.push("<!--[-->");
$$renderer2.push(`<div class="space-y-2"><!--[-->`);
const each_array_1 = ensure_array_like(Array(4));
for (let $$index_1 = 0, $$length = each_array_1.length; $$index_1 < $$length; $$index_1++) {
each_array_1[$$index_1];
$$renderer2.push(`<div class="h-16 bg-surface/50 rounded-lg animate-pulse"></div>`);
}
$$renderer2.push(`<!--]--></div>`);
}
$$renderer2.push(`<!--]--></div> <div class="pt-6 border-t border-subtle/20 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> `);
if (predictions.length === 0) {
$$renderer2.push("<!--[-->");
$$renderer2.push(`<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>`);
} else {
$$renderer2.push("<!--[!-->");
$$renderer2.push(`<div class="space-y-2"><!--[-->`);
const each_array_3 = ensure_array_like(predictions);
for (let i = 0, $$length = each_array_3.length; i < $$length; i++) {
let pred = each_array_3[i];
$$renderer2.push(`<div class="p-3 bg-surface/40 border border-subtle/20 rounded-lg 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">${escape_html(i + 1)}</div> <div class="flex-1 min-w-0"><p class="text-sm text-text line-clamp-2">${escape_html(pred.content)}</p> <div class="flex gap-3 mt-1 text-xs text-muted"><span>${escape_html(pred.nodeType)}</span> `);
if (pred.retention) {
$$renderer2.push("<!--[-->");
$$renderer2.push(`<span>${escape_html((Number(pred.retention) * 100).toFixed(0))}% retention</span>`);
} else {
$$renderer2.push("<!--[!-->");
}
$$renderer2.push(`<!--]--> `);
if (pred.predictedNeed) {
$$renderer2.push("<!--[-->");
$$renderer2.push(`<span class="text-dream-glow">${escape_html(pred.predictedNeed)} need</span>`);
} else {
$$renderer2.push("<!--[!-->");
}
$$renderer2.push(`<!--]--></div></div></div>`);
}
$$renderer2.push(`<!--]--></div>`);
}
$$renderer2.push(`<!--]--></div></div>`);
});
}
export {
_page as default
};

View file

@ -0,0 +1,109 @@
import { c as escape_html, a as attr, e as ensure_array_like, b as attr_class, d as stringify, ac as attr_style } from "../../../../chunks/index.js";
import { a as api } from "../../../../chunks/api.js";
import { N as NODE_TYPE_COLORS } from "../../../../chunks/index3.js";
function _page($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
let memories = [];
let searchQuery = "";
let selectedType = "";
let selectedTag = "";
let minRetention = 0;
let loading = true;
let selectedMemory = null;
async function loadMemories() {
loading = true;
try {
const params = {};
if (searchQuery) ;
if (selectedType) ;
if (selectedTag) ;
if (minRetention > 0) ;
const res = await api.memories.list(params);
memories = res.memories;
} catch {
memories = [];
} finally {
loading = false;
}
}
function retentionColor(r) {
if (r > 0.7) return "#10b981";
if (r > 0.4) return "#f59e0b";
return "#ef4444";
}
$$renderer2.push(`<div class="p-6 max-w-6xl mx-auto space-y-6"><div class="flex items-center justify-between"><h1 class="text-xl text-bright font-semibold">Memories</h1> <span class="text-dim text-sm">${escape_html(memories.length)} results</span></div> <div class="flex gap-3 flex-wrap"><input type="text" placeholder="Search memories..."${attr("value", searchQuery)} class="flex-1 min-w-64 px-4 py-2.5 bg-surface border border-subtle/40 rounded-lg text-text text-sm placeholder:text-muted focus:outline-none focus:border-synapse/60 focus:ring-1 focus:ring-synapse/30 transition"/> `);
$$renderer2.select(
{
value: selectedType,
onchange: loadMemories,
class: "px-3 py-2.5 bg-surface border border-subtle/40 rounded-lg text-dim text-sm focus:outline-none"
},
($$renderer3) => {
$$renderer3.option({ value: "" }, ($$renderer4) => {
$$renderer4.push(`All types`);
});
$$renderer3.option({ value: "fact" }, ($$renderer4) => {
$$renderer4.push(`Fact`);
});
$$renderer3.option({ value: "concept" }, ($$renderer4) => {
$$renderer4.push(`Concept`);
});
$$renderer3.option({ value: "event" }, ($$renderer4) => {
$$renderer4.push(`Event`);
});
$$renderer3.option({ value: "person" }, ($$renderer4) => {
$$renderer4.push(`Person`);
});
$$renderer3.option({ value: "place" }, ($$renderer4) => {
$$renderer4.push(`Place`);
});
$$renderer3.option({ value: "note" }, ($$renderer4) => {
$$renderer4.push(`Note`);
});
$$renderer3.option({ value: "pattern" }, ($$renderer4) => {
$$renderer4.push(`Pattern`);
});
$$renderer3.option({ value: "decision" }, ($$renderer4) => {
$$renderer4.push(`Decision`);
});
}
);
$$renderer2.push(` <div class="flex items-center gap-2 text-xs text-dim"><span>Min retention:</span> <input type="range" min="0" max="1" step="0.1"${attr("value", minRetention)} class="w-24 accent-synapse"/> <span>${escape_html((minRetention * 100).toFixed(0))}%</span></div></div> `);
if (loading) {
$$renderer2.push("<!--[-->");
$$renderer2.push(`<div class="grid gap-3"><!--[-->`);
const each_array = ensure_array_like(Array(8));
for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
each_array[$$index];
$$renderer2.push(`<div class="h-24 bg-surface/50 rounded-lg animate-pulse"></div>`);
}
$$renderer2.push(`<!--]--></div>`);
} else {
$$renderer2.push("<!--[!-->");
$$renderer2.push(`<div class="grid gap-3"><!--[-->`);
const each_array_1 = ensure_array_like(memories);
for (let $$index_2 = 0, $$length = each_array_1.length; $$index_2 < $$length; $$index_2++) {
let memory = each_array_1[$$index_2];
$$renderer2.push(`<button${attr_class(`text-left p-4 bg-surface/50 border border-subtle/20 rounded-lg hover:border-synapse/30 hover:bg-surface transition-all duration-200 group ${stringify(selectedMemory?.id === memory.id ? "border-synapse/50 glow-synapse" : "")}`)}><div class="flex items-start justify-between gap-4"><div class="flex-1 min-w-0"><div class="flex items-center gap-2 mb-2"><span class="w-2 h-2 rounded-full"${attr_style(`background: ${stringify(NODE_TYPE_COLORS[memory.nodeType] || "#6b7280")}`)}></span> <span class="text-xs text-dim">${escape_html(memory.nodeType)}</span> <!--[-->`);
const each_array_2 = ensure_array_like(memory.tags.slice(0, 3));
for (let $$index_1 = 0, $$length2 = each_array_2.length; $$index_1 < $$length2; $$index_1++) {
let tag = each_array_2[$$index_1];
$$renderer2.push(`<span class="text-xs px-1.5 py-0.5 bg-deep rounded text-muted">${escape_html(tag)}</span>`);
}
$$renderer2.push(`<!--]--></div> <p class="text-sm text-text leading-relaxed line-clamp-2">${escape_html(memory.content)}</p></div> <div class="flex flex-col items-end gap-1 flex-shrink-0"><div class="w-12 h-1.5 bg-deep rounded-full overflow-hidden"><div class="h-full rounded-full"${attr_style(`width: ${stringify(memory.retentionStrength * 100)}%; background: ${stringify(retentionColor(memory.retentionStrength))}`)}></div></div> <span class="text-xs text-muted">${escape_html((memory.retentionStrength * 100).toFixed(0))}%</span></div></div> `);
if (selectedMemory?.id === memory.id) {
$$renderer2.push("<!--[-->");
$$renderer2.push(`<div class="mt-4 pt-4 border-t border-subtle/20 space-y-3"><p class="text-sm text-text whitespace-pre-wrap">${escape_html(memory.content)}</p> <div class="grid grid-cols-3 gap-3 text-xs text-dim"><div>Storage: ${escape_html((memory.storageStrength * 100).toFixed(1))}%</div> <div>Retrieval: ${escape_html((memory.retrievalStrength * 100).toFixed(1))}%</div> <div>Created: ${escape_html(new Date(memory.createdAt).toLocaleDateString())}</div></div> <div class="flex gap-2"><button class="px-3 py-1.5 bg-recall/20 text-recall text-xs rounded hover:bg-recall/30">Promote</button> <button class="px-3 py-1.5 bg-decay/20 text-decay text-xs rounded hover:bg-decay/30">Demote</button> <button class="px-3 py-1.5 bg-decay/10 text-decay/60 text-xs rounded hover:bg-decay/20 ml-auto">Delete</button></div></div>`);
} else {
$$renderer2.push("<!--[!-->");
}
$$renderer2.push(`<!--]--></button>`);
}
$$renderer2.push(`<!--]--></div>`);
}
$$renderer2.push(`<!--]--></div>`);
});
}
export {
_page as default
};

View file

@ -0,0 +1,51 @@
import { c as escape_html, s as store_get, ac as attr_style, d as stringify, b as attr_class, a as attr, e as ensure_array_like, f as unsubscribe_stores } from "../../../../chunks/index.js";
import { m as memoryCount, a as avgRetention, i as isConnected } from "../../../../chunks/websocket.js";
function _page($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
var $$store_subs;
let consolidating = false;
let dreaming = false;
$$renderer2.push(`<div class="p-6 max-w-4xl mx-auto space-y-8"><div class="flex items-center justify-between"><h1 class="text-xl text-bright font-semibold">Settings &amp; System</h1> <button class="text-xs text-dim hover:text-text transition">Refresh</button></div> <div class="grid grid-cols-2 md:grid-cols-4 gap-3"><div class="p-4 bg-surface/30 border border-subtle/20 rounded-lg text-center"><div class="text-2xl text-bright font-bold">${escape_html(store_get($$store_subs ??= {}, "$memoryCount", memoryCount))}</div> <div class="text-xs text-dim mt-1">Memories</div></div> <div class="p-4 bg-surface/30 border border-subtle/20 rounded-lg text-center"><div class="text-2xl font-bold"${attr_style(`color: ${stringify(store_get($$store_subs ??= {}, "$avgRetention", avgRetention) > 0.7 ? "#10b981" : store_get($$store_subs ??= {}, "$avgRetention", avgRetention) > 0.4 ? "#f59e0b" : "#ef4444")}`)}>${escape_html((store_get($$store_subs ??= {}, "$avgRetention", avgRetention) * 100).toFixed(1))}%</div> <div class="text-xs text-dim mt-1">Avg Retention</div></div> <div class="p-4 bg-surface/30 border border-subtle/20 rounded-lg text-center"><div class="text-2xl text-bright font-bold flex items-center justify-center gap-2"><div${attr_class(`w-2.5 h-2.5 rounded-full ${stringify(store_get($$store_subs ??= {}, "$isConnected", isConnected) ? "bg-recall animate-pulse-glow" : "bg-decay")}`)}></div> <span class="text-sm">${escape_html(store_get($$store_subs ??= {}, "$isConnected", isConnected) ? "Online" : "Offline")}</span></div> <div class="text-xs text-dim mt-1">WebSocket</div></div> <div class="p-4 bg-surface/30 border border-subtle/20 rounded-lg text-center"><div class="text-2xl text-synapse-glow font-bold">v2.0</div> <div class="text-xs text-dim mt-1">Vestige</div></div></div> <section class="space-y-4"><h2 class="text-sm text-bright font-semibold flex items-center gap-2"><span class="text-dream">◈</span> Cognitive Operations</h2> <div class="p-4 bg-surface/30 border border-subtle/20 rounded-lg space-y-3"><div class="flex items-center justify-between"><div><div class="text-sm text-text font-medium">FSRS-6 Consolidation</div> <div class="text-xs text-dim">Apply spaced-repetition decay, regenerate embeddings, run maintenance</div></div> <button${attr("disabled", consolidating, true)} class="px-4 py-2 bg-warning/20 border border-warning/40 text-warning text-sm rounded-lg hover:bg-warning/30 transition disabled:opacity-50 flex items-center gap-2">`);
{
$$renderer2.push("<!--[!-->");
$$renderer2.push(`Consolidate`);
}
$$renderer2.push(`<!--]--></button></div> `);
{
$$renderer2.push("<!--[!-->");
}
$$renderer2.push(`<!--]--></div> <div class="p-4 bg-surface/30 border border-subtle/20 rounded-lg space-y-3"><div class="flex items-center justify-between"><div><div class="text-sm text-text font-medium">Memory Dream Cycle</div> <div class="text-xs text-dim">Replay memories, discover hidden connections, synthesize insights</div></div> <button${attr("disabled", dreaming, true)}${attr_class(`px-4 py-2 bg-dream/20 border border-dream/40 text-dream-glow text-sm rounded-lg hover:bg-dream/30 transition disabled:opacity-50 flex items-center gap-2 ${stringify("")}`)}>`);
{
$$renderer2.push("<!--[!-->");
$$renderer2.push(`Dream`);
}
$$renderer2.push(`<!--]--></button></div> `);
{
$$renderer2.push("<!--[!-->");
}
$$renderer2.push(`<!--]--></div></section> `);
{
$$renderer2.push("<!--[!-->");
}
$$renderer2.push(`<!--]--> <section class="space-y-4"><h2 class="text-sm text-bright font-semibold flex items-center gap-2"><span class="text-synapse">⌨</span> Keyboard Shortcuts</h2> <div class="p-4 bg-surface/30 border border-subtle/20 rounded-lg"><div class="grid grid-cols-2 gap-2 text-xs"><!--[-->`);
const each_array_2 = ensure_array_like([
{ key: "⌘ K", desc: "Command palette" },
{ key: "/", desc: "Focus search" },
{ key: "G", desc: "Go to Graph" },
{ key: "M", desc: "Go to Memories" },
{ key: "T", desc: "Go to Timeline" },
{ key: "F", desc: "Go to Feed" },
{ key: "E", desc: "Go to Explore" },
{ key: "S", desc: "Go to Stats" }
]);
for (let $$index_2 = 0, $$length = each_array_2.length; $$index_2 < $$length; $$index_2++) {
let shortcut = each_array_2[$$index_2];
$$renderer2.push(`<div class="flex items-center gap-2 py-1"><kbd class="px-1.5 py-0.5 bg-deep rounded text-[10px] font-mono text-muted min-w-[2rem] text-center">${escape_html(shortcut.key)}</kbd> <span class="text-dim">${escape_html(shortcut.desc)}</span></div>`);
}
$$renderer2.push(`<!--]--></div></div></section> <section class="space-y-4"><h2 class="text-sm text-bright font-semibold flex items-center gap-2"><span class="text-memory">◎</span> About</h2> <div class="p-4 bg-surface/30 border border-subtle/20 rounded-lg space-y-3"><div class="flex items-center gap-4"><div class="w-12 h-12 rounded-xl bg-gradient-to-br from-dream to-synapse flex items-center justify-center text-bright text-xl font-bold shadow-lg shadow-synapse/20">V</div> <div><div class="text-sm text-bright font-semibold">Vestige v2.0 "Cognitive Leap"</div> <div class="text-xs text-dim">Your AI's long-term memory system</div></div></div> <div class="grid grid-cols-2 gap-2 text-xs text-dim pt-2 border-t border-subtle/10"><div>29 cognitive modules</div> <div>FSRS-6 spaced repetition</div> <div>Nomic Embed v1.5 (256d)</div> <div>Jina Reranker v1 Turbo</div> <div>USearch HNSW (20x FAISS)</div> <div>Local-first, zero cloud</div></div> <div class="text-[10px] text-muted pt-1">Built with Rust + Axum + SvelteKit 2 + Svelte 5 + Three.js + Tailwind CSS 4</div></div></section></div>`);
if ($$store_subs) unsubscribe_stores($$store_subs);
});
}
export {
_page as default
};

View file

@ -0,0 +1,20 @@
import { e as ensure_array_like } from "../../../../chunks/index.js";
function _page($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
$$renderer2.push(`<div class="p-6 max-w-5xl mx-auto space-y-6"><h1 class="text-xl text-bright font-semibold">System Stats</h1> `);
{
$$renderer2.push("<!--[-->");
$$renderer2.push(`<div class="grid grid-cols-2 lg:grid-cols-4 gap-4"><!--[-->`);
const each_array = ensure_array_like(Array(8));
for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
each_array[$$index];
$$renderer2.push(`<div class="h-24 bg-surface/50 rounded-lg animate-pulse"></div>`);
}
$$renderer2.push(`<!--]--></div>`);
}
$$renderer2.push(`<!--]--></div>`);
});
}
export {
_page as default
};

View file

@ -0,0 +1,97 @@
import { e as ensure_array_like, c as escape_html, ac as attr_style, d as stringify } from "../../../../chunks/index.js";
import { a as api } from "../../../../chunks/api.js";
import { N as NODE_TYPE_COLORS } from "../../../../chunks/index3.js";
function _page($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
let timeline = [];
let loading = true;
let days = 14;
let expandedDay = null;
async function loadTimeline() {
loading = true;
try {
const res = await api.timeline(days, 500);
timeline = res.timeline;
} catch {
timeline = [];
} finally {
loading = false;
}
}
$$renderer2.push(`<div class="p-6 max-w-4xl mx-auto space-y-6"><div class="flex items-center justify-between"><h1 class="text-xl text-bright font-semibold">Timeline</h1> `);
$$renderer2.select(
{
value: days,
onchange: loadTimeline,
class: "px-3 py-2 bg-surface border border-subtle/40 rounded-lg text-dim text-sm"
},
($$renderer3) => {
$$renderer3.option({ value: 7 }, ($$renderer4) => {
$$renderer4.push(`7 days`);
});
$$renderer3.option({ value: 14 }, ($$renderer4) => {
$$renderer4.push(`14 days`);
});
$$renderer3.option({ value: 30 }, ($$renderer4) => {
$$renderer4.push(`30 days`);
});
$$renderer3.option({ value: 90 }, ($$renderer4) => {
$$renderer4.push(`90 days`);
});
}
);
$$renderer2.push(`</div> `);
if (loading) {
$$renderer2.push("<!--[-->");
$$renderer2.push(`<div class="space-y-4"><!--[-->`);
const each_array = ensure_array_like(Array(7));
for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
each_array[$$index];
$$renderer2.push(`<div class="h-16 bg-surface/50 rounded-lg animate-pulse"></div>`);
}
$$renderer2.push(`<!--]--></div>`);
} else if (timeline.length === 0) {
$$renderer2.push("<!--[1-->");
$$renderer2.push(`<div class="text-center py-20 text-dim"><p>No memories in the selected time range.</p></div>`);
} else {
$$renderer2.push("<!--[!-->");
$$renderer2.push(`<div class="relative"><div class="absolute left-6 top-0 bottom-0 w-px bg-subtle/30"></div> <div class="space-y-4"><!--[-->`);
const each_array_1 = ensure_array_like(timeline);
for (let $$index_3 = 0, $$length = each_array_1.length; $$index_3 < $$length; $$index_3++) {
let day = each_array_1[$$index_3];
$$renderer2.push(`<div class="relative pl-14"><div class="absolute left-4 top-3 w-5 h-5 rounded-full border-2 border-synapse bg-abyss flex items-center justify-center"><div class="w-2 h-2 rounded-full bg-synapse"></div></div> <button class="w-full text-left p-4 bg-surface/40 border border-subtle/20 rounded-lg hover:border-synapse/30 transition-all"><div class="flex items-center justify-between"><div><span class="text-sm text-bright font-medium">${escape_html(day.date)}</span> <span class="text-xs text-dim ml-2">${escape_html(day.count)} memories</span></div> <div class="flex gap-1"><!--[-->`);
const each_array_2 = ensure_array_like(day.memories.slice(0, 10));
for (let $$index_1 = 0, $$length2 = each_array_2.length; $$index_1 < $$length2; $$index_1++) {
let m = each_array_2[$$index_1];
$$renderer2.push(`<div class="w-2 h-2 rounded-full"${attr_style(`background: ${stringify(NODE_TYPE_COLORS[m.nodeType] || "#6b7280")}; opacity: ${stringify(0.3 + m.retentionStrength * 0.7)}`)}></div>`);
}
$$renderer2.push(`<!--]--> `);
if (day.memories.length > 10) {
$$renderer2.push("<!--[-->");
$$renderer2.push(`<span class="text-xs text-muted">+${escape_html(day.memories.length - 10)}</span>`);
} else {
$$renderer2.push("<!--[!-->");
}
$$renderer2.push(`<!--]--></div></div> `);
if (expandedDay === day.date) {
$$renderer2.push("<!--[-->");
$$renderer2.push(`<div class="mt-3 pt-3 border-t border-subtle/20 space-y-2"><!--[-->`);
const each_array_3 = ensure_array_like(day.memories);
for (let $$index_2 = 0, $$length2 = each_array_3.length; $$index_2 < $$length2; $$index_2++) {
let m = each_array_3[$$index_2];
$$renderer2.push(`<div class="flex items-start gap-2 text-sm"><div class="w-2 h-2 mt-1.5 rounded-full flex-shrink-0"${attr_style(`background: ${stringify(NODE_TYPE_COLORS[m.nodeType] || "#6b7280")}`)}></div> <div class="flex-1 min-w-0"><span class="text-dim line-clamp-1">${escape_html(m.content)}</span></div> <span class="text-xs text-muted flex-shrink-0">${escape_html((m.retentionStrength * 100).toFixed(0))}%</span></div>`);
}
$$renderer2.push(`<!--]--></div>`);
} else {
$$renderer2.push("<!--[!-->");
}
$$renderer2.push(`<!--]--></button></div>`);
}
$$renderer2.push(`<!--]--></div></div>`);
}
$$renderer2.push(`<!--]--></div>`);
});
}
export {
_page as default
};

View file

@ -0,0 +1,97 @@
import { g as getContext, e as ensure_array_like, s as store_get, a as attr, b as attr_class, c as escape_html, d as stringify, f as unsubscribe_stores } from "../../chunks/index.js";
import "@sveltejs/kit/internal";
import "../../chunks/exports.js";
import "../../chunks/utils2.js";
import "@sveltejs/kit/internal/server";
import "../../chunks/root.js";
import "../../chunks/state.svelte.js";
import { b as base } from "../../chunks/server.js";
import { i as isConnected, m as memoryCount, a as avgRetention } from "../../chunks/websocket.js";
const getStores = () => {
const stores$1 = getContext("__svelte__");
return {
/** @type {typeof page} */
page: {
subscribe: stores$1.page.subscribe
},
/** @type {typeof navigating} */
navigating: {
subscribe: stores$1.navigating.subscribe
},
/** @type {typeof updated} */
updated: stores$1.updated
};
};
const page = {
subscribe(fn) {
const store = getStores().page;
return store.subscribe(fn);
}
};
function _layout($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
var $$store_subs;
let { children } = $$props;
const nav = [
{ href: "/", label: "Graph", icon: "◎", shortcut: "G" },
{
href: "/memories",
label: "Memories",
icon: "◈",
shortcut: "M"
},
{
href: "/timeline",
label: "Timeline",
icon: "◷",
shortcut: "T"
},
{ href: "/feed", label: "Feed", icon: "◉", shortcut: "F" },
{ href: "/explore", label: "Explore", icon: "◬", shortcut: "E" },
{
href: "/intentions",
label: "Intentions",
icon: "◇",
shortcut: "I"
},
{ href: "/stats", label: "Stats", icon: "◫", shortcut: "S" },
{
href: "/settings",
label: "Settings",
icon: "⚙",
shortcut: ","
}
];
const mobileNav = nav.slice(0, 5);
function isActive(href, currentPath) {
const path = currentPath.startsWith(base) ? currentPath.slice(base.length) || "/" : currentPath;
if (href === "/") return path === "/" || path === "/graph";
return path.startsWith(href);
}
$$renderer2.push(`<div class="flex flex-col md:flex-row h-screen overflow-hidden bg-void"><nav class="hidden md:flex w-16 lg:w-56 flex-shrink-0 bg-abyss border-r border-subtle/30 flex-col"><a href="/" class="flex items-center gap-3 px-4 py-5 border-b border-subtle/20"><div class="w-8 h-8 rounded-lg bg-gradient-to-br from-dream to-synapse flex items-center justify-center text-bright text-sm font-bold">V</div> <span class="hidden lg:block text-sm font-semibold text-bright tracking-wide">VESTIGE</span></a> <div class="flex-1 py-3 flex flex-col gap-1 px-2"><!--[-->`);
const each_array = ensure_array_like(nav);
for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
let item = each_array[$$index];
const active = isActive(item.href, store_get($$store_subs ??= {}, "$page", page).url.pathname);
$$renderer2.push(`<a${attr("href", item.href)}${attr_class(`flex items-center gap-3 px-3 py-2.5 rounded-lg transition-all duration-200 text-sm ${stringify(active ? "bg-synapse/15 text-synapse-glow border border-synapse/30 shadow-[0_0_12px_rgba(99,102,241,0.15)]" : "text-dim hover:text-text hover:bg-surface border border-transparent")}`)}><span class="text-base w-5 text-center">${escape_html(item.icon)}</span> <span class="hidden lg:block">${escape_html(item.label)}</span> <span class="hidden lg:block ml-auto text-[10px] text-muted/50 font-mono">${escape_html(item.shortcut)}</span></a>`);
}
$$renderer2.push(`<!--]--></div> <div class="px-2 pb-2"><button class="w-full flex items-center gap-2 px-3 py-2 rounded-lg text-xs text-muted hover:text-dim hover:bg-surface/50 transition border border-subtle/20"><span class="text-[10px] font-mono bg-surface/60 px-1.5 py-0.5 rounded">⌘K</span> <span class="hidden lg:block">Command</span></button></div> <div class="px-3 py-4 border-t border-subtle/20 space-y-2"><div class="flex items-center gap-2 text-xs"><div${attr_class(`w-2 h-2 rounded-full ${stringify(store_get($$store_subs ??= {}, "$isConnected", isConnected) ? "bg-recall animate-pulse-glow" : "bg-decay")}`)}></div> <span class="hidden lg:block text-dim">${escape_html(store_get($$store_subs ??= {}, "$isConnected", isConnected) ? "Connected" : "Offline")}</span></div> <div class="hidden lg:block text-xs text-muted"><div>${escape_html(store_get($$store_subs ??= {}, "$memoryCount", memoryCount))} memories</div> <div>${escape_html((store_get($$store_subs ??= {}, "$avgRetention", avgRetention) * 100).toFixed(0))}% retention</div></div></div></nav> <main class="flex-1 overflow-y-auto pb-16 md:pb-0"><div class="animate-page-in svelte-12qhfyh">`);
children($$renderer2);
$$renderer2.push(`<!----></div></main> <nav class="md:hidden fixed bottom-0 inset-x-0 bg-abyss/95 backdrop-blur-xl border-t border-subtle/30 z-40 safe-bottom svelte-12qhfyh"><div class="flex items-center justify-around px-2 py-1"><!--[-->`);
const each_array_1 = ensure_array_like(mobileNav);
for (let $$index_1 = 0, $$length = each_array_1.length; $$index_1 < $$length; $$index_1++) {
let item = each_array_1[$$index_1];
const active = isActive(item.href, store_get($$store_subs ??= {}, "$page", page).url.pathname);
$$renderer2.push(`<a${attr("href", item.href)}${attr_class(`flex flex-col items-center gap-0.5 px-3 py-2 rounded-lg transition-all min-w-[3.5rem] ${stringify(active ? "text-synapse-glow" : "text-muted")}`)}><span class="text-lg">${escape_html(item.icon)}</span> <span class="text-[9px]">${escape_html(item.label)}</span></a>`);
}
$$renderer2.push(`<!--]--> <button class="flex flex-col items-center gap-0.5 px-3 py-2 rounded-lg text-muted min-w-[3.5rem]"><span class="text-lg">⋯</span> <span class="text-[9px]">More</span></button></div></nav></div> `);
{
$$renderer2.push("<!--[!-->");
}
$$renderer2.push(`<!--]-->`);
if ($$store_subs) unsubscribe_stores($$store_subs);
});
}
export {
_layout as default
};

View file

@ -0,0 +1,24 @@
import { a as attr, b as attr_class, c as escape_html, d as stringify } from "../../chunks/index.js";
import "../../chunks/websocket.js";
function _page($$renderer, $$props) {
$$renderer.component(($$renderer2) => {
let isDreaming = false;
$$renderer2.push(`<div class="h-full relative">`);
{
$$renderer2.push("<!--[-->");
$$renderer2.push(`<div class="h-full flex items-center justify-center"><div class="text-center space-y-4"><div class="w-16 h-16 mx-auto rounded-full border-2 border-synapse/30 border-t-synapse animate-spin"></div> <p class="text-dim text-sm">Loading memory graph...</p></div></div>`);
}
$$renderer2.push(`<!--]--> <div class="absolute top-4 left-4 flex gap-2 z-10"><button${attr("disabled", isDreaming, true)}${attr_class(`px-4 py-2 rounded-lg bg-dream/20 border border-dream/40 text-dream-glow text-sm hover:bg-dream/30 transition-all disabled:opacity-50 backdrop-blur-sm ${stringify("")}`)}>${escape_html("◎ Dream")}</button></div> <div class="absolute top-4 right-4 z-10 text-xs text-dim backdrop-blur-sm bg-abyss/60 rounded-lg px-3 py-2 border border-subtle/20">`);
{
$$renderer2.push("<!--[!-->");
}
$$renderer2.push(`<!--]--></div> `);
{
$$renderer2.push("<!--[!-->");
}
$$renderer2.push(`<!--]--></div>`);
});
}
export {
_page as default
};