0xarchit commited on
Commit
199cee1
·
1 Parent(s): 22032c4

feat(inference): fix ui and shap

Browse files
inference/template/index.html CHANGED
@@ -1,15 +1,19 @@
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>Inference Studio | Neural Network Visualizer</title>
7
  <link rel="stylesheet" href="/static/styles.css">
8
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
  <link rel="preconnect" href="https://fonts.googleapis.com">
10
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
- <link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;700&family=JetBrains+Mono:wght@300;500&display=swap" rel="stylesheet">
 
 
12
  </head>
 
13
  <body>
14
  <div class="app-container">
15
  <header>
@@ -86,4 +90,5 @@
86
  </div>
87
  <script src="/static/script.js"></script>
88
  </body>
89
- </html>
 
 
1
  <!DOCTYPE html>
2
  <html lang="en">
3
+
4
  <head>
5
  <meta charset="UTF-8">
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Inference Studio | Machine Learning Assignment</title>
8
  <link rel="stylesheet" href="/static/styles.css">
9
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
10
  <link rel="preconnect" href="https://fonts.googleapis.com">
11
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
12
+ <link
13
+ href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;700&family=JetBrains+Mono:wght@300;500&display=swap"
14
+ rel="stylesheet">
15
  </head>
16
+
17
  <body>
18
  <div class="app-container">
19
  <header>
 
90
  </div>
91
  <script src="/static/script.js"></script>
92
  </body>
93
+
94
+ </html>
inference/template/script.js CHANGED
@@ -153,7 +153,7 @@ elements.inferenceForm.onsubmit = async (e) => {
153
  function renderResult(data) {
154
  const scores = Object.entries(data.scores).sort((a, b) => b[1] - a[1]);
155
  const shap = Object.entries(data.shap || {});
156
- const maxShap = Math.max(...shap.map(s => Math.abs(s[1])), 1);
157
 
158
  elements.resultDisplay.innerHTML = `
159
  <div class="result-view">
@@ -176,14 +176,15 @@ function renderResult(data) {
176
  </div>
177
 
178
  <div class="shap-chart">
179
- <span style="font-size: 0.65rem; color: var(--text-dim); letter-spacing: 4px; font-weight: 700; text-transform: uppercase; margin-bottom: 10px;">Top Decision Drivers</span>
180
  ${shap.map(([feat, val]) => `
181
  <div class="shap-row">
182
- <span class="score-label" style="width: 120px;" title="${feat}">${feat}</span>
183
- <div class="shap-bar-container">
184
- <div class="shap-bar-fill ${val >= 0 ? 'positive' : 'negative'}" style="width: 0%"></div>
 
185
  </div>
186
- <span class="score-label" style="text-align: right; width: 40px;">${val >= 0 ? '+' : '-'}${Math.abs(val).toFixed(2)}</span>
187
  </div>
188
  `).join('')}
189
  </div>
@@ -194,8 +195,11 @@ function renderResult(data) {
194
  const fills = elements.resultDisplay.querySelectorAll('.bar-fill');
195
  scores.forEach((s, i) => fills[i].style.width = `${s[1] * 100}%`);
196
 
197
- const shapFills = elements.resultDisplay.querySelectorAll('.shap-bar-fill');
198
- shap.forEach((s, i) => shapFills[i].style.width = `${(Math.abs(s[1]) / maxShap) * 100}%`);
 
 
 
199
  }, 100);
200
  }
201
 
 
153
  function renderResult(data) {
154
  const scores = Object.entries(data.scores).sort((a, b) => b[1] - a[1]);
155
  const shap = Object.entries(data.shap || {});
156
+ const maxShap = Math.max(...shap.map(s => Math.abs(s[1])), 0.001);
157
 
158
  elements.resultDisplay.innerHTML = `
159
  <div class="result-view">
 
176
  </div>
177
 
178
  <div class="shap-chart">
179
+ <span style="font-size: 0.65rem; color: var(--text-dim); letter-spacing: 4px; font-weight: 700; text-transform: uppercase; margin-bottom: 10px;">SHAP Decision Drivers</span>
180
  ${shap.map(([feat, val]) => `
181
  <div class="shap-row">
182
+ <span class="score-label" style="font-size: 0.75rem; overflow: hidden; text-overflow: ellipsis;" title="${feat}">${feat}</span>
183
+ <div class="shap-viz">
184
+ <div class="shap-axis"></div>
185
+ <div class="shap-bar ${val >= 0 ? 'positive' : 'negative'}" style="width: 0%"></div>
186
  </div>
187
+ <span class="score-label" style="text-align: right; font-weight: 700; color: ${val >= 0 ? '#10b981' : '#ef4444'}">${val >= 0 ? '+' : ''}${val.toFixed(2)}</span>
188
  </div>
189
  `).join('')}
190
  </div>
 
195
  const fills = elements.resultDisplay.querySelectorAll('.bar-fill');
196
  scores.forEach((s, i) => fills[i].style.width = `${s[1] * 100}%`);
197
 
198
+ const shapBars = elements.resultDisplay.querySelectorAll('.shap-bar');
199
+ shap.forEach((s, i) => {
200
+ const width = (Math.abs(s[1]) / maxShap) * 50; // Max 50% from center
201
+ shapBars[i].style.width = `${width}%`;
202
+ });
203
  }, 100);
204
  }
205
 
inference/template/styles.css CHANGED
@@ -80,10 +80,11 @@ header {
80
  .tabs {
81
  display: flex;
82
  gap: 12px;
83
- justify-content: center;
84
  overflow-x: auto;
85
  padding: 10px;
86
  scrollbar-width: none;
 
 
87
  }
88
 
89
  .tabs::-webkit-scrollbar { display: none; }
@@ -300,41 +301,55 @@ option { background: #0f172a; color: #fff; }
300
  .shap-chart {
301
  display: flex;
302
  flex-direction: column;
303
- gap: 15px;
304
  margin-top: 40px;
305
  padding-top: 40px;
306
  border-top: 1px solid var(--border);
307
  }
308
 
309
  .shap-row {
310
- display: flex;
 
311
  align-items: center;
312
  gap: 15px;
313
  }
314
 
315
- .shap-bar-container {
316
- flex: 1;
317
- height: 8px;
318
- background: rgba(255, 255, 255, 0.03);
319
- border-radius: 100px;
320
  position: relative;
321
- overflow: hidden;
 
 
 
 
 
 
 
 
 
 
 
322
  }
323
 
324
- .shap-bar-fill {
325
  height: 100%;
326
- border-radius: 100px;
327
- transition: width 1s ease-out;
 
328
  }
329
 
330
- .shap-bar-fill.positive {
 
331
  background: #10b981;
332
- box-shadow: 0 0 10px rgba(16, 185, 129, 0.3);
333
  }
334
 
335
- .shap-bar-fill.negative {
 
336
  background: #ef4444;
337
- box-shadow: 0 0 10px rgba(239, 68, 68, 0.3);
338
  }
339
 
340
  #modal-overlay {
@@ -420,7 +435,26 @@ option { background: #0f172a; color: #fff; }
420
  header { flex-direction: row; justify-content: space-between; align-items: center; }
421
  .logo { align-items: flex-start; }
422
  .btn-group { flex-direction: row; }
 
423
  }
424
 
425
  @keyframes slide-reveal { from { opacity: 0; transform: translateY(40px); } to { opacity: 1; transform: translateY(0); } }
426
  @keyframes modal-enter { from { opacity: 0; transform: scale(0.9); } to { opacity: 1; transform: scale(1); } }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  .tabs {
81
  display: flex;
82
  gap: 12px;
 
83
  overflow-x: auto;
84
  padding: 10px;
85
  scrollbar-width: none;
86
+ flex-wrap: nowrap;
87
+ -webkit-overflow-scrolling: touch;
88
  }
89
 
90
  .tabs::-webkit-scrollbar { display: none; }
 
301
  .shap-chart {
302
  display: flex;
303
  flex-direction: column;
304
+ gap: 12px;
305
  margin-top: 40px;
306
  padding-top: 40px;
307
  border-top: 1px solid var(--border);
308
  }
309
 
310
  .shap-row {
311
+ display: grid;
312
+ grid-template-columns: 100px 1fr 40px;
313
  align-items: center;
314
  gap: 15px;
315
  }
316
 
317
+ .shap-viz {
318
+ height: 12px;
319
+ background: rgba(255, 255, 255, 0.02);
320
+ border-radius: 4px;
 
321
  position: relative;
322
+ display: flex;
323
+ align-items: center;
324
+ }
325
+
326
+ .shap-axis {
327
+ position: absolute;
328
+ left: 50%;
329
+ top: -5px;
330
+ bottom: -5px;
331
+ width: 1px;
332
+ background: rgba(255, 255, 255, 0.2);
333
+ z-index: 1;
334
  }
335
 
336
+ .shap-bar {
337
  height: 100%;
338
+ position: absolute;
339
+ transition: all 1s cubic-bezier(0.34, 1.56, 0.64, 1);
340
+ border-radius: 2px;
341
  }
342
 
343
+ .shap-bar.positive {
344
+ left: 50%;
345
  background: #10b981;
346
+ box-shadow: 0 0 15px rgba(16, 185, 129, 0.4);
347
  }
348
 
349
+ .shap-bar.negative {
350
+ right: 50%;
351
  background: #ef4444;
352
+ box-shadow: 0 0 15px rgba(239, 68, 68, 0.4);
353
  }
354
 
355
  #modal-overlay {
 
435
  header { flex-direction: row; justify-content: space-between; align-items: center; }
436
  .logo { align-items: flex-start; }
437
  .btn-group { flex-direction: row; }
438
+ .tabs { justify-content: center; }
439
  }
440
 
441
  @keyframes slide-reveal { from { opacity: 0; transform: translateY(40px); } to { opacity: 1; transform: translateY(0); } }
442
  @keyframes modal-enter { from { opacity: 0; transform: scale(0.9); } to { opacity: 1; transform: scale(1); } }
443
+
444
+ @media (max-width: 1024px) {
445
+ .main-layout { grid-template-columns: 1fr; }
446
+ .hero { padding: 80px 0 40px; }
447
+ }
448
+
449
+ @media (max-width: 640px) {
450
+ body { padding: 15px; }
451
+ .hero h1 { font-size: 2.5rem; }
452
+ .spec-grid { grid-template-columns: 1fr; }
453
+ .modal { width: 95%; margin: 10px; }
454
+ .modal-header, .modal-content { padding: 20px; }
455
+ .prediction-box { padding: 30px; }
456
+ .label-val { font-size: 2.5rem; }
457
+ .shap-row { flex-direction: column; align-items: flex-start; gap: 5px; }
458
+ .shap-row .score-label { width: 100% !important; text-align: left !important; }
459
+ .tab-btn { padding: 10px 20px; font-size: 0.85rem; }
460
+ }