quantum-ai-v2/data_analyzer/templates/data_analyzer/detail.html
Claude 179e473897 Fix JavaScript CSRF token error in data analyzer
- Add missing {% csrf_token %} to data analyzer template
- Resolves "Cannot read properties of null (reading 'value')" error
- JavaScript now properly accesses CSRF token for form submission

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-12 08:53:58 +05:30

781 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: var(--gradient-hero);
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: var(--text-primary);
margin-bottom: clamp(12px, 3vw, 16px);
}
/* Form inputs */
.form-input {
width: 100%;
padding: clamp(12px, 3vw, 16px) clamp(16px, 4vw, 20px);
border: 2px solid var(--border-medium);
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: var(--success-green);
}
.help-text {
font-size: clamp(12px, 3vw, 14px);
color: var(--text-secondary);
}
/* File upload zone */
.file-upload-zone {
border: 2px dashed var(--border-medium);
border-radius: clamp(12px, 3vw, 16px);
padding: clamp(24px, 6vw, 40px);
text-align: center;
transition: all 0.3s ease;
background: var(--background-light);
cursor: pointer;
margin-bottom: clamp(12px, 3vw, 16px);
}
.file-upload-zone.dragover {
border-color: var(--success-green);
background: rgba(16, 185, 129, 0.1);
}
.file-upload-zone:hover {
border-color: var(--success-green);
background: rgba(16, 185, 129, 0.1);
}
.file-preview {
background: rgba(16, 185, 129, 0.1);
border: 1px solid rgba(16, 185, 129, 0.5);
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 var(--border-light);
border-radius: clamp(8px, 2vw, 12px);
cursor: pointer;
background: white;
transition: all 0.2s ease;
min-height: 44px;
}
.radio-option.selected {
border-color: var(--success-green);
background: rgba(16, 185, 129, 0.1);
}
.radio-option input[type="radio"] {
margin: 0;
}
/* Processing status */
.processing-status {
padding: clamp(16px, 4vw, 20px);
background: rgba(16, 185, 129, 0.1);
border: 1px solid var(--success-green);
border-radius: clamp(8px, 2vw, 12px);
color: var(--success-dark);
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: var(--background-page);
border: 1px solid var(--border-light);
border-radius: 12px;
padding: 24px;
margin-bottom: 20px;
white-space: pre-line;
line-height: 1.7;
color: var(--text-primary);
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: var(--gradient-success);
color: white;
flex: 1;
min-width: 120px;
}
.btn-secondary {
background: var(--background-subtle);
color: var(--text-primary);
border: 2px solid var(--border-medium);
flex: 1;
min-width: 120px;
}
.btn:hover {
transform: translateY(-1px);
}
.btn:disabled {
background: var(--border-strong);
color: white;
cursor: not-allowed;
transform: none;
opacity: 0.6;
}
/* Wallet sidebar */
.wallet-section {
position: sticky;
top: 20px;
}
.wallet-balance {
font-size: clamp(24px, 6vw, 28px);
font-weight: 700;
color: var(--text-primary);
margin-bottom: 8px;
}
.balance-label {
font-size: clamp(14px, 3.5vw, 16px);
color: var(--text-secondary);
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: var(--success-dark);
}
.usage-info ul {
margin: 0;
font-size: 12px;
color: var(--text-primary);
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: var(--success-green);
}
/* 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">
{% csrf_token %}
<!-- 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: var(--text-primary); margin-bottom: 8px;">
Choose or drag your data file here
</div>
<div style="font-size: clamp(14px, 3.5vw, 16px); color: var(--text-secondary);">
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: var(--text-primary);" id="fileName"></div>
<div style="font-size: 14px; color: var(--text-secondary);" id="fileSize"></div>
</div>
<button type="button" onclick="removeFile()" style="
background: var(--error-red);
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: var(--text-secondary);">
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: var(--text-secondary);">
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: var(--text-secondary);">
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: var(--text-primary); margin: 0;">
Data Analysis Results
</h3>
<div style="
background: var(--success-green);
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: var(--success-green);' : 'background: var(--error-red);'}
`;
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(() => {
toast.remove();
}, 3000);
}
</script>
{% endblock %}