Spaces:
Sleeping
Sleeping
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"/> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"/> | |
<title>Data Analyst Agent</title> | |
<style> | |
* { margin:0; padding:0; box-sizing:border-box; } | |
body { | |
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
line-height: 1.6; color:#f5f5f5; | |
background: linear-gradient(135deg,#1e1e2f 0%,#2a2a3d 100%); | |
min-height:100vh; | |
} | |
.container { max-width:1200px; margin:0 auto; padding:20px; } | |
.header { text-align:center; margin-bottom:30px; color:#fff; } | |
.header h1 { font-size:2.5rem; margin-bottom:10px; text-shadow:2px 2px 4px rgba(0,0,0,.6); } | |
.header p { font-size:1.1rem; opacity:.85; } | |
.main-card { | |
background:#2c2c3e; border-radius:15px; box-shadow:0 10px 30px rgba(0,0,0,.4); | |
padding:30px; margin-bottom:30px; | |
} | |
.form-group label { display:block; margin-bottom:8px; font-weight:600; color:#ddd; } | |
.file-input { | |
width:100%; padding:15px; border:2px dashed #555; border-radius:8px; | |
background:#1f1f2e; cursor:pointer; transition:.3s ease; text-align:center; position:relative; | |
color:#bbb; | |
} | |
.file-input:hover { border-color:#667eea; background:#2a2a40; color:#fff; } | |
.file-info { margin-top:10px; padding:10px; background:#333; border-radius:5px; font-size:14px; color:#a8c7ff; display:none; } | |
.btn { | |
background:linear-gradient(135deg,#667eea 0%,#764ba2 100%); color:#fff; border:none; | |
padding:15px 30px; border-radius:8px; font-size:16px; font-weight:600; cursor:pointer; transition:.3s ease; width:100%; | |
} | |
.btn:hover { transform:translateY(-2px); box-shadow:0 5px 15px rgba(102,126,234,.6); } | |
.btn:disabled { background:#555; cursor:not-allowed; transform:none; box-shadow:none; } | |
.btn-secondary { background:#444; color:#ddd; } | |
.button-group { display:flex; gap:10px; margin-top:20px; flex-wrap:wrap; } | |
.loading { display:none; text-align:center; padding:20px; } | |
.spinner { | |
border:4px solid #444; border-top:4px solid #667eea; border-radius:50%; | |
width:40px; height:40px; animation:spin 1s linear infinite; margin:0 auto 15px; | |
} | |
@keyframes spin { 0%{transform:rotate(0)} 100%{transform:rotate(360deg)} } | |
.results { display:none; margin-top:30px; } | |
.result-item { background:#1f1f2e; border-left:4px solid #667eea; padding:20px; margin-bottom:20px; border-radius:0 8px 8px 0; } | |
.question { font-weight:600; color:#fff; margin-bottom:15px; font-size:1.1rem; } | |
.answer { color:#ccc; line-height:1.6; } | |
.answer pre { | |
background:#2c2c3e; color:#eee; | |
} | |
.answer img { | |
max-width:100%; height:auto; border-radius:8px; margin:10px 0; | |
box-shadow:0 4px 8px rgba(0,0,0,.5); cursor:pointer; transition:transform .3s ease; | |
} | |
.answer img:hover { transform:scale(1.02); } | |
.error { background:#3b1f1f; border-left-color:#dc3545; color:#ffb3b3; } | |
.modal { display:none; position:fixed; z-index:1000; left:0; top:0; width:100%; height:100%; background:rgba(0,0,0,.9); } | |
.modal-content { margin:auto; display:block; width:80%; max-width:700px; max-height:80%; object-fit:contain; } | |
.close { position:absolute; top:15px; right:35px; color:#f1f1f1; font-size:40px; font-weight:bold; cursor:pointer; } | |
.close:hover { color:#bbb; } | |
@media (max-width:768px){ | |
.container{padding:10px} | |
.header h1{font-size:2rem} | |
.main-card{padding:20px} | |
.button-group{flex-direction:column} | |
.btn-secondary{width:100%} | |
} | |
</style> | |
</head> | |
<body> | |
<div class="container"> | |
<div class="header"> | |
<h1>Data Analyst Agent</h1> | |
<p>Upload your questions file and optional dataset to get intelligent answers with visualizations</p> | |
</div> | |
<div class="main-card"> | |
<form id="analysisForm"> | |
<!-- Questions file (REQUIRED) --> | |
<div class="form-group"> | |
<label for="questions_file">Questions File (.txt) <span style="color:#dc3545">*</span></label> | |
<div class="file-input-wrapper"> | |
<div class="file-input" id="questionsDrop"> | |
<input type="file" id="questions_file" name="questions_file" accept=".txt"/> | |
<span>📁 Click to upload your questions (.txt)</span> | |
</div> | |
</div> | |
<div id="questionsInfo" class="file-info"></div> | |
</div> | |
<!-- Dataset file (OPTIONAL) --> | |
<div class="form-group"> | |
<label for="data_file">Upload Dataset (Optional)</label> | |
<div class="file-input-wrapper"> | |
<div class="file-input" id="dataDrop"> | |
<input type="file" id="data_file" name="data_file" accept=".csv,.xlsx,.xls,.json,.parquet,.txt"/> | |
<span>📁 Click or drag & drop your dataset</span> | |
</div> | |
</div> | |
<div id="dataInfo" class="file-info"></div> | |
</div> | |
<div class="button-group"> | |
<button type="submit" class="btn" id="submitBtn">🚀 Analyze Data</button> | |
<button type="button" class="btn btn-secondary" id="clearBtn">🗑️ Clear</button> | |
</div> | |
</form> | |
<div class="loading" id="loading"> | |
<div class="spinner"></div> | |
<p>Analyzing your data... This may take a few moments.</p> | |
</div> | |
<div class="results" id="results"> | |
<h3>📊 Analysis Results</h3> | |
<div id="resultsContent"></div> | |
</div> | |
</div> | |
</div> | |
<!-- Image Modal --> | |
<div id="imageModal" class="modal"> | |
<span class="close">×</span> | |
<img class="modal-content" id="modalImage" alt="Visualization"/> | |
</div> | |
<script> | |
/* ---------- Image normalization helpers ---------- */ | |
function normalizeImageData(answer) { | |
let s = null; | |
if (typeof answer === 'string') { s = answer.trim(); } | |
else if (answer && typeof answer === 'object') { | |
s = answer.image || answer.base64 || answer.plot || answer.data || null; | |
if (typeof s === 'string') s = s.trim(); | |
} | |
if (!s) return null; | |
s = s.replace(/^data:aimage\//i, 'data:image/'); | |
if (/^data:image\//i.test(s)) return s; | |
const base64Like = /^[A-Za-z0-9+/=\r\n]+$/.test(s) && s.length > 200 && !/\s{2,}/.test(s); | |
if (base64Like) return 'data:image/png;base64,' + s; | |
return null; | |
} | |
function renderAnswerInto(answer, container, onImageClick) { | |
const maybeImg = normalizeImageData(answer); | |
if (maybeImg) { | |
const img = document.createElement('img'); | |
img.src = maybeImg; | |
img.alt = 'Generated visualization'; | |
img.addEventListener('click', () => onImageClick(maybeImg)); | |
container.appendChild(img); | |
return; | |
} | |
const text = (answer && typeof answer === 'object') ? JSON.stringify(answer, null, 2) : String(answer ?? ''); | |
if (text.includes('\n') || text.length > 120) { | |
const pre = document.createElement('pre'); | |
pre.style.whiteSpace = 'pre-wrap'; | |
pre.style.background = '#f8f9fa'; | |
pre.style.padding = '15px'; | |
pre.style.borderRadius = '5px'; | |
pre.style.overflow = 'auto'; | |
pre.textContent = text; | |
container.appendChild(pre); | |
} else { | |
container.textContent = text; | |
} | |
} | |
/* ---------------- App ---------------- */ | |
class DataAnalystApp { | |
constructor() { | |
this.form = document.getElementById('analysisForm'); | |
this.qFileInput = document.getElementById('questions_file'); | |
this.dFileInput = document.getElementById('data_file'); | |
this.qDrop = document.getElementById('questionsDrop'); | |
this.dDrop = document.getElementById('dataDrop'); | |
this.qInfo = document.getElementById('questionsInfo'); | |
this.dInfo = document.getElementById('dataInfo'); | |
this.submitBtn = document.getElementById('submitBtn'); | |
this.clearBtn = document.getElementById('clearBtn'); | |
this.loading = document.getElementById('loading'); | |
this.results = document.getElementById('results'); | |
this.resultsContent = document.getElementById('resultsContent'); | |
this.modal = document.getElementById('imageModal'); | |
this.modalImage = document.getElementById('modalImage'); | |
this.closeModal = document.querySelector('.close'); | |
this.initEventListeners(); | |
} | |
initEventListeners() { | |
this.form.addEventListener('submit', (e) => this.handleSubmit(e)); | |
this.clearBtn.addEventListener('click', () => this.clearForm()); | |
this.qFileInput.addEventListener('change', (e) => this.handleFileSelect(e, 'q')); | |
this.dFileInput.addEventListener('change', (e) => this.handleFileSelect(e, 'd')); | |
this.closeModal.addEventListener('click', () => this.hideImageModal()); | |
this.modal.addEventListener('click', (e) => { if (e.target === this.modal) this.hideImageModal(); }); | |
this.setupDrop(this.dDrop, (files) => { if (files && files.length > 0) { this.dFileInput.files = files; this.handleFileSelect({ target: { files } }, 'd'); } }); | |
this.setupDrop(this.qDrop, (files) => { if (files && files.length > 0) { this.qFileInput.files = files; this.handleFileSelect({ target: { files } }, 'q'); } }); | |
} | |
setupDrop(zone, onDropFiles) { | |
zone.addEventListener('dragover', (e) => { e.preventDefault(); zone.style.borderColor = '#667eea'; }); | |
zone.addEventListener('dragleave', (e) => { e.preventDefault(); zone.style.borderColor = '#e1e5e9'; }); | |
zone.addEventListener('drop', (e) => { e.preventDefault(); zone.style.borderColor = '#e1e5e9'; onDropFiles(e.dataTransfer.files); }); | |
} | |
handleFileSelect(event, type) { | |
const file = event.target.files[0]; | |
if (type === 'q') { | |
if (file) { this.qInfo.innerHTML = `<strong>Questions:</strong> ${file.name} (${(file.size / 1024).toFixed(2)} KB)`; this.qInfo.style.display = 'block'; } | |
else { this.qInfo.style.display = 'none'; } | |
} else { | |
if (file) { this.dInfo.innerHTML = `<strong>Dataset:</strong> ${file.name} (${(file.size / 1024).toFixed(2)} KB)`; this.dInfo.style.display = 'block'; } | |
else { this.dInfo.style.display = 'none'; } | |
} | |
} | |
async handleSubmit(event) { | |
event.preventDefault(); | |
if (!this.qFileInput.files[0]) { alert('Please upload your questions .txt file.'); return; } | |
this.showLoading(); this.hideResults(); | |
try { | |
const formData = new FormData(); | |
formData.append('questions_file', this.qFileInput.files[0]); | |
if (this.dFileInput.files[0]) formData.append('data_file', this.dFileInput.files[0]); | |
const response = await fetch('/api', { method:'POST', body:formData }); // changed endpoint | |
if (!response.ok) { | |
let msg = `HTTP ${response.status}`; | |
try { const err = await response.json(); if (err && err.detail) msg = err.detail; } catch {} | |
throw new Error(msg); | |
} | |
const data = await response.json(); | |
this.displayResults(data); | |
} catch (err) { | |
console.error('Analysis failed:', err); | |
this.displayError(err.message || 'Unknown error'); | |
} finally { | |
this.hideLoading(); | |
} | |
} | |
displayResults(data) { | |
this.resultsContent.innerHTML = ''; | |
if (data.error) { this.displayError(data.error); return; } | |
Object.entries(data).forEach(([question, answer]) => { | |
const item = document.createElement('div'); | |
item.className = 'result-item'; | |
const qDiv = document.createElement('div'); | |
qDiv.className = 'question'; | |
qDiv.textContent = question; | |
const aDiv = document.createElement('div'); | |
aDiv.className = 'answer'; | |
renderAnswerInto(answer, aDiv, (src) => this.showImageModal(src)); | |
item.appendChild(qDiv); item.appendChild(aDiv); | |
this.resultsContent.appendChild(item); | |
}); | |
this.showResults(); this.scrollToResults(); | |
} | |
displayError(message) { | |
this.resultsContent.innerHTML = ''; | |
const item = document.createElement('div'); | |
item.className = 'result-item error'; | |
const title = document.createElement('div'); | |
title.className = 'question'; | |
title.textContent = '❌ Error'; | |
const content = document.createElement('div'); | |
content.className = 'answer'; | |
content.textContent = message; | |
item.appendChild(title); item.appendChild(content); | |
this.resultsContent.appendChild(item); | |
this.showResults(); this.scrollToResults(); | |
} | |
showImageModal(src) { this.modalImage.src = src; this.modal.style.display = 'block'; document.body.style.overflow = 'hidden'; } | |
hideImageModal() { this.modal.style.display = 'none'; document.body.style.overflow = 'auto'; } | |
showLoading() { this.loading.style.display = 'block'; this.submitBtn.disabled = true; this.submitBtn.textContent = '⏳ Analyzing...'; } | |
hideLoading() { this.loading.style.display = 'none'; this.submitBtn.disabled = false; this.submitBtn.textContent = '🚀 Analyze Data'; } | |
showResults() { this.results.style.display = 'block'; } | |
hideResults() { this.results.style.display = 'none'; } | |
scrollToResults() { setTimeout(() => this.results.scrollIntoView({ behavior:'smooth', block:'start' }), 100); } | |
clearForm() { this.qFileInput.value = ''; this.dFileInput.value = ''; this.qInfo.style.display = 'none'; this.dInfo.style.display = 'none'; this.hideResults(); this.hideLoading(); } | |
} | |
document.addEventListener('DOMContentLoaded', () => { new DataAnalystApp(); }); | |
</script> | |
</body> | |
</html> | |