/* ────────────────────────────────────────────── APPLICATION STATE ────────────────────────────────────────────── */ window.APP = { columns: [], numericColumns: [], target: '', task: '', leaderboard: null, metrics: [], bestModel: '', nPredictions: 0 }; /* ────────────────────────────────────────────── NAVIGATION ────────────────────────────────────────────── */ function showSection(name) { document.querySelectorAll('.section').forEach(s => s.classList.remove('active')); document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active')); const sec = document.getElementById('sec-' + name); if (sec) sec.classList.add('active'); const nav = document.querySelector('.nav-item[data-section="' + name + '"]'); if (nav) nav.classList.add('active'); } /* ────────────────────────────────────────────── TABLE RENDERER ────────────────────────────────────────────── */ function renderTable(containerId, rows, columns) { const el = document.getElementById(containerId); if (!el || !rows || !columns) { if (el) el.innerHTML = ''; return; } let html = ''; columns.forEach(c => { html += ''; }); html += ''; rows.forEach(r => { html += ''; columns.forEach(c => { const val = r[c] !== undefined && r[c] !== null ? String(r[c]) : ''; html += ''; }); html += ''; }); html += '
' + escHtml(String(c)) + '
' + escHtml(val) + '
'; el.innerHTML = html; } function escHtml(s) { const d = document.createElement('div'); d.textContent = s; return d.innerHTML; } /* ────────────────────────────────────────────── PLOTLY HELPER ────────────────────────────────────────────── */ function renderPlotly(divId, figure) { const el = document.getElementById(divId); if (!el) return; // Always clear any existing content (spinner, old chart) first el.innerHTML = ''; if (!figure || !figure.data) return; const style = getComputedStyle(document.documentElement); const textColor = style.getPropertyValue('--chart-text').trim() || '#71717a'; const gridColor = style.getPropertyValue('--chart-grid').trim() || 'rgba(30,27,75,0.15)'; const layout = Object.assign({}, figure.layout || {}, { paper_bgcolor: 'rgba(0,0,0,0)', plot_bgcolor: 'rgba(0,0,0,0)', font: { family: 'Inter, sans-serif', size: 12, color: textColor }, margin: figure.layout && figure.layout.margin ? figure.layout.margin : { l: 40, r: 20, t: 40, b: 40 } }); layout.xaxis = Object.assign({}, layout.xaxis || {}, { gridcolor: gridColor, zeroline: false }); layout.yaxis = Object.assign({}, layout.yaxis || {}, { gridcolor: gridColor, zeroline: false }); try { Plotly.newPlot(divId, figure.data, layout, { responsive: true, displayModeBar: false }); } catch (e) { el.innerHTML = '

Chart rendering failed

'; } } /* ────────────────────────────────────────────── FETCH WRAPPER ────────────────────────────────────────────── */ async function apiFetch(url, options) { try { const res = await fetch(url, options); if (!res.ok) { let msg = 'Request failed'; try { const e = await res.json(); msg = e.detail || e.error || e.message || msg; } catch (_) {} throw new Error(msg); } return res; } catch (err) { console.error('API Error:', url, err); throw err; } } async function apiJson(url, options) { const res = await apiFetch(url, options); return res.json(); } /* ────────────────────────────────────────────── FILE UPLOAD & DRAG-DROP ────────────────────────────────────────────── */ (function initUpload() { const area = document.getElementById('upload-area'); const input = document.getElementById('file-input'); area.addEventListener('dragover', e => { e.preventDefault(); e.stopPropagation(); area.classList.add('dragover'); }); area.addEventListener('dragleave', e => { e.preventDefault(); e.stopPropagation(); area.classList.remove('dragover'); }); area.addEventListener('drop', e => { e.preventDefault(); e.stopPropagation(); area.classList.remove('dragover'); if (e.dataTransfer.files.length) { input.files = e.dataTransfer.files; uploadFile(e.dataTransfer.files[0]); } }); input.addEventListener('change', () => { if (input.files.length) uploadFile(input.files[0]); }); })(); async function uploadFile(file) { const area = document.getElementById('upload-area'); area.innerHTML = `
Uploading ${escHtml(file.name)}...
Please wait
`; // Step 1: Upload the file let data; try { const fd = new FormData(); fd.append('file', file); const res = await fetch('/api/upload', { method: 'POST', body: fd }); if (!res.ok) { let msg = 'Upload failed'; try { const e = await res.json(); msg = e.detail || e.error || e.message || msg; } catch (_) {} throw new Error(msg); } data = await res.json(); } catch (err) { console.error('Upload error:', err); area.innerHTML = `
Upload failed
${escHtml(err.message)}
`; area.onclick = () => document.getElementById('file-input').click(); return; } // Step 2: Process the response (errors here should NOT show "Upload failed") try { APP.columns = data.columns || []; APP.numericColumns = data.numeric_columns || []; area.innerHTML = `
${escHtml(file.name)}
${data.rows || 0} rows, ${(data.columns || []).length} columns
`; area.onclick = null; // Reset Build section (clear previous training results) const resultsEl = document.getElementById('build-results'); if (resultsEl) resultsEl.style.display = 'none'; const evalPlots = document.getElementById('eval-plots'); if (evalPlots) evalPlots.innerHTML = ''; const lbTable = document.getElementById('leaderboard-table'); if (lbTable) lbTable.innerHTML = ''; const metricChart = document.getElementById('metric-chart'); if (metricChart) metricChart.innerHTML = ''; // Reset Explore chart const exploreChart = document.getElementById('explore-chart'); if (exploreChart) exploreChart.innerHTML = ''; // Reset Export const exportModel = document.getElementById('export-model'); if (exportModel) exportModel.textContent = '--'; const exportTask = document.getElementById('export-task'); if (exportTask) exportTask.textContent = '--'; renderMetrics(data); populateTargetSelect(data.columns || []); if (data.target) { document.getElementById('target-select').value = data.target; APP.target = data.target; APP.task = data.task || 'classification'; document.getElementById('task-badge-wrap').innerHTML = '' + escHtml(APP.task) + ''; if (APP.task === 'regression') { selectTaskByValue('regression'); } else { selectTaskByValue('classification'); } } if (data.preview) renderTable('preview-table', data.preview.rows, data.preview.columns); if (data.types) renderTable('types-table', data.types.rows, data.types.columns); if (data.stats) renderTable('stats-table', data.stats.rows, data.stats.columns); document.getElementById('data-metrics').style.display = ''; document.getElementById('target-section').style.display = ''; document.getElementById('data-tabs-section').style.display = ''; // Render auto-insights if (data.insights && data.insights.length) { renderInsights(data.insights); document.getElementById('data-insights').style.display = ''; } populateExploreDropdowns(); } catch (err2) { console.error('Post-upload processing error:', err2); } } function renderMetrics(data) { const grid = document.getElementById('data-metrics'); const items = [ { label: 'Rows', value: (data.rows || 0).toLocaleString() }, { label: 'Columns', value: data.columns ? data.columns.length : 0 }, { label: 'Missing', value: (data.missing || 0).toLocaleString() }, { label: 'Duplicates', value: (data.duplicates || 0).toLocaleString() } ]; grid.innerHTML = items.map(m => `
${m.label}
${m.value}
`).join(''); } function populateTargetSelect(cols) { const sel = document.getElementById('target-select'); sel.innerHTML = ''; cols.forEach(c => { sel.innerHTML += ''; }); } async function setTarget(col) { if (!col) { document.getElementById('task-badge-wrap').innerHTML = ''; return; } try { const fd = new FormData(); fd.append('target', col); const data = await apiJson('/api/target', { method: 'POST', body: fd }); APP.target = col; APP.task = data.task || 'Classification'; document.getElementById('task-badge-wrap').innerHTML = '' + escHtml(APP.task) + ''; // Sync build section radio if (APP.task.toLowerCase() === 'regression') { selectTaskByValue('regression'); } else { selectTaskByValue('classification'); } } catch (err) { document.getElementById('task-badge-wrap').innerHTML = 'Error'; } } /* ────────────────────────────────────────────── DATA TABS ────────────────────────────────────────────── */ function switchDataTab(tab) { document.querySelectorAll('#data-tab-bar .tab-btn').forEach(b => b.classList.remove('active')); document.querySelectorAll('#data-tabs-section .tab-panel').forEach(p => p.classList.remove('active')); event.target.classList.add('active'); document.getElementById('data-tab-' + tab).classList.add('active'); } /* ────────────────────────────────────────────── EXPLORE SECTION ────────────────────────────────────────────── */ // Plot type configuration: which features each plot needs const PLOT_CONF = { histogram: { feat1: true, feat2: false, endpoint: '/api/explore/distribution' }, kde: { feat1: true, feat2: false, endpoint: '/api/explore/kde' }, boxplot: { feat1: true, feat2: false, endpoint: '/api/explore/boxplot' }, violin: { feat1: true, feat2: false, endpoint: '/api/explore/violin' }, missing_bar: { feat1: false, feat2: false, endpoint: '/api/explore/missing' }, missing_heatmap: { feat1: false, feat2: false, endpoint: '/api/explore/missing_heatmap' }, correlation: { feat1: false, feat2: false, endpoint: '/api/explore/correlation' }, pairplot: { feat1: false, feat2: false, endpoint: '/api/explore/pairplot' }, scatter: { feat1: true, feat2: true, endpoint: '/api/explore/scatter_xy' }, jointplot: { feat1: true, feat2: true, endpoint: '/api/explore/jointplot' }, countplot: { feat1: true, feat2: false, endpoint: '/api/explore/countplot' }, pie: { feat1: false, feat2: false, endpoint: '/api/explore/pie' }, class_dist: { feat1: false, feat2: false, endpoint: '/api/explore/target' }, target_hist: { feat1: false, feat2: false, endpoint: '/api/explore/target' }, mean_target: { feat1: true, feat2: false, endpoint: '/api/explore/mean_target' }, scatter_index: { feat1: true, feat2: false, endpoint: '/api/explore/scatter_index' }, grouped_box: { feat1: true, feat2: false, endpoint: '/api/explore/grouped_box' }, facetgrid: { feat1: true, feat2: false, endpoint: '/api/explore/facetgrid' } }; function populateExploreDropdowns() { const cols = APP.columns; ['plot-feat1', 'plot-feat2'].forEach(id => { const sel = document.getElementById(id); if (!sel) return; sel.innerHTML = ''; cols.forEach(c => { sel.innerHTML += ''; }); }); } function switchExploreMain(tab) { document.querySelectorAll('#explore-main-tabs .tab-btn').forEach(b => b.classList.remove('active')); document.querySelectorAll('#sec-explore .tab-panel').forEach(p => p.classList.remove('active')); event.target.classList.add('active'); document.getElementById('explore-panel-' + tab).classList.add('active'); if (tab === 'quality') loadQuality(); } function onPlotTypeChange() { const type = document.getElementById('plot-type').value; const conf = PLOT_CONF[type] || {}; document.getElementById('feat1-group').style.display = conf.feat1 ? '' : 'none'; document.getElementById('feat2-group').style.display = conf.feat2 ? '' : 'none'; // Reset chart visibility state const chart = document.getElementById('explore-chart'); const msg = document.getElementById('explore-msg'); chart.style.display = ''; // Auto-generate if no feature selection needed if (!conf.feat1 && !conf.feat2) { generatePlot(); } else { // Check if a feature is already selected — auto-generate const f1 = document.getElementById('plot-feat1').value; const f2 = document.getElementById('plot-feat2').value; if (conf.feat1 && f1 && (!conf.feat2 || f2)) { generatePlot(); } else { chart.innerHTML = ''; msg.style.display = ''; msg.textContent = 'Select a feature to generate the plot'; } } } async function generatePlot() { const type = document.getElementById('plot-type').value; const conf = PLOT_CONF[type]; if (!conf) return; const f1 = document.getElementById('plot-feat1').value; const f2 = document.getElementById('plot-feat2').value; // Validate required features if (conf.feat1 && !f1) return; if (conf.feat2 && !f2) return; const chart = document.getElementById('explore-chart'); const msg = document.getElementById('explore-msg'); // Always reset visibility before loading chart.style.display = ''; msg.style.display = 'none'; chart.innerHTML = '
'; try { const fd = new FormData(); if (conf.feat1 && f1) fd.append('feature', f1); if (conf.feat2 && f2) fd.append('feature2', f2); const data = await apiJson(conf.endpoint, { method: 'POST', body: fd }); if (data.figure) { chart.style.display = ''; msg.style.display = 'none'; renderPlotly('explore-chart', data.figure); } else { chart.innerHTML = ''; chart.style.display = 'none'; msg.style.display = ''; msg.textContent = data.message || 'No data available for this plot'; } } catch (err) { chart.style.display = ''; chart.innerHTML = '

' + escHtml(err.message) + '

'; msg.style.display = 'none'; } } async function loadQuality() { try { const data = await apiJson('/api/explore/quality', { method: 'POST' }); if (data.rows && data.rows.length) { const cols = Object.keys(data.rows[0]); renderTable('quality-table', data.rows, cols); } else { document.getElementById('quality-table').innerHTML = '

No quality data available

'; } } catch (err) { document.getElementById('quality-table').innerHTML = '

' + escHtml(err.message) + '

'; } } /* ────────────────────────────────────────────── BUILD SECTION ────────────────────────────────────────────── */ function selectTask(el, value) { document.querySelectorAll('#task-radio-group .radio-item').forEach(r => r.classList.remove('active')); el.classList.add('active'); el.querySelector('input').checked = true; APP.task = value.charAt(0).toUpperCase() + value.slice(1); } function selectTaskByValue(value) { const items = document.querySelectorAll('#task-radio-group .radio-item'); items.forEach(item => { const input = item.querySelector('input'); if (input.value === value) { item.classList.add('active'); input.checked = true; } else { item.classList.remove('active'); } }); } function toggleCollapsible(header) { header.classList.toggle('open'); header.nextElementSibling.classList.toggle('open'); } async function trainModels() { const btn = document.getElementById('train-btn'); const loading = document.getElementById('train-loading'); const statusEl = document.getElementById('train-status'); btn.disabled = true; loading.classList.remove('hidden'); statusEl.textContent = 'Initializing pipeline...'; document.getElementById('build-results').style.display = 'none'; const fd = new FormData(); const task = document.querySelector('input[name="task"]:checked').value; fd.append('task', task); fd.append('test_size', document.getElementById('test-size').value); fd.append('cv_folds', document.getElementById('cv-folds').value); // Preprocessing options document.querySelectorAll('input[name="prep"]').forEach(cb => { fd.append(cb.value, cb.checked ? 'true' : 'false'); }); // Progress simulation const phases = [ 'Preprocessing data...', 'Setting up experiment...', 'Comparing models...', 'Evaluating best model...', 'Generating plots...' ]; let pi = 0; const progressTimer = setInterval(() => { if (pi < phases.length) { statusEl.textContent = phases[pi++]; } }, 4000); try { const data = await apiJson('/api/train', { method: 'POST', body: fd }); clearInterval(progressTimer); loading.classList.add('hidden'); btn.disabled = false; // Store state APP.bestModel = data.model_name || '--'; APP.leaderboard = data.leaderboard || null; APP.metrics = data.metrics || []; APP.nPredictions = data.n_predictions || 0; // Show results document.getElementById('build-results').style.display = ''; document.getElementById('best-model-name').textContent = APP.bestModel; // Training Summary Banner if (data.summary) renderTrainingSummary(data.summary); // Leaderboard if (data.leaderboard) { renderTable('leaderboard-table', data.leaderboard.rows, data.leaderboard.columns); } // Metric dropdown const metricSel = document.getElementById('metric-select'); metricSel.innerHTML = ''; (data.metrics || []).forEach(m => { metricSel.innerHTML += ''; }); renderMetricChart(); // Eval plots renderEvalPlots(data.plots || []); // Update export document.getElementById('export-model').textContent = APP.bestModel; document.getElementById('export-task').textContent = APP.task || task; document.getElementById('export-preds').textContent = APP.nPredictions.toLocaleString(); } catch (err) { clearInterval(progressTimer); loading.classList.add('hidden'); btn.disabled = false; statusEl.textContent = 'Training failed: ' + err.message; setTimeout(() => loading.classList.add('hidden'), 2000); alert('Training failed: ' + err.message); } } function renderMetricChart() { if (!APP.leaderboard) return; const metric = document.getElementById('metric-select').value; if (!metric) return; const lb = APP.leaderboard; const modelCol = lb.columns.find(c => c.toLowerCase() === 'model') || lb.columns[0]; const metricIdx = lb.columns.indexOf(metric); if (metricIdx === -1) return; const models = lb.rows.map(r => r[modelCol] || ''); const values = lb.rows.map(r => parseFloat(r[metric]) || 0); const cs = getComputedStyle(document.documentElement); const isLight = document.documentElement.getAttribute('data-theme') === 'light'; // Color palette: top 3 get strong indigo, rest get medium indigo (NEVER faded/transparent) const barPalette = ['#4f46e5', '#6366f1', '#818cf8', '#a5b4fc', '#93a0f5', '#7c8cf0', '#6b7de8', '#8b96f2', '#7988ed', '#6a7ae6']; const colors = values.map((_, i) => barPalette[i % barPalette.length]); const barOutline = isLight ? '#4338ca' : 'rgba(255,255,255,0.15)'; const barOutlineWidth = isLight ? 1.5 : 1; const valTextColor = cs.getPropertyValue('--chart-text').trim() || '#a1a1aa'; const figure = { data: [{ type: 'bar', x: values, y: models, orientation: 'h', marker: { color: colors, line: { width: barOutlineWidth, color: barOutline } }, text: values.map(v => v.toFixed(4)), textposition: 'outside', textfont: { family: 'Inter', size: 11, color: valTextColor } }], layout: { yaxis: { autorange: 'reversed', tickfont: { size: 11 } }, xaxis: { title: metric }, margin: { l: 160, r: 60, t: 20, b: 40 }, height: Math.max(300, models.length * 36 + 80) } }; renderPlotly('metric-chart', figure); } function renderEvalPlots(plots) { const container = document.getElementById('eval-plots'); if (!plots || !plots.length) { container.innerHTML = '

No evaluation plots available

'; return; } container.innerHTML = plots.map((p, i) => `
${escHtml(p.label || 'Plot')}
`).join(''); // Render each Plotly figure plots.forEach((p, i) => { if (p.figure && p.figure.data) { renderPlotly('eval-plot-' + i, p.figure); } }); } /* ────────────────────────────────────────────── TUNING ────────────────────────────────────────────── */ async function tuneModel() { const btn = document.getElementById('tune-btn'); const loading = document.getElementById('tune-loading'); btn.disabled = true; loading.classList.remove('hidden'); document.getElementById('tune-results').style.display = 'none'; const fd = new FormData(); fd.append('method', document.getElementById('tune-method').value); fd.append('iterations', document.getElementById('tune-iter').value); try { const data = await apiJson('/api/tune', { method: 'POST', body: fd }); loading.classList.add('hidden'); btn.disabled = false; document.getElementById('tune-results').style.display = ''; const improvedEl = document.getElementById('tune-improved'); if (data.improved) { improvedEl.innerHTML = 'Model improved after tuning: ' + escHtml(data.model_name || '') + ''; APP.bestModel = data.model_name || APP.bestModel; document.getElementById('best-model-name').textContent = APP.bestModel; document.getElementById('export-model').textContent = APP.bestModel; } else { improvedEl.innerHTML = 'No improvement found. Keeping original model.'; } if (data.tune_results) { renderTable('tune-table', data.tune_results.rows, data.tune_results.columns); } // Update eval plots if provided if (data.plots && data.plots.length) { renderEvalPlots(data.plots); } } catch (err) { loading.classList.add('hidden'); btn.disabled = false; alert('Tuning failed: ' + err.message); } } /* ────────────────────────────────────────────── EXPORT ────────────────────────────────────────────── */ function downloadExport(type) { const url = '/api/export/' + type; const a = document.createElement('a'); a.href = url; a.download = ''; document.body.appendChild(a); a.click(); document.body.removeChild(a); } /* ────────────────────────────────────────────── AUTO INSIGHTS ────────────────────────────────────────────── */ const INSIGHT_ICONS = { success: '', warning: '', info: '' }; function renderInsights(insights) { const el = document.getElementById('data-insights'); if (!el || !insights || !insights.length) return; el.innerHTML = '
' + insights.map((ins, i) => `
${INSIGHT_ICONS[ins.type] || INSIGHT_ICONS.info}
${escHtml(ins.title || '')}
${escHtml(ins.detail || '')}
`).join('') + '
'; } /* ────────────────────────────────────────────── TRAINING SUMMARY ────────────────────────────────────────────── */ function renderTrainingSummary(summary) { if (!summary) return; const container = document.getElementById('training-summary'); if (!container) return; const featurePills = (summary.top_features || []).map(f => `${escHtml(f)}` ).join(''); container.innerHTML = `
Training Summary
${escHtml(summary.key_metric || '')}
${summary.key_metric_value || '--'}
Models Compared
${summary.n_models || '--'}
Best Model
${escHtml(summary.model || '--')}
${featurePills ? '
Top Predictive Features
' + featurePills + '
' : ''}
${escHtml(summary.text || '')}
`; container.style.display = ''; } /* ────────────────────────────────────────────── THEME TOGGLE ────────────────────────────────────────────── */ function toggleTheme() { const html = document.documentElement; const isLight = html.getAttribute('data-theme') === 'light'; const newTheme = isLight ? 'dark' : 'light'; html.setAttribute('data-theme', newTheme); localStorage.setItem('automl-theme', newTheme); updateThemeIcon(newTheme); reThemePlotly(newTheme); } function updateThemeIcon(theme) { const moon = document.getElementById('icon-moon'); const sun = document.getElementById('icon-sun'); if (theme === 'light') { moon.style.display = 'none'; sun.style.display = ''; } else { moon.style.display = ''; sun.style.display = 'none'; } } function reThemePlotly(theme) { // Small delay to let CSS variables update requestAnimationFrame(() => { const style = getComputedStyle(document.documentElement); const textColor = style.getPropertyValue('--chart-text').trim() || '#71717a'; const gridColor = style.getPropertyValue('--chart-grid').trim() || 'rgba(30,27,75,0.15)'; document.querySelectorAll('.js-plotly-plot').forEach(el => { try { Plotly.relayout(el, { 'font.color': textColor, 'xaxis.gridcolor': gridColor, 'yaxis.gridcolor': gridColor, }); } catch (_) {} }); }); } // Restore saved theme (function() { const saved = localStorage.getItem('automl-theme'); if (saved === 'light') { document.documentElement.setAttribute('data-theme', 'light'); updateThemeIcon('light'); } })(); /* ────────────────────────────────────────────── INIT ────────────────────────────────────────────── */ showSection('data');