| <!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 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 class="container mx-auto px-4 py-8"> |
| <div class="flex flex-col lg:flex-row gap-8"> |
| |
| <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"> |
| |
| </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> |
| |
| |
| <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> |
|
|
| |
| <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> |
|
|