superlocalmemory 2.8.6 → 3.0.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/LICENSE +9 -1
- package/NOTICE +63 -0
- package/README.md +165 -480
- package/bin/slm +17 -449
- package/bin/slm-npm +1 -1
- package/conftest.py +5 -0
- package/docs/api-reference.md +284 -0
- package/docs/architecture.md +149 -0
- package/docs/auto-memory.md +150 -0
- package/docs/cli-reference.md +276 -0
- package/docs/compliance.md +191 -0
- package/docs/configuration.md +182 -0
- package/docs/getting-started.md +102 -0
- package/docs/ide-setup.md +261 -0
- package/docs/mcp-tools.md +220 -0
- package/docs/migration-from-v2.md +170 -0
- package/docs/profiles.md +173 -0
- package/docs/troubleshooting.md +310 -0
- package/{configs → ide/configs}/antigravity-mcp.json +3 -3
- package/ide/configs/chatgpt-desktop-mcp.json +16 -0
- package/{configs → ide/configs}/claude-desktop-mcp.json +3 -3
- package/{configs → ide/configs}/codex-mcp.toml +4 -4
- package/{configs → ide/configs}/continue-mcp.yaml +4 -3
- package/{configs → ide/configs}/continue-skills.yaml +6 -6
- package/ide/configs/cursor-mcp.json +15 -0
- package/{configs → ide/configs}/gemini-cli-mcp.json +2 -2
- package/{configs → ide/configs}/jetbrains-mcp.json +2 -2
- package/{configs → ide/configs}/opencode-mcp.json +2 -2
- package/{configs → ide/configs}/perplexity-mcp.json +2 -2
- package/{configs → ide/configs}/vscode-copilot-mcp.json +2 -2
- package/{configs → ide/configs}/windsurf-mcp.json +3 -3
- package/{configs → ide/configs}/zed-mcp.json +2 -2
- package/{hooks → ide/hooks}/context-hook.js +9 -20
- package/ide/hooks/memory-list-skill.js +70 -0
- package/ide/hooks/memory-profile-skill.js +101 -0
- package/ide/hooks/memory-recall-skill.js +62 -0
- package/ide/hooks/memory-remember-skill.js +68 -0
- package/ide/hooks/memory-reset-skill.js +160 -0
- package/{hooks → ide/hooks}/post-recall-hook.js +2 -2
- package/ide/integrations/langchain/README.md +106 -0
- package/ide/integrations/langchain/langchain_superlocalmemory/__init__.py +9 -0
- package/ide/integrations/langchain/langchain_superlocalmemory/chat_message_history.py +201 -0
- package/ide/integrations/langchain/pyproject.toml +38 -0
- package/{src/learning → ide/integrations/langchain}/tests/__init__.py +1 -0
- package/ide/integrations/langchain/tests/test_chat_message_history.py +215 -0
- package/ide/integrations/langchain/tests/test_security.py +117 -0
- package/ide/integrations/llamaindex/README.md +81 -0
- package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/__init__.py +9 -0
- package/ide/integrations/llamaindex/llama_index/storage/chat_store/superlocalmemory/base.py +316 -0
- package/ide/integrations/llamaindex/pyproject.toml +43 -0
- package/{src/lifecycle → ide/integrations/llamaindex}/tests/__init__.py +1 -2
- package/ide/integrations/llamaindex/tests/test_chat_store.py +294 -0
- package/ide/integrations/llamaindex/tests/test_security.py +241 -0
- package/{skills → ide/skills}/slm-build-graph/SKILL.md +6 -6
- package/{skills → ide/skills}/slm-list-recent/SKILL.md +5 -5
- package/{skills → ide/skills}/slm-recall/SKILL.md +5 -5
- package/{skills → ide/skills}/slm-remember/SKILL.md +6 -6
- package/{skills → ide/skills}/slm-show-patterns/SKILL.md +7 -7
- package/{skills → ide/skills}/slm-status/SKILL.md +9 -9
- package/{skills → ide/skills}/slm-switch-profile/SKILL.md +9 -9
- package/package.json +13 -22
- package/pyproject.toml +85 -0
- package/scripts/build-dmg.sh +417 -0
- package/scripts/install-skills.ps1 +334 -0
- package/scripts/postinstall.js +2 -2
- package/scripts/start-dashboard.ps1 +52 -0
- package/scripts/start-dashboard.sh +41 -0
- package/scripts/sync-wiki.ps1 +127 -0
- package/scripts/sync-wiki.sh +82 -0
- package/scripts/test-dmg.sh +161 -0
- package/scripts/test-npm-package.ps1 +252 -0
- package/scripts/test-npm-package.sh +207 -0
- package/scripts/verify-install.ps1 +294 -0
- package/scripts/verify-install.sh +266 -0
- package/src/superlocalmemory/__init__.py +0 -0
- package/src/superlocalmemory/attribution/__init__.py +9 -0
- package/src/superlocalmemory/attribution/mathematical_dna.py +235 -0
- package/src/superlocalmemory/attribution/signer.py +153 -0
- package/src/superlocalmemory/attribution/watermark.py +189 -0
- package/src/superlocalmemory/cli/__init__.py +5 -0
- package/src/superlocalmemory/cli/commands.py +245 -0
- package/src/superlocalmemory/cli/main.py +89 -0
- package/src/superlocalmemory/cli/migrate_cmd.py +55 -0
- package/src/superlocalmemory/cli/post_install.py +99 -0
- package/src/superlocalmemory/cli/setup_wizard.py +129 -0
- package/src/superlocalmemory/compliance/__init__.py +0 -0
- package/src/superlocalmemory/compliance/abac.py +204 -0
- package/src/superlocalmemory/compliance/audit.py +314 -0
- package/src/superlocalmemory/compliance/eu_ai_act.py +131 -0
- package/src/superlocalmemory/compliance/gdpr.py +294 -0
- package/src/superlocalmemory/compliance/lifecycle.py +158 -0
- package/src/superlocalmemory/compliance/retention.py +232 -0
- package/src/superlocalmemory/compliance/scheduler.py +148 -0
- package/src/superlocalmemory/core/__init__.py +0 -0
- package/src/superlocalmemory/core/config.py +391 -0
- package/src/superlocalmemory/core/embeddings.py +293 -0
- package/src/superlocalmemory/core/engine.py +701 -0
- package/src/superlocalmemory/core/hooks.py +65 -0
- package/src/superlocalmemory/core/maintenance.py +172 -0
- package/src/superlocalmemory/core/modes.py +140 -0
- package/src/superlocalmemory/core/profiles.py +234 -0
- package/src/superlocalmemory/core/registry.py +117 -0
- package/src/superlocalmemory/dynamics/__init__.py +0 -0
- package/src/superlocalmemory/dynamics/fisher_langevin_coupling.py +223 -0
- package/src/superlocalmemory/encoding/__init__.py +0 -0
- package/src/superlocalmemory/encoding/consolidator.py +485 -0
- package/src/superlocalmemory/encoding/emotional.py +125 -0
- package/src/superlocalmemory/encoding/entity_resolver.py +525 -0
- package/src/superlocalmemory/encoding/entropy_gate.py +104 -0
- package/src/superlocalmemory/encoding/fact_extractor.py +775 -0
- package/src/superlocalmemory/encoding/foresight.py +91 -0
- package/src/superlocalmemory/encoding/graph_builder.py +302 -0
- package/src/superlocalmemory/encoding/observation_builder.py +160 -0
- package/src/superlocalmemory/encoding/scene_builder.py +183 -0
- package/src/superlocalmemory/encoding/signal_inference.py +90 -0
- package/src/superlocalmemory/encoding/temporal_parser.py +426 -0
- package/src/superlocalmemory/encoding/type_router.py +235 -0
- package/src/superlocalmemory/hooks/__init__.py +3 -0
- package/src/superlocalmemory/hooks/auto_capture.py +111 -0
- package/src/superlocalmemory/hooks/auto_recall.py +93 -0
- package/src/superlocalmemory/hooks/ide_connector.py +204 -0
- package/src/superlocalmemory/hooks/rules_engine.py +99 -0
- package/src/superlocalmemory/infra/__init__.py +3 -0
- package/src/superlocalmemory/infra/auth_middleware.py +82 -0
- package/src/superlocalmemory/infra/backup.py +317 -0
- package/src/superlocalmemory/infra/cache_manager.py +267 -0
- package/src/superlocalmemory/infra/event_bus.py +381 -0
- package/src/superlocalmemory/infra/rate_limiter.py +135 -0
- package/src/{webhook_dispatcher.py → superlocalmemory/infra/webhook_dispatcher.py} +104 -101
- package/src/superlocalmemory/learning/__init__.py +0 -0
- package/src/superlocalmemory/learning/adaptive.py +172 -0
- package/src/superlocalmemory/learning/behavioral.py +490 -0
- package/src/superlocalmemory/learning/behavioral_listener.py +94 -0
- package/src/superlocalmemory/learning/bootstrap.py +298 -0
- package/src/superlocalmemory/learning/cross_project.py +399 -0
- package/src/superlocalmemory/learning/database.py +376 -0
- package/src/superlocalmemory/learning/engagement.py +323 -0
- package/src/superlocalmemory/learning/features.py +138 -0
- package/src/superlocalmemory/learning/feedback.py +316 -0
- package/src/superlocalmemory/learning/outcomes.py +255 -0
- package/src/superlocalmemory/learning/project_context.py +366 -0
- package/src/superlocalmemory/learning/ranker.py +155 -0
- package/src/superlocalmemory/learning/source_quality.py +303 -0
- package/src/superlocalmemory/learning/workflows.py +309 -0
- package/src/superlocalmemory/llm/__init__.py +0 -0
- package/src/superlocalmemory/llm/backbone.py +316 -0
- package/src/superlocalmemory/math/__init__.py +0 -0
- package/src/superlocalmemory/math/fisher.py +356 -0
- package/src/superlocalmemory/math/langevin.py +398 -0
- package/src/superlocalmemory/math/sheaf.py +257 -0
- package/src/superlocalmemory/mcp/__init__.py +0 -0
- package/src/superlocalmemory/mcp/resources.py +245 -0
- package/src/superlocalmemory/mcp/server.py +61 -0
- package/src/superlocalmemory/mcp/tools.py +18 -0
- package/src/superlocalmemory/mcp/tools_core.py +305 -0
- package/src/superlocalmemory/mcp/tools_v28.py +223 -0
- package/src/superlocalmemory/mcp/tools_v3.py +286 -0
- package/src/superlocalmemory/retrieval/__init__.py +0 -0
- package/src/superlocalmemory/retrieval/agentic.py +295 -0
- package/src/superlocalmemory/retrieval/ann_index.py +223 -0
- package/src/superlocalmemory/retrieval/bm25_channel.py +185 -0
- package/src/superlocalmemory/retrieval/bridge_discovery.py +170 -0
- package/src/superlocalmemory/retrieval/engine.py +390 -0
- package/src/superlocalmemory/retrieval/entity_channel.py +179 -0
- package/src/superlocalmemory/retrieval/fusion.py +78 -0
- package/src/superlocalmemory/retrieval/profile_channel.py +105 -0
- package/src/superlocalmemory/retrieval/reranker.py +154 -0
- package/src/superlocalmemory/retrieval/semantic_channel.py +232 -0
- package/src/superlocalmemory/retrieval/strategy.py +96 -0
- package/src/superlocalmemory/retrieval/temporal_channel.py +175 -0
- package/src/superlocalmemory/server/__init__.py +1 -0
- package/src/superlocalmemory/server/api.py +248 -0
- package/src/superlocalmemory/server/routes/__init__.py +4 -0
- package/src/superlocalmemory/server/routes/agents.py +107 -0
- package/src/superlocalmemory/server/routes/backup.py +91 -0
- package/src/superlocalmemory/server/routes/behavioral.py +127 -0
- package/src/superlocalmemory/server/routes/compliance.py +160 -0
- package/src/superlocalmemory/server/routes/data_io.py +188 -0
- package/src/superlocalmemory/server/routes/events.py +183 -0
- package/src/superlocalmemory/server/routes/helpers.py +85 -0
- package/src/superlocalmemory/server/routes/learning.py +273 -0
- package/src/superlocalmemory/server/routes/lifecycle.py +116 -0
- package/src/superlocalmemory/server/routes/memories.py +399 -0
- package/src/superlocalmemory/server/routes/profiles.py +219 -0
- package/src/superlocalmemory/server/routes/stats.py +346 -0
- package/src/superlocalmemory/server/routes/v3_api.py +365 -0
- package/src/superlocalmemory/server/routes/ws.py +82 -0
- package/src/superlocalmemory/server/security_middleware.py +57 -0
- package/src/superlocalmemory/server/ui.py +245 -0
- package/src/superlocalmemory/storage/__init__.py +0 -0
- package/src/superlocalmemory/storage/access_control.py +182 -0
- package/src/superlocalmemory/storage/database.py +594 -0
- package/src/superlocalmemory/storage/migrations.py +303 -0
- package/src/superlocalmemory/storage/models.py +406 -0
- package/src/superlocalmemory/storage/schema.py +726 -0
- package/src/superlocalmemory/storage/v2_migrator.py +317 -0
- package/src/superlocalmemory/trust/__init__.py +0 -0
- package/src/superlocalmemory/trust/gate.py +130 -0
- package/src/superlocalmemory/trust/provenance.py +124 -0
- package/src/superlocalmemory/trust/scorer.py +347 -0
- package/src/superlocalmemory/trust/signals.py +153 -0
- package/ui/index.html +278 -5
- package/ui/js/auto-settings.js +70 -0
- package/ui/js/dashboard.js +90 -0
- package/ui/js/fact-detail.js +92 -0
- package/ui/js/feedback.js +2 -2
- package/ui/js/ide-status.js +102 -0
- package/ui/js/math-health.js +98 -0
- package/ui/js/recall-lab.js +127 -0
- package/ui/js/settings.js +2 -2
- package/ui/js/trust-dashboard.js +73 -0
- package/api_server.py +0 -724
- package/bin/aider-smart +0 -72
- package/bin/superlocalmemoryv2-learning +0 -4
- package/bin/superlocalmemoryv2-list +0 -3
- package/bin/superlocalmemoryv2-patterns +0 -4
- package/bin/superlocalmemoryv2-profile +0 -3
- package/bin/superlocalmemoryv2-recall +0 -3
- package/bin/superlocalmemoryv2-remember +0 -3
- package/bin/superlocalmemoryv2-reset +0 -3
- package/bin/superlocalmemoryv2-status +0 -3
- package/configs/chatgpt-desktop-mcp.json +0 -16
- package/configs/cursor-mcp.json +0 -15
- package/hooks/memory-list-skill.js +0 -139
- package/hooks/memory-profile-skill.js +0 -273
- package/hooks/memory-recall-skill.js +0 -114
- package/hooks/memory-remember-skill.js +0 -127
- package/hooks/memory-reset-skill.js +0 -274
- package/mcp_server.py +0 -1808
- package/requirements-core.txt +0 -22
- package/requirements-learning.txt +0 -12
- package/requirements.txt +0 -12
- package/src/agent_registry.py +0 -411
- package/src/auth_middleware.py +0 -61
- package/src/auto_backup.py +0 -459
- package/src/behavioral/__init__.py +0 -49
- package/src/behavioral/behavioral_listener.py +0 -203
- package/src/behavioral/behavioral_patterns.py +0 -275
- package/src/behavioral/cross_project_transfer.py +0 -206
- package/src/behavioral/outcome_inference.py +0 -194
- package/src/behavioral/outcome_tracker.py +0 -193
- package/src/behavioral/tests/__init__.py +0 -4
- package/src/behavioral/tests/test_behavioral_integration.py +0 -108
- package/src/behavioral/tests/test_behavioral_patterns.py +0 -150
- package/src/behavioral/tests/test_cross_project_transfer.py +0 -142
- package/src/behavioral/tests/test_mcp_behavioral.py +0 -139
- package/src/behavioral/tests/test_mcp_report_outcome.py +0 -117
- package/src/behavioral/tests/test_outcome_inference.py +0 -107
- package/src/behavioral/tests/test_outcome_tracker.py +0 -96
- package/src/cache_manager.py +0 -518
- package/src/compliance/__init__.py +0 -48
- package/src/compliance/abac_engine.py +0 -149
- package/src/compliance/abac_middleware.py +0 -116
- package/src/compliance/audit_db.py +0 -215
- package/src/compliance/audit_logger.py +0 -148
- package/src/compliance/retention_manager.py +0 -289
- package/src/compliance/retention_scheduler.py +0 -186
- package/src/compliance/tests/__init__.py +0 -4
- package/src/compliance/tests/test_abac_enforcement.py +0 -95
- package/src/compliance/tests/test_abac_engine.py +0 -124
- package/src/compliance/tests/test_abac_mcp_integration.py +0 -118
- package/src/compliance/tests/test_audit_db.py +0 -123
- package/src/compliance/tests/test_audit_logger.py +0 -98
- package/src/compliance/tests/test_mcp_audit.py +0 -128
- package/src/compliance/tests/test_mcp_retention_policy.py +0 -125
- package/src/compliance/tests/test_retention_manager.py +0 -131
- package/src/compliance/tests/test_retention_scheduler.py +0 -99
- package/src/compression/__init__.py +0 -25
- package/src/compression/cli.py +0 -150
- package/src/compression/cold_storage.py +0 -217
- package/src/compression/config.py +0 -72
- package/src/compression/orchestrator.py +0 -133
- package/src/compression/tier2_compressor.py +0 -228
- package/src/compression/tier3_compressor.py +0 -153
- package/src/compression/tier_classifier.py +0 -148
- package/src/db_connection_manager.py +0 -536
- package/src/embedding_engine.py +0 -63
- package/src/embeddings/__init__.py +0 -47
- package/src/embeddings/cache.py +0 -70
- package/src/embeddings/cli.py +0 -113
- package/src/embeddings/constants.py +0 -47
- package/src/embeddings/database.py +0 -91
- package/src/embeddings/engine.py +0 -247
- package/src/embeddings/model_loader.py +0 -145
- package/src/event_bus.py +0 -562
- package/src/graph/__init__.py +0 -36
- package/src/graph/build_helpers.py +0 -74
- package/src/graph/cli.py +0 -87
- package/src/graph/cluster_builder.py +0 -188
- package/src/graph/cluster_summary.py +0 -148
- package/src/graph/constants.py +0 -47
- package/src/graph/edge_builder.py +0 -162
- package/src/graph/entity_extractor.py +0 -95
- package/src/graph/graph_core.py +0 -226
- package/src/graph/graph_search.py +0 -231
- package/src/graph/hierarchical.py +0 -207
- package/src/graph/schema.py +0 -99
- package/src/graph_engine.py +0 -52
- package/src/hnsw_index.py +0 -628
- package/src/hybrid_search.py +0 -46
- package/src/learning/__init__.py +0 -217
- package/src/learning/adaptive_ranker.py +0 -682
- package/src/learning/bootstrap/__init__.py +0 -69
- package/src/learning/bootstrap/constants.py +0 -93
- package/src/learning/bootstrap/db_queries.py +0 -316
- package/src/learning/bootstrap/sampling.py +0 -82
- package/src/learning/bootstrap/text_utils.py +0 -71
- package/src/learning/cross_project_aggregator.py +0 -857
- package/src/learning/db/__init__.py +0 -40
- package/src/learning/db/constants.py +0 -44
- package/src/learning/db/schema.py +0 -279
- package/src/learning/engagement_tracker.py +0 -628
- package/src/learning/feature_extractor.py +0 -708
- package/src/learning/feedback_collector.py +0 -806
- package/src/learning/learning_db.py +0 -915
- package/src/learning/project_context_manager.py +0 -572
- package/src/learning/ranking/__init__.py +0 -33
- package/src/learning/ranking/constants.py +0 -84
- package/src/learning/ranking/helpers.py +0 -278
- package/src/learning/source_quality_scorer.py +0 -676
- package/src/learning/synthetic_bootstrap.py +0 -755
- package/src/learning/tests/test_adaptive_ranker.py +0 -325
- package/src/learning/tests/test_adaptive_ranker_v28.py +0 -60
- package/src/learning/tests/test_aggregator.py +0 -306
- package/src/learning/tests/test_auto_retrain_v28.py +0 -35
- package/src/learning/tests/test_e2e_ranking_v28.py +0 -82
- package/src/learning/tests/test_feature_extractor_v28.py +0 -93
- package/src/learning/tests/test_feedback_collector.py +0 -294
- package/src/learning/tests/test_learning_db.py +0 -602
- package/src/learning/tests/test_learning_db_v28.py +0 -110
- package/src/learning/tests/test_learning_init_v28.py +0 -48
- package/src/learning/tests/test_outcome_signals.py +0 -48
- package/src/learning/tests/test_project_context.py +0 -292
- package/src/learning/tests/test_schema_migration.py +0 -319
- package/src/learning/tests/test_signal_inference.py +0 -397
- package/src/learning/tests/test_source_quality.py +0 -351
- package/src/learning/tests/test_synthetic_bootstrap.py +0 -429
- package/src/learning/tests/test_workflow_miner.py +0 -318
- package/src/learning/workflow_pattern_miner.py +0 -655
- package/src/lifecycle/__init__.py +0 -54
- package/src/lifecycle/bounded_growth.py +0 -239
- package/src/lifecycle/compaction_engine.py +0 -226
- package/src/lifecycle/lifecycle_engine.py +0 -355
- package/src/lifecycle/lifecycle_evaluator.py +0 -257
- package/src/lifecycle/lifecycle_scheduler.py +0 -130
- package/src/lifecycle/retention_policy.py +0 -285
- package/src/lifecycle/tests/test_bounded_growth.py +0 -193
- package/src/lifecycle/tests/test_compaction.py +0 -179
- package/src/lifecycle/tests/test_lifecycle_engine.py +0 -137
- package/src/lifecycle/tests/test_lifecycle_evaluation.py +0 -177
- package/src/lifecycle/tests/test_lifecycle_scheduler.py +0 -127
- package/src/lifecycle/tests/test_lifecycle_search.py +0 -109
- package/src/lifecycle/tests/test_mcp_compact.py +0 -149
- package/src/lifecycle/tests/test_mcp_lifecycle_status.py +0 -114
- package/src/lifecycle/tests/test_retention_policy.py +0 -162
- package/src/mcp_tools_v28.py +0 -281
- package/src/memory/__init__.py +0 -36
- package/src/memory/cli.py +0 -205
- package/src/memory/constants.py +0 -39
- package/src/memory/helpers.py +0 -28
- package/src/memory/schema.py +0 -166
- package/src/memory-profiles.py +0 -595
- package/src/memory-reset.py +0 -491
- package/src/memory_compression.py +0 -989
- package/src/memory_store_v2.py +0 -1155
- package/src/migrate_v1_to_v2.py +0 -629
- package/src/pattern_learner.py +0 -34
- package/src/patterns/__init__.py +0 -24
- package/src/patterns/analyzers.py +0 -251
- package/src/patterns/learner.py +0 -271
- package/src/patterns/scoring.py +0 -171
- package/src/patterns/store.py +0 -225
- package/src/patterns/terminology.py +0 -140
- package/src/provenance_tracker.py +0 -312
- package/src/qualixar_attribution.py +0 -139
- package/src/qualixar_watermark.py +0 -78
- package/src/query_optimizer.py +0 -511
- package/src/rate_limiter.py +0 -83
- package/src/search/__init__.py +0 -20
- package/src/search/cli.py +0 -77
- package/src/search/constants.py +0 -26
- package/src/search/engine.py +0 -241
- package/src/search/fusion.py +0 -122
- package/src/search/index_loader.py +0 -114
- package/src/search/methods.py +0 -162
- package/src/search_engine_v2.py +0 -401
- package/src/setup_validator.py +0 -482
- package/src/subscription_manager.py +0 -391
- package/src/tree/__init__.py +0 -59
- package/src/tree/builder.py +0 -185
- package/src/tree/nodes.py +0 -202
- package/src/tree/queries.py +0 -257
- package/src/tree/schema.py +0 -80
- package/src/tree_manager.py +0 -19
- package/src/trust/__init__.py +0 -45
- package/src/trust/constants.py +0 -66
- package/src/trust/queries.py +0 -157
- package/src/trust/schema.py +0 -95
- package/src/trust/scorer.py +0 -299
- package/src/trust/signals.py +0 -95
- package/src/trust_scorer.py +0 -44
- package/ui/app.js +0 -1588
- package/ui/js/graph-cytoscape-monolithic-backup.js +0 -1168
- package/ui/js/graph-cytoscape.js +0 -1168
- package/ui/js/graph-d3-backup.js +0 -32
- package/ui/js/graph.js +0 -32
- package/ui_server.py +0 -286
- /package/docs/{ACCESSIBILITY.md → v2-archive/ACCESSIBILITY.md} +0 -0
- /package/docs/{ARCHITECTURE.md → v2-archive/ARCHITECTURE.md} +0 -0
- /package/docs/{CLI-COMMANDS-REFERENCE.md → v2-archive/CLI-COMMANDS-REFERENCE.md} +0 -0
- /package/docs/{COMPRESSION-README.md → v2-archive/COMPRESSION-README.md} +0 -0
- /package/docs/{FRAMEWORK-INTEGRATIONS.md → v2-archive/FRAMEWORK-INTEGRATIONS.md} +0 -0
- /package/docs/{MCP-MANUAL-SETUP.md → v2-archive/MCP-MANUAL-SETUP.md} +0 -0
- /package/docs/{MCP-TROUBLESHOOTING.md → v2-archive/MCP-TROUBLESHOOTING.md} +0 -0
- /package/docs/{PATTERN-LEARNING.md → v2-archive/PATTERN-LEARNING.md} +0 -0
- /package/docs/{PROFILES-GUIDE.md → v2-archive/PROFILES-GUIDE.md} +0 -0
- /package/docs/{RESET-GUIDE.md → v2-archive/RESET-GUIDE.md} +0 -0
- /package/docs/{SEARCH-ENGINE-V2.2.0.md → v2-archive/SEARCH-ENGINE-V2.2.0.md} +0 -0
- /package/docs/{SEARCH-INTEGRATION-GUIDE.md → v2-archive/SEARCH-INTEGRATION-GUIDE.md} +0 -0
- /package/docs/{UI-SERVER.md → v2-archive/UI-SERVER.md} +0 -0
- /package/docs/{UNIVERSAL-INTEGRATION.md → v2-archive/UNIVERSAL-INTEGRATION.md} +0 -0
- /package/docs/{V2.2.0-OPTIONAL-SEARCH.md → v2-archive/V2.2.0-OPTIONAL-SEARCH.md} +0 -0
- /package/docs/{WINDOWS-INSTALL-README.txt → v2-archive/WINDOWS-INSTALL-README.txt} +0 -0
- /package/docs/{WINDOWS-POST-INSTALL.txt → v2-archive/WINDOWS-POST-INSTALL.txt} +0 -0
- /package/docs/{example_graph_usage.py → v2-archive/example_graph_usage.py} +0 -0
- /package/{completions → ide/completions}/slm.bash +0 -0
- /package/{completions → ide/completions}/slm.zsh +0 -0
- /package/{configs → ide/configs}/cody-commands.json +0 -0
- /package/{install-skills.sh → scripts/install-skills.sh} +0 -0
- /package/{install.ps1 → scripts/install.ps1} +0 -0
- /package/{install.sh → scripts/install.sh} +0 -0
package/ui/app.js
DELETED
|
@@ -1,1588 +0,0 @@
|
|
|
1
|
-
// SuperLocalMemory V2 - UI Application
|
|
2
|
-
// Note: All data from API is from our own trusted local database.
|
|
3
|
-
// All user-facing strings are escaped via escapeHtml() before DOM insertion.
|
|
4
|
-
// innerHTML usage is safe here: all dynamic values are sanitized, and no
|
|
5
|
-
// external/untrusted input reaches the DOM.
|
|
6
|
-
|
|
7
|
-
let graphData = { nodes: [], links: [] };
|
|
8
|
-
let currentMemoryDetail = null; // Memory currently shown in modal
|
|
9
|
-
let lastSearchResults = null; // Cached search results for export
|
|
10
|
-
|
|
11
|
-
// ============================================================================
|
|
12
|
-
// Dark Mode
|
|
13
|
-
// ============================================================================
|
|
14
|
-
|
|
15
|
-
function initDarkMode() {
|
|
16
|
-
var saved = localStorage.getItem('slm-theme');
|
|
17
|
-
var theme;
|
|
18
|
-
if (saved) {
|
|
19
|
-
theme = saved;
|
|
20
|
-
} else {
|
|
21
|
-
// Respect system preference on first load
|
|
22
|
-
theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
23
|
-
}
|
|
24
|
-
applyTheme(theme);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function applyTheme(theme) {
|
|
28
|
-
document.documentElement.setAttribute('data-bs-theme', theme);
|
|
29
|
-
var icon = document.getElementById('theme-icon');
|
|
30
|
-
if (icon) {
|
|
31
|
-
icon.className = theme === 'dark' ? 'bi bi-moon-stars-fill' : 'bi bi-sun-fill';
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function toggleDarkMode() {
|
|
36
|
-
var current = document.documentElement.getAttribute('data-bs-theme');
|
|
37
|
-
var next = current === 'dark' ? 'light' : 'dark';
|
|
38
|
-
localStorage.setItem('slm-theme', next);
|
|
39
|
-
applyTheme(next);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// ============================================================================
|
|
43
|
-
// Animated Counter
|
|
44
|
-
// ============================================================================
|
|
45
|
-
|
|
46
|
-
function animateCounter(elementId, target) {
|
|
47
|
-
var el = document.getElementById(elementId);
|
|
48
|
-
if (!el) return;
|
|
49
|
-
var duration = 600;
|
|
50
|
-
var startTime = null;
|
|
51
|
-
|
|
52
|
-
function step(timestamp) {
|
|
53
|
-
if (!startTime) startTime = timestamp;
|
|
54
|
-
var progress = Math.min((timestamp - startTime) / duration, 1);
|
|
55
|
-
var eased = 1 - Math.pow(1 - progress, 3); // ease-out cubic
|
|
56
|
-
el.textContent = Math.floor(eased * target).toLocaleString();
|
|
57
|
-
if (progress < 1) {
|
|
58
|
-
requestAnimationFrame(step);
|
|
59
|
-
} else {
|
|
60
|
-
el.textContent = target.toLocaleString();
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (target === 0) {
|
|
65
|
-
el.textContent = '0';
|
|
66
|
-
} else {
|
|
67
|
-
requestAnimationFrame(step);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// ============================================================================
|
|
72
|
-
// HTML Escaping — all dynamic text MUST pass through this before DOM insertion
|
|
73
|
-
// ============================================================================
|
|
74
|
-
|
|
75
|
-
function escapeHtml(text) {
|
|
76
|
-
if (!text) return '';
|
|
77
|
-
var div = document.createElement('div');
|
|
78
|
-
div.appendChild(document.createTextNode(String(text)));
|
|
79
|
-
return div.innerHTML;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// ============================================================================
|
|
83
|
-
// Loading / Empty State helpers
|
|
84
|
-
// ============================================================================
|
|
85
|
-
|
|
86
|
-
function showLoading(containerId, message) {
|
|
87
|
-
var el = document.getElementById(containerId);
|
|
88
|
-
if (!el) return;
|
|
89
|
-
// Build DOM nodes instead of innerHTML for loading state
|
|
90
|
-
el.textContent = '';
|
|
91
|
-
var wrapper = document.createElement('div');
|
|
92
|
-
wrapper.className = 'loading';
|
|
93
|
-
var spinner = document.createElement('div');
|
|
94
|
-
spinner.className = 'spinner-border text-primary';
|
|
95
|
-
spinner.setAttribute('role', 'status');
|
|
96
|
-
var msg = document.createElement('div');
|
|
97
|
-
msg.textContent = message || 'Loading...';
|
|
98
|
-
wrapper.appendChild(spinner);
|
|
99
|
-
wrapper.appendChild(msg);
|
|
100
|
-
el.appendChild(wrapper);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function showEmpty(containerId, icon, message) {
|
|
104
|
-
var el = document.getElementById(containerId);
|
|
105
|
-
if (!el) return;
|
|
106
|
-
el.textContent = '';
|
|
107
|
-
var wrapper = document.createElement('div');
|
|
108
|
-
wrapper.className = 'empty-state';
|
|
109
|
-
var iconEl = document.createElement('i');
|
|
110
|
-
iconEl.className = 'bi bi-' + icon + ' d-block';
|
|
111
|
-
var p = document.createElement('p');
|
|
112
|
-
p.textContent = message;
|
|
113
|
-
wrapper.appendChild(iconEl);
|
|
114
|
-
wrapper.appendChild(p);
|
|
115
|
-
el.appendChild(wrapper);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// ============================================================================
|
|
119
|
-
// Safe HTML builder — constructs sanitized HTML strings from trusted templates
|
|
120
|
-
// and escaped dynamic values. Used for table/card rendering where DOM-node-by-
|
|
121
|
-
// node construction would be impractical for 50+ row tables.
|
|
122
|
-
// ============================================================================
|
|
123
|
-
|
|
124
|
-
function safeHtml(templateParts) {
|
|
125
|
-
// Tagged template literal helper: safeHtml`<b>${userValue}</b>`
|
|
126
|
-
// All interpolated values are auto-escaped.
|
|
127
|
-
var args = Array.prototype.slice.call(arguments, 1);
|
|
128
|
-
var result = '';
|
|
129
|
-
for (var i = 0; i < templateParts.length; i++) {
|
|
130
|
-
result += templateParts[i];
|
|
131
|
-
if (i < args.length) {
|
|
132
|
-
result += escapeHtml(String(args[i]));
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
return result;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// ============================================================================
|
|
139
|
-
// Stats
|
|
140
|
-
// ============================================================================
|
|
141
|
-
|
|
142
|
-
async function loadStats() {
|
|
143
|
-
try {
|
|
144
|
-
var response = await fetch('/api/stats');
|
|
145
|
-
var data = await response.json();
|
|
146
|
-
animateCounter('stat-memories', data.overview.total_memories);
|
|
147
|
-
animateCounter('stat-clusters', data.overview.total_clusters);
|
|
148
|
-
animateCounter('stat-nodes', data.overview.graph_nodes);
|
|
149
|
-
animateCounter('stat-edges', data.overview.graph_edges);
|
|
150
|
-
populateFilters(data.categories, data.projects);
|
|
151
|
-
} catch (error) {
|
|
152
|
-
console.error('Error loading stats:', error);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function populateFilters(categories, projects) {
|
|
157
|
-
var categorySelect = document.getElementById('filter-category');
|
|
158
|
-
var projectSelect = document.getElementById('filter-project');
|
|
159
|
-
categories.forEach(function(cat) {
|
|
160
|
-
if (cat.category) {
|
|
161
|
-
var option = document.createElement('option');
|
|
162
|
-
option.value = cat.category;
|
|
163
|
-
option.textContent = cat.category + ' (' + cat.count + ')';
|
|
164
|
-
categorySelect.appendChild(option);
|
|
165
|
-
}
|
|
166
|
-
});
|
|
167
|
-
projects.forEach(function(proj) {
|
|
168
|
-
if (proj.project_name) {
|
|
169
|
-
var option = document.createElement('option');
|
|
170
|
-
option.value = proj.project_name;
|
|
171
|
-
option.textContent = proj.project_name + ' (' + proj.count + ')';
|
|
172
|
-
projectSelect.appendChild(option);
|
|
173
|
-
}
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// ============================================================================
|
|
178
|
-
// Graph
|
|
179
|
-
// ============================================================================
|
|
180
|
-
|
|
181
|
-
async function loadGraph() {
|
|
182
|
-
var maxNodes = document.getElementById('graph-max-nodes').value;
|
|
183
|
-
try {
|
|
184
|
-
var response = await fetch('/api/graph?max_nodes=' + maxNodes);
|
|
185
|
-
graphData = await response.json();
|
|
186
|
-
renderGraph(graphData);
|
|
187
|
-
} catch (error) {
|
|
188
|
-
console.error('Error loading graph:', error);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function renderGraph(data) {
|
|
193
|
-
var container = document.getElementById('graph-container');
|
|
194
|
-
container.textContent = '';
|
|
195
|
-
var width = container.clientWidth || 1200;
|
|
196
|
-
var height = 600;
|
|
197
|
-
var svg = d3.select('#graph-container').append('svg').attr('width', width).attr('height', height);
|
|
198
|
-
var tooltip = d3.select('body').append('div').attr('class', 'tooltip-custom').style('opacity', 0);
|
|
199
|
-
var colorScale = d3.scaleOrdinal(d3.schemeCategory10);
|
|
200
|
-
var simulation = d3.forceSimulation(data.nodes).force('link', d3.forceLink(data.links).id(function(d) { return d.id; }).distance(100)).force('charge', d3.forceManyBody().strength(-200)).force('center', d3.forceCenter(width / 2, height / 2)).force('collision', d3.forceCollide().radius(20));
|
|
201
|
-
var link = svg.append('g').selectAll('line').data(data.links).enter().append('line').attr('class', 'link').attr('stroke-width', function(d) { return Math.sqrt(d.weight * 2); });
|
|
202
|
-
var node = svg.append('g').selectAll('circle').data(data.nodes).enter().append('circle').attr('class', 'node').attr('r', function(d) { return 5 + (d.importance || 5); }).attr('fill', function(d) { return colorScale(d.cluster_id || 0); }).call(d3.drag().on('start', dragStarted).on('drag', dragged).on('end', dragEnded)).on('mouseover', function(event, d) { tooltip.transition().duration(200).style('opacity', .9); var label = d.category || d.project_name || 'Memory #' + d.id; tooltip.text(label + ': ' + (d.content_preview || d.summary || 'No content')).style('left', (event.pageX + 10) + 'px').style('top', (event.pageY - 28) + 'px'); }).on('mouseout', function() { tooltip.transition().duration(500).style('opacity', 0); }).on('click', function(event, d) { openMemoryDetail(d); });
|
|
203
|
-
simulation.on('tick', function() { link.attr('x1', function(d) { return d.source.x; }).attr('y1', function(d) { return d.source.y; }).attr('x2', function(d) { return d.target.x; }).attr('y2', function(d) { return d.target.y; }); node.attr('cx', function(d) { return d.x; }).attr('cy', function(d) { return d.y; }); });
|
|
204
|
-
function dragStarted(event, d) { if (!event.active) simulation.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y; }
|
|
205
|
-
function dragged(event, d) { d.fx = event.x; d.fy = event.y; }
|
|
206
|
-
function dragEnded(event, d) { if (!event.active) simulation.alphaTarget(0); d.fx = null; d.fy = null; }
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// ============================================================================
|
|
210
|
-
// Memories
|
|
211
|
-
// ============================================================================
|
|
212
|
-
|
|
213
|
-
async function loadMemories() {
|
|
214
|
-
var category = document.getElementById('filter-category').value;
|
|
215
|
-
var project = document.getElementById('filter-project').value;
|
|
216
|
-
var url = '/api/memories?limit=50';
|
|
217
|
-
if (category) url += '&category=' + encodeURIComponent(category);
|
|
218
|
-
if (project) url += '&project_name=' + encodeURIComponent(project);
|
|
219
|
-
|
|
220
|
-
showLoading('memories-list', 'Loading memories...');
|
|
221
|
-
try {
|
|
222
|
-
var response = await fetch(url);
|
|
223
|
-
var data = await response.json();
|
|
224
|
-
lastSearchResults = null; // Clear search cache when browsing
|
|
225
|
-
var exportBtn = document.getElementById('export-search-btn');
|
|
226
|
-
if (exportBtn) exportBtn.style.display = 'none';
|
|
227
|
-
renderMemoriesTable(data.memories, false);
|
|
228
|
-
} catch (error) {
|
|
229
|
-
console.error('Error loading memories:', error);
|
|
230
|
-
showEmpty('memories-list', 'exclamation-triangle', 'Failed to load memories');
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
function renderMemoriesTable(memories, showScores) {
|
|
235
|
-
var container = document.getElementById('memories-list');
|
|
236
|
-
if (!memories || memories.length === 0) {
|
|
237
|
-
showEmpty('memories-list', 'journal-x', 'No memories found. Try a different search or filter.');
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// Store memories for row-click access
|
|
242
|
-
window._slmMemories = memories;
|
|
243
|
-
|
|
244
|
-
var scoreHeader = showScores ? '<th>Score</th>' : '';
|
|
245
|
-
|
|
246
|
-
// All dynamic values below are escaped via escapeHtml() — safe for innerHTML.
|
|
247
|
-
var rows = '';
|
|
248
|
-
memories.forEach(function(mem, idx) {
|
|
249
|
-
var content = mem.summary || mem.content || '';
|
|
250
|
-
var contentPreview = content.length > 100 ? content.substring(0, 100) + '...' : content;
|
|
251
|
-
var importance = mem.importance || 5;
|
|
252
|
-
var importanceClass = importance >= 8 ? 'success' : importance >= 5 ? 'warning' : 'secondary';
|
|
253
|
-
|
|
254
|
-
var scoreCell = '';
|
|
255
|
-
if (showScores) {
|
|
256
|
-
var score = mem.score || 0;
|
|
257
|
-
var pct = Math.round(score * 100);
|
|
258
|
-
var barColor = pct >= 70 ? '#43e97b' : pct >= 40 ? '#f9c74f' : '#f94144';
|
|
259
|
-
scoreCell = '<td><span class="score-label">' + escapeHtml(String(pct)) + '%</span>'
|
|
260
|
-
+ '<div class="score-bar-container"><div class="score-bar">'
|
|
261
|
-
+ '<div class="score-bar-fill" style="width:' + pct + '%;background:' + barColor + '"></div>'
|
|
262
|
-
+ '</div></div></td>';
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
rows += '<tr data-mem-idx="' + idx + '">'
|
|
266
|
-
+ '<td>' + escapeHtml(String(mem.id)) + '</td>'
|
|
267
|
-
+ '<td><span class="badge bg-primary">' + escapeHtml(mem.category || 'None') + '</span></td>'
|
|
268
|
-
+ '<td><small>' + escapeHtml(mem.project_name || '-') + '</small></td>'
|
|
269
|
-
+ '<td class="memory-content" title="' + escapeHtml(content) + '">' + escapeHtml(contentPreview) + '</td>'
|
|
270
|
-
+ scoreCell
|
|
271
|
-
+ '<td><span class="badge bg-' + importanceClass + ' badge-importance">' + escapeHtml(String(importance)) + '</span></td>'
|
|
272
|
-
+ '<td>' + escapeHtml(String(mem.cluster_id || '-')) + '</td>'
|
|
273
|
-
+ '<td><small>' + escapeHtml(formatDate(mem.created_at)) + '</small></td>'
|
|
274
|
-
+ '</tr>';
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
var html = '<table class="table table-hover memory-table"><thead><tr>'
|
|
278
|
-
+ '<th class="sortable" data-sort="id">ID</th>'
|
|
279
|
-
+ '<th class="sortable" data-sort="category">Category</th>'
|
|
280
|
-
+ '<th class="sortable" data-sort="project">Project</th>'
|
|
281
|
-
+ '<th>Content</th>'
|
|
282
|
-
+ scoreHeader
|
|
283
|
-
+ '<th class="sortable" data-sort="importance">Importance</th>'
|
|
284
|
-
+ '<th>Cluster</th>'
|
|
285
|
-
+ '<th class="sortable" data-sort="created">Created</th>'
|
|
286
|
-
+ '</tr></thead><tbody>' + rows + '</tbody></table>';
|
|
287
|
-
|
|
288
|
-
// Safe: all interpolated values above are escaped via escapeHtml()
|
|
289
|
-
container.innerHTML = html; // nosemgrep: innerHTML-xss — all values escaped
|
|
290
|
-
|
|
291
|
-
// Attach click handlers via delegation
|
|
292
|
-
var table = container.querySelector('table');
|
|
293
|
-
if (table) {
|
|
294
|
-
table.addEventListener('click', function(e) {
|
|
295
|
-
// Check if clicking a sortable header
|
|
296
|
-
var th = e.target.closest('th.sortable');
|
|
297
|
-
if (th) {
|
|
298
|
-
handleSort(th);
|
|
299
|
-
return;
|
|
300
|
-
}
|
|
301
|
-
var row = e.target.closest('tr[data-mem-idx]');
|
|
302
|
-
if (row) {
|
|
303
|
-
var idx = parseInt(row.getAttribute('data-mem-idx'), 10);
|
|
304
|
-
if (window._slmMemories && window._slmMemories[idx]) {
|
|
305
|
-
openMemoryDetail(window._slmMemories[idx]);
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
// ============================================================================
|
|
313
|
-
// Search
|
|
314
|
-
// ============================================================================
|
|
315
|
-
|
|
316
|
-
async function searchMemories() {
|
|
317
|
-
var query = document.getElementById('search-query').value;
|
|
318
|
-
if (!query.trim()) { loadMemories(); return; }
|
|
319
|
-
|
|
320
|
-
showLoading('memories-list', 'Searching...');
|
|
321
|
-
try {
|
|
322
|
-
var response = await fetch('/api/search', {
|
|
323
|
-
method: 'POST',
|
|
324
|
-
headers: { 'Content-Type': 'application/json' },
|
|
325
|
-
body: JSON.stringify({ query: query, limit: 20, min_score: 0.3 })
|
|
326
|
-
});
|
|
327
|
-
var data = await response.json();
|
|
328
|
-
|
|
329
|
-
// Sort by relevance score descending
|
|
330
|
-
var results = data.results || [];
|
|
331
|
-
results.sort(function(a, b) { return (b.score || 0) - (a.score || 0); });
|
|
332
|
-
|
|
333
|
-
// Cache for export
|
|
334
|
-
lastSearchResults = results;
|
|
335
|
-
|
|
336
|
-
// Show export search results button
|
|
337
|
-
var exportBtn = document.getElementById('export-search-btn');
|
|
338
|
-
if (exportBtn) exportBtn.style.display = results.length > 0 ? '' : 'none';
|
|
339
|
-
|
|
340
|
-
renderMemoriesTable(results, true);
|
|
341
|
-
} catch (error) {
|
|
342
|
-
console.error('Error searching:', error);
|
|
343
|
-
showEmpty('memories-list', 'exclamation-triangle', 'Search failed. Please try again.');
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// ============================================================================
|
|
348
|
-
// Memory Detail Modal
|
|
349
|
-
// ============================================================================
|
|
350
|
-
|
|
351
|
-
function openMemoryDetail(mem) {
|
|
352
|
-
currentMemoryDetail = mem;
|
|
353
|
-
var body = document.getElementById('memory-detail-body');
|
|
354
|
-
if (!mem) {
|
|
355
|
-
body.textContent = 'No memory data';
|
|
356
|
-
return;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
var content = mem.content || mem.summary || '(no content)';
|
|
360
|
-
var tags = mem.tags || '';
|
|
361
|
-
var importance = mem.importance || 5;
|
|
362
|
-
var importanceClass = importance >= 8 ? 'success' : importance >= 5 ? 'warning' : 'secondary';
|
|
363
|
-
|
|
364
|
-
var scoreBlock = '';
|
|
365
|
-
if (typeof mem.score === 'number') {
|
|
366
|
-
var pct = Math.round(mem.score * 100);
|
|
367
|
-
var barColor = pct >= 70 ? '#43e97b' : pct >= 40 ? '#f9c74f' : '#f94144';
|
|
368
|
-
scoreBlock = '<dt>Relevance Score</dt><dd><span class="score-label">'
|
|
369
|
-
+ escapeHtml(String(pct)) + '%</span>'
|
|
370
|
-
+ '<div class="score-bar-container"><div class="score-bar">'
|
|
371
|
-
+ '<div class="score-bar-fill" style="width:' + pct + '%;background:' + barColor + '"></div>'
|
|
372
|
-
+ '</div></div></dd>';
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// All values escaped — safe for innerHTML
|
|
376
|
-
var html = '<div class="memory-detail-content">' + escapeHtml(content) + '</div>'
|
|
377
|
-
+ '<hr>'
|
|
378
|
-
+ '<dl class="memory-detail-meta row">'
|
|
379
|
-
+ '<div class="col-md-6">'
|
|
380
|
-
+ '<dt>ID</dt><dd>' + escapeHtml(String(mem.id || '-')) + '</dd>'
|
|
381
|
-
+ '<dt>Category</dt><dd><span class="badge bg-primary">' + escapeHtml(mem.category || 'None') + '</span></dd>'
|
|
382
|
-
+ '<dt>Project</dt><dd>' + escapeHtml(mem.project_name || '-') + '</dd>'
|
|
383
|
-
+ '<dt>Tags</dt><dd>' + (tags ? formatTags(tags) : '<span class="text-muted">None</span>') + '</dd>'
|
|
384
|
-
+ '</div>'
|
|
385
|
-
+ '<div class="col-md-6">'
|
|
386
|
-
+ '<dt>Importance</dt><dd><span class="badge bg-' + importanceClass + '">' + escapeHtml(String(importance)) + '/10</span></dd>'
|
|
387
|
-
+ '<dt>Cluster</dt><dd>' + escapeHtml(String(mem.cluster_id || '-')) + '</dd>'
|
|
388
|
-
+ '<dt>Created</dt><dd>' + escapeHtml(formatDateFull(mem.created_at)) + '</dd>'
|
|
389
|
-
+ (mem.updated_at ? '<dt>Updated</dt><dd>' + escapeHtml(formatDateFull(mem.updated_at)) + '</dd>' : '')
|
|
390
|
-
+ scoreBlock
|
|
391
|
-
+ '</div>'
|
|
392
|
-
+ '</dl>';
|
|
393
|
-
|
|
394
|
-
body.innerHTML = html; // nosemgrep: innerHTML-xss — all values escaped
|
|
395
|
-
|
|
396
|
-
var modal = new bootstrap.Modal(document.getElementById('memoryDetailModal'));
|
|
397
|
-
modal.show();
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
function formatTags(tags) {
|
|
401
|
-
if (!tags) return '';
|
|
402
|
-
var tagList = typeof tags === 'string' ? tags.split(',') : tags;
|
|
403
|
-
return tagList.map(function(t) {
|
|
404
|
-
var tag = t.trim();
|
|
405
|
-
return tag ? '<span class="badge bg-secondary me-1">' + escapeHtml(tag) + '</span>' : '';
|
|
406
|
-
}).join('');
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
// ============================================================================
|
|
410
|
-
// Copy / Export from Modal
|
|
411
|
-
// ============================================================================
|
|
412
|
-
|
|
413
|
-
function copyMemoryToClipboard() {
|
|
414
|
-
if (!currentMemoryDetail) return;
|
|
415
|
-
var text = currentMemoryDetail.content || currentMemoryDetail.summary || '';
|
|
416
|
-
navigator.clipboard.writeText(text).then(function() {
|
|
417
|
-
showToast('Copied to clipboard');
|
|
418
|
-
}).catch(function() {
|
|
419
|
-
// Fallback for older browsers
|
|
420
|
-
var ta = document.createElement('textarea');
|
|
421
|
-
ta.value = text;
|
|
422
|
-
document.body.appendChild(ta);
|
|
423
|
-
ta.select();
|
|
424
|
-
document.execCommand('copy');
|
|
425
|
-
document.body.removeChild(ta);
|
|
426
|
-
showToast('Copied to clipboard');
|
|
427
|
-
});
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
function exportMemoryAsMarkdown() {
|
|
431
|
-
if (!currentMemoryDetail) return;
|
|
432
|
-
var mem = currentMemoryDetail;
|
|
433
|
-
var md = '# Memory #' + (mem.id || 'unknown') + '\n\n';
|
|
434
|
-
md += '**Category:** ' + (mem.category || 'None') + ' \n';
|
|
435
|
-
md += '**Project:** ' + (mem.project_name || '-') + ' \n';
|
|
436
|
-
md += '**Importance:** ' + (mem.importance || 5) + '/10 \n';
|
|
437
|
-
md += '**Tags:** ' + (mem.tags || 'None') + ' \n';
|
|
438
|
-
md += '**Created:** ' + (mem.created_at || '-') + ' \n';
|
|
439
|
-
if (mem.cluster_id) md += '**Cluster:** ' + mem.cluster_id + ' \n';
|
|
440
|
-
md += '\n---\n\n';
|
|
441
|
-
md += mem.content || mem.summary || '(no content)';
|
|
442
|
-
md += '\n\n---\n*Exported from SuperLocalMemory V2*\n';
|
|
443
|
-
|
|
444
|
-
downloadFile('memory-' + (mem.id || 'export') + '.md', md, 'text/markdown');
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
// ============================================================================
|
|
448
|
-
// Export All / Search Results
|
|
449
|
-
// ============================================================================
|
|
450
|
-
|
|
451
|
-
function exportAll(format) {
|
|
452
|
-
// Trigger browser download from the API endpoint
|
|
453
|
-
var url = '/api/export?format=' + encodeURIComponent(format);
|
|
454
|
-
var category = document.getElementById('filter-category').value;
|
|
455
|
-
var project = document.getElementById('filter-project').value;
|
|
456
|
-
if (category) url += '&category=' + encodeURIComponent(category);
|
|
457
|
-
if (project) url += '&project_name=' + encodeURIComponent(project);
|
|
458
|
-
window.location.href = url;
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
function exportSearchResults() {
|
|
462
|
-
if (!lastSearchResults || lastSearchResults.length === 0) {
|
|
463
|
-
showToast('No search results to export');
|
|
464
|
-
return;
|
|
465
|
-
}
|
|
466
|
-
var content = JSON.stringify({
|
|
467
|
-
exported_at: new Date().toISOString(),
|
|
468
|
-
query: document.getElementById('search-query').value,
|
|
469
|
-
total: lastSearchResults.length,
|
|
470
|
-
results: lastSearchResults
|
|
471
|
-
}, null, 2);
|
|
472
|
-
downloadFile('search-results-' + Date.now() + '.json', content, 'application/json');
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
// ============================================================================
|
|
476
|
-
// File Download helper
|
|
477
|
-
// ============================================================================
|
|
478
|
-
|
|
479
|
-
function downloadFile(filename, content, mimeType) {
|
|
480
|
-
var blob = new Blob([content], { type: mimeType });
|
|
481
|
-
var url = URL.createObjectURL(blob);
|
|
482
|
-
var a = document.createElement('a');
|
|
483
|
-
a.href = url;
|
|
484
|
-
a.download = filename;
|
|
485
|
-
document.body.appendChild(a);
|
|
486
|
-
a.click();
|
|
487
|
-
document.body.removeChild(a);
|
|
488
|
-
URL.revokeObjectURL(url);
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
// ============================================================================
|
|
492
|
-
// Toast notification
|
|
493
|
-
// ============================================================================
|
|
494
|
-
|
|
495
|
-
function showToast(message) {
|
|
496
|
-
var toast = document.createElement('div');
|
|
497
|
-
toast.style.cssText = 'position:fixed;bottom:24px;right:24px;background:#333;color:#fff;padding:10px 20px;border-radius:8px;font-size:0.9rem;z-index:9999;opacity:0;transition:opacity 0.3s;';
|
|
498
|
-
toast.textContent = message;
|
|
499
|
-
document.body.appendChild(toast);
|
|
500
|
-
requestAnimationFrame(function() { toast.style.opacity = '1'; });
|
|
501
|
-
setTimeout(function() {
|
|
502
|
-
toast.style.opacity = '0';
|
|
503
|
-
setTimeout(function() {
|
|
504
|
-
if (toast.parentNode) document.body.removeChild(toast);
|
|
505
|
-
}, 300);
|
|
506
|
-
}, 2000);
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
// ============================================================================
|
|
510
|
-
// Clusters
|
|
511
|
-
// ============================================================================
|
|
512
|
-
|
|
513
|
-
async function loadClusters() {
|
|
514
|
-
showLoading('clusters-list', 'Loading clusters...');
|
|
515
|
-
try {
|
|
516
|
-
var response = await fetch('/api/clusters');
|
|
517
|
-
var data = await response.json();
|
|
518
|
-
renderClusters(data.clusters);
|
|
519
|
-
} catch (error) {
|
|
520
|
-
console.error('Error loading clusters:', error);
|
|
521
|
-
showEmpty('clusters-list', 'collection', 'Failed to load clusters');
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
function renderClusters(clusters) {
|
|
526
|
-
var container = document.getElementById('clusters-list');
|
|
527
|
-
if (!clusters || clusters.length === 0) {
|
|
528
|
-
showEmpty('clusters-list', 'collection', 'No clusters found. Run "slm build-graph" to generate clusters.');
|
|
529
|
-
return;
|
|
530
|
-
}
|
|
531
|
-
var colors = ['#667eea', '#f093fb', '#4facfe', '#43e97b', '#fa709a'];
|
|
532
|
-
|
|
533
|
-
// All dynamic values escaped — safe for innerHTML
|
|
534
|
-
var html = '';
|
|
535
|
-
clusters.forEach(function(cluster, idx) {
|
|
536
|
-
var color = colors[idx % colors.length];
|
|
537
|
-
html += '<div class="card cluster-card" style="border-color: ' + color + '">'
|
|
538
|
-
+ '<div class="card-body">'
|
|
539
|
-
+ '<h6 class="card-title">Cluster ' + escapeHtml(String(cluster.cluster_id))
|
|
540
|
-
+ ' <span class="badge bg-secondary float-end">' + escapeHtml(String(cluster.member_count)) + ' memories</span></h6>'
|
|
541
|
-
+ '<p class="mb-2"><strong>Avg Importance:</strong> ' + escapeHtml(parseFloat(cluster.avg_importance).toFixed(1)) + '</p>'
|
|
542
|
-
+ '<p class="mb-2"><strong>Categories:</strong> ' + escapeHtml(cluster.categories || 'None') + '</p>'
|
|
543
|
-
+ '<div><strong>Top Entities:</strong><br/>';
|
|
544
|
-
if (cluster.top_entities && cluster.top_entities.length > 0) {
|
|
545
|
-
cluster.top_entities.forEach(function(e) {
|
|
546
|
-
html += '<span class="badge bg-info entity-badge">' + escapeHtml(e.entity) + ' (' + escapeHtml(String(e.count)) + ')</span> ';
|
|
547
|
-
});
|
|
548
|
-
} else {
|
|
549
|
-
html += '<span class="text-muted">No entities</span>';
|
|
550
|
-
}
|
|
551
|
-
html += '</div></div></div>';
|
|
552
|
-
});
|
|
553
|
-
container.innerHTML = html; // nosemgrep: innerHTML-xss — all values escaped
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
// ============================================================================
|
|
557
|
-
// Patterns
|
|
558
|
-
// ============================================================================
|
|
559
|
-
|
|
560
|
-
async function loadPatterns() {
|
|
561
|
-
showLoading('patterns-list', 'Loading patterns...');
|
|
562
|
-
try {
|
|
563
|
-
var response = await fetch('/api/patterns');
|
|
564
|
-
var data = await response.json();
|
|
565
|
-
renderPatterns(data.patterns);
|
|
566
|
-
} catch (error) {
|
|
567
|
-
console.error('Error loading patterns:', error);
|
|
568
|
-
showEmpty('patterns-list', 'puzzle', 'Failed to load patterns');
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
function renderPatterns(patterns) {
|
|
573
|
-
var container = document.getElementById('patterns-list');
|
|
574
|
-
if (!patterns || Object.keys(patterns).length === 0) {
|
|
575
|
-
showEmpty('patterns-list', 'puzzle', 'No patterns learned yet. Use SuperLocalMemory for a while to build patterns.');
|
|
576
|
-
return;
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
var typeIcons = { preference: 'heart', style: 'palette', terminology: 'code-slash' };
|
|
580
|
-
var typeLabels = { preference: 'Preferences', style: 'Coding Style', terminology: 'Terminology' };
|
|
581
|
-
|
|
582
|
-
// Build using DOM for safety
|
|
583
|
-
container.textContent = '';
|
|
584
|
-
|
|
585
|
-
for (var type in patterns) {
|
|
586
|
-
if (!patterns.hasOwnProperty(type)) continue;
|
|
587
|
-
var items = patterns[type];
|
|
588
|
-
|
|
589
|
-
var header = document.createElement('h6');
|
|
590
|
-
header.className = 'mt-3 mb-2';
|
|
591
|
-
var icon = document.createElement('i');
|
|
592
|
-
icon.className = 'bi bi-' + (typeIcons[type] || 'puzzle') + ' me-1';
|
|
593
|
-
header.appendChild(icon);
|
|
594
|
-
header.appendChild(document.createTextNode(typeLabels[type] || type));
|
|
595
|
-
var countBadge = document.createElement('span');
|
|
596
|
-
countBadge.className = 'badge bg-secondary ms-2';
|
|
597
|
-
countBadge.textContent = items.length;
|
|
598
|
-
header.appendChild(countBadge);
|
|
599
|
-
container.appendChild(header);
|
|
600
|
-
|
|
601
|
-
var group = document.createElement('div');
|
|
602
|
-
group.className = 'list-group mb-3';
|
|
603
|
-
|
|
604
|
-
items.forEach(function(pattern) {
|
|
605
|
-
var pct = Math.round(pattern.confidence * 100);
|
|
606
|
-
var barColor = pct >= 60 ? '#43e97b' : pct >= 40 ? '#f9c74f' : '#6c757d';
|
|
607
|
-
var badgeClass = pct >= 60 ? 'bg-success' : pct >= 40 ? 'bg-warning text-dark' : 'bg-secondary';
|
|
608
|
-
|
|
609
|
-
var item = document.createElement('div');
|
|
610
|
-
item.className = 'list-group-item';
|
|
611
|
-
|
|
612
|
-
var topRow = document.createElement('div');
|
|
613
|
-
topRow.className = 'd-flex justify-content-between align-items-center';
|
|
614
|
-
var keyEl = document.createElement('strong');
|
|
615
|
-
keyEl.textContent = pattern.key;
|
|
616
|
-
var badge = document.createElement('span');
|
|
617
|
-
badge.className = 'badge ' + badgeClass;
|
|
618
|
-
badge.textContent = pct + '%';
|
|
619
|
-
topRow.appendChild(keyEl);
|
|
620
|
-
topRow.appendChild(badge);
|
|
621
|
-
item.appendChild(topRow);
|
|
622
|
-
|
|
623
|
-
// Confidence bar
|
|
624
|
-
var barContainer = document.createElement('div');
|
|
625
|
-
barContainer.className = 'confidence-bar';
|
|
626
|
-
var barFill = document.createElement('div');
|
|
627
|
-
barFill.className = 'confidence-fill';
|
|
628
|
-
barFill.style.width = pct + '%';
|
|
629
|
-
barFill.style.background = barColor;
|
|
630
|
-
barContainer.appendChild(barFill);
|
|
631
|
-
item.appendChild(barContainer);
|
|
632
|
-
|
|
633
|
-
var valueEl = document.createElement('div');
|
|
634
|
-
valueEl.className = 'mt-1';
|
|
635
|
-
var valueSmall = document.createElement('small');
|
|
636
|
-
valueSmall.className = 'text-muted';
|
|
637
|
-
valueSmall.textContent = typeof pattern.value === 'string' ? pattern.value : JSON.stringify(pattern.value);
|
|
638
|
-
valueEl.appendChild(valueSmall);
|
|
639
|
-
item.appendChild(valueEl);
|
|
640
|
-
|
|
641
|
-
var evidenceEl = document.createElement('small');
|
|
642
|
-
evidenceEl.className = 'text-muted';
|
|
643
|
-
evidenceEl.textContent = 'Evidence: ' + (pattern.evidence_count || '?') + ' memories';
|
|
644
|
-
item.appendChild(evidenceEl);
|
|
645
|
-
|
|
646
|
-
group.appendChild(item);
|
|
647
|
-
});
|
|
648
|
-
|
|
649
|
-
container.appendChild(group);
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
// ============================================================================
|
|
654
|
-
// Timeline
|
|
655
|
-
// ============================================================================
|
|
656
|
-
|
|
657
|
-
async function loadTimeline() {
|
|
658
|
-
showLoading('timeline-chart', 'Loading timeline...');
|
|
659
|
-
try {
|
|
660
|
-
var response = await fetch('/api/timeline?days=30');
|
|
661
|
-
var data = await response.json();
|
|
662
|
-
renderTimeline(data.timeline);
|
|
663
|
-
} catch (error) {
|
|
664
|
-
console.error('Error loading timeline:', error);
|
|
665
|
-
showEmpty('timeline-chart', 'clock-history', 'Failed to load timeline');
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
function renderTimeline(timeline) {
|
|
670
|
-
var container = document.getElementById('timeline-chart');
|
|
671
|
-
if (!timeline || timeline.length === 0) {
|
|
672
|
-
showEmpty('timeline-chart', 'clock-history', 'No timeline data for the last 30 days.');
|
|
673
|
-
return;
|
|
674
|
-
}
|
|
675
|
-
var margin = { top: 20, right: 20, bottom: 50, left: 50 };
|
|
676
|
-
var width = container.clientWidth - margin.left - margin.right;
|
|
677
|
-
var height = 300 - margin.top - margin.bottom;
|
|
678
|
-
container.textContent = '';
|
|
679
|
-
var svg = d3.select('#timeline-chart').append('svg').attr('width', width + margin.left + margin.right).attr('height', height + margin.top + margin.bottom).append('g').attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
|
|
680
|
-
var x = d3.scaleBand().range([0, width]).domain(timeline.map(function(d) { return d.date || d.period; })).padding(0.1);
|
|
681
|
-
var y = d3.scaleLinear().range([height, 0]).domain([0, d3.max(timeline, function(d) { return d.count; })]);
|
|
682
|
-
svg.append('g').attr('transform', 'translate(0,' + height + ')').call(d3.axisBottom(x)).selectAll('text').attr('transform', 'rotate(-45)').style('text-anchor', 'end');
|
|
683
|
-
svg.append('g').call(d3.axisLeft(y));
|
|
684
|
-
svg.selectAll('.bar').data(timeline).enter().append('rect').attr('class', 'bar').attr('x', function(d) { return x(d.date || d.period); }).attr('y', function(d) { return y(d.count); }).attr('width', x.bandwidth()).attr('height', function(d) { return height - y(d.count); }).attr('fill', '#667eea').attr('rx', 3);
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
// ============================================================================
|
|
688
|
-
// Date Formatters
|
|
689
|
-
// ============================================================================
|
|
690
|
-
|
|
691
|
-
function formatDate(dateString) {
|
|
692
|
-
if (!dateString) return '-';
|
|
693
|
-
var date = new Date(dateString);
|
|
694
|
-
return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
function formatDateFull(dateString) {
|
|
698
|
-
if (!dateString) return '-';
|
|
699
|
-
var date = new Date(dateString);
|
|
700
|
-
return date.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' });
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
// ============================================================================
|
|
704
|
-
// Event Listeners
|
|
705
|
-
// ============================================================================
|
|
706
|
-
|
|
707
|
-
document.getElementById('memories-tab').addEventListener('shown.bs.tab', loadMemories);
|
|
708
|
-
document.getElementById('clusters-tab').addEventListener('shown.bs.tab', loadClusters);
|
|
709
|
-
document.getElementById('patterns-tab').addEventListener('shown.bs.tab', loadPatterns);
|
|
710
|
-
document.getElementById('timeline-tab').addEventListener('shown.bs.tab', loadTimeline);
|
|
711
|
-
document.getElementById('settings-tab').addEventListener('shown.bs.tab', loadSettings);
|
|
712
|
-
document.getElementById('search-query').addEventListener('keypress', function(e) { if (e.key === 'Enter') searchMemories(); });
|
|
713
|
-
|
|
714
|
-
document.getElementById('profile-select').addEventListener('change', function() {
|
|
715
|
-
switchProfile(this.value);
|
|
716
|
-
});
|
|
717
|
-
|
|
718
|
-
document.getElementById('add-profile-btn').addEventListener('click', function() {
|
|
719
|
-
createProfile();
|
|
720
|
-
});
|
|
721
|
-
|
|
722
|
-
var newProfileInput = document.getElementById('new-profile-name');
|
|
723
|
-
if (newProfileInput) {
|
|
724
|
-
newProfileInput.addEventListener('keypress', function(e) {
|
|
725
|
-
if (e.key === 'Enter') createProfile();
|
|
726
|
-
});
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
window.addEventListener('DOMContentLoaded', function() {
|
|
730
|
-
initDarkMode();
|
|
731
|
-
loadProfiles();
|
|
732
|
-
loadStats();
|
|
733
|
-
loadGraph();
|
|
734
|
-
|
|
735
|
-
// v2.5 — Event Bus + Agent Registry
|
|
736
|
-
initEventStream();
|
|
737
|
-
loadEventStats();
|
|
738
|
-
loadAgents();
|
|
739
|
-
});
|
|
740
|
-
|
|
741
|
-
// ============================================================================
|
|
742
|
-
// Profile Management
|
|
743
|
-
// ============================================================================
|
|
744
|
-
|
|
745
|
-
async function loadProfiles() {
|
|
746
|
-
try {
|
|
747
|
-
var response = await fetch('/api/profiles');
|
|
748
|
-
var data = await response.json();
|
|
749
|
-
var select = document.getElementById('profile-select');
|
|
750
|
-
select.textContent = '';
|
|
751
|
-
var profiles = data.profiles || [];
|
|
752
|
-
var active = data.active_profile || 'default';
|
|
753
|
-
|
|
754
|
-
profiles.forEach(function(p) {
|
|
755
|
-
var opt = document.createElement('option');
|
|
756
|
-
opt.value = p.name;
|
|
757
|
-
opt.textContent = p.name + (p.memory_count ? ' (' + p.memory_count + ')' : '');
|
|
758
|
-
if (p.name === active) opt.selected = true;
|
|
759
|
-
select.appendChild(opt);
|
|
760
|
-
});
|
|
761
|
-
} catch (error) {
|
|
762
|
-
console.error('Error loading profiles:', error);
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
async function createProfile(nameOverride) {
|
|
767
|
-
var name = nameOverride || document.getElementById('new-profile-name').value.trim();
|
|
768
|
-
if (!name) {
|
|
769
|
-
// Prompt with a simple browser dialog if called from the "+" button
|
|
770
|
-
name = prompt('Enter new profile name:');
|
|
771
|
-
if (!name || !name.trim()) return;
|
|
772
|
-
name = name.trim();
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
// Validate: alphanumeric, dashes, underscores only
|
|
776
|
-
if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
|
|
777
|
-
showToast('Invalid name. Use letters, numbers, dashes, underscores.');
|
|
778
|
-
return;
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
try {
|
|
782
|
-
var response = await fetch('/api/profiles/create', {
|
|
783
|
-
method: 'POST',
|
|
784
|
-
headers: { 'Content-Type': 'application/json' },
|
|
785
|
-
body: JSON.stringify({ profile_name: name })
|
|
786
|
-
});
|
|
787
|
-
var data = await response.json();
|
|
788
|
-
if (response.status === 409) {
|
|
789
|
-
showToast('Profile "' + name + '" already exists');
|
|
790
|
-
return;
|
|
791
|
-
}
|
|
792
|
-
if (!response.ok) {
|
|
793
|
-
showToast(data.detail || 'Failed to create profile');
|
|
794
|
-
return;
|
|
795
|
-
}
|
|
796
|
-
showToast('Profile "' + name + '" created');
|
|
797
|
-
var input = document.getElementById('new-profile-name');
|
|
798
|
-
if (input) input.value = '';
|
|
799
|
-
loadProfiles();
|
|
800
|
-
loadProfilesTable();
|
|
801
|
-
} catch (error) {
|
|
802
|
-
console.error('Error creating profile:', error);
|
|
803
|
-
showToast('Error creating profile');
|
|
804
|
-
}
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
async function deleteProfile(name) {
|
|
808
|
-
if (name === 'default') {
|
|
809
|
-
showToast('Cannot delete the default profile');
|
|
810
|
-
return;
|
|
811
|
-
}
|
|
812
|
-
if (!confirm('Delete profile "' + name + '"?\nIts memories will be moved to the default profile.')) {
|
|
813
|
-
return;
|
|
814
|
-
}
|
|
815
|
-
try {
|
|
816
|
-
var response = await fetch('/api/profiles/' + encodeURIComponent(name), {
|
|
817
|
-
method: 'DELETE'
|
|
818
|
-
});
|
|
819
|
-
var data = await response.json();
|
|
820
|
-
if (!response.ok) {
|
|
821
|
-
showToast(data.detail || 'Failed to delete profile');
|
|
822
|
-
return;
|
|
823
|
-
}
|
|
824
|
-
showToast(data.message || 'Profile deleted');
|
|
825
|
-
loadProfiles();
|
|
826
|
-
loadProfilesTable();
|
|
827
|
-
loadStats();
|
|
828
|
-
} catch (error) {
|
|
829
|
-
console.error('Error deleting profile:', error);
|
|
830
|
-
showToast('Error deleting profile');
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
async function loadProfilesTable() {
|
|
835
|
-
var container = document.getElementById('profiles-table');
|
|
836
|
-
if (!container) return;
|
|
837
|
-
try {
|
|
838
|
-
var response = await fetch('/api/profiles');
|
|
839
|
-
var data = await response.json();
|
|
840
|
-
var profiles = data.profiles || [];
|
|
841
|
-
var active = data.active_profile || 'default';
|
|
842
|
-
|
|
843
|
-
if (profiles.length === 0) {
|
|
844
|
-
showEmpty('profiles-table', 'people', 'No profiles found.');
|
|
845
|
-
return;
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
var table = document.createElement('table');
|
|
849
|
-
table.className = 'table table-sm mb-0';
|
|
850
|
-
var thead = document.createElement('thead');
|
|
851
|
-
var headRow = document.createElement('tr');
|
|
852
|
-
['Name', 'Memories', 'Status', 'Actions'].forEach(function(h) {
|
|
853
|
-
var th = document.createElement('th');
|
|
854
|
-
th.textContent = h;
|
|
855
|
-
headRow.appendChild(th);
|
|
856
|
-
});
|
|
857
|
-
thead.appendChild(headRow);
|
|
858
|
-
table.appendChild(thead);
|
|
859
|
-
|
|
860
|
-
var tbody = document.createElement('tbody');
|
|
861
|
-
profiles.forEach(function(p) {
|
|
862
|
-
var row = document.createElement('tr');
|
|
863
|
-
|
|
864
|
-
var nameCell = document.createElement('td');
|
|
865
|
-
var nameIcon = document.createElement('i');
|
|
866
|
-
nameIcon.className = 'bi bi-person me-1';
|
|
867
|
-
nameCell.appendChild(nameIcon);
|
|
868
|
-
nameCell.appendChild(document.createTextNode(p.name));
|
|
869
|
-
row.appendChild(nameCell);
|
|
870
|
-
|
|
871
|
-
var countCell = document.createElement('td');
|
|
872
|
-
countCell.textContent = (p.memory_count || 0) + ' memories';
|
|
873
|
-
row.appendChild(countCell);
|
|
874
|
-
|
|
875
|
-
var statusCell = document.createElement('td');
|
|
876
|
-
if (p.name === active) {
|
|
877
|
-
var badge = document.createElement('span');
|
|
878
|
-
badge.className = 'badge bg-success';
|
|
879
|
-
badge.textContent = 'Active';
|
|
880
|
-
statusCell.appendChild(badge);
|
|
881
|
-
} else {
|
|
882
|
-
var switchBtn = document.createElement('button');
|
|
883
|
-
switchBtn.className = 'btn btn-sm btn-outline-primary';
|
|
884
|
-
switchBtn.textContent = 'Switch';
|
|
885
|
-
switchBtn.addEventListener('click', (function(n) {
|
|
886
|
-
return function() { switchProfile(n); };
|
|
887
|
-
})(p.name));
|
|
888
|
-
statusCell.appendChild(switchBtn);
|
|
889
|
-
}
|
|
890
|
-
row.appendChild(statusCell);
|
|
891
|
-
|
|
892
|
-
var actionsCell = document.createElement('td');
|
|
893
|
-
if (p.name !== 'default') {
|
|
894
|
-
var delBtn = document.createElement('button');
|
|
895
|
-
delBtn.className = 'btn btn-sm btn-outline-danger btn-delete-profile';
|
|
896
|
-
delBtn.title = 'Delete profile';
|
|
897
|
-
var delIcon = document.createElement('i');
|
|
898
|
-
delIcon.className = 'bi bi-trash';
|
|
899
|
-
delBtn.appendChild(delIcon);
|
|
900
|
-
delBtn.addEventListener('click', (function(n) {
|
|
901
|
-
return function() { deleteProfile(n); };
|
|
902
|
-
})(p.name));
|
|
903
|
-
actionsCell.appendChild(delBtn);
|
|
904
|
-
} else {
|
|
905
|
-
var protectedBadge = document.createElement('span');
|
|
906
|
-
protectedBadge.className = 'badge bg-secondary';
|
|
907
|
-
protectedBadge.textContent = 'Protected';
|
|
908
|
-
actionsCell.appendChild(protectedBadge);
|
|
909
|
-
}
|
|
910
|
-
row.appendChild(actionsCell);
|
|
911
|
-
|
|
912
|
-
tbody.appendChild(row);
|
|
913
|
-
});
|
|
914
|
-
table.appendChild(tbody);
|
|
915
|
-
|
|
916
|
-
container.textContent = '';
|
|
917
|
-
container.appendChild(table);
|
|
918
|
-
} catch (error) {
|
|
919
|
-
console.error('Error loading profiles table:', error);
|
|
920
|
-
showEmpty('profiles-table', 'exclamation-triangle', 'Failed to load profiles');
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
async function switchProfile(profileName) {
|
|
925
|
-
try {
|
|
926
|
-
var response = await fetch('/api/profiles/' + encodeURIComponent(profileName) + '/switch', {
|
|
927
|
-
method: 'POST'
|
|
928
|
-
});
|
|
929
|
-
var data = await response.json();
|
|
930
|
-
if (data.success || data.active_profile) {
|
|
931
|
-
showToast('Switched to profile: ' + profileName);
|
|
932
|
-
loadProfiles();
|
|
933
|
-
loadStats();
|
|
934
|
-
loadGraph();
|
|
935
|
-
loadProfilesTable();
|
|
936
|
-
var activeTab = document.querySelector('#mainTabs .nav-link.active');
|
|
937
|
-
if (activeTab) activeTab.click();
|
|
938
|
-
} else {
|
|
939
|
-
showToast('Failed to switch profile');
|
|
940
|
-
}
|
|
941
|
-
} catch (error) {
|
|
942
|
-
console.error('Error switching profile:', error);
|
|
943
|
-
showToast('Error switching profile');
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
// ============================================================================
|
|
948
|
-
// Settings & Backup
|
|
949
|
-
// ============================================================================
|
|
950
|
-
|
|
951
|
-
async function loadSettings() {
|
|
952
|
-
loadProfilesTable();
|
|
953
|
-
loadBackupStatus();
|
|
954
|
-
loadBackupList();
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
async function loadBackupStatus() {
|
|
958
|
-
try {
|
|
959
|
-
var response = await fetch('/api/backup/status');
|
|
960
|
-
var data = await response.json();
|
|
961
|
-
renderBackupStatus(data);
|
|
962
|
-
document.getElementById('backup-interval').value = data.interval_hours <= 24 ? '24' : '168';
|
|
963
|
-
document.getElementById('backup-max').value = data.max_backups || 10;
|
|
964
|
-
document.getElementById('backup-enabled').checked = data.enabled !== false;
|
|
965
|
-
} catch (error) {
|
|
966
|
-
var container = document.getElementById('backup-status');
|
|
967
|
-
var alert = document.createElement('div');
|
|
968
|
-
alert.className = 'alert alert-warning mb-0';
|
|
969
|
-
alert.textContent = 'Auto-backup not available. Update to v2.4.0+.';
|
|
970
|
-
container.textContent = '';
|
|
971
|
-
container.appendChild(alert);
|
|
972
|
-
}
|
|
973
|
-
}
|
|
974
|
-
|
|
975
|
-
function renderBackupStatus(data) {
|
|
976
|
-
var container = document.getElementById('backup-status');
|
|
977
|
-
container.textContent = '';
|
|
978
|
-
|
|
979
|
-
var lastBackup = data.last_backup ? formatDateFull(data.last_backup) : 'Never';
|
|
980
|
-
var nextBackup = data.next_backup || 'N/A';
|
|
981
|
-
if (nextBackup === 'overdue') nextBackup = 'Overdue';
|
|
982
|
-
else if (nextBackup !== 'N/A' && nextBackup !== 'unknown') nextBackup = formatDateFull(nextBackup);
|
|
983
|
-
|
|
984
|
-
var statusColor = data.enabled ? 'text-success' : 'text-secondary';
|
|
985
|
-
var statusText = data.enabled ? 'Active' : 'Disabled';
|
|
986
|
-
|
|
987
|
-
// Build DOM nodes for safety
|
|
988
|
-
var row = document.createElement('div');
|
|
989
|
-
row.className = 'row g-2 mb-2';
|
|
990
|
-
|
|
991
|
-
var stats = [
|
|
992
|
-
{ value: statusText, label: 'Status', cls: statusColor },
|
|
993
|
-
{ value: String(data.backup_count || 0), label: 'Backups', cls: '' },
|
|
994
|
-
{ value: (data.total_size_mb || 0) + ' MB', label: 'Storage', cls: '' }
|
|
995
|
-
];
|
|
996
|
-
|
|
997
|
-
stats.forEach(function(s) {
|
|
998
|
-
var col = document.createElement('div');
|
|
999
|
-
col.className = 'col-4';
|
|
1000
|
-
var stat = document.createElement('div');
|
|
1001
|
-
stat.className = 'backup-stat';
|
|
1002
|
-
var val = document.createElement('div');
|
|
1003
|
-
val.className = 'value ' + s.cls;
|
|
1004
|
-
val.textContent = s.value;
|
|
1005
|
-
var lbl = document.createElement('div');
|
|
1006
|
-
lbl.className = 'label';
|
|
1007
|
-
lbl.textContent = s.label;
|
|
1008
|
-
stat.appendChild(val);
|
|
1009
|
-
stat.appendChild(lbl);
|
|
1010
|
-
col.appendChild(stat);
|
|
1011
|
-
row.appendChild(col);
|
|
1012
|
-
});
|
|
1013
|
-
container.appendChild(row);
|
|
1014
|
-
|
|
1015
|
-
var details = [
|
|
1016
|
-
{ label: 'Last backup:', value: lastBackup },
|
|
1017
|
-
{ label: 'Next backup:', value: nextBackup },
|
|
1018
|
-
{ label: 'Interval:', value: data.interval_display || '-' }
|
|
1019
|
-
];
|
|
1020
|
-
details.forEach(function(d) {
|
|
1021
|
-
var div = document.createElement('div');
|
|
1022
|
-
div.className = 'small text-muted';
|
|
1023
|
-
var strong = document.createElement('strong');
|
|
1024
|
-
strong.textContent = d.label + ' ';
|
|
1025
|
-
div.appendChild(strong);
|
|
1026
|
-
div.appendChild(document.createTextNode(d.value));
|
|
1027
|
-
container.appendChild(div);
|
|
1028
|
-
});
|
|
1029
|
-
}
|
|
1030
|
-
|
|
1031
|
-
async function saveBackupConfig() {
|
|
1032
|
-
try {
|
|
1033
|
-
var response = await fetch('/api/backup/configure', {
|
|
1034
|
-
method: 'POST',
|
|
1035
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1036
|
-
body: JSON.stringify({
|
|
1037
|
-
interval_hours: parseInt(document.getElementById('backup-interval').value),
|
|
1038
|
-
max_backups: parseInt(document.getElementById('backup-max').value),
|
|
1039
|
-
enabled: document.getElementById('backup-enabled').checked
|
|
1040
|
-
})
|
|
1041
|
-
});
|
|
1042
|
-
var data = await response.json();
|
|
1043
|
-
renderBackupStatus(data);
|
|
1044
|
-
showToast('Backup settings saved');
|
|
1045
|
-
} catch (error) {
|
|
1046
|
-
console.error('Error saving backup config:', error);
|
|
1047
|
-
showToast('Failed to save backup settings');
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
1050
|
-
|
|
1051
|
-
async function createBackupNow() {
|
|
1052
|
-
showToast('Creating backup...');
|
|
1053
|
-
try {
|
|
1054
|
-
var response = await fetch('/api/backup/create', { method: 'POST' });
|
|
1055
|
-
var data = await response.json();
|
|
1056
|
-
if (data.success) {
|
|
1057
|
-
showToast('Backup created: ' + data.filename);
|
|
1058
|
-
loadBackupStatus();
|
|
1059
|
-
loadBackupList();
|
|
1060
|
-
} else {
|
|
1061
|
-
showToast('Backup failed');
|
|
1062
|
-
}
|
|
1063
|
-
} catch (error) {
|
|
1064
|
-
console.error('Error creating backup:', error);
|
|
1065
|
-
showToast('Backup failed');
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
async function loadBackupList() {
|
|
1070
|
-
try {
|
|
1071
|
-
var response = await fetch('/api/backup/list');
|
|
1072
|
-
var data = await response.json();
|
|
1073
|
-
renderBackupList(data.backups || []);
|
|
1074
|
-
} catch (error) {
|
|
1075
|
-
var container = document.getElementById('backup-list');
|
|
1076
|
-
container.textContent = 'Backup list unavailable';
|
|
1077
|
-
}
|
|
1078
|
-
}
|
|
1079
|
-
|
|
1080
|
-
function renderBackupList(backups) {
|
|
1081
|
-
var container = document.getElementById('backup-list');
|
|
1082
|
-
if (!backups || backups.length === 0) {
|
|
1083
|
-
showEmpty('backup-list', 'archive', 'No backups yet. Create your first backup above.');
|
|
1084
|
-
return;
|
|
1085
|
-
}
|
|
1086
|
-
|
|
1087
|
-
// Build table using DOM nodes
|
|
1088
|
-
var table = document.createElement('table');
|
|
1089
|
-
table.className = 'table table-sm';
|
|
1090
|
-
var thead = document.createElement('thead');
|
|
1091
|
-
var headRow = document.createElement('tr');
|
|
1092
|
-
['Filename', 'Size', 'Age', 'Created'].forEach(function(h) {
|
|
1093
|
-
var th = document.createElement('th');
|
|
1094
|
-
th.textContent = h;
|
|
1095
|
-
headRow.appendChild(th);
|
|
1096
|
-
});
|
|
1097
|
-
thead.appendChild(headRow);
|
|
1098
|
-
table.appendChild(thead);
|
|
1099
|
-
|
|
1100
|
-
var tbody = document.createElement('tbody');
|
|
1101
|
-
backups.forEach(function(b) {
|
|
1102
|
-
var row = document.createElement('tr');
|
|
1103
|
-
var age = b.age_hours < 48 ? Math.round(b.age_hours) + 'h ago' : Math.round(b.age_hours / 24) + 'd ago';
|
|
1104
|
-
var cells = [b.filename, b.size_mb + ' MB', age, formatDateFull(b.created)];
|
|
1105
|
-
cells.forEach(function(text) {
|
|
1106
|
-
var td = document.createElement('td');
|
|
1107
|
-
td.textContent = text;
|
|
1108
|
-
row.appendChild(td);
|
|
1109
|
-
});
|
|
1110
|
-
tbody.appendChild(row);
|
|
1111
|
-
});
|
|
1112
|
-
table.appendChild(tbody);
|
|
1113
|
-
|
|
1114
|
-
container.textContent = '';
|
|
1115
|
-
container.appendChild(table);
|
|
1116
|
-
}
|
|
1117
|
-
|
|
1118
|
-
// ============================================================================
|
|
1119
|
-
// Column Sorting
|
|
1120
|
-
// ============================================================================
|
|
1121
|
-
|
|
1122
|
-
var currentSort = { column: null, direction: 'asc' };
|
|
1123
|
-
|
|
1124
|
-
function handleSort(th) {
|
|
1125
|
-
var col = th.getAttribute('data-sort');
|
|
1126
|
-
if (!col) return;
|
|
1127
|
-
|
|
1128
|
-
if (currentSort.column === col) {
|
|
1129
|
-
currentSort.direction = currentSort.direction === 'asc' ? 'desc' : 'asc';
|
|
1130
|
-
} else {
|
|
1131
|
-
currentSort.column = col;
|
|
1132
|
-
currentSort.direction = 'asc';
|
|
1133
|
-
}
|
|
1134
|
-
|
|
1135
|
-
// Update header classes
|
|
1136
|
-
document.querySelectorAll('#memories-list th.sortable').forEach(function(h) {
|
|
1137
|
-
h.classList.remove('sort-asc', 'sort-desc');
|
|
1138
|
-
});
|
|
1139
|
-
th.classList.add('sort-' + currentSort.direction);
|
|
1140
|
-
|
|
1141
|
-
// Sort the data
|
|
1142
|
-
if (!window._slmMemories) return;
|
|
1143
|
-
var memories = window._slmMemories.slice();
|
|
1144
|
-
var dir = currentSort.direction === 'asc' ? 1 : -1;
|
|
1145
|
-
|
|
1146
|
-
memories.sort(function(a, b) {
|
|
1147
|
-
var av, bv;
|
|
1148
|
-
switch (col) {
|
|
1149
|
-
case 'id': return ((a.id || 0) - (b.id || 0)) * dir;
|
|
1150
|
-
case 'importance': return ((a.importance || 0) - (b.importance || 0)) * dir;
|
|
1151
|
-
case 'category':
|
|
1152
|
-
av = (a.category || '').toLowerCase(); bv = (b.category || '').toLowerCase();
|
|
1153
|
-
return av < bv ? -dir : av > bv ? dir : 0;
|
|
1154
|
-
case 'project':
|
|
1155
|
-
av = (a.project_name || '').toLowerCase(); bv = (b.project_name || '').toLowerCase();
|
|
1156
|
-
return av < bv ? -dir : av > bv ? dir : 0;
|
|
1157
|
-
case 'created':
|
|
1158
|
-
av = a.created_at || ''; bv = b.created_at || '';
|
|
1159
|
-
return av < bv ? -dir : av > bv ? dir : 0;
|
|
1160
|
-
case 'score': return ((a.score || 0) - (b.score || 0)) * dir;
|
|
1161
|
-
default: return 0;
|
|
1162
|
-
}
|
|
1163
|
-
});
|
|
1164
|
-
|
|
1165
|
-
window._slmMemories = memories;
|
|
1166
|
-
var showScores = memories.length > 0 && typeof memories[0].score === 'number';
|
|
1167
|
-
renderMemoriesTable(memories, showScores);
|
|
1168
|
-
}
|
|
1169
|
-
|
|
1170
|
-
// ============================================================================
|
|
1171
|
-
// v2.5 — Live Event Stream (SSE)
|
|
1172
|
-
// ============================================================================
|
|
1173
|
-
// Security note: All dynamic values are escaped via escapeHtml() before DOM insertion.
|
|
1174
|
-
// Data originates from our own trusted local SQLite database (localhost only).
|
|
1175
|
-
// No external/untrusted user input reaches the DOM — same pattern as existing code above.
|
|
1176
|
-
|
|
1177
|
-
var _eventSource = null;
|
|
1178
|
-
var _eventStreamItems = [];
|
|
1179
|
-
var _maxEventStreamItems = 200;
|
|
1180
|
-
|
|
1181
|
-
function initEventStream() {
|
|
1182
|
-
try {
|
|
1183
|
-
_eventSource = new EventSource('/events/stream');
|
|
1184
|
-
|
|
1185
|
-
_eventSource.onopen = function() {
|
|
1186
|
-
var badge = document.getElementById('event-connection-status');
|
|
1187
|
-
if (badge) {
|
|
1188
|
-
badge.textContent = 'Connected';
|
|
1189
|
-
badge.className = 'badge bg-success me-2';
|
|
1190
|
-
}
|
|
1191
|
-
};
|
|
1192
|
-
|
|
1193
|
-
_eventSource.onmessage = function(e) {
|
|
1194
|
-
try {
|
|
1195
|
-
var event = JSON.parse(e.data);
|
|
1196
|
-
appendEventToStream(event);
|
|
1197
|
-
} catch (err) {
|
|
1198
|
-
// Ignore parse errors (keepalive comments)
|
|
1199
|
-
}
|
|
1200
|
-
};
|
|
1201
|
-
|
|
1202
|
-
_eventSource.onerror = function() {
|
|
1203
|
-
var badge = document.getElementById('event-connection-status');
|
|
1204
|
-
if (badge) {
|
|
1205
|
-
badge.textContent = 'Reconnecting...';
|
|
1206
|
-
badge.className = 'badge bg-warning me-2';
|
|
1207
|
-
}
|
|
1208
|
-
};
|
|
1209
|
-
|
|
1210
|
-
['memory.created', 'memory.updated', 'memory.deleted', 'memory.recalled',
|
|
1211
|
-
'agent.connected', 'agent.disconnected', 'graph.updated', 'pattern.learned'
|
|
1212
|
-
].forEach(function(type) {
|
|
1213
|
-
_eventSource.addEventListener(type, function(e) {
|
|
1214
|
-
try {
|
|
1215
|
-
appendEventToStream(JSON.parse(e.data));
|
|
1216
|
-
} catch (err) { /* ignore */ }
|
|
1217
|
-
});
|
|
1218
|
-
});
|
|
1219
|
-
} catch (err) {
|
|
1220
|
-
console.log('SSE not available:', err);
|
|
1221
|
-
var badge = document.getElementById('event-connection-status');
|
|
1222
|
-
if (badge) {
|
|
1223
|
-
badge.textContent = 'Unavailable';
|
|
1224
|
-
badge.className = 'badge bg-secondary me-2';
|
|
1225
|
-
}
|
|
1226
|
-
}
|
|
1227
|
-
}
|
|
1228
|
-
|
|
1229
|
-
function appendEventToStream(event) {
|
|
1230
|
-
var container = document.getElementById('event-stream');
|
|
1231
|
-
if (!container) return;
|
|
1232
|
-
|
|
1233
|
-
if (_eventStreamItems.length === 0) {
|
|
1234
|
-
container.textContent = '';
|
|
1235
|
-
}
|
|
1236
|
-
|
|
1237
|
-
_eventStreamItems.push(event);
|
|
1238
|
-
if (_eventStreamItems.length > _maxEventStreamItems) {
|
|
1239
|
-
_eventStreamItems.shift();
|
|
1240
|
-
}
|
|
1241
|
-
|
|
1242
|
-
var filter = document.getElementById('event-type-filter');
|
|
1243
|
-
var filterValue = filter ? filter.value : '';
|
|
1244
|
-
if (filterValue && event.event_type !== filterValue) return;
|
|
1245
|
-
|
|
1246
|
-
var typeColors = {
|
|
1247
|
-
'memory.created': 'text-success', 'memory.updated': 'text-info',
|
|
1248
|
-
'memory.deleted': 'text-danger', 'memory.recalled': 'text-primary',
|
|
1249
|
-
'agent.connected': 'text-warning', 'agent.disconnected': 'text-secondary',
|
|
1250
|
-
'graph.updated': 'text-info', 'pattern.learned': 'text-success'
|
|
1251
|
-
};
|
|
1252
|
-
var typeIcons = {
|
|
1253
|
-
'memory.created': 'bi-plus-circle', 'memory.updated': 'bi-pencil',
|
|
1254
|
-
'memory.deleted': 'bi-trash', 'memory.recalled': 'bi-search',
|
|
1255
|
-
'agent.connected': 'bi-plug', 'agent.disconnected': 'bi-plug',
|
|
1256
|
-
'graph.updated': 'bi-diagram-3', 'pattern.learned': 'bi-lightbulb'
|
|
1257
|
-
};
|
|
1258
|
-
|
|
1259
|
-
var colorClass = typeColors[event.event_type] || 'text-muted';
|
|
1260
|
-
var iconClass = typeIcons[event.event_type] || 'bi-circle';
|
|
1261
|
-
var ts = event.timestamp ? new Date(event.timestamp).toLocaleTimeString() : '';
|
|
1262
|
-
var payload = event.payload || {};
|
|
1263
|
-
var preview = payload.content_preview || payload.agent_id || payload.agent_name || '';
|
|
1264
|
-
if (preview.length > 80) preview = preview.substring(0, 80) + '...';
|
|
1265
|
-
|
|
1266
|
-
// Build event line using safe DOM methods + escapeHtml for all dynamic content
|
|
1267
|
-
var div = document.createElement('div');
|
|
1268
|
-
div.className = 'event-line mb-1 pb-1 border-bottom border-opacity-25';
|
|
1269
|
-
|
|
1270
|
-
var timeSpan = document.createElement('small');
|
|
1271
|
-
timeSpan.className = 'text-muted';
|
|
1272
|
-
timeSpan.textContent = ts;
|
|
1273
|
-
|
|
1274
|
-
var icon = document.createElement('i');
|
|
1275
|
-
icon.className = 'bi ' + iconClass + ' ' + colorClass;
|
|
1276
|
-
icon.style.marginLeft = '6px';
|
|
1277
|
-
|
|
1278
|
-
var typeSpan = document.createElement('span');
|
|
1279
|
-
typeSpan.className = colorClass + ' fw-bold';
|
|
1280
|
-
typeSpan.style.marginLeft = '4px';
|
|
1281
|
-
typeSpan.textContent = event.event_type;
|
|
1282
|
-
|
|
1283
|
-
div.appendChild(timeSpan);
|
|
1284
|
-
div.appendChild(document.createTextNode(' '));
|
|
1285
|
-
div.appendChild(icon);
|
|
1286
|
-
div.appendChild(document.createTextNode(' '));
|
|
1287
|
-
div.appendChild(typeSpan);
|
|
1288
|
-
div.appendChild(document.createTextNode(' '));
|
|
1289
|
-
|
|
1290
|
-
if (event.memory_id) {
|
|
1291
|
-
var badge = document.createElement('span');
|
|
1292
|
-
badge.className = 'badge bg-secondary';
|
|
1293
|
-
badge.textContent = '#' + event.memory_id;
|
|
1294
|
-
div.appendChild(badge);
|
|
1295
|
-
div.appendChild(document.createTextNode(' '));
|
|
1296
|
-
}
|
|
1297
|
-
|
|
1298
|
-
var previewSpan = document.createElement('span');
|
|
1299
|
-
previewSpan.className = 'text-muted';
|
|
1300
|
-
previewSpan.textContent = preview;
|
|
1301
|
-
div.appendChild(previewSpan);
|
|
1302
|
-
|
|
1303
|
-
container.insertBefore(div, container.firstChild);
|
|
1304
|
-
|
|
1305
|
-
while (container.children.length > _maxEventStreamItems) {
|
|
1306
|
-
container.removeChild(container.lastChild);
|
|
1307
|
-
}
|
|
1308
|
-
}
|
|
1309
|
-
|
|
1310
|
-
function filterEvents() {
|
|
1311
|
-
var container = document.getElementById('event-stream');
|
|
1312
|
-
if (!container) return;
|
|
1313
|
-
container.textContent = '';
|
|
1314
|
-
|
|
1315
|
-
var filter = document.getElementById('event-type-filter');
|
|
1316
|
-
var filterValue = filter ? filter.value : '';
|
|
1317
|
-
|
|
1318
|
-
var filtered = filterValue
|
|
1319
|
-
? _eventStreamItems.filter(function(e) { return e.event_type === filterValue; })
|
|
1320
|
-
: _eventStreamItems;
|
|
1321
|
-
|
|
1322
|
-
filtered.forEach(function(event) {
|
|
1323
|
-
appendEventToStream(event);
|
|
1324
|
-
});
|
|
1325
|
-
}
|
|
1326
|
-
|
|
1327
|
-
function clearEventStream() {
|
|
1328
|
-
_eventStreamItems = [];
|
|
1329
|
-
var container = document.getElementById('event-stream');
|
|
1330
|
-
if (container) {
|
|
1331
|
-
container.textContent = '';
|
|
1332
|
-
var placeholder = document.createElement('div');
|
|
1333
|
-
placeholder.className = 'text-muted text-center py-4';
|
|
1334
|
-
var pIcon = document.createElement('i');
|
|
1335
|
-
pIcon.className = 'bi bi-broadcast';
|
|
1336
|
-
pIcon.style.fontSize = '2rem';
|
|
1337
|
-
placeholder.appendChild(pIcon);
|
|
1338
|
-
var pText = document.createElement('p');
|
|
1339
|
-
pText.className = 'mt-2';
|
|
1340
|
-
pText.textContent = 'Event stream cleared. Waiting for new events...';
|
|
1341
|
-
placeholder.appendChild(pText);
|
|
1342
|
-
container.appendChild(placeholder);
|
|
1343
|
-
}
|
|
1344
|
-
}
|
|
1345
|
-
|
|
1346
|
-
async function loadEventStats() {
|
|
1347
|
-
try {
|
|
1348
|
-
var response = await fetch('/api/events/stats');
|
|
1349
|
-
var stats = await response.json();
|
|
1350
|
-
var el;
|
|
1351
|
-
el = document.getElementById('event-stat-total');
|
|
1352
|
-
if (el) el.textContent = (stats.total_events || 0).toLocaleString();
|
|
1353
|
-
el = document.getElementById('event-stat-24h');
|
|
1354
|
-
if (el) el.textContent = (stats.events_last_24h || 0).toLocaleString();
|
|
1355
|
-
el = document.getElementById('event-stat-listeners');
|
|
1356
|
-
if (el) el.textContent = (stats.listener_count || 0).toLocaleString();
|
|
1357
|
-
el = document.getElementById('event-stat-buffer');
|
|
1358
|
-
if (el) el.textContent = (stats.buffer_size || 0).toLocaleString();
|
|
1359
|
-
} catch (err) {
|
|
1360
|
-
console.log('Event stats not available:', err);
|
|
1361
|
-
}
|
|
1362
|
-
}
|
|
1363
|
-
|
|
1364
|
-
// ============================================================================
|
|
1365
|
-
// v2.5 — Connected Agents
|
|
1366
|
-
// ============================================================================
|
|
1367
|
-
|
|
1368
|
-
async function loadAgents() {
|
|
1369
|
-
try {
|
|
1370
|
-
var response = await fetch('/api/agents');
|
|
1371
|
-
var data = await response.json();
|
|
1372
|
-
var agents = data.agents || [];
|
|
1373
|
-
var stats = data.stats || {};
|
|
1374
|
-
|
|
1375
|
-
var el;
|
|
1376
|
-
el = document.getElementById('agent-stat-total');
|
|
1377
|
-
if (el) el.textContent = (stats.total_agents || 0).toLocaleString();
|
|
1378
|
-
el = document.getElementById('agent-stat-active');
|
|
1379
|
-
if (el) el.textContent = (stats.active_last_24h || 0).toLocaleString();
|
|
1380
|
-
el = document.getElementById('agent-stat-writes');
|
|
1381
|
-
if (el) el.textContent = (stats.total_writes || 0).toLocaleString();
|
|
1382
|
-
el = document.getElementById('agent-stat-recalls');
|
|
1383
|
-
if (el) el.textContent = (stats.total_recalls || 0).toLocaleString();
|
|
1384
|
-
|
|
1385
|
-
var container = document.getElementById('agents-list');
|
|
1386
|
-
if (!container) return;
|
|
1387
|
-
|
|
1388
|
-
if (agents.length === 0) {
|
|
1389
|
-
container.textContent = '';
|
|
1390
|
-
var empty = document.createElement('div');
|
|
1391
|
-
empty.className = 'text-muted text-center py-4';
|
|
1392
|
-
var emptyIcon = document.createElement('i');
|
|
1393
|
-
emptyIcon.className = 'bi bi-robot';
|
|
1394
|
-
emptyIcon.style.fontSize = '2rem';
|
|
1395
|
-
empty.appendChild(emptyIcon);
|
|
1396
|
-
var emptyText = document.createElement('p');
|
|
1397
|
-
emptyText.className = 'mt-2';
|
|
1398
|
-
emptyText.textContent = 'No agents registered yet. Agents appear automatically when they connect via MCP, CLI, or REST.';
|
|
1399
|
-
empty.appendChild(emptyText);
|
|
1400
|
-
container.appendChild(empty);
|
|
1401
|
-
loadTrustOverview();
|
|
1402
|
-
return;
|
|
1403
|
-
}
|
|
1404
|
-
|
|
1405
|
-
// Build agent table using safe DOM methods
|
|
1406
|
-
var table = document.createElement('table');
|
|
1407
|
-
table.className = 'table table-hover table-sm';
|
|
1408
|
-
var thead = document.createElement('thead');
|
|
1409
|
-
var headerRow = document.createElement('tr');
|
|
1410
|
-
['Agent', 'Protocol', 'Trust', 'Writes', 'Recalls', 'Last Seen'].forEach(function(h) {
|
|
1411
|
-
var th = document.createElement('th');
|
|
1412
|
-
th.textContent = h;
|
|
1413
|
-
headerRow.appendChild(th);
|
|
1414
|
-
});
|
|
1415
|
-
thead.appendChild(headerRow);
|
|
1416
|
-
table.appendChild(thead);
|
|
1417
|
-
|
|
1418
|
-
var tbody = document.createElement('tbody');
|
|
1419
|
-
agents.forEach(function(agent) {
|
|
1420
|
-
var tr = document.createElement('tr');
|
|
1421
|
-
|
|
1422
|
-
// Agent name cell
|
|
1423
|
-
var tdName = document.createElement('td');
|
|
1424
|
-
var strong = document.createElement('strong');
|
|
1425
|
-
strong.textContent = agent.agent_name || agent.agent_id;
|
|
1426
|
-
tdName.appendChild(strong);
|
|
1427
|
-
tdName.appendChild(document.createElement('br'));
|
|
1428
|
-
var smallId = document.createElement('small');
|
|
1429
|
-
smallId.className = 'text-muted';
|
|
1430
|
-
smallId.textContent = agent.agent_id;
|
|
1431
|
-
tdName.appendChild(smallId);
|
|
1432
|
-
tr.appendChild(tdName);
|
|
1433
|
-
|
|
1434
|
-
// Protocol badge
|
|
1435
|
-
var tdProto = document.createElement('td');
|
|
1436
|
-
var protoBadge = document.createElement('span');
|
|
1437
|
-
var protocolColors = {
|
|
1438
|
-
'mcp': 'bg-primary', 'cli': 'bg-success', 'rest': 'bg-info',
|
|
1439
|
-
'python': 'bg-secondary'
|
|
1440
|
-
};
|
|
1441
|
-
protoBadge.className = 'badge ' + (protocolColors[agent.protocol] || 'bg-secondary');
|
|
1442
|
-
protoBadge.textContent = agent.protocol;
|
|
1443
|
-
tdProto.appendChild(protoBadge);
|
|
1444
|
-
tr.appendChild(tdProto);
|
|
1445
|
-
|
|
1446
|
-
// Trust score
|
|
1447
|
-
var tdTrust = document.createElement('td');
|
|
1448
|
-
var trustScore = agent.trust_score != null ? agent.trust_score : 0.667;
|
|
1449
|
-
tdTrust.className = trustScore < 0.3 ? 'text-danger fw-bold'
|
|
1450
|
-
: trustScore < 0.5 ? 'text-warning fw-bold' : 'text-success fw-bold';
|
|
1451
|
-
tdTrust.textContent = trustScore.toFixed(2);
|
|
1452
|
-
tr.appendChild(tdTrust);
|
|
1453
|
-
|
|
1454
|
-
// Writes
|
|
1455
|
-
var tdW = document.createElement('td');
|
|
1456
|
-
tdW.textContent = agent.memories_written || 0;
|
|
1457
|
-
tr.appendChild(tdW);
|
|
1458
|
-
|
|
1459
|
-
// Recalls
|
|
1460
|
-
var tdR = document.createElement('td');
|
|
1461
|
-
tdR.textContent = agent.memories_recalled || 0;
|
|
1462
|
-
tr.appendChild(tdR);
|
|
1463
|
-
|
|
1464
|
-
// Last seen
|
|
1465
|
-
var tdLast = document.createElement('td');
|
|
1466
|
-
var lastSmall = document.createElement('small');
|
|
1467
|
-
lastSmall.textContent = agent.last_seen ? new Date(agent.last_seen).toLocaleString() : 'Never';
|
|
1468
|
-
tdLast.appendChild(lastSmall);
|
|
1469
|
-
tr.appendChild(tdLast);
|
|
1470
|
-
|
|
1471
|
-
tbody.appendChild(tr);
|
|
1472
|
-
});
|
|
1473
|
-
table.appendChild(tbody);
|
|
1474
|
-
|
|
1475
|
-
container.textContent = '';
|
|
1476
|
-
container.appendChild(table);
|
|
1477
|
-
|
|
1478
|
-
loadTrustOverview();
|
|
1479
|
-
|
|
1480
|
-
} catch (err) {
|
|
1481
|
-
console.log('Agents not available:', err);
|
|
1482
|
-
var container = document.getElementById('agents-list');
|
|
1483
|
-
if (container) {
|
|
1484
|
-
container.textContent = '';
|
|
1485
|
-
var msg = document.createElement('small');
|
|
1486
|
-
msg.className = 'text-muted';
|
|
1487
|
-
msg.textContent = 'Agent registry not available. This feature requires v2.5+.';
|
|
1488
|
-
container.appendChild(msg);
|
|
1489
|
-
}
|
|
1490
|
-
}
|
|
1491
|
-
}
|
|
1492
|
-
|
|
1493
|
-
async function loadTrustOverview() {
|
|
1494
|
-
try {
|
|
1495
|
-
var response = await fetch('/api/trust/stats');
|
|
1496
|
-
var stats = await response.json();
|
|
1497
|
-
var container = document.getElementById('trust-overview');
|
|
1498
|
-
if (!container) return;
|
|
1499
|
-
|
|
1500
|
-
container.textContent = '';
|
|
1501
|
-
var row = document.createElement('div');
|
|
1502
|
-
row.className = 'row g-3';
|
|
1503
|
-
|
|
1504
|
-
// Total signals card
|
|
1505
|
-
var col1 = document.createElement('div');
|
|
1506
|
-
col1.className = 'col-md-4';
|
|
1507
|
-
var card1 = document.createElement('div');
|
|
1508
|
-
card1.className = 'border rounded p-3 text-center';
|
|
1509
|
-
var val1 = document.createElement('div');
|
|
1510
|
-
val1.className = 'fs-4 fw-bold';
|
|
1511
|
-
val1.textContent = (stats.total_signals || 0).toLocaleString();
|
|
1512
|
-
card1.appendChild(val1);
|
|
1513
|
-
var lbl1 = document.createElement('small');
|
|
1514
|
-
lbl1.className = 'text-muted';
|
|
1515
|
-
lbl1.textContent = 'Total Signals Collected';
|
|
1516
|
-
card1.appendChild(lbl1);
|
|
1517
|
-
col1.appendChild(card1);
|
|
1518
|
-
row.appendChild(col1);
|
|
1519
|
-
|
|
1520
|
-
// Avg trust card
|
|
1521
|
-
var col2 = document.createElement('div');
|
|
1522
|
-
col2.className = 'col-md-4';
|
|
1523
|
-
var card2 = document.createElement('div');
|
|
1524
|
-
card2.className = 'border rounded p-3 text-center';
|
|
1525
|
-
var val2 = document.createElement('div');
|
|
1526
|
-
val2.className = 'fs-4 fw-bold';
|
|
1527
|
-
val2.textContent = (stats.avg_trust_score || 0.667).toFixed(3);
|
|
1528
|
-
card2.appendChild(val2);
|
|
1529
|
-
var lbl2 = document.createElement('small');
|
|
1530
|
-
lbl2.className = 'text-muted';
|
|
1531
|
-
lbl2.textContent = 'Average Trust Score';
|
|
1532
|
-
card2.appendChild(lbl2);
|
|
1533
|
-
col2.appendChild(card2);
|
|
1534
|
-
row.appendChild(col2);
|
|
1535
|
-
|
|
1536
|
-
// Enforcement card
|
|
1537
|
-
var col3 = document.createElement('div');
|
|
1538
|
-
col3.className = 'col-md-4';
|
|
1539
|
-
var card3 = document.createElement('div');
|
|
1540
|
-
card3.className = 'border rounded p-3 text-center';
|
|
1541
|
-
var val3 = document.createElement('div');
|
|
1542
|
-
val3.className = 'fs-4 fw-bold text-info';
|
|
1543
|
-
val3.textContent = stats.enforcement || 'disabled';
|
|
1544
|
-
card3.appendChild(val3);
|
|
1545
|
-
var lbl3 = document.createElement('small');
|
|
1546
|
-
lbl3.className = 'text-muted';
|
|
1547
|
-
lbl3.textContent = 'Enforcement Status';
|
|
1548
|
-
card3.appendChild(lbl3);
|
|
1549
|
-
col3.appendChild(card3);
|
|
1550
|
-
row.appendChild(col3);
|
|
1551
|
-
|
|
1552
|
-
container.appendChild(row);
|
|
1553
|
-
|
|
1554
|
-
// Signal breakdown
|
|
1555
|
-
if (stats.by_signal_type && Object.keys(stats.by_signal_type).length > 0) {
|
|
1556
|
-
var breakdownDiv = document.createElement('div');
|
|
1557
|
-
breakdownDiv.className = 'col-12 mt-3';
|
|
1558
|
-
var h6 = document.createElement('h6');
|
|
1559
|
-
h6.textContent = 'Signal Breakdown';
|
|
1560
|
-
breakdownDiv.appendChild(h6);
|
|
1561
|
-
var badgeWrap = document.createElement('div');
|
|
1562
|
-
badgeWrap.className = 'd-flex flex-wrap gap-2';
|
|
1563
|
-
Object.keys(stats.by_signal_type).forEach(function(type) {
|
|
1564
|
-
var count = stats.by_signal_type[type];
|
|
1565
|
-
var signalClass = (type.indexOf('high_volume') >= 0 || type.indexOf('quick_delete') >= 0)
|
|
1566
|
-
? 'bg-danger' : (type.indexOf('recalled') >= 0 || type.indexOf('high_importance') >= 0)
|
|
1567
|
-
? 'bg-success' : 'bg-secondary';
|
|
1568
|
-
var b = document.createElement('span');
|
|
1569
|
-
b.className = 'badge ' + signalClass;
|
|
1570
|
-
b.textContent = type + ': ' + count;
|
|
1571
|
-
badgeWrap.appendChild(b);
|
|
1572
|
-
});
|
|
1573
|
-
breakdownDiv.appendChild(badgeWrap);
|
|
1574
|
-
container.appendChild(breakdownDiv);
|
|
1575
|
-
}
|
|
1576
|
-
|
|
1577
|
-
} catch (err) {
|
|
1578
|
-
console.log('Trust stats not available:', err);
|
|
1579
|
-
var container = document.getElementById('trust-overview');
|
|
1580
|
-
if (container) {
|
|
1581
|
-
container.textContent = '';
|
|
1582
|
-
var msg = document.createElement('small');
|
|
1583
|
-
msg.className = 'text-muted';
|
|
1584
|
-
msg.textContent = 'Trust scoring data will appear here once agents interact with memory.';
|
|
1585
|
-
container.appendChild(msg);
|
|
1586
|
-
}
|
|
1587
|
-
}
|
|
1588
|
-
}
|