harshkumar27/data-analysis-agent
0
1<!DOCTYPE html>2<html lang="en">3<head>4 <meta charset="UTF-8"/>5 <meta name="viewport" content="width=device-width, initial-scale=1.0"/>6 <title>TDS Data Analyst Agent</title>7 <style>8 * { margin:0; padding:0; box-sizing:border-box; }9 body {10 font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;11 line-height: 1.6; color:#f5f5f5;12 background: linear-gradient(135deg,#1e1e2f 0%,#2a2a3d 100%);13 min-height:100vh;14 }15 .container { max-width:1200px; margin:0 auto; padding:20px; }16 .header { text-align:center; margin-bottom:30px; color:#fff; }17 .header h1 { font-size:2.5rem; margin-bottom:10px; text-shadow:2px 2px 4px rgba(0,0,0,.6); }18 .header p { font-size:1.1rem; opacity:.85; }19 .main-card {20 background:#2c2c3e; border-radius:15px; box-shadow:0 10px 30px rgba(0,0,0,.4);21 padding:30px; margin-bottom:30px;22 }23 .form-group label { display:block; margin-bottom:8px; font-weight:600; color:#ddd; }24 .file-input {25 width:100%; padding:15px; border:2px dashed #555; border-radius:8px;26 background:#1f1f2e; cursor:pointer; transition:.3s ease; text-align:center; position:relative;27 color:#bbb;28 }29 .file-input:hover { border-color:#667eea; background:#2a2a40; color:#fff; }30 .file-info { margin-top:10px; padding:10px; background:#333; border-radius:5px; font-size:14px; color:#a8c7ff; display:none; }31 .btn {32 background:linear-gradient(135deg,#667eea 0%,#764ba2 100%); color:#fff; border:none;33 padding:15px 30px; border-radius:8px; font-size:16px; font-weight:600; cursor:pointer; transition:.3s ease; width:100%;34 }35 .btn:hover { transform:translateY(-2px); box-shadow:0 5px 15px rgba(102,126,234,.6); }36 .btn:disabled { background:#555; cursor:not-allowed; transform:none; box-shadow:none; }37 .btn-secondary { background:#444; color:#ddd; }38 .button-group { display:flex; gap:10px; margin-top:20px; flex-wrap:wrap; }39 .loading { display:none; text-align:center; padding:20px; }40 .spinner {41 border:4px solid #444; border-top:4px solid #667eea; border-radius:50%;42 width:40px; height:40px; animation:spin 1s linear infinite; margin:0 auto 15px;43 }44 @keyframes spin { 0%{transform:rotate(0)} 100%{transform:rotate(360deg)} }45 .results { display:none; margin-top:30px; }46 .result-item { background:#1f1f2e; border-left:4px solid #667eea; padding:20px; margin-bottom:20px; border-radius:0 8px 8px 0; }47 .question { font-weight:600; color:#fff; margin-bottom:15px; font-size:1.1rem; }48 .answer { color:#ccc; line-height:1.6; }49 .answer pre {50 background:#2c2c3e; color:#eee;51 }52 .answer img {53 max-width:100%; height:auto; border-radius:8px; margin:10px 0;54 box-shadow:0 4px 8px rgba(0,0,0,.5); cursor:pointer; transition:transform .3s ease;55 }56 .answer img:hover { transform:scale(1.02); }57 .error { background:#3b1f1f; border-left-color:#dc3545; color:#ffb3b3; }58 .modal { display:none; position:fixed; z-index:1000; left:0; top:0; width:100%; height:100%; background:rgba(0,0,0,.9); }59 .modal-content { margin:auto; display:block; width:80%; max-width:700px; max-height:80%; object-fit:contain; }60 .close { position:absolute; top:15px; right:35px; color:#f1f1f1; font-size:40px; font-weight:bold; cursor:pointer; }61 .close:hover { color:#bbb; }62 @media (max-width:768px){63 .container{padding:10px}64 .header h1{font-size:2rem}65 .main-card{padding:20px}66 .button-group{flex-direction:column}67 .btn-secondary{width:100%}68 }69</style>70</head>71<body>72 <div class="container">73 <div class="header">74 <h1>๐ค TDS Data Analyst Agent</h1>75 <p>Upload your questions file and optional dataset to get intelligent answers with visualizations</p>76 </div>77 78 <div class="main-card">79 <form id="analysisForm">80 <!-- Questions file (REQUIRED) -->81 <div class="form-group">82 <label for="questions_file">Questions File (.txt) <span style="color:#dc3545">*</span></label>83 <div class="file-input-wrapper">84 <div class="file-input" id="questionsDrop">85 <input type="file" id="questions_file" name="questions_file" accept=".txt"/>86 <span>๐ Click to upload your questions (.txt)</span>87 </div>88 </div>89 <div id="questionsInfo" class="file-info"></div>90 </div>91 92 <!-- Dataset file (OPTIONAL) -->93 <div class="form-group">94 <label for="data_file">Upload Dataset (Optional)</label>95 <div class="file-input-wrapper">96 <div class="file-input" id="dataDrop">97 <input type="file" id="data_file" name="data_file" accept=".csv,.xlsx,.xls,.json,.parquet,.txt"/>98 <span>๐ Click or drag & drop your dataset</span>99 </div>100 </div>101 <div id="dataInfo" class="file-info"></div>102 </div>103 104 <div class="button-group">105 <button type="submit" class="btn" id="submitBtn">๐ Analyze Data</button>106 <button type="button" class="btn btn-secondary" id="clearBtn">๐๏ธ Clear</button>107 </div>108 </form>109 110 <div class="loading" id="loading">111 <div class="spinner"></div>112 <p>Analyzing your data... This may take a few moments.</p>113 </div>114 115 <div class="results" id="results">116 <h3>๐ Analysis Results</h3>117 <div id="resultsContent"></div>118 </div>119 </div>120 </div>121 122 <!-- Image Modal -->123 <div id="imageModal" class="modal">124 <span class="close">×</span>125 <img class="modal-content" id="modalImage" alt="Visualization"/>126 </div>127 128 <script>129 /* ---------- Image normalization helpers ---------- */130 function normalizeImageData(answer) {131 let s = null;132 if (typeof answer === 'string') { s = answer.trim(); }133 else if (answer && typeof answer === 'object') {134 s = answer.image || answer.base64 || answer.plot || answer.data || null;135 if (typeof s === 'string') s = s.trim();136 }137 if (!s) return null;138 139 s = s.replace(/^data:aimage\//i, 'data:image/');140 141 if (/^data:image\//i.test(s)) return s;142 143 const base64Like = /^[A-Za-z0-9+/=\r\n]+$/.test(s) && s.length > 200 && !/\s{2,}/.test(s);144 if (base64Like) return 'data:image/png;base64,' + s;145 146 return null;147 }148 149 function renderAnswerInto(answer, container, onImageClick) {150 const maybeImg = normalizeImageData(answer);151 if (maybeImg) {152 const img = document.createElement('img');153 img.src = maybeImg;154 img.alt = 'Generated visualization';155 img.addEventListener('click', () => onImageClick(maybeImg));156 container.appendChild(img);157 return;158 }159 const text = (answer && typeof answer === 'object') ? JSON.stringify(answer, null, 2) : String(answer ?? '');160 if (text.includes('\n') || text.length > 120) {161 const pre = document.createElement('pre');162 pre.style.whiteSpace = 'pre-wrap';163 pre.style.background = '#f8f9fa';164 pre.style.padding = '15px';165 pre.style.borderRadius = '5px';166 pre.style.overflow = 'auto';167 pre.textContent = text;168 container.appendChild(pre);169 } else {170 container.textContent = text;171 }172 }173 174 /* ---------------- App ---------------- */175 class DataAnalystApp {176 constructor() {177 this.form = document.getElementById('analysisForm');178 this.qFileInput = document.getElementById('questions_file');179 this.dFileInput = document.getElementById('data_file');180 this.qDrop = document.getElementById('questionsDrop');181 this.dDrop = document.getElementById('dataDrop');182 this.qInfo = document.getElementById('questionsInfo');183 this.dInfo = document.getElementById('dataInfo');184 this.submitBtn = document.getElementById('submitBtn');185 this.clearBtn = document.getElementById('clearBtn');186 this.loading = document.getElementById('loading');187 this.results = document.getElementById('results');188 this.resultsContent = document.getElementById('resultsContent');189 this.modal = document.getElementById('imageModal');190 this.modalImage = document.getElementById('modalImage');191 this.closeModal = document.querySelector('.close');192 this.initEventListeners();193 }194 195 initEventListeners() {196 this.form.addEventListener('submit', (e) => this.handleSubmit(e));197 this.clearBtn.addEventListener('click', () => this.clearForm());198 this.qFileInput.addEventListener('change', (e) => this.handleFileSelect(e, 'q'));199 this.dFileInput.addEventListener('change', (e) => this.handleFileSelect(e, 'd'));200 this.closeModal.addEventListener('click', () => this.hideImageModal());201 this.modal.addEventListener('click', (e) => { if (e.target === this.modal) this.hideImageModal(); });202 this.setupDrop(this.dDrop, (files) => { if (files && files.length > 0) { this.dFileInput.files = files; this.handleFileSelect({ target: { files } }, 'd'); } });203 this.setupDrop(this.qDrop, (files) => { if (files && files.length > 0) { this.qFileInput.files = files; this.handleFileSelect({ target: { files } }, 'q'); } });204 }205 206 setupDrop(zone, onDropFiles) {207 zone.addEventListener('dragover', (e) => { e.preventDefault(); zone.style.borderColor = '#667eea'; });208 zone.addEventListener('dragleave', (e) => { e.preventDefault(); zone.style.borderColor = '#e1e5e9'; });209 zone.addEventListener('drop', (e) => { e.preventDefault(); zone.style.borderColor = '#e1e5e9'; onDropFiles(e.dataTransfer.files); });210 }211 212 handleFileSelect(event, type) {213 const file = event.target.files[0];214 if (type === 'q') {215 if (file) { this.qInfo.innerHTML = `<strong>Questions:</strong> ${file.name} (${(file.size / 1024).toFixed(2)} KB)`; this.qInfo.style.display = 'block'; }216 else { this.qInfo.style.display = 'none'; }217 } else {218 if (file) { this.dInfo.innerHTML = `<strong>Dataset:</strong> ${file.name} (${(file.size / 1024).toFixed(2)} KB)`; this.dInfo.style.display = 'block'; }219 else { this.dInfo.style.display = 'none'; }220 }221 }222 223 async handleSubmit(event) {224 event.preventDefault();225 if (!this.qFileInput.files[0]) { alert('Please upload your questions .txt file.'); return; }226 this.showLoading(); this.hideResults();227 228 try {229 const formData = new FormData();230 formData.append('questions_file', this.qFileInput.files[0]);231 if (this.dFileInput.files[0]) formData.append('data_file', this.dFileInput.files[0]);232 233 const response = await fetch('/api', { method:'POST', body:formData }); // changed endpoint234 235 if (!response.ok) {236 let msg = `HTTP ${response.status}`;237 try { const err = await response.json(); if (err && err.detail) msg = err.detail; } catch {}238 throw new Error(msg);239 }240 241 const data = await response.json();242 this.displayResults(data);243 } catch (err) {244 console.error('Analysis failed:', err);245 this.displayError(err.message || 'Unknown error');246 } finally {247 this.hideLoading();248 }249 }250 251 displayResults(data) {252 this.resultsContent.innerHTML = '';253 if (data.error) { this.displayError(data.error); return; }254 Object.entries(data).forEach(([question, answer]) => {255 const item = document.createElement('div');256 item.className = 'result-item';257 const qDiv = document.createElement('div');258 qDiv.className = 'question';259 qDiv.textContent = question;260 const aDiv = document.createElement('div');261 aDiv.className = 'answer';262 renderAnswerInto(answer, aDiv, (src) => this.showImageModal(src));263 item.appendChild(qDiv); item.appendChild(aDiv);264 this.resultsContent.appendChild(item);265 });266 this.showResults(); this.scrollToResults();267 }268 269 displayError(message) {270 this.resultsContent.innerHTML = '';271 const item = document.createElement('div');272 item.className = 'result-item error';273 const title = document.createElement('div');274 title.className = 'question';275 title.textContent = 'โ Error';276 const content = document.createElement('div');277 content.className = 'answer';278 content.textContent = message;279 item.appendChild(title); item.appendChild(content);280 this.resultsContent.appendChild(item);281 this.showResults(); this.scrollToResults();282 }283 284 showImageModal(src) { this.modalImage.src = src; this.modal.style.display = 'block'; document.body.style.overflow = 'hidden'; }285 hideImageModal() { this.modal.style.display = 'none'; document.body.style.overflow = 'auto'; }286 showLoading() { this.loading.style.display = 'block'; this.submitBtn.disabled = true; this.submitBtn.textContent = 'โณ Analyzing...'; }287 hideLoading() { this.loading.style.display = 'none'; this.submitBtn.disabled = false; this.submitBtn.textContent = '๐ Analyze Data'; }288 showResults() { this.results.style.display = 'block'; }289 hideResults() { this.results.style.display = 'none'; }290 scrollToResults() { setTimeout(() => this.results.scrollIntoView({ behavior:'smooth', block:'start' }), 100); }291 clearForm() { this.qFileInput.value = ''; this.dFileInput.value = ''; this.qInfo.style.display = 'none'; this.dInfo.style.display = 'none'; this.hideResults(); this.hideLoading(); }292 }293 294 document.addEventListener('DOMContentLoaded', () => { new DataAnalystApp(); });295 </script>296</body>297</html>298 