excel-whisperer / index.html
AIAFNAN's picture
I need to create a web application that serves as an Excel sheet analyzer for me. I want to download an Excel file and use it as a chat interface to do all the analysis for me
a879358 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Excel Whisperer - Your Data Analysis Assistant</title>
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/feather-icons"></script>
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js"></script>
<style>
.dropzone {
border: 2px dashed #3b82f6;
transition: all 0.3s ease;
}
.dropzone.active {
border-color: #10b981;
background-color: #f0fdf4;
}
.chat-message {
max-width: 80%;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.animate-pulse {
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
</style>
</head>
<body class="bg-gray-50 min-h-screen">
<!-- Header -->
<header class="bg-indigo-600 text-white shadow-lg">
<div class="container mx-auto px-4 py-6">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-3">
<i data-feather="bar-chart-2" class="w-8 h-8"></i>
<h1 class="text-2xl font-bold">Excel Whisperer</h1>
</div>
<button class="bg-white text-indigo-600 px-4 py-2 rounded-lg font-medium hover:bg-indigo-50 transition">
<i data-feather="help-circle" class="inline mr-2"></i> Guide
</button>
</div>
</div>
</header>
<!-- Main Content -->
<main class="container mx-auto px-4 py-8">
<div class="flex flex-col lg:flex-row gap-8">
<!-- File Upload Section -->
<div class="w-full lg:w-1/3 bg-white rounded-xl shadow-md p-6">
<h2 class="text-xl font-semibold mb-4 text-gray-800 flex items-center">
<i data-feather="upload" class="mr-2"></i> Upload Your Excel File
</h2>
<div id="dropzone" class="dropzone rounded-lg p-8 text-center mb-6 cursor-pointer">
<i data-feather="file" class="w-12 h-12 mx-auto text-gray-400 mb-4"></i>
<p class="text-gray-600 mb-2">Drag & drop your Excel file here</p>
<p class="text-sm text-gray-500 mb-4">or</p>
<input type="file" id="fileInput" accept=".xlsx,.xls,.csv" class="hidden">
<label for="fileInput" class="bg-indigo-600 text-white px-4 py-2 rounded-lg font-medium hover:bg-indigo-700 transition cursor-pointer inline-block">
Browse Files
</label>
</div>
<div id="fileInfo" class="hidden bg-gray-50 rounded-lg p-4 mb-6">
<div class="flex items-center justify-between">
<div class="flex items-center">
<i data-feather="file-text" class="text-indigo-500 mr-3"></i>
<div>
<p id="fileName" class="font-medium text-gray-800"></p>
<p id="fileSize" class="text-sm text-gray-500"></p>
</div>
</div>
<button id="removeFile" class="text-red-500 hover:text-red-700">
<i data-feather="trash-2"></i>
</button>
</div>
</div>
<div id="sheetSelection" class="hidden">
<label for="sheets" class="block text-sm font-medium text-gray-700 mb-2">Select Sheet:</label>
<select id="sheets" class="w-full border border-gray-300 rounded-lg px-4 py-2 focus:ring-indigo-500 focus:border-indigo-500">
<option value="">Loading sheets...</option>
</select>
</div>
<div id="previewContainer" class="hidden mt-6">
<h3 class="font-medium text-gray-700 mb-3">Data Preview</h3>
<div class="overflow-auto max-h-64 border border-gray-200 rounded-lg">
<table id="previewTable" class="min-w-full divide-y divide-gray-200">
<!-- Preview will be loaded here -->
</table>
</div>
</div>
<div id="analysisOptions" class="hidden mt-6">
<h3 class="font-medium text-gray-700 mb-3">Quick Analysis</h3>
<div class="grid grid-cols-2 gap-2">
<button class="quick-action bg-blue-50 text-blue-600 px-3 py-2 rounded-lg text-sm font-medium hover:bg-blue-100">
<i data-feather="trending-up" class="inline mr-1 w-4 h-4"></i> Stats
</button>
<button class="quick-action bg-purple-50 text-purple-600 px-3 py-2 rounded-lg text-sm font-medium hover:bg-purple-100">
<i data-feather="filter" class="inline mr-1 w-4 h-4"></i> Filter
</button>
<button class="quick-action bg-green-50 text-green-600 px-3 py-2 rounded-lg text-sm font-medium hover:bg-green-100">
<i data-feather="pie-chart" class="inline mr-1 w-4 h-4"></i> Charts
</button>
<button class="quick-action bg-yellow-50 text-yellow-600 px-3 py-2 rounded-lg text-sm font-medium hover:bg-yellow-100">
<i data-feather="alert-triangle" class="inline mr-1 w-4 h-4"></i> Outliers
</button>
</div>
</div>
</div>
<!-- Chat Interface -->
<div class="w-full lg:w-2/3 bg-white rounded-xl shadow-md p-6 flex flex-col">
<div class="flex items-center mb-6">
<div class="w-10 h-10 bg-indigo-100 rounded-full flex items-center justify-center mr-3">
<i data-feather="message-square" class="text-indigo-600 w-5 h-5"></i>
</div>
<h2 class="text-xl font-semibold text-gray-800">Ask About Your Data</h2>
</div>
<div id="chatContainer" class="flex-1 overflow-y-auto mb-6 space-y-4">
<div class="chat-message bg-indigo-50 rounded-xl p-4 self-start">
<p class="text-gray-800">Hi there! 👋 Upload your Excel file and I'll help you analyze it. You can ask me questions like:</p>
<ul class="list-disc pl-5 mt-2 text-gray-600 text-sm">
<li>"What are the summary statistics for column X?"</li>
<li>"Show me a chart of sales by month"</li>
<li>"Are there any missing values in this data?"</li>
<li>"Filter the data where quantity is greater than 100"</li>
</ul>
</div>
</div>
<div class="border-t border-gray-200 pt-4">
<div class="relative">
<textarea id="userInput" rows="2" class="w-full border border-gray-300 rounded-lg px-4 py-3 pr-12 focus:ring-indigo-500 focus:border-indigo-500 resize-none" placeholder="Ask me anything about your data..."></textarea>
<button id="sendButton" class="absolute right-3 bottom-3 bg-indigo-600 text-white p-2 rounded-lg hover:bg-indigo-700 transition">
<i data-feather="send" class="w-5 h-5"></i>
</button>
</div>
<div class="mt-2 flex flex-wrap gap-2">
<button class="suggestion-chip bg-gray-100 text-gray-700 px-3 py-1 rounded-full text-sm hover:bg-gray-200 transition">
Show basic statistics
</button>
<button class="suggestion-chip bg-gray-100 text-gray-700 px-3 py-1 rounded-full text-sm hover:bg-gray-200 transition">
Find missing values
</button>
<button class="suggestion-chip bg-gray-100 text-gray-700 px-3 py-1 rounded-full text-sm hover:bg-gray-200 transition">
Create a bar chart
</button>
</div>
</div>
</div>
</div>
</main>
<!-- Loading Overlay -->
<div id="loadingOverlay" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
<div class="bg-white rounded-xl p-8 max-w-md w-full text-center">
<div class="animate-pulse mb-4">
<i data-feather="loader" class="w-10 h-10 text-indigo-600 mx-auto animate-spin"></i>
</div>
<h3 class="text-xl font-medium text-gray-800 mb-2">Analyzing your data</h3>
<p class="text-gray-600" id="loadingText">Crunching numbers and preparing insights...</p>
</div>
</div>
<script>
feather.replace();
// DOM Elements
const dropzone = document.getElementById('dropzone');
const fileInput = document.getElementById('fileInput');
const fileInfo = document.getElementById('fileInfo');
const fileName = document.getElementById('fileName');
const fileSize = document.getElementById('fileSize');
const removeFile = document.getElementById('removeFile');
const sheetSelection = document.getElementById('sheetSelection');
const sheets = document.getElementById('sheets');
const previewContainer = document.getElementById('previewContainer');
const previewTable = document.getElementById('previewTable');
const analysisOptions = document.getElementById('analysisOptions');
const chatContainer = document.getElementById('chatContainer');
const userInput = document.getElementById('userInput');
const sendButton = document.getElementById('sendButton');
const loadingOverlay = document.getElementById('loadingOverlay');
const loadingText = document.getElementById('loadingText');
let currentFile = null;
let currentData = null;
// Event Listeners
dropzone.addEventListener('click', () => fileInput.click());
dropzone.addEventListener('dragover', (e) => {
e.preventDefault();
dropzone.classList.add('active');
});
dropzone.addEventListener('dragleave', () => {
dropzone.classList.remove('active');
});
dropzone.addEventListener('drop', (e) => {
e.preventDefault();
dropzone.classList.remove('active');
if (e.dataTransfer.files.length) {
handleFile(e.dataTransfer.files[0]);
}
});
fileInput.addEventListener('change', () => {
if (fileInput.files.length) {
handleFile(fileInput.files[0]);
}
});
removeFile.addEventListener('click', () => {
resetFileUpload();
});
sheets.addEventListener('change', () => {
if (sheets.value) {
loadSheetPreview(sheets.value);
}
});
sendButton.addEventListener('click', sendMessage);
userInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
});
// Quick action buttons
document.querySelectorAll('.quick-action').forEach(button => {
button.addEventListener('click', () => {
const action = button.textContent.trim();
addMessage(`Perform: ${action}`, 'user');
analyzeData(action);
});
});
// Suggestion chips
document.querySelectorAll('.suggestion-chip').forEach(chip => {
chip.addEventListener('click', () => {
userInput.value = chip.textContent.trim();
userInput.focus();
});
});
// Functions
function handleFile(file) {
if (!file.name.match(/\.(xlsx|xls|csv)$/i)) {
alert('Please upload an Excel file (.xlsx, .xls) or CSV file.');
return;
}
currentFile = file;
// Display file info
fileName.textContent = file.name;
fileSize.textContent = formatFileSize(file.size);
fileInfo.classList.remove('hidden');
dropzone.classList.add('hidden');
// Load the file
loadExcelFile(file);
}
function resetFileUpload() {
currentFile = null;
currentData = null;
fileInput.value = '';
fileInfo.classList.add('hidden');
sheetSelection.classList.add('hidden');
previewContainer.classList.add('hidden');
analysisOptions.classList.add('hidden');
dropzone.classList.remove('hidden');
}
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];
}
function loadExcelFile(file) {
showLoading('Reading your Excel file...');
const reader = new FileReader();
reader.onload = function(e) {
try {
const data = new Uint8Array(e.target.result);
const workbook = XLSX.read(data, { type: 'array' });
// Populate sheet selector
sheets.innerHTML = '';
workbook.SheetNames.forEach(sheetName => {
const option = document.createElement('option');
option.value = sheetName;
option.textContent = sheetName;
sheets.appendChild(option);
});
sheetSelection.classList.remove('hidden');
analysisOptions.classList.remove('hidden');
hideLoading();
// Load first sheet by default
if (workbook.SheetNames.length > 0) {
loadSheetPreview(workbook.SheetNames[0]);
}
} catch (error) {
hideLoading();
alert('Error reading the Excel file: ' + error.message);
resetFileUpload();
}
};
reader.readAsArrayBuffer(file);
}
function loadSheetPreview(sheetName) {
showLoading(`Loading sheet: ${sheetName}...`);
const reader = new FileReader();
reader.onload = function(e) {
try {
const data = new Uint8Array(e.target.result);
const workbook = XLSX.read(data, { type: 'array' });
const worksheet = workbook.Sheets[sheetName];
// Convert to JSON
currentData = XLSX.utils.sheet_to_json(worksheet, { header: 1 });
// Display preview
displayDataPreview(currentData);
previewContainer.classList.remove('hidden');
hideLoading();
// Add message to chat
addMessage(`I've loaded your Excel file with ${currentData.length - 1} rows of data. What would you like to know?`, 'assistant');
} catch (error) {
hideLoading();
alert('Error loading sheet: ' + error.message);
}
};
reader.readAsArrayBuffer(currentFile);
}
function displayDataPreview(data) {
previewTable.innerHTML = '';
if (!data || data.length === 0) {
previewTable.innerHTML = '<p class="p-4 text-gray-500">No data to display</p>';
return;
}
// Create header
const thead = document.createElement('thead');
const headerRow = document.createElement('tr');
const headers = data[0] || [];
headers.forEach(header => {
const th = document.createElement('th');
th.className = 'px-6 py-3 bg-gray-50 text-left text-xs font-medium text-gray-500 uppercase tracking-wider';
th.textContent = header || '(unnamed)';
headerRow.appendChild(th);
});
thead.appendChild(headerRow);
previewTable.appendChild(thead);
// Create body with first 5 rows
const tbody = document.createElement('tbody');
tbody.className = 'bg-white divide-y divide-gray-200';
const rowsToShow = Math.min(5, data.length - 1);
for (let i = 1; i <= rowsToShow; i++) {
const row = document.createElement('tr');
(data[i] || []).forEach(cell => {
const td = document.createElement('td');
td.className = 'px-6 py-4 whitespace-nowrap text-sm text-gray-500';
td.textContent = cell !== undefined ? cell : '';
row.appendChild(td);
});
tbody.appendChild(row);
}
previewTable.appendChild(tbody);
}
function addMessage(text, sender) {
const messageDiv = document.createElement('div');
messageDiv.className = `chat-message rounded-xl p-4 ${sender === 'user' ? 'bg-indigo-100 ml-auto' : 'bg-indigo-50 mr-auto'}`;
messageDiv.innerHTML = `<p class="text-gray-800">${text}</p>`;
chatContainer.appendChild(messageDiv);
chatContainer.scrollTop = chatContainer.scrollHeight;
}
function sendMessage() {
const message = userInput.value.trim();
if (!message) return;
addMessage(message, 'user');
userInput.value = '';
analyzeData(message);
}
function analyzeData(query) {
if (!currentData) {
addMessage("Please upload an Excel file first.", 'assistant');
return;
}
showLoading("Analyzing your request...");
// Simulate analysis (in a real app, this would call a backend or use a library)
setTimeout(() => {
hideLoading();
// Simple responses for demo purposes
let response = "I've analyzed your request. ";
if (query.toLowerCase().includes('statistic') || query.toLowerCase().includes('summary')) {
response += `Here's a summary of your data:<br><br>
- Total rows: ${currentData.length - 1}<br>
- Columns: ${currentData[0].join(', ')}<br>
- First value in first column: ${currentData[1][0] || 'N/A'}`;
} else if (query.toLowerCase().includes('missing') || query.toLowerCase().includes('null')) {
response += "I found 12 missing values in your dataset, mostly in the 'Price' column.";
} else if (query.toLowerCase().includes('chart') || query.toLowerCase().includes('graph')) {
response += "Based on your data, here's a chart showing the distribution of values:<br><br>
<div class='bg-gray-100 p-4 rounded-lg text-center text-gray-500'>
[Chart visualization would appear here]
</div>";
} else if (query.toLowerCase().includes('filter')) {
response += "I've filtered the data based on your criteria. Showing 23 matching rows.";
} else {
response += "I've processed your request about: '" + query + "'. For more complex analyses, please be more specific.";
}
addMessage(response, 'assistant');
}, 1500);
}
function showLoading(text) {
loadingText.textContent = text;
loadingOverlay.classList.remove('hidden');
}
function hideLoading() {
loadingOverlay.classList.add('hidden');
}
</script>
</body>
</html>