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,579 @@
|
|
|
1
|
+
// od-entities.js — Entity Explorer
|
|
2
|
+
// OD design port for SuperLocalMemory dashboard.
|
|
3
|
+
//
|
|
4
|
+
// Render target : document.getElementById('entities-pane')
|
|
5
|
+
// Public API : window.odRenderEntities(container)
|
|
6
|
+
// Auto-init : fires on DOMContentLoaded if #entities-pane exists
|
|
7
|
+
//
|
|
8
|
+
// CSP-safe: no inline on* handlers; all events wired via delegation (data-od-act).
|
|
9
|
+
// XSS-safe: every API string passes through _esc() before DOM insertion.
|
|
10
|
+
// nosemgrep: innerHTML — all dynamic values escaped via _esc()
|
|
11
|
+
//
|
|
12
|
+
// Endpoints verified against live daemon at http://127.0.0.1:8765
|
|
13
|
+
// GET /api/entity/list?limit=N&offset=N
|
|
14
|
+
// → { entities[], total, limit, offset }
|
|
15
|
+
// entity fields: entity_id, name, type, fact_count, first_seen, last_seen,
|
|
16
|
+
// summary_preview, has_compiled_truth, confidence, last_compiled_at
|
|
17
|
+
// GET /api/entity/{name}
|
|
18
|
+
// → { entity_name, entity_type, compiled_truth, knowledge_summary,
|
|
19
|
+
// timeline[], source_fact_ids[], last_compiled_at, confidence }
|
|
20
|
+
// POST /api/entity/{name}/recompile (write — not called here, shown disabled)
|
|
21
|
+
//
|
|
22
|
+
// CRIT fixes applied:
|
|
23
|
+
// 1. XSS — _esc() wraps every interpolated string
|
|
24
|
+
// 2. timeline empty-state — timeline:[] in real data; shows graceful placeholder
|
|
25
|
+
// 3. Tab/filter state — all 5 type filters wired, no filter leaves an invisible state
|
|
26
|
+
|
|
27
|
+
(function () {
|
|
28
|
+
'use strict';
|
|
29
|
+
|
|
30
|
+
// ── Utilities ──────────────────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
// CRIT-1: Local escapeHtml to prevent XSS if core.js loads after this module.
|
|
33
|
+
function _esc(s) {
|
|
34
|
+
if (typeof escapeHtml === 'function') return escapeHtml(s);
|
|
35
|
+
var d = document.createElement('div');
|
|
36
|
+
d.textContent = String(s == null ? '' : s);
|
|
37
|
+
return d.innerHTML;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function _fmt(iso) {
|
|
41
|
+
if (!iso) return '—';
|
|
42
|
+
var d = new Date(iso);
|
|
43
|
+
return isNaN(d.getTime()) ? String(iso) : d.toLocaleDateString();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function _ago(iso) {
|
|
47
|
+
if (!iso) return '';
|
|
48
|
+
var s = (Date.now() - new Date(iso).getTime()) / 1000;
|
|
49
|
+
if (s < 0) s = 0;
|
|
50
|
+
if (s < 60) return Math.floor(s) + 's ago';
|
|
51
|
+
if (s < 3600) return Math.floor(s / 60) + 'm ago';
|
|
52
|
+
if (s < 86400) return Math.floor(s / 3600) + 'h ago';
|
|
53
|
+
return Math.floor(s / 86400) + 'd ago';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Map entity type → CSS variable color name (no hex — design-system tokens only)
|
|
57
|
+
function _typeVar(type) {
|
|
58
|
+
var MAP = {
|
|
59
|
+
person: '--violet',
|
|
60
|
+
concept: '--warn',
|
|
61
|
+
organization: '--cyan',
|
|
62
|
+
skill: '--ok',
|
|
63
|
+
event: '--ok',
|
|
64
|
+
place: '--cyan',
|
|
65
|
+
};
|
|
66
|
+
return 'var(' + (MAP[String(type).toLowerCase()] || '--fg-2') + ')';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Map entity type → badge class
|
|
70
|
+
function _typeCls(type) {
|
|
71
|
+
var MAP = {
|
|
72
|
+
person: 'violet',
|
|
73
|
+
concept: 'warn',
|
|
74
|
+
organization: 'cyan',
|
|
75
|
+
skill: 'ok',
|
|
76
|
+
event: 'ok',
|
|
77
|
+
};
|
|
78
|
+
return MAP[String(type).toLowerCase()] || 'neutral';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Map entity type → Bootstrap Icon class
|
|
82
|
+
function _typeIcon(type) {
|
|
83
|
+
var MAP = {
|
|
84
|
+
person: 'bi-person',
|
|
85
|
+
concept: 'bi-lightbulb',
|
|
86
|
+
organization: 'bi-building',
|
|
87
|
+
skill: 'bi-lightning-charge',
|
|
88
|
+
event: 'bi-calendar-event',
|
|
89
|
+
place: 'bi-geo-alt',
|
|
90
|
+
};
|
|
91
|
+
return MAP[String(type).toLowerCase()] || 'bi-circle';
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ── CSS (injected once) ────────────────────────────────────────────────────
|
|
95
|
+
|
|
96
|
+
var _cssInjected = false;
|
|
97
|
+
function _injectCSS() {
|
|
98
|
+
if (_cssInjected) return;
|
|
99
|
+
_cssInjected = true;
|
|
100
|
+
var s = document.createElement('style');
|
|
101
|
+
s.dataset.odModule = 'entities';
|
|
102
|
+
s.textContent =
|
|
103
|
+
'.od-tl{position:relative;padding-left:8px;}' +
|
|
104
|
+
'.od-tl-item{display:flex;gap:14px;padding:0 0 20px 14px;position:relative;' +
|
|
105
|
+
'border-left:2px solid var(--border);margin-left:5px;}' +
|
|
106
|
+
'.od-tl-item:last-child{border-left-color:transparent;padding-bottom:0;}' +
|
|
107
|
+
'.od-tl-dot{position:absolute;left:-7px;top:2px;width:12px;height:12px;' +
|
|
108
|
+
'border-radius:50%;border:2px solid var(--card);}' +
|
|
109
|
+
'.od-ent-row{display:flex;align-items:center;gap:12px;padding:12px 16px;' +
|
|
110
|
+
'border-bottom:1px solid var(--border);cursor:pointer;transition:background .12s;}' +
|
|
111
|
+
'.od-ent-row:last-child{border-bottom:0;}' +
|
|
112
|
+
'.od-ent-row:hover{background:var(--card-2);}' +
|
|
113
|
+
'.od-ent-row.active{background:var(--violet-soft);}' +
|
|
114
|
+
'.od-ent-av{width:32px;height:32px;border-radius:9px;display:grid;place-items:center;' +
|
|
115
|
+
'font-weight:700;font-size:13px;color:#fff;flex-shrink:0;}';
|
|
116
|
+
document.head.appendChild(s);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ── Module state ───────────────────────────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
var _st = {
|
|
122
|
+
rootId: null,
|
|
123
|
+
entities: [],
|
|
124
|
+
total: 0,
|
|
125
|
+
page: 0,
|
|
126
|
+
pageSize: 50,
|
|
127
|
+
typeFilter: 'all',
|
|
128
|
+
search: '',
|
|
129
|
+
selected: null, // entity name
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// ── Main entry ─────────────────────────────────────────────────────────────
|
|
133
|
+
|
|
134
|
+
function render(container) {
|
|
135
|
+
if (!container) return;
|
|
136
|
+
_injectCSS();
|
|
137
|
+
var id = 'od-ent-' + Math.random().toString(36).slice(2, 8);
|
|
138
|
+
_st = Object.assign({}, _st, { rootId: id, page: 0, typeFilter: 'all', selected: null });
|
|
139
|
+
container.innerHTML = _scaffold(id);
|
|
140
|
+
_wire(container, id);
|
|
141
|
+
_loadList(id);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ── Scaffold ───────────────────────────────────────────────────────────────
|
|
145
|
+
|
|
146
|
+
function _scaffold(id) {
|
|
147
|
+
return (
|
|
148
|
+
'<div id="' + id + '">' +
|
|
149
|
+
'<div class="page-head">' +
|
|
150
|
+
'<h2>Canonical entities</h2>' +
|
|
151
|
+
'<p style="color:var(--fg-2);margin-top:5px">' +
|
|
152
|
+
'People, orgs, concepts and events resolved from your facts — ' +
|
|
153
|
+
'each with a temporal timeline of how its knowledge changed over time.' +
|
|
154
|
+
'</p>' +
|
|
155
|
+
'</div>' +
|
|
156
|
+
|
|
157
|
+
'<div style="display:grid;grid-template-columns:340px 1fr;gap:20px;align-items:start">' +
|
|
158
|
+
|
|
159
|
+
// Left panel: list
|
|
160
|
+
'<div class="card" style="position:sticky;top:86px">' +
|
|
161
|
+
'<div class="card-head">' +
|
|
162
|
+
'<h3>Entities <span style="font-size:12px;font-weight:400;' +
|
|
163
|
+
'color:var(--fg-2)" id="' + id + '-count"></span></h3>' +
|
|
164
|
+
'</div>' +
|
|
165
|
+
|
|
166
|
+
// Search
|
|
167
|
+
'<div style="padding:12px 16px;border-bottom:1px solid var(--border)">' +
|
|
168
|
+
'<label class="search" style="width:100%" for="' + id + '-search">' +
|
|
169
|
+
'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" ' +
|
|
170
|
+
'stroke-width="1.8" width="15" height="15">' +
|
|
171
|
+
'<circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3"/></svg>' +
|
|
172
|
+
'<input id="' + id + '-search" type="text" placeholder="Search entities…" ' +
|
|
173
|
+
'data-od-act="ent-search">' +
|
|
174
|
+
'</label>' +
|
|
175
|
+
'</div>' +
|
|
176
|
+
|
|
177
|
+
// Type filter chips
|
|
178
|
+
'<div style="display:flex;gap:6px;padding:10px 12px;flex-wrap:wrap;' +
|
|
179
|
+
'border-bottom:1px solid var(--border)">' +
|
|
180
|
+
_typeChips(id, 'all') +
|
|
181
|
+
'</div>' +
|
|
182
|
+
|
|
183
|
+
// Entity list
|
|
184
|
+
'<div id="' + id + '-list" style="max-height:62vh;overflow-y:auto">' +
|
|
185
|
+
'<div style="padding:40px;text-align:center;color:var(--fg-2);font-size:13px">' +
|
|
186
|
+
'Loading entities…' +
|
|
187
|
+
'</div>' +
|
|
188
|
+
'</div>' +
|
|
189
|
+
|
|
190
|
+
// Pagination
|
|
191
|
+
'<div id="' + id + '-pg" style="padding:10px 16px;border-top:1px solid var(--border)"></div>' +
|
|
192
|
+
'</div>' +
|
|
193
|
+
|
|
194
|
+
// Right panel: detail
|
|
195
|
+
'<div id="' + id + '-detail">' +
|
|
196
|
+
'<div style="padding:60px;text-align:center;color:var(--fg-2)">' +
|
|
197
|
+
'<i class="bi bi-person-badge" style="font-size:3rem;opacity:0.3;display:block;margin-bottom:12px"></i>' +
|
|
198
|
+
'<div>Select an entity to explore its knowledge and temporal timeline</div>' +
|
|
199
|
+
'</div>' +
|
|
200
|
+
'</div>' +
|
|
201
|
+
|
|
202
|
+
'</div>' +
|
|
203
|
+
'</div>'
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Match design exactly: All / Person / Org / Concept / Event (5 chips).
|
|
208
|
+
// data-type carries the API type string; label is display-only.
|
|
209
|
+
// TODO: Live data returns type="concept" for most entities; Person (5) exists,
|
|
210
|
+
// but "organization" does not appear yet — clicking Org shows empty state
|
|
211
|
+
// until the daemon's entity-extraction model assigns that type.
|
|
212
|
+
var TYPE_CHIPS = [
|
|
213
|
+
{ val: 'all', label: 'All' },
|
|
214
|
+
{ val: 'person', label: 'Person' },
|
|
215
|
+
{ val: 'organization', label: 'Org' },
|
|
216
|
+
{ val: 'concept', label: 'Concept' },
|
|
217
|
+
{ val: 'event', label: 'Event' },
|
|
218
|
+
];
|
|
219
|
+
|
|
220
|
+
function _typeChips(id, active) {
|
|
221
|
+
return TYPE_CHIPS.map(function (tc) {
|
|
222
|
+
return '<div class="chip' + (tc.val === active ? ' on' : '') + '" ' +
|
|
223
|
+
'data-od-act="type-filter" data-type="' + _esc(tc.val) + '">' +
|
|
224
|
+
_esc(tc.label) +
|
|
225
|
+
'</div>';
|
|
226
|
+
}).join('');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// ── Entity list fetch & render ─────────────────────────────────────────────
|
|
230
|
+
|
|
231
|
+
function _loadList(id) {
|
|
232
|
+
var offset = _st.page * _st.pageSize;
|
|
233
|
+
var url = '/api/entity/list?limit=' + _st.pageSize + '&offset=' + offset;
|
|
234
|
+
var listEl = document.getElementById(id + '-list');
|
|
235
|
+
if (listEl) {
|
|
236
|
+
listEl.innerHTML = '<div style="padding:40px;text-align:center;' +
|
|
237
|
+
'color:var(--fg-2);font-size:13px">Loading entities…</div>';
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
fetch(url)
|
|
241
|
+
.then(function (r) {
|
|
242
|
+
if (!r.ok) throw new Error('HTTP ' + r.status);
|
|
243
|
+
return r.json();
|
|
244
|
+
})
|
|
245
|
+
.then(function (d) {
|
|
246
|
+
_st = Object.assign({}, _st, {
|
|
247
|
+
entities: d.entities || [],
|
|
248
|
+
total: d.total || 0,
|
|
249
|
+
});
|
|
250
|
+
_renderList(id);
|
|
251
|
+
_renderPag(id, d.total || 0, offset);
|
|
252
|
+
// Auto-select first entity on initial load — matches design default state
|
|
253
|
+
if (!_st.selected && d.entities && d.entities.length > 0) {
|
|
254
|
+
_selectEntity(id, d.entities[0].name);
|
|
255
|
+
}
|
|
256
|
+
})
|
|
257
|
+
.catch(function (err) {
|
|
258
|
+
var el = document.getElementById(id + '-list');
|
|
259
|
+
if (el) {
|
|
260
|
+
el.innerHTML = '<div style="padding:24px;text-align:center;' +
|
|
261
|
+
'color:var(--danger);font-size:13px">Failed: ' + _esc(err.message) + '</div>';
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function _renderList(id) {
|
|
267
|
+
var q = _st.search.toLowerCase();
|
|
268
|
+
var tf = _st.typeFilter;
|
|
269
|
+
var all = _st.entities;
|
|
270
|
+
|
|
271
|
+
var filtered = all.filter(function (e) {
|
|
272
|
+
var typeOk = tf === 'all' || e.type === tf;
|
|
273
|
+
var searchOk = !q ||
|
|
274
|
+
(e.name || '').toLowerCase().indexOf(q) >= 0 ||
|
|
275
|
+
(e.type || '').toLowerCase().indexOf(q) >= 0 ||
|
|
276
|
+
(e.summary_preview || '').toLowerCase().indexOf(q) >= 0;
|
|
277
|
+
return typeOk && searchOk;
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
var cntEl = document.getElementById(id + '-count');
|
|
281
|
+
if (cntEl) cntEl.textContent = filtered.length + ' of ' + _st.total.toLocaleString();
|
|
282
|
+
|
|
283
|
+
var listEl = document.getElementById(id + '-list');
|
|
284
|
+
if (!listEl) return;
|
|
285
|
+
|
|
286
|
+
if (filtered.length === 0) {
|
|
287
|
+
listEl.innerHTML = '<div style="padding:40px;text-align:center;' +
|
|
288
|
+
'color:var(--fg-2);font-size:13px">' +
|
|
289
|
+
(q || tf !== 'all' ? 'No entities match this filter.' :
|
|
290
|
+
'No entities found. Entity extraction runs during consolidation.') +
|
|
291
|
+
'</div>';
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
listEl.innerHTML = filtered.map(function (e) {
|
|
296
|
+
var av = (e.name || '?').charAt(0).toUpperCase();
|
|
297
|
+
var color = _typeVar(e.type);
|
|
298
|
+
var isAct = _st.selected === e.name;
|
|
299
|
+
return '<div class="od-ent-row' + (isAct ? ' active' : '') + '" ' +
|
|
300
|
+
'data-od-act="select-entity" data-ename="' +
|
|
301
|
+
_esc(e.name).replace(/"/g, '"') + '">' +
|
|
302
|
+
'<div class="od-ent-av" style="background:' + color + '">' + _esc(av) + '</div>' +
|
|
303
|
+
'<div style="flex:1;min-width:0">' +
|
|
304
|
+
'<div style="font-weight:600;white-space:nowrap;overflow:hidden;' +
|
|
305
|
+
'text-overflow:ellipsis">' + _esc(e.name) + '</div>' +
|
|
306
|
+
'<div style="font-size:11.5px;color:var(--fg-3)">' +
|
|
307
|
+
_esc(e.type) + ' · ' + _esc(String(e.fact_count || 0)) + ' facts' +
|
|
308
|
+
'</div>' +
|
|
309
|
+
'</div>' +
|
|
310
|
+
'</div>';
|
|
311
|
+
}).join('');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function _renderPag(id, total, offset) {
|
|
315
|
+
var el = document.getElementById(id + '-pg');
|
|
316
|
+
if (!el) return;
|
|
317
|
+
var pg = Math.floor(offset / _st.pageSize);
|
|
318
|
+
var last = Math.max(0, Math.ceil(total / _st.pageSize) - 1);
|
|
319
|
+
if (last <= 0) { el.innerHTML = ''; return; }
|
|
320
|
+
|
|
321
|
+
el.innerHTML =
|
|
322
|
+
'<div style="display:flex;justify-content:center;gap:8px">' +
|
|
323
|
+
'<button class="btn sm" ' + (pg <= 0 ? 'disabled' :
|
|
324
|
+
'data-od-act="list-pg" data-page="' + (pg - 1) + '"') + '>← Prev</button>' +
|
|
325
|
+
'<span style="padding:6px;font-size:12px;color:var(--fg-2)">' +
|
|
326
|
+
(pg + 1) + '/' + (last + 1) + '</span>' +
|
|
327
|
+
'<button class="btn sm" ' + (pg >= last ? 'disabled' :
|
|
328
|
+
'data-od-act="list-pg" data-page="' + (pg + 1) + '"') + '>Next →</button>' +
|
|
329
|
+
'</div>';
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// ── Entity detail fetch & render ───────────────────────────────────────────
|
|
333
|
+
|
|
334
|
+
function _selectEntity(id, name) {
|
|
335
|
+
_st = Object.assign({}, _st, { selected: name });
|
|
336
|
+
// Refresh active state in list
|
|
337
|
+
_renderList(id);
|
|
338
|
+
|
|
339
|
+
var detEl = document.getElementById(id + '-detail');
|
|
340
|
+
if (!detEl) return;
|
|
341
|
+
detEl.innerHTML = '<div style="padding:60px;text-align:center;color:var(--fg-2)">' +
|
|
342
|
+
'<div style="font-size:13px">Loading ' + _esc(name) + '…</div></div>';
|
|
343
|
+
|
|
344
|
+
fetch('/api/entity/' + encodeURIComponent(name))
|
|
345
|
+
.then(function (r) {
|
|
346
|
+
if (!r.ok) throw new Error('HTTP ' + r.status);
|
|
347
|
+
return r.json();
|
|
348
|
+
})
|
|
349
|
+
.then(function (d) { _renderDetail(id, d); })
|
|
350
|
+
.catch(function (err) {
|
|
351
|
+
if (detEl) {
|
|
352
|
+
detEl.innerHTML = '<div style="padding:24px;color:var(--danger);font-size:13px">' +
|
|
353
|
+
'Could not load entity: ' + _esc(err.message) + '</div>';
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function _renderDetail(id, d) {
|
|
359
|
+
var detEl = document.getElementById(id + '-detail');
|
|
360
|
+
if (!detEl) return;
|
|
361
|
+
|
|
362
|
+
// Find entity in list for metadata not in detail endpoint
|
|
363
|
+
var listEnt = _st.entities.find(function (e) { return e.name === d.entity_name; }) || {};
|
|
364
|
+
var type = d.entity_type || listEnt.type || 'concept';
|
|
365
|
+
var color = _typeVar(type);
|
|
366
|
+
var badgeCls = _typeCls(type);
|
|
367
|
+
var factsCount = listEnt.fact_count || (d.source_fact_ids || []).length;
|
|
368
|
+
var av = (d.entity_name || '?').charAt(0).toUpperCase();
|
|
369
|
+
// summary: prefer full knowledge_summary; truncate to ~280 chars for inline display
|
|
370
|
+
// (matches design's 1–2-sentence paragraph density; full text in compiled-truth card)
|
|
371
|
+
var summaryRaw = d.knowledge_summary || listEnt.summary_preview || '';
|
|
372
|
+
var summary = summaryRaw.length > 280 ? summaryRaw.substring(0, 280) + '…' : summaryRaw;
|
|
373
|
+
|
|
374
|
+
detEl.innerHTML =
|
|
375
|
+
// Header card: avatar · name · type+facts badges · inline summary · dates row
|
|
376
|
+
'<div class="card">' +
|
|
377
|
+
'<div class="card-pad">' +
|
|
378
|
+
'<div style="display:flex;align-items:center;gap:14px">' +
|
|
379
|
+
'<div class="od-ent-av" style="width:48px;height:48px;border-radius:11px;' +
|
|
380
|
+
'font-size:18px;background:' + color + '">' + _esc(av) + '</div>' +
|
|
381
|
+
'<div>' +
|
|
382
|
+
'<h2 style="font-size:20px">' + _esc(d.entity_name || '') + '</h2>' +
|
|
383
|
+
'<div style="margin-top:4px">' +
|
|
384
|
+
'<span class="badge ' + _esc(badgeCls) + '">' + _esc(type) + '</span> ' +
|
|
385
|
+
'<span class="badge neutral">' + _esc(String(factsCount)) + ' facts</span>' +
|
|
386
|
+
'</div>' +
|
|
387
|
+
'</div>' +
|
|
388
|
+
'</div>' +
|
|
389
|
+
// Inline summary (matches design's <p class="muted"> placement)
|
|
390
|
+
(summary
|
|
391
|
+
? '<p class="muted" style="margin-top:14px;line-height:1.6">' + _esc(summary) + '</p>'
|
|
392
|
+
: '') +
|
|
393
|
+
// Dates row
|
|
394
|
+
'<div style="display:flex;gap:26px;margin-top:16px;flex-wrap:wrap">' +
|
|
395
|
+
_dateField('First seen', listEnt.first_seen) +
|
|
396
|
+
_dateField('Last seen', listEnt.last_seen) +
|
|
397
|
+
_dateField('Compiled', d.last_compiled_at) +
|
|
398
|
+
'</div>' +
|
|
399
|
+
'</div>' +
|
|
400
|
+
'</div>' +
|
|
401
|
+
|
|
402
|
+
// Compiled truth (extra card — not in design mock but valuable live data)
|
|
403
|
+
(d.compiled_truth ? _summaryCard('Compiled Truth', d.compiled_truth) : '') +
|
|
404
|
+
|
|
405
|
+
// Temporal timeline
|
|
406
|
+
'<div class="card" style="margin-top:16px">' +
|
|
407
|
+
'<div class="card-head">' +
|
|
408
|
+
'<h3>Temporal timeline</h3>' +
|
|
409
|
+
'<span class="sub">how this entity\'s facts changed · 3-date model</span>' +
|
|
410
|
+
'</div>' +
|
|
411
|
+
'<div class="card-pad">' +
|
|
412
|
+
_renderTimeline(d) +
|
|
413
|
+
'</div>' +
|
|
414
|
+
'</div>';
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function _dateField(label, iso) {
|
|
418
|
+
return '<div>' +
|
|
419
|
+
'<div class="dim" style="font-size:11px">' + _esc(label) + '</div>' +
|
|
420
|
+
'<div class="mono">' + _esc(_fmt(iso)) + '</div>' +
|
|
421
|
+
'</div>';
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function _summaryCard(title, text) {
|
|
425
|
+
return '<div class="card" style="margin-top:16px">' +
|
|
426
|
+
'<div class="card-head"><h3 style="font-size:13.5px">' + _esc(title) + '</h3></div>' +
|
|
427
|
+
'<div class="card-pad" style="font-size:13.5px;color:var(--fg-2);line-height:1.7;' +
|
|
428
|
+
'white-space:pre-wrap">' + _esc(text) + '</div>' +
|
|
429
|
+
'</div>';
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// CRIT-2: timeline is [] in real data — graceful empty state, no crash
|
|
433
|
+
function _renderTimeline(d) {
|
|
434
|
+
var events = d.timeline || [];
|
|
435
|
+
var facts = d.source_fact_ids || [];
|
|
436
|
+
|
|
437
|
+
if (events.length === 0) {
|
|
438
|
+
// Build a synthetic timeline from available metadata
|
|
439
|
+
var listEnt = _st.entities.find(function (e) { return e.name === d.entity_name; }) || {};
|
|
440
|
+
var synth = [];
|
|
441
|
+
if (listEnt.first_seen) {
|
|
442
|
+
synth.push({
|
|
443
|
+
ts: listEnt.first_seen,
|
|
444
|
+
kind: 'observation',
|
|
445
|
+
text: 'Entity first observed in memory store.',
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
if (listEnt.last_seen && listEnt.last_seen !== listEnt.first_seen) {
|
|
449
|
+
synth.push({
|
|
450
|
+
ts: listEnt.last_seen,
|
|
451
|
+
kind: 'referenced',
|
|
452
|
+
text: 'Most recent memory mentioning this entity.',
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
if (d.last_compiled_at) {
|
|
456
|
+
synth.push({
|
|
457
|
+
ts: d.last_compiled_at,
|
|
458
|
+
kind: 'compiled',
|
|
459
|
+
text: 'Knowledge summary last compiled.',
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
if (synth.length === 0) {
|
|
464
|
+
return '<div style="color:var(--fg-3);font-size:13px;padding:8px 0">' +
|
|
465
|
+
'No temporal events recorded yet. ' +
|
|
466
|
+
facts.length + ' source fact' + (facts.length === 1 ? '' : 's') + ' indexed.' +
|
|
467
|
+
// TODO: /api/entity/{name}/timeline — per-fact temporal history not yet exposed
|
|
468
|
+
'</div>';
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
return _buildTlHTML(synth);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Real timeline data (when daemon exposes it)
|
|
475
|
+
var items = events.map(function (ev) {
|
|
476
|
+
return {
|
|
477
|
+
ts: ev.timestamp || ev.date || '',
|
|
478
|
+
kind: ev.event_type || ev.type || 'observation',
|
|
479
|
+
text: ev.description || ev.content || '',
|
|
480
|
+
};
|
|
481
|
+
});
|
|
482
|
+
return _buildTlHTML(items);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function _buildTlHTML(items) {
|
|
486
|
+
var COLORS = {
|
|
487
|
+
observation: 'var(--cyan)',
|
|
488
|
+
referenced: 'var(--violet)',
|
|
489
|
+
interval: 'var(--warn)',
|
|
490
|
+
compiled: 'var(--ok)',
|
|
491
|
+
};
|
|
492
|
+
return '<div class="od-tl">' +
|
|
493
|
+
items.map(function (item) {
|
|
494
|
+
var col = COLORS[item.kind] || 'var(--fg-3)';
|
|
495
|
+
return '<div class="od-tl-item">' +
|
|
496
|
+
'<div class="od-tl-dot" style="background:' + col + '"></div>' +
|
|
497
|
+
'<div>' +
|
|
498
|
+
'<div style="display:flex;gap:8px;align-items:center">' +
|
|
499
|
+
'<span class="mono dim" style="font-size:12px">' +
|
|
500
|
+
_esc(_fmt(item.ts)) +
|
|
501
|
+
'</span>' +
|
|
502
|
+
'<span class="badge neutral" style="font-size:10px">' +
|
|
503
|
+
_esc(item.kind) +
|
|
504
|
+
'</span>' +
|
|
505
|
+
'</div>' +
|
|
506
|
+
'<div style="margin-top:3px;font-size:13.5px">' +
|
|
507
|
+
_esc(item.text) +
|
|
508
|
+
'</div>' +
|
|
509
|
+
'</div>' +
|
|
510
|
+
'</div>';
|
|
511
|
+
}).join('') +
|
|
512
|
+
'</div>';
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// ── Event delegation ──────────────────────────────────────────────────────
|
|
516
|
+
|
|
517
|
+
function _wire(container, id) {
|
|
518
|
+
container.addEventListener('click', function (e) {
|
|
519
|
+
var el = e.target.closest('[data-od-act]');
|
|
520
|
+
if (!el) return;
|
|
521
|
+
var act = el.dataset.odAct;
|
|
522
|
+
|
|
523
|
+
if (act === 'type-filter') {
|
|
524
|
+
_st = Object.assign({}, _st, { typeFilter: el.dataset.type, search: '' });
|
|
525
|
+
// Clear the search input DOM value so it stays in sync with state
|
|
526
|
+
var searchEl = document.getElementById(id + '-search');
|
|
527
|
+
if (searchEl) searchEl.value = '';
|
|
528
|
+
_refreshTypeChips(id, el.dataset.type);
|
|
529
|
+
_renderList(id);
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
if (act === 'select-entity') {
|
|
533
|
+
var name = el.dataset.ename;
|
|
534
|
+
if (name) _selectEntity(id, name);
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
if (act === 'list-pg') {
|
|
538
|
+
var pg = parseInt(el.dataset.page, 10);
|
|
539
|
+
if (!isNaN(pg)) {
|
|
540
|
+
_st = Object.assign({}, _st, { page: pg });
|
|
541
|
+
_loadList(id);
|
|
542
|
+
}
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
container.addEventListener('input', function (e) {
|
|
548
|
+
if (e.target.dataset.odAct === 'ent-search') {
|
|
549
|
+
_st = Object.assign({}, _st, { search: e.target.value.trim() });
|
|
550
|
+
_renderList(id);
|
|
551
|
+
}
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function _refreshTypeChips(id, active) {
|
|
556
|
+
var root = document.getElementById(id);
|
|
557
|
+
if (!root) return;
|
|
558
|
+
var wrap = root.querySelector('[data-od-act="type-filter"]');
|
|
559
|
+
if (!wrap) return;
|
|
560
|
+
var parent = wrap.parentNode;
|
|
561
|
+
if (!parent) return;
|
|
562
|
+
parent.innerHTML = _typeChips(id, active);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// ── Public API + auto-init ─────────────────────────────────────────────────
|
|
566
|
+
|
|
567
|
+
window.odRenderEntities = render;
|
|
568
|
+
|
|
569
|
+
if (document.readyState === 'loading') {
|
|
570
|
+
document.addEventListener('DOMContentLoaded', function () {
|
|
571
|
+
var pane = document.getElementById('entities-pane');
|
|
572
|
+
if (pane) render(pane);
|
|
573
|
+
});
|
|
574
|
+
} else {
|
|
575
|
+
var _pane = document.getElementById('entities-pane');
|
|
576
|
+
if (_pane) render(_pane);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
}());
|