nexo-brain 2.3.2 → 2.5.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/README.md +77 -8
- package/bin/nexo-brain.js +230 -22
- package/bin/nexo.js +55 -0
- package/community/skills/.gitkeep +1 -0
- package/package.json +5 -2
- package/src/auto_update.py +158 -8
- package/src/cli.py +605 -0
- package/src/cognitive/_ingest.py +1 -1
- package/src/cognitive/_memory.py +4 -4
- package/src/crons/manifest.json +8 -0
- package/src/dashboard/app.py +709 -37
- package/src/dashboard/templates/adaptive.html +112 -218
- package/src/dashboard/templates/artifacts.html +133 -0
- package/src/dashboard/templates/backups.html +136 -0
- package/src/dashboard/templates/base.html +413 -0
- package/src/dashboard/templates/calendar.html +523 -652
- package/src/dashboard/templates/chat.html +356 -0
- package/src/dashboard/templates/claims.html +259 -0
- package/src/dashboard/templates/cortex.html +262 -0
- package/src/dashboard/templates/credentials.html +128 -0
- package/src/dashboard/templates/crons.html +370 -0
- package/src/dashboard/templates/dashboard.html +384 -572
- package/src/dashboard/templates/dreams.html +252 -0
- package/src/dashboard/templates/email.html +160 -0
- package/src/dashboard/templates/evolution.html +189 -0
- package/src/dashboard/templates/feed.html +249 -0
- package/src/dashboard/templates/followup_health.html +170 -0
- package/src/dashboard/templates/graph.html +191 -269
- package/src/dashboard/templates/guard.html +259 -0
- package/src/dashboard/templates/inbox.html +220 -336
- package/src/dashboard/templates/memory.html +317 -197
- package/src/dashboard/templates/operations.html +498 -652
- package/src/dashboard/templates/plugins.html +185 -0
- package/src/dashboard/templates/rules.html +246 -0
- package/src/dashboard/templates/sentiment.html +247 -0
- package/src/dashboard/templates/sessions.html +215 -171
- package/src/dashboard/templates/skills.html +329 -0
- package/src/dashboard/templates/somatic.html +68 -172
- package/src/dashboard/templates/triggers.html +133 -0
- package/src/dashboard/templates/trust.html +360 -0
- package/src/db/__init__.py +5 -0
- package/src/db/_schema.py +25 -1
- package/src/db/_sessions.py +22 -0
- package/src/db/_skills.py +983 -252
- package/src/doctor/__init__.py +1 -0
- package/src/doctor/formatters.py +52 -0
- package/src/doctor/models.py +44 -0
- package/src/doctor/orchestrator.py +42 -0
- package/src/doctor/providers/__init__.py +1 -0
- package/src/doctor/providers/boot.py +206 -0
- package/src/doctor/providers/deep.py +292 -0
- package/src/doctor/providers/runtime.py +686 -0
- package/src/hooks/capture-tool-logs.sh +18 -4
- package/src/hooks/post-compact.sh +5 -1
- package/src/hooks/pre-compact.sh +1 -1
- package/src/plugin_loader.py +14 -0
- package/src/plugins/doctor.py +36 -0
- package/src/plugins/evolution.py +2 -1
- package/src/plugins/skills.py +135 -175
- package/src/requirements.txt +1 -0
- package/src/script_registry.py +322 -0
- package/src/scripts/deep-sleep/apply_findings.py +63 -33
- package/src/scripts/deep-sleep/collect.py +38 -9
- package/src/scripts/deep-sleep/extract-prompt.md +14 -0
- package/src/scripts/deep-sleep/synthesize-prompt.md +36 -0
- package/src/scripts/deep-sleep/synthesize.py +37 -1
- package/src/scripts/nexo-dashboard.sh +29 -0
- package/src/scripts/nexo-day-orchestrator.sh +139 -0
- package/src/scripts/nexo-evolution-run.py +2 -1
- package/src/scripts/nexo-learning-housekeep.py +1 -1
- package/src/scripts/nexo-watchdog.sh +1 -1
- package/src/server.py +9 -5
- package/src/skills/run-runtime-doctor/guide.md +12 -0
- package/src/skills/run-runtime-doctor/script.py +21 -0
- package/src/skills/run-runtime-doctor/skill.json +25 -0
- package/src/skills_runtime.py +347 -0
- package/src/tools_menu.py +3 -2
- package/src/tools_sessions.py +126 -0
- package/src/user_context.py +46 -0
- package/templates/nexo_helper.py +45 -0
- package/templates/script-template.py +44 -0
- package/templates/skill-script-template.py +39 -0
- package/templates/skill-template.md +33 -0
|
@@ -1,622 +1,434 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
<
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
<a href="/ops" class="w-10 h-10 rounded-lg flex items-center justify-center text-slate-400 hover:bg-slate-800 hover:text-slate-200 transition-colors" title="Operations">
|
|
37
|
-
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"/></svg>
|
|
38
|
-
</a>
|
|
39
|
-
<!-- Calendar -->
|
|
40
|
-
<a href="/calendar" class="w-10 h-10 rounded-lg flex items-center justify-center text-slate-400 hover:bg-slate-800 hover:text-slate-200 transition-colors" title="Calendar">
|
|
41
|
-
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/></svg>
|
|
42
|
-
</a>
|
|
43
|
-
<!-- Inbox -->
|
|
44
|
-
<a href="/inbox" class="w-10 h-10 rounded-lg flex items-center justify-center text-slate-400 hover:bg-slate-800 hover:text-slate-200 transition-colors relative" title="Inbox">
|
|
45
|
-
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/></svg>
|
|
46
|
-
<span id="inbox-badge" class="hidden absolute -top-0.5 -right-0.5 w-4 h-4 bg-pink-500 rounded-full text-xs font-medium items-center justify-center text-white">0</span>
|
|
47
|
-
</a>
|
|
48
|
-
|
|
49
|
-
<div class="w-6 border-t border-slate-700 my-2"></div>
|
|
50
|
-
|
|
51
|
-
<!-- Memory -->
|
|
52
|
-
<a href="/memory" class="w-10 h-10 rounded-lg flex items-center justify-center text-slate-400 hover:bg-slate-800 hover:text-slate-200 transition-colors" title="Memory">
|
|
53
|
-
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/></svg>
|
|
54
|
-
</a>
|
|
55
|
-
<!-- Graph -->
|
|
56
|
-
<a href="/graph" class="w-10 h-10 rounded-lg flex items-center justify-center text-slate-400 hover:bg-slate-800 hover:text-slate-200 transition-colors" title="Graph">
|
|
57
|
-
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"/></svg>
|
|
58
|
-
</a>
|
|
59
|
-
<!-- Sessions -->
|
|
60
|
-
<a href="/sessions" class="w-10 h-10 rounded-lg flex items-center justify-center text-slate-400 hover:bg-slate-800 hover:text-slate-200 transition-colors" title="Sessions">
|
|
61
|
-
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
|
62
|
-
</a>
|
|
63
|
-
<!-- Somatic -->
|
|
64
|
-
<a href="/somatic" class="w-10 h-10 rounded-lg flex items-center justify-center text-slate-400 hover:bg-slate-800 hover:text-slate-200 transition-colors" title="Somatic">
|
|
65
|
-
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"/></svg>
|
|
66
|
-
</a>
|
|
67
|
-
<!-- Adaptive -->
|
|
68
|
-
<a href="/adaptive" class="w-10 h-10 rounded-lg flex items-center justify-center text-slate-400 hover:bg-slate-800 hover:text-slate-200 transition-colors" title="Adaptive">
|
|
69
|
-
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4"/></svg>
|
|
70
|
-
</a>
|
|
71
|
-
</nav>
|
|
72
|
-
|
|
73
|
-
<!-- Trust score footer -->
|
|
74
|
-
<div class="mt-auto pt-4 border-t border-slate-800 w-full flex flex-col items-center" id="sidebar-trust">
|
|
75
|
-
<div class="text-xs text-slate-400 uppercase tracking-wider">Trust</div>
|
|
76
|
-
<div class="text-sm font-mono font-medium text-violet-400" id="sidebar-trust-value">--</div>
|
|
1
|
+
{% extends "base.html" %}
|
|
2
|
+
|
|
3
|
+
{% block title %}Cognitive Pulse{% endblock %}
|
|
4
|
+
{% block page_title %}Cognitive Pulse{% endblock %}
|
|
5
|
+
|
|
6
|
+
{% block header_actions %}
|
|
7
|
+
<button onclick="openQuickCreate('reminder')" class="text-xs px-2.5 py-1 rounded-md bg-slate-800 text-slate-300 hover:bg-slate-700 transition-colors">+ Reminder</button>
|
|
8
|
+
<button onclick="openQuickCreate('followup')" class="text-xs px-2.5 py-1 rounded-md bg-slate-800 text-slate-300 hover:bg-slate-700 transition-colors">+ Followup</button>
|
|
9
|
+
<button onclick="openQuickCreate('note')" class="text-xs px-2.5 py-1 rounded-md bg-slate-800 text-slate-300 hover:bg-slate-700 transition-colors">+ Note</button>
|
|
10
|
+
{% endblock %}
|
|
11
|
+
|
|
12
|
+
{% block content %}
|
|
13
|
+
<div class="space-y-4">
|
|
14
|
+
<!-- Row 1: 4 stat cards -->
|
|
15
|
+
<div class="grid grid-cols-4 gap-4">
|
|
16
|
+
<!-- Trust Score -->
|
|
17
|
+
<div class="bg-slate-900/50 border border-slate-800/50 rounded-xl p-5 card">
|
|
18
|
+
<div class="text-xs uppercase tracking-wider text-slate-400 font-medium mb-3">Trust Score</div>
|
|
19
|
+
<div class="flex items-center gap-4">
|
|
20
|
+
<div class="relative">
|
|
21
|
+
<svg id="trust-gauge" viewBox="0 0 80 80" class="w-16 h-16">
|
|
22
|
+
<circle cx="40" cy="40" r="32" fill="none" stroke="rgba(51,65,85,0.4)" stroke-width="6"/>
|
|
23
|
+
<circle id="trust-arc" cx="40" cy="40" r="32" fill="none" stroke="#7C3AED" stroke-width="6"
|
|
24
|
+
stroke-dasharray="0 201" stroke-dashoffset="50.3" stroke-linecap="round"
|
|
25
|
+
style="transition: stroke-dasharray 1s ease-out, stroke 0.5s;"/>
|
|
26
|
+
<text id="trust-center" x="40" y="44" text-anchor="middle" fill="#7C3AED" class="font-mono font-bold" style="font-size:16px">--</text>
|
|
27
|
+
</svg>
|
|
28
|
+
</div>
|
|
29
|
+
<div>
|
|
30
|
+
<div class="text-xs text-slate-500" id="trust-label">loading...</div>
|
|
31
|
+
<div class="mt-1 h-1 w-20 bg-slate-800 rounded-full overflow-hidden">
|
|
32
|
+
<div id="trust-bar" class="h-full bg-gradient-to-r from-violet-500 to-pink-500 rounded-full transition-all duration-700" style="width:0%"></div>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
77
36
|
</div>
|
|
78
|
-
</aside>
|
|
79
37
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
<!-- Top bar -->
|
|
84
|
-
<header class="h-12 border-b border-slate-800/50 flex items-center justify-between px-6 sticky top-0 bg-gray-950/95 backdrop-blur z-40">
|
|
85
|
-
<h1 class="text-sm font-display font-semibold text-slate-100">Operations Center</h1>
|
|
38
|
+
<!-- Active Sessions -->
|
|
39
|
+
<div class="bg-slate-900/50 border border-slate-800/50 rounded-xl p-5 card">
|
|
40
|
+
<div class="text-xs uppercase tracking-wider text-slate-400 font-medium mb-3">Sessions</div>
|
|
86
41
|
<div class="flex items-center gap-2">
|
|
87
|
-
<
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
<span class="text-
|
|
42
|
+
<span class="relative flex h-2.5 w-2.5">
|
|
43
|
+
<span id="session-pulse" class="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
|
|
44
|
+
<span class="relative inline-flex rounded-full h-2.5 w-2.5 bg-emerald-500"></span>
|
|
45
|
+
</span>
|
|
46
|
+
<span class="text-2xl font-mono font-semibold text-slate-200" id="session-count">0</span>
|
|
92
47
|
</div>
|
|
93
|
-
|
|
48
|
+
<div class="mt-2 text-xs text-slate-500" id="session-label">active terminals</div>
|
|
49
|
+
<div class="mt-1 text-xs text-slate-600 font-mono" id="session-detail">--</div>
|
|
50
|
+
</div>
|
|
94
51
|
|
|
95
|
-
<!--
|
|
96
|
-
<
|
|
52
|
+
<!-- Overdue Items -->
|
|
53
|
+
<a href="/ops" class="bg-slate-900/50 border border-slate-800/50 rounded-xl p-5 card block hover:border-slate-700/50 transition-colors">
|
|
54
|
+
<div class="text-xs uppercase tracking-wider text-slate-400 font-medium mb-3">Overdue</div>
|
|
55
|
+
<div class="flex items-end gap-2">
|
|
56
|
+
<span class="text-2xl font-mono font-semibold" id="overdue-count">0</span>
|
|
57
|
+
</div>
|
|
58
|
+
<div class="mt-2 text-xs text-slate-500" id="overdue-detail">reminders & followups</div>
|
|
59
|
+
<div class="mt-1 text-xs text-violet-500 font-medium">view in ops →</div>
|
|
60
|
+
</a>
|
|
97
61
|
|
|
98
|
-
|
|
99
|
-
|
|
62
|
+
<!-- Watchdog -->
|
|
63
|
+
<div class="bg-slate-900/50 border border-slate-800/50 rounded-xl p-5 card">
|
|
64
|
+
<div class="text-xs uppercase tracking-wider text-slate-400 font-medium mb-3">Watchdog</div>
|
|
65
|
+
<div class="flex items-center gap-2 mb-3">
|
|
66
|
+
<span id="watchdog-badge" class="inline-flex items-center px-2 py-0.5 rounded text-xs font-mono font-medium bg-slate-700 text-slate-300">--</span>
|
|
67
|
+
</div>
|
|
68
|
+
<div id="watchdog-services" class="space-y-1.5">
|
|
69
|
+
<div class="text-xs text-slate-500">loading...</div>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
100
73
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
</div>
|
|
111
|
-
<div class="mt-2 flex items-center gap-1.5">
|
|
112
|
-
<span class="text-xs text-slate-500" id="trust-label">loading...</span>
|
|
113
|
-
</div>
|
|
114
|
-
</div>
|
|
74
|
+
<!-- Row 2: 3 detail cards -->
|
|
75
|
+
<div class="grid grid-cols-3 gap-4">
|
|
76
|
+
<!-- Today's Agenda -->
|
|
77
|
+
<div class="bg-slate-900/50 border border-slate-800/50 rounded-xl p-5 card">
|
|
78
|
+
<div class="text-xs uppercase tracking-wider text-slate-400 font-medium mb-3">Today's Agenda</div>
|
|
79
|
+
<ul id="agenda-list" class="space-y-1.5">
|
|
80
|
+
<li class="text-xs text-slate-600 py-1">No items due today</li>
|
|
81
|
+
</ul>
|
|
82
|
+
</div>
|
|
115
83
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
<span class="text-2xl font-mono font-semibold text-slate-200" id="session-count">0</span>
|
|
84
|
+
<!-- Cognitive Memory -->
|
|
85
|
+
<div class="bg-slate-900/50 border border-slate-800/50 rounded-xl p-5 card">
|
|
86
|
+
<div class="text-xs uppercase tracking-wider text-slate-400 font-medium mb-3">Cognitive Memory</div>
|
|
87
|
+
<div class="space-y-3">
|
|
88
|
+
<div>
|
|
89
|
+
<div class="flex items-center justify-between mb-1">
|
|
90
|
+
<span class="text-xs text-slate-400 uppercase tracking-wide">STM</span>
|
|
91
|
+
<span class="text-xs font-mono text-slate-300" id="cog-stm">--</span>
|
|
125
92
|
</div>
|
|
126
|
-
<div class="
|
|
127
|
-
|
|
128
|
-
</div>
|
|
129
|
-
|
|
130
|
-
<!-- Overdue Items -->
|
|
131
|
-
<a href="/ops" class="bg-slate-900/50 border border-slate-800/50 rounded-lg p-4 hover:border-slate-700 transition-colors block">
|
|
132
|
-
<div class="text-xs uppercase tracking-wider text-slate-400 font-medium mb-3">Overdue</div>
|
|
133
|
-
<div class="flex items-end gap-2">
|
|
134
|
-
<span class="text-2xl font-mono font-semibold" id="overdue-count">0</span>
|
|
93
|
+
<div class="h-1 bg-slate-800 rounded-full overflow-hidden">
|
|
94
|
+
<div id="cog-stm-bar" class="h-full bg-violet-500/60 rounded-full transition-all duration-500" style="width:0%"></div>
|
|
135
95
|
</div>
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
<div class="bg-slate-900/50 border border-slate-800/50 rounded-lg p-4">
|
|
142
|
-
<div class="text-xs uppercase tracking-wider text-slate-400 font-medium mb-3">Watchdog</div>
|
|
143
|
-
<div class="flex items-center gap-2 mb-3">
|
|
144
|
-
<span id="watchdog-badge" class="inline-flex items-center px-2 py-0.5 rounded text-xs font-mono font-medium bg-slate-700 text-slate-300">--</span>
|
|
96
|
+
</div>
|
|
97
|
+
<div>
|
|
98
|
+
<div class="flex items-center justify-between mb-1">
|
|
99
|
+
<span class="text-xs text-slate-400 uppercase tracking-wide">LTM</span>
|
|
100
|
+
<span class="text-xs font-mono text-slate-300" id="cog-ltm">--</span>
|
|
145
101
|
</div>
|
|
146
|
-
<div
|
|
147
|
-
<div class="
|
|
102
|
+
<div class="h-1 bg-slate-800 rounded-full overflow-hidden">
|
|
103
|
+
<div id="cog-ltm-bar" class="h-full bg-pink-500/60 rounded-full transition-all duration-500" style="width:0%"></div>
|
|
148
104
|
</div>
|
|
149
105
|
</div>
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
<div class="grid grid-cols-3 gap-4">
|
|
154
|
-
|
|
155
|
-
<!-- Today's Agenda -->
|
|
156
|
-
<div class="bg-slate-900/50 border border-slate-800/50 rounded-lg p-4">
|
|
157
|
-
<div class="text-xs uppercase tracking-wider text-slate-400 font-medium mb-3">Today's Agenda</div>
|
|
158
|
-
<ul id="agenda-list" class="space-y-1.5">
|
|
159
|
-
<li class="text-xs text-slate-600 py-1">No items due today</li>
|
|
160
|
-
</ul>
|
|
106
|
+
<div class="flex items-center justify-between pt-1 border-t border-slate-800">
|
|
107
|
+
<span class="text-xs text-slate-500">Avg strength</span>
|
|
108
|
+
<span class="text-xs font-mono text-slate-400" id="cog-strength">--</span>
|
|
161
109
|
</div>
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
<div class="text-xs uppercase tracking-wider text-slate-400 font-medium mb-3">Cognitive Memory</div>
|
|
166
|
-
<div class="space-y-3">
|
|
167
|
-
<!-- STM -->
|
|
110
|
+
<div class="pt-1 border-t border-slate-800">
|
|
111
|
+
<div class="text-xs text-slate-400 uppercase tracking-wide mb-2">Knowledge Graph</div>
|
|
112
|
+
<div class="grid grid-cols-3 gap-2 text-center">
|
|
168
113
|
<div>
|
|
169
|
-
<div class="
|
|
170
|
-
|
|
171
|
-
<span class="text-xs font-mono text-slate-300" id="cog-stm">--</span>
|
|
172
|
-
</div>
|
|
173
|
-
<div class="h-1 bg-slate-800 rounded-full overflow-hidden">
|
|
174
|
-
<div id="cog-stm-bar" class="h-full bg-violet-500/60 rounded-full transition-all duration-500" style="width:0%"></div>
|
|
175
|
-
</div>
|
|
114
|
+
<div class="text-sm font-mono font-medium text-slate-200" id="kg-nodes">--</div>
|
|
115
|
+
<div class="text-xs text-slate-500 mt-0.5">nodes</div>
|
|
176
116
|
</div>
|
|
177
|
-
<!-- LTM -->
|
|
178
117
|
<div>
|
|
179
|
-
<div class="
|
|
180
|
-
|
|
181
|
-
<span class="text-xs font-mono text-slate-300" id="cog-ltm">--</span>
|
|
182
|
-
</div>
|
|
183
|
-
<div class="h-1 bg-slate-800 rounded-full overflow-hidden">
|
|
184
|
-
<div id="cog-ltm-bar" class="h-full bg-pink-500/60 rounded-full transition-all duration-500" style="width:0%"></div>
|
|
185
|
-
</div>
|
|
118
|
+
<div class="text-sm font-mono font-medium text-slate-200" id="kg-edges">--</div>
|
|
119
|
+
<div class="text-xs text-slate-500 mt-0.5">edges</div>
|
|
186
120
|
</div>
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
<
|
|
190
|
-
<span class="text-xs font-mono text-slate-400" id="cog-strength">--</span>
|
|
191
|
-
</div>
|
|
192
|
-
<!-- KG divider -->
|
|
193
|
-
<div class="pt-1 border-t border-slate-800">
|
|
194
|
-
<div class="text-xs text-slate-400 uppercase tracking-wide mb-2">Knowledge Graph</div>
|
|
195
|
-
<div class="grid grid-cols-3 gap-2 text-center">
|
|
196
|
-
<div>
|
|
197
|
-
<div class="text-sm font-mono font-medium text-slate-200" id="kg-nodes">--</div>
|
|
198
|
-
<div class="text-xs text-slate-500 mt-0.5">nodes</div>
|
|
199
|
-
</div>
|
|
200
|
-
<div>
|
|
201
|
-
<div class="text-sm font-mono font-medium text-slate-200" id="kg-edges">--</div>
|
|
202
|
-
<div class="text-xs text-slate-500 mt-0.5">edges</div>
|
|
203
|
-
</div>
|
|
204
|
-
<div>
|
|
205
|
-
<div class="text-sm font-mono font-medium text-slate-200" id="kg-historical">--</div>
|
|
206
|
-
<div class="text-xs text-slate-500 mt-0.5">hist.</div>
|
|
207
|
-
</div>
|
|
208
|
-
</div>
|
|
121
|
+
<div>
|
|
122
|
+
<div class="text-sm font-mono font-medium text-slate-200" id="kg-historical">--</div>
|
|
123
|
+
<div class="text-xs text-slate-500 mt-0.5">hist.</div>
|
|
209
124
|
</div>
|
|
210
125
|
</div>
|
|
211
126
|
</div>
|
|
212
|
-
|
|
213
|
-
<!-- Recent Sessions -->
|
|
214
|
-
<div class="bg-slate-900/50 border border-slate-800/50 rounded-lg p-4">
|
|
215
|
-
<div class="flex items-center justify-between mb-3">
|
|
216
|
-
<div class="text-xs uppercase tracking-wider text-slate-500 font-medium">Recent Sessions</div>
|
|
217
|
-
<a href="/sessions" class="text-xs text-violet-400 hover:text-violet-300 transition-colors">view all →</a>
|
|
218
|
-
</div>
|
|
219
|
-
<div id="recent-sessions" class="space-y-3">
|
|
220
|
-
<div class="text-xs text-slate-600">loading...</div>
|
|
221
|
-
</div>
|
|
222
|
-
</div>
|
|
223
127
|
</div>
|
|
224
|
-
|
|
225
128
|
</div>
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
129
|
+
|
|
130
|
+
<!-- Recent Sessions -->
|
|
131
|
+
<div class="bg-slate-900/50 border border-slate-800/50 rounded-xl p-5 card">
|
|
132
|
+
<div class="flex items-center justify-between mb-3">
|
|
133
|
+
<div class="text-xs uppercase tracking-wider text-slate-400 font-medium">Recent Sessions</div>
|
|
134
|
+
<a href="/sessions" class="text-xs text-violet-400 hover:text-violet-300 transition-colors">view all →</a>
|
|
135
|
+
</div>
|
|
136
|
+
<div id="recent-sessions" class="space-y-3">
|
|
137
|
+
<div class="text-xs text-slate-600">loading...</div>
|
|
234
138
|
</div>
|
|
235
|
-
<form id="modal-form" onsubmit="submitQuickCreate(event)" class="space-y-3">
|
|
236
|
-
<div>
|
|
237
|
-
<label class="block text-xs text-slate-400 mb-1">Description</label>
|
|
238
|
-
<textarea name="description" rows="3" required class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-sm text-slate-200 focus:outline-none focus:ring-1 focus:ring-violet-500 resize-none placeholder-slate-600"></textarea>
|
|
239
|
-
</div>
|
|
240
|
-
<div>
|
|
241
|
-
<label class="block text-xs text-slate-400 mb-1">Date <span class="text-slate-600">(optional)</span></label>
|
|
242
|
-
<input type="date" name="date" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-sm text-slate-200 focus:outline-none focus:ring-1 focus:ring-violet-500">
|
|
243
|
-
</div>
|
|
244
|
-
<div id="category-group" class="hidden">
|
|
245
|
-
<label class="block text-xs text-slate-400 mb-1">Category</label>
|
|
246
|
-
<select name="category" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-sm text-slate-200 focus:outline-none focus:ring-1 focus:ring-violet-500">
|
|
247
|
-
<option value="general">General</option>
|
|
248
|
-
<option value="ideas">Ideas</option>
|
|
249
|
-
<option value="my-project">My Project</option>
|
|
250
|
-
<option value="project-a">Project A</option>
|
|
251
|
-
<option value="server">Server</option>
|
|
252
|
-
</select>
|
|
253
|
-
</div>
|
|
254
|
-
<div id="note-direction-group" class="hidden">
|
|
255
|
-
<label class="block text-xs text-slate-400 mb-1">Direction</label>
|
|
256
|
-
<select name="direction" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-sm text-slate-200 focus:outline-none focus:ring-1 focus:ring-violet-500">
|
|
257
|
-
<option value="to_nexo">To NEXO</option>
|
|
258
|
-
<option value="to_user">To User</option>
|
|
259
|
-
</select>
|
|
260
|
-
</div>
|
|
261
|
-
<input type="hidden" name="type" id="modal-type">
|
|
262
|
-
<div class="flex justify-end gap-2 pt-2">
|
|
263
|
-
<button type="button" onclick="closeModal()" class="px-3 py-1.5 text-xs rounded-lg text-slate-400 hover:text-slate-200 transition-colors">Cancel</button>
|
|
264
|
-
<button type="submit" class="px-3 py-1.5 text-xs rounded-lg bg-violet-600 text-white hover:bg-violet-500 transition-colors font-medium">Create</button>
|
|
265
|
-
</div>
|
|
266
|
-
</form>
|
|
267
139
|
</div>
|
|
268
140
|
</div>
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
<!-- Quick Create Modal -->
|
|
144
|
+
<div id="modal-overlay" class="hidden fixed inset-0 bg-black/60 backdrop-blur-sm z-[60] flex items-center justify-center" onclick="if(event.target===this)closeModal()">
|
|
145
|
+
<div class="bg-slate-900 border border-slate-700 rounded-xl p-6 w-full max-w-md shadow-2xl animate-slide-in">
|
|
146
|
+
<div class="flex items-center justify-between mb-4">
|
|
147
|
+
<h2 id="modal-title" class="text-sm font-display font-semibold text-slate-100">New Item</h2>
|
|
148
|
+
<button onclick="closeModal()" class="text-slate-400 hover:text-white text-lg leading-none">×</button>
|
|
149
|
+
</div>
|
|
150
|
+
<form id="modal-form" onsubmit="submitQuickCreate(event)" class="space-y-3">
|
|
151
|
+
<div>
|
|
152
|
+
<label class="block text-xs text-slate-400 mb-1">Description</label>
|
|
153
|
+
<textarea name="description" rows="3" required class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-sm text-slate-200 focus:outline-none focus:ring-1 focus:ring-violet-500 resize-none placeholder-slate-600"></textarea>
|
|
154
|
+
</div>
|
|
155
|
+
<div>
|
|
156
|
+
<label class="block text-xs text-slate-400 mb-1">Date <span class="text-slate-600">(optional)</span></label>
|
|
157
|
+
<input type="date" name="date" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-sm text-slate-200 focus:outline-none focus:ring-1 focus:ring-violet-500">
|
|
158
|
+
</div>
|
|
159
|
+
<div id="category-group" class="hidden">
|
|
160
|
+
<label class="block text-xs text-slate-400 mb-1">Category</label>
|
|
161
|
+
<select name="category" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-sm text-slate-200 focus:outline-none focus:ring-1 focus:ring-violet-500">
|
|
162
|
+
<option value="general">General</option>
|
|
163
|
+
<option value="ideas">Ideas</option>
|
|
164
|
+
<option value="my-project">My Project</option>
|
|
165
|
+
<option value="project-a">Project A</option>
|
|
166
|
+
<option value="server">Server</option>
|
|
167
|
+
</select>
|
|
168
|
+
</div>
|
|
169
|
+
<div id="note-direction-group" class="hidden">
|
|
170
|
+
<label class="block text-xs text-slate-400 mb-1">Direction</label>
|
|
171
|
+
<select name="direction" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-sm text-slate-200 focus:outline-none focus:ring-1 focus:ring-violet-500">
|
|
172
|
+
<option value="to_nexo">To NEXO</option>
|
|
173
|
+
<option value="to_user">To User</option>
|
|
174
|
+
</select>
|
|
175
|
+
</div>
|
|
176
|
+
<input type="hidden" name="type" id="modal-type">
|
|
177
|
+
<div class="flex justify-end gap-2 pt-2">
|
|
178
|
+
<button type="button" onclick="closeModal()" class="px-3 py-1.5 text-xs rounded-lg text-slate-400 hover:text-slate-200 transition-colors">Cancel</button>
|
|
179
|
+
<button type="submit" class="px-3 py-1.5 text-xs rounded-lg bg-violet-600 text-white hover:bg-violet-500 transition-colors font-medium">Create</button>
|
|
180
|
+
</div>
|
|
181
|
+
</form>
|
|
273
182
|
</div>
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
183
|
+
</div>
|
|
184
|
+
{% endblock %}
|
|
185
|
+
|
|
186
|
+
{% block scripts %}
|
|
187
|
+
<script>
|
|
188
|
+
function getToday() { return new Date().toISOString().split('T')[0]; }
|
|
189
|
+
|
|
190
|
+
// -----------------------------------------------------------------------
|
|
191
|
+
// Modal
|
|
192
|
+
// -----------------------------------------------------------------------
|
|
193
|
+
function openQuickCreate(type) {
|
|
194
|
+
const overlay = document.getElementById('modal-overlay');
|
|
195
|
+
const title = document.getElementById('modal-title');
|
|
196
|
+
const typeInput = document.getElementById('modal-type');
|
|
197
|
+
document.getElementById('modal-form').reset();
|
|
198
|
+
|
|
199
|
+
typeInput.value = type;
|
|
200
|
+
document.getElementById('category-group').classList.toggle('hidden', type !== 'reminder');
|
|
201
|
+
document.getElementById('note-direction-group').classList.toggle('hidden', type !== 'note');
|
|
202
|
+
|
|
203
|
+
if (type === 'reminder') title.textContent = 'New Reminder';
|
|
204
|
+
else if (type === 'followup') title.textContent = 'New Followup';
|
|
205
|
+
else title.textContent = 'New Note';
|
|
206
|
+
|
|
207
|
+
overlay.classList.remove('hidden');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function closeModal() {
|
|
211
|
+
document.getElementById('modal-overlay').classList.add('hidden');
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
document.addEventListener('keydown', e => { if (e.key === 'Escape') closeModal(); });
|
|
215
|
+
|
|
216
|
+
async function submitQuickCreate(e) {
|
|
217
|
+
e.preventDefault();
|
|
218
|
+
const fd = new FormData(document.getElementById('modal-form'));
|
|
219
|
+
const type = fd.get('type');
|
|
220
|
+
let url, body;
|
|
221
|
+
|
|
222
|
+
if (type === 'reminder') {
|
|
223
|
+
url = '/api/reminders';
|
|
224
|
+
body = { description: fd.get('description'), date: fd.get('date') || null, category: fd.get('category') || 'general' };
|
|
225
|
+
} else if (type === 'followup') {
|
|
226
|
+
url = '/api/followups';
|
|
227
|
+
body = { description: fd.get('description'), date: fd.get('date') || null };
|
|
228
|
+
} else {
|
|
229
|
+
url = '/api/inbox';
|
|
230
|
+
body = { direction: fd.get('direction') || 'to_nexo', content: fd.get('description') };
|
|
283
231
|
}
|
|
284
232
|
|
|
285
|
-
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
233
|
+
try {
|
|
234
|
+
const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
|
|
235
|
+
const data = await res.json();
|
|
236
|
+
if (data.success) { showToast('Created successfully'); closeModal(); loadDashboardData(); }
|
|
237
|
+
else showToast(data.error || data.detail || 'Create failed');
|
|
238
|
+
} catch (err) { showToast('Error: ' + err.message); }
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// -----------------------------------------------------------------------
|
|
242
|
+
// Dashboard data
|
|
243
|
+
// -----------------------------------------------------------------------
|
|
244
|
+
async function loadDashboardData() {
|
|
245
|
+
const today = getToday();
|
|
246
|
+
|
|
247
|
+
const [trustData, statsData, remindersData, followupsData, sessionsData, watchdogData, inboxData] =
|
|
248
|
+
await Promise.all([
|
|
249
|
+
fetchJSON('/api/trust'),
|
|
250
|
+
fetchJSON('/api/stats'),
|
|
251
|
+
fetchJSON('/api/reminders'),
|
|
252
|
+
fetchJSON('/api/followups'),
|
|
253
|
+
fetchJSON('/api/sessions?limit=3'),
|
|
254
|
+
fetchJSON('/api/watchdog'),
|
|
255
|
+
fetchJSON('/api/inbox/unread'),
|
|
256
|
+
]);
|
|
257
|
+
|
|
258
|
+
// --- Trust Score (animated gauge) ---
|
|
259
|
+
if (trustData) {
|
|
260
|
+
const score = trustData.current_score ?? 0;
|
|
261
|
+
const pct = Math.max(0, Math.min(100, score));
|
|
262
|
+
const circumference = 2 * Math.PI * 32;
|
|
263
|
+
const arc = (pct / 100) * circumference;
|
|
264
|
+
|
|
265
|
+
const trustArc = document.getElementById('trust-arc');
|
|
266
|
+
const trustCenter = document.getElementById('trust-center');
|
|
267
|
+
trustArc.setAttribute('stroke-dasharray', `${arc} ${circumference - arc}`);
|
|
268
|
+
trustCenter.textContent = Math.round(score);
|
|
269
|
+
|
|
270
|
+
// Color by score
|
|
271
|
+
const color = pct >= 75 ? '#7C3AED' : pct >= 50 ? '#F59E0B' : '#EF4444';
|
|
272
|
+
trustArc.setAttribute('stroke', color);
|
|
273
|
+
trustCenter.setAttribute('fill', color);
|
|
274
|
+
|
|
275
|
+
document.getElementById('trust-bar').style.width = pct + '%';
|
|
276
|
+
document.getElementById('sidebar-trust-value').textContent = score.toFixed(1);
|
|
277
|
+
|
|
278
|
+
const label = pct >= 75 ? 'high alignment' : pct >= 50 ? 'moderate' : pct >= 25 ? 'low -- more caution' : 'critical';
|
|
279
|
+
document.getElementById('trust-label').textContent = label;
|
|
295
280
|
}
|
|
296
281
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
const
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
if (
|
|
307
|
-
|
|
282
|
+
// --- Active Sessions ---
|
|
283
|
+
if (sessionsData && sessionsData.sessions) {
|
|
284
|
+
const cutoff = Date.now() - 15 * 60 * 1000;
|
|
285
|
+
const active = sessionsData.sessions.filter(s => {
|
|
286
|
+
const ts = new Date(s.last_heartbeat || s.created_at || 0).getTime();
|
|
287
|
+
return ts > cutoff;
|
|
288
|
+
});
|
|
289
|
+
document.getElementById('session-count').textContent = active.length;
|
|
290
|
+
document.getElementById('session-label').textContent = active.length === 1 ? 'active terminal' : 'active terminals';
|
|
291
|
+
if (active.length > 0) {
|
|
292
|
+
const names = active.slice(0, 2).map(s => s.session_id ? s.session_id.substring(0, 8) : '??').join(', ');
|
|
293
|
+
document.getElementById('session-detail').textContent = names + (active.length > 2 ? ' +' + (active.length - 2) : '');
|
|
294
|
+
} else {
|
|
295
|
+
document.getElementById('session-detail').textContent = 'none active';
|
|
296
|
+
document.getElementById('session-pulse').classList.remove('animate-ping');
|
|
297
|
+
}
|
|
308
298
|
}
|
|
309
299
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
300
|
+
// --- Overdue Items ---
|
|
301
|
+
if (remindersData || followupsData) {
|
|
302
|
+
const excludeStatus = ['completed', 'COMPLETED', 'archived', 'deleted', 'DELETED', 'blocked', 'waiting'];
|
|
303
|
+
const reminders = (remindersData?.reminders || []).filter(r =>
|
|
304
|
+
!excludeStatus.includes(r.status) && r.date && r.date <= today);
|
|
305
|
+
const followups = (followupsData?.followups || []).filter(f =>
|
|
306
|
+
!excludeStatus.includes(f.status) && f.date && f.date <= today);
|
|
307
|
+
const total = reminders.length + followups.length;
|
|
308
|
+
const el = document.getElementById('overdue-count');
|
|
309
|
+
el.textContent = total;
|
|
310
|
+
el.className = 'text-2xl font-mono font-semibold ' + (total > 0 ? 'text-red-400' : 'text-slate-200');
|
|
311
|
+
document.getElementById('overdue-detail').textContent = reminders.length + ' reminders, ' + followups.length + ' followups';
|
|
316
312
|
}
|
|
317
313
|
|
|
318
|
-
|
|
319
|
-
|
|
314
|
+
// --- Watchdog ---
|
|
315
|
+
const watchdogBadge = document.getElementById('watchdog-badge');
|
|
316
|
+
const watchdogServices = document.getElementById('watchdog-services');
|
|
317
|
+
if (watchdogData && !watchdogData.error) {
|
|
318
|
+
const overall = watchdogData.summary?.overall || 'UNKNOWN';
|
|
319
|
+
const isPass = overall === 'PASS' || overall === 'ok';
|
|
320
|
+
watchdogBadge.textContent = overall;
|
|
321
|
+
watchdogBadge.className = 'inline-flex items-center px-2 py-0.5 rounded text-xs font-mono font-medium ' +
|
|
322
|
+
(isPass ? 'bg-emerald-500/10 text-emerald-400' : 'bg-red-500/10 text-red-400');
|
|
323
|
+
|
|
324
|
+
const services = watchdogData.services || watchdogData.checks || [];
|
|
325
|
+
if (Array.isArray(services) && services.length > 0) {
|
|
326
|
+
watchdogServices.innerHTML = services.map(svc => {
|
|
327
|
+
const ok = svc.status === 'ok' || svc.status === 'PASS' || svc.pass === true;
|
|
328
|
+
const dot = ok ? 'bg-emerald-500' : 'bg-red-500';
|
|
329
|
+
const name = svc.name || svc.service || 'unknown';
|
|
330
|
+
return `<div class="flex items-center gap-1.5"><span class="w-1.5 h-1.5 rounded-full ${dot} flex-shrink-0"></span><span class="text-xs text-slate-400 truncate">${escapeHtml(name)}</span></div>`;
|
|
331
|
+
}).join('');
|
|
332
|
+
} else {
|
|
333
|
+
const entries = Object.entries(watchdogData).filter(([k]) => k !== 'summary' && k !== 'timestamp');
|
|
334
|
+
if (entries.length > 0) {
|
|
335
|
+
watchdogServices.innerHTML = entries.map(([key, val]) => {
|
|
336
|
+
if (typeof val !== 'object' || val === null) return '';
|
|
337
|
+
const ok = val.status === 'PASS' || val.status === 'ok' || val.pass === true;
|
|
338
|
+
const dot = ok ? 'bg-emerald-500' : 'bg-red-500';
|
|
339
|
+
return `<div class="flex items-center gap-1.5"><span class="w-1.5 h-1.5 rounded-full ${dot} flex-shrink-0"></span><span class="text-xs text-slate-400 truncate">${escapeHtml(key)}</span></div>`;
|
|
340
|
+
}).filter(Boolean).join('');
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
} else {
|
|
344
|
+
watchdogBadge.textContent = 'N/A';
|
|
345
|
+
watchdogBadge.className = 'inline-flex items-center px-2 py-0.5 rounded text-xs font-mono font-medium bg-slate-700 text-slate-400';
|
|
346
|
+
watchdogServices.innerHTML = '<div class="text-xs text-slate-600">no watchdog data</div>';
|
|
320
347
|
}
|
|
321
348
|
|
|
322
|
-
//
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
|
|
349
|
+
// --- Agenda ---
|
|
350
|
+
const agendaList = document.getElementById('agenda-list');
|
|
351
|
+
const agendaItems = [];
|
|
352
|
+
if (remindersData?.reminders) {
|
|
353
|
+
const excludeStatus = ['completed', 'COMPLETED', 'archived', 'deleted', 'DELETED'];
|
|
354
|
+
remindersData.reminders.filter(r => !excludeStatus.includes(r.status) && r.date && r.date <= today)
|
|
355
|
+
.forEach(r => agendaItems.push({ text: r.description, type: 'reminder', date: r.date }));
|
|
329
356
|
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
// Modal
|
|
335
|
-
// -----------------------------------------------------------------------
|
|
336
|
-
function openQuickCreate(type) {
|
|
337
|
-
const overlay = document.getElementById('modal-overlay');
|
|
338
|
-
const title = document.getElementById('modal-title');
|
|
339
|
-
const typeInput = document.getElementById('modal-type');
|
|
340
|
-
const categoryGroup = document.getElementById('category-group');
|
|
341
|
-
const noteDirectionGroup = document.getElementById('note-direction-group');
|
|
342
|
-
document.getElementById('modal-form').reset();
|
|
343
|
-
|
|
344
|
-
typeInput.value = type;
|
|
345
|
-
categoryGroup.classList.toggle('hidden', type !== 'reminder');
|
|
346
|
-
noteDirectionGroup.classList.toggle('hidden', type !== 'note');
|
|
347
|
-
|
|
348
|
-
if (type === 'reminder') title.textContent = 'New Reminder';
|
|
349
|
-
else if (type === 'followup') title.textContent = 'New Followup';
|
|
350
|
-
else title.textContent = 'New Note';
|
|
351
|
-
|
|
352
|
-
overlay.classList.remove('hidden');
|
|
357
|
+
if (followupsData?.followups) {
|
|
358
|
+
const excludeStatus = ['completed', 'COMPLETED', 'archived', 'deleted', 'DELETED'];
|
|
359
|
+
followupsData.followups.filter(f => !excludeStatus.includes(f.status) && f.date && f.date <= today)
|
|
360
|
+
.forEach(f => agendaItems.push({ text: f.description, type: 'followup', date: f.date }));
|
|
353
361
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
362
|
+
if (agendaItems.length > 0) {
|
|
363
|
+
agendaList.innerHTML = agendaItems.slice(0, 8).map(item => {
|
|
364
|
+
const color = item.type === 'reminder' ? 'text-amber-400' : 'text-violet-400';
|
|
365
|
+
const badge = item.type === 'reminder' ? 'bg-amber-500/10 text-amber-400' : 'bg-violet-500/10 text-violet-400';
|
|
366
|
+
return `<li class="flex items-start gap-2 py-1">
|
|
367
|
+
<span class="px-1.5 py-0.5 rounded text-[9px] font-medium ${badge} mt-0.5">${item.type}</span>
|
|
368
|
+
<span class="text-xs text-slate-300 leading-relaxed">${escapeHtml(item.text || '--')}</span>
|
|
369
|
+
</li>`;
|
|
370
|
+
}).join('');
|
|
371
|
+
} else {
|
|
372
|
+
agendaList.innerHTML = '<li class="text-xs text-slate-600 py-1">No items due today</li>';
|
|
357
373
|
}
|
|
358
374
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
const
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
const res = await fetch(url, {
|
|
380
|
-
method: 'POST',
|
|
381
|
-
headers: { 'Content-Type': 'application/json' },
|
|
382
|
-
body: JSON.stringify(body)
|
|
383
|
-
});
|
|
384
|
-
const data = await res.json();
|
|
385
|
-
if (data.success) {
|
|
386
|
-
showToast('Created successfully');
|
|
387
|
-
closeModal();
|
|
388
|
-
loadDashboardData();
|
|
389
|
-
} else {
|
|
390
|
-
showToast(data.error || 'Failed to create');
|
|
391
|
-
}
|
|
392
|
-
} catch (err) {
|
|
393
|
-
showToast('Error: ' + err.message);
|
|
375
|
+
// --- Cognitive Memory ---
|
|
376
|
+
if (statsData) {
|
|
377
|
+
const cog = statsData.cognitive || statsData;
|
|
378
|
+
const stm = cog.stm_total ?? cog.stm_active ?? cog.stm_count ?? cog.stm ?? 0;
|
|
379
|
+
const ltm = cog.ltm_active ?? cog.ltm_count ?? cog.ltm ?? 0;
|
|
380
|
+
const strength = cog.avg_stm_strength ?? cog.avg_strength ?? 0;
|
|
381
|
+
const stmCap = cog.stm_capacity || 500;
|
|
382
|
+
|
|
383
|
+
document.getElementById('cog-stm').textContent = stm;
|
|
384
|
+
document.getElementById('cog-ltm').textContent = ltm;
|
|
385
|
+
document.getElementById('cog-strength').textContent = typeof strength === 'number' ? strength.toFixed(2) : strength;
|
|
386
|
+
document.getElementById('cog-stm-bar').style.width = Math.min(100, (stm / stmCap) * 100) + '%';
|
|
387
|
+
document.getElementById('cog-ltm-bar').style.width = Math.min(100, ltm > 0 ? 60 : 0) + '%';
|
|
388
|
+
|
|
389
|
+
// KG stats
|
|
390
|
+
const kg = statsData.knowledge_graph || statsData.kg || {};
|
|
391
|
+
if (kg.nodes !== undefined || kg.entities !== undefined || kg.total_nodes !== undefined) {
|
|
392
|
+
document.getElementById('kg-nodes').textContent = formatNumber(kg.nodes ?? kg.total_nodes ?? kg.entities ?? 0);
|
|
393
|
+
document.getElementById('kg-edges').textContent = formatNumber(kg.edges ?? kg.edges_active ?? kg.relations ?? 0);
|
|
394
|
+
document.getElementById('kg-historical').textContent = formatNumber(kg.historical ?? kg.edges_historical ?? 0);
|
|
394
395
|
}
|
|
395
396
|
}
|
|
396
397
|
|
|
397
|
-
//
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
// --- Trust Score ---
|
|
415
|
-
if (trustData) {
|
|
416
|
-
const score = trustData.current_score ?? 0;
|
|
417
|
-
const pct = Math.max(0, Math.min(100, score));
|
|
418
|
-
document.getElementById('trust-value').textContent = score.toFixed(1);
|
|
419
|
-
document.getElementById('trust-bar').style.width = pct + '%';
|
|
420
|
-
document.getElementById('sidebar-trust-value').textContent = score.toFixed(1);
|
|
421
|
-
const label = pct >= 75 ? 'high alignment' : pct >= 50 ? 'moderate' : pct >= 25 ? 'low — more caution' : 'critical';
|
|
422
|
-
document.getElementById('trust-label').textContent = label;
|
|
423
|
-
// Color trust value by score
|
|
424
|
-
const trustEl = document.getElementById('trust-value');
|
|
425
|
-
trustEl.className = 'text-2xl font-mono font-semibold ' +
|
|
426
|
-
(pct >= 75 ? 'text-violet-400' : pct >= 50 ? 'text-amber-400' : 'text-red-400');
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
// --- Active Sessions ---
|
|
430
|
-
if (sessionsData && sessionsData.sessions) {
|
|
431
|
-
const cutoff = Date.now() - 15 * 60 * 1000;
|
|
432
|
-
const active = sessionsData.sessions.filter(s => {
|
|
433
|
-
const ts = new Date(s.last_heartbeat || s.created_at || 0).getTime();
|
|
434
|
-
return ts > cutoff;
|
|
435
|
-
});
|
|
436
|
-
document.getElementById('session-count').textContent = active.length;
|
|
437
|
-
document.getElementById('session-label').textContent = active.length === 1 ? 'active terminal' : 'active terminals';
|
|
438
|
-
if (active.length > 0) {
|
|
439
|
-
const names = active.slice(0, 2).map(s => s.session_id ? s.session_id.substring(0, 8) : '??').join(', ');
|
|
440
|
-
document.getElementById('session-detail').textContent = names + (active.length > 2 ? ' +' + (active.length - 2) : '');
|
|
441
|
-
} else {
|
|
442
|
-
document.getElementById('session-detail').textContent = 'none active';
|
|
443
|
-
document.getElementById('session-pulse').classList.remove('animate-ping');
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
// --- Overdue Items ---
|
|
448
|
-
if (remindersData || followupsData) {
|
|
449
|
-
const excludeStatus = ['completed', 'COMPLETED', 'archived', 'deleted', 'DELETED', 'blocked', 'waiting'];
|
|
450
|
-
const reminders = (remindersData?.reminders || []).filter(r =>
|
|
451
|
-
!excludeStatus.includes(r.status) && r.date && r.date <= today
|
|
452
|
-
);
|
|
453
|
-
const followups = (followupsData?.followups || []).filter(f =>
|
|
454
|
-
!excludeStatus.includes(f.status) && f.date && f.date <= today
|
|
455
|
-
);
|
|
456
|
-
const total = reminders.length + followups.length;
|
|
457
|
-
const el = document.getElementById('overdue-count');
|
|
458
|
-
el.textContent = total;
|
|
459
|
-
el.className = 'text-2xl font-mono font-semibold ' + (total > 0 ? 'text-red-400' : 'text-slate-200');
|
|
460
|
-
document.getElementById('overdue-detail').textContent =
|
|
461
|
-
reminders.length + ' reminders, ' + followups.length + ' followups';
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
// --- Watchdog ---
|
|
465
|
-
const watchdogBadge = document.getElementById('watchdog-badge');
|
|
466
|
-
const watchdogServices = document.getElementById('watchdog-services');
|
|
467
|
-
if (watchdogData && !watchdogData.error) {
|
|
468
|
-
const overall = watchdogData.summary?.overall || 'UNKNOWN';
|
|
469
|
-
const isPass = overall === 'PASS' || overall === 'ok';
|
|
470
|
-
watchdogBadge.textContent = overall;
|
|
471
|
-
watchdogBadge.className = 'inline-flex items-center px-2 py-0.5 rounded text-xs font-mono font-medium ' +
|
|
472
|
-
(isPass ? 'bg-emerald-500/10 text-emerald-400' : 'bg-red-500/10 text-red-400');
|
|
473
|
-
|
|
474
|
-
const services = watchdogData.services || watchdogData.checks || [];
|
|
475
|
-
if (Array.isArray(services) && services.length > 0) {
|
|
476
|
-
watchdogServices.innerHTML = services.map(svc => {
|
|
477
|
-
const ok = svc.status === 'ok' || svc.status === 'PASS' || svc.pass === true;
|
|
478
|
-
const dot = ok ? 'bg-emerald-500' : 'bg-red-500';
|
|
479
|
-
const name = svc.name || svc.service || 'unknown';
|
|
480
|
-
return `<div class="flex items-center gap-1.5">
|
|
481
|
-
<span class="w-1.5 h-1.5 rounded-full ${dot} flex-shrink-0"></span>
|
|
482
|
-
<span class="text-xs text-slate-400 truncate">${escapeHtml(name)}</span>
|
|
483
|
-
</div>`;
|
|
484
|
-
}).join('');
|
|
485
|
-
} else {
|
|
486
|
-
// Try to parse from object keys
|
|
487
|
-
const entries = Object.entries(watchdogData).filter(([k]) => k !== 'summary' && k !== 'timestamp');
|
|
488
|
-
if (entries.length > 0) {
|
|
489
|
-
watchdogServices.innerHTML = entries.map(([key, val]) => {
|
|
490
|
-
if (typeof val !== 'object' || val === null) return '';
|
|
491
|
-
const ok = val.status === 'PASS' || val.status === 'ok' || val.pass === true;
|
|
492
|
-
const dot = ok ? 'bg-emerald-500' : 'bg-red-500';
|
|
493
|
-
return `<div class="flex items-center gap-1.5">
|
|
494
|
-
<span class="w-1.5 h-1.5 rounded-full ${dot} flex-shrink-0"></span>
|
|
495
|
-
<span class="text-xs text-slate-400 truncate">${escapeHtml(key)}</span>
|
|
496
|
-
</div>`;
|
|
497
|
-
}).filter(Boolean).join('');
|
|
498
|
-
} else {
|
|
499
|
-
watchdogServices.innerHTML = '<div class="text-xs text-slate-500">no services</div>';
|
|
500
|
-
}
|
|
501
|
-
}
|
|
398
|
+
// --- Recent Sessions ---
|
|
399
|
+
if (sessionsData) {
|
|
400
|
+
const diaries = sessionsData.diaries || sessionsData.sessions || [];
|
|
401
|
+
const container = document.getElementById('recent-sessions');
|
|
402
|
+
if (diaries.length > 0) {
|
|
403
|
+
container.innerHTML = diaries.slice(0, 3).map(d => {
|
|
404
|
+
return `<div class="border-b border-slate-800/30 pb-2.5 last:border-0 last:pb-0">
|
|
405
|
+
<div class="flex items-center gap-2 mb-1">
|
|
406
|
+
<span class="text-[10px] font-mono text-violet-400">${escapeHtml(String(d.session_id || d.id || '').substring(0, 8))}</span>
|
|
407
|
+
<span class="text-[10px] text-slate-600">${relativeTime(d.created_at)}</span>
|
|
408
|
+
${d.domain ? `<span class="text-[10px] px-1 py-0.5 rounded bg-slate-800 text-slate-400">${escapeHtml(d.domain)}</span>` : ''}
|
|
409
|
+
</div>
|
|
410
|
+
${d.summary ? `<p class="text-xs text-slate-400 leading-relaxed line-clamp-2">${escapeHtml(d.summary)}</p>` : ''}
|
|
411
|
+
${d.mental_state ? `<div class="text-[10px] text-violet-400/60 mt-1 italic">${escapeHtml(d.mental_state)}</div>` : ''}
|
|
412
|
+
</div>`;
|
|
413
|
+
}).join('');
|
|
502
414
|
} else {
|
|
503
|
-
|
|
504
|
-
watchdogBadge.className = 'inline-flex items-center px-2 py-0.5 rounded text-xs font-mono font-medium bg-slate-700 text-slate-500';
|
|
505
|
-
watchdogServices.innerHTML = '<div class="text-xs text-slate-500">unavailable</div>';
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
// --- Cognitive Memory ---
|
|
509
|
-
if (statsData) {
|
|
510
|
-
const cog = statsData.cognitive || {};
|
|
511
|
-
const stm = cog.stm_count ?? cog.stm ?? 0;
|
|
512
|
-
const ltm = cog.ltm_count ?? cog.ltm ?? 0;
|
|
513
|
-
const maxMem = Math.max(stm + ltm, 100);
|
|
514
|
-
|
|
515
|
-
document.getElementById('cog-stm').textContent = stm;
|
|
516
|
-
document.getElementById('cog-ltm').textContent = ltm;
|
|
517
|
-
document.getElementById('cog-stm-bar').style.width = Math.min(100, (stm / maxMem) * 100) + '%';
|
|
518
|
-
document.getElementById('cog-ltm-bar').style.width = Math.min(100, (ltm / maxMem) * 100) + '%';
|
|
519
|
-
|
|
520
|
-
const strength = cog.avg_strength;
|
|
521
|
-
document.getElementById('cog-strength').textContent =
|
|
522
|
-
typeof strength === 'number' ? strength.toFixed(3) : (strength ?? '--');
|
|
523
|
-
|
|
524
|
-
const kg = statsData.knowledge_graph || {};
|
|
525
|
-
document.getElementById('kg-nodes').textContent = kg.nodes ?? '--';
|
|
526
|
-
document.getElementById('kg-edges').textContent = kg.edges_active ?? '--';
|
|
527
|
-
document.getElementById('kg-historical').textContent = kg.edges_historical ?? '--';
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
// --- Today's Agenda ---
|
|
531
|
-
if (remindersData || followupsData) {
|
|
532
|
-
const items = [];
|
|
533
|
-
(remindersData?.reminders || []).forEach(r => {
|
|
534
|
-
if (r.status === 'PENDING' && r.date && r.date <= today) {
|
|
535
|
-
items.push({ ...r, _type: 'R', _overdue: r.date < today });
|
|
536
|
-
}
|
|
537
|
-
});
|
|
538
|
-
(followupsData?.followups || []).forEach(f => {
|
|
539
|
-
if (f.status === 'PENDING' && f.date && f.date <= today) {
|
|
540
|
-
items.push({ ...f, _type: 'F', _overdue: f.date < today });
|
|
541
|
-
}
|
|
542
|
-
});
|
|
543
|
-
|
|
544
|
-
// Sort: overdue first, then by date
|
|
545
|
-
items.sort((a, b) => {
|
|
546
|
-
if (a._overdue && !b._overdue) return -1;
|
|
547
|
-
if (!a._overdue && b._overdue) return 1;
|
|
548
|
-
return (a.date || '').localeCompare(b.date || '');
|
|
549
|
-
});
|
|
550
|
-
|
|
551
|
-
const list = document.getElementById('agenda-list');
|
|
552
|
-
if (items.length === 0) {
|
|
553
|
-
list.innerHTML = '<li class="text-xs text-slate-600 py-1">No items due today</li>';
|
|
554
|
-
} else {
|
|
555
|
-
list.innerHTML = items.slice(0, 6).map(item => {
|
|
556
|
-
const badgeColor = item._type === 'R'
|
|
557
|
-
? (item._overdue ? 'bg-red-500/15 text-red-400' : 'bg-violet-500/15 text-violet-400')
|
|
558
|
-
: (item._overdue ? 'bg-orange-500/15 text-orange-400' : 'bg-pink-500/15 text-pink-400');
|
|
559
|
-
const desc = (item.description || '').length > 72
|
|
560
|
-
? item.description.substring(0, 72) + '…'
|
|
561
|
-
: item.description;
|
|
562
|
-
const dateLabel = item._overdue ? relativeDate(item.date) : 'today';
|
|
563
|
-
return `<li class="flex items-start gap-2 py-1 border-b border-slate-800/50 last:border-0">
|
|
564
|
-
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-mono font-medium flex-shrink-0 mt-0.5 ${badgeColor}">${item._type}</span>
|
|
565
|
-
<span class="text-xs text-slate-300 leading-relaxed flex-1 min-w-0">${escapeHtml(desc)}</span>
|
|
566
|
-
<span class="text-xs text-slate-500 flex-shrink-0 font-mono">${dateLabel}</span>
|
|
567
|
-
</li>`;
|
|
568
|
-
}).join('');
|
|
569
|
-
|
|
570
|
-
if (items.length > 6) {
|
|
571
|
-
list.innerHTML += `<li class="text-xs text-slate-500 pt-1">+${items.length - 6} more in <a href="/ops" class="text-violet-500 hover:text-violet-400">ops</a></li>`;
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
// --- Recent Sessions ---
|
|
577
|
-
if (sessionsData && sessionsData.sessions) {
|
|
578
|
-
const container = document.getElementById('recent-sessions');
|
|
579
|
-
const diaries = sessionsData.sessions.slice(0, 3);
|
|
580
|
-
if (diaries.length === 0) {
|
|
581
|
-
container.innerHTML = '<div class="text-xs text-slate-600">No session diaries yet</div>';
|
|
582
|
-
} else {
|
|
583
|
-
container.innerHTML = diaries.map(s => {
|
|
584
|
-
const summary = (s.summary || s.mental_state || 'No summary').substring(0, 160);
|
|
585
|
-
const sid = (s.session_id || s.id || '--').toString().substring(0, 10);
|
|
586
|
-
const dateStr = s.created_at
|
|
587
|
-
? new Date(s.created_at).toLocaleDateString('en-GB', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' })
|
|
588
|
-
: '';
|
|
589
|
-
return `<div class="pb-3 border-b border-slate-800/50 last:border-0 last:pb-0">
|
|
590
|
-
<div class="flex items-center justify-between mb-1">
|
|
591
|
-
<span class="text-xs font-mono text-slate-500">${escapeHtml(sid)}</span>
|
|
592
|
-
<span class="text-xs text-slate-500">${escapeHtml(dateStr)}</span>
|
|
593
|
-
</div>
|
|
594
|
-
<p class="text-xs text-slate-400 leading-relaxed">${escapeHtml(summary)}</p>
|
|
595
|
-
</div>`;
|
|
596
|
-
}).join('');
|
|
597
|
-
}
|
|
415
|
+
container.innerHTML = '<div class="text-xs text-slate-600">No recent sessions</div>';
|
|
598
416
|
}
|
|
417
|
+
}
|
|
599
418
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
} else {
|
|
609
|
-
badge.classList.add('hidden');
|
|
610
|
-
badge.classList.remove('flex');
|
|
611
|
-
}
|
|
419
|
+
// --- Inbox badge ---
|
|
420
|
+
if (inboxData) {
|
|
421
|
+
const count = inboxData.count ?? inboxData.unread ?? (Array.isArray(inboxData) ? inboxData.length : 0);
|
|
422
|
+
const badge = document.getElementById('inbox-badge');
|
|
423
|
+
if (badge && count > 0) {
|
|
424
|
+
badge.textContent = count;
|
|
425
|
+
badge.classList.remove('hidden');
|
|
426
|
+
badge.classList.add('flex');
|
|
612
427
|
}
|
|
613
428
|
}
|
|
429
|
+
}
|
|
614
430
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
setInterval(loadDashboardData, 60000);
|
|
620
|
-
</script>
|
|
621
|
-
</body>
|
|
622
|
-
</html>
|
|
431
|
+
loadDashboardData();
|
|
432
|
+
setInterval(loadDashboardData, 60000);
|
|
433
|
+
</script>
|
|
434
|
+
{% endblock %}
|