AutoML / frontend /main.js
Al1Abdullah's picture
Implement unified sidebar toggle and refine mobile UI
1ffd5bd
document.addEventListener('DOMContentLoaded', () => {
const navLinks = document.querySelectorAll('.nav-link');
const pages = document.querySelectorAll('.page');
const sidebar = document.querySelector('.sidebar');
const mainContent = document.querySelector('.main-content');
const sidebarToggle = document.getElementById('sidebar-toggle'); // Unified toggle button
const overlay = document.querySelector('.overlay');
const loader = document.querySelector('.loader');
function animateNavText() {
const navTexts = document.querySelectorAll('.nav-text');
navTexts.forEach(text => {
text.style.animation = 'none';
text.offsetHeight; // Trigger reflow
text.style.animation = '';
});
}
navLinks.forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
const pageId = link.dataset.page;
pages.forEach(page => {
page.classList.remove('active');
});
navLinks.forEach(navLink => {
navLink.classList.remove('active');
});
document.getElementById(pageId).classList.add('active');
link.classList.add('active');
// Close sidebar and overlay after navigation on mobile
if (isMobileView() && sidebar.classList.contains('active')) {
sidebar.classList.remove('active');
overlay.classList.remove('active');
sidebarToggle.querySelector('i').classList.remove('fa-times');
sidebarToggle.querySelector('i').classList.add('fa-bars');
}
});
});
// Function to check if it's a mobile view
function isMobileView() {
return window.innerWidth <= 768;
}
// Initial setup for sidebar based on screen size
function setupSidebarState() {
if (isMobileView()) {
sidebar.classList.remove('collapsed'); // Ensure desktop collapsed state is removed
sidebar.classList.remove('active'); // Start hidden on mobile
overlay.classList.remove('active');
mainContent.classList.remove('collapsed'); // Main content always full width on mobile
sidebarToggle.classList.add('mobile-toggle'); // Ensure mobile toggle styles are applied
sidebarToggle.querySelector('i').classList.remove('fa-times'); // Ensure bars icon
sidebarToggle.querySelector('i').classList.add('fa-bars');
} else {
// Desktop default state
sidebar.classList.remove('active'); // Ensure mobile active state is removed
overlay.classList.remove('active');
sidebarToggle.classList.remove('mobile-toggle'); // Remove mobile toggle styles
sidebarToggle.querySelector('i').classList.remove('fa-bars'); // Ensure arrow icon
sidebarToggle.querySelector('i').classList.add('fa-angle-left');
// Set initial desktop collapsed state if desired, or leave expanded
// sidebar.classList.add('collapsed');
// mainContent.classList.add('collapsed');
}
}
setupSidebarState(); // Call on initial load
// Unified event listener for sidebar toggle
sidebarToggle.addEventListener('click', () => {
if (isMobileView()) {
// Mobile behavior: sidebar as overlay
sidebar.classList.toggle('active');
overlay.classList.toggle('active');
// Change icon based on sidebar state
if (sidebar.classList.contains('active')) {
sidebarToggle.querySelector('i').classList.remove('fa-bars');
sidebarToggle.querySelector('i').classList.add('fa-times');
} else {
sidebarToggle.querySelector('i').classList.remove('fa-times');
sidebarToggle.querySelector('i').classList.add('fa-bars');
}
} else {
// Desktop behavior: sidebar collapses/expands
sidebar.classList.toggle('collapsed');
mainContent.classList.toggle('collapsed');
// Change icon based on sidebar state
if (sidebar.classList.contains('collapsed')) {
sidebarToggle.querySelector('i').classList.remove('fa-angle-left');
sidebarToggle.querySelector('i').classList.add('fa-angle-right');
} else {
sidebarToggle.querySelector('i').classList.remove('fa-angle-right');
sidebarToggle.querySelector('i').classList.add('fa-angle-left');
}
if (!sidebar.classList.contains('collapsed')) {
animateNavText();
}
}
});
// Event listener for overlay click (to close sidebar on mobile)
overlay.addEventListener('click', () => {
if (isMobileView() && sidebar.classList.contains('active')) {
sidebar.classList.remove('active');
overlay.classList.remove('active');
sidebarToggle.querySelector('i').classList.remove('fa-times');
sidebarToggle.querySelector('i').classList.add('fa-bars');
}
});
// Adjust sidebar on window resize
window.addEventListener('resize', setupSidebarState);
function formatAIResponse(text) {
text = text.replace(/\*\*(.*?)\*\*/g, '<strong>$1<\/strong>');
text = text.replace(/^\d+\.\s+(.*)/gm, '<li>$1<\/li>');
text = text.replace(/(<li>.*<\/li>)/s, '<ol>$1<\/ol>');
text = text.replace(/^\*\s+(.*)/gm, '<li>$1<\/li>');
text = text.replace(/(<li>.*<\/li>)/s, '<ul>$1<\/ul>');
return text;
}
const csvUpload = document.getElementById('csv-upload');
const uploadStatus = document.getElementById('upload-status');
const columnList = document.getElementById('column-list');
const plotType = document.getElementById('plot-type');
const plotCol1 = document.getElementById('plot-col1');
const plotCol2 = document.getElementById('plot-col2');
const scatterColorContainer = document.getElementById('scatter-color-container');
const scatterColor = document.getElementById('scatter-color');
const generatePlot = document.getElementById('generate-plot');
const plotImg = document.getElementById('plot-img');
const plotError = document.getElementById('plot-error');
const learningType = document.getElementById('learning-type');
const modelDropdown = document.getElementById('model-dropdown');
const targetColumnDropdown = document.getElementById('target-column-dropdown');
const trainModel = document.getElementById('train-model');
const trainOutput = document.getElementById('train-output');
const aiQuestion = document.getElementById('ai-question');
const askAi = document.getElementById('ask-ai');
const aiAnswer = document.getElementById('ai-answer');
csvUpload.addEventListener('change', async (event) => {
const file = event.target.files[0];
if (!file) return;
uploadStatus.textContent = 'Uploading...';
const formData = new FormData();
formData.append('file', file);
try {
const response = await fetch('/api/upload', {
method: 'POST',
body: formData
});
const result = await response.json();
uploadStatus.textContent = result.message || result.error;
if (response.ok) {
updateColumnSelectors();
setLearningType();
updatePlotOptions();
}
} catch (error) {
uploadStatus.textContent = `Error: ${error.message}`;
}
});
async function updateColumnSelectors() {
try {
const response = await fetch('/api/columns');
const result = await response.json();
const columns = result.columns || [];
[columnList, plotCol1, plotCol2, targetColumnDropdown, scatterColor].forEach(selector => {
selector.innerHTML = '';
const defaultOption = document.createElement('option');
defaultOption.value = 'None';
defaultOption.textContent = 'None';
selector.appendChild(defaultOption);
columns.forEach(col => {
const option = document.createElement('option');
option.value = col;
option.textContent = col;
selector.appendChild(option);
});
});
} catch (error) {
console.error('Error updating column selectors:', error);
}
}
plotType.addEventListener('change', () => {
if (plotType.value === 'Scatter') {
scatterColorContainer.style.display = 'block';
} else {
scatterColorContainer.style.display = 'none';
}
});
generatePlot.addEventListener('click', async () => {
if (!plotType.value) {
plotError.textContent = 'Please select a plot type.';
return;
}
loader.style.display = 'block';
plotImg.src = '';
plotError.textContent = '';
const body = {
plot_type: plotType.value,
col1: plotCol1.value,
col2: plotCol2.value,
color_col: scatterColor.value
};
try {
const response = await fetch('/api/plot', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
const result = await response.json();
if (result.image) {
plotImg.src = `data:image/png;base64,${result.image}`;
} else {
plotError.textContent = result.error;
}
} catch (error) {
plotError.textContent = `Error: ${error.message}`;
} finally {
loader.style.display = 'none';
}
});
function formatMetrics(metrics) {
let formatted = '\n';
for (const [key, value] of Object.entries(metrics)) {
if (typeof value === 'object' && value !== null) {
formatted += `<strong>${key}:<\/strong>\n`;
for (const [subKey, subValue] of Object.entries(value)) {
formatted += ` ${subKey}: ${subValue}\n`;
}
} else {
formatted += `<strong>${key}:<\/strong> ${value}\n`;
}
}
return formatted;
}
async function setLearningType() {
try {
const response = await fetch('/api/learning_type');
const result = await response.json();
if (result.learning_type) {
learningType.disabled = false;
learningType.value = result.learning_type;
learningType.dispatchEvent(new Event('change'));
learningType.disabled = true;
if (result.learning_type === 'Supervised' && result.target_column) {
targetColumnDropdown.value = result.target_column;
}
}
} catch (error) {
console.error('Error setting learning type:', error);
}
}
async function updatePlotOptions() {
try {
const response = await fetch('/api/plot_options');
const result = await response.json();
const plots = result.plots || [];
plotType.innerHTML = '';
const defaultOption = document.createElement('option');
defaultOption.value = '';
defaultOption.textContent = 'Select Plot Type';
plotType.appendChild(defaultOption);
plots.forEach(plotName => {
const option = document.createElement('option');
option.value = plotName;
option.textContent = plotName;
plotType.appendChild(option);
});
} catch (error) {
console.error('Error updating plot options:', error);
}
}
trainModel.addEventListener('click', async () => {
trainOutput.textContent = 'Training in progress...';
loader.style.display = 'block';
const body = {
learning_type: learningType.value,
model_name: modelDropdown.value,
target_col: targetColumnDropdown.value
};
try {
const response = await fetch('/api/train', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
const result = await response.json();
let output = result.message || result.error;
if (result.metrics) {
output += formatMetrics(result.metrics);
}
if (result.result) {
output += `\n<strong>Result:<\/strong> ${JSON.stringify(result.result, null, 2)}`;
}
trainOutput.innerHTML = output;
} catch (error) {
trainOutput.textContent = `Error: ${error.message}`;
}
});
askAi.addEventListener('click', async () => {
aiAnswer.textContent = 'Thinking...';
loader.style.display = 'block';
const body = {
user_query: aiQuestion.value
};
try {
const response = await fetch('/api/ask', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
const result = await response.json();
aiAnswer.innerHTML = formatAIResponse(result.answer || result.error);
} catch (error) {
aiAnswer.textContent = `Error: ${error.message}`;
} finally {
loader.style.display = 'none';
}
});
learningType.addEventListener('change', () => {
const supervisedModels = ["Logistic Regression", "Naive Bayes", "Decision Tree", "Random Forest", "SVM", "KNN", "XGBoost", "CatBoost", "Linear Regression"];
const unsupervisedModels = ["KMeans", "DBSCAN", "PCA"];
const models = learningType.value === 'Supervised' ? supervisedModels : unsupervisedModels;
modelDropdown.innerHTML = '';
models.forEach(model => {
const option = document.createElement('option');
option.value = model;
option.textContent = model;
modelDropdown.appendChild(option);
});
});
learningType.dispatchEvent(new Event('change'));
// Add click-to-copy functionality to output boxes
[trainOutput, aiAnswer, uploadStatus, plotError].forEach(el => {
el.addEventListener('click', () => {
const textToCopy = el.textContent;
navigator.clipboard.writeText(textToCopy).then(() => {
const originalText = el.textContent;
el.textContent = 'Copied!';
setTimeout(() => {
el.textContent = originalText;
}, 1000);
});
});
});
});