synap-git 0.2.0__py3-none-any.whl
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.
- synap_git/__init__.py +5 -0
- synap_git/api/__init__.py +5 -0
- synap_git/api/app.py +111 -0
- synap_git/api/static/index.html +615 -0
- synap_git/cli/__init__.py +3 -0
- synap_git/cli/__main__.py +4 -0
- synap_git/cli/main.py +1174 -0
- synap_git/config.py +169 -0
- synap_git/diagnostics/__init__.py +0 -0
- synap_git/diagnostics/logger.py +82 -0
- synap_git/diagnostics/tracing.py +71 -0
- synap_git/embeddings/__init__.py +0 -0
- synap_git/git/__init__.py +11 -0
- synap_git/git/state.py +139 -0
- synap_git/indexer/__init__.py +0 -0
- synap_git/indexer/daemon.py +143 -0
- synap_git/indexer/engine.py +327 -0
- synap_git/indexer/scanner.py +463 -0
- synap_git/indexer/wiki.py +78 -0
- synap_git/mcp/__init__.py +5 -0
- synap_git/mcp/server.py +232 -0
- synap_git/parser/__init__.py +0 -0
- synap_git/parser/registry.py +209 -0
- synap_git/provider/anthropic.py +86 -0
- synap_git/provider/base.py +44 -0
- synap_git/provider/factory.py +101 -0
- synap_git/provider/gemini.py +98 -0
- synap_git/provider/mock.py +66 -0
- synap_git/provider/ollama.py +98 -0
- synap_git/provider/openai.py +90 -0
- synap_git/py.typed +1 -0
- synap_git/retrieval/__init__.py +0 -0
- synap_git/retrieval/engine.py +258 -0
- synap_git/retrieval/memory.py +123 -0
- synap_git/storage/__init__.py +3 -0
- synap_git/storage/sqlite.py +575 -0
- synap_git/utils/__init__.py +0 -0
- synap_git/utils/serialization.py +68 -0
- synap_git-0.2.0.dist-info/METADATA +251 -0
- synap_git-0.2.0.dist-info/RECORD +43 -0
- synap_git-0.2.0.dist-info/WHEEL +4 -0
- synap_git-0.2.0.dist-info/entry_points.txt +2 -0
- synap_git-0.2.0.dist-info/licenses/LICENSE.md +201 -0
|
@@ -0,0 +1,615 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Synap Operational Dashboard</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root {
|
|
9
|
+
--bg: #09090b;
|
|
10
|
+
--surface: #18181b;
|
|
11
|
+
--surface-hover: #27272a;
|
|
12
|
+
--border: #27272a;
|
|
13
|
+
--text-primary: #f4f4f5;
|
|
14
|
+
--text-secondary: #a1a1aa;
|
|
15
|
+
--accent: #3b82f6;
|
|
16
|
+
--success: #22c55e;
|
|
17
|
+
--warning: #eab308;
|
|
18
|
+
--danger: #ef4444;
|
|
19
|
+
--font-sans: 'Inter', -apple-system, system-ui, sans-serif;
|
|
20
|
+
--font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, monospace;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
body {
|
|
24
|
+
font-family: var(--font-sans);
|
|
25
|
+
background: var(--bg);
|
|
26
|
+
color: var(--text-primary);
|
|
27
|
+
margin: 0;
|
|
28
|
+
padding: 2rem;
|
|
29
|
+
line-height: 1.5;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
header {
|
|
33
|
+
display: flex;
|
|
34
|
+
justify-content: space-between;
|
|
35
|
+
align-items: center;
|
|
36
|
+
border-bottom: 1px solid var(--border);
|
|
37
|
+
padding-bottom: 1.5rem;
|
|
38
|
+
margin-bottom: 2rem;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
h1 { margin: 0; font-size: 1.5rem; font-weight: 600; letter-spacing: -0.025em; }
|
|
42
|
+
h2 { margin: 0 0 1rem 0; font-size: 1.125rem; font-weight: 500; color: var(--text-primary); border-bottom: 1px solid var(--border); padding-bottom: 0.5rem;}
|
|
43
|
+
|
|
44
|
+
.status-badge {
|
|
45
|
+
display: inline-flex;
|
|
46
|
+
align-items: center;
|
|
47
|
+
gap: 0.5rem;
|
|
48
|
+
padding: 0.25rem 0.75rem;
|
|
49
|
+
border-radius: 9999px;
|
|
50
|
+
font-size: 0.875rem;
|
|
51
|
+
font-weight: 500;
|
|
52
|
+
background: rgba(34, 197, 94, 0.1);
|
|
53
|
+
color: var(--success);
|
|
54
|
+
border: 1px solid rgba(34, 197, 94, 0.2);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.status-dot {
|
|
58
|
+
width: 8px;
|
|
59
|
+
height: 8px;
|
|
60
|
+
border-radius: 50%;
|
|
61
|
+
background: currentColor;
|
|
62
|
+
animation: pulse 2s infinite;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@keyframes pulse {
|
|
66
|
+
0% { opacity: 1; }
|
|
67
|
+
50% { opacity: 0.5; }
|
|
68
|
+
100% { opacity: 1; }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.grid {
|
|
72
|
+
display: grid;
|
|
73
|
+
grid-template-columns: repeat(auto-fit, minmax(380px, 1fr));
|
|
74
|
+
gap: 1.5rem;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.card {
|
|
78
|
+
background: var(--surface);
|
|
79
|
+
border: 1px solid var(--border);
|
|
80
|
+
border-radius: 0.5rem;
|
|
81
|
+
padding: 1.5rem;
|
|
82
|
+
display: flex;
|
|
83
|
+
flex-direction: column;
|
|
84
|
+
margin-bottom: 1.5rem;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.stats-grid {
|
|
88
|
+
display: grid;
|
|
89
|
+
grid-template-columns: repeat(2, 1fr);
|
|
90
|
+
gap: 1rem;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.stat-box {
|
|
94
|
+
background: var(--bg);
|
|
95
|
+
border: 1px solid var(--border);
|
|
96
|
+
padding: 1rem;
|
|
97
|
+
border-radius: 0.375rem;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.stat-value { font-size: 1.5rem; font-weight: 600; font-family: var(--font-mono); }
|
|
101
|
+
.stat-label { color: var(--text-secondary); font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.05em; margin-top: 0.25rem; }
|
|
102
|
+
|
|
103
|
+
table { width: 100%; border-collapse: collapse; font-size: 0.875rem; }
|
|
104
|
+
th, td { text-align: left; padding: 0.75rem; border-bottom: 1px solid var(--border); }
|
|
105
|
+
th { color: var(--text-secondary); font-weight: 500; }
|
|
106
|
+
|
|
107
|
+
code {
|
|
108
|
+
background: var(--bg);
|
|
109
|
+
padding: 0.2rem 0.4rem;
|
|
110
|
+
border-radius: 0.25rem;
|
|
111
|
+
font-family: var(--font-mono);
|
|
112
|
+
font-size: 0.8125rem;
|
|
113
|
+
color: var(--accent);
|
|
114
|
+
border: 1px solid var(--border);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.empty-state {
|
|
118
|
+
color: var(--text-secondary);
|
|
119
|
+
text-align: center;
|
|
120
|
+
padding: 2rem;
|
|
121
|
+
font-style: italic;
|
|
122
|
+
background: var(--bg);
|
|
123
|
+
border-radius: 0.375rem;
|
|
124
|
+
border: 1px dashed var(--border);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.row-hover:hover {
|
|
128
|
+
background: var(--bg);
|
|
129
|
+
}
|
|
130
|
+
</style>
|
|
131
|
+
</head>
|
|
132
|
+
<body>
|
|
133
|
+
<header>
|
|
134
|
+
<div>
|
|
135
|
+
<h1>Synap <span style="color: var(--text-secondary); font-weight: 400;">Operations</span></h1>
|
|
136
|
+
<p id="repo-path" style="margin: 0.25rem 0 0 0; font-size: 0.875rem; color: var(--text-secondary); font-family: var(--font-mono);">Connecting...</p>
|
|
137
|
+
</div>
|
|
138
|
+
<div class="status-badge">
|
|
139
|
+
<div class="status-dot"></div>
|
|
140
|
+
Daemon Active
|
|
141
|
+
</div>
|
|
142
|
+
</header>
|
|
143
|
+
|
|
144
|
+
<div class="grid">
|
|
145
|
+
<!-- Index Health -->
|
|
146
|
+
<div class="card">
|
|
147
|
+
<h2>State Overview</h2>
|
|
148
|
+
<div class="stats-grid">
|
|
149
|
+
<div class="stat-box">
|
|
150
|
+
<div class="stat-value" id="stat-files">-</div>
|
|
151
|
+
<div class="stat-label">Indexed Files</div>
|
|
152
|
+
</div>
|
|
153
|
+
<div class="stat-box">
|
|
154
|
+
<div class="stat-value" id="stat-symbols">-</div>
|
|
155
|
+
<div class="stat-label">Symbols Extracted</div>
|
|
156
|
+
</div>
|
|
157
|
+
<div class="stat-box">
|
|
158
|
+
<div class="stat-value" id="stat-branch">-</div>
|
|
159
|
+
<div class="stat-label">Active Branch</div>
|
|
160
|
+
</div>
|
|
161
|
+
<div class="stat-box">
|
|
162
|
+
<div class="stat-value" id="stat-commit" style="font-size: 1rem; margin-top:0.3rem;">-</div>
|
|
163
|
+
<div class="stat-label">HEAD OID</div>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
|
|
168
|
+
<!-- L3 Memory (Lessons) -->
|
|
169
|
+
<div class="card" style="grid-column: span 2;">
|
|
170
|
+
<h2>L3 Agent Memory (Lessons)</h2>
|
|
171
|
+
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem;">
|
|
172
|
+
<div>
|
|
173
|
+
<h3 style="font-size: 0.9rem; font-weight: 600; text-transform: uppercase; margin-bottom: 0.5rem; color: var(--success);">Approved (Active)</h3>
|
|
174
|
+
<table>
|
|
175
|
+
<thead>
|
|
176
|
+
<tr>
|
|
177
|
+
<th>ID</th>
|
|
178
|
+
<th>Rule / Details</th>
|
|
179
|
+
<th>Expires</th>
|
|
180
|
+
</tr>
|
|
181
|
+
</thead>
|
|
182
|
+
<tbody id="approved-lessons-body">
|
|
183
|
+
<tr><td colspan="3" class="empty-state">Loading approved lessons...</td></tr>
|
|
184
|
+
</tbody>
|
|
185
|
+
</table>
|
|
186
|
+
</div>
|
|
187
|
+
<div>
|
|
188
|
+
<h3 style="font-size: 0.9rem; font-weight: 600; text-transform: uppercase; margin-bottom: 0.5rem; color: var(--warning);">Pending Approval</h3>
|
|
189
|
+
<table>
|
|
190
|
+
<thead>
|
|
191
|
+
<tr>
|
|
192
|
+
<th>Commit</th>
|
|
193
|
+
<th>What Failed</th>
|
|
194
|
+
<th>Files</th>
|
|
195
|
+
</tr>
|
|
196
|
+
</thead>
|
|
197
|
+
<tbody id="pending-lessons-body">
|
|
198
|
+
<tr><td colspan="3" class="empty-state">Loading pending lessons...</td></tr>
|
|
199
|
+
</tbody>
|
|
200
|
+
</table>
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
|
|
205
|
+
<!-- LLM Cost & Usage -->
|
|
206
|
+
<div class="card" style="grid-column: span 2;">
|
|
207
|
+
<h2>LLM Call Cost & Usage</h2>
|
|
208
|
+
<div style="display: grid; grid-template-columns: 1fr 2fr; gap: 1.5rem;">
|
|
209
|
+
<div class="stats-grid" style="grid-template-columns: 1fr; gap: 0.75rem;">
|
|
210
|
+
<div class="stat-box">
|
|
211
|
+
<div class="stat-value" id="cost-total-usd">$0.000000</div>
|
|
212
|
+
<div class="stat-label">Total Cost (USD)</div>
|
|
213
|
+
</div>
|
|
214
|
+
<div class="stat-box">
|
|
215
|
+
<div class="stat-value" id="cost-total-calls">0</div>
|
|
216
|
+
<div class="stat-label">Total LLM Calls</div>
|
|
217
|
+
</div>
|
|
218
|
+
<div class="stat-box">
|
|
219
|
+
<div class="stat-value" id="cost-total-tokens">0</div>
|
|
220
|
+
<div class="stat-label">Total Tokens (In+Out)</div>
|
|
221
|
+
</div>
|
|
222
|
+
</div>
|
|
223
|
+
<div>
|
|
224
|
+
<h3 style="font-size: 0.9rem; font-weight: 600; text-transform: uppercase; margin-bottom: 0.5rem; color: var(--text-secondary);">Recent LLM Calls</h3>
|
|
225
|
+
<div style="max-height: 220px; overflow-y: auto;">
|
|
226
|
+
<table>
|
|
227
|
+
<thead>
|
|
228
|
+
<tr>
|
|
229
|
+
<th>Time</th>
|
|
230
|
+
<th>Provider</th>
|
|
231
|
+
<th>Model</th>
|
|
232
|
+
<th>Purpose</th>
|
|
233
|
+
<th>Tokens</th>
|
|
234
|
+
<th>Cost</th>
|
|
235
|
+
</tr>
|
|
236
|
+
</thead>
|
|
237
|
+
<tbody id="cost-calls-body">
|
|
238
|
+
<tr><td colspan="6" class="empty-state">No calls recorded.</td></tr>
|
|
239
|
+
</tbody>
|
|
240
|
+
</table>
|
|
241
|
+
</div>
|
|
242
|
+
</div>
|
|
243
|
+
</div>
|
|
244
|
+
</div>
|
|
245
|
+
|
|
246
|
+
<!-- Checkpoints -->
|
|
247
|
+
<div class="card" style="grid-column: span 2;">
|
|
248
|
+
<h2>Recent Checkpoints</h2>
|
|
249
|
+
<table>
|
|
250
|
+
<thead>
|
|
251
|
+
<tr>
|
|
252
|
+
<th>ID</th>
|
|
253
|
+
<th>Action</th>
|
|
254
|
+
<th>Branch</th>
|
|
255
|
+
<th>Time</th>
|
|
256
|
+
</tr>
|
|
257
|
+
</thead>
|
|
258
|
+
<tbody id="checkpoints-body">
|
|
259
|
+
<tr><td colspan="4" class="empty-state">Loading checkpoints...</td></tr>
|
|
260
|
+
</tbody>
|
|
261
|
+
</table>
|
|
262
|
+
</div>
|
|
263
|
+
|
|
264
|
+
<!-- Latest Operational Trace -->
|
|
265
|
+
<div class="card" style="grid-column: span 2;" id="trace-card">
|
|
266
|
+
<h2>Latest Operational Trace</h2>
|
|
267
|
+
<div id="trace-info" style="display: none;">
|
|
268
|
+
<div style="display: flex; justify-content: space-between; margin-bottom: 1rem; border-bottom: 1px solid var(--border); padding-bottom: 0.75rem;">
|
|
269
|
+
<div>
|
|
270
|
+
<strong style="color: var(--accent);">Type:</strong> <span id="trace-type" style="text-transform: uppercase; font-weight: 500; font-family: var(--font-mono); color: var(--accent);">-</span>
|
|
271
|
+
<span style="margin-left: 1.5rem;"><strong style="color: var(--text-secondary);">ID:</strong> <code id="trace-id" style="font-size: 0.75rem;">-</code></span>
|
|
272
|
+
<span style="margin-left: 1.5rem;"><strong style="color: var(--text-secondary);">Time:</strong> <span id="trace-time" style="font-family: var(--font-mono);">-</span></span>
|
|
273
|
+
</div>
|
|
274
|
+
<div>
|
|
275
|
+
<span id="trace-dirty-warning" class="status-badge" style="background: rgba(234, 179, 8, 0.1); color: var(--warning); border: 1px solid rgba(234, 179, 8, 0.2); display: none;">
|
|
276
|
+
<div class="status-dot" style="background: var(--warning);"></div> Dirty Tree
|
|
277
|
+
</span>
|
|
278
|
+
</div>
|
|
279
|
+
</div>
|
|
280
|
+
|
|
281
|
+
<p style="margin: 0 0 1rem 0; font-weight: 500; font-size: 1rem; color: var(--text-primary);" id="trace-summary">-</p>
|
|
282
|
+
|
|
283
|
+
<!-- Retrieval Specific Details -->
|
|
284
|
+
<div id="retrieval-details" style="display: none;">
|
|
285
|
+
<div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem; margin-bottom: 1.5rem;">
|
|
286
|
+
<div class="stat-box">
|
|
287
|
+
<div class="stat-value" id="trace-retrieval-nodes">-</div>
|
|
288
|
+
<div class="stat-label">Nodes Explored</div>
|
|
289
|
+
</div>
|
|
290
|
+
<div class="stat-box">
|
|
291
|
+
<div class="stat-value" id="trace-retrieval-tokens">-</div>
|
|
292
|
+
<div class="stat-label">Tokens Used / Budget</div>
|
|
293
|
+
</div>
|
|
294
|
+
<div class="stat-box">
|
|
295
|
+
<div class="stat-value" id="trace-retrieval-latency">-</div>
|
|
296
|
+
<div class="stat-label">Latency (ms)</div>
|
|
297
|
+
</div>
|
|
298
|
+
<div class="stat-box">
|
|
299
|
+
<div class="stat-value" id="trace-retrieval-budget-pct">-</div>
|
|
300
|
+
<div class="stat-label">Budget Utilized</div>
|
|
301
|
+
</div>
|
|
302
|
+
</div>
|
|
303
|
+
|
|
304
|
+
<h3 style="font-size: 0.9rem; font-weight: 600; text-transform: uppercase; margin-bottom: 0.5rem; color: var(--text-secondary);">Phase Latencies (Timeline)</h3>
|
|
305
|
+
<div style="display: flex; gap: 4px; height: 16px; background: var(--border); border-radius: 4px; overflow: hidden; margin-bottom: 0.5rem;" id="timeline-bar">
|
|
306
|
+
<!-- timeline segments filled dynamically -->
|
|
307
|
+
</div>
|
|
308
|
+
<div id="timeline-labels" style="display: grid; grid-template-columns: repeat(5, 1fr); gap: 0.5rem; font-size: 0.75rem; color: var(--text-secondary); margin-bottom: 1.5rem;">
|
|
309
|
+
<!-- timeline labels filled dynamically -->
|
|
310
|
+
</div>
|
|
311
|
+
|
|
312
|
+
<h3 style="font-size: 0.9rem; font-weight: 600; text-transform: uppercase; margin-bottom: 0.5rem; color: var(--text-secondary);">Structural Traversal (Hops)</h3>
|
|
313
|
+
<div id="structural-hops-container" style="margin-bottom: 1.5rem; font-family: var(--font-mono); font-size: 0.75rem; max-height: 150px; overflow-y: auto; background: var(--bg); border: 1px solid var(--border); padding: 0.75rem; border-radius: 0.375rem;">
|
|
314
|
+
<!-- structural hops list -->
|
|
315
|
+
</div>
|
|
316
|
+
|
|
317
|
+
<h3 style="font-size: 0.9rem; font-weight: 600; text-transform: uppercase; margin-bottom: 0.5rem; color: var(--text-secondary);">Grounding Source Provenance & Truncation</h3>
|
|
318
|
+
<table style="font-size: 0.75rem;">
|
|
319
|
+
<thead>
|
|
320
|
+
<tr>
|
|
321
|
+
<th>Symbol</th>
|
|
322
|
+
<th>Path</th>
|
|
323
|
+
<th>Score</th>
|
|
324
|
+
<th>Tokens</th>
|
|
325
|
+
<th>Status / Reason</th>
|
|
326
|
+
</tr>
|
|
327
|
+
</thead>
|
|
328
|
+
<tbody id="provenance-body">
|
|
329
|
+
<!-- filled dynamically -->
|
|
330
|
+
</tbody>
|
|
331
|
+
</table>
|
|
332
|
+
</div>
|
|
333
|
+
|
|
334
|
+
<!-- Generic/Other Details (JSON view) -->
|
|
335
|
+
<div id="generic-details" style="display: none;">
|
|
336
|
+
<pre id="generic-json" style="background: var(--bg); border: 1px solid var(--border); padding: 1rem; border-radius: 0.375rem; font-family: var(--font-mono); font-size: 0.8125rem; overflow-x: auto; max-height: 300px; color: var(--text-secondary);"></pre>
|
|
337
|
+
</div>
|
|
338
|
+
</div>
|
|
339
|
+
<div id="trace-empty" class="empty-state">
|
|
340
|
+
No operational traces recorded. Run queries or tool invocations to populate.
|
|
341
|
+
</div>
|
|
342
|
+
</div>
|
|
343
|
+
</div>
|
|
344
|
+
|
|
345
|
+
<script>
|
|
346
|
+
function timeAgo(timestamp) {
|
|
347
|
+
const seconds = Math.floor((new Date() - new Date(timestamp * 1000)) / 1000);
|
|
348
|
+
if (seconds < 60) return `${seconds}s ago`;
|
|
349
|
+
if (seconds < 3600) return `${Math.floor(seconds/60)}m ago`;
|
|
350
|
+
return `${Math.floor(seconds/3600)}h ago`;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
async function updateDashboard() {
|
|
354
|
+
try {
|
|
355
|
+
// Fetch Status
|
|
356
|
+
const statusRes = await fetch('/api/v1/status');
|
|
357
|
+
const status = await statusRes.json();
|
|
358
|
+
|
|
359
|
+
document.getElementById('repo-path').textContent = status.repository_path;
|
|
360
|
+
document.getElementById('stat-files').textContent = status.files.toLocaleString();
|
|
361
|
+
document.getElementById('stat-symbols').textContent = status.symbols.toLocaleString();
|
|
362
|
+
document.getElementById('stat-branch').textContent = status.branch;
|
|
363
|
+
document.getElementById('stat-commit').innerHTML = `<code>${status.git_commit?.substring(0,7) || 'unknown'}</code>`;
|
|
364
|
+
|
|
365
|
+
// Update Daemon Status Badge
|
|
366
|
+
const daemonBadge = document.querySelector('.status-badge');
|
|
367
|
+
if (status.daemon) {
|
|
368
|
+
const statusStr = status.daemon.status.toUpperCase();
|
|
369
|
+
if (statusStr === 'HEALTHY') {
|
|
370
|
+
daemonBadge.style.color = 'var(--success)';
|
|
371
|
+
daemonBadge.style.background = 'rgba(34, 197, 94, 0.1)';
|
|
372
|
+
daemonBadge.style.borderColor = 'rgba(34, 197, 94, 0.2)';
|
|
373
|
+
daemonBadge.innerHTML = `<div class="status-dot"></div> Daemon Active (PID ${status.daemon.pid})`;
|
|
374
|
+
} else if (statusStr === 'STALE') {
|
|
375
|
+
daemonBadge.style.color = 'var(--warning)';
|
|
376
|
+
daemonBadge.style.background = 'rgba(234, 179, 8, 0.1)';
|
|
377
|
+
daemonBadge.style.borderColor = 'rgba(234, 179, 8, 0.2)';
|
|
378
|
+
daemonBadge.innerHTML = `<div class="status-dot" style="background: var(--warning);"></div> Daemon Stale (PID ${status.daemon.pid})`;
|
|
379
|
+
} else {
|
|
380
|
+
daemonBadge.style.color = 'var(--danger)';
|
|
381
|
+
daemonBadge.style.background = 'rgba(239, 68, 68, 0.1)';
|
|
382
|
+
daemonBadge.style.borderColor = 'rgba(239, 68, 68, 0.2)';
|
|
383
|
+
daemonBadge.innerHTML = `<div class="status-dot" style="background: var(--danger);"></div> Daemon Degraded`;
|
|
384
|
+
}
|
|
385
|
+
} else {
|
|
386
|
+
daemonBadge.style.color = 'var(--text-secondary)';
|
|
387
|
+
daemonBadge.style.background = 'rgba(161, 161, 170, 0.1)';
|
|
388
|
+
daemonBadge.style.borderColor = 'rgba(161, 161, 170, 0.2)';
|
|
389
|
+
daemonBadge.innerHTML = `<div class="status-dot" style="background: var(--text-secondary); animation: none;"></div> Daemon Inactive`;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Fetch Checkpoints
|
|
393
|
+
const cpRes = await fetch('/api/v1/checkpoints');
|
|
394
|
+
const cpData = await cpRes.json();
|
|
395
|
+
const cpBody = document.getElementById('checkpoints-body');
|
|
396
|
+
if (cpData.checkpoints && cpData.checkpoints.length > 0) {
|
|
397
|
+
cpBody.innerHTML = cpData.checkpoints.map(cp => `
|
|
398
|
+
<tr class="row-hover">
|
|
399
|
+
<td><code>${cp.checkpoint_id.split('-')[0]}</code></td>
|
|
400
|
+
<td>${cp.doing}</td>
|
|
401
|
+
<td><span style="border:1px solid var(--border); padding:2px 6px; border-radius:4px; font-size:11px;">${cp.branch}</span></td>
|
|
402
|
+
<td style="color:var(--text-secondary);">${timeAgo(cp.created_at)}</td>
|
|
403
|
+
</tr>
|
|
404
|
+
`).join('');
|
|
405
|
+
} else {
|
|
406
|
+
cpBody.innerHTML = `<tr><td colspan="4" class="empty-state">No checkpoints recorded yet.</td></tr>`;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Fetch Lessons
|
|
410
|
+
const memRes = await fetch('/api/v1/memory');
|
|
411
|
+
const memData = await memRes.json();
|
|
412
|
+
|
|
413
|
+
const approvedBody = document.getElementById('approved-lessons-body');
|
|
414
|
+
if (memData.approved && memData.approved.length > 0) {
|
|
415
|
+
approvedBody.innerHTML = memData.approved.map(l => {
|
|
416
|
+
const expires = new Date(l.expires_at * 1000).toLocaleDateString();
|
|
417
|
+
return `
|
|
418
|
+
<tr class="row-hover">
|
|
419
|
+
<td><code>${l.lesson_id.substring(0,8)}</code></td>
|
|
420
|
+
<td><span style="font-weight:500;">Failed:</span> ${l.what_failed}<br/><span style="color:var(--text-secondary); font-size:11px;">Why: ${l.why_failed}</span></td>
|
|
421
|
+
<td style="color:var(--text-secondary);">${expires}</td>
|
|
422
|
+
</tr>
|
|
423
|
+
`;
|
|
424
|
+
}).join('');
|
|
425
|
+
} else {
|
|
426
|
+
approvedBody.innerHTML = `<tr><td colspan="3" class="empty-state">No approved rules in memory.</td></tr>`;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const pendingBody = document.getElementById('pending-lessons-body');
|
|
430
|
+
if (memData.pending && memData.pending.length > 0) {
|
|
431
|
+
pendingBody.innerHTML = memData.pending.map(l => {
|
|
432
|
+
let files = [];
|
|
433
|
+
try { files = JSON.parse(l.files_affected); } catch(e){}
|
|
434
|
+
return `
|
|
435
|
+
<tr class="row-hover">
|
|
436
|
+
<td><code>${l.revert_commit.substring(0,7)}</code></td>
|
|
437
|
+
<td>${l.what_failed}</td>
|
|
438
|
+
<td>${files.length} file(s)</td>
|
|
439
|
+
</tr>
|
|
440
|
+
`;
|
|
441
|
+
}).join('');
|
|
442
|
+
} else {
|
|
443
|
+
pendingBody.innerHTML = `<tr><td colspan="3" class="empty-state">No pending lessons needing approval.</td></tr>`;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Fetch Cost & Usage
|
|
447
|
+
const costRes = await fetch('/api/v1/cost');
|
|
448
|
+
const costData = await costRes.json();
|
|
449
|
+
const costBody = document.getElementById('cost-calls-body');
|
|
450
|
+
|
|
451
|
+
let totalCost = 0;
|
|
452
|
+
let totalCalls = 0;
|
|
453
|
+
let totalTokens = 0;
|
|
454
|
+
|
|
455
|
+
if (costData.calls && costData.calls.length > 0) {
|
|
456
|
+
totalCalls = costData.calls.length;
|
|
457
|
+
costBody.innerHTML = costData.calls.slice(0, 15).map(c => {
|
|
458
|
+
totalCost += c.cost_usd;
|
|
459
|
+
const tokens = c.input_tokens + c.output_tokens;
|
|
460
|
+
totalTokens += tokens;
|
|
461
|
+
|
|
462
|
+
const timeStr = new Date(c.created_at * 1000).toLocaleTimeString();
|
|
463
|
+
return `
|
|
464
|
+
<tr class="row-hover">
|
|
465
|
+
<td>${timeStr}</td>
|
|
466
|
+
<td>${c.provider}</td>
|
|
467
|
+
<td><code>${c.model}</code></td>
|
|
468
|
+
<td>${c.purpose}</td>
|
|
469
|
+
<td>${tokens.toLocaleString()}</td>
|
|
470
|
+
<td style="color:var(--success); font-family:var(--font-mono); font-size:11px;">$${c.cost_usd.toFixed(6)}</td>
|
|
471
|
+
</tr>
|
|
472
|
+
`;
|
|
473
|
+
}).join('');
|
|
474
|
+
|
|
475
|
+
costData.calls.slice(15).forEach(c => {
|
|
476
|
+
totalCost += c.cost_usd;
|
|
477
|
+
totalTokens += (c.input_tokens + c.output_tokens);
|
|
478
|
+
});
|
|
479
|
+
} else {
|
|
480
|
+
costBody.innerHTML = `<tr><td colspan="6" class="empty-state">No LLM calls recorded yet.</td></tr>`;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
document.getElementById('cost-total-usd').textContent = `$${totalCost.toFixed(6)}`;
|
|
484
|
+
document.getElementById('cost-total-calls').textContent = totalCalls.toLocaleString();
|
|
485
|
+
document.getElementById('cost-total-tokens').textContent = totalTokens.toLocaleString();
|
|
486
|
+
|
|
487
|
+
// Fetch Latest Trace
|
|
488
|
+
const traceRes = await fetch('/api/v1/trace/latest');
|
|
489
|
+
const trace = await traceRes.json();
|
|
490
|
+
const traceInfo = document.getElementById('trace-info');
|
|
491
|
+
const traceEmpty = document.getElementById('trace-empty');
|
|
492
|
+
|
|
493
|
+
if (trace && trace.trace_id && trace.trace_id !== 'none' && trace.trace_id !== 'error') {
|
|
494
|
+
traceEmpty.style.display = 'none';
|
|
495
|
+
traceInfo.style.display = 'block';
|
|
496
|
+
|
|
497
|
+
document.getElementById('trace-type').textContent = trace.type;
|
|
498
|
+
document.getElementById('trace-id').textContent = trace.trace_id;
|
|
499
|
+
document.getElementById('trace-time').textContent = new Date(trace.timestamp).toLocaleTimeString();
|
|
500
|
+
document.getElementById('trace-summary').textContent = trace.summary;
|
|
501
|
+
|
|
502
|
+
if (trace.type === 'retrieval' && trace.details) {
|
|
503
|
+
document.getElementById('retrieval-details').style.display = 'block';
|
|
504
|
+
document.getElementById('generic-details').style.display = 'none';
|
|
505
|
+
|
|
506
|
+
const details = trace.details;
|
|
507
|
+
|
|
508
|
+
const dirtyWarning = document.getElementById('trace-dirty-warning');
|
|
509
|
+
if (details.dirty_tree_warning) {
|
|
510
|
+
dirtyWarning.style.display = 'inline-flex';
|
|
511
|
+
} else {
|
|
512
|
+
dirtyWarning.style.display = 'none';
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
document.getElementById('trace-retrieval-nodes').textContent = details.nodes_explored;
|
|
516
|
+
|
|
517
|
+
const alloc = details.token_allocation || {};
|
|
518
|
+
const used = details.tokens_used || 0;
|
|
519
|
+
const budget = alloc.retrieval_budget || 3400;
|
|
520
|
+
document.getElementById('trace-retrieval-tokens').textContent = `${used} / ${budget}`;
|
|
521
|
+
|
|
522
|
+
const lat = details.latency_ms || 0;
|
|
523
|
+
document.getElementById('trace-retrieval-latency').textContent = lat.toFixed(1);
|
|
524
|
+
|
|
525
|
+
const pct = budget > 0 ? (used / budget * 100).toFixed(1) : 0;
|
|
526
|
+
document.getElementById('trace-retrieval-budget-pct').textContent = `${pct}%`;
|
|
527
|
+
|
|
528
|
+
const tl = details.timeline || {};
|
|
529
|
+
const phases = [
|
|
530
|
+
{ key: 'lexical_search_ms', name: 'Lexical', color: '#3b82f6' },
|
|
531
|
+
{ key: 'structural_expansion_ms', name: 'Structural', color: '#8b5cf6' },
|
|
532
|
+
{ key: 'semantic_ranking_ms', name: 'Semantic', color: '#ec4899' },
|
|
533
|
+
{ key: 'context_packing_ms', name: 'Packing', color: '#f59e0b' },
|
|
534
|
+
{ key: 'llm_generation_ms', name: 'LLM Gen', color: '#10b981' }
|
|
535
|
+
];
|
|
536
|
+
|
|
537
|
+
const totalMs = tl.total_ms || 1;
|
|
538
|
+
const barContainer = document.getElementById('timeline-bar');
|
|
539
|
+
const labelContainer = document.getElementById('timeline-labels');
|
|
540
|
+
|
|
541
|
+
barContainer.innerHTML = '';
|
|
542
|
+
labelContainer.innerHTML = '';
|
|
543
|
+
|
|
544
|
+
phases.forEach(phase => {
|
|
545
|
+
const val = tl[phase.key] || 0;
|
|
546
|
+
const share = (val / totalMs * 100);
|
|
547
|
+
|
|
548
|
+
if (val > 0) {
|
|
549
|
+
const segment = document.createElement('div');
|
|
550
|
+
segment.style.width = `${share}%`;
|
|
551
|
+
segment.style.backgroundColor = phase.color;
|
|
552
|
+
segment.title = `${phase.name}: ${val.toFixed(1)} ms`;
|
|
553
|
+
barContainer.appendChild(segment);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
const label = document.createElement('div');
|
|
557
|
+
label.innerHTML = `<span style="display:inline-block; width:8px; height:8px; border-radius:50%; background-color:${phase.color}; margin-right:4px;"></span>${phase.name}: ${val.toFixed(1)}ms`;
|
|
558
|
+
labelContainer.appendChild(label);
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
const hops = details.structural_hops || [];
|
|
562
|
+
const hopsContainer = document.getElementById('structural-hops-container');
|
|
563
|
+
if (hops.length > 0) {
|
|
564
|
+
hopsContainer.innerHTML = hops.map(h =>
|
|
565
|
+
`<div style="margin-bottom: 2px;">• <span style="color:var(--accent);">${h.from_symbol}</span> ➔ <span style="color:var(--success);">${h.to_symbol}</span> <span style="color:var(--text-secondary); font-size:10px;">(dist: ${h.distance})</span></div>`
|
|
566
|
+
).join('');
|
|
567
|
+
} else {
|
|
568
|
+
hopsContainer.innerHTML = '<div class="empty-state" style="padding:0.5rem; font-size:11px;">No hops navigated</div>';
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
const elements = details.elements || [];
|
|
572
|
+
const provBody = document.getElementById('provenance-body');
|
|
573
|
+
if (elements.length > 0) {
|
|
574
|
+
provBody.innerHTML = elements.map(el => {
|
|
575
|
+
const isTruncated = el.reason && el.reason.startsWith('truncated');
|
|
576
|
+
const statusHtml = isTruncated
|
|
577
|
+
? `<span style="color:var(--danger); font-weight:500;">Truncated</span><br/><span style="color:var(--text-secondary); font-size:10px;">${el.reason}</span>`
|
|
578
|
+
: `<span style="color:var(--success); font-weight:500;">Packed</span><br/><span style="color:var(--text-secondary); font-size:10px;">${el.reason}</span>`;
|
|
579
|
+
|
|
580
|
+
return `<tr class="row-hover" style="${isTruncated ? 'opacity: 0.6;' : ''}">
|
|
581
|
+
<td><code>${el.name}</code></td>
|
|
582
|
+
<td style="font-family:var(--font-mono); font-size:11px;">${el.path}</td>
|
|
583
|
+
<td>${el.score ? el.score.toFixed(2) : '-'}</td>
|
|
584
|
+
<td>${el.tokens}</td>
|
|
585
|
+
<td>${statusHtml}</td>
|
|
586
|
+
</tr>`;
|
|
587
|
+
}).join('');
|
|
588
|
+
} else {
|
|
589
|
+
provBody.innerHTML = `<tr><td colspan="5" class="empty-state">No elements in trace.</td></tr>`;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
} else {
|
|
593
|
+
document.getElementById('retrieval-details').style.display = 'none';
|
|
594
|
+
document.getElementById('generic-details').style.display = 'block';
|
|
595
|
+
document.getElementById('generic-json').textContent = JSON.stringify(trace.details, null, 2);
|
|
596
|
+
}
|
|
597
|
+
} else {
|
|
598
|
+
traceEmpty.style.display = 'block';
|
|
599
|
+
traceInfo.style.display = 'none';
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
} catch (e) {
|
|
603
|
+
console.error("Dashboard update failed:", e);
|
|
604
|
+
document.querySelector('.status-badge').style.color = 'var(--danger)';
|
|
605
|
+
document.querySelector('.status-badge').style.background = 'rgba(239, 68, 68, 0.1)';
|
|
606
|
+
document.querySelector('.status-badge').style.borderColor = 'rgba(239, 68, 68, 0.2)';
|
|
607
|
+
document.querySelector('.status-badge').innerHTML = '<div class="status-dot"></div> Connection Lost';
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
updateDashboard();
|
|
612
|
+
setInterval(updateDashboard, 5000);
|
|
613
|
+
</script>
|
|
614
|
+
</body>
|
|
615
|
+
</html>
|