superlocalmemory 3.7.7 → 3.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/ATTRIBUTION.md +1 -3
- package/CHANGELOG.md +85 -0
- package/README.md +199 -29
- package/package.json +4 -2
- package/plugin/.claude-plugin/plugin.json +2 -2
- package/plugin/CLAUDE.md +8 -8
- package/plugin/agents/slm-governance-advisor.md +80 -0
- package/plugin/agents/slm-loop-runner.md +71 -0
- package/plugin/agents/slm-memory-advisor.md +10 -5
- package/plugin/agents/slm-optimize-advisor.md +9 -3
- package/plugin/commands/slm-loop.md +31 -0
- package/plugin/hooks/hooks.json +79 -0
- package/plugin/requirements.txt +1 -1
- package/plugin/scripts/slm-launch +46 -7
- package/plugin/settings.json +9 -0
- package/plugin/skills/slm-cache/SKILL.md +9 -1
- package/plugin/skills/slm-compress/SKILL.md +8 -1
- package/plugin/skills/slm-governance/SKILL.md +248 -0
- package/plugin/skills/slm-graph/SKILL.md +17 -3
- package/plugin/skills/slm-loop/SKILL.md +99 -0
- package/plugin/skills/slm-mesh/SKILL.md +282 -0
- package/plugin/skills/slm-profile/SKILL.md +148 -0
- package/plugin/skills/slm-recall/SKILL.md +46 -10
- package/plugin/skills/slm-remember/SKILL.md +48 -1
- package/plugin/skills/slm-scope/SKILL.md +176 -0
- package/plugin/skills/slm-session/SKILL.md +24 -1
- package/plugin/skills/slm-status/SKILL.md +18 -1
- package/plugin-src/agents/slm-governance-advisor.md +80 -0
- package/plugin-src/agents/slm-loop-runner.md +71 -0
- package/plugin-src/agents/slm-memory-advisor.md +10 -5
- package/plugin-src/agents/slm-optimize-advisor.md +9 -3
- package/plugin-src/commands/slm-loop.md +31 -0
- package/plugin-src/hooks/hooks.json +79 -0
- package/plugin-src/manifest.json +7 -2
- package/plugin-src/requirements.txt +1 -1
- package/plugin-src/rules/AGENTS.md +57 -18
- package/plugin-src/rules/CLAUDE.md.fragment +8 -8
- package/plugin-src/scripts/slm-launch +46 -7
- package/plugin-src/settings.json +9 -0
- package/plugin-src/skills/slm-cache/SKILL.md +9 -1
- package/plugin-src/skills/slm-compress/SKILL.md +8 -1
- package/plugin-src/skills/slm-governance/SKILL.md +248 -0
- package/plugin-src/skills/slm-graph/SKILL.md +17 -3
- package/plugin-src/skills/slm-loop/SKILL.md +99 -0
- package/plugin-src/skills/slm-mesh/SKILL.md +282 -0
- package/plugin-src/skills/slm-profile/SKILL.md +148 -0
- package/plugin-src/skills/slm-recall/SKILL.md +46 -10
- package/plugin-src/skills/slm-remember/SKILL.md +48 -1
- package/plugin-src/skills/slm-scope/SKILL.md +176 -0
- package/plugin-src/skills/slm-session/SKILL.md +24 -1
- package/plugin-src/skills/slm-status/SKILL.md +18 -1
- package/pyproject.toml +1 -2
- package/scripts/postinstall/validation.js +2 -0
- package/scripts/postinstall-interactive.js +74 -2
- package/src/superlocalmemory/__init__.py +1 -1
- package/src/superlocalmemory/access/__init__.py +3 -0
- package/src/superlocalmemory/access/rbac.py +477 -0
- package/src/superlocalmemory/cli/commands.py +96 -12
- package/src/superlocalmemory/cli/compress_cmd.py +17 -7
- package/src/superlocalmemory/cli/loop_cmd.py +192 -0
- package/src/superlocalmemory/cli/main.py +39 -4
- package/src/superlocalmemory/cli/mesh_cmd.py +38 -0
- package/src/superlocalmemory/cli/optimize_cmd.py +3 -0
- package/src/superlocalmemory/cli/pending_store.py +49 -13
- package/src/superlocalmemory/cli/proxy_cmd.py +4 -0
- package/src/superlocalmemory/cli/scale_engine_cmd.py +6 -0
- package/src/superlocalmemory/cli/setup_wizard.py +22 -13
- package/src/superlocalmemory/compliance/audit.py +6 -0
- package/src/superlocalmemory/compliance/gdpr.py +128 -138
- package/src/superlocalmemory/compliance/retention.py +176 -45
- package/src/superlocalmemory/core/backend_orchestrator.py +5 -43
- package/src/superlocalmemory/core/community_summary.py +267 -0
- package/src/superlocalmemory/core/config.py +216 -3
- package/src/superlocalmemory/core/consolidation_engine.py +95 -22
- package/src/superlocalmemory/core/context_cache.py +61 -18
- package/src/superlocalmemory/core/embedding_worker.py +17 -2
- package/src/superlocalmemory/core/embeddings.py +12 -1
- package/src/superlocalmemory/core/engine.py +17 -1
- package/src/superlocalmemory/core/engine_ingestion.py +29 -0
- package/src/superlocalmemory/core/engine_wiring.py +13 -0
- package/src/superlocalmemory/core/entity_community.py +178 -0
- package/src/superlocalmemory/core/graph_analyzer.py +39 -2
- package/src/superlocalmemory/core/graph_pruner.py +13 -8
- package/src/superlocalmemory/core/key_expander.py +138 -0
- package/src/superlocalmemory/core/maintenance.py +23 -0
- package/src/superlocalmemory/core/modes.py +1 -1
- package/src/superlocalmemory/core/mutations.py +2 -2
- package/src/superlocalmemory/core/pii.py +105 -0
- package/src/superlocalmemory/core/progressive_abstraction.py +208 -0
- package/src/superlocalmemory/core/recall_pipeline.py +2 -0
- package/src/superlocalmemory/core/recall_worker.py +20 -6
- package/src/superlocalmemory/core/scale_engine.py +60 -1
- package/src/superlocalmemory/core/security_primitives.py +40 -2
- package/src/superlocalmemory/core/store_pipeline.py +35 -11
- package/src/superlocalmemory/core/worker_pool.py +21 -6
- package/src/superlocalmemory/encoding/entity_reflexion.py +200 -0
- package/src/superlocalmemory/encoding/entity_resolver.py +34 -24
- package/src/superlocalmemory/encoding/fact_extractor.py +26 -1
- package/src/superlocalmemory/encoding/temporal_validator.py +64 -1
- package/src/superlocalmemory/evolution/evolution_store.py +122 -45
- package/src/superlocalmemory/evolution/llm_dispatch.py +12 -1
- package/src/superlocalmemory/evolution/model_selection.py +160 -0
- package/src/superlocalmemory/evolution/mutation_generator.py +16 -0
- package/src/superlocalmemory/evolution/skill_evolver.py +127 -42
- package/src/superlocalmemory/evolution/triggers.py +22 -13
- package/src/superlocalmemory/graph/cozo_backend.py +43 -20
- package/src/superlocalmemory/hooks/adapter_base.py +5 -1
- package/src/superlocalmemory/hooks/auto_recall.py +13 -1
- package/src/superlocalmemory/hooks/claude_code_hooks.py +11 -0
- package/src/superlocalmemory/hooks/codex_assets.py +64 -5
- package/src/superlocalmemory/hooks/hook_daemon.py +20 -3
- package/src/superlocalmemory/hooks/memory_protocol.py +54 -0
- package/src/superlocalmemory/hooks/portable_kit.py +114 -1
- package/src/superlocalmemory/infra/auth_middleware.py +28 -0
- package/src/superlocalmemory/infra/backup.py +12 -1
- package/src/superlocalmemory/infra/daemon_identity.py +40 -4
- package/src/superlocalmemory/infra/data_root.py +43 -4
- package/src/superlocalmemory/infra/event_bus.py +107 -24
- package/src/superlocalmemory/infra/rate_limiter.py +93 -0
- package/src/superlocalmemory/ingestion/adapter_manager.py +4 -1
- package/src/superlocalmemory/ingestion/credentials.py +1 -1
- package/src/superlocalmemory/learning/cross_project.py +28 -19
- package/src/superlocalmemory/learning/reward_proxy.py +42 -9
- package/src/superlocalmemory/loops/__init__.py +56 -0
- package/src/superlocalmemory/loops/budget.py +58 -0
- package/src/superlocalmemory/loops/engine.py +164 -0
- package/src/superlocalmemory/loops/ledger.py +243 -0
- package/src/superlocalmemory/loops/models.py +152 -0
- package/src/superlocalmemory/loops/rules.py +52 -0
- package/src/superlocalmemory/mcp/_daemon_proxy.py +3 -0
- package/src/superlocalmemory/mcp/_pool_adapter.py +2 -0
- package/src/superlocalmemory/mcp/profiles.py +103 -0
- package/src/superlocalmemory/mcp/server.py +21 -49
- package/src/superlocalmemory/mcp/tools_active.py +4 -7
- package/src/superlocalmemory/mcp/tools_code_graph.py +51 -5
- package/src/superlocalmemory/mcp/tools_core.py +50 -5
- package/src/superlocalmemory/mcp/tools_evolution.py +6 -3
- package/src/superlocalmemory/mcp/tools_loops.py +300 -0
- package/src/superlocalmemory/mcp/tools_mesh.py +140 -4
- package/src/superlocalmemory/mcp/tools_optimize.py +15 -8
- package/src/superlocalmemory/mesh/broker.py +237 -129
- package/src/superlocalmemory/mesh/remote_sync.py +50 -8
- package/src/superlocalmemory/optimize/NOTICE +1 -6
- package/src/superlocalmemory/optimize/adapters/anthropic_adapter.py +1 -4
- package/src/superlocalmemory/optimize/adapters/openai_adapter.py +1 -4
- package/src/superlocalmemory/optimize/cache/semantic.py +27 -19
- package/src/superlocalmemory/optimize/compress/align.py +32 -26
- package/src/superlocalmemory/optimize/compress/ccr.py +14 -71
- package/src/superlocalmemory/optimize/compress/router.py +105 -22
- package/src/superlocalmemory/optimize/config/defaults.py +1 -1
- package/src/superlocalmemory/optimize/config/schema.py +87 -4
- package/src/superlocalmemory/optimize/metrics/counters.py +13 -4
- package/src/superlocalmemory/optimize/metrics/estimator.py +0 -3
- package/src/superlocalmemory/optimize/proxy/_helpers.py +31 -4
- package/src/superlocalmemory/optimize/storage/db.py +38 -9
- package/src/superlocalmemory/optimize/storage/schema.py +10 -0
- package/src/superlocalmemory/parameterization/pattern_extractor.py +6 -3
- package/src/superlocalmemory/retrieval/agentic.py +1 -1
- package/src/superlocalmemory/retrieval/bm25_channel.py +68 -10
- package/src/superlocalmemory/retrieval/engine.py +168 -26
- package/src/superlocalmemory/retrieval/entity_channel.py +7 -5
- package/src/superlocalmemory/retrieval/hopfield_channel.py +9 -2
- package/src/superlocalmemory/retrieval/semantic_channel.py +114 -21
- package/src/superlocalmemory/retrieval/spreading_activation.py +11 -2
- package/src/superlocalmemory/retrieval/temporal_channel.py +48 -9
- package/src/superlocalmemory/retrieval/temporal_frame.py +102 -0
- package/src/superlocalmemory/retrieval/temporal_validity_filter.py +135 -0
- package/src/superlocalmemory/retrieval/time_window.py +181 -0
- package/src/superlocalmemory/server/api.py +21 -4
- package/src/superlocalmemory/server/profile_runtime.py +125 -8
- package/src/superlocalmemory/server/rbac_enforce.py +142 -0
- package/src/superlocalmemory/server/recall_health.py +24 -3
- package/src/superlocalmemory/server/recall_serializer.py +19 -1
- package/src/superlocalmemory/server/routes/abstraction.py +115 -0
- package/src/superlocalmemory/server/routes/agents.py +128 -38
- package/src/superlocalmemory/server/routes/backup.py +34 -10
- package/src/superlocalmemory/server/routes/behavioral.py +13 -12
- package/src/superlocalmemory/server/routes/brain.py +21 -5
- package/src/superlocalmemory/server/routes/chat.py +72 -16
- package/src/superlocalmemory/server/routes/compliance.py +171 -21
- package/src/superlocalmemory/server/routes/config_api.py +436 -0
- package/src/superlocalmemory/server/routes/data_io.py +30 -8
- package/src/superlocalmemory/server/routes/entity.py +9 -4
- package/src/superlocalmemory/server/routes/events.py +24 -8
- package/src/superlocalmemory/server/routes/evolution.py +135 -17
- package/src/superlocalmemory/server/routes/helpers.py +16 -1
- package/src/superlocalmemory/server/routes/ingest.py +7 -4
- package/src/superlocalmemory/server/routes/insights.py +3 -3
- package/src/superlocalmemory/server/routes/learning.py +14 -14
- package/src/superlocalmemory/server/routes/lifecycle.py +59 -8
- package/src/superlocalmemory/server/routes/memories.py +221 -49
- package/src/superlocalmemory/server/routes/mesh.py +95 -15
- package/src/superlocalmemory/server/routes/optimize.py +33 -1
- package/src/superlocalmemory/server/routes/prewarm.py +2 -0
- package/src/superlocalmemory/server/routes/profiles.py +63 -17
- package/src/superlocalmemory/server/routes/ratelimit.py +124 -0
- package/src/superlocalmemory/server/routes/rbac.py +367 -0
- package/src/superlocalmemory/server/routes/stats.py +13 -6
- package/src/superlocalmemory/server/routes/tiers.py +11 -9
- package/src/superlocalmemory/server/routes/v3_api.py +194 -81
- package/src/superlocalmemory/server/routes/ws.py +5 -2
- package/src/superlocalmemory/server/security_middleware.py +12 -5
- package/src/superlocalmemory/server/ui.py +30 -5
- package/src/superlocalmemory/server/unified_daemon.py +431 -75
- package/src/superlocalmemory/server/write_identity.py +38 -8
- package/src/superlocalmemory/storage/database.py +265 -53
- package/src/superlocalmemory/storage/migration_runner.py +53 -0
- package/src/superlocalmemory/storage/migrations/M021_ingestion_log_profile.py +108 -0
- package/src/superlocalmemory/storage/migrations/M022_entity_aliases_profile.py +86 -0
- package/src/superlocalmemory/storage/migrations/M023_mesh_profile_isolation.py +194 -0
- package/src/superlocalmemory/storage/migrations/M024_rbac_users_roles.py +87 -0
- package/src/superlocalmemory/storage/migrations/M025_perf_indexes.py +90 -0
- package/src/superlocalmemory/storage/migrations/M026_rbac_memberships_fk.py +136 -0
- package/src/superlocalmemory/storage/migrations/M027_transferable_patterns_profile.py +163 -0
- package/src/superlocalmemory/storage/models.py +4 -0
- package/src/superlocalmemory/storage/schema.py +87 -0
- package/src/superlocalmemory/storage/schema_v32.py +0 -9
- package/src/superlocalmemory/storage/schema_v343.py +24 -12
- package/src/superlocalmemory/trust/gate.py +49 -8
- package/src/superlocalmemory/ui/assets/slm-icon-white.svg +64 -0
- package/src/superlocalmemory/ui/assets/slm-icon.svg +36 -0
- package/src/superlocalmemory/ui/css/design-system.css +621 -0
- package/src/superlocalmemory/ui/css/neural-glass.css +6 -0
- package/src/superlocalmemory/ui/css/od-bridge.css +158 -0
- package/src/superlocalmemory/ui/favicon.svg +35 -4
- package/src/superlocalmemory/ui/index.html +306 -173
- package/src/superlocalmemory/ui/js/brain.js +5 -20
- package/src/superlocalmemory/ui/js/core.js +47 -31
- package/src/superlocalmemory/ui/js/dashboard.js +314 -63
- package/src/superlocalmemory/ui/js/event-delegation.js +102 -0
- package/src/superlocalmemory/ui/js/knowledge-graph.js +11 -11
- package/src/superlocalmemory/ui/js/math-health.js +1 -1
- package/src/superlocalmemory/ui/js/memories.js +15 -4
- package/src/superlocalmemory/ui/js/memory-chat.js +7 -7
- package/src/superlocalmemory/ui/js/ng-entities.js +6 -8
- package/src/superlocalmemory/ui/js/ng-ingestion.js +4 -4
- package/src/superlocalmemory/ui/js/ng-mesh.js +4 -9
- package/src/superlocalmemory/ui/js/ng-shell.js +8 -8
- package/src/superlocalmemory/ui/js/ng-skills.js +54 -2
- package/src/superlocalmemory/ui/js/od-agents.js +544 -0
- package/src/superlocalmemory/ui/js/od-auth-gate.js +257 -0
- package/src/superlocalmemory/ui/js/od-backup.js +780 -0
- package/src/superlocalmemory/ui/js/od-brain.js +779 -0
- package/src/superlocalmemory/ui/js/od-entities.js +579 -0
- package/src/superlocalmemory/ui/js/od-graph.js +593 -0
- package/src/superlocalmemory/ui/js/od-health.js +539 -0
- package/src/superlocalmemory/ui/js/od-mcp.js +508 -0
- package/src/superlocalmemory/ui/js/od-memories.js +887 -0
- package/src/superlocalmemory/ui/js/od-mesh.js +539 -0
- package/src/superlocalmemory/ui/js/od-operations.js +1250 -0
- package/src/superlocalmemory/ui/js/od-optimize.js +787 -0
- package/src/superlocalmemory/ui/js/od-settings.js +1053 -0
- package/src/superlocalmemory/ui/js/od-shell.js +593 -0
- package/src/superlocalmemory/ui/js/od-skills.js +573 -0
- package/src/superlocalmemory/ui/js/od-team.js +258 -0
- package/src/superlocalmemory/ui/js/profiles.js +159 -46
- package/src/superlocalmemory/ui/js/settings.js +2 -2
- package/src/superlocalmemory/ui/js/timeline.js +34 -5
- package/src/superlocalmemory/ui/js/trust-dashboard.js +2 -2
- package/src/superlocalmemory/vector/lancedb_backend.py +8 -6
- package/src/superlocalmemory/learning/behavioral_listener.py +0 -94
|
@@ -0,0 +1,779 @@
|
|
|
1
|
+
// Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar — AGPL-3.0
|
|
2
|
+
// od-brain.js — OD-themed "Living Brain" screen for SuperLocalMemory.
|
|
3
|
+
//
|
|
4
|
+
// Renders the approved design into window.odRenderBrain(container).
|
|
5
|
+
// ALL data from these confirmed live endpoints (no seed / no invented data):
|
|
6
|
+
// GET /api/learning/status → ranking_phase, stats, engagement, tech_preferences,
|
|
7
|
+
// workflow_patterns, source_scores
|
|
8
|
+
// GET /api/behavioral/status → total_outcomes, outcome_breakdown, patterns[],
|
|
9
|
+
// cross_project_transfers, recent_outcomes[]
|
|
10
|
+
// GET /api/behavioral/assertions?category=skill_performance&limit=50 → assertions[]
|
|
11
|
+
// GET /api/behavioral/tool-events?limit=500 → events[].{created_at,tool_name,…}
|
|
12
|
+
//
|
|
13
|
+
// Heatmap is built from tool-events aggregated by UTC date — real activity density,
|
|
14
|
+
// not the random slmHeatmap() fallback.
|
|
15
|
+
//
|
|
16
|
+
// CSP-safe: no eval(), no innerHTML for API data, no inline event handlers.
|
|
17
|
+
// Immutable style: EL() always creates new nodes; no mutation of existing DOM.
|
|
18
|
+
|
|
19
|
+
/* global window, document, fetch, Promise, setTimeout, getComputedStyle */
|
|
20
|
+
|
|
21
|
+
(function () {
|
|
22
|
+
'use strict';
|
|
23
|
+
|
|
24
|
+
// ======================================================================
|
|
25
|
+
// Fetch
|
|
26
|
+
// ======================================================================
|
|
27
|
+
function apiFetch(path) {
|
|
28
|
+
return fetch(path, { credentials: 'same-origin' })
|
|
29
|
+
.then(function (r) {
|
|
30
|
+
if (!r.ok) throw new Error(path + ' → ' + r.status);
|
|
31
|
+
return r.json();
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function fetchAll() {
|
|
36
|
+
return Promise.all([
|
|
37
|
+
apiFetch('/api/learning/status'),
|
|
38
|
+
apiFetch('/api/behavioral/status'),
|
|
39
|
+
apiFetch('/api/behavioral/assertions?category=skill_performance&limit=50'),
|
|
40
|
+
apiFetch('/api/behavioral/tool-events?limit=500'),
|
|
41
|
+
]);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ======================================================================
|
|
45
|
+
// DOM helper — API strings always go through .text → textContent (XSS-safe)
|
|
46
|
+
// ======================================================================
|
|
47
|
+
function EL(tag, props, kids) {
|
|
48
|
+
var el = document.createElement(tag);
|
|
49
|
+
var p = props || {};
|
|
50
|
+
Object.keys(p).forEach(function (k) {
|
|
51
|
+
if (k === 'className') el.className = String(p[k]);
|
|
52
|
+
else if (k === 'text') el.textContent = p[k] == null ? '' : String(p[k]);
|
|
53
|
+
else el.setAttribute(k, String(p[k]));
|
|
54
|
+
});
|
|
55
|
+
(kids || []).forEach(function (c) { if (c != null) el.appendChild(c); });
|
|
56
|
+
return el;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ======================================================================
|
|
60
|
+
// Formatters
|
|
61
|
+
// ======================================================================
|
|
62
|
+
function fmtNum(n) { return Number(n || 0).toLocaleString(); }
|
|
63
|
+
|
|
64
|
+
function fmtKB(kb) {
|
|
65
|
+
var v = Number(kb || 0);
|
|
66
|
+
if (v === 0) return '0 KB';
|
|
67
|
+
return v < 1024 ? v.toFixed(1) + ' KB' : (v / 1024).toFixed(1) + ' MB';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function phaseLabel(raw) {
|
|
71
|
+
return (raw || 'cold_start')
|
|
72
|
+
.replace(/_/g, '-')
|
|
73
|
+
.replace(/\b([a-z])/g, function (c) { return c.toUpperCase(); });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ======================================================================
|
|
77
|
+
// Heatmap — real activity density from tool-events aggregated by UTC date
|
|
78
|
+
// ======================================================================
|
|
79
|
+
function buildDateMap(events) {
|
|
80
|
+
var map = {};
|
|
81
|
+
(events || []).forEach(function (e) {
|
|
82
|
+
if (!e.created_at) return;
|
|
83
|
+
var d = String(e.created_at).slice(0, 10);
|
|
84
|
+
map[d] = (map[d] || 0) + 1;
|
|
85
|
+
});
|
|
86
|
+
return map;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function signalLevel(count) {
|
|
90
|
+
if (count <= 0) return 0;
|
|
91
|
+
if (count <= 3) return 1;
|
|
92
|
+
if (count <= 10) return 2;
|
|
93
|
+
if (count <= 25) return 3;
|
|
94
|
+
return 4;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function buildHeatmap(el, dateMap, weeks) {
|
|
98
|
+
var totalDays = (weeks || 26) * 7;
|
|
99
|
+
var today = new Date(); today.setHours(0, 0, 0, 0);
|
|
100
|
+
el.replaceChildren();
|
|
101
|
+
for (var i = 0; i < totalDays; i++) {
|
|
102
|
+
var d = new Date(today);
|
|
103
|
+
d.setDate(today.getDate() - (totalDays - 1 - i));
|
|
104
|
+
var ds = d.toISOString().slice(0, 10);
|
|
105
|
+
var cnt = (dateMap && dateMap[ds]) || 0;
|
|
106
|
+
var cell = document.createElement('i');
|
|
107
|
+
cell.setAttribute('data-l', String(signalLevel(cnt)));
|
|
108
|
+
cell.setAttribute('title', ds + ': ' + cnt + ' event' + (cnt === 1 ? '' : 's'));
|
|
109
|
+
el.appendChild(cell);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ======================================================================
|
|
114
|
+
// UI primitives
|
|
115
|
+
// ======================================================================
|
|
116
|
+
function meter(pct, color) {
|
|
117
|
+
var outer = EL('div', { className: 'meter' });
|
|
118
|
+
var fill = document.createElement('i');
|
|
119
|
+
fill.style.width = Math.min(100, Math.max(0, pct)) + '%';
|
|
120
|
+
if (color) fill.style.background = color;
|
|
121
|
+
outer.appendChild(fill);
|
|
122
|
+
return outer;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function inlineBar(pct) {
|
|
126
|
+
var wrap = EL('span', {
|
|
127
|
+
style: 'display:inline-block;vertical-align:middle;width:56px;height:6px;' +
|
|
128
|
+
'border-radius:99px;background:var(--card-2);overflow:hidden;margin-right:6px',
|
|
129
|
+
});
|
|
130
|
+
wrap.appendChild(EL('span', {
|
|
131
|
+
style: 'display:block;height:100%;width:' + Math.min(100, pct) + '%;' +
|
|
132
|
+
'background:linear-gradient(90deg,var(--cyan),var(--violet))',
|
|
133
|
+
}));
|
|
134
|
+
return wrap;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function heatLegend() {
|
|
138
|
+
var w = EL('div', { className: 'heat-legend' });
|
|
139
|
+
w.appendChild(document.createTextNode('less '));
|
|
140
|
+
for (var i = 0; i <= 4; i++) w.appendChild(EL('i', { 'data-l': String(i) }));
|
|
141
|
+
w.appendChild(document.createTextNode(' more'));
|
|
142
|
+
return w;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// isNumeric=true → class="value num" (tabular digits, 30px default from CSS)
|
|
146
|
+
// isNumeric=false → class="value" + font-size:24px (design uses smaller text for long labels)
|
|
147
|
+
function kpiCard(icon, label, value, delta, dUp, vColor, isNumeric) {
|
|
148
|
+
var card = EL('div', { className: 'card kpi' });
|
|
149
|
+
var lbl = EL('div', { className: 'label' });
|
|
150
|
+
if (typeof window.slmIcon === 'function') {
|
|
151
|
+
var ic = document.createElement('span');
|
|
152
|
+
ic.innerHTML = window.slmIcon(icon);
|
|
153
|
+
lbl.appendChild(ic);
|
|
154
|
+
}
|
|
155
|
+
lbl.appendChild(document.createTextNode(' ' + label));
|
|
156
|
+
var val = EL('div', { className: isNumeric ? 'value num' : 'value', text: value });
|
|
157
|
+
if (!isNumeric) val.style.fontSize = '24px';
|
|
158
|
+
if (vColor) val.style.color = vColor;
|
|
159
|
+
card.appendChild(lbl);
|
|
160
|
+
card.appendChild(val);
|
|
161
|
+
card.appendChild(EL('div', { className: 'delta ' + (dUp ? 'up' : ''), text: delta }));
|
|
162
|
+
return card;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ======================================================================
|
|
166
|
+
// Phase style injection (once — not in design-system.css yet)
|
|
167
|
+
// ======================================================================
|
|
168
|
+
function injectPhaseStyle() {
|
|
169
|
+
if (document.getElementById('od-brain-phase-style')) return;
|
|
170
|
+
var st = document.createElement('style');
|
|
171
|
+
st.id = 'od-brain-phase-style';
|
|
172
|
+
st.textContent =
|
|
173
|
+
'.phase{flex:1;text-align:center;padding:10px 6px;border-radius:var(--r-md);' +
|
|
174
|
+
'background:var(--card-2);border:1px solid var(--border)}' +
|
|
175
|
+
'.phase b{display:block;font-size:13px}' +
|
|
176
|
+
'.phase span{font-size:11px;color:var(--fg-3)}' +
|
|
177
|
+
'.phase.done{border-color:color-mix(in srgb,var(--ok) 40%,transparent)}' +
|
|
178
|
+
'.phase.done b{color:var(--ok)}' +
|
|
179
|
+
'.phase.now{border-color:var(--violet-line);background:var(--violet-soft)}' +
|
|
180
|
+
'.phase.now b{color:var(--violet)}';
|
|
181
|
+
document.head.appendChild(st);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ======================================================================
|
|
185
|
+
// Tab: OVERVIEW
|
|
186
|
+
// ======================================================================
|
|
187
|
+
function buildOverview(learning, behavioral, dateMap) {
|
|
188
|
+
var sec = EL('section', { className: 'tabpane active', 'data-p': 'overview' });
|
|
189
|
+
var stats = (learning && learning.stats) || {};
|
|
190
|
+
var eng = (learning && learning.engagement) || {};
|
|
191
|
+
var beh = behavioral || {};
|
|
192
|
+
var ML_GATE = 2000;
|
|
193
|
+
var signals = Number(stats.feedback_count || stats.total || 0);
|
|
194
|
+
var pct = Math.min(100, Math.round(signals / ML_GATE * 100));
|
|
195
|
+
var phase = (learning && learning.ranking_phase) || 'cold_start';
|
|
196
|
+
var modelActive = phase === 'ml_model';
|
|
197
|
+
var healthStatus = (eng.health_status || 'INACTIVE').toUpperCase();
|
|
198
|
+
var healthColor = healthStatus === 'HEALTHY' ? 'var(--ok)'
|
|
199
|
+
: healthStatus === 'ACTIVE' ? 'var(--cyan)' : undefined;
|
|
200
|
+
var pCount = ((beh.patterns) || []).length;
|
|
201
|
+
|
|
202
|
+
// KPI strip
|
|
203
|
+
var strip = EL('div', { className: 'kpi-strip', style: 'margin-bottom:16px' });
|
|
204
|
+
// Ranking phase: text label → isNumeric=false (font-size:24px to match design)
|
|
205
|
+
strip.appendChild(kpiCard('skill', 'Ranking phase', phaseLabel(phase),
|
|
206
|
+
'▲ ' + fmtNum(ML_GATE - signals) + ' to ML Model phase', !modelActive, undefined, false));
|
|
207
|
+
// Feedback signals: numeric → isNumeric=true
|
|
208
|
+
strip.appendChild(kpiCard('optimize', 'Feedback signals', fmtNum(signals),
|
|
209
|
+
'▲ ' + fmtNum(stats.unique_queries || 0) + ' unique queries', true, undefined, true));
|
|
210
|
+
// Engagement health: text label → isNumeric=false
|
|
211
|
+
strip.appendChild(kpiCard('health', 'Engagement health',
|
|
212
|
+
healthStatus.charAt(0) + healthStatus.slice(1).toLowerCase(),
|
|
213
|
+
(eng.days_active || 0) + ' days active · ' + Number(eng.memories_per_day || 0).toFixed(1) + ' mem/day',
|
|
214
|
+
healthStatus === 'HEALTHY', healthColor, false));
|
|
215
|
+
// Patterns: numeric → isNumeric=true
|
|
216
|
+
strip.appendChild(kpiCard('brain', 'Patterns learned', String(pCount),
|
|
217
|
+
'▲ ' + (beh.cross_project_transfers || 0) + ' transferable', pCount > 0, undefined, true));
|
|
218
|
+
sec.appendChild(strip);
|
|
219
|
+
|
|
220
|
+
// 2-column grid
|
|
221
|
+
var grid = EL('div', { className: 'grid', style: 'grid-template-columns:1fr 1fr;align-items:start' });
|
|
222
|
+
|
|
223
|
+
// Progress card
|
|
224
|
+
var pcrd = EL('div', { className: 'card' });
|
|
225
|
+
var ph = EL('div', { className: 'card-head' });
|
|
226
|
+
ph.appendChild(EL('h3', { text: 'Adaptive ranking progress' }));
|
|
227
|
+
ph.appendChild(EL('span', { className: 'sub', text: 'baseline → rule-based → ML model' }));
|
|
228
|
+
pcrd.appendChild(ph);
|
|
229
|
+
var pb = EL('div', { className: 'card-pad' });
|
|
230
|
+
var pmeta = EL('div', {
|
|
231
|
+
style: 'display:flex;justify-content:space-between;font-size:12px;color:var(--fg-2);margin-bottom:8px',
|
|
232
|
+
});
|
|
233
|
+
pmeta.appendChild(EL('span', { text: fmtNum(signals) + ' / ' + fmtNum(ML_GATE) + ' signals' }));
|
|
234
|
+
pmeta.appendChild(EL('span', { className: 'num', text: pct + '%' }));
|
|
235
|
+
pb.appendChild(pmeta);
|
|
236
|
+
pb.appendChild(meter(pct));
|
|
237
|
+
var phasesRow = EL('div', {
|
|
238
|
+
style: 'display:flex;justify-content:space-between;margin-top:18px;gap:12px',
|
|
239
|
+
});
|
|
240
|
+
[
|
|
241
|
+
{ t: 'Baseline', d: '0–20 signals', done: true, now: false },
|
|
242
|
+
{ t: 'Rule-Based', d: '20–200+ · active', done: false, now: !modelActive },
|
|
243
|
+
{ t: 'ML Model', d: '2000+ · ' + (modelActive ? 'active' : 'training set'), done: false, now: modelActive },
|
|
244
|
+
].forEach(function (phI) {
|
|
245
|
+
var el = EL('div', {
|
|
246
|
+
className: 'phase' + (phI.done ? ' done' : '') + (phI.now ? ' now' : ''),
|
|
247
|
+
});
|
|
248
|
+
el.appendChild(EL('b', { text: phI.t }));
|
|
249
|
+
el.appendChild(EL('span', { text: phI.d }));
|
|
250
|
+
phasesRow.appendChild(el);
|
|
251
|
+
});
|
|
252
|
+
pb.appendChild(phasesRow);
|
|
253
|
+
pcrd.appendChild(pb);
|
|
254
|
+
grid.appendChild(pcrd);
|
|
255
|
+
|
|
256
|
+
// Privacy card
|
|
257
|
+
var priv = EL('div', { className: 'card' });
|
|
258
|
+
var prvh = EL('div', { className: 'card-head' });
|
|
259
|
+
prvh.appendChild(EL('h3', { text: 'Privacy & data' }));
|
|
260
|
+
prvh.appendChild(EL('span', { className: 'sub', text: 'learning.db — separate from memory.db' }));
|
|
261
|
+
priv.appendChild(prvh);
|
|
262
|
+
var prvb = EL('div', { className: 'card-pad', style: 'display:flex;flex-direction:column;gap:2px' });
|
|
263
|
+
[
|
|
264
|
+
['Learning DB size', fmtKB(stats.db_size_kb)],
|
|
265
|
+
['Patterns learned', String(pCount)],
|
|
266
|
+
['Models trained', String(stats.models_trained || 0)],
|
|
267
|
+
['Sources tracked', String(stats.tracked_sources || 0)],
|
|
268
|
+
].forEach(function (row) {
|
|
269
|
+
var r = EL('div', { className: 'list-row' });
|
|
270
|
+
r.appendChild(EL('span', { className: 'muted', style: 'flex:1', text: row[0] }));
|
|
271
|
+
r.appendChild(EL('b', { className: 'num', text: row[1] }));
|
|
272
|
+
prvb.appendChild(r);
|
|
273
|
+
});
|
|
274
|
+
var lr = EL('div', { className: 'list-row' });
|
|
275
|
+
lr.appendChild(EL('span', { className: 'muted', style: 'flex:1', text: 'Never leaves device' }));
|
|
276
|
+
var badge = EL('span', { className: 'badge ok' });
|
|
277
|
+
badge.appendChild(EL('span', { className: 'dot' }));
|
|
278
|
+
badge.appendChild(document.createTextNode(' local'));
|
|
279
|
+
lr.appendChild(badge);
|
|
280
|
+
prvb.appendChild(lr);
|
|
281
|
+
priv.appendChild(prvb);
|
|
282
|
+
grid.appendChild(priv);
|
|
283
|
+
sec.appendChild(grid);
|
|
284
|
+
|
|
285
|
+
// Heatmap card (real data from tool-events)
|
|
286
|
+
var hmc = EL('div', { className: 'card', style: 'margin-top:16px' });
|
|
287
|
+
var hmh = EL('div', { className: 'card-head' });
|
|
288
|
+
hmh.appendChild(EL('h3', { text: "How I'm getting smarter" }));
|
|
289
|
+
hmh.appendChild(EL('span', { className: 'sub', text: 'reward signal · last 26 weeks' }));
|
|
290
|
+
hmh.appendChild(EL('div', { className: 'spacer' }));
|
|
291
|
+
hmh.appendChild(heatLegend());
|
|
292
|
+
hmc.appendChild(hmh);
|
|
293
|
+
var hmb = EL('div', { className: 'card-pad', style: 'overflow-x:auto' });
|
|
294
|
+
var hmEl = EL('div', { className: 'heatmap', id: 'od-brain-heat1' });
|
|
295
|
+
buildHeatmap(hmEl, dateMap, 26);
|
|
296
|
+
hmb.appendChild(hmEl);
|
|
297
|
+
hmc.appendChild(hmb);
|
|
298
|
+
sec.appendChild(hmc);
|
|
299
|
+
return sec;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// ======================================================================
|
|
303
|
+
// Tab: REWARD SIGNAL
|
|
304
|
+
// ======================================================================
|
|
305
|
+
function buildReward(behavioral, dateMap) {
|
|
306
|
+
var sec = EL('section', { className: 'tabpane', 'data-p': 'reward' });
|
|
307
|
+
var beh = behavioral || {};
|
|
308
|
+
|
|
309
|
+
// Density heatmap
|
|
310
|
+
var hmcr = EL('div', { className: 'card', style: 'margin-bottom:16px' });
|
|
311
|
+
var hmhr = EL('div', { className: 'card-head' });
|
|
312
|
+
hmhr.appendChild(EL('h3', { text: 'Reward signal density' }));
|
|
313
|
+
hmhr.appendChild(EL('span', { className: 'sub', text: 'positive engagement per day · last 26 weeks' }));
|
|
314
|
+
hmhr.appendChild(EL('div', { className: 'spacer' }));
|
|
315
|
+
hmhr.appendChild(heatLegend());
|
|
316
|
+
hmcr.appendChild(hmhr);
|
|
317
|
+
var hmbr = EL('div', { className: 'card-pad', style: 'overflow-x:auto' });
|
|
318
|
+
var hmElR = EL('div', { className: 'heatmap', id: 'od-brain-heat2' });
|
|
319
|
+
buildHeatmap(hmElR, dateMap, 26);
|
|
320
|
+
hmbr.appendChild(hmElR);
|
|
321
|
+
hmcr.appendChild(hmbr);
|
|
322
|
+
sec.appendChild(hmcr);
|
|
323
|
+
|
|
324
|
+
// 2-column: sparkline + outcome mix
|
|
325
|
+
var grid = EL('div', { className: 'grid', style: 'grid-template-columns:1fr 1fr;align-items:start' });
|
|
326
|
+
|
|
327
|
+
// Feedback sparkline (last 30 days from dateMap)
|
|
328
|
+
var fbCard = EL('div', { className: 'card' });
|
|
329
|
+
var fbH = EL('div', { className: 'card-head' });
|
|
330
|
+
fbH.appendChild(EL('h3', { text: 'Feedback signals over time' }));
|
|
331
|
+
fbH.appendChild(EL('span', { className: 'sub', text: 'cumulative' }));
|
|
332
|
+
fbCard.appendChild(fbH);
|
|
333
|
+
var fbB = EL('div', { className: 'card-pad' });
|
|
334
|
+
var fbSp = EL('div', { id: 'od-brain-sp-fb' });
|
|
335
|
+
var today = new Date(); today.setHours(0, 0, 0, 0);
|
|
336
|
+
var sparkVals = [];
|
|
337
|
+
for (var si = 29; si >= 0; si--) {
|
|
338
|
+
var sd = new Date(today); sd.setDate(today.getDate() - si);
|
|
339
|
+
sparkVals.push((dateMap && dateMap[sd.toISOString().slice(0, 10)]) || 0);
|
|
340
|
+
}
|
|
341
|
+
var hasSparkData = sparkVals.some(function (v) { return v > 0; });
|
|
342
|
+
if (hasSparkData && typeof window.slmSpark === 'function') {
|
|
343
|
+
fbSp.innerHTML = window.slmSpark(sparkVals, { w: 600, h: 150, color: 'var(--violet)' });
|
|
344
|
+
var sv1 = fbSp.querySelector('svg'); if (sv1) sv1.style.height = '150px';
|
|
345
|
+
} else {
|
|
346
|
+
fbSp.appendChild(EL('p', {
|
|
347
|
+
className: 'muted',
|
|
348
|
+
style: 'padding:32px;text-align:center;font-size:13px',
|
|
349
|
+
text: 'No signal history in the last 30 days.',
|
|
350
|
+
}));
|
|
351
|
+
}
|
|
352
|
+
fbB.appendChild(fbSp);
|
|
353
|
+
fbCard.appendChild(fbB);
|
|
354
|
+
grid.appendChild(fbCard);
|
|
355
|
+
|
|
356
|
+
// Outcome mix
|
|
357
|
+
var outCard = EL('div', { className: 'card' });
|
|
358
|
+
var outH = EL('div', { className: 'card-head' });
|
|
359
|
+
outH.appendChild(EL('h3', { text: 'Outcome mix' }));
|
|
360
|
+
outH.appendChild(EL('span', { className: 'sub', text: 'heuristic reward attribution' }));
|
|
361
|
+
outCard.appendChild(outH);
|
|
362
|
+
var outB = EL('div', { className: 'card-pad', id: 'od-brain-outcomes' });
|
|
363
|
+
var total = beh.total_outcomes || 0;
|
|
364
|
+
var bd = beh.outcome_breakdown || {};
|
|
365
|
+
if (total === 0) {
|
|
366
|
+
outB.appendChild(EL('p', {
|
|
367
|
+
className: 'muted',
|
|
368
|
+
style: 'padding:16px;text-align:center;font-size:13px',
|
|
369
|
+
text: 'No outcome data yet. Report outcomes in your sessions to train the ranker.',
|
|
370
|
+
}));
|
|
371
|
+
} else {
|
|
372
|
+
[
|
|
373
|
+
['Productive (success)', bd.success || 0, 'var(--ok)'],
|
|
374
|
+
['Session continued (partial)', bd.partial || 0, 'var(--cyan)'],
|
|
375
|
+
['Failure signal', bd.failure || 0, 'var(--danger)'],
|
|
376
|
+
].forEach(function (r) {
|
|
377
|
+
var p2 = Math.round(r[1] / total * 100);
|
|
378
|
+
var rw = EL('div', { style: 'margin-bottom:13px' });
|
|
379
|
+
var rm = EL('div', {
|
|
380
|
+
style: 'display:flex;justify-content:space-between;font-size:13px;margin-bottom:6px',
|
|
381
|
+
});
|
|
382
|
+
rm.appendChild(EL('span', { text: r[0] }));
|
|
383
|
+
rm.appendChild(EL('b', { className: 'num', text: p2 + '%' }));
|
|
384
|
+
rw.appendChild(rm);
|
|
385
|
+
rw.appendChild(meter(p2, r[2]));
|
|
386
|
+
outB.appendChild(rw);
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
outCard.appendChild(outB);
|
|
390
|
+
grid.appendChild(outCard);
|
|
391
|
+
sec.appendChild(grid);
|
|
392
|
+
return sec;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// ======================================================================
|
|
396
|
+
// Tab: BEHAVIOUR
|
|
397
|
+
// ======================================================================
|
|
398
|
+
function buildBehaviour(learning, behavioral) {
|
|
399
|
+
var sec = EL('section', { className: 'tabpane', 'data-p': 'behaviour' });
|
|
400
|
+
var l = learning || {};
|
|
401
|
+
var beh = behavioral || {};
|
|
402
|
+
var grid1 = EL('div', { className: 'grid', style: 'grid-template-columns:1fr 1fr;align-items:start' });
|
|
403
|
+
|
|
404
|
+
// Tech preferences table
|
|
405
|
+
var tc = EL('div', { className: 'card' });
|
|
406
|
+
var tch = EL('div', { className: 'card-head' });
|
|
407
|
+
tch.appendChild(EL('h3', { text: 'Tech preferences' }));
|
|
408
|
+
tch.appendChild(EL('span', { className: 'sub', text: 'Layer 1 · confidence-weighted' }));
|
|
409
|
+
tc.appendChild(tch);
|
|
410
|
+
var tcb = EL('div', { className: 'card-pad' });
|
|
411
|
+
var techItems = l.tech_preferences || [];
|
|
412
|
+
if (techItems.length === 0) {
|
|
413
|
+
tcb.appendChild(EL('p', {
|
|
414
|
+
className: 'muted',
|
|
415
|
+
style: 'padding:16px;text-align:center;font-size:13px',
|
|
416
|
+
text: 'No tech preferences learned yet. These emerge after consistent cross-session usage.',
|
|
417
|
+
}));
|
|
418
|
+
} else {
|
|
419
|
+
var tbl = EL('table', { className: 'tbl' });
|
|
420
|
+
var thead = EL('thead');
|
|
421
|
+
var thr = EL('tr');
|
|
422
|
+
['Preference', 'Value', 'Confidence', 'Evidence'].forEach(function (h) {
|
|
423
|
+
thr.appendChild(EL('th', { text: h }));
|
|
424
|
+
});
|
|
425
|
+
thead.appendChild(thr); tbl.appendChild(thead);
|
|
426
|
+
var tbody = EL('tbody');
|
|
427
|
+
techItems.slice(0, 10).forEach(function (t) {
|
|
428
|
+
var tr = EL('tr');
|
|
429
|
+
tr.appendChild(EL('td', { className: 'muted', text: String(t.name || t.preference || '—') }));
|
|
430
|
+
var vd = EL('td');
|
|
431
|
+
vd.appendChild(EL('b', { text: String(t.value || '—') }));
|
|
432
|
+
tr.appendChild(vd);
|
|
433
|
+
var cp = Math.round(Number(t.confidence || t.strength || 0) * 100);
|
|
434
|
+
var cd = EL('td');
|
|
435
|
+
cd.appendChild(inlineBar(cp));
|
|
436
|
+
cd.appendChild(document.createTextNode(cp + '%'));
|
|
437
|
+
tr.appendChild(cd);
|
|
438
|
+
tr.appendChild(EL('td', { className: 'num', text: String(t.evidence_count || t.count || 0) }));
|
|
439
|
+
tbody.appendChild(tr);
|
|
440
|
+
});
|
|
441
|
+
tbl.appendChild(tbody); tcb.appendChild(tbl);
|
|
442
|
+
}
|
|
443
|
+
tc.appendChild(tcb); grid1.appendChild(tc);
|
|
444
|
+
|
|
445
|
+
// Workflow patterns
|
|
446
|
+
var wc = EL('div', { className: 'card' });
|
|
447
|
+
var wch = EL('div', { className: 'card-head' });
|
|
448
|
+
wch.appendChild(EL('h3', { text: 'Workflow patterns' }));
|
|
449
|
+
wch.appendChild(EL('span', { className: 'sub', text: 'Layer 3 · sequence & temporal' }));
|
|
450
|
+
wc.appendChild(wch);
|
|
451
|
+
var wcb = EL('div', { className: 'card-pad' });
|
|
452
|
+
var wfPats = l.workflow_patterns || [];
|
|
453
|
+
if (wfPats.length === 0) {
|
|
454
|
+
wcb.appendChild(EL('p', {
|
|
455
|
+
className: 'muted',
|
|
456
|
+
style: 'padding:16px;text-align:center;font-size:13px',
|
|
457
|
+
text: 'Workflow patterns emerge after 50+ recall sessions.',
|
|
458
|
+
}));
|
|
459
|
+
} else {
|
|
460
|
+
wfPats.slice(0, 3).forEach(function (wf) {
|
|
461
|
+
var wd = EL('div', { style: 'margin-top:12px' });
|
|
462
|
+
wd.appendChild(EL('span', { className: 'badge violet', text: String(wf.type || 'pattern') }));
|
|
463
|
+
wd.appendChild(EL('div', {
|
|
464
|
+
style: 'margin-top:6px;font-size:13px',
|
|
465
|
+
text: String(wf.description || wf.pattern || '—'),
|
|
466
|
+
}));
|
|
467
|
+
wcb.appendChild(wd);
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
wc.appendChild(wcb); grid1.appendChild(wc);
|
|
471
|
+
sec.appendChild(grid1);
|
|
472
|
+
|
|
473
|
+
var grid2 = EL('div', {
|
|
474
|
+
className: 'grid',
|
|
475
|
+
style: 'grid-template-columns:1fr 1fr;align-items:start;margin-top:16px',
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
// Cross-project transfers
|
|
479
|
+
var xc = EL('div', { className: 'card' });
|
|
480
|
+
var xch = EL('div', { className: 'card-head' });
|
|
481
|
+
xch.appendChild(EL('h3', { text: 'Cross-project transfers' }));
|
|
482
|
+
xch.appendChild(EL('span', { className: 'sub', text: 'patterns reused across projects' }));
|
|
483
|
+
xc.appendChild(xch);
|
|
484
|
+
var xcb = EL('div', { className: 'card-pad' });
|
|
485
|
+
var xPats = (beh.patterns || []).filter(function (p) {
|
|
486
|
+
return p.pattern_type === 'cross_project' || p.is_transferable;
|
|
487
|
+
});
|
|
488
|
+
if (xPats.length === 0) {
|
|
489
|
+
xcb.appendChild(EL('p', {
|
|
490
|
+
className: 'muted',
|
|
491
|
+
style: 'padding:16px;text-align:center;font-size:13px',
|
|
492
|
+
text: 'No cross-project patterns yet. These appear when the same behaviour is detected in 2+ projects.',
|
|
493
|
+
}));
|
|
494
|
+
} else {
|
|
495
|
+
xPats.slice(0, 5).forEach(function (x) {
|
|
496
|
+
var item = EL('div', { className: 'feed-item' });
|
|
497
|
+
if (typeof window.slmIcon === 'function') {
|
|
498
|
+
var ic = EL('span', { className: 'ic', style: 'background:var(--violet-soft);color:var(--violet)' });
|
|
499
|
+
ic.innerHTML = window.slmIcon('link');
|
|
500
|
+
item.appendChild(ic);
|
|
501
|
+
}
|
|
502
|
+
var info = EL('div');
|
|
503
|
+
info.appendChild(EL('b', { text: String(x.pattern_key || x.name || '—') }));
|
|
504
|
+
info.appendChild(EL('div', {
|
|
505
|
+
className: 'dim',
|
|
506
|
+
style: 'font-size:12px;margin-top:2px',
|
|
507
|
+
text: String(x.description || x.pattern_type || '—'),
|
|
508
|
+
}));
|
|
509
|
+
item.appendChild(info); xcb.appendChild(item);
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
xc.appendChild(xcb); grid2.appendChild(xc);
|
|
513
|
+
|
|
514
|
+
// Recent outcomes
|
|
515
|
+
var rc = EL('div', { className: 'card' });
|
|
516
|
+
var rch = EL('div', { className: 'card-head' });
|
|
517
|
+
rch.appendChild(EL('h3', { text: 'Recent outcomes' }));
|
|
518
|
+
rc.appendChild(rch);
|
|
519
|
+
var rcb = EL('div', { className: 'card-pad' });
|
|
520
|
+
var recOuts = beh.recent_outcomes || [];
|
|
521
|
+
if (recOuts.length === 0) {
|
|
522
|
+
rcb.appendChild(EL('p', {
|
|
523
|
+
className: 'muted',
|
|
524
|
+
style: 'padding:16px;text-align:center;font-size:13px',
|
|
525
|
+
text: 'No outcome reports yet. Use /report-outcome to teach the ranker.',
|
|
526
|
+
}));
|
|
527
|
+
} else {
|
|
528
|
+
recOuts.slice(0, 8).forEach(function (r) {
|
|
529
|
+
var item = EL('div', { className: 'feed-item' });
|
|
530
|
+
var out = String(r.outcome || 'unknown');
|
|
531
|
+
var bcls = out === 'success' ? 'ok' : out === 'failure' ? 'danger' : 'warn';
|
|
532
|
+
var bdg = EL('span', { className: 'badge ' + bcls });
|
|
533
|
+
bdg.appendChild(EL('span', { className: 'dot' }));
|
|
534
|
+
bdg.appendChild(document.createTextNode(' ' + out));
|
|
535
|
+
item.appendChild(bdg);
|
|
536
|
+
item.appendChild(EL('span', {
|
|
537
|
+
style: 'flex:1;font-size:13px',
|
|
538
|
+
text: String(r.context || r.description || '—'),
|
|
539
|
+
}));
|
|
540
|
+
item.appendChild(EL('time', { text: String(r.time_ago || r.created_at || '') }));
|
|
541
|
+
rcb.appendChild(item);
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
rc.appendChild(rcb); grid2.appendChild(rc);
|
|
545
|
+
sec.appendChild(grid2);
|
|
546
|
+
return sec;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// ======================================================================
|
|
550
|
+
// Tab: CONNECTED CLIENTS
|
|
551
|
+
// ======================================================================
|
|
552
|
+
function buildClients(dateMap) {
|
|
553
|
+
var sec = EL('section', { className: 'tabpane', 'data-p': 'clients' });
|
|
554
|
+
|
|
555
|
+
// Sparkline from tool-event activity (last 22 data-points matching design)
|
|
556
|
+
var evc = EL('div', { className: 'card', style: 'margin-bottom:16px' });
|
|
557
|
+
var evh = EL('div', { className: 'card-head' });
|
|
558
|
+
evh.appendChild(EL('h3', { text: 'Connected-client evolution' }));
|
|
559
|
+
evh.appendChild(EL('span', { className: 'sub', text: 'tool-event activity over time (proxy metric)' }));
|
|
560
|
+
evc.appendChild(evh);
|
|
561
|
+
var evb = EL('div', { className: 'card-pad' });
|
|
562
|
+
var today = new Date(); today.setHours(0, 0, 0, 0);
|
|
563
|
+
var cVals = [];
|
|
564
|
+
for (var ci = 21; ci >= 0; ci--) {
|
|
565
|
+
var cd = new Date(today); cd.setDate(today.getDate() - ci);
|
|
566
|
+
cVals.push((dateMap && dateMap[cd.toISOString().slice(0, 10)]) || 0);
|
|
567
|
+
}
|
|
568
|
+
var evSp = EL('div', { id: 'od-brain-sp-clients' });
|
|
569
|
+
var hasData = cVals.some(function (v) { return v > 0; });
|
|
570
|
+
if (hasData && typeof window.slmSpark === 'function') {
|
|
571
|
+
evSp.innerHTML = window.slmSpark(cVals, { w: 600, h: 150, color: 'var(--cyan)' });
|
|
572
|
+
var sv2 = evSp.querySelector('svg'); if (sv2) sv2.style.height = '150px';
|
|
573
|
+
} else {
|
|
574
|
+
evSp.appendChild(EL('p', { className: 'muted', style: 'padding:32px;text-align:center',
|
|
575
|
+
text: 'No event history in this period.' }));
|
|
576
|
+
}
|
|
577
|
+
evb.appendChild(evSp);
|
|
578
|
+
// TODO: GET /api/clients — no live connected-client session data available via these endpoints.
|
|
579
|
+
// tool-events captures tool invocations but not distinct client identities or session counts.
|
|
580
|
+
evb.appendChild(EL('p', {
|
|
581
|
+
className: 'muted',
|
|
582
|
+
style: 'margin-top:12px;font-size:13px',
|
|
583
|
+
text: 'Bars above represent all tool invocations logged to this daemon. ' +
|
|
584
|
+
'A dedicated client-session endpoint is not yet exposed via the public API.',
|
|
585
|
+
}));
|
|
586
|
+
evc.appendChild(evb);
|
|
587
|
+
sec.appendChild(evc);
|
|
588
|
+
|
|
589
|
+
// Empty-state clients table
|
|
590
|
+
var tc = EL('div', { className: 'card' });
|
|
591
|
+
var tch = EL('div', { className: 'card-head' });
|
|
592
|
+
tch.appendChild(EL('h3', { text: 'Clients' }));
|
|
593
|
+
tc.appendChild(tch);
|
|
594
|
+
var tcb = EL('div', { className: 'card-pad' });
|
|
595
|
+
tcb.appendChild(EL('p', {
|
|
596
|
+
className: 'muted',
|
|
597
|
+
style: 'padding:16px;text-align:center;font-size:13px',
|
|
598
|
+
text: 'Client details are not yet available via API. Coming in a future daemon release.',
|
|
599
|
+
}));
|
|
600
|
+
tc.appendChild(tcb);
|
|
601
|
+
sec.appendChild(tc);
|
|
602
|
+
return sec;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// ======================================================================
|
|
606
|
+
// Tab: SOURCE QUALITY
|
|
607
|
+
// ======================================================================
|
|
608
|
+
function buildSourceQuality(learning) {
|
|
609
|
+
var sec = EL('section', { className: 'tabpane', 'data-p': 'sources' });
|
|
610
|
+
var scores = (learning && learning.source_scores) || {};
|
|
611
|
+
var entries = Object.keys(scores).sort(function (a, b) {
|
|
612
|
+
return Number(scores[b]) - Number(scores[a]);
|
|
613
|
+
});
|
|
614
|
+
var card = EL('div', { className: 'card' });
|
|
615
|
+
var ch = EL('div', { className: 'card-head' });
|
|
616
|
+
ch.appendChild(EL('h3', { text: 'Source quality' }));
|
|
617
|
+
ch.appendChild(EL('span', { className: 'sub', text: 'per-tool retrieval scoring · 0.0–1.0' }));
|
|
618
|
+
card.appendChild(ch);
|
|
619
|
+
var cb = EL('div', { className: 'card-pad' });
|
|
620
|
+
if (entries.length === 0) {
|
|
621
|
+
// TODO: GET /api/learning/status → source_scores is empty until ML ranking pipeline
|
|
622
|
+
// processes sufficient interactions and runs consolidation.
|
|
623
|
+
cb.appendChild(EL('p', {
|
|
624
|
+
className: 'muted',
|
|
625
|
+
style: 'padding:16px;text-align:center;font-size:13px',
|
|
626
|
+
text: 'Source quality scores are computed by the ML ranking pipeline. ' +
|
|
627
|
+
'No data yet — scores appear after consolidation runs.',
|
|
628
|
+
}));
|
|
629
|
+
} else {
|
|
630
|
+
entries.forEach(function (k) {
|
|
631
|
+
var v = Number(scores[k]);
|
|
632
|
+
var row = EL('div', { style: 'margin-bottom:14px' });
|
|
633
|
+
var meta = EL('div', {
|
|
634
|
+
style: 'display:flex;justify-content:space-between;font-size:13px;margin-bottom:6px',
|
|
635
|
+
});
|
|
636
|
+
meta.appendChild(EL('span', { className: 'mono', text: k }));
|
|
637
|
+
meta.appendChild(EL('b', { className: 'num', text: v.toFixed(2) }));
|
|
638
|
+
row.appendChild(meta);
|
|
639
|
+
row.appendChild(meter(v * 100));
|
|
640
|
+
cb.appendChild(row);
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
card.appendChild(cb); sec.appendChild(card);
|
|
644
|
+
return sec;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// ======================================================================
|
|
648
|
+
// Tab row
|
|
649
|
+
// ======================================================================
|
|
650
|
+
function buildTabRow(pCount) {
|
|
651
|
+
var tabDiv = EL('div', { className: 'tabs', id: 'od-brain-tabs' });
|
|
652
|
+
[
|
|
653
|
+
['overview', 'Overview', null],
|
|
654
|
+
['reward', 'Reward signal', null],
|
|
655
|
+
['behaviour', 'Behaviour', pCount],
|
|
656
|
+
['clients', 'Connected clients', null],
|
|
657
|
+
['sources', 'Source quality', null],
|
|
658
|
+
].forEach(function (td, i) {
|
|
659
|
+
var btn = EL('button', { className: 'tab' + (i === 0 ? ' active' : ''), 'data-t': td[0] });
|
|
660
|
+
btn.appendChild(document.createTextNode(td[1]));
|
|
661
|
+
if (td[2] !== null) {
|
|
662
|
+
btn.appendChild(document.createTextNode(' '));
|
|
663
|
+
btn.appendChild(EL('span', { className: 'cnt', text: String(td[2]) }));
|
|
664
|
+
}
|
|
665
|
+
tabDiv.appendChild(btn);
|
|
666
|
+
});
|
|
667
|
+
return tabDiv;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// ======================================================================
|
|
671
|
+
// Wire tab switching — addEventListener only (CSP-safe, no onclick)
|
|
672
|
+
// ======================================================================
|
|
673
|
+
function wireTabs(container) {
|
|
674
|
+
var tabRow = container.querySelector('#od-brain-tabs');
|
|
675
|
+
if (!tabRow) return;
|
|
676
|
+
var btns = tabRow.querySelectorAll('.tab');
|
|
677
|
+
btns.forEach(function (btn) {
|
|
678
|
+
btn.addEventListener('click', function () {
|
|
679
|
+
btns.forEach(function (b) { b.classList.remove('active'); });
|
|
680
|
+
btn.classList.add('active');
|
|
681
|
+
var t = btn.getAttribute('data-t');
|
|
682
|
+
container.querySelectorAll('.tabpane').forEach(function (s) {
|
|
683
|
+
s.classList.toggle('active', s.getAttribute('data-p') === t);
|
|
684
|
+
});
|
|
685
|
+
});
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// ======================================================================
|
|
690
|
+
// Loading / error
|
|
691
|
+
// ======================================================================
|
|
692
|
+
function showLoading(container) {
|
|
693
|
+
container.replaceChildren();
|
|
694
|
+
var w = EL('div', { style: 'padding:32px;text-align:center' });
|
|
695
|
+
var spin = EL('div', { className: 'spinner-border text-primary', role: 'status' });
|
|
696
|
+
spin.appendChild(EL('span', { className: 'visually-hidden', text: 'Loading' }));
|
|
697
|
+
w.appendChild(spin);
|
|
698
|
+
w.appendChild(EL('span', { className: 'text-muted ms-2', text: ' Loading Brain…' }));
|
|
699
|
+
container.appendChild(w);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
function showError(container, err) {
|
|
703
|
+
container.replaceChildren();
|
|
704
|
+
var w = EL('div', { style: 'padding:24px' });
|
|
705
|
+
w.appendChild(EL('p', { className: 'text-danger fw-bold', text: 'Could not load Brain data.' }));
|
|
706
|
+
w.appendChild(EL('p', {
|
|
707
|
+
className: 'text-muted',
|
|
708
|
+
text: 'The daemon may be starting up. Details in the browser console.',
|
|
709
|
+
}));
|
|
710
|
+
var btn = EL('button', {
|
|
711
|
+
className: 'btn btn-sm btn-outline-secondary',
|
|
712
|
+
type: 'button',
|
|
713
|
+
text: 'Retry',
|
|
714
|
+
});
|
|
715
|
+
btn.addEventListener('click', function () { render(container); });
|
|
716
|
+
w.appendChild(btn);
|
|
717
|
+
container.appendChild(w);
|
|
718
|
+
if (err && window.console) window.console.debug('[od-brain]', err.message || String(err));
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
// ======================================================================
|
|
722
|
+
// Main render
|
|
723
|
+
// ======================================================================
|
|
724
|
+
function render(container) {
|
|
725
|
+
showLoading(container);
|
|
726
|
+
injectPhaseStyle();
|
|
727
|
+
fetchAll().then(function (results) {
|
|
728
|
+
var learning = results[0] || {};
|
|
729
|
+
var behavioral = results[1] || {};
|
|
730
|
+
var events = ((results[3] && results[3].events) || []);
|
|
731
|
+
var dateMap = buildDateMap(events);
|
|
732
|
+
var pCount = ((behavioral.patterns) || []).length;
|
|
733
|
+
|
|
734
|
+
var head = EL('div', { className: 'page-head' });
|
|
735
|
+
head.appendChild(EL('h2', { text: 'The living brain' }));
|
|
736
|
+
head.appendChild(EL('p', {
|
|
737
|
+
text: 'How your memory is getting smarter — ranking phase, the reward signal it learns from, ' +
|
|
738
|
+
'and the behavioural patterns it has extracted. Everything trained on-device from your own usage.',
|
|
739
|
+
}));
|
|
740
|
+
|
|
741
|
+
container.replaceChildren(
|
|
742
|
+
head,
|
|
743
|
+
buildTabRow(pCount),
|
|
744
|
+
buildOverview(learning, behavioral, dateMap),
|
|
745
|
+
buildReward(behavioral, dateMap),
|
|
746
|
+
buildBehaviour(learning, behavioral),
|
|
747
|
+
buildClients(dateMap),
|
|
748
|
+
buildSourceQuality(learning)
|
|
749
|
+
);
|
|
750
|
+
wireTabs(container);
|
|
751
|
+
}).catch(function (err) { showError(container, err); });
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// ======================================================================
|
|
755
|
+
// Public API — od-shell.js calls window.odRenderBrain(pane)
|
|
756
|
+
// ======================================================================
|
|
757
|
+
window.odRenderBrain = function (container) { if (container) render(container); };
|
|
758
|
+
|
|
759
|
+
// Legacy compatibility — ng-shell.js calls window.loadBrain()
|
|
760
|
+
window.loadBrain = function () {
|
|
761
|
+
var c = document.getElementById('brain-pane');
|
|
762
|
+
if (c) render(c);
|
|
763
|
+
};
|
|
764
|
+
|
|
765
|
+
// Auto-boot when the pane is already visible (e.g., deep-link to #brain-pane)
|
|
766
|
+
(function boot() {
|
|
767
|
+
function tryBoot() {
|
|
768
|
+
var c = document.getElementById('brain-pane');
|
|
769
|
+
if (!c) return;
|
|
770
|
+
if (c.classList.contains('active') || c.classList.contains('show')) render(c);
|
|
771
|
+
}
|
|
772
|
+
if (document.readyState === 'loading') {
|
|
773
|
+
document.addEventListener('DOMContentLoaded', tryBoot);
|
|
774
|
+
} else {
|
|
775
|
+
tryBoot();
|
|
776
|
+
}
|
|
777
|
+
}());
|
|
778
|
+
|
|
779
|
+
}());
|