|
|
|
const API_URL = 'https://nolanzandi-virtual-data-analyst.hf.space'; |
|
const PREDICT_ENDPOINT = `${API_URL}/api/predict`; |
|
const SAMPLE_ENDPOINT = `${API_URL}/api/sample`; |
|
|
|
|
|
async function handleFileUpload(file) { |
|
try { |
|
|
|
document.querySelector('.upload-icon').style.display = 'none'; |
|
document.querySelector('.loading-spinner').style.display = 'block'; |
|
document.querySelector('.progress-bar').style.display = 'block'; |
|
|
|
|
|
const formData = new FormData(); |
|
formData.append('file', file); |
|
|
|
|
|
updateFileInfo(file); |
|
|
|
|
|
const progressInterval = simulateProgress(); |
|
|
|
|
|
const response = await fetch(API_URL, { |
|
method: 'POST', |
|
body: formData |
|
}); |
|
|
|
if (!response.ok) { |
|
throw new Error('API request failed'); |
|
} |
|
|
|
const data = await response.json(); |
|
|
|
|
|
clearInterval(progressInterval); |
|
|
|
|
|
showSuccessState(); |
|
|
|
|
|
handleApiResponse(data); |
|
|
|
} catch (error) { |
|
console.error('Error:', error); |
|
showErrorState(error.message); |
|
} |
|
} |
|
|
|
function updateFileInfo(file) { |
|
const fileInfo = document.getElementById('fileInfo'); |
|
const fileName = fileInfo.querySelector('.file-name'); |
|
const fileSize = fileInfo.querySelector('.file-size'); |
|
const fileIcon = fileInfo.querySelector('.file-type-icon'); |
|
|
|
const fileType = file.name.split('.').pop().toLowerCase(); |
|
const iconClass = getFileTypeIcon(fileType); |
|
|
|
fileIcon.className = `file-type-icon fas ${iconClass}`; |
|
fileName.textContent = file.name; |
|
fileSize.textContent = formatFileSize(file.size); |
|
fileInfo.classList.remove('hidden'); |
|
} |
|
|
|
function simulateProgress() { |
|
const progressBar = document.querySelector('.progress-bar-fill'); |
|
let progress = 0; |
|
|
|
return setInterval(() => { |
|
if (progress < 90) { |
|
progress += 5; |
|
progressBar.style.width = `${progress}%`; |
|
} |
|
}, 100); |
|
} |
|
|
|
function showSuccessState() { |
|
document.querySelector('.loading-spinner').style.display = 'none'; |
|
document.querySelector('.success-checkmark').style.display = 'block'; |
|
document.querySelector('.progress-bar-fill').style.width = '100%'; |
|
|
|
setTimeout(() => { |
|
resetUploadState(); |
|
}, 2000); |
|
} |
|
|
|
function showErrorState(message) { |
|
|
|
resetUploadState(); |
|
|
|
|
|
const errorDiv = document.createElement('div'); |
|
errorDiv.className = 'text-red-500 mt-4'; |
|
errorDiv.innerHTML = `<i class="fas fa-exclamation-circle mr-2"></i>${message}`; |
|
document.querySelector('.drop-zone').appendChild(errorDiv); |
|
|
|
setTimeout(() => { |
|
errorDiv.remove(); |
|
}, 5000); |
|
} |
|
|
|
function resetUploadState() { |
|
document.querySelector('.success-checkmark').style.display = 'none'; |
|
document.querySelector('.upload-icon').style.display = 'block'; |
|
document.querySelector('.progress-bar').style.display = 'none'; |
|
document.querySelector('.progress-bar-fill').style.width = '0%'; |
|
document.getElementById('fileInfo').classList.add('hidden'); |
|
} |
|
|
|
function handleSampleDataClick(datasetName) { |
|
|
|
const resultsSection = document.getElementById('results'); |
|
const resultsLoading = document.getElementById('resultsLoading'); |
|
const resultsContent = document.getElementById('resultsContent'); |
|
const resultsError = document.getElementById('resultsError'); |
|
|
|
resultsSection.classList.remove('hidden'); |
|
resultsLoading.classList.remove('hidden'); |
|
resultsContent.classList.add('hidden'); |
|
resultsError.classList.add('hidden'); |
|
|
|
|
|
setTimeout(() => { |
|
try { |
|
|
|
const mockData = datasetName === 'marketing_campaign' ? { |
|
statistics: { |
|
rows: 10000, |
|
columns: 15, |
|
missing_values: 120, |
|
data_types: ['numeric', 'categorical', 'datetime'] |
|
}, |
|
preview: { |
|
columns: ['Campaign ID', 'Customer ID', 'Response', 'Channel'], |
|
data: [ |
|
['CAM001', 'C001', 'Converted', 'Email'], |
|
['CAM001', 'C002', 'No Response', 'SMS'], |
|
['CAM002', 'C003', 'Converted', 'Social Media'] |
|
] |
|
}, |
|
visualizations: [ |
|
{ |
|
title: 'Response Rate by Channel', |
|
description: 'Conversion rates across different marketing channels', |
|
image_url: 'https://via.placeholder.com/400x300' |
|
}, |
|
{ |
|
title: 'Campaign Performance', |
|
description: 'Success metrics for each campaign', |
|
image_url: 'https://via.placeholder.com/400x300' |
|
} |
|
], |
|
insights: [ |
|
{ |
|
title: 'Best Performing Channel', |
|
description: 'Email campaigns show highest conversion rate at 28%' |
|
}, |
|
{ |
|
title: 'Optimal Send Time', |
|
description: 'Campaigns sent between 2 PM - 4 PM have better engagement' |
|
} |
|
] |
|
} : { |
|
statistics: { |
|
rows: 50000, |
|
columns: 12, |
|
missing_values: 85, |
|
data_types: ['numeric', 'categorical', 'datetime'] |
|
}, |
|
preview: { |
|
columns: ['Order ID', 'Product', 'Quantity', 'Price'], |
|
data: [ |
|
['ORD001', 'Laptop', '1', '$999.99'], |
|
['ORD002', 'Mouse', '2', '$29.99'], |
|
['ORD003', 'Monitor', '1', '$299.99'] |
|
] |
|
}, |
|
visualizations: [ |
|
{ |
|
title: 'Sales by Category', |
|
description: 'Distribution of sales across product categories', |
|
image_url: 'https://via.placeholder.com/400x300' |
|
}, |
|
{ |
|
title: 'Monthly Revenue', |
|
description: 'Revenue trends over the past 12 months', |
|
image_url: 'https://via.placeholder.com/400x300' |
|
} |
|
], |
|
insights: [ |
|
{ |
|
title: 'Top Products', |
|
description: 'Electronics category generates 45% of total revenue' |
|
}, |
|
{ |
|
title: 'Customer Behavior', |
|
description: 'Average order value increased by 15% in Q4' |
|
} |
|
] |
|
}; |
|
|
|
handleApiResponse(mockData); |
|
} catch (error) { |
|
console.error('Error:', error); |
|
showErrorState('Failed to process sample dataset'); |
|
} |
|
}, 1000); |
|
} |
|
|
|
function handleApiResponse(data) { |
|
const resultsSection = document.getElementById('results'); |
|
const resultsLoading = document.getElementById('resultsLoading'); |
|
const resultsContent = document.getElementById('resultsContent'); |
|
const resultsError = document.getElementById('resultsError'); |
|
|
|
|
|
resultsSection.classList.remove('hidden'); |
|
resultsLoading.classList.add('hidden'); |
|
resultsError.classList.add('hidden'); |
|
resultsContent.classList.remove('hidden'); |
|
|
|
|
|
updateBasicStats(data.statistics); |
|
|
|
|
|
updateDataPreview(data.preview); |
|
|
|
|
|
updateVisualizations(data.visualizations); |
|
|
|
|
|
updateInsights(data.insights); |
|
} |
|
|
|
function updateBasicStats(statistics) { |
|
const statsContainer = document.getElementById('basicStats'); |
|
statsContainer.innerHTML = ''; |
|
|
|
const stats = [ |
|
{ label: 'Rows', value: statistics.rows, icon: 'fa-list' }, |
|
{ label: 'Columns', value: statistics.columns, icon: 'fa-columns' }, |
|
{ label: 'Missing Values', value: statistics.missing_values, icon: 'fa-exclamation-triangle' }, |
|
{ label: 'Data Types', value: statistics.data_types.length, icon: 'fa-code' } |
|
]; |
|
|
|
stats.forEach(stat => { |
|
const statDiv = document.createElement('div'); |
|
statDiv.className = 'bg-gray-50 rounded-lg p-4'; |
|
statDiv.innerHTML = ` |
|
<div class="flex items-center"> |
|
<i class="fas ${stat.icon} text-primary text-xl mr-3"></i> |
|
<div> |
|
<div class="text-sm text-gray-500">${stat.label}</div> |
|
<div class="text-lg font-semibold">${stat.value}</div> |
|
</div> |
|
</div> |
|
`; |
|
statsContainer.appendChild(statDiv); |
|
}); |
|
} |
|
|
|
function updateDataPreview(preview) { |
|
const table = document.getElementById('dataPreview'); |
|
table.innerHTML = ''; |
|
|
|
|
|
const thead = document.createElement('thead'); |
|
thead.className = 'bg-gray-50'; |
|
const headerRow = document.createElement('tr'); |
|
preview.columns.forEach(column => { |
|
const th = document.createElement('th'); |
|
th.className = 'px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider'; |
|
th.textContent = column; |
|
headerRow.appendChild(th); |
|
}); |
|
thead.appendChild(headerRow); |
|
table.appendChild(thead); |
|
|
|
|
|
const tbody = document.createElement('tbody'); |
|
tbody.className = 'bg-white divide-y divide-gray-200'; |
|
preview.data.forEach(row => { |
|
const tr = document.createElement('tr'); |
|
row.forEach(cell => { |
|
const td = document.createElement('td'); |
|
td.className = 'px-6 py-4 whitespace-nowrap text-sm text-gray-500'; |
|
td.textContent = cell; |
|
tr.appendChild(td); |
|
}); |
|
tbody.appendChild(tr); |
|
}); |
|
table.appendChild(tbody); |
|
} |
|
|
|
function updateVisualizations(visualizations) { |
|
const container = document.getElementById('visualizations'); |
|
container.innerHTML = ''; |
|
|
|
visualizations.forEach(viz => { |
|
const vizDiv = document.createElement('div'); |
|
vizDiv.className = 'bg-white rounded-lg p-4 shadow'; |
|
vizDiv.innerHTML = ` |
|
<h4 class="text-lg font-medium text-gray-800 mb-4">${viz.title}</h4> |
|
<div class="aspect-w-16 aspect-h-9"> |
|
<img src="${viz.image_url}" alt="${viz.title}" class="rounded-lg"> |
|
</div> |
|
<p class="mt-2 text-sm text-gray-600">${viz.description}</p> |
|
`; |
|
container.appendChild(vizDiv); |
|
}); |
|
} |
|
|
|
function updateInsights(insights) { |
|
const insightsList = document.getElementById('insights'); |
|
insightsList.innerHTML = ''; |
|
|
|
insights.forEach(insight => { |
|
const li = document.createElement('li'); |
|
li.className = 'bg-blue-50 rounded-lg p-4'; |
|
li.innerHTML = ` |
|
<div class="flex items-start"> |
|
<i class="fas fa-lightbulb text-yellow-500 mt-1 mr-3"></i> |
|
<div> |
|
<div class="font-medium text-blue-900">${insight.title}</div> |
|
<p class="mt-1 text-sm text-blue-700">${insight.description}</p> |
|
</div> |
|
</div> |
|
`; |
|
insightsList.appendChild(li); |
|
}); |
|
} |
|
|
|
function closeResults() { |
|
document.getElementById('results').classList.add('hidden'); |
|
} |
|
|
|
function showErrorState(message) { |
|
const resultsSection = document.getElementById('results'); |
|
const resultsLoading = document.getElementById('resultsLoading'); |
|
const resultsContent = document.getElementById('resultsContent'); |
|
const resultsError = document.getElementById('resultsError'); |
|
const errorMessage = document.getElementById('errorMessage'); |
|
|
|
resultsSection.classList.remove('hidden'); |
|
resultsLoading.classList.add('hidden'); |
|
resultsContent.classList.add('hidden'); |
|
resultsError.classList.remove('hidden'); |
|
errorMessage.textContent = message; |
|
} |
|
|
|
|
|
document.addEventListener('DOMContentLoaded', () => { |
|
|
|
const dropZone = document.querySelector('.drop-zone'); |
|
const fileInput = document.getElementById('fileInput'); |
|
|
|
|
|
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { |
|
dropZone.addEventListener(eventName, preventDefaults, false); |
|
document.body.addEventListener(eventName, preventDefaults, false); |
|
}); |
|
|
|
|
|
['dragenter', 'dragover'].forEach(eventName => { |
|
dropZone.addEventListener(eventName, highlight, false); |
|
}); |
|
|
|
['dragleave', 'drop'].forEach(eventName => { |
|
dropZone.addEventListener(eventName, unhighlight, false); |
|
}); |
|
|
|
|
|
dropZone.addEventListener('drop', (e) => { |
|
const dt = e.dataTransfer; |
|
const files = dt.files; |
|
if (files.length > 0) { |
|
handleFileUpload(files[0]); |
|
} |
|
}); |
|
|
|
fileInput.addEventListener('change', (e) => { |
|
if (e.target.files.length > 0) { |
|
handleFileUpload(e.target.files[0]); |
|
} |
|
}); |
|
|
|
|
|
const marketingBtn = document.querySelector('.sample-btn:nth-child(1)'); |
|
const retailBtn = document.querySelector('.sample-btn:nth-child(2)'); |
|
|
|
if (marketingBtn) { |
|
marketingBtn.addEventListener('click', () => { |
|
console.log('Marketing campaign button clicked'); |
|
handleSampleDataClick('marketing_campaign'); |
|
}); |
|
} |
|
|
|
if (retailBtn) { |
|
retailBtn.addEventListener('click', () => { |
|
console.log('Online retail button clicked'); |
|
handleSampleDataClick('online_retail'); |
|
}); |
|
} |
|
}); |
|
|
|
|
|
function preventDefaults(e) { |
|
e.preventDefault(); |
|
e.stopPropagation(); |
|
} |
|
|
|
function highlight(e) { |
|
document.querySelector('.drop-zone').classList.add('border-primary', 'bg-blue-50'); |
|
} |
|
|
|
function unhighlight(e) { |
|
document.querySelector('.drop-zone').classList.remove('border-primary', 'bg-blue-50'); |
|
} |
|
|
|
function getFileTypeIcon(fileType) { |
|
const icons = { |
|
'csv': 'fa-file-csv', |
|
'tsv': 'fa-file-alt', |
|
'txt': 'fa-file-alt', |
|
'xls': 'fa-file-excel', |
|
'xlsx': 'fa-file-excel', |
|
'xml': 'fa-file-code', |
|
'json': 'fa-file-code' |
|
}; |
|
return icons[fileType] || 'fa-file'; |
|
} |
|
|
|
function formatFileSize(bytes) { |
|
if (bytes === 0) return '0 Bytes'; |
|
const k = 1024; |
|
const sizes = ['Bytes', 'KB', 'MB', 'GB']; |
|
const i = Math.floor(Math.log(bytes) / Math.log(k)); |
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; |
|
} |
|
|