Data_Analyst_Agent / index.html
Aditya-Darshan-G's picture
Update index.html
a15dd26 verified
<!DOCTYPE html>
<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">&times;</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>