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.
- gradia/__init__.py +38 -1
- gradia/cli/main.py +1 -1
- gradia/core/config.py +71 -13
- gradia/core/migration.py +324 -0
- gradia/events/__init__.py +17 -0
- gradia/events/logger.py +215 -0
- gradia/events/models.py +170 -0
- gradia/events/tracker.py +337 -0
- gradia/trainer/engine.py +175 -3
- gradia/viz/server.py +153 -17
- gradia/viz/static/css/timeline.css +419 -0
- gradia/viz/static/js/timeline.js +471 -0
- gradia/viz/templates/configure.html +1 -1
- gradia/viz/templates/index.html +11 -9
- gradia/viz/templates/timeline.html +195 -0
- gradia-2.0.0.dist-info/METADATA +394 -0
- gradia-2.0.0.dist-info/RECORD +30 -0
- {gradia-1.0.0.dist-info → gradia-2.0.0.dist-info}/WHEEL +1 -1
- gradia-1.0.0.dist-info/METADATA +0 -143
- gradia-1.0.0.dist-info/RECORD +0 -22
- {gradia-1.0.0.dist-info → gradia-2.0.0.dist-info}/entry_points.txt +0 -0
- {gradia-1.0.0.dist-info → gradia-2.0.0.dist-info}/licenses/LICENSE +0 -0
- {gradia-1.0.0.dist-info → gradia-2.0.0.dist-info}/top_level.txt +0 -0
|
@@ -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);
|
gradia/viz/templates/index.html
CHANGED
|
@@ -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
|
-
|
|
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
|
-
<!--
|
|
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>
|