gradia 1.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 +1 -0
- gradia/cli/__init__.py +0 -0
- gradia/cli/main.py +91 -0
- gradia/core/config.py +56 -0
- gradia/core/inspector.py +37 -0
- gradia/core/scenario.py +118 -0
- gradia/models/base.py +39 -0
- gradia/models/sklearn_wrappers.py +114 -0
- gradia/trainer/callbacks.py +48 -0
- gradia/trainer/engine.py +203 -0
- gradia/viz/assets/logo.png +0 -0
- gradia/viz/server.py +228 -0
- gradia/viz/static/css/style.css +312 -0
- gradia/viz/static/js/app.js +348 -0
- gradia/viz/templates/configure.html +304 -0
- gradia/viz/templates/index.html +147 -0
- gradia-1.0.0.dist-info/METADATA +143 -0
- gradia-1.0.0.dist-info/RECORD +22 -0
- gradia-1.0.0.dist-info/WHEEL +5 -0
- gradia-1.0.0.dist-info/entry_points.txt +2 -0
- gradia-1.0.0.dist-info/licenses/LICENSE +21 -0
- gradia-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--bg-primary: #343541;
|
|
3
|
+
--bg-secondary: #202123;
|
|
4
|
+
--bg-card: #444654;
|
|
5
|
+
--text-primary: #ececf1;
|
|
6
|
+
--text-secondary: #c5c5d2;
|
|
7
|
+
--accent: #10a37f;
|
|
8
|
+
/* ChatGPT Green */
|
|
9
|
+
--accent-glow: rgba(16, 163, 127, 0.2);
|
|
10
|
+
--success: #10a37f;
|
|
11
|
+
--border: #565869;
|
|
12
|
+
--font-main: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
body {
|
|
16
|
+
margin: 0;
|
|
17
|
+
padding: 0;
|
|
18
|
+
font-family: var(--font-main);
|
|
19
|
+
background-color: var(--bg-primary);
|
|
20
|
+
color: var(--text-primary);
|
|
21
|
+
overflow-x: hidden;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.layout {
|
|
25
|
+
display: grid;
|
|
26
|
+
grid-template-columns: 260px 1fr;
|
|
27
|
+
height: 100vh;
|
|
28
|
+
overflow: hidden;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* Sidebar */
|
|
32
|
+
.sidebar {
|
|
33
|
+
background-color: var(--bg-secondary);
|
|
34
|
+
padding: 15px;
|
|
35
|
+
display: flex;
|
|
36
|
+
flex-direction: column;
|
|
37
|
+
height: 100%;
|
|
38
|
+
border-right: 1px solid rgba(255, 255, 255, 0.1);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.sidebar .brand-logo {
|
|
42
|
+
height: 40px;
|
|
43
|
+
margin-bottom: 20px;
|
|
44
|
+
margin-left: 10px;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.sidebar-section {
|
|
48
|
+
margin-bottom: 30px;
|
|
49
|
+
padding: 0 10px;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.sidebar-label {
|
|
53
|
+
font-size: 0.75rem;
|
|
54
|
+
color: var(--text-secondary);
|
|
55
|
+
text-transform: uppercase;
|
|
56
|
+
font-weight: 600;
|
|
57
|
+
margin-bottom: 10px;
|
|
58
|
+
letter-spacing: 0.5px;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* Main Content */
|
|
62
|
+
.main {
|
|
63
|
+
padding: 40px 60px;
|
|
64
|
+
overflow-y: auto;
|
|
65
|
+
background-color: var(--bg-primary);
|
|
66
|
+
position: relative;
|
|
67
|
+
max-width: 1200px;
|
|
68
|
+
margin: 0 auto;
|
|
69
|
+
width: 100%;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.grid {
|
|
73
|
+
display: grid;
|
|
74
|
+
grid-template-columns: repeat(2, 1fr);
|
|
75
|
+
gap: 20px;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/* Wide charts should span full width */
|
|
79
|
+
.col-full {
|
|
80
|
+
grid-column: span 2;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.card {
|
|
84
|
+
background: var(--bg-card);
|
|
85
|
+
border: 1px solid rgba(255, 255, 255, 0.05);
|
|
86
|
+
border-radius: 8px;
|
|
87
|
+
padding: 20px;
|
|
88
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
|
89
|
+
transition: none;
|
|
90
|
+
/* Flat design doesn't hover much */
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.card h3 {
|
|
94
|
+
margin-top: 0;
|
|
95
|
+
margin-bottom: 15px;
|
|
96
|
+
font-size: 1rem;
|
|
97
|
+
color: var(--text-primary);
|
|
98
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
99
|
+
padding-bottom: 10px;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
canvas {
|
|
103
|
+
width: 100% !important;
|
|
104
|
+
height: 250px !important;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/* Wizard overrides for consistency */
|
|
108
|
+
.container-center {
|
|
109
|
+
background: var(--bg-primary);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.wizard-card {
|
|
113
|
+
background: var(--bg-secondary);
|
|
114
|
+
border: 1px solid var(--border);
|
|
115
|
+
border-radius: 8px;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/* Brand */
|
|
119
|
+
.brand-logo {
|
|
120
|
+
height: 80px;
|
|
121
|
+
margin-bottom: 25px;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.subtitle {
|
|
125
|
+
color: var(--text-secondary);
|
|
126
|
+
text-align: center;
|
|
127
|
+
margin-top: 0px;
|
|
128
|
+
margin-bottom: 25px;
|
|
129
|
+
font-size: 0.9rem;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.footer {
|
|
133
|
+
display: flex;
|
|
134
|
+
flex-direction: column;
|
|
135
|
+
align-items: center;
|
|
136
|
+
margin-top: 50px;
|
|
137
|
+
padding-top: 20px;
|
|
138
|
+
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.footer-text {
|
|
142
|
+
font-size: 0.8rem;
|
|
143
|
+
color: #8e8ea0;
|
|
144
|
+
margin-bottom: 10px;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.social-links {
|
|
148
|
+
display: flex;
|
|
149
|
+
gap: 20px;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.social-icon {
|
|
153
|
+
width: 20px;
|
|
154
|
+
height: 20px;
|
|
155
|
+
fill: #8e8ea0;
|
|
156
|
+
transition: fill 0.2s;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.social-icon:hover {
|
|
160
|
+
fill: var(--text-primary);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.description {
|
|
164
|
+
font-size: 0.75rem;
|
|
165
|
+
color: #8b949e;
|
|
166
|
+
font-weight: 400;
|
|
167
|
+
margin-top: 4px;
|
|
168
|
+
display: block;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.dual-input {
|
|
172
|
+
display: flex;
|
|
173
|
+
gap: 15px;
|
|
174
|
+
align-items: center;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.dual-input input[type=number] {
|
|
178
|
+
width: 80px;
|
|
179
|
+
background: #0d1117;
|
|
180
|
+
border: 1px solid #30363d;
|
|
181
|
+
border-radius: 6px;
|
|
182
|
+
color: #fff;
|
|
183
|
+
padding: 8px;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.logo-large {
|
|
187
|
+
font-size: 3rem;
|
|
188
|
+
font-weight: 800;
|
|
189
|
+
margin-bottom: 20px;
|
|
190
|
+
background: linear-gradient(90deg, #58a6ff, #a371f7);
|
|
191
|
+
-webkit-background-clip: text;
|
|
192
|
+
-webkit-text-fill-color: transparent;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.logo-large span {
|
|
196
|
+
font-size: 1rem;
|
|
197
|
+
color: var(--text-secondary);
|
|
198
|
+
-webkit-text-fill-color: var(--text-secondary);
|
|
199
|
+
font-weight: 400;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.wizard-card {
|
|
203
|
+
width: 100%;
|
|
204
|
+
max-width: 500px;
|
|
205
|
+
padding: 40px;
|
|
206
|
+
background: #161b22;
|
|
207
|
+
border: 1px solid #30363d;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.scenario-badge {
|
|
211
|
+
background: rgba(88, 166, 255, 0.1);
|
|
212
|
+
color: #58a6ff;
|
|
213
|
+
padding: 10px;
|
|
214
|
+
border-radius: 6px;
|
|
215
|
+
margin-bottom: 30px;
|
|
216
|
+
text-align: center;
|
|
217
|
+
font-size: 0.9rem;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.highlight {
|
|
221
|
+
font-weight: 700;
|
|
222
|
+
color: #fff;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.form-group {
|
|
226
|
+
margin-bottom: 25px;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
label {
|
|
230
|
+
display: block;
|
|
231
|
+
margin-bottom: 10px;
|
|
232
|
+
color: #f0f6fc;
|
|
233
|
+
font-size: 0.95rem;
|
|
234
|
+
font-weight: 600;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.val-badge {
|
|
238
|
+
background: #30363d;
|
|
239
|
+
padding: 2px 8px;
|
|
240
|
+
border-radius: 10px;
|
|
241
|
+
color: #fff;
|
|
242
|
+
font-size: 0.8rem;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/* Form Controls */
|
|
246
|
+
.select-wrapper select {
|
|
247
|
+
width: 100%;
|
|
248
|
+
padding: 12px;
|
|
249
|
+
background: #0d1117;
|
|
250
|
+
border: 1px solid #30363d;
|
|
251
|
+
border-radius: 6px;
|
|
252
|
+
color: #f0f6fc;
|
|
253
|
+
font-family: inherit;
|
|
254
|
+
font-size: 1rem;
|
|
255
|
+
outline: none;
|
|
256
|
+
cursor: pointer;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.select-wrapper select:focus {
|
|
260
|
+
border-color: var(--accent);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
input[type=range] {
|
|
264
|
+
width: 100%;
|
|
265
|
+
background: transparent;
|
|
266
|
+
-webkit-appearance: none;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
input[type=range]::-webkit-slider-thumb {
|
|
270
|
+
-webkit-appearance: none;
|
|
271
|
+
height: 16px;
|
|
272
|
+
width: 16px;
|
|
273
|
+
border-radius: 50%;
|
|
274
|
+
background: var(--accent);
|
|
275
|
+
cursor: pointer;
|
|
276
|
+
margin-top: -6px;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
input[type=range]::-webkit-slider-runnable-track {
|
|
280
|
+
width: 100%;
|
|
281
|
+
height: 4px;
|
|
282
|
+
background: #30363d;
|
|
283
|
+
border-radius: 2px;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.btn-primary {
|
|
287
|
+
width: 100%;
|
|
288
|
+
padding: 15px;
|
|
289
|
+
background: var(--success);
|
|
290
|
+
color: white;
|
|
291
|
+
border: none;
|
|
292
|
+
border-radius: 6px;
|
|
293
|
+
font-size: 1rem;
|
|
294
|
+
font-weight: 700;
|
|
295
|
+
cursor: pointer;
|
|
296
|
+
transition: background 0.2s;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.btn-primary:hover {
|
|
300
|
+
background: #2ea043;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.btn-primary:disabled {
|
|
304
|
+
background: #30363d;
|
|
305
|
+
cursor: not-allowed;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.sub-label {
|
|
309
|
+
font-size: 0.75rem;
|
|
310
|
+
color: #484f58;
|
|
311
|
+
margin-bottom: 8px;
|
|
312
|
+
}
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
// app.js
|
|
2
|
+
|
|
3
|
+
const state = {
|
|
4
|
+
events: [],
|
|
5
|
+
lastIdx: 0,
|
|
6
|
+
charts: {},
|
|
7
|
+
scenario: null,
|
|
8
|
+
totalEpochs: 0
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
async function fetchEvents() {
|
|
12
|
+
try {
|
|
13
|
+
const res = await fetch('/api/events');
|
|
14
|
+
const allEvents = await res.json();
|
|
15
|
+
|
|
16
|
+
// Process new events
|
|
17
|
+
if (allEvents.length > state.lastIdx) {
|
|
18
|
+
const newEvents = allEvents.slice(state.lastIdx);
|
|
19
|
+
newEvents.forEach(handleEvent);
|
|
20
|
+
state.lastIdx = allEvents.length;
|
|
21
|
+
}
|
|
22
|
+
} catch (e) {
|
|
23
|
+
console.error("Failed to fetch events", e);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function handleEvent(event) {
|
|
28
|
+
console.log("Event:", event.type, event.data);
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
switch (event.type) {
|
|
32
|
+
case 'train_begin':
|
|
33
|
+
document.getElementById('status-text').innerText = "Training";
|
|
34
|
+
state.scenario = event.data;
|
|
35
|
+
state.totalEpochs = event.data.epochs || 20; // Default fallback
|
|
36
|
+
// Init Progress
|
|
37
|
+
updateProgress(0);
|
|
38
|
+
|
|
39
|
+
renderScenarioInfo(event.data);
|
|
40
|
+
initCharts(event.data);
|
|
41
|
+
break;
|
|
42
|
+
|
|
43
|
+
case 'epoch_end':
|
|
44
|
+
ensureCharts(); // Robustness
|
|
45
|
+
updateCharts(event.data);
|
|
46
|
+
// document.getElementById('epoch-text').innerText = event.data.epoch; // Removed in favor of bar
|
|
47
|
+
updateMetricsTable(event.data);
|
|
48
|
+
updateProgress(event.data.epoch);
|
|
49
|
+
break;
|
|
50
|
+
|
|
51
|
+
case 'system_metrics':
|
|
52
|
+
ensureCharts(); // Robustness
|
|
53
|
+
updateSystemCharts(event.data);
|
|
54
|
+
break;
|
|
55
|
+
|
|
56
|
+
case 'train_end':
|
|
57
|
+
document.getElementById('status-text').innerText = "Completed";
|
|
58
|
+
updateProgress(state.totalEpochs); // Ensure full feature
|
|
59
|
+
document.getElementById('status-text').style.color = "var(--success)";
|
|
60
|
+
if (event.data.feature_importance) {
|
|
61
|
+
updateFeatureImportance(event.data.feature_importance);
|
|
62
|
+
}
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
} catch (err) {
|
|
66
|
+
console.error("Error handling event " + event.type, err);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function ensureCharts() {
|
|
71
|
+
if (!state.charts.metric) {
|
|
72
|
+
// Fallback init if train_begin missed
|
|
73
|
+
initCharts({ features: [], target_column: 'Unknown' });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function renderScenarioInfo(data) {
|
|
78
|
+
const el = document.getElementById('scenario-details');
|
|
79
|
+
// data.scenario is a string repr, or we use individual fields
|
|
80
|
+
el.innerHTML = `
|
|
81
|
+
Target: <span style="color:white">${data.features ? 'Defined' : 'Auto'}</span><br>
|
|
82
|
+
Features: <span style="color:white">${data.features ? data.features.length : 0}</span>
|
|
83
|
+
`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function initCharts(data) {
|
|
87
|
+
// Metric Chart
|
|
88
|
+
const ctx = document.getElementById('metricChart').getContext('2d');
|
|
89
|
+
state.charts.metric = new Chart(ctx, {
|
|
90
|
+
type: 'line',
|
|
91
|
+
data: {
|
|
92
|
+
labels: [],
|
|
93
|
+
datasets: [
|
|
94
|
+
{ label: 'Train', data: [], borderColor: '#58a6ff', tension: 0.3 },
|
|
95
|
+
{ label: 'Test', data: [], borderColor: '#a371f7', tension: 0.3 }
|
|
96
|
+
]
|
|
97
|
+
},
|
|
98
|
+
options: {
|
|
99
|
+
responsive: true, maintainAspectRatio: false, animation: false,
|
|
100
|
+
scales: { y: { grid: { color: '#30363d' } }, x: { grid: { color: '#30363d' } } },
|
|
101
|
+
plugins: { legend: { labels: { color: '#8b949e' } } }
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// System Chart (Gradient Area)
|
|
106
|
+
const ctxSys = document.getElementById('systemChart').getContext('2d');
|
|
107
|
+
|
|
108
|
+
// Gradients
|
|
109
|
+
const gradCpu = ctxSys.createLinearGradient(0, 0, 0, 400);
|
|
110
|
+
gradCpu.addColorStop(0, 'rgba(35, 134, 54, 0.5)');
|
|
111
|
+
gradCpu.addColorStop(1, 'rgba(35, 134, 54, 0.0)');
|
|
112
|
+
|
|
113
|
+
const gradRam = ctxSys.createLinearGradient(0, 0, 0, 400);
|
|
114
|
+
gradRam.addColorStop(0, 'rgba(247, 129, 102, 0.5)');
|
|
115
|
+
gradRam.addColorStop(1, 'rgba(247, 129, 102, 0.0)');
|
|
116
|
+
|
|
117
|
+
state.charts.system = new Chart(ctxSys, {
|
|
118
|
+
type: 'line',
|
|
119
|
+
data: {
|
|
120
|
+
labels: [],
|
|
121
|
+
datasets: [
|
|
122
|
+
{
|
|
123
|
+
label: 'CPU %',
|
|
124
|
+
data: [],
|
|
125
|
+
borderColor: '#238636',
|
|
126
|
+
backgroundColor: gradCpu,
|
|
127
|
+
fill: true,
|
|
128
|
+
tension: 0.4,
|
|
129
|
+
pointRadius: 0
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
label: 'RAM %',
|
|
133
|
+
data: [],
|
|
134
|
+
borderColor: '#f78166',
|
|
135
|
+
backgroundColor: gradRam,
|
|
136
|
+
fill: true,
|
|
137
|
+
tension: 0.4,
|
|
138
|
+
pointRadius: 0
|
|
139
|
+
}
|
|
140
|
+
]
|
|
141
|
+
},
|
|
142
|
+
options: {
|
|
143
|
+
responsive: true, maintainAspectRatio: false, animation: false,
|
|
144
|
+
scales: {
|
|
145
|
+
y: { min: 0, max: 100, grid: { color: '#30363d' } },
|
|
146
|
+
x: { display: false }
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// FI Chart
|
|
152
|
+
const ctxFi = document.getElementById('fiChart').getContext('2d');
|
|
153
|
+
state.charts.fi = new Chart(ctxFi, {
|
|
154
|
+
type: 'bar',
|
|
155
|
+
data: {
|
|
156
|
+
labels: [],
|
|
157
|
+
datasets: [{
|
|
158
|
+
label: 'Importance',
|
|
159
|
+
data: [],
|
|
160
|
+
backgroundColor: '#238636',
|
|
161
|
+
borderRadius: 4
|
|
162
|
+
}]
|
|
163
|
+
},
|
|
164
|
+
options: {
|
|
165
|
+
responsive: true, maintainAspectRatio: false, indexAxis: 'y',
|
|
166
|
+
scales: { y: { grid: { display: false }, ticks: { color: '#8b949e' } }, x: { grid: { color: '#30363d' } } },
|
|
167
|
+
plugins: { legend: { display: false } }
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function updateCharts(data) {
|
|
173
|
+
if (!state.charts.metric) return;
|
|
174
|
+
|
|
175
|
+
// Check which metrics to plot
|
|
176
|
+
let trainVal = data.train_acc || data.train_mse;
|
|
177
|
+
let testVal = data.test_acc || data.test_mse;
|
|
178
|
+
|
|
179
|
+
const chart = state.charts.metric;
|
|
180
|
+
chart.data.labels.push(data.epoch || 1);
|
|
181
|
+
chart.data.datasets[0].data.push(trainVal);
|
|
182
|
+
chart.data.datasets[1].data.push(testVal);
|
|
183
|
+
|
|
184
|
+
// Use proper labels
|
|
185
|
+
if (data.train_acc) {
|
|
186
|
+
chart.data.datasets[0].label = "Train Accuracy";
|
|
187
|
+
chart.data.datasets[1].label = "Test Accuracy";
|
|
188
|
+
} else {
|
|
189
|
+
chart.data.datasets[0].label = "Train MSE";
|
|
190
|
+
chart.data.datasets[1].label = "Test MSE";
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
chart.update();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function updateSystemCharts(data) {
|
|
197
|
+
if (!state.charts.system) return;
|
|
198
|
+
|
|
199
|
+
const chart = state.charts.system;
|
|
200
|
+
// Keep max 60 points
|
|
201
|
+
if (chart.data.labels.length > 60) {
|
|
202
|
+
chart.data.labels.shift();
|
|
203
|
+
chart.data.datasets[0].data.shift();
|
|
204
|
+
chart.data.datasets[1].data.shift();
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
chart.data.labels.push(""); // timestamp irrelevant for sparkline feel
|
|
208
|
+
chart.data.datasets[0].data.push(data.cpu);
|
|
209
|
+
chart.data.datasets[1].data.push(data.ram);
|
|
210
|
+
chart.update();
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function updateFeatureImportance(fiMap) {
|
|
214
|
+
if (!state.charts.fi || !fiMap) return;
|
|
215
|
+
|
|
216
|
+
const labels = Object.keys(fiMap);
|
|
217
|
+
const values = Object.values(fiMap);
|
|
218
|
+
|
|
219
|
+
state.charts.fi.data.labels = labels;
|
|
220
|
+
state.charts.fi.data.datasets[0].data = values;
|
|
221
|
+
state.charts.fi.update();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function updateMetricsTable(data) {
|
|
225
|
+
const el = document.getElementById('metrics-table');
|
|
226
|
+
let html = "";
|
|
227
|
+
|
|
228
|
+
// Priority metrics first
|
|
229
|
+
const priority = ['train_acc', 'test_acc', 'f1', 'precision', 'recall', 'train_mse', 'test_mse', 'mae', 'r2'];
|
|
230
|
+
|
|
231
|
+
// Process priority first
|
|
232
|
+
for (const key of priority) {
|
|
233
|
+
if (data[key] !== undefined) {
|
|
234
|
+
html += renderMetricRow(key, data[key]);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Rest
|
|
239
|
+
for (const [k, v] of Object.entries(data)) {
|
|
240
|
+
if (typeof v === 'number' && k !== 'epoch' && !priority.includes(k)) {
|
|
241
|
+
html += renderMetricRow(k, v);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
el.innerHTML = html;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function renderMetricRow(k, v) {
|
|
248
|
+
// Pretty names
|
|
249
|
+
const names = {
|
|
250
|
+
'train_acc': 'Train Accuracy', 'test_acc': 'Test Accuracy',
|
|
251
|
+
'train_mse': 'Train MSE', 'test_mse': 'Test MSE',
|
|
252
|
+
'f1': 'F1 Score', 'precision': 'Precision', 'recall': 'Recall',
|
|
253
|
+
'mae': 'MAE', 'r2': 'R² Score'
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
const label = names[k] || k;
|
|
257
|
+
const color = k.startsWith('test') || k === 'f1' ? 'var(--accent)' : 'var(--text-secondary)';
|
|
258
|
+
|
|
259
|
+
let valStr = String(v);
|
|
260
|
+
if (typeof v === 'number') {
|
|
261
|
+
valStr = v.toFixed(4);
|
|
262
|
+
} else if (v === null || v === undefined) {
|
|
263
|
+
valStr = "-";
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return `<div style="display:flex; justify-content:space-between; margin-bottom:8px; border-bottom:1px solid #202020; padding-bottom:4px">
|
|
267
|
+
<span style="color:${color}; font-size:0.85rem">${label}</span>
|
|
268
|
+
<span style="font-family:monospace; font-weight:600">${valStr}</span>
|
|
269
|
+
</div>`;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function updateProgress(current) {
|
|
273
|
+
const total = state.totalEpochs;
|
|
274
|
+
const pct = Math.min((current / total) * 100, 100);
|
|
275
|
+
|
|
276
|
+
document.getElementById('progress-fill').style.width = `${pct}%`;
|
|
277
|
+
document.getElementById('progress-text').innerText = `${current} / ${total} Epochs`;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// ... (polling)
|
|
281
|
+
let pollInterval = setInterval(fetchEvents, 500);
|
|
282
|
+
fetchEvents();
|
|
283
|
+
|
|
284
|
+
/* --- New Actions --- */
|
|
285
|
+
|
|
286
|
+
function downloadReport(type) {
|
|
287
|
+
if (type === 'json') {
|
|
288
|
+
window.open('/api/report/json', '_blank');
|
|
289
|
+
} else {
|
|
290
|
+
window.open('/api/report/pdf', '_blank');
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
async function runEvaluation() {
|
|
295
|
+
const btn = document.querySelector('button[onclick="runEvaluation()"]');
|
|
296
|
+
btn.innerText = "Running...";
|
|
297
|
+
btn.disabled = true;
|
|
298
|
+
|
|
299
|
+
try {
|
|
300
|
+
const res = await fetch('/api/evaluate', { method: 'POST' });
|
|
301
|
+
const data = await res.json();
|
|
302
|
+
|
|
303
|
+
if (data.confusion_matrix) {
|
|
304
|
+
renderConfusionMatrix(data.confusion_matrix, data.classes);
|
|
305
|
+
} else {
|
|
306
|
+
alert("Evaluation passed, but only classification supports Matrix currently.");
|
|
307
|
+
}
|
|
308
|
+
} catch (e) {
|
|
309
|
+
alert("Evaluation failed: " + e);
|
|
310
|
+
} finally {
|
|
311
|
+
btn.innerText = "Run Evaluation";
|
|
312
|
+
btn.disabled = false;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function renderConfusionMatrix(matrix, classes) {
|
|
317
|
+
document.getElementById('eval-card').style.display = 'block';
|
|
318
|
+
const container = document.getElementById('cm-container');
|
|
319
|
+
|
|
320
|
+
// Simple HTML heatmap
|
|
321
|
+
let html = '<table style="border-collapse: collapse; font-size: 0.8rem;">';
|
|
322
|
+
|
|
323
|
+
// Header
|
|
324
|
+
html += '<tr><td></td>';
|
|
325
|
+
classes.forEach(c => html += `<td style="padding:5px; font-weight:bold; color:var(--text-secondary)">${c}</td>`);
|
|
326
|
+
html += '</tr>';
|
|
327
|
+
|
|
328
|
+
// Find max for color scaling
|
|
329
|
+
let maxVal = 0;
|
|
330
|
+
matrix.forEach(row => row.forEach(v => maxVal = Math.max(maxVal, v)));
|
|
331
|
+
|
|
332
|
+
matrix.forEach((row, i) => {
|
|
333
|
+
html += `<tr><td style="padding:5px; font-weight:bold; color:var(--text-secondary)">${classes[i]}</td>`;
|
|
334
|
+
row.forEach(val => {
|
|
335
|
+
const intensity = maxVal > 0 ? val / maxVal : 0;
|
|
336
|
+
// Green intensity
|
|
337
|
+
const bg = `rgba(16, 163, 127, ${intensity * 0.8 + 0.1})`;
|
|
338
|
+
html += `<td style="background:${bg}; padding:10px 15px; text-align:center; border:1px solid #333; color:white;">${val}</td>`;
|
|
339
|
+
});
|
|
340
|
+
html += '</tr>';
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
html += '</table>';
|
|
344
|
+
container.innerHTML = html;
|
|
345
|
+
|
|
346
|
+
// Scroll to it
|
|
347
|
+
document.getElementById('eval-card').scrollIntoView({ behavior: 'smooth' });
|
|
348
|
+
}
|