gradia 1.0.0__py3-none-any.whl → 2.0.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.
@@ -0,0 +1,471 @@
1
+ // timeline.js - Learning Timeline v2.0
2
+
3
+ const timelineState = {
4
+ summaries: [],
5
+ insights: null,
6
+ currentSample: null,
7
+ charts: {},
8
+ pollInterval: null,
9
+ lastSummaryCount: 0
10
+ };
11
+
12
+ // ============================================================================
13
+ // Initialization
14
+ // ============================================================================
15
+
16
+ async function initTimeline() {
17
+ initStabilityChart();
18
+ startPolling();
19
+ await fetchInitialData();
20
+ }
21
+
22
+ function startPolling() {
23
+ timelineState.pollInterval = setInterval(async () => {
24
+ await fetchSummaries();
25
+ await fetchInsights();
26
+ }, 1000);
27
+ }
28
+
29
+ async function fetchInitialData() {
30
+ await fetchSummaries();
31
+ await fetchInsights();
32
+ }
33
+
34
+ // ============================================================================
35
+ // Data Fetching
36
+ // ============================================================================
37
+
38
+ async function fetchSummaries() {
39
+ try {
40
+ const res = await fetch('/api/timeline/summaries');
41
+ if (!res.ok) return;
42
+
43
+ const summaries = await res.json();
44
+
45
+ if (summaries.length > timelineState.lastSummaryCount) {
46
+ timelineState.summaries = summaries;
47
+ timelineState.lastSummaryCount = summaries.length;
48
+ updateTimelineOverview();
49
+ updateTrainingContext();
50
+ }
51
+ } catch (e) {
52
+ console.error('Failed to fetch summaries:', e);
53
+ }
54
+ }
55
+
56
+ async function fetchInsights() {
57
+ try {
58
+ const res = await fetch('/api/timeline/insights');
59
+ if (!res.ok) return;
60
+
61
+ const insights = await res.json();
62
+ timelineState.insights = insights;
63
+
64
+ updateInstabilityPanel();
65
+ updateStabilityChart();
66
+ } catch (e) {
67
+ console.error('Failed to fetch insights:', e);
68
+ }
69
+ }
70
+
71
+ async function fetchSampleTimeline(sampleId) {
72
+ try {
73
+ const res = await fetch(`/api/timeline/sample/${sampleId}`);
74
+ if (!res.ok) return null;
75
+ return await res.json();
76
+ } catch (e) {
77
+ console.error('Failed to fetch sample timeline:', e);
78
+ return null;
79
+ }
80
+ }
81
+
82
+ // ============================================================================
83
+ // Timeline Overview (Block A)
84
+ // ============================================================================
85
+
86
+ function updateTimelineOverview() {
87
+ const container = document.getElementById('timeline-overview');
88
+ const insights = timelineState.insights;
89
+ const summaries = timelineState.summaries;
90
+
91
+ if (!insights || !summaries.length) {
92
+ container.innerHTML = '<div class="timeline-loading"><span>Waiting for training data...</span></div>';
93
+ return;
94
+ }
95
+
96
+ const trackedSamples = insights.tracked_samples || [];
97
+ const maxEpoch = Math.max(...summaries.map(s => s.epoch), 1);
98
+
99
+ // Build sample stability map from insights
100
+ const stabilityMap = {};
101
+
102
+ // Process all stability data from insights
103
+ for (const item of insights.top_flipping || []) {
104
+ stabilityMap[item.sample_id] = item.stability_class;
105
+ }
106
+ for (const item of insights.late_learners || []) {
107
+ stabilityMap[item.sample_id] = 'late_learner';
108
+ }
109
+ for (const item of insights.never_correct || []) {
110
+ stabilityMap[item.sample_id] = 'stable_wrong';
111
+ }
112
+
113
+ // Build HTML
114
+ let html = '';
115
+
116
+ // Epoch axis
117
+ html += '<div class="epoch-axis">';
118
+ for (let e = 1; e <= maxEpoch; e++) {
119
+ html += `<span class="epoch-label">${e}</span>`;
120
+ }
121
+ html += '</div>';
122
+
123
+ html += '<div class="timeline-grid">';
124
+
125
+ // Limit to first 50 samples for performance
126
+ const displaySamples = trackedSamples.slice(0, 50);
127
+
128
+ for (const sampleId of displaySamples) {
129
+ const stability = stabilityMap[sampleId] || 'unknown';
130
+
131
+ html += `<div class="timeline-row">`;
132
+ html += `<span class="timeline-sample-id" onclick="openSampleInspector(${sampleId})">#${sampleId}</span>`;
133
+ html += `<div class="timeline-epochs">`;
134
+
135
+ // Create epoch cells (simplified - full implementation would fetch per-sample events)
136
+ for (let e = 1; e <= maxEpoch; e++) {
137
+ const cellClass = getStabilityCellClass(stability, e, maxEpoch);
138
+ html += `<div class="epoch-cell ${cellClass}"
139
+ onclick="openSampleInspector(${sampleId})"
140
+ title="Sample ${sampleId}, Epoch ${e}"></div>`;
141
+ }
142
+
143
+ html += '</div></div>';
144
+ }
145
+
146
+ html += '</div>';
147
+
148
+ if (trackedSamples.length > 50) {
149
+ html += `<div style="text-align: center; padding: 10px; color: var(--text-secondary); font-size: 0.8rem;">
150
+ Showing 50 of ${trackedSamples.length} tracked samples
151
+ </div>`;
152
+ }
153
+
154
+ container.innerHTML = html;
155
+ }
156
+
157
+ function getStabilityCellClass(stability, epoch, maxEpoch) {
158
+ // Simplified visualization based on stability class
159
+ switch (stability) {
160
+ case 'stable_correct':
161
+ return 'correct';
162
+ case 'stable_wrong':
163
+ return 'wrong';
164
+ case 'unstable':
165
+ // Alternate for visual effect
166
+ return epoch % 2 === 0 ? 'correct' : 'wrong';
167
+ case 'late_learner':
168
+ // Show transition
169
+ const transitionPoint = Math.floor(maxEpoch * 0.6);
170
+ return epoch >= transitionPoint ? 'correct' : 'wrong';
171
+ default:
172
+ return '';
173
+ }
174
+ }
175
+
176
+ // ============================================================================
177
+ // Training Context (Block D)
178
+ // ============================================================================
179
+
180
+ function updateTrainingContext() {
181
+ const summaries = timelineState.summaries;
182
+ const insights = timelineState.insights;
183
+
184
+ if (summaries.length > 0) {
185
+ const latest = summaries[summaries.length - 1];
186
+ document.getElementById('current-epoch').textContent = latest.epoch;
187
+ document.getElementById('training-status').textContent = 'Training';
188
+ document.getElementById('training-status').style.color = 'var(--accent)';
189
+ }
190
+
191
+ if (insights) {
192
+ document.getElementById('tracked-count').textContent = insights.total_tracked || 0;
193
+ }
194
+ }
195
+
196
+ // ============================================================================
197
+ // Instability Panel (Block C)
198
+ // ============================================================================
199
+
200
+ function updateInstabilityPanel() {
201
+ const insights = timelineState.insights;
202
+ if (!insights) return;
203
+
204
+ // Top Flipping
205
+ const flippingList = document.getElementById('flipping-list');
206
+ if (insights.top_flipping && insights.top_flipping.length > 0) {
207
+ flippingList.innerHTML = insights.top_flipping.slice(0, 5).map(item => `
208
+ <div class="sample-item" onclick="openSampleInspector(${item.sample_id})">
209
+ <span class="sample-id">#${item.sample_id}</span>
210
+ <span class="sample-meta">Label: ${item.true_label}</span>
211
+ <span class="flip-badge">${item.flip_count} flips</span>
212
+ </div>
213
+ `).join('');
214
+ } else {
215
+ flippingList.innerHTML = '<span class="empty-state">No flipping samples detected</span>';
216
+ }
217
+
218
+ // Late Learners
219
+ const lateList = document.getElementById('late-learners-list');
220
+ if (insights.late_learners && insights.late_learners.length > 0) {
221
+ lateList.innerHTML = insights.late_learners.slice(0, 5).map(item => `
222
+ <div class="sample-item" onclick="openSampleInspector(${item.sample_id})">
223
+ <span class="sample-id">#${item.sample_id}</span>
224
+ <span class="sample-meta">First correct: Epoch ${item.first_correct_epoch}</span>
225
+ </div>
226
+ `).join('');
227
+ } else {
228
+ lateList.innerHTML = '<span class="empty-state">No late learners detected</span>';
229
+ }
230
+
231
+ // Never Correct
232
+ const neverList = document.getElementById('never-correct-list');
233
+ if (insights.never_correct && insights.never_correct.length > 0) {
234
+ neverList.innerHTML = insights.never_correct.slice(0, 5).map(item => `
235
+ <div class="sample-item" onclick="openSampleInspector(${item.sample_id})">
236
+ <span class="sample-id">#${item.sample_id}</span>
237
+ <span class="sample-meta">True: ${item.true_label} → Pred: ${item.current_prediction}</span>
238
+ </div>
239
+ `).join('');
240
+ } else {
241
+ neverList.innerHTML = '<span class="empty-state">All samples correct at least once</span>';
242
+ }
243
+ }
244
+
245
+ // ============================================================================
246
+ // Stability Chart
247
+ // ============================================================================
248
+
249
+ function initStabilityChart() {
250
+ const ctx = document.getElementById('stabilityChart').getContext('2d');
251
+
252
+ timelineState.charts.stability = new Chart(ctx, {
253
+ type: 'doughnut',
254
+ data: {
255
+ labels: ['Stable Correct', 'Stable Wrong', 'Unstable', 'Late Learner'],
256
+ datasets: [{
257
+ data: [0, 0, 0, 0],
258
+ backgroundColor: ['#238636', '#da3633', '#d29922', '#58a6ff'],
259
+ borderColor: '#0d1117',
260
+ borderWidth: 2
261
+ }]
262
+ },
263
+ options: {
264
+ responsive: true,
265
+ maintainAspectRatio: false,
266
+ plugins: {
267
+ legend: {
268
+ position: 'bottom',
269
+ labels: { color: '#8b949e', padding: 15 }
270
+ }
271
+ }
272
+ }
273
+ });
274
+ }
275
+
276
+ function updateStabilityChart() {
277
+ const insights = timelineState.insights;
278
+ if (!insights || !insights.stability_distribution) return;
279
+
280
+ const dist = insights.stability_distribution;
281
+ const chart = timelineState.charts.stability;
282
+
283
+ chart.data.datasets[0].data = [
284
+ dist.stable_correct || 0,
285
+ dist.stable_wrong || 0,
286
+ dist.unstable || 0,
287
+ dist.late_learner || 0
288
+ ];
289
+ chart.update();
290
+ }
291
+
292
+ // ============================================================================
293
+ // Sample Inspector (Block B)
294
+ // ============================================================================
295
+
296
+ async function openSampleInspector(sampleId) {
297
+ const modal = document.getElementById('sample-inspector-modal');
298
+ modal.style.display = 'flex';
299
+
300
+ // Fetch sample data
301
+ const data = await fetchSampleTimeline(sampleId);
302
+ if (!data) {
303
+ alert('Could not load sample data');
304
+ closeSampleInspector();
305
+ return;
306
+ }
307
+
308
+ timelineState.currentSample = data;
309
+
310
+ // Update header stats
311
+ document.getElementById('inspector-sample-id').textContent = `#${data.sample_id}`;
312
+ document.getElementById('inspector-true-label').textContent = data.true_label || '-';
313
+ document.getElementById('inspector-flip-count').textContent = data.flip_count || 0;
314
+ document.getElementById('inspector-stability').textContent = formatStability(data.stability_class);
315
+
316
+ // Update charts
317
+ updateInspectorCharts(data);
318
+
319
+ // Update event history
320
+ updateEventHistory(data.events || []);
321
+ }
322
+
323
+ function closeSampleInspector() {
324
+ document.getElementById('sample-inspector-modal').style.display = 'none';
325
+ timelineState.currentSample = null;
326
+ }
327
+
328
+ function formatStability(stability) {
329
+ const map = {
330
+ 'stable_correct': '✓ Stable',
331
+ 'stable_wrong': '✗ Persistent Error',
332
+ 'unstable': '⚡ Unstable',
333
+ 'late_learner': '🕐 Late Learner',
334
+ 'unknown': '?'
335
+ };
336
+ return map[stability] || stability;
337
+ }
338
+
339
+ function updateInspectorCharts(data) {
340
+ const events = data.events || [];
341
+ if (!events.length) return;
342
+
343
+ const epochs = events.map(e => e.epoch);
344
+ const confidences = events.map(e => e.confidence);
345
+ const predictions = events.map(e => e.predicted_label);
346
+ const correctness = events.map(e => e.correct ? 1 : 0);
347
+
348
+ // Prediction trajectory chart
349
+ const predCtx = document.getElementById('predictionChart').getContext('2d');
350
+ if (timelineState.charts.prediction) {
351
+ timelineState.charts.prediction.destroy();
352
+ }
353
+
354
+ timelineState.charts.prediction = new Chart(predCtx, {
355
+ type: 'line',
356
+ data: {
357
+ labels: epochs,
358
+ datasets: [{
359
+ label: 'Correct',
360
+ data: correctness,
361
+ borderColor: '#238636',
362
+ backgroundColor: 'rgba(35, 134, 54, 0.1)',
363
+ fill: true,
364
+ tension: 0.3,
365
+ stepped: true
366
+ }]
367
+ },
368
+ options: {
369
+ responsive: true,
370
+ maintainAspectRatio: false,
371
+ scales: {
372
+ y: {
373
+ min: 0,
374
+ max: 1,
375
+ ticks: {
376
+ callback: v => v === 1 ? 'Correct' : 'Wrong',
377
+ color: '#8b949e'
378
+ },
379
+ grid: { color: '#30363d' }
380
+ },
381
+ x: {
382
+ title: { display: true, text: 'Epoch', color: '#8b949e' },
383
+ grid: { color: '#30363d' }
384
+ }
385
+ },
386
+ plugins: { legend: { display: false } }
387
+ }
388
+ });
389
+
390
+ // Confidence chart
391
+ const confCtx = document.getElementById('confidenceChart').getContext('2d');
392
+ if (timelineState.charts.confidence) {
393
+ timelineState.charts.confidence.destroy();
394
+ }
395
+
396
+ timelineState.charts.confidence = new Chart(confCtx, {
397
+ type: 'line',
398
+ data: {
399
+ labels: epochs,
400
+ datasets: [{
401
+ label: 'Confidence',
402
+ data: confidences,
403
+ borderColor: '#58a6ff',
404
+ backgroundColor: 'rgba(88, 166, 255, 0.1)',
405
+ fill: true,
406
+ tension: 0.3
407
+ }]
408
+ },
409
+ options: {
410
+ responsive: true,
411
+ maintainAspectRatio: false,
412
+ scales: {
413
+ y: {
414
+ min: 0,
415
+ max: 1,
416
+ grid: { color: '#30363d' },
417
+ ticks: { color: '#8b949e' }
418
+ },
419
+ x: {
420
+ title: { display: true, text: 'Epoch', color: '#8b949e' },
421
+ grid: { color: '#30363d' }
422
+ }
423
+ },
424
+ plugins: { legend: { display: false } }
425
+ }
426
+ });
427
+ }
428
+
429
+ function updateEventHistory(events) {
430
+ const container = document.getElementById('event-history-list');
431
+
432
+ if (!events.length) {
433
+ container.innerHTML = '<span class="empty-state">No events recorded</span>';
434
+ return;
435
+ }
436
+
437
+ // Detect flips
438
+ const flips = new Set();
439
+ for (let i = 1; i < events.length; i++) {
440
+ if (events[i].predicted_label !== events[i-1].predicted_label) {
441
+ flips.add(i);
442
+ }
443
+ }
444
+
445
+ container.innerHTML = events.map((e, idx) => `
446
+ <div class="event-item ${e.correct ? 'correct' : 'wrong'}">
447
+ <span class="epoch-badge">Epoch ${e.epoch}</span>
448
+ <span>True: ${e.true_label}</span>
449
+ <span>Pred: ${e.predicted_label}</span>
450
+ <span>Conf: ${(e.confidence * 100).toFixed(1)}%</span>
451
+ <span>${flips.has(idx) ? '<span class="flip-marker">⚡ FLIP</span>' : ''}</span>
452
+ </div>
453
+ `).join('');
454
+ }
455
+
456
+ // Close modal on backdrop click
457
+ document.getElementById('sample-inspector-modal')?.addEventListener('click', function(e) {
458
+ if (e.target === this) {
459
+ closeSampleInspector();
460
+ }
461
+ });
462
+
463
+ // Keyboard handler
464
+ document.addEventListener('keydown', function(e) {
465
+ if (e.key === 'Escape') {
466
+ closeSampleInspector();
467
+ }
468
+ });
469
+
470
+ // Initialize on load
471
+ document.addEventListener('DOMContentLoaded', initTimeline);
@@ -30,7 +30,7 @@
30
30
  <div style="flex-grow:1"></div>
