nexo-brain 5.3.26 → 5.3.27

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.
Files changed (211) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/package.json +1 -1
  3. package/src/server.py +3 -0
  4. package/src/tools_sessions.py +6 -1
  5. package/src/dashboard/static/favicon 2.svg +0 -32
  6. package/src/dashboard/static/nexo-logo 2.png +0 -0
  7. package/src/dashboard/static/nexo-logo 2.svg +0 -40
  8. package/src/dashboard/static/style 2.css +0 -2458
  9. package/src/dashboard/templates/adaptive 2.html +0 -118
  10. package/src/dashboard/templates/artifacts 2.html +0 -133
  11. package/src/dashboard/templates/backups 2.html +0 -136
  12. package/src/dashboard/templates/base 2.html +0 -417
  13. package/src/dashboard/templates/calendar 2.html +0 -591
  14. package/src/dashboard/templates/chat 2.html +0 -356
  15. package/src/dashboard/templates/claims 2.html +0 -259
  16. package/src/dashboard/templates/cortex 2.html +0 -321
  17. package/src/dashboard/templates/credentials 2.html +0 -128
  18. package/src/dashboard/templates/crons 2.html +0 -370
  19. package/src/dashboard/templates/dashboard 2.html +0 -494
  20. package/src/dashboard/templates/dreams 2.html +0 -252
  21. package/src/dashboard/templates/email 2.html +0 -160
  22. package/src/dashboard/templates/evolution 2.html +0 -189
  23. package/src/dashboard/templates/feed 2.html +0 -249
  24. package/src/dashboard/templates/followup_health 2.html +0 -170
  25. package/src/dashboard/templates/graph 2.html +0 -201
  26. package/src/dashboard/templates/guard 2.html +0 -259
  27. package/src/dashboard/templates/inbox 2.html +0 -251
  28. package/src/dashboard/templates/memory 2.html +0 -420
  29. package/src/dashboard/templates/operations 2.html +0 -608
  30. package/src/dashboard/templates/plugins 2.html +0 -185
  31. package/src/dashboard/templates/protocol 2.html +0 -199
  32. package/src/dashboard/templates/rules 2.html +0 -246
  33. package/src/dashboard/templates/sentiment 2.html +0 -247
  34. package/src/dashboard/templates/sessions 2.html +0 -218
  35. package/src/dashboard/templates/skills 2.html +0 -329
  36. package/src/dashboard/templates/somatic 2.html +0 -73
  37. package/src/dashboard/templates/triggers 2.html +0 -133
  38. package/src/dashboard/templates/trust 2.html +0 -360
  39. package/src/db/__init__ 2.py +0 -259
  40. package/src/db/_core 2.py +0 -437
  41. package/src/db/_credentials 2.py +0 -124
  42. package/src/db/_episodic 2.py +0 -762
  43. package/src/db/_evolution 2.py +0 -54
  44. package/src/db/_fts 2.py +0 -406
  45. package/src/db/_goal_profiles 2.py +0 -376
  46. package/src/db/_hot_context 2.py +0 -660
  47. package/src/db/_outcomes 2.py +0 -800
  48. package/src/db/_personal_scripts 2.py +0 -582
  49. package/src/db/_sessions 2.py +0 -330
  50. package/src/db/_tasks 2.py +0 -91
  51. package/src/db/_watchers 2.py +0 -173
  52. package/src/doctor/formatters 2.py +0 -52
  53. package/src/doctor/models 2.py +0 -69
  54. package/src/doctor/planes 2.py +0 -87
  55. package/src/doctor/providers/__init__ 2.py +0 -1
  56. package/src/doctor/providers/deep 2.py +0 -367
  57. package/src/evolution_cycle 2.py +0 -519
  58. package/src/hooks/auto_capture 2.py +0 -208
  59. package/src/hooks/caffeinate-guard 2.sh +0 -8
  60. package/src/hooks/capture-session 2.sh +0 -21
  61. package/src/hooks/capture-tool-logs 2.sh +0 -158
  62. package/src/hooks/daily-briefing-check 2.sh +0 -33
  63. package/src/hooks/heartbeat-enforcement 2.py +0 -90
  64. package/src/hooks/heartbeat-posttool 2.sh +0 -18
  65. package/src/hooks/inbox-hook 2.sh +0 -76
  66. package/src/hooks/post-compact 2.sh +0 -152
  67. package/src/hooks/pre-compact 2.sh +0 -169
  68. package/src/hooks/protocol-guardrail 2.sh +0 -10
  69. package/src/hooks/protocol-pretool-guardrail 2.sh +0 -9
  70. package/src/hooks/session-stop 2.sh +0 -52
  71. package/src/kg_populate 2.py +0 -292
  72. package/src/maintenance 2.py +0 -53
  73. package/src/memory_backends 2.py +0 -71
  74. package/src/migrate_embeddings 2.py +0 -124
  75. package/src/nexo_sdk 2.py +0 -103
  76. package/src/observability 2.py +0 -199
  77. package/src/plugin_loader 2.py +0 -217
  78. package/src/plugins/__init__ 2.py +0 -0
  79. package/src/plugins/artifact_registry 2.py +0 -450
  80. package/src/plugins/backup 2.py +0 -127
  81. package/src/plugins/claims_tools 2.py +0 -119
  82. package/src/plugins/cognitive_memory 2.py +0 -609
  83. package/src/plugins/core_rules 2.py +0 -252
  84. package/src/plugins/cortex 2.py +0 -1155
  85. package/src/plugins/entities 2.py +0 -67
  86. package/src/plugins/episodic_memory 2.py +0 -560
  87. package/src/plugins/evolution 2.py +0 -167
  88. package/src/plugins/goal_engine 2.py +0 -142
  89. package/src/plugins/guard 2.py +0 -862
  90. package/src/plugins/impact 2.py +0 -29
  91. package/src/plugins/knowledge_graph_tools 2.py +0 -137
  92. package/src/plugins/media_memory_tools 2.py +0 -98
  93. package/src/plugins/memory_export 2.py +0 -196
  94. package/src/plugins/outcomes 2.py +0 -130
  95. package/src/plugins/personal_scripts 2.py +0 -117
  96. package/src/plugins/preferences 2.py +0 -47
  97. package/src/plugins/protocol 2.py +0 -1449
  98. package/src/plugins/simple_api 2.py +0 -106
  99. package/src/plugins/skills 2.py +0 -341
  100. package/src/plugins/state_watchers 2.py +0 -79
  101. package/src/plugins/update 2.py +0 -986
  102. package/src/plugins/user_state_tools 2.py +0 -43
  103. package/src/plugins/workflow 2.py +0 -588
  104. package/src/protocol_settings 2.py +0 -59
  105. package/src/public_contribution 2.py +0 -466
  106. package/src/public_evolution_queue 2.py +0 -241
  107. package/src/requirements 2.txt +0 -14
  108. package/src/retroactive_learnings 2.py +0 -373
  109. package/src/rules/__init__ 2.py +0 -0
  110. package/src/rules/core-rules 2.json +0 -331
  111. package/src/rules/migrate 2.py +0 -207
  112. package/src/runtime_power 2.py +0 -874
  113. package/src/script_registry 2.py +0 -1559
  114. package/src/scripts/check-context 2.py +0 -272
  115. package/src/scripts/deep-sleep/apply_findings 2.py +0 -2327
  116. package/src/scripts/deep-sleep/collect 2.py +0 -928
  117. package/src/scripts/deep-sleep/extract 2.py +0 -330
  118. package/src/scripts/deep-sleep/extract-prompt 2.md +0 -285
  119. package/src/scripts/deep-sleep/synthesize 2.py +0 -312
  120. package/src/scripts/deep-sleep/synthesize-prompt 2.md +0 -336
  121. package/src/scripts/nexo-agent-run 2.py +0 -75
  122. package/src/scripts/nexo-auto-update 2.py +0 -6
  123. package/src/scripts/nexo-backup 2.sh +0 -25
  124. package/src/scripts/nexo-brain-activation 2.sh +0 -140
  125. package/src/scripts/nexo-catchup 2.py +0 -300
  126. package/src/scripts/nexo-cognitive-decay 2.py +0 -257
  127. package/src/scripts/nexo-cortex-cycle 2.py +0 -293
  128. package/src/scripts/nexo-cron-wrapper 2.sh +0 -53
  129. package/src/scripts/nexo-daily-self-audit 2.py +0 -2161
  130. package/src/scripts/nexo-dashboard 2.sh +0 -29
  131. package/src/scripts/nexo-deep-sleep 2.sh +0 -86
  132. package/src/scripts/nexo-evolution-run 2.py +0 -1664
  133. package/src/scripts/nexo-followup-hygiene 2.py +0 -139
  134. package/src/scripts/nexo-hook-record 2.py +0 -42
  135. package/src/scripts/nexo-immune 2.py +0 -936
  136. package/src/scripts/nexo-impact-scorer 2.py +0 -117
  137. package/src/scripts/nexo-inbox-hook 2.sh +0 -74
  138. package/src/scripts/nexo-install 2.py +0 -6
  139. package/src/scripts/nexo-learning-housekeep 2.py +0 -401
  140. package/src/scripts/nexo-learning-validator 2.py +0 -266
  141. package/src/scripts/nexo-migrate 2.py +0 -260
  142. package/src/scripts/nexo-outcome-checker 2.py +0 -127
  143. package/src/scripts/nexo-postmortem-consolidator 2.py +0 -456
  144. package/src/scripts/nexo-pre-commit 2.py +0 -120
  145. package/src/scripts/nexo-prevent-sleep 2.sh +0 -35
  146. package/src/scripts/nexo-proactive-dashboard 2.py +0 -354
  147. package/src/scripts/nexo-reflection 2.py +0 -256
  148. package/src/scripts/nexo-runtime-preflight 2.py +0 -274
  149. package/src/scripts/nexo-sleep 2.py +0 -631
  150. package/src/scripts/nexo-snapshot-restore 2.sh +0 -35
  151. package/src/scripts/nexo-sync-clients 2.py +0 -16
  152. package/src/scripts/nexo-synthesis 2.py +0 -475
  153. package/src/scripts/nexo-tcc-approve 2.sh +0 -79
  154. package/src/scripts/nexo-update 2.sh +0 -306
  155. package/src/scripts/nexo-watchdog 2.sh +0 -1207
  156. package/src/scripts/nexo-watchdog-smoke 2.py +0 -119
  157. package/src/scripts/rehydrate_learnings_from_archive 2.py +0 -245
  158. package/src/server 2.py +0 -1296
  159. package/src/skills/run-nexo-audit-phase/guide 2.md +0 -43
  160. package/src/skills/run-nexo-audit-phase/skill 2.json +0 -59
  161. package/src/skills/run-nexo-core-fix-cycle/guide 2.md +0 -17
  162. package/src/skills/run-nexo-core-fix-cycle/script 2.py +0 -276
  163. package/src/skills/run-nexo-core-fix-cycle/skill 2.json +0 -58
  164. package/src/skills/run-release-final-audit/guide 2.md +0 -16
  165. package/src/skills/run-release-final-audit/script 2.py +0 -259
  166. package/src/skills/run-release-final-audit/skill 2.json +0 -77
  167. package/src/skills/run-runtime-doctor/guide 2.md +0 -12
  168. package/src/skills/run-runtime-doctor/script 2.py +0 -21
  169. package/src/skills/run-runtime-doctor/skill 2.json +0 -25
  170. package/src/skills_runtime 2.py +0 -932
  171. package/src/state_watchers_runtime 2.py +0 -475
  172. package/src/storage_router 2.py +0 -32
  173. package/src/system_catalog 2.py +0 -786
  174. package/src/tools_coordination 2.py +0 -103
  175. package/src/tools_credentials 2.py +0 -68
  176. package/src/tools_drive 2.py +0 -487
  177. package/src/tools_hot_context 2.py +0 -163
  178. package/src/tools_learnings 2.py +0 -612
  179. package/src/tools_menu 2.py +0 -229
  180. package/src/tools_reminders 2.py +0 -88
  181. package/src/tools_reminders_crud 2.py +0 -363
  182. package/src/tools_sessions 2.py +0 -1054
  183. package/src/tools_system_catalog 2.py +0 -19
  184. package/src/tools_task_history 2.py +0 -57
  185. package/src/tools_transcripts 2.py +0 -98
  186. package/src/transcript_utils 2.py +0 -412
  187. package/src/user_context 2.py +0 -46
  188. package/src/user_data_portability 2.py +0 -328
  189. package/src/user_state_model 2.py +0 -170
  190. package/templates/CLAUDE.md 2.template +0 -108
  191. package/templates/CODEX.AGENTS.md 2.template +0 -66
  192. package/templates/launchagents/README 2.md +0 -132
  193. package/templates/launchagents/com.nexo.auto-close-sessions 2.plist +0 -39
  194. package/templates/launchagents/com.nexo.catchup 2.plist +0 -39
  195. package/templates/launchagents/com.nexo.cognitive-decay 2.plist +0 -40
  196. package/templates/launchagents/com.nexo.dashboard 2.plist +0 -43
  197. package/templates/launchagents/com.nexo.deep-sleep 2.plist +0 -43
  198. package/templates/launchagents/com.nexo.evolution 2.plist +0 -44
  199. package/templates/launchagents/com.nexo.followup-hygiene 2.plist +0 -45
  200. package/templates/launchagents/com.nexo.immune 2.plist +0 -41
  201. package/templates/launchagents/com.nexo.postmortem 2.plist +0 -45
  202. package/templates/launchagents/com.nexo.self-audit 2.plist +0 -47
  203. package/templates/launchagents/com.nexo.synthesis 2.plist +0 -45
  204. package/templates/launchagents/com.nexo.watchdog 2.plist +0 -37
  205. package/templates/nexo_helper 2.py +0 -301
  206. package/templates/openclaw 2.json +0 -13
  207. package/templates/plugin-template 2.py +0 -40
  208. package/templates/script-template 2.py +0 -59
  209. package/templates/script-template 2.sh +0 -13
  210. package/templates/skill-script-template 2.py +0 -48
  211. package/templates/skill-template 2.md +0 -33
@@ -1,356 +0,0 @@
1
- {% extends "base.html" %}
2
-
3
- {% block title %}NEXO Chat{% endblock %}
4
- {% block page_title %}NEXO Chat{% endblock %}
5
- {% block page_subtitle %}
6
- <span class="text-[10px] px-1.5 py-0.5 bg-nexo-500/20 text-nexo-400 rounded font-bold uppercase ml-2">Beta</span>
7
- {% endblock %}
8
-
9
- {% block content %}
10
- <div class="flex flex-col" style="height: calc(100vh - 7.5rem);">
11
-
12
- <!-- Chat messages area -->
13
- <div id="chat-messages" class="flex-1 overflow-y-auto px-2 py-4 space-y-4 scroll-smooth">
14
- <!-- Welcome message rendered by JS -->
15
- </div>
16
-
17
- <!-- Suggested prompts -->
18
- <div id="suggestions" class="flex items-center gap-2 px-2 py-3 overflow-x-auto flex-shrink-0">
19
- <button onclick="sendSuggestion(this)" class="suggestion-pill whitespace-nowrap px-3 py-1.5 rounded-lg text-xs transition-all">
20
- What happened last night?
21
- </button>
22
- <button onclick="sendSuggestion(this)" class="suggestion-pill whitespace-nowrap px-3 py-1.5 rounded-lg text-xs transition-all">
23
- Trust status
24
- </button>
25
- <button onclick="sendSuggestion(this)" class="suggestion-pill whitespace-nowrap px-3 py-1.5 rounded-lg text-xs transition-all">
26
- Active skills
27
- </button>
28
- <button onclick="sendSuggestion(this)" class="suggestion-pill whitespace-nowrap px-3 py-1.5 rounded-lg text-xs transition-all">
29
- Recent crons
30
- </button>
31
- <button onclick="sendSuggestion(this)" class="suggestion-pill whitespace-nowrap px-3 py-1.5 rounded-lg text-xs transition-all">
32
- Pending followups
33
- </button>
34
- </div>
35
-
36
- <!-- Input bar -->
37
- <div class="flex-shrink-0 border-t border-slate-800/50 bg-slate-900/30 backdrop-blur-sm rounded-b-xl px-3 py-3">
38
- <div class="flex items-end gap-2 max-w-3xl mx-auto">
39
- <div class="flex-1 relative">
40
- <textarea
41
- id="chat-input"
42
- rows="1"
43
- placeholder="Ask me anything..."
44
- class="w-full bg-slate-900/80 border border-slate-700/50 rounded-xl px-4 py-3 text-sm text-slate-200 placeholder-slate-600 resize-none focus:outline-none focus:border-nexo-500/50 focus:ring-1 focus:ring-nexo-500/20 transition-all"
45
- onkeydown="handleKeydown(event)"
46
- oninput="autoResize(this)"
47
- ></textarea>
48
- </div>
49
- <button
50
- id="send-btn"
51
- onclick="sendMessage()"
52
- class="w-10 h-10 rounded-xl bg-nexo-500 hover:bg-nexo-600 text-white flex items-center justify-center transition-all duration-200 hover:scale-105 active:scale-95 disabled:opacity-30 disabled:hover:scale-100 flex-shrink-0"
53
- disabled
54
- >
55
- <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19V5m0 0l-7 7m7-7l7 7"/></svg>
56
- </button>
57
- </div>
58
- </div>
59
- </div>
60
-
61
- <style>
62
- #chat-messages::-webkit-scrollbar { width: 4px; }
63
- #chat-messages::-webkit-scrollbar-thumb { background: #334155; border-radius: 2px; }
64
-
65
- .suggestion-pill {
66
- background: rgba(30, 41, 59, 0.6);
67
- border: 1px solid rgba(51, 65, 85, 0.4);
68
- color: #94a3b8;
69
- }
70
- .suggestion-pill:hover {
71
- background: rgba(124, 58, 237, 0.12);
72
- border-color: rgba(124, 58, 237, 0.3);
73
- color: #c4b5fd;
74
- }
75
-
76
- .bubble-user {
77
- background: rgba(124, 58, 237, 0.2);
78
- border: 1px solid rgba(124, 58, 237, 0.25);
79
- border-radius: 16px 16px 4px 16px;
80
- }
81
- .bubble-nexo {
82
- background: rgba(30, 41, 59, 0.6);
83
- border: 1px solid rgba(51, 65, 85, 0.4);
84
- border-radius: 16px 16px 16px 4px;
85
- }
86
-
87
- @keyframes bubbleIn {
88
- 0% { opacity: 0; transform: translateY(8px) scale(0.97); }
89
- 100% { opacity: 1; transform: translateY(0) scale(1); }
90
- }
91
- .chat-bubble { animation: bubbleIn 0.3s cubic-bezier(0.16, 1, 0.3, 1); }
92
-
93
- @keyframes typingDot {
94
- 0%, 60%, 100% { opacity: 0.3; transform: translateY(0); }
95
- 30% { opacity: 1; transform: translateY(-4px); }
96
- }
97
- .typing-dot {
98
- width: 6px; height: 6px; border-radius: 50%; background: #7C3AED;
99
- display: inline-block;
100
- }
101
- .typing-dot:nth-child(1) { animation: typingDot 1.4s ease-in-out infinite; }
102
- .typing-dot:nth-child(2) { animation: typingDot 1.4s ease-in-out infinite 0.2s; }
103
- .typing-dot:nth-child(3) { animation: typingDot 1.4s ease-in-out infinite 0.4s; }
104
-
105
- .data-table-toggle {
106
- cursor: pointer;
107
- transition: color 0.15s;
108
- }
109
- .data-table-toggle:hover { color: #c4b5fd; }
110
-
111
- .data-section {
112
- max-height: 0;
113
- overflow: hidden;
114
- transition: max-height 0.3s ease;
115
- }
116
- .data-section.open {
117
- max-height: 500px;
118
- overflow-y: auto;
119
- }
120
-
121
- #chat-input {
122
- max-height: 120px;
123
- line-height: 1.5;
124
- }
125
-
126
- .nexo-avatar {
127
- width: 28px; height: 28px;
128
- background: linear-gradient(135deg, #7C3AED, #EC4899);
129
- border-radius: 8px;
130
- display: flex; align-items: center; justify-content: center;
131
- font-size: 11px; font-weight: 700; color: white;
132
- flex-shrink: 0;
133
- }
134
- </style>
135
- {% endblock %}
136
-
137
- {% block scripts %}
138
- <script>
139
- const STORAGE_KEY = 'nexo-chat-history';
140
- let chatHistory = [];
141
- let isWaiting = false;
142
-
143
- function loadHistory() {
144
- try {
145
- const stored = sessionStorage.getItem(STORAGE_KEY);
146
- if (stored) chatHistory = JSON.parse(stored);
147
- } catch {}
148
- }
149
-
150
- function saveHistory() {
151
- try {
152
- sessionStorage.setItem(STORAGE_KEY, JSON.stringify(chatHistory.slice(-100)));
153
- } catch {}
154
- }
155
-
156
- function autoResize(el) {
157
- el.style.height = 'auto';
158
- el.style.height = Math.min(el.scrollHeight, 120) + 'px';
159
- document.getElementById('send-btn').disabled = !el.value.trim();
160
- }
161
-
162
- function handleKeydown(e) {
163
- if (e.key === 'Enter' && !e.shiftKey) {
164
- e.preventDefault();
165
- sendMessage();
166
- }
167
- }
168
-
169
- function scrollToBottom() {
170
- const container = document.getElementById('chat-messages');
171
- requestAnimationFrame(() => { container.scrollTop = container.scrollHeight; });
172
- }
173
-
174
- function renderMessage(msg) {
175
- if (msg.role === 'user') {
176
- return `
177
- <div class="flex justify-end chat-bubble">
178
- <div class="bubble-user px-4 py-2.5 max-w-lg">
179
- <p class="text-sm text-slate-200 leading-relaxed">${escapeHtml(msg.content)}</p>
180
- </div>
181
- </div>
182
- `;
183
- }
184
-
185
- // NEXO response
186
- let dataHtml = '';
187
- if (msg.data && msg.data.length > 0) {
188
- const cols = Object.keys(msg.data[0]);
189
- dataHtml = `
190
- <div class="mt-3">
191
- <button class="data-table-toggle text-[10px] uppercase tracking-wider text-slate-500 font-semibold flex items-center gap-1" onclick="toggleDataSection(this)">
192
- <svg class="w-3 h-3 transition-transform duration-200 data-chevron" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>
193
- ${msg.data.length} results
194
- </button>
195
- <div class="data-section mt-2">
196
- <div class="bg-slate-950/50 rounded-lg border border-slate-800/30 overflow-x-auto">
197
- <table class="w-full text-xs">
198
- <thead>
199
- <tr class="border-b border-slate-800/40">
200
- ${cols.map(c => `<th class="px-3 py-2 text-left text-[10px] uppercase tracking-wider text-slate-600 font-semibold">${escapeHtml(c)}</th>`).join('')}
201
- </tr>
202
- </thead>
203
- <tbody class="divide-y divide-slate-800/20">
204
- ${msg.data.slice(0, 20).map(row => `
205
- <tr class="hover:bg-slate-800/20 transition-colors">
206
- ${cols.map(c => `<td class="px-3 py-1.5 font-mono text-slate-400 whitespace-nowrap">${escapeHtml(String(row[c] ?? '--'))}</td>`).join('')}
207
- </tr>
208
- `).join('')}
209
- </tbody>
210
- </table>
211
- ${msg.data.length > 20 ? `<div class="px-3 py-2 text-[10px] text-slate-600 border-t border-slate-800/30">+ ${msg.data.length - 20} more rows</div>` : ''}
212
- </div>
213
- </div>
214
- </div>
215
- `;
216
- }
217
-
218
- return `
219
- <div class="flex items-start gap-2.5 chat-bubble">
220
- <div class="nexo-avatar">N</div>
221
- <div class="bubble-nexo px-4 py-2.5 max-w-2xl">
222
- <div class="text-sm text-slate-300 leading-relaxed chat-answer">${formatAnswer(msg.content)}</div>
223
- ${dataHtml}
224
- ${msg.query_type ? `<div class="mt-2 text-[9px] text-slate-600 font-mono">query: ${escapeHtml(msg.query_type)}</div>` : ''}
225
- </div>
226
- </div>
227
- `;
228
- }
229
-
230
- function formatAnswer(text) {
231
- if (!text) return '';
232
- // Basic markdown-like formatting
233
- let html = escapeHtml(text);
234
- html = html.replace(/\*\*(.*?)\*\*/g, '<strong class="text-slate-100 font-semibold">$1</strong>');
235
- html = html.replace(/`(.*?)`/g, '<code class="text-xs bg-slate-800/60 px-1 py-0.5 rounded text-nexo-300 font-mono">$1</code>');
236
- html = html.replace(/\n/g, '<br>');
237
- return html;
238
- }
239
-
240
- function toggleDataSection(btn) {
241
- const section = btn.nextElementSibling;
242
- const chevron = btn.querySelector('.data-chevron');
243
- section.classList.toggle('open');
244
- chevron.style.transform = section.classList.contains('open') ? 'rotate(90deg)' : '';
245
- }
246
-
247
- function showTyping() {
248
- const container = document.getElementById('chat-messages');
249
- const typingEl = document.createElement('div');
250
- typingEl.id = 'typing-indicator';
251
- typingEl.className = 'flex items-start gap-2.5 chat-bubble';
252
- typingEl.innerHTML = `
253
- <div class="nexo-avatar">N</div>
254
- <div class="bubble-nexo px-4 py-3">
255
- <div class="flex items-center gap-1.5">
256
- <div class="typing-dot"></div>
257
- <div class="typing-dot"></div>
258
- <div class="typing-dot"></div>
259
- </div>
260
- </div>
261
- `;
262
- container.appendChild(typingEl);
263
- scrollToBottom();
264
- }
265
-
266
- function hideTyping() {
267
- const el = document.getElementById('typing-indicator');
268
- if (el) el.remove();
269
- }
270
-
271
- function renderAllMessages() {
272
- const container = document.getElementById('chat-messages');
273
- if (chatHistory.length === 0) {
274
- container.innerHTML = `
275
- <div class="flex items-start gap-2.5 chat-bubble">
276
- <div class="nexo-avatar">N</div>
277
- <div class="bubble-nexo px-4 py-3 max-w-xl">
278
- <p class="text-sm text-slate-300 leading-relaxed">I'm <strong class="text-nexo-400">NEXO Chat</strong>. Ask me anything about the system.</p>
279
- <p class="text-xs text-slate-500 mt-2">I can query diaries, crons, trust, skills, followups, and more.</p>
280
- </div>
281
- </div>
282
- `;
283
- return;
284
- }
285
- container.innerHTML = chatHistory.map(renderMessage).join('');
286
- scrollToBottom();
287
- }
288
-
289
- function sendSuggestion(btn) {
290
- const text = btn.textContent.trim();
291
- document.getElementById('chat-input').value = text;
292
- document.getElementById('send-btn').disabled = false;
293
- sendMessage();
294
- }
295
-
296
- async function sendMessage() {
297
- const input = document.getElementById('chat-input');
298
- const text = input.value.trim();
299
- if (!text || isWaiting) return;
300
-
301
- // Add user message
302
- chatHistory.push({ role: 'user', content: text });
303
- saveHistory();
304
- renderAllMessages();
305
-
306
- // Clear input
307
- input.value = '';
308
- input.style.height = 'auto';
309
- document.getElementById('send-btn').disabled = true;
310
-
311
- // Hide suggestions after first message
312
- document.getElementById('suggestions').style.display = chatHistory.filter(m => m.role === 'user').length > 1 ? 'none' : '';
313
-
314
- // Show typing
315
- isWaiting = true;
316
- showTyping();
317
-
318
- try {
319
- const res = await fetch('/api/chat', {
320
- method: 'POST',
321
- headers: { 'Content-Type': 'application/json' },
322
- body: JSON.stringify({ message: text })
323
- });
324
-
325
- hideTyping();
326
-
327
- if (!res.ok) {
328
- const errText = await res.text().catch(() => 'Error');
329
- chatHistory.push({ role: 'nexo', content: `Error: ${res.status} - ${errText}` });
330
- } else {
331
- const data = await res.json();
332
- chatHistory.push({
333
- role: 'nexo',
334
- content: data.answer || 'Sin respuesta.',
335
- data: data.data || null,
336
- query_type: data.query_type || null
337
- });
338
- }
339
- } catch (err) {
340
- hideTyping();
341
- chatHistory.push({ role: 'nexo', content: `Error de conexion: ${err.message}` });
342
- }
343
-
344
- isWaiting = false;
345
- saveHistory();
346
- renderAllMessages();
347
- }
348
-
349
- // Initialize
350
- loadHistory();
351
- renderAllMessages();
352
-
353
- // Focus input
354
- document.getElementById('chat-input').focus();
355
- </script>
356
- {% endblock %}
@@ -1,259 +0,0 @@
1
- {% extends "base.html" %}
2
-
3
- {% block title %}Claims Network{% endblock %}
4
- {% block page_title %}Claims Network{% endblock %}
5
-
6
- {% block content %}
7
- <div class="space-y-5">
8
- <!-- Status counts -->
9
- <div class="grid grid-cols-4 gap-4" id="status-cards">
10
- <div class="bg-slate-900/50 border border-slate-800/50 rounded-xl p-4 card">
11
- <div class="text-xs uppercase tracking-wider text-slate-400 font-medium mb-2">Total Claims</div>
12
- <div class="text-xl font-mono font-semibold text-slate-200" id="total-claims">--</div>
13
- </div>
14
- <div class="bg-slate-900/50 border border-slate-800/50 rounded-xl p-4 card">
15
- <div class="text-xs uppercase tracking-wider text-emerald-400/70 font-medium mb-2">Verified</div>
16
- <div class="text-xl font-mono font-semibold text-emerald-400" id="verified-claims">--</div>
17
- </div>
18
- <div class="bg-slate-900/50 border border-slate-800/50 rounded-xl p-4 card">
19
- <div class="text-xs uppercase tracking-wider text-amber-400/70 font-medium mb-2">Pending</div>
20
- <div class="text-xl font-mono font-semibold text-amber-400" id="pending-claims">--</div>
21
- </div>
22
- <div class="bg-slate-900/50 border border-slate-800/50 rounded-xl p-4 card">
23
- <div class="text-xs uppercase tracking-wider text-red-400/70 font-medium mb-2">Disputed</div>
24
- <div class="text-xl font-mono font-semibold text-red-400" id="disputed-claims">--</div>
25
- </div>
26
- </div>
27
-
28
- <!-- Filter bar -->
29
- <div class="flex items-center gap-2 flex-wrap">
30
- <span class="text-xs text-slate-500">Filter:</span>
31
- <button onclick="filterClaims('all')" class="filter-btn active px-2.5 py-1 rounded-md text-xs bg-slate-800 text-slate-300 hover:bg-slate-700 transition-colors" data-filter="all">All</button>
32
- <button onclick="filterClaims('verified')" class="filter-btn px-2.5 py-1 rounded-md text-xs bg-slate-800/50 text-slate-400 hover:bg-slate-700 transition-colors" data-filter="verified">Verified</button>
33
- <button onclick="filterClaims('pending')" class="filter-btn px-2.5 py-1 rounded-md text-xs bg-slate-800/50 text-slate-400 hover:bg-slate-700 transition-colors" data-filter="pending">Pending</button>
34
- <button onclick="filterClaims('disputed')" class="filter-btn px-2.5 py-1 rounded-md text-xs bg-slate-800/50 text-slate-400 hover:bg-slate-700 transition-colors" data-filter="disputed">Disputed</button>
35
- <div class="flex-1"></div>
36
- <input type="text" id="claim-search" placeholder="Search claims..." class="px-3 py-1.5 rounded-lg bg-slate-800/50 border border-slate-700/50 text-xs text-slate-200 placeholder-slate-600 focus:outline-none focus:ring-1 focus:ring-violet-500 w-48" oninput="searchClaims(this.value)">
37
- </div>
38
-
39
- <div class="grid grid-cols-3 gap-5">
40
- <!-- Network visualization -->
41
- <div class="col-span-2 bg-slate-900/50 border border-slate-800/50 rounded-xl p-5 card">
42
- <div class="text-xs uppercase tracking-wider text-slate-400 font-medium mb-4">Claim Network</div>
43
- <canvas id="network-canvas" class="w-full rounded-lg" style="height: 420px; background: rgba(15,23,42,0.5);"></canvas>
44
- </div>
45
-
46
- <!-- Claim detail panel -->
47
- <div class="bg-slate-900/50 border border-slate-800/50 rounded-xl p-5 card">
48
- <div class="text-xs uppercase tracking-wider text-slate-400 font-medium mb-4">Claim Details</div>
49
- <div id="claim-detail" class="text-xs text-slate-600 text-center py-12">
50
- Click a node to see details
51
- </div>
52
- </div>
53
- </div>
54
-
55
- <!-- Claims list -->
56
- <div class="bg-slate-900/50 border border-slate-800/50 rounded-xl p-5 card">
57
- <div class="text-xs uppercase tracking-wider text-slate-400 font-medium mb-4">All Claims</div>
58
- <div class="space-y-2" id="claims-list">
59
- <div class="text-xs text-slate-600 py-4 text-center">loading...</div>
60
- </div>
61
- </div>
62
- </div>
63
- {% endblock %}
64
-
65
- {% block scripts %}
66
- <script>
67
- let allClaims = [];
68
- let allLinks = [];
69
- let currentFilter = 'all';
70
- let nodes = [];
71
- let selectedNode = null;
72
-
73
- const STATUS_COLORS = {
74
- verified: { bg: 'bg-emerald-500/10', text: 'text-emerald-400', fill: '#10B981' },
75
- pending: { bg: 'bg-amber-500/10', text: 'text-amber-400', fill: '#F59E0B' },
76
- disputed: { bg: 'bg-red-500/10', text: 'text-red-400', fill: '#EF4444' },
77
- unknown: { bg: 'bg-slate-500/10', text: 'text-slate-400', fill: '#64748b' },
78
- };
79
-
80
- function filterClaims(status) {
81
- currentFilter = status;
82
- document.querySelectorAll('.filter-btn').forEach(b => {
83
- b.classList.toggle('active', b.dataset.filter === status);
84
- b.classList.toggle('bg-slate-800', b.dataset.filter === status);
85
- b.classList.toggle('text-slate-300', b.dataset.filter === status);
86
- b.classList.toggle('bg-slate-800/50', b.dataset.filter !== status);
87
- b.classList.toggle('text-slate-400', b.dataset.filter !== status);
88
- });
89
- renderClaimsList();
90
- drawNetwork();
91
- }
92
-
93
- function searchClaims(query) {
94
- const q = query.toLowerCase();
95
- document.querySelectorAll('#claims-list .claim-item').forEach(el => {
96
- const text = el.textContent.toLowerCase();
97
- el.style.display = text.includes(q) ? '' : 'none';
98
- });
99
- }
100
-
101
- function getFilteredClaims() {
102
- if (currentFilter === 'all') return allClaims;
103
- return allClaims.filter(c => (c.verification_status || 'pending') === currentFilter);
104
- }
105
-
106
- function renderClaimsList() {
107
- const container = document.getElementById('claims-list');
108
- const claims = getFilteredClaims();
109
- if (!claims.length) { container.innerHTML = '<div class="text-xs text-slate-600 py-4 text-center">No claims found</div>'; return; }
110
-
111
- container.innerHTML = claims.map(c => {
112
- const status = c.verification_status || 'pending';
113
- const sc = STATUS_COLORS[status] || STATUS_COLORS.unknown;
114
- const conf = c.confidence != null ? (c.confidence * 100).toFixed(0) + '%' : '--';
115
- return `<div class="claim-item flex items-start gap-3 py-2.5 px-3 rounded-lg hover:bg-slate-800/30 transition-colors cursor-pointer border border-transparent hover:border-slate-800/50" onclick="selectClaim(${c.id})">
116
- <span class="w-1.5 h-1.5 rounded-full mt-1.5 flex-shrink-0" style="background:${sc.fill}"></span>
117
- <div class="flex-1 min-w-0">
118
- <div class="text-xs text-slate-300 leading-relaxed">${escapeHtml(c.text || '--')}</div>
119
- <div class="flex items-center gap-2 mt-1">
120
- <span class="px-1.5 py-0.5 rounded text-[10px] font-medium ${sc.bg} ${sc.text}">${status}</span>
121
- ${c.domain ? `<span class="text-[10px] text-slate-500">${escapeHtml(c.domain)}</span>` : ''}
122
- <span class="text-[10px] text-slate-600 font-mono">conf: ${conf}</span>
123
- </div>
124
- </div>
125
- </div>`;
126
- }).join('');
127
- }
128
-
129
- function selectClaim(id) {
130
- const c = allClaims.find(x => x.id === id);
131
- if (!c) return;
132
- selectedNode = id;
133
-
134
- const status = c.verification_status || 'pending';
135
- const sc = STATUS_COLORS[status] || STATUS_COLORS.unknown;
136
- const linkedClaims = allLinks.filter(l => l.source_claim_id === id || l.target_claim_id === id);
137
-
138
- document.getElementById('claim-detail').innerHTML = `
139
- <div class="space-y-4 text-left">
140
- <div>
141
- <span class="px-1.5 py-0.5 rounded text-[10px] font-medium ${sc.bg} ${sc.text}">${status}</span>
142
- ${c.domain ? `<span class="ml-2 text-[10px] text-slate-500">${escapeHtml(c.domain)}</span>` : ''}
143
- </div>
144
- <p class="text-sm text-slate-200 leading-relaxed">${escapeHtml(c.text || '--')}</p>
145
- <div class="grid grid-cols-2 gap-3">
146
- <div><div class="text-[10px] text-slate-500 uppercase">Confidence</div><div class="text-sm font-mono text-slate-300">${c.confidence != null ? (c.confidence * 100).toFixed(1) + '%' : '--'}</div></div>
147
- <div><div class="text-[10px] text-slate-500 uppercase">ID</div><div class="text-sm font-mono text-slate-300">#${c.id}</div></div>
148
- </div>
149
- ${linkedClaims.length ? `
150
- <div class="border-t border-slate-800/50 pt-3">
151
- <div class="text-[10px] text-slate-500 uppercase mb-2">Linked Claims (${linkedClaims.length})</div>
152
- <div class="space-y-1.5">${linkedClaims.map(l => {
153
- const otherId = l.source_claim_id === id ? l.target_claim_id : l.source_claim_id;
154
- const other = allClaims.find(x => x.id === otherId);
155
- return `<div class="text-xs text-slate-400 flex items-center gap-2">
156
- <span class="text-[10px] text-violet-400 font-mono">${escapeHtml(l.relation || 'related')}</span>
157
- <span class="truncate">${other ? escapeHtml((other.text || '').substring(0, 60)) : '#' + otherId}</span>
158
- </div>`;
159
- }).join('')}</div>
160
- </div>` : ''}
161
- </div>`;
162
-
163
- drawNetwork();
164
- }
165
-
166
- function drawNetwork() {
167
- const canvas = document.getElementById('network-canvas');
168
- const ctx = canvas.getContext('2d');
169
- const rect = canvas.getBoundingClientRect();
170
- canvas.width = rect.width * window.devicePixelRatio;
171
- canvas.height = rect.height * window.devicePixelRatio;
172
- ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
173
- const w = rect.width, h = rect.height;
174
-
175
- ctx.clearRect(0, 0, w, h);
176
-
177
- const filtered = getFilteredClaims();
178
- const idSet = new Set(filtered.map(c => c.id));
179
-
180
- if (!nodes.length) {
181
- nodes = filtered.map((c, i) => {
182
- const angle = (2 * Math.PI * i) / filtered.length;
183
- const r = Math.min(w, h) * 0.35;
184
- return { id: c.id, x: w / 2 + r * Math.cos(angle) + (Math.random() - 0.5) * 40, y: h / 2 + r * Math.sin(angle) + (Math.random() - 0.5) * 40, claim: c };
185
- });
186
- }
187
-
188
- // Draw links
189
- const visLinks = allLinks.filter(l => idSet.has(l.source_claim_id) && idSet.has(l.target_claim_id));
190
- visLinks.forEach(l => {
191
- const src = nodes.find(n => n.id === l.source_claim_id);
192
- const tgt = nodes.find(n => n.id === l.target_claim_id);
193
- if (!src || !tgt) return;
194
- ctx.beginPath();
195
- ctx.moveTo(src.x, src.y);
196
- ctx.lineTo(tgt.x, tgt.y);
197
- ctx.strokeStyle = (selectedNode === src.id || selectedNode === tgt.id) ? 'rgba(124,58,237,0.5)' : 'rgba(51,65,85,0.3)';
198
- ctx.lineWidth = (selectedNode === src.id || selectedNode === tgt.id) ? 2 : 1;
199
- ctx.stroke();
200
- });
201
-
202
- // Draw nodes
203
- nodes.forEach(n => {
204
- if (!idSet.has(n.id)) return;
205
- const status = n.claim.verification_status || 'pending';
206
- const color = (STATUS_COLORS[status] || STATUS_COLORS.unknown).fill;
207
- const isSelected = selectedNode === n.id;
208
- const radius = isSelected ? 8 : 5;
209
-
210
- ctx.beginPath();
211
- ctx.arc(n.x, n.y, radius, 0, 2 * Math.PI);
212
- ctx.fillStyle = color;
213
- ctx.fill();
214
- if (isSelected) {
215
- ctx.strokeStyle = '#fff';
216
- ctx.lineWidth = 2;
217
- ctx.stroke();
218
- }
219
- });
220
- }
221
-
222
- canvas_click: {
223
- const canvas = document.getElementById('network-canvas');
224
- canvas.addEventListener('click', e => {
225
- const rect = canvas.getBoundingClientRect();
226
- const x = e.clientX - rect.left;
227
- const y = e.clientY - rect.top;
228
- let closest = null, minDist = 20;
229
- nodes.forEach(n => {
230
- const d = Math.hypot(n.x - x, n.y - y);
231
- if (d < minDist) { minDist = d; closest = n; }
232
- });
233
- if (closest) selectClaim(closest.id);
234
- });
235
- }
236
-
237
- async function loadClaims() {
238
- const data = await fetchJSON('/api/claims');
239
- if (!data) return;
240
-
241
- allClaims = data.claims || [];
242
- allLinks = data.links || [];
243
- const counts = data.status_counts || {};
244
-
245
- document.getElementById('total-claims').textContent = allClaims.length;
246
- document.getElementById('verified-claims').textContent = counts.verified || 0;
247
- document.getElementById('pending-claims').textContent = counts.pending || 0;
248
- document.getElementById('disputed-claims').textContent = counts.disputed || 0;
249
-
250
- nodes = [];
251
- renderClaimsList();
252
- drawNetwork();
253
- }
254
-
255
- loadClaims();
256
- setInterval(loadClaims, 60000);
257
- window.addEventListener('resize', () => { if (allClaims.length) { nodes = []; drawNetwork(); } });
258
- </script>
259
- {% endblock %}