universal-agent-context 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.
- uacs/__init__.py +12 -0
- uacs/adapters/__init__.py +19 -0
- uacs/adapters/agent_skill_adapter.py +202 -0
- uacs/adapters/agents_md_adapter.py +330 -0
- uacs/adapters/base.py +261 -0
- uacs/adapters/clinerules_adapter.py +39 -0
- uacs/adapters/cursorrules_adapter.py +39 -0
- uacs/api.py +262 -0
- uacs/cli/__init__.py +6 -0
- uacs/cli/context.py +349 -0
- uacs/cli/main.py +195 -0
- uacs/cli/mcp.py +115 -0
- uacs/cli/memory.py +142 -0
- uacs/cli/packages.py +309 -0
- uacs/cli/skills.py +144 -0
- uacs/cli/utils.py +24 -0
- uacs/config/repositories.yaml +26 -0
- uacs/context/__init__.py +0 -0
- uacs/context/agent_context.py +406 -0
- uacs/context/shared_context.py +661 -0
- uacs/context/unified_context.py +332 -0
- uacs/mcp_server_entry.py +80 -0
- uacs/memory/__init__.py +5 -0
- uacs/memory/simple_memory.py +255 -0
- uacs/packages/__init__.py +26 -0
- uacs/packages/manager.py +413 -0
- uacs/packages/models.py +60 -0
- uacs/packages/sources.py +270 -0
- uacs/protocols/__init__.py +5 -0
- uacs/protocols/mcp/__init__.py +8 -0
- uacs/protocols/mcp/manager.py +77 -0
- uacs/protocols/mcp/skills_server.py +700 -0
- uacs/skills_validator.py +367 -0
- uacs/utils/__init__.py +5 -0
- uacs/utils/paths.py +24 -0
- uacs/visualization/README.md +132 -0
- uacs/visualization/__init__.py +36 -0
- uacs/visualization/models.py +195 -0
- uacs/visualization/static/index.html +857 -0
- uacs/visualization/storage.py +402 -0
- uacs/visualization/visualization.py +328 -0
- uacs/visualization/web_server.py +364 -0
- universal_agent_context-0.2.0.dist-info/METADATA +873 -0
- universal_agent_context-0.2.0.dist-info/RECORD +47 -0
- universal_agent_context-0.2.0.dist-info/WHEEL +4 -0
- universal_agent_context-0.2.0.dist-info/entry_points.txt +2 -0
- universal_agent_context-0.2.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,857 @@
|
|
|
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>UACS Context Visualizer</title>
|
|
7
|
+
<script src="https://d3js.org/d3.v7.min.js"></script>
|
|
8
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.js"></script>
|
|
9
|
+
<style>
|
|
10
|
+
* {
|
|
11
|
+
margin: 0;
|
|
12
|
+
padding: 0;
|
|
13
|
+
box-sizing: border-box;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
body {
|
|
17
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
18
|
+
background: #0f0f23;
|
|
19
|
+
color: #cccccc;
|
|
20
|
+
overflow: hidden;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.header {
|
|
24
|
+
background: #1a1a2e;
|
|
25
|
+
padding: 20px;
|
|
26
|
+
border-bottom: 2px solid #16213e;
|
|
27
|
+
display: flex;
|
|
28
|
+
justify-content: space-between;
|
|
29
|
+
align-items: center;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.header h1 {
|
|
33
|
+
color: #00d4ff;
|
|
34
|
+
font-size: 24px;
|
|
35
|
+
font-weight: 600;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.header .status {
|
|
39
|
+
display: flex;
|
|
40
|
+
align-items: center;
|
|
41
|
+
gap: 10px;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.status-indicator {
|
|
45
|
+
width: 10px;
|
|
46
|
+
height: 10px;
|
|
47
|
+
border-radius: 50%;
|
|
48
|
+
background: #ff4444;
|
|
49
|
+
animation: pulse 2s infinite;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.status-indicator.connected {
|
|
53
|
+
background: #44ff44;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@keyframes pulse {
|
|
57
|
+
0%, 100% { opacity: 1; }
|
|
58
|
+
50% { opacity: 0.5; }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.nav {
|
|
62
|
+
background: #16213e;
|
|
63
|
+
padding: 15px 20px;
|
|
64
|
+
display: flex;
|
|
65
|
+
gap: 15px;
|
|
66
|
+
border-bottom: 1px solid #0f0f23;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.nav-button {
|
|
70
|
+
background: transparent;
|
|
71
|
+
border: 2px solid #00d4ff;
|
|
72
|
+
color: #00d4ff;
|
|
73
|
+
padding: 10px 20px;
|
|
74
|
+
border-radius: 5px;
|
|
75
|
+
cursor: pointer;
|
|
76
|
+
font-size: 14px;
|
|
77
|
+
font-weight: 500;
|
|
78
|
+
transition: all 0.3s;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.nav-button:hover {
|
|
82
|
+
background: #00d4ff;
|
|
83
|
+
color: #0f0f23;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.nav-button.active {
|
|
87
|
+
background: #00d4ff;
|
|
88
|
+
color: #0f0f23;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.container {
|
|
92
|
+
display: flex;
|
|
93
|
+
height: calc(100vh - 130px);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.main-view {
|
|
97
|
+
flex: 1;
|
|
98
|
+
padding: 20px;
|
|
99
|
+
overflow: auto;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.sidebar {
|
|
103
|
+
width: 350px;
|
|
104
|
+
background: #1a1a2e;
|
|
105
|
+
padding: 20px;
|
|
106
|
+
border-left: 2px solid #16213e;
|
|
107
|
+
overflow: auto;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.view {
|
|
111
|
+
display: none;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.view.active {
|
|
115
|
+
display: block;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.card {
|
|
119
|
+
background: #1a1a2e;
|
|
120
|
+
border: 1px solid #16213e;
|
|
121
|
+
border-radius: 8px;
|
|
122
|
+
padding: 20px;
|
|
123
|
+
margin-bottom: 20px;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.card h3 {
|
|
127
|
+
color: #00d4ff;
|
|
128
|
+
margin-bottom: 15px;
|
|
129
|
+
font-size: 18px;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.stat-grid {
|
|
133
|
+
display: grid;
|
|
134
|
+
grid-template-columns: repeat(2, 1fr);
|
|
135
|
+
gap: 15px;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.stat-item {
|
|
139
|
+
background: #0f0f23;
|
|
140
|
+
padding: 15px;
|
|
141
|
+
border-radius: 5px;
|
|
142
|
+
border-left: 3px solid #00d4ff;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.stat-label {
|
|
146
|
+
color: #888888;
|
|
147
|
+
font-size: 12px;
|
|
148
|
+
margin-bottom: 5px;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.stat-value {
|
|
152
|
+
color: #00d4ff;
|
|
153
|
+
font-size: 24px;
|
|
154
|
+
font-weight: 600;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
#graph-container {
|
|
158
|
+
width: 100%;
|
|
159
|
+
height: 600px;
|
|
160
|
+
background: #1a1a2e;
|
|
161
|
+
border-radius: 8px;
|
|
162
|
+
border: 1px solid #16213e;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.node {
|
|
166
|
+
cursor: pointer;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.node circle {
|
|
170
|
+
fill: #00d4ff;
|
|
171
|
+
stroke: #16213e;
|
|
172
|
+
stroke-width: 2px;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.node.summary circle {
|
|
176
|
+
fill: #ff6b35;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.node text {
|
|
180
|
+
fill: #cccccc;
|
|
181
|
+
font-size: 12px;
|
|
182
|
+
pointer-events: none;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.link {
|
|
186
|
+
stroke: #16213e;
|
|
187
|
+
stroke-opacity: 0.6;
|
|
188
|
+
stroke-width: 2px;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.chart-container {
|
|
192
|
+
position: relative;
|
|
193
|
+
height: 300px;
|
|
194
|
+
margin-top: 20px;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.topic-cluster {
|
|
198
|
+
background: #0f0f23;
|
|
199
|
+
padding: 10px 15px;
|
|
200
|
+
border-radius: 5px;
|
|
201
|
+
margin-bottom: 10px;
|
|
202
|
+
border-left: 3px solid #00d4ff;
|
|
203
|
+
display: flex;
|
|
204
|
+
justify-content: space-between;
|
|
205
|
+
align-items: center;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.topic-name {
|
|
209
|
+
color: #cccccc;
|
|
210
|
+
font-size: 14px;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.topic-count {
|
|
214
|
+
color: #00d4ff;
|
|
215
|
+
font-weight: 600;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.heatmap-cell {
|
|
219
|
+
fill: #1a1a2e;
|
|
220
|
+
stroke: #0f0f23;
|
|
221
|
+
stroke-width: 2;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.heatmap-cell:hover {
|
|
225
|
+
stroke: #00d4ff;
|
|
226
|
+
stroke-width: 3;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.empty-state {
|
|
230
|
+
text-align: center;
|
|
231
|
+
padding: 60px 20px;
|
|
232
|
+
color: #666666;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.empty-state svg {
|
|
236
|
+
width: 100px;
|
|
237
|
+
height: 100px;
|
|
238
|
+
margin-bottom: 20px;
|
|
239
|
+
opacity: 0.3;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.loading {
|
|
243
|
+
display: flex;
|
|
244
|
+
align-items: center;
|
|
245
|
+
justify-content: center;
|
|
246
|
+
height: 100%;
|
|
247
|
+
font-size: 18px;
|
|
248
|
+
color: #666666;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.metric-bar {
|
|
252
|
+
height: 40px;
|
|
253
|
+
background: #0f0f23;
|
|
254
|
+
border-radius: 5px;
|
|
255
|
+
overflow: hidden;
|
|
256
|
+
margin-bottom: 15px;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.metric-bar-fill {
|
|
260
|
+
height: 100%;
|
|
261
|
+
background: linear-gradient(90deg, #00d4ff, #0088ff);
|
|
262
|
+
display: flex;
|
|
263
|
+
align-items: center;
|
|
264
|
+
padding: 0 15px;
|
|
265
|
+
color: white;
|
|
266
|
+
font-weight: 600;
|
|
267
|
+
font-size: 14px;
|
|
268
|
+
transition: width 0.5s ease;
|
|
269
|
+
}
|
|
270
|
+
</style>
|
|
271
|
+
</head>
|
|
272
|
+
<body>
|
|
273
|
+
<div class="header">
|
|
274
|
+
<h1>UACS Context Visualizer</h1>
|
|
275
|
+
<div class="status">
|
|
276
|
+
<div class="status-indicator" id="statusIndicator"></div>
|
|
277
|
+
<span id="statusText">Connecting...</span>
|
|
278
|
+
</div>
|
|
279
|
+
</div>
|
|
280
|
+
|
|
281
|
+
<div class="nav">
|
|
282
|
+
<button class="nav-button active" data-view="conversation">Conversation Flow</button>
|
|
283
|
+
<button class="nav-button" data-view="dashboard">Token Dashboard</button>
|
|
284
|
+
<button class="nav-button" data-view="deduplication">Deduplication</button>
|
|
285
|
+
<button class="nav-button" data-view="quality">Quality Distribution</button>
|
|
286
|
+
<button class="nav-button" data-view="topics">Topic Clusters</button>
|
|
287
|
+
</div>
|
|
288
|
+
|
|
289
|
+
<div class="container">
|
|
290
|
+
<div class="main-view">
|
|
291
|
+
<!-- Conversation Flow View -->
|
|
292
|
+
<div class="view active" id="conversation-view">
|
|
293
|
+
<div id="graph-container"></div>
|
|
294
|
+
</div>
|
|
295
|
+
|
|
296
|
+
<!-- Token Dashboard View -->
|
|
297
|
+
<div class="view" id="dashboard-view">
|
|
298
|
+
<div class="card">
|
|
299
|
+
<h3>Token Usage Overview</h3>
|
|
300
|
+
<div class="chart-container">
|
|
301
|
+
<canvas id="tokenChart"></canvas>
|
|
302
|
+
</div>
|
|
303
|
+
</div>
|
|
304
|
+
<div class="card">
|
|
305
|
+
<h3>Compression Efficiency</h3>
|
|
306
|
+
<div class="chart-container">
|
|
307
|
+
<canvas id="compressionChart"></canvas>
|
|
308
|
+
</div>
|
|
309
|
+
</div>
|
|
310
|
+
</div>
|
|
311
|
+
|
|
312
|
+
<!-- Deduplication View -->
|
|
313
|
+
<div class="view" id="deduplication-view">
|
|
314
|
+
<div class="card">
|
|
315
|
+
<h3>Deduplication Statistics</h3>
|
|
316
|
+
<div id="dedupStats"></div>
|
|
317
|
+
</div>
|
|
318
|
+
<div class="card">
|
|
319
|
+
<h3>Duplicate Content Heatmap</h3>
|
|
320
|
+
<div id="dedupHeatmap"></div>
|
|
321
|
+
</div>
|
|
322
|
+
</div>
|
|
323
|
+
|
|
324
|
+
<!-- Quality Distribution View -->
|
|
325
|
+
<div class="view" id="quality-view">
|
|
326
|
+
<div class="card">
|
|
327
|
+
<h3>Content Quality Distribution</h3>
|
|
328
|
+
<div class="chart-container">
|
|
329
|
+
<canvas id="qualityChart"></canvas>
|
|
330
|
+
</div>
|
|
331
|
+
</div>
|
|
332
|
+
<div class="card">
|
|
333
|
+
<h3>Quality Metrics</h3>
|
|
334
|
+
<div id="qualityMetrics"></div>
|
|
335
|
+
</div>
|
|
336
|
+
</div>
|
|
337
|
+
|
|
338
|
+
<!-- Topic Clusters View -->
|
|
339
|
+
<div class="view" id="topics-view">
|
|
340
|
+
<div class="card">
|
|
341
|
+
<h3>Topic Network</h3>
|
|
342
|
+
<div id="topicGraph" style="height: 500px;"></div>
|
|
343
|
+
</div>
|
|
344
|
+
</div>
|
|
345
|
+
</div>
|
|
346
|
+
|
|
347
|
+
<div class="sidebar">
|
|
348
|
+
<div class="card">
|
|
349
|
+
<h3>Statistics</h3>
|
|
350
|
+
<div class="stat-grid" id="statsGrid">
|
|
351
|
+
<div class="loading">Loading...</div>
|
|
352
|
+
</div>
|
|
353
|
+
</div>
|
|
354
|
+
<div class="card">
|
|
355
|
+
<h3>Recent Topics</h3>
|
|
356
|
+
<div id="topicsList">
|
|
357
|
+
<div class="loading">Loading...</div>
|
|
358
|
+
</div>
|
|
359
|
+
</div>
|
|
360
|
+
</div>
|
|
361
|
+
</div>
|
|
362
|
+
|
|
363
|
+
<script>
|
|
364
|
+
// Global state
|
|
365
|
+
let ws = null;
|
|
366
|
+
let currentData = {};
|
|
367
|
+
let charts = {};
|
|
368
|
+
|
|
369
|
+
// Initialize
|
|
370
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
371
|
+
setupNavigation();
|
|
372
|
+
connectWebSocket();
|
|
373
|
+
initializeCharts();
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
// Navigation
|
|
377
|
+
function setupNavigation() {
|
|
378
|
+
document.querySelectorAll('.nav-button').forEach(button => {
|
|
379
|
+
button.addEventListener('click', () => {
|
|
380
|
+
const view = button.dataset.view;
|
|
381
|
+
switchView(view);
|
|
382
|
+
});
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function switchView(viewName) {
|
|
387
|
+
// Update buttons
|
|
388
|
+
document.querySelectorAll('.nav-button').forEach(btn => {
|
|
389
|
+
btn.classList.toggle('active', btn.dataset.view === viewName);
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
// Update views
|
|
393
|
+
document.querySelectorAll('.view').forEach(view => {
|
|
394
|
+
view.classList.toggle('active', view.id === `${viewName}-view`);
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
// Refresh view with current data
|
|
398
|
+
if (currentData.graph) {
|
|
399
|
+
updateView(viewName, currentData);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// WebSocket connection
|
|
404
|
+
function connectWebSocket() {
|
|
405
|
+
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
406
|
+
const wsUrl = `${protocol}//${window.location.host}/ws`;
|
|
407
|
+
|
|
408
|
+
ws = new WebSocket(wsUrl);
|
|
409
|
+
|
|
410
|
+
ws.onopen = () => {
|
|
411
|
+
updateStatus(true);
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
ws.onmessage = (event) => {
|
|
415
|
+
const data = JSON.parse(event.data);
|
|
416
|
+
currentData = data;
|
|
417
|
+
updateAllViews(data);
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
ws.onerror = (error) => {
|
|
421
|
+
console.error('WebSocket error:', error);
|
|
422
|
+
updateStatus(false);
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
ws.onclose = () => {
|
|
426
|
+
updateStatus(false);
|
|
427
|
+
setTimeout(connectWebSocket, 5000);
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function updateStatus(connected) {
|
|
432
|
+
const indicator = document.getElementById('statusIndicator');
|
|
433
|
+
const text = document.getElementById('statusText');
|
|
434
|
+
|
|
435
|
+
if (connected) {
|
|
436
|
+
indicator.classList.add('connected');
|
|
437
|
+
text.textContent = 'Connected';
|
|
438
|
+
} else {
|
|
439
|
+
indicator.classList.remove('connected');
|
|
440
|
+
text.textContent = 'Disconnected';
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Update all views
|
|
445
|
+
function updateAllViews(data) {
|
|
446
|
+
updateStatsGrid(data.stats);
|
|
447
|
+
updateTopicsList(data.topics);
|
|
448
|
+
|
|
449
|
+
// Update active view
|
|
450
|
+
const activeView = document.querySelector('.nav-button.active').dataset.view;
|
|
451
|
+
updateView(activeView, data);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function updateView(viewName, data) {
|
|
455
|
+
switch(viewName) {
|
|
456
|
+
case 'conversation':
|
|
457
|
+
updateConversationFlow(data.graph);
|
|
458
|
+
break;
|
|
459
|
+
case 'dashboard':
|
|
460
|
+
updateDashboard(data.stats);
|
|
461
|
+
break;
|
|
462
|
+
case 'deduplication':
|
|
463
|
+
updateDeduplication(data.graph);
|
|
464
|
+
break;
|
|
465
|
+
case 'quality':
|
|
466
|
+
updateQuality(data.quality);
|
|
467
|
+
break;
|
|
468
|
+
case 'topics':
|
|
469
|
+
updateTopics(data.topics);
|
|
470
|
+
break;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Stats grid
|
|
475
|
+
function updateStatsGrid(stats) {
|
|
476
|
+
const grid = document.getElementById('statsGrid');
|
|
477
|
+
grid.innerHTML = `
|
|
478
|
+
<div class="stat-item">
|
|
479
|
+
<div class="stat-label">Entries</div>
|
|
480
|
+
<div class="stat-value">${stats.entry_count || 0}</div>
|
|
481
|
+
</div>
|
|
482
|
+
<div class="stat-item">
|
|
483
|
+
<div class="stat-label">Summaries</div>
|
|
484
|
+
<div class="stat-value">${stats.summary_count || 0}</div>
|
|
485
|
+
</div>
|
|
486
|
+
<div class="stat-item">
|
|
487
|
+
<div class="stat-label">Total Tokens</div>
|
|
488
|
+
<div class="stat-value">${(stats.total_tokens || 0).toLocaleString()}</div>
|
|
489
|
+
</div>
|
|
490
|
+
<div class="stat-item">
|
|
491
|
+
<div class="stat-label">Tokens Saved</div>
|
|
492
|
+
<div class="stat-value">${(stats.tokens_saved || 0).toLocaleString()}</div>
|
|
493
|
+
</div>
|
|
494
|
+
<div class="stat-item">
|
|
495
|
+
<div class="stat-label">Compression</div>
|
|
496
|
+
<div class="stat-value">${stats.compression_ratio || '0%'}</div>
|
|
497
|
+
</div>
|
|
498
|
+
<div class="stat-item">
|
|
499
|
+
<div class="stat-label">Avg Quality</div>
|
|
500
|
+
<div class="stat-value">${stats.avg_quality || '0.00'}</div>
|
|
501
|
+
</div>
|
|
502
|
+
`;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// Topics list
|
|
506
|
+
function updateTopicsList(topics) {
|
|
507
|
+
const list = document.getElementById('topicsList');
|
|
508
|
+
if (!topics || !topics.clusters || topics.clusters.length === 0) {
|
|
509
|
+
list.innerHTML = '<div style="color: #666; text-align: center;">No topics yet</div>';
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
list.innerHTML = topics.clusters.slice(0, 5).map(cluster => `
|
|
514
|
+
<div class="topic-cluster">
|
|
515
|
+
<span class="topic-name">${cluster.topic}</span>
|
|
516
|
+
<span class="topic-count">${cluster.count}</span>
|
|
517
|
+
</div>
|
|
518
|
+
`).join('');
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Conversation Flow
|
|
522
|
+
function updateConversationFlow(graph) {
|
|
523
|
+
const container = document.getElementById('graph-container');
|
|
524
|
+
container.innerHTML = '';
|
|
525
|
+
|
|
526
|
+
if (!graph.nodes || graph.nodes.length === 0) {
|
|
527
|
+
container.innerHTML = '<div class="empty-state"><p>No context entries yet</p></div>';
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const width = container.clientWidth;
|
|
532
|
+
const height = container.clientHeight;
|
|
533
|
+
|
|
534
|
+
const svg = d3.select('#graph-container')
|
|
535
|
+
.append('svg')
|
|
536
|
+
.attr('width', width)
|
|
537
|
+
.attr('height', height);
|
|
538
|
+
|
|
539
|
+
const simulation = d3.forceSimulation(graph.nodes)
|
|
540
|
+
.force('link', d3.forceLink(graph.edges).id(d => d.id).distance(100))
|
|
541
|
+
.force('charge', d3.forceManyBody().strength(-300))
|
|
542
|
+
.force('center', d3.forceCenter(width / 2, height / 2));
|
|
543
|
+
|
|
544
|
+
const link = svg.append('g')
|
|
545
|
+
.selectAll('line')
|
|
546
|
+
.data(graph.edges)
|
|
547
|
+
.enter()
|
|
548
|
+
.append('line')
|
|
549
|
+
.attr('class', 'link');
|
|
550
|
+
|
|
551
|
+
const node = svg.append('g')
|
|
552
|
+
.selectAll('g')
|
|
553
|
+
.data(graph.nodes)
|
|
554
|
+
.enter()
|
|
555
|
+
.append('g')
|
|
556
|
+
.attr('class', d => `node ${d.type}`)
|
|
557
|
+
.call(d3.drag()
|
|
558
|
+
.on('start', dragstarted)
|
|
559
|
+
.on('drag', dragged)
|
|
560
|
+
.on('end', dragended));
|
|
561
|
+
|
|
562
|
+
node.append('circle')
|
|
563
|
+
.attr('r', d => d.type === 'summary' ? 15 : 10);
|
|
564
|
+
|
|
565
|
+
node.append('text')
|
|
566
|
+
.attr('dx', 15)
|
|
567
|
+
.attr('dy', 5)
|
|
568
|
+
.text(d => d.id.substring(0, 8));
|
|
569
|
+
|
|
570
|
+
simulation.on('tick', () => {
|
|
571
|
+
link
|
|
572
|
+
.attr('x1', d => d.source.x)
|
|
573
|
+
.attr('y1', d => d.source.y)
|
|
574
|
+
.attr('x2', d => d.target.x)
|
|
575
|
+
.attr('y2', d => d.target.y);
|
|
576
|
+
|
|
577
|
+
node.attr('transform', d => `translate(${d.x},${d.y})`);
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
function dragstarted(event) {
|
|
581
|
+
if (!event.active) simulation.alphaTarget(0.3).restart();
|
|
582
|
+
event.subject.fx = event.subject.x;
|
|
583
|
+
event.subject.fy = event.subject.y;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function dragged(event) {
|
|
587
|
+
event.subject.fx = event.x;
|
|
588
|
+
event.subject.fy = event.y;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function dragended(event) {
|
|
592
|
+
if (!event.active) simulation.alphaTarget(0);
|
|
593
|
+
event.subject.fx = null;
|
|
594
|
+
event.subject.fy = null;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// Initialize Charts
|
|
599
|
+
function initializeCharts() {
|
|
600
|
+
// Token Chart
|
|
601
|
+
const tokenCtx = document.getElementById('tokenChart');
|
|
602
|
+
charts.token = new Chart(tokenCtx, {
|
|
603
|
+
type: 'doughnut',
|
|
604
|
+
data: {
|
|
605
|
+
labels: ['Used', 'Saved'],
|
|
606
|
+
datasets: [{
|
|
607
|
+
data: [0, 0],
|
|
608
|
+
backgroundColor: ['#00d4ff', '#ff6b35']
|
|
609
|
+
}]
|
|
610
|
+
},
|
|
611
|
+
options: {
|
|
612
|
+
responsive: true,
|
|
613
|
+
maintainAspectRatio: false,
|
|
614
|
+
plugins: {
|
|
615
|
+
legend: {
|
|
616
|
+
labels: { color: '#cccccc' }
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
// Compression Chart
|
|
623
|
+
const compressionCtx = document.getElementById('compressionChart');
|
|
624
|
+
charts.compression = new Chart(compressionCtx, {
|
|
625
|
+
type: 'bar',
|
|
626
|
+
data: {
|
|
627
|
+
labels: ['Entries', 'Summaries'],
|
|
628
|
+
datasets: [{
|
|
629
|
+
label: 'Count',
|
|
630
|
+
data: [0, 0],
|
|
631
|
+
backgroundColor: '#00d4ff'
|
|
632
|
+
}]
|
|
633
|
+
},
|
|
634
|
+
options: {
|
|
635
|
+
responsive: true,
|
|
636
|
+
maintainAspectRatio: false,
|
|
637
|
+
plugins: {
|
|
638
|
+
legend: {
|
|
639
|
+
labels: { color: '#cccccc' }
|
|
640
|
+
}
|
|
641
|
+
},
|
|
642
|
+
scales: {
|
|
643
|
+
y: {
|
|
644
|
+
ticks: { color: '#cccccc' },
|
|
645
|
+
grid: { color: '#16213e' }
|
|
646
|
+
},
|
|
647
|
+
x: {
|
|
648
|
+
ticks: { color: '#cccccc' },
|
|
649
|
+
grid: { color: '#16213e' }
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
// Quality Chart
|
|
656
|
+
const qualityCtx = document.getElementById('qualityChart');
|
|
657
|
+
charts.quality = new Chart(qualityCtx, {
|
|
658
|
+
type: 'bar',
|
|
659
|
+
data: {
|
|
660
|
+
labels: ['High', 'Medium', 'Low'],
|
|
661
|
+
datasets: [{
|
|
662
|
+
label: 'Entries',
|
|
663
|
+
data: [0, 0, 0],
|
|
664
|
+
backgroundColor: ['#44ff44', '#ffaa00', '#ff4444']
|
|
665
|
+
}]
|
|
666
|
+
},
|
|
667
|
+
options: {
|
|
668
|
+
responsive: true,
|
|
669
|
+
maintainAspectRatio: false,
|
|
670
|
+
plugins: {
|
|
671
|
+
legend: {
|
|
672
|
+
labels: { color: '#cccccc' }
|
|
673
|
+
}
|
|
674
|
+
},
|
|
675
|
+
scales: {
|
|
676
|
+
y: {
|
|
677
|
+
ticks: { color: '#cccccc' },
|
|
678
|
+
grid: { color: '#16213e' }
|
|
679
|
+
},
|
|
680
|
+
x: {
|
|
681
|
+
ticks: { color: '#cccccc' },
|
|
682
|
+
grid: { color: '#16213e' }
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// Update Dashboard
|
|
690
|
+
function updateDashboard(stats) {
|
|
691
|
+
if (charts.token) {
|
|
692
|
+
charts.token.data.datasets[0].data = [
|
|
693
|
+
stats.total_tokens || 0,
|
|
694
|
+
stats.tokens_saved || 0
|
|
695
|
+
];
|
|
696
|
+
charts.token.update();
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
if (charts.compression) {
|
|
700
|
+
charts.compression.data.datasets[0].data = [
|
|
701
|
+
stats.entry_count || 0,
|
|
702
|
+
stats.summary_count || 0
|
|
703
|
+
];
|
|
704
|
+
charts.compression.update();
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// Update Deduplication
|
|
709
|
+
function updateDeduplication(graph) {
|
|
710
|
+
const statsDiv = document.getElementById('dedupStats');
|
|
711
|
+
const uniqueHashes = new Set();
|
|
712
|
+
|
|
713
|
+
if (graph.nodes) {
|
|
714
|
+
graph.nodes.forEach(node => {
|
|
715
|
+
if (node.type === 'entry') {
|
|
716
|
+
uniqueHashes.add(node.id);
|
|
717
|
+
}
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
const duplicatesPrevented = Math.max(0, (graph.nodes?.length || 0) - uniqueHashes.size);
|
|
722
|
+
const dedupRate = graph.nodes?.length > 0
|
|
723
|
+
? ((duplicatesPrevented / graph.nodes.length) * 100).toFixed(1)
|
|
724
|
+
: 0;
|
|
725
|
+
|
|
726
|
+
statsDiv.innerHTML = `
|
|
727
|
+
<div class="stat-grid">
|
|
728
|
+
<div class="stat-item">
|
|
729
|
+
<div class="stat-label">Unique Entries</div>
|
|
730
|
+
<div class="stat-value">${uniqueHashes.size}</div>
|
|
731
|
+
</div>
|
|
732
|
+
<div class="stat-item">
|
|
733
|
+
<div class="stat-label">Duplicates Prevented</div>
|
|
734
|
+
<div class="stat-value">${duplicatesPrevented}</div>
|
|
735
|
+
</div>
|
|
736
|
+
</div>
|
|
737
|
+
<div class="metric-bar">
|
|
738
|
+
<div class="metric-bar-fill" style="width: ${100 - dedupRate}%">
|
|
739
|
+
${100 - dedupRate}% Unique
|
|
740
|
+
</div>
|
|
741
|
+
</div>
|
|
742
|
+
`;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// Update Quality
|
|
746
|
+
function updateQuality(quality) {
|
|
747
|
+
if (!quality) return;
|
|
748
|
+
|
|
749
|
+
if (charts.quality) {
|
|
750
|
+
charts.quality.data.datasets[0].data = [
|
|
751
|
+
quality.high_quality || 0,
|
|
752
|
+
quality.medium_quality || 0,
|
|
753
|
+
quality.low_quality || 0
|
|
754
|
+
];
|
|
755
|
+
charts.quality.update();
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
const metricsDiv = document.getElementById('qualityMetrics');
|
|
759
|
+
const avgQuality = quality.average || 0;
|
|
760
|
+
const percentage = (avgQuality * 100).toFixed(1);
|
|
761
|
+
|
|
762
|
+
metricsDiv.innerHTML = `
|
|
763
|
+
<div class="metric-bar">
|
|
764
|
+
<div class="metric-bar-fill" style="width: ${percentage}%">
|
|
765
|
+
Average Quality: ${avgQuality.toFixed(2)}
|
|
766
|
+
</div>
|
|
767
|
+
</div>
|
|
768
|
+
<div class="stat-grid" style="margin-top: 20px;">
|
|
769
|
+
<div class="stat-item">
|
|
770
|
+
<div class="stat-label">High Quality</div>
|
|
771
|
+
<div class="stat-value">${quality.high_quality || 0}</div>
|
|
772
|
+
</div>
|
|
773
|
+
<div class="stat-item">
|
|
774
|
+
<div class="stat-label">Medium Quality</div>
|
|
775
|
+
<div class="stat-value">${quality.medium_quality || 0}</div>
|
|
776
|
+
</div>
|
|
777
|
+
<div class="stat-item">
|
|
778
|
+
<div class="stat-label">Low Quality</div>
|
|
779
|
+
<div class="stat-value">${quality.low_quality || 0}</div>
|
|
780
|
+
</div>
|
|
781
|
+
</div>
|
|
782
|
+
`;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// Update Topics
|
|
786
|
+
function updateTopics(topics) {
|
|
787
|
+
const container = document.getElementById('topicGraph');
|
|
788
|
+
container.innerHTML = '';
|
|
789
|
+
|
|
790
|
+
if (!topics || !topics.clusters || topics.clusters.length === 0) {
|
|
791
|
+
container.innerHTML = '<div class="empty-state"><p>No topics yet</p></div>';
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
const width = container.clientWidth;
|
|
796
|
+
const height = container.clientHeight;
|
|
797
|
+
|
|
798
|
+
const nodes = topics.clusters.map(c => ({
|
|
799
|
+
id: c.topic,
|
|
800
|
+
count: c.count
|
|
801
|
+
}));
|
|
802
|
+
|
|
803
|
+
const svg = d3.select('#topicGraph')
|
|
804
|
+
.append('svg')
|
|
805
|
+
.attr('width', width)
|
|
806
|
+
.attr('height', height);
|
|
807
|
+
|
|
808
|
+
const simulation = d3.forceSimulation(nodes)
|
|
809
|
+
.force('charge', d3.forceManyBody().strength(-100))
|
|
810
|
+
.force('center', d3.forceCenter(width / 2, height / 2))
|
|
811
|
+
.force('collision', d3.forceCollide().radius(d => Math.sqrt(d.count) * 10 + 5));
|
|
812
|
+
|
|
813
|
+
const node = svg.append('g')
|
|
814
|
+
.selectAll('g')
|
|
815
|
+
.data(nodes)
|
|
816
|
+
.enter()
|
|
817
|
+
.append('g')
|
|
818
|
+
.attr('class', 'node');
|
|
819
|
+
|
|
820
|
+
node.append('circle')
|
|
821
|
+
.attr('r', d => Math.sqrt(d.count) * 10 + 5)
|
|
822
|
+
.attr('fill', '#00d4ff');
|
|
823
|
+
|
|
824
|
+
node.append('text')
|
|
825
|
+
.attr('text-anchor', 'middle')
|
|
826
|
+
.attr('dy', 5)
|
|
827
|
+
.text(d => d.id)
|
|
828
|
+
.style('fill', '#0f0f23')
|
|
829
|
+
.style('font-weight', '600');
|
|
830
|
+
|
|
831
|
+
simulation.on('tick', () => {
|
|
832
|
+
node.attr('transform', d => `translate(${d.x},${d.y})`);
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
// Fetch initial data
|
|
837
|
+
async function fetchInitialData() {
|
|
838
|
+
try {
|
|
839
|
+
const [graph, stats, topics, quality] = await Promise.all([
|
|
840
|
+
fetch('/api/graph').then(r => r.json()),
|
|
841
|
+
fetch('/api/stats').then(r => r.json()),
|
|
842
|
+
fetch('/api/topics').then(r => r.json()),
|
|
843
|
+
fetch('/api/quality').then(r => r.json())
|
|
844
|
+
]);
|
|
845
|
+
|
|
846
|
+
currentData = { graph, stats, topics, quality };
|
|
847
|
+
updateAllViews(currentData);
|
|
848
|
+
} catch (error) {
|
|
849
|
+
console.error('Error fetching initial data:', error);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
// Fetch initial data on load
|
|
854
|
+
fetchInitialData();
|
|
855
|
+
</script>
|
|
856
|
+
</body>
|
|
857
|
+
</html>
|