const state = {
activeId: null,
fields: {},
specs: {}
};
const elements = {
tabs: document.querySelectorAll('.tab-btn'),
fieldsContainer: document.getElementById('fields-container'),
inferenceForm: document.getElementById('inference-form'),
resultDisplay: document.getElementById('result-display'),
infoTrigger: document.querySelector('.info-trigger'),
modal: document.getElementById('modal-overlay'),
modalClose: document.getElementById('modal-close'),
specsContent: document.getElementById('specs-content'),
modalTitle: document.getElementById('modal-title'),
btnAutofill: document.getElementById('btn-autofill')
};
async function autofill() {
elements.btnAutofill.disabled = true;
const btnText = elements.btnAutofill.querySelector('.btn-text');
const originalText = btnText.innerText;
btnText.innerText = 'Extracting...';
try {
const res = await fetch(`/api/sample/${state.activeId}`);
const data = await res.json();
Object.entries(data).forEach(([key, val]) => {
const input = elements.inferenceForm.querySelector(`[name="${key}"]`);
if (input) {
input.value = val;
input.style.borderColor = 'var(--secondary)';
input.style.boxShadow = '0 0 15px rgba(0, 210, 255, 0.3)';
setTimeout(() => {
input.style.borderColor = '';
input.style.boxShadow = '';
}, 1500);
}
});
} catch (e) {
console.error(e);
} finally {
elements.btnAutofill.disabled = false;
btnText.innerText = originalText;
}
}
elements.btnAutofill.onclick = autofill;
async function loadTab(id) {
if (state.activeId === id) return;
state.activeId = id;
elements.tabs.forEach(btn => btn.classList.toggle('active', btn.dataset.id === id));
elements.fieldsContainer.style.opacity = '0';
elements.fieldsContainer.style.transform = 'translateY(10px)';
try {
const [fields, info] = await Promise.all([
fetch(`/api/fields/${id}`).then(r => r.json()),
fetch(`/api/info/${id}`).then(r => r.json())
]);
state.fields[id] = fields;
state.specs[id] = info;
renderFields(fields);
elements.resultDisplay.innerHTML = `
System Initialized. Awaiting Input Data.
`;
} catch (e) {
elements.fieldsContainer.innerHTML = `System Error: ${e.message}
`;
} finally {
setTimeout(() => {
elements.fieldsContainer.style.opacity = '1';
elements.fieldsContainer.style.transform = 'translateY(0)';
elements.fieldsContainer.style.transition = 'all 0.5s cubic-bezier(0.2, 0.8, 0.2, 1)';
}, 50);
}
}
function renderFields(fields) {
elements.fieldsContainer.innerHTML = fields.map((f, i) => `
${f.type === 'select' ? `
` : `
`}
`).join('');
}
elements.inferenceForm.onsubmit = async (e) => {
e.preventDefault();
const formData = new FormData(elements.inferenceForm);
const payload = { features: {} };
let hasEmpty = false;
formData.forEach((v, k) => {
if (v === "") hasEmpty = true;
payload.features[k] = v;
});
if (hasEmpty) {
alert("Please complete all parameters before execution.");
return;
}
const btn = document.querySelector('.btn-primary');
const btnText = btn.querySelector('.btn-text');
const originalText = btnText.innerText;
btn.disabled = true;
btnText.innerText = 'Calculating...';
elements.resultDisplay.scrollIntoView({ behavior: 'smooth', block: 'center' });
try {
const res = await fetch(`/api/run/${state.activeId}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
const data = await res.json();
if (!res.ok) throw new Error(data.detail || 'Inference engine failure');
renderResult(data);
} catch (e) {
elements.resultDisplay.innerHTML = `
Execution Error: ${e.message}
`;
} finally {
btn.disabled = false;
btnText.innerText = originalText;
}
};
function renderResult(data) {
const scores = Object.entries(data.scores).sort((a, b) => b[1] - a[1]);
const shap = Object.entries(data.shap || {});
const maxShap = Math.max(...shap.map(s => Math.abs(s[1])), 0.001);
elements.resultDisplay.innerHTML = `
Prediction Output
${data.result}
Probability Vectors
${scores.map(([label, score]) => `
${label}
${(score * 100).toFixed(1)}%
`).join('')}
SHAP Decision Drivers
${shap.map(([feat, val]) => `
${feat}
${val >= 0 ? '+' : ''}${val.toFixed(2)}
`).join('')}
`;
setTimeout(() => {
const fills = elements.resultDisplay.querySelectorAll('.bar-fill');
scores.forEach((s, i) => fills[i].style.width = `${s[1] * 100}%`);
const shapBars = elements.resultDisplay.querySelectorAll('.shap-bar');
shap.forEach((s, i) => {
const width = (Math.abs(s[1]) / maxShap) * 50; // Max 50% from center
shapBars[i].style.width = `${width}%`;
});
}, 100);
}
elements.infoTrigger.onclick = () => {
const info = state.specs[state.activeId];
if (!info) return;
elements.modalTitle.innerText = `${info.title} Architecture`;
elements.specsContent.innerHTML = `
Volume
${info.dataset.rows.toLocaleString()}
Dimensions
${info.dataset.cols}
Model Type
${info.model.type || 'Ensemble'}
Target
${info.dataset.target}
${info.metrics.length ? Object.keys(info.metrics[0]).map(k => `| ${k} | `).join('') : ''}
${info.metrics.map(m => `
${Object.values(m).map(v => `| ${typeof v === 'number' ? v.toFixed(4) : v} | `).join('')}
`).join('')}
Explore Kaggle Dataset
`;
elements.modal.classList.remove('hidden');
document.body.style.overflow = 'hidden';
};
elements.modalClose.onclick = () => {
elements.modal.classList.add('hidden');
document.body.style.overflow = 'auto';
};
window.onclick = (e) => {
if (e.target === elements.modal) {
elements.modal.classList.add('hidden');
document.body.style.overflow = 'auto';
}
};
elements.tabs.forEach(btn => btn.onclick = () => {
loadTab(btn.dataset.id);
btn.scrollIntoView({ behavior: 'smooth', inline: 'center', block: 'nearest' });
});
const initialId = elements.tabs[0]?.dataset.id;
if (initialId) loadTab(initialId);