31
31
 
32
32
  <div class="sidebar-section" style="font-size: 0.75rem; color: #565869;">
33
- v1.3.0
33
+ v2.0.0
34
34
  </div>
35
35
  </aside>
36
36
 
@@ -29,6 +29,10 @@
29
29
 
30
30
  <div class="sidebar-label">Actions</div>
31
31
  <div style="display: flex; flex-direction: column; gap: 10px;">
32
+ <button class="btn-primary" onclick="window.location.href='/timeline'"
33
+ style="background:rgba(88, 166, 255, 0.1); border:1px solid var(--accent); color:var(--accent); font-size: 0.8rem; padding: 10px;">🔬
34
+ Learning Timeline</button>
35
+
32
36
  <button class="btn-primary" onclick="window.location.href='/configure'"
33
37
  style="background:var(--bg-card); border:1px solid var(--border); font-size: 0.8rem; padding: 10px;">New
34
38
  Experiment</button>
@@ -49,7 +53,7 @@
49
53
  <div style="flex-grow:1"></div>
50
54
 
51
55
  <div class="sidebar-section" style="font-size: 0.75rem; color: #565869;">
52
- v1.3.0
56
+ v2.0.0
53
57
  </div>
54
58
  </aside>
55
59
 
@@ -117,24 +121,22 @@
117
121
  </div>
118
122
  <div class="social-links">
119
123
  <!-- Instagram -->
120
- <a href="https://instagram.com" target="_blank" title="Instagram">
124
+ <a href="https://instagram.com/stifler.xd" target="_blank" title="Instagram @stifler.xd">
121
125
  <svg class="social-icon" viewBox="0 0 24 24">
122
126
  <path
123
127
  d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z" />
124
128
  </svg>
125
129
  </a>
126
- <!-- HF (Placeholder Circle) -->
127
- <a href="https://huggingface.co" target="_blank" title="Hugging Face">
130
+ <!-- Hugging Face -->
131
+ <a href="https://huggingface.co/STiFLeR7" target="_blank" title="Hugging Face @STiFLeR7">
128
132
  <svg class="social-icon" viewBox="0 0 24 24">
129
- <path
130
- d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z" />
133
+ <path d="M12 2C6.477 2 2 6.477 2 12c0 4.42 2.865 8.166 6.839 9.489.5.092.682-.217.682-.482 0-.237-.008-.866-.013-1.7-2.782.604-3.369-1.34-3.369-1.34-.454-1.156-1.11-1.463-1.11-1.463-.908-.62.069-.608.069-.608 1.003.07 1.531 1.03 1.531 1.03.892 1.529 2.341 1.087 2.91.831.092-.646.35-1.086.636-1.336-2.22-.253-4.555-1.11-4.555-4.943 0-1.091.39-1.984 1.029-2.683-.103-.253-.446-1.27.098-2.647 0 0 .84-.269 2.75 1.025A9.578 9.578 0 0112 6.836c.85.004 1.705.114 2.504.336 1.909-1.294 2.747-1.025 2.747-1.025.546 1.377.203 2.394.1 2.647.64.699 1.028 1.592 1.028 2.683 0 3.842-2.339 4.687-4.566 4.935.359.309.678.919.678 1.852 0 1.336-.012 2.415-.012 2.743 0 .267.18.578.688.48C19.138 20.163 22 16.418 22 12c0-5.523-4.477-10-10-10z"/>
131
134
  </svg>
132
135
  </a>
133
136
  <!-- PyPI -->
134
- <a href="https://pypi.org" target="_blank" title="PyPI">
137
+ <a href="https://pypi.org/user/stifler.xd/" target="_blank" title="PyPI @stifler.xd">
135
138
  <svg class="social-icon" viewBox="0 0 24 24">
136
- <path
137
- d="M12.8 1.6C12.8 1.05 12.35 0.6 11.8 0.6H3.2C2.65 0.6 2.2 1.05 2.2 1.6V10.2C2.2 10.75 2.65 11.2 3.2 11.2H4.8V12.8H3.2C2.1 12.8 1.2 13.7 1.2 14.8V21.8C1.2 22.9 2.1 23.8 3.2 23.8H10.2C11.3 23.8 12.2 22.9 12.2 21.8V20.2H12.8C13.9 20.2 14.8 19.3 14.8 18.2V11.2H11.2V10.2H11.8C12.35 10.2 12.8 9.75 12.8 9.2V1.6ZM10.2 21.8H3.2V14.8H10.2V21.8ZM20.8 11.2H13.8V18.2H14.8V19.2C14.8 19.75 15.25 20.2 15.8 20.2H21.2C22.3 20.2 23.2 19.3 23.2 18.2V11.2C23.2 10.1 22.3 9.2 21.2 9.2H20.8V11.2ZM19.2 16.2H16.2V13.2H19.2V16.2Z" />
139
+ <path d="M12.042 0L4.018 3.531v5.969l3.16-1.339V4.719l4.864-2.063 4.864 2.063v3.442l3.16 1.339V3.531L12.042 0zm7.976 7.406l-7.976 3.375-7.976-3.375v8.813l7.976 3.375 7.976-3.375V7.406zM4.018 17.906v3.563L12.042 25l8.024-3.531v-3.563l-8.024 3.375-8.024-3.375z"/>
138
140
  </svg>
139
141
  </a>
140
142
  </div>