nexo-brain 5.3.26 → 5.3.28

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 (212) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/package.json +1 -1
  3. package/src/hook_guardrails.py +44 -0
  4. package/src/server.py +3 -0
  5. package/src/tools_sessions.py +6 -1
  6. package/src/dashboard/static/favicon 2.svg +0 -32
  7. package/src/dashboard/static/nexo-logo 2.png +0 -0
  8. package/src/dashboard/static/nexo-logo 2.svg +0 -40
  9. package/src/dashboard/static/style 2.css +0 -2458
  10. package/src/dashboard/templates/adaptive 2.html +0 -118
  11. package/src/dashboard/templates/artifacts 2.html +0 -133
  12. package/src/dashboard/templates/backups 2.html +0 -136
  13. package/src/dashboard/templates/base 2.html +0 -417
  14. package/src/dashboard/templates/calendar 2.html +0 -591
  15. package/src/dashboard/templates/chat 2.html +0 -356
  16. package/src/dashboard/templates/claims 2.html +0 -259
  17. package/src/dashboard/templates/cortex 2.html +0 -321
  18. package/src/dashboard/templates/credentials 2.html +0 -128
  19. package/src/dashboard/templates/crons 2.html +0 -370
  20. package/src/dashboard/templates/dashboard 2.html +0 -494
  21. package/src/dashboard/templates/dreams 2.html +0 -252
  22. package/src/dashboard/templates/email 2.html +0 -160
  23. package/src/dashboard/templates/evolution 2.html +0 -189
  24. package/src/dashboard/templates/feed 2.html +0 -249
  25. package/src/dashboard/templates/followup_health 2.html +0 -170
  26. package/src/dashboard/templates/graph 2.html +0 -201
  27. package/src/dashboard/templates/guard 2.html +0 -259
  28. package/src/dashboard/templates/inbox 2.html +0 -251
  29. package/src/dashboard/templates/memory 2.html +0 -420
  30. package/src/dashboard/templates/operations 2.html +0 -608
  31. package/src/dashboard/templates/plugins 2.html +0 -185
  32. package/src/dashboard/templates/protocol 2.html +0 -199
  33. package/src/dashboard/templates/rules 2.html +0 -246
  34. package/src/dashboard/templates/sentiment 2.html +0 -247
  35. package/src/dashboard/templates/sessions 2.html +0 -218
  36. package/src/dashboard/templates/skills 2.html +0 -329
  37. package/src/dashboard/templates/somatic 2.html +0 -73
  38. package/src/dashboard/templates/triggers 2.html +0 -133
  39. package/src/dashboard/templates/trust 2.html +0 -360
  40. package/src/db/__init__ 2.py +0 -259
  41. package/src/db/_core 2.py +0 -437
  42. package/src/db/_credentials 2.py +0 -124
  43. package/src/db/_episodic 2.py +0 -762
  44. package/src/db/_evolution 2.py +0 -54
  45. package/src/db/_fts 2.py +0 -406
  46. package/src/db/_goal_profiles 2.py +0 -376
  47. package/src/db/_hot_context 2.py +0 -660
  48. package/src/db/_outcomes 2.py +0 -800
  49. package/src/db/_personal_scripts 2.py +0 -582
  50. package/src/db/_sessions 2.py +0 -330
  51. package/src/db/_tasks 2.py +0 -91
  52. package/src/db/_watchers 2.py +0 -173
  53. package/src/doctor/formatters 2.py +0 -52
  54. package/src/doctor/models 2.py +0 -69
  55. package/src/doctor/planes 2.py +0 -87
  56. package/src/doctor/providers/__init__ 2.py +0 -1
  57. package/src/doctor/providers/deep 2.py +0 -367
  58. package/src/evolution_cycle 2.py +0 -519
  59. package/src/hooks/auto_capture 2.py +0 -208
  60. package/src/hooks/caffeinate-guard 2.sh +0 -8
  61. package/src/hooks/capture-session 2.sh +0 -21
  62. package/src/hooks/capture-tool-logs 2.sh +0 -158
  63. package/src/hooks/daily-briefing-check 2.sh +0 -33
  64. package/src/hooks/heartbeat-enforcement 2.py +0 -90
  65. package/src/hooks/heartbeat-posttool 2.sh +0 -18
  66. package/src/hooks/inbox-hook 2.sh +0 -76
  67. package/src/hooks/post-compact 2.sh +0 -152
  68. package/src/hooks/pre-compact 2.sh +0 -169
  69. package/src/hooks/protocol-guardrail 2.sh +0 -10
  70. package/src/hooks/protocol-pretool-guardrail 2.sh +0 -9
  71. package/src/hooks/session-stop 2.sh +0 -52
  72. package/src/kg_populate 2.py +0 -292
  73. package/src/maintenance 2.py +0 -53
  74. package/src/memory_backends 2.py +0 -71
  75. package/src/migrate_embeddings 2.py +0 -124
  76. package/src/nexo_sdk 2.py +0 -103
  77. package/src/observability 2.py +0 -199
  78. package/src/plugin_loader 2.py +0 -217
  79. package/src/plugins/__init__ 2.py +0 -0
  80. package/src/plugins/artifact_registry 2.py +0 -450
  81. package/src/plugins/backup 2.py +0 -127
  82. package/src/plugins/claims_tools 2.py +0 -119
  83. package/src/plugins/cognitive_memory 2.py +0 -609
  84. package/src/plugins/core_rules 2.py +0 -252
  85. package/src/plugins/cortex 2.py +0 -1155
  86. package/src/plugins/entities 2.py +0 -67
  87. package/src/plugins/episodic_memory 2.py +0 -560
  88. package/src/plugins/evolution 2.py +0 -167
  89. package/src/plugins/goal_engine 2.py +0 -142
  90. package/src/plugins/guard 2.py +0 -862
  91. package/src/plugins/impact 2.py +0 -29
  92. package/src/plugins/knowledge_graph_tools 2.py +0 -137
  93. package/src/plugins/media_memory_tools 2.py +0 -98
  94. package/src/plugins/memory_export 2.py +0 -196
  95. package/src/plugins/outcomes 2.py +0 -130
  96. package/src/plugins/personal_scripts 2.py +0 -117
  97. package/src/plugins/preferences 2.py +0 -47
  98. package/src/plugins/protocol 2.py +0 -1449
  99. package/src/plugins/simple_api 2.py +0 -106
  100. package/src/plugins/skills 2.py +0 -341
  101. package/src/plugins/state_watchers 2.py +0 -79
  102. package/src/plugins/update 2.py +0 -986
  103. package/src/plugins/user_state_tools 2.py +0 -43
  104. package/src/plugins/workflow 2.py +0 -588
  105. package/src/protocol_settings 2.py +0 -59
  106. package/src/public_contribution 2.py +0 -466
  107. package/src/public_evolution_queue 2.py +0 -241
  108. package/src/requirements 2.txt +0 -14
  109. package/src/retroactive_learnings 2.py +0 -373
  110. package/src/rules/__init__ 2.py +0 -0
  111. package/src/rules/core-rules 2.json +0 -331
  112. package/src/rules/migrate 2.py +0 -207
  113. package/src/runtime_power 2.py +0 -874
  114. package/src/script_registry 2.py +0 -1559
  115. package/src/scripts/check-context 2.py +0 -272
  116. package/src/scripts/deep-sleep/apply_findings 2.py +0 -2327
  117. package/src/scripts/deep-sleep/collect 2.py +0 -928
  118. package/src/scripts/deep-sleep/extract 2.py +0 -330
  119. package/src/scripts/deep-sleep/extract-prompt 2.md +0 -285
  120. package/src/scripts/deep-sleep/synthesize 2.py +0 -312
  121. package/src/scripts/deep-sleep/synthesize-prompt 2.md +0 -336
  122. package/src/scripts/nexo-agent-run 2.py +0 -75
  123. package/src/scripts/nexo-auto-update 2.py +0 -6
  124. package/src/scripts/nexo-backup 2.sh +0 -25
  125. package/src/scripts/nexo-brain-activation 2.sh +0 -140
  126. package/src/scripts/nexo-catchup 2.py +0 -300
  127. package/src/scripts/nexo-cognitive-decay 2.py +0 -257
  128. package/src/scripts/nexo-cortex-cycle 2.py +0 -293
  129. package/src/scripts/nexo-cron-wrapper 2.sh +0 -53
  130. package/src/scripts/nexo-daily-self-audit 2.py +0 -2161
  131. package/src/scripts/nexo-dashboard 2.sh +0 -29
  132. package/src/scripts/nexo-deep-sleep 2.sh +0 -86
  133. package/src/scripts/nexo-evolution-run 2.py +0 -1664
  134. package/src/scripts/nexo-followup-hygiene 2.py +0 -139
  135. package/src/scripts/nexo-hook-record 2.py +0 -42
  136. package/src/scripts/nexo-immune 2.py +0 -936
  137. package/src/scripts/nexo-impact-scorer 2.py +0 -117
  138. package/src/scripts/nexo-inbox-hook 2.sh +0 -74
  139. package/src/scripts/nexo-install 2.py +0 -6
  140. package/src/scripts/nexo-learning-housekeep 2.py +0 -401
  141. package/src/scripts/nexo-learning-validator 2.py +0 -266
  142. package/src/scripts/nexo-migrate 2.py +0 -260
  143. package/src/scripts/nexo-outcome-checker 2.py +0 -127
  144. package/src/scripts/nexo-postmortem-consolidator 2.py +0 -456
  145. package/src/scripts/nexo-pre-commit 2.py +0 -120
  146. package/src/scripts/nexo-prevent-sleep 2.sh +0 -35
  147. package/src/scripts/nexo-proactive-dashboard 2.py +0 -354
  148. package/src/scripts/nexo-reflection 2.py +0 -256
  149. package/src/scripts/nexo-runtime-preflight 2.py +0 -274
  150. package/src/scripts/nexo-sleep 2.py +0 -631
  151. package/src/scripts/nexo-snapshot-restore 2.sh +0 -35
  152. package/src/scripts/nexo-sync-clients 2.py +0 -16
  153. package/src/scripts/nexo-synthesis 2.py +0 -475
  154. package/src/scripts/nexo-tcc-approve 2.sh +0 -79
  155. package/src/scripts/nexo-update 2.sh +0 -306
  156. package/src/scripts/nexo-watchdog 2.sh +0 -1207
  157. package/src/scripts/nexo-watchdog-smoke 2.py +0 -119
  158. package/src/scripts/rehydrate_learnings_from_archive 2.py +0 -245
  159. package/src/server 2.py +0 -1296
  160. package/src/skills/run-nexo-audit-phase/guide 2.md +0 -43
  161. package/src/skills/run-nexo-audit-phase/skill 2.json +0 -59
  162. package/src/skills/run-nexo-core-fix-cycle/guide 2.md +0 -17
  163. package/src/skills/run-nexo-core-fix-cycle/script 2.py +0 -276
  164. package/src/skills/run-nexo-core-fix-cycle/skill 2.json +0 -58
  165. package/src/skills/run-release-final-audit/guide 2.md +0 -16
  166. package/src/skills/run-release-final-audit/script 2.py +0 -259
  167. package/src/skills/run-release-final-audit/skill 2.json +0 -77
  168. package/src/skills/run-runtime-doctor/guide 2.md +0 -12
  169. package/src/skills/run-runtime-doctor/script 2.py +0 -21
  170. package/src/skills/run-runtime-doctor/skill 2.json +0 -25
  171. package/src/skills_runtime 2.py +0 -932
  172. package/src/state_watchers_runtime 2.py +0 -475
  173. package/src/storage_router 2.py +0 -32
  174. package/src/system_catalog 2.py +0 -786
  175. package/src/tools_coordination 2.py +0 -103
  176. package/src/tools_credentials 2.py +0 -68
  177. package/src/tools_drive 2.py +0 -487
  178. package/src/tools_hot_context 2.py +0 -163
  179. package/src/tools_learnings 2.py +0 -612
  180. package/src/tools_menu 2.py +0 -229
  181. package/src/tools_reminders 2.py +0 -88
  182. package/src/tools_reminders_crud 2.py +0 -363
  183. package/src/tools_sessions 2.py +0 -1054
  184. package/src/tools_system_catalog 2.py +0 -19
  185. package/src/tools_task_history 2.py +0 -57
  186. package/src/tools_transcripts 2.py +0 -98
  187. package/src/transcript_utils 2.py +0 -412
  188. package/src/user_context 2.py +0 -46
  189. package/src/user_data_portability 2.py +0 -328
  190. package/src/user_state_model 2.py +0 -170
  191. package/templates/CLAUDE.md 2.template +0 -108
  192. package/templates/CODEX.AGENTS.md 2.template +0 -66
  193. package/templates/launchagents/README 2.md +0 -132
  194. package/templates/launchagents/com.nexo.auto-close-sessions 2.plist +0 -39
  195. package/templates/launchagents/com.nexo.catchup 2.plist +0 -39
  196. package/templates/launchagents/com.nexo.cognitive-decay 2.plist +0 -40
  197. package/templates/launchagents/com.nexo.dashboard 2.plist +0 -43
  198. package/templates/launchagents/com.nexo.deep-sleep 2.plist +0 -43
  199. package/templates/launchagents/com.nexo.evolution 2.plist +0 -44
  200. package/templates/launchagents/com.nexo.followup-hygiene 2.plist +0 -45
  201. package/templates/launchagents/com.nexo.immune 2.plist +0 -41
  202. package/templates/launchagents/com.nexo.postmortem 2.plist +0 -45
  203. package/templates/launchagents/com.nexo.self-audit 2.plist +0 -47
  204. package/templates/launchagents/com.nexo.synthesis 2.plist +0 -45
  205. package/templates/launchagents/com.nexo.watchdog 2.plist +0 -37
  206. package/templates/nexo_helper 2.py +0 -301
  207. package/templates/openclaw 2.json +0 -13
  208. package/templates/plugin-template 2.py +0 -40
  209. package/templates/script-template 2.py +0 -59
  210. package/templates/script-template 2.sh +0 -13
  211. package/templates/skill-script-template 2.py +0 -48
  212. 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 %}