Spaces:
Sleeping
Sleeping
feat(inference): fix ui and shap
Browse files- inference/template/index.html +8 -3
- inference/template/script.js +12 -8
- inference/template/styles.css +50 -16
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 |
|
| 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
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
| 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])),
|
| 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;">
|
| 180 |
${shap.map(([feat, val]) => `
|
| 181 |
<div class="shap-row">
|
| 182 |
-
<span class="score-label" style="
|
| 183 |
-
<div class="shap-
|
| 184 |
-
<div class="shap-
|
|
|
|
| 185 |
</div>
|
| 186 |
-
<span class="score-label" style="text-align: right;
|
| 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
|
| 198 |
-
shap.forEach((s, i) =>
|
|
|
|
|
|
|
|
|
|
| 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:
|
| 304 |
margin-top: 40px;
|
| 305 |
padding-top: 40px;
|
| 306 |
border-top: 1px solid var(--border);
|
| 307 |
}
|
| 308 |
|
| 309 |
.shap-row {
|
| 310 |
-
display:
|
|
|
|
| 311 |
align-items: center;
|
| 312 |
gap: 15px;
|
| 313 |
}
|
| 314 |
|
| 315 |
-
.shap-
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
border-radius: 100px;
|
| 320 |
position: relative;
|
| 321 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 322 |
}
|
| 323 |
|
| 324 |
-
.shap-bar
|
| 325 |
height: 100%;
|
| 326 |
-
|
| 327 |
-
transition:
|
|
|
|
| 328 |
}
|
| 329 |
|
| 330 |
-
.shap-bar
|
|
|
|
| 331 |
background: #10b981;
|
| 332 |
-
box-shadow: 0 0
|
| 333 |
}
|
| 334 |
|
| 335 |
-
.shap-bar
|
|
|
|
| 336 |
background: #ef4444;
|
| 337 |
-
box-shadow: 0 0
|
| 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 |
+
}
|