quantum-ai-v3/data_analyzer/templates/data_analyzer/detail.html
Claude 07d61dbf63 Fix Data Analyzer wallet balance persistence issue
Replace demo JavaScript implementation with real backend processing:
- Remove fake analyzeData() function that only updated frontend
- Add real AJAX submission to /agents/data-analyzer/process/ endpoint
- Implement proper polling for results using request ID
- Add displayResults() function to handle backend responses
- Update wallet balance from server response to ensure persistence
- Remove generateSampleAnalysis() demo function
- Fix wallet balance to persist across page navigation

This resolves the issue where wallet deduction would revert when
navigating to other pages, as the frontend was using demo mode
instead of actual database transactions.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-11 22:23:25 +05:30

778 lines
27 KiB
HTML

{% extends 'base.html' %}
{% load static %}
{% block title %}Data Analyzer Agent - NetCop AI Hub{% endblock %}
{% block extra_css %}
<style>
* {
box-sizing: border-box;
}
/* Override main-container for full-width sections */
.main-container {
max-width: none;
padding: 0;
margin-top: 0;
}
/* Page background */
.data-analyzer-page {
background: linear-gradient(135deg, #f6f8ff 0%, #e8f0fe 50%, #f0f7ff 100%);
min-height: calc(100vh - 80px);
padding: clamp(20px, 5vw, 40px);
width: 100vw;
margin-left: calc(-50vw + 50%);
}
.container {
max-width: 1280px;
margin: 0 auto;
padding: 0 clamp(16px, 4vw, 24px);
}
/* Main grid layout */
.main-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(350px, 100%), 1fr));
gap: clamp(16px, 4vw, 24px);
align-items: start;
}
/* Card styles with glassmorphism */
.card {
background: rgba(255, 255, 255, 0.9);
border-radius: clamp(12px, 3vw, 16px);
padding: clamp(16px, 4vw, 24px);
border: 1px solid rgba(255, 255, 255, 0.3);
backdrop-filter: blur(20px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
margin-bottom: clamp(16px, 4vw, 24px);
}
.section-title {
font-size: clamp(16px, 4vw, 18px);
font-weight: 600;
color: #1f2937;
margin-bottom: clamp(12px, 3vw, 16px);
}
/* Form inputs */
.form-input {
width: 100%;
padding: clamp(12px, 3vw, 16px) clamp(16px, 4vw, 20px);
border: 2px solid #e5e7eb;
border-radius: clamp(8px, 2vw, 12px);
font-size: clamp(14px, 3.5vw, 16px);
transition: border-color 0.2s ease;
min-height: 48px;
margin-bottom: clamp(12px, 3vw, 16px);
}
.form-input:focus {
outline: none;
border-color: #10b981;
}
.help-text {
font-size: clamp(12px, 3vw, 14px);
color: #6b7280;
}
/* File upload zone */
.file-upload-zone {
border: 2px dashed #d1d5db;
border-radius: clamp(12px, 3vw, 16px);
padding: clamp(24px, 6vw, 40px);
text-align: center;
transition: all 0.3s ease;
background: #f9fafb;
cursor: pointer;
margin-bottom: clamp(12px, 3vw, 16px);
}
.file-upload-zone.dragover {
border-color: #10b981;
background: #f0fdf4;
}
.file-upload-zone:hover {
border-color: #10b981;
background: #f0fdf4;
}
.file-preview {
background: #f0fdf4;
border: 1px solid #86efac;
border-radius: 8px;
padding: 12px;
margin-top: 12px;
display: flex;
align-items: center;
gap: 12px;
}
/* Radio button grids */
.radio-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(180px, 100%), 1fr));
gap: clamp(8px, 2vw, 12px);
}
.radio-option {
display: flex;
align-items: center;
gap: clamp(8px, 2vw, 12px);
padding: clamp(12px, 3vw, 16px);
border: 2px solid #e5e7eb;
border-radius: clamp(8px, 2vw, 12px);
cursor: pointer;
background: white;
transition: all 0.2s ease;
min-height: 44px;
}
.radio-option.selected {
border-color: #10b981;
background: #f0fdf4;
}
.radio-option input[type="radio"] {
margin: 0;
}
/* Processing status */
.processing-status {
padding: clamp(16px, 4vw, 20px);
background: #f0fdf4;
border: 1px solid #10b981;
border-radius: clamp(8px, 2vw, 12px);
color: #047857;
font-weight: 600;
text-align: center;
margin-bottom: clamp(16px, 4vw, 24px);
}
/* Results card */
.results-card {
background: rgba(255, 255, 255, 0.9);
border-radius: 16px;
padding: 24px;
border: 1px solid rgba(255, 255, 255, 0.3);
backdrop-filter: blur(20px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
margin-top: 24px;
}
.results-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 20px;
}
.results-content {
background: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 12px;
padding: 24px;
margin-bottom: 20px;
white-space: pre-line;
line-height: 1.7;
color: #374151;
font-size: 15px;
}
.action-buttons {
display: flex;
gap: 12px;
margin-top: 20px;
flex-wrap: wrap;
}
/* Button styles */
.btn {
padding: clamp(12px, 3vw, 16px) clamp(20px, 5vw, 32px);
border: none;
border-radius: clamp(8px, 2vw, 12px);
font-weight: 600;
cursor: pointer;
font-size: clamp(14px, 3.5vw, 16px);
min-height: 48px;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
transition: transform 0.1s ease;
text-decoration: none;
}
.btn-primary {
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
color: white;
flex: 1;
min-width: 120px;
}
.btn-secondary {
background: white;
color: #374151;
border: 2px solid #e5e7eb;
flex: 1;
min-width: 120px;
}
.btn:hover {
transform: translateY(-1px);
}
.btn:disabled {
background: #9ca3af;
cursor: not-allowed;
transform: none;
}
/* Wallet sidebar */
.wallet-section {
position: sticky;
top: 20px;
}
.wallet-balance {
font-size: clamp(24px, 6vw, 28px);
font-weight: 700;
color: #1f2937;
margin-bottom: 8px;
}
.balance-label {
font-size: clamp(14px, 3.5vw, 16px);
color: #6b7280;
margin-bottom: clamp(16px, 4vw, 20px);
}
.process-btn {
width: 100%;
margin-bottom: 12px;
}
.usage-info {
padding: 16px;
background: rgba(16, 185, 129, 0.1);
border-radius: 12px;
border: 1px solid rgba(16, 185, 129, 0.2);
}
.usage-info h4 {
margin: 0 0 8px 0;
font-size: 14px;
font-weight: 600;
color: #047857;
}
.usage-info ul {
margin: 0;
font-size: 12px;
color: #374151;
line-height: 1.4;
list-style: none;
padding-left: 0;
}
.usage-info li {
margin: 4px 0;
padding-left: 16px;
position: relative;
}
.usage-info li::before {
content: "•";
position: absolute;
left: 0;
color: #10b981;
}
/* Responsive */
@media (max-width: 768px) {
.main-grid {
grid-template-columns: 1fr;
}
.radio-grid {
grid-template-columns: 1fr;
}
.action-buttons {
flex-direction: column;
}
}
</style>
{% endblock %}
{% block content %}
<div class="data-analyzer-page">
<div class="container">
<!-- Main Content Grid -->
<div class="main-grid">
<!-- Input Form -->
<div>
<!-- File Upload Section -->
<div class="card">
<h3 class="section-title">📁 Upload Your Data File</h3>
<div class="file-upload-zone" id="fileUploadZone" onclick="document.getElementById('fileInput').click()">
<div style="font-size: clamp(32px, 8vw, 48px); margin-bottom: 12px;">📊</div>
<div style="font-size: clamp(16px, 4vw, 18px); font-weight: 600; color: #374151; margin-bottom: 8px;">
Choose or drag your data file here
</div>
<div style="font-size: clamp(14px, 3.5vw, 16px); color: #6b7280;">
Supports PDF, CSV, Excel files (up to 10MB)
</div>
</div>
<input
type="file"
id="fileInput"
accept=".pdf,.csv,.xlsx,.xls"
style="display: none;"
/>
<div id="filePreview" class="file-preview" style="display: none;">
<div style="font-size: 24px;">📄</div>
<div style="flex: 1;">
<div style="font-weight: 600; color: #1f2937;" id="fileName"></div>
<div style="font-size: 14px; color: #6b7280;" id="fileSize"></div>
</div>
<button type="button" onclick="removeFile()" style="
background: #ef4444;
color: white;
border: none;
border-radius: 4px;
padding: 4px 8px;
cursor: pointer;
font-size: 12px;
">
Remove
</button>
</div>
</div>
<!-- Analysis Type Selection -->
<div class="card">
<h3 class="section-title">🔍 Analysis Type</h3>
<div class="radio-grid">
<label class="radio-option selected" data-value="summary">
<input type="radio" name="analysisType" value="summary" checked />
<div style="font-size: clamp(16px, 4vw, 20px);">📋</div>
<div>
<div style="font-weight: 600; margin-bottom: clamp(2px, 1vw, 4px); font-size: clamp(14px, 3.5vw, 16px);">
Summary Analysis
</div>
<div style="font-size: clamp(12px, 3vw, 14px); color: #6b7280;">
Quick overview and key insights
</div>
</div>
</label>
<label class="radio-option" data-value="detailed">
<input type="radio" name="analysisType" value="detailed" />
<div style="font-size: clamp(16px, 4vw, 20px);">📈</div>
<div>
<div style="font-weight: 600; margin-bottom: clamp(2px, 1vw, 4px); font-size: clamp(14px, 3.5vw, 16px);">
Detailed Analysis
</div>
<div style="font-size: clamp(12px, 3vw, 14px); color: #6b7280;">
Comprehensive statistical analysis
</div>
</div>
</label>
<label class="radio-option" data-value="statistical">
<input type="radio" name="analysisType" value="statistical" />
<div style="font-size: clamp(16px, 4vw, 20px);">📊</div>
<div>
<div style="font-weight: 600; margin-bottom: clamp(2px, 1vw, 4px); font-size: clamp(14px, 3.5vw, 16px);">
Statistical Analysis
</div>
<div style="font-size: clamp(12px, 3vw, 14px); color: #6b7280;">
Advanced statistics and trends
</div>
</div>
</label>
</div>
</div>
<!-- Processing Status -->
<div id="processingStatus" class="processing-status" style="display: none;">
<div style="font-size: clamp(16px, 4vw, 18px); margin-bottom: 8px;">
⏳ Processing...
</div>
<div style="font-size: clamp(14px, 3.5vw, 16px);" id="statusText">
Analyzing your data file...
</div>
</div>
<!-- Results -->
<div id="analysisResults" class="results-card" style="display: none;">
<div class="results-header">
<div style="font-size: 24px;"></div>
<h3 style="font-size: 20px; font-weight: 600; color: #1f2937; margin: 0;">
Data Analysis Results
</h3>
<div style="
background: #10b981;
color: white;
padding: 6px 12px;
border-radius: 6px;
font-size: 14px;
font-weight: 600;
margin-left: auto;
">
✅ Complete
</div>
</div>
<div class="results-content" id="analysisContent">
<!-- Analysis data will be displayed here -->
</div>
<div class="action-buttons">
<button onclick="copyAnalysisReport()" class="btn btn-primary">
📋 Copy Report
</button>
<button onclick="downloadAnalysisReport()" class="btn btn-secondary">
💾 Download Report
</button>
</div>
</div>
</div>
<!-- Wallet Sidebar -->
<div class="wallet-section">
<div class="card">
<h3 class="section-title">💳 Your Wallet</h3>
<div class="wallet-balance" id="walletBalance">{{ user.wallet_balance|floatformat:2 }} AED</div>
<div class="balance-label">Available Balance</div>
<button
class="btn btn-primary process-btn"
id="processButton"
onclick="analyzeData()"
>
📊 Analyze Data (5.00 AED)
</button>
</div>
<div class="usage-info">
<h4>💡 How it works</h4>
<ul>
<li>Upload PDF, CSV, or Excel files</li>
<li>Choose your analysis depth</li>
<li>Get AI-powered insights</li>
<li>Download comprehensive reports</li>
</ul>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
let selectedFile = null;
// File upload handling
const fileInput = document.getElementById('fileInput');
const fileUploadZone = document.getElementById('fileUploadZone');
const filePreview = document.getElementById('filePreview');
// Handle file input change
fileInput.addEventListener('change', function(e) {
if (e.target.files.length > 0) {
selectedFile = e.target.files[0];
showFilePreview(selectedFile);
}
});
// Drag and drop functionality
fileUploadZone.addEventListener('dragover', function(e) {
e.preventDefault();
fileUploadZone.classList.add('dragover');
});
fileUploadZone.addEventListener('dragleave', function(e) {
e.preventDefault();
fileUploadZone.classList.remove('dragover');
});
fileUploadZone.addEventListener('drop', function(e) {
e.preventDefault();
fileUploadZone.classList.remove('dragover');
const files = e.dataTransfer.files;
if (files.length > 0) {
const file = files[0];
if (isValidFile(file)) {
selectedFile = file;
fileInput.files = files;
showFilePreview(file);
} else {
showToast('Please select a valid data file (PDF, CSV, Excel)', 'error');
}
}
});
// Show file preview
function showFilePreview(file) {
document.getElementById('fileName').textContent = file.name;
document.getElementById('fileSize').textContent = formatFileSize(file.size);
filePreview.style.display = 'flex';
fileUploadZone.style.display = 'none';
}
// Remove file
function removeFile() {
selectedFile = null;
fileInput.value = '';
filePreview.style.display = 'none';
fileUploadZone.style.display = 'block';
}
// Validate file type
function isValidFile(file) {
const validTypes = [
'application/pdf',
'text/csv',
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
];
return validTypes.includes(file.type) || file.name.match(/\.(pdf|csv|xlsx|xls)$/i);
}
// Format file size
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];
}
// Radio button handling
document.querySelectorAll('.radio-option').forEach(option => {
option.addEventListener('click', function() {
document.querySelectorAll('.radio-option').forEach(opt => opt.classList.remove('selected'));
this.classList.add('selected');
this.querySelector('input[type="radio"]').checked = true;
});
});
// Analysis function
function analyzeData() {
if (!selectedFile) {
showToast('Please select a data file', 'error');
return;
}
// Check user authentication
{% if not user.is_authenticated %}
window.location.href = "{% url 'authentication:login' %}";
return;
{% endif %}
// Check wallet balance
const balance = {{ user.wallet_balance|default:0 }};
if (balance < 5.00) {
showToast('Insufficient balance! You need 5.00 AED.', 'error');
setTimeout(() => {
window.location.href = "{% url 'core:wallet' %}";
}, 2000);
return;
}
const analysisType = document.querySelector('input[name="analysisType"]:checked').value;
// Show processing status
document.getElementById('processingStatus').style.display = 'block';
document.getElementById('processButton').disabled = true;
document.getElementById('processButton').innerHTML = '⏳ Processing...';
document.getElementById('analysisResults').style.display = 'none';
// Processing steps for user feedback
const steps = [
'Reading file structure...',
'Extracting data patterns...',
'Performing statistical analysis...',
'Generating insights...',
'Finalizing report...'
];
let currentStep = 0;
const stepInterval = setInterval(() => {
if (currentStep < steps.length) {
document.getElementById('statusText').textContent = steps[currentStep];
currentStep++;
} else {
clearInterval(stepInterval);
}
}, 1000);
// Submit form data to backend
const formData = new FormData();
formData.append('file', selectedFile);
formData.append('analysis_type', analysisType);
formData.append('csrfmiddlewaretoken', document.querySelector('[name=csrfmiddlewaretoken]').value);
fetch('/agents/data-analyzer/process/', {
method: 'POST',
body: formData,
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(response => response.json())
.then(result => {
clearInterval(stepInterval);
if (result.success && result.request_id) {
// Start polling for results
pollForResults(result.request_id);
} else {
// Handle immediate response
document.getElementById('processingStatus').style.display = 'none';
document.getElementById('processButton').disabled = false;
document.getElementById('processButton').innerHTML = '📊 Analyze Data (5.00 AED)';
if (result.error) {
showToast(`${result.error}`, 'error');
} else {
displayResults(result);
}
}
})
.catch(error => {
clearInterval(stepInterval);
console.error('Error:', error);
document.getElementById('processingStatus').style.display = 'none';
document.getElementById('processButton').disabled = false;
document.getElementById('processButton').innerHTML = '📊 Analyze Data (5.00 AED)';
showToast('❌ Network error - please try again', 'error');
});
}
// Display analysis results
function displayResults(result) {
const resultsContainer = document.getElementById('analysisResults');
const contentContainer = document.getElementById('analysisContent');
if (result.success && result.status === 'completed') {
// Use the analysis content from backend
const content = result.report_text || result.analysis_results || result.insights_summary || 'Data analysis completed successfully!';
contentContainer.textContent = content;
resultsContainer.style.display = 'block';
// Update wallet balance if provided
if (result.wallet_balance !== undefined) {
updateWalletBalance(result.wallet_balance);
}
showToast('✅ Data analysis completed and payment processed!', 'success');
} else {
showToast('❌ Failed to analyze data - no charge applied', 'error');
}
}
// Poll for results
function pollForResults(requestId) {
let pollCount = 0;
const maxPolls = 60; // 60 seconds maximum for data analysis
const pollInterval = setInterval(() => {
pollCount++;
fetch(`/agents/data-analyzer/result/${requestId}/`)
.then(response => response.json())
.then(result => {
if (result.status === 'completed' || result.status === 'failed') {
clearInterval(pollInterval);
document.getElementById('processingStatus').style.display = 'none';
document.getElementById('processButton').disabled = false;
document.getElementById('processButton').innerHTML = '📊 Analyze Data (5.00 AED)';
displayResults(result);
} else if (pollCount >= maxPolls) {
clearInterval(pollInterval);
document.getElementById('processingStatus').style.display = 'none';
document.getElementById('processButton').disabled = false;
document.getElementById('processButton').innerHTML = '📊 Analyze Data (5.00 AED)';
showToast('❌ Processing timeout - please try again', 'error');
}
})
.catch(error => {
console.error('Error polling results:', error);
if (pollCount >= maxPolls) {
clearInterval(pollInterval);
document.getElementById('processingStatus').style.display = 'none';
document.getElementById('processButton').disabled = false;
document.getElementById('processButton').innerHTML = '📊 Analyze Data (5.00 AED)';
showToast('❌ Network error - please try again', 'error');
}
});
}, 1000);
}
function updateWalletBalance(newBalance) {
// Update wallet balance display
const balanceElements = document.querySelectorAll('[data-wallet-balance]');
balanceElements.forEach(element => {
element.textContent = `${newBalance.toFixed(2)} AED`;
});
window.currentWalletBalance = newBalance;
}
function copyAnalysisReport() {
const reportText = document.getElementById('analysisContent').textContent;
navigator.clipboard.writeText(reportText).then(() => {
showToast('📋 Analysis report copied to clipboard!', 'success');
}).catch(() => {
showToast('Failed to copy report', 'error');
});
}
function downloadAnalysisReport() {
const reportText = document.getElementById('analysisContent').textContent;
const blob = new Blob([reportText], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `data-analysis-report-${Date.now()}.txt`;
a.click();
URL.revokeObjectURL(url);
showToast('💾 Analysis report downloaded!', 'success');
}
function showToast(message, type = 'info') {
const toast = document.createElement('div');
toast.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
padding: 12px 20px;
border-radius: 8px;
color: white;
font-weight: 600;
z-index: 1000;
${type === 'success' ? 'background: #10b981;' : 'background: #ef4444;'}
`;
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(() => {
toast.remove();
}, 3000);
}
</script>
{% endblock %}