mirror of
https://github.com/thecyberlearn/quantum-ai.git
synced 2026-08-18 13:52:59 +00:00
data analyzer ui fix
This commit is contained in:
parent
6733027bfc
commit
f442faf86b
@ -819,7 +819,7 @@ body {
|
|||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
width: 400px;
|
width: min(400px, 90vw);
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
background: var(--surface);
|
background: var(--surface);
|
||||||
border-left: 1px solid var(--outline);
|
border-left: 1px solid var(--outline);
|
||||||
@ -1161,38 +1161,26 @@ body {
|
|||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Toast Notifications */
|
|
||||||
.toast {
|
|
||||||
position: fixed;
|
|
||||||
top: 20px;
|
|
||||||
right: 20px;
|
|
||||||
background: var(--surface);
|
|
||||||
border: 1px solid var(--outline);
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
padding: var(--spacing-md) var(--spacing-lg);
|
|
||||||
box-shadow: var(--shadow-lg);
|
|
||||||
z-index: 1000;
|
|
||||||
max-width: 400px;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toast.success {
|
|
||||||
border-color: var(--success);
|
|
||||||
background: #f0fdf4;
|
|
||||||
color: #16a34a;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toast.error {
|
|
||||||
border-color: var(--error);
|
|
||||||
background: #fef2f2;
|
|
||||||
color: #dc2626;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
<script>
|
||||||
|
// Consolidated DOMContentLoaded initialization
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Set data for JavaScript access
|
||||||
|
document.body.setAttribute('data-user-authenticated', '{{ user.is_authenticated|yesno:"true,false" }}');
|
||||||
|
document.body.setAttribute('data-login-url', '{% url "authentication:login" %}');
|
||||||
|
document.body.setAttribute('data-wallet-balance', '{{ user.wallet_balance|default:0 }}');
|
||||||
|
document.body.setAttribute('data-wallet-url', '{% url "core:wallet" %}');
|
||||||
|
|
||||||
|
// Initialize radio selection
|
||||||
|
selectRadio('summary');
|
||||||
|
|
||||||
|
// Initialize agent widgets
|
||||||
|
initializeAgent();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
<div class="agent-container">
|
<div class="agent-container">
|
||||||
<!-- Agent Header -->
|
<!-- Agent Header -->
|
||||||
<div class="agent-header">
|
<div class="agent-header">
|
||||||
@ -1201,13 +1189,7 @@ body {
|
|||||||
<p class="agent-subtitle">Upload your data file and get AI-powered analysis with advanced insights</p>
|
<p class="agent-subtitle">Upload your data file and get AI-powered analysis with advanced insights</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-controls">
|
<div class="header-controls">
|
||||||
<!-- Quick Agent Access Toggle -->
|
<div class="wallet-card widget-small" style="margin-bottom: 0;">
|
||||||
<button class="quick-agent-toggle" onclick="toggleQuickAgents()" title="Quick access to other agents">
|
|
||||||
<span class="toggle-icon">🚀</span>
|
|
||||||
<span class="toggle-text">Other Agents</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div class="wallet-card" style="margin-bottom: 0; min-width: 280px; max-width: 280px;">
|
|
||||||
<div class="wallet-header">
|
<div class="wallet-header">
|
||||||
<h3 class="wallet-title">Your Wallet</h3>
|
<h3 class="wallet-title">Your Wallet</h3>
|
||||||
<div class="wallet-icon">💳</div>
|
<div class="wallet-icon">💳</div>
|
||||||
@ -1223,11 +1205,11 @@ body {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Quick Agent Access Panel -->
|
<!-- Quick Agent Access Panel -->
|
||||||
<div class="quick-agents-overlay" id="quickAgentsOverlay" onclick="closeQuickAgents()"></div>
|
<div class="quick-agents-overlay" id="quickAgentsOverlay" onclick="closeQuickAgents()" aria-hidden="true"></div>
|
||||||
<div class="quick-agents-panel" id="quickAgentsPanel">
|
<div class="quick-agents-panel" id="quickAgentsPanel" role="dialog" aria-labelledby="quickAgentsTitle" aria-hidden="true">
|
||||||
<div class="quick-agents-header">
|
<div class="quick-agents-header">
|
||||||
<h3>Quick Access to Other Agents</h3>
|
<h3 id="quickAgentsTitle">Quick Access to Other Agents</h3>
|
||||||
<button class="close-panel" onclick="toggleQuickAgents()">×</button>
|
<button class="close-panel" onclick="toggleQuickAgents()" aria-label="Close quick agents panel">×</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="quick-agents-grid">
|
<div class="quick-agents-grid">
|
||||||
<a href="/agents/weather-reporter/" class="quick-agent-card">
|
<a href="/agents/weather-reporter/" class="quick-agent-card">
|
||||||
@ -1274,7 +1256,7 @@ body {
|
|||||||
<!-- Agent Grid -->
|
<!-- Agent Grid -->
|
||||||
<div class="agent-grid">
|
<div class="agent-grid">
|
||||||
<!-- Upload Widget -->
|
<!-- Upload Widget -->
|
||||||
<div class="agent-widget widget-large" style="flex: 1; margin-right: var(--spacing-lg);">
|
<div class="agent-widget widget-large" style="flex: 1; margin-right: clamp(0px, var(--spacing-lg), 2vw);">
|
||||||
<div class="widget-header">
|
<div class="widget-header">
|
||||||
<h3 class="widget-title">
|
<h3 class="widget-title">
|
||||||
<span class="widget-icon">📤</span>
|
<span class="widget-icon">📤</span>
|
||||||
@ -1288,12 +1270,14 @@ body {
|
|||||||
<!-- File Upload -->
|
<!-- File Upload -->
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">Upload Data File</label>
|
<label class="form-label">Upload Data File</label>
|
||||||
<div class="file-upload-area" onclick="document.getElementById('dataFile').click()">
|
<div class="file-upload-area" onclick="document.getElementById('dataFile').click()"
|
||||||
<div class="upload-icon">📄</div>
|
role="button" tabindex="0" aria-label="Click to upload data file or drag and drop"
|
||||||
|
onkeydown="if(event.key==='Enter'||event.key===' '){document.getElementById('dataFile').click()}">
|
||||||
|
<div class="upload-icon" aria-hidden="true">📄</div>
|
||||||
<div class="upload-text">Click to upload or drag and drop</div>
|
<div class="upload-text">Click to upload or drag and drop</div>
|
||||||
<div class="upload-hint">Supports PDF, CSV, Excel files (max 10MB)</div>
|
<div class="upload-hint" id="uploadHint">Supports PDF, CSV, Excel files (max 10MB)</div>
|
||||||
<input type="file" id="dataFile" name="file" accept=".pdf,.csv,.xlsx,.xls"
|
<input type="file" id="dataFile" name="file" accept=".pdf,.csv,.xlsx,.xls"
|
||||||
style="display: none;" required>
|
style="display: none;" required aria-describedby="uploadHint">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -1345,7 +1329,7 @@ body {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- How It Works Widget - Positioned on the right -->
|
<!-- How It Works Widget - Positioned on the right -->
|
||||||
<div class="agent-widget widget-small" style="min-width: 280px; max-width: 280px; margin-left: auto;">
|
<div class="agent-widget widget-small" style="min-width: min(280px, 100%); max-width: min(280px, 100%); margin-left: auto;">
|
||||||
<div class="widget-header">
|
<div class="widget-header">
|
||||||
<h3 class="widget-title">
|
<h3 class="widget-title">
|
||||||
<span class="widget-icon">ℹ️</span>
|
<span class="widget-icon">ℹ️</span>
|
||||||
@ -1359,10 +1343,23 @@ body {
|
|||||||
<li>Get AI-powered insights</li>
|
<li>Get AI-powered insights</li>
|
||||||
<li>Copy or download results</li>
|
<li>Copy or download results</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
|
<!-- Other Agents Button -->
|
||||||
|
<button class="quick-agent-toggle btn btn-secondary btn-full" onclick="toggleQuickAgents()"
|
||||||
|
title="Quick access to other agents"
|
||||||
|
aria-label="Open quick access panel for other AI agents"
|
||||||
|
aria-expanded="false"
|
||||||
|
aria-controls="quickAgentsPanel"
|
||||||
|
style="margin-top: var(--spacing-md);">
|
||||||
|
<span class="toggle-icon" aria-hidden="true">🚀</span>
|
||||||
|
<span class="toggle-text">Explore Other Agents</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Main Content Grid -->
|
||||||
|
<div class="agent-grid">
|
||||||
<!-- Processing Status -->
|
<!-- Processing Status -->
|
||||||
<div id="processingStatus" class="agent-widget widget-wide processing-status">
|
<div id="processingStatus" class="agent-widget widget-wide processing-status">
|
||||||
<div class="widget-header">
|
<div class="widget-header">
|
||||||
@ -1406,6 +1403,59 @@ body {
|
|||||||
// Simple JavaScript - beginner friendly
|
// Simple JavaScript - beginner friendly
|
||||||
let currentResults = '';
|
let currentResults = '';
|
||||||
|
|
||||||
|
// HTML sanitization function
|
||||||
|
function sanitizeHTML(str) {
|
||||||
|
const temp = document.createElement('div');
|
||||||
|
temp.textContent = str;
|
||||||
|
return temp.innerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debounce function for performance optimization
|
||||||
|
function debounce(func, wait) {
|
||||||
|
let timeout;
|
||||||
|
return function executedFunction(...args) {
|
||||||
|
const later = () => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
func(...args);
|
||||||
|
};
|
||||||
|
clearTimeout(timeout);
|
||||||
|
timeout = setTimeout(later, wait);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Safe HTML insertion with allowed tags
|
||||||
|
function safeSetHTML(element, htmlString) {
|
||||||
|
// Allow specific HTML tags that are safe for results display
|
||||||
|
const allowedTags = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'br', 'strong', 'em', 'ul', 'ol', 'li', 'div', 'span', 'code'];
|
||||||
|
const allowedClasses = ['summary', 'key-points', 'insights'];
|
||||||
|
|
||||||
|
// Create a temporary container
|
||||||
|
const temp = document.createElement('div');
|
||||||
|
temp.innerHTML = htmlString;
|
||||||
|
|
||||||
|
// Remove any script tags and event handlers
|
||||||
|
const scripts = temp.querySelectorAll('script');
|
||||||
|
scripts.forEach(script => script.remove());
|
||||||
|
|
||||||
|
// Remove dangerous attributes
|
||||||
|
const allElements = temp.querySelectorAll('*');
|
||||||
|
allElements.forEach(el => {
|
||||||
|
// Remove event handlers
|
||||||
|
for (let attr of el.attributes) {
|
||||||
|
if (attr.name.startsWith('on')) {
|
||||||
|
el.removeAttribute(attr.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only allow specific classes
|
||||||
|
if (el.className && !allowedClasses.some(cls => el.className.includes(cls))) {
|
||||||
|
el.removeAttribute('class');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
element.innerHTML = temp.innerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
// Form submission
|
// Form submission
|
||||||
document.getElementById('agentForm').addEventListener('submit', function(e) {
|
document.getElementById('agentForm').addEventListener('submit', function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -1418,17 +1468,18 @@ document.getElementById('agentForm').addEventListener('submit', function(e) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check authentication
|
// Check authentication
|
||||||
{% if not user.is_authenticated %}
|
const isAuthenticated = document.body.getAttribute('data-user-authenticated') === 'true';
|
||||||
window.location.href = "{% url 'authentication:login' %}";
|
if (!isAuthenticated) {
|
||||||
|
window.location.href = document.body.getAttribute('data-login-url');
|
||||||
return;
|
return;
|
||||||
{% endif %}
|
}
|
||||||
|
|
||||||
// Check wallet balance
|
// Check wallet balance
|
||||||
const balance = {{ user.wallet_balance|default:0 }};
|
const balance = parseFloat(document.body.getAttribute('data-wallet-balance') || 0);
|
||||||
if (balance < 5.00) {
|
if (balance < 5.00) {
|
||||||
showMessage('Insufficient balance! You need 5.00 AED.', 'error');
|
showMessage('Insufficient balance! You need 5.00 AED.', 'error');
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
window.location.href = "{% url 'core:wallet' %}";
|
window.location.href = document.body.getAttribute('data-wallet-url');
|
||||||
}, 1500);
|
}, 1500);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1524,7 +1575,7 @@ function showResults(result) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
currentResults = rawText; // For copy/download functionality
|
currentResults = rawText; // For copy/download functionality
|
||||||
document.getElementById('resultsContent').innerHTML = content;
|
safeSetHTML(document.getElementById('resultsContent'), content);
|
||||||
document.getElementById('resultsContainer').style.display = 'block';
|
document.getElementById('resultsContainer').style.display = 'block';
|
||||||
|
|
||||||
// Show success message
|
// Show success message
|
||||||
@ -1780,8 +1831,8 @@ function selectRadio(value) {
|
|||||||
document.getElementById(value).checked = true;
|
document.getElementById(value).checked = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// File Upload Interactions
|
// File Upload Interactions with debouncing
|
||||||
document.getElementById('dataFile').addEventListener('change', function(e) {
|
const handleFileChange = debounce(function(e) {
|
||||||
const uploadArea = document.querySelector('.file-upload-area');
|
const uploadArea = document.querySelector('.file-upload-area');
|
||||||
const uploadText = document.querySelector('.upload-text');
|
const uploadText = document.querySelector('.upload-text');
|
||||||
const uploadHint = document.querySelector('.upload-hint');
|
const uploadHint = document.querySelector('.upload-hint');
|
||||||
@ -1796,22 +1847,24 @@ document.getElementById('dataFile').addEventListener('change', function(e) {
|
|||||||
uploadHint.textContent = 'Supports PDF, CSV, Excel files (max 10MB)';
|
uploadHint.textContent = 'Supports PDF, CSV, Excel files (max 10MB)';
|
||||||
uploadArea.classList.remove('active');
|
uploadArea.classList.remove('active');
|
||||||
}
|
}
|
||||||
});
|
}, 150);
|
||||||
|
|
||||||
// Drag and Drop
|
document.getElementById('dataFile').addEventListener('change', handleFileChange);
|
||||||
|
|
||||||
|
// Drag and Drop with debouncing
|
||||||
const uploadArea = document.querySelector('.file-upload-area');
|
const uploadArea = document.querySelector('.file-upload-area');
|
||||||
|
|
||||||
uploadArea.addEventListener('dragover', function(e) {
|
const handleDragOver = debounce(function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
uploadArea.classList.add('active');
|
uploadArea.classList.add('active');
|
||||||
});
|
}, 50);
|
||||||
|
|
||||||
uploadArea.addEventListener('dragleave', function(e) {
|
const handleDragLeave = debounce(function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
uploadArea.classList.remove('active');
|
uploadArea.classList.remove('active');
|
||||||
});
|
}, 100);
|
||||||
|
|
||||||
uploadArea.addEventListener('drop', function(e) {
|
const handleDrop = function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
uploadArea.classList.remove('active');
|
uploadArea.classList.remove('active');
|
||||||
|
|
||||||
@ -1822,12 +1875,13 @@ uploadArea.addEventListener('drop', function(e) {
|
|||||||
const event = new Event('change', { bubbles: true });
|
const event = new Event('change', { bubbles: true });
|
||||||
document.getElementById('dataFile').dispatchEvent(event);
|
document.getElementById('dataFile').dispatchEvent(event);
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
// Initialize radio selection
|
uploadArea.addEventListener('dragover', handleDragOver);
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
uploadArea.addEventListener('dragleave', handleDragLeave);
|
||||||
selectRadio('summary'); // Set default selection
|
uploadArea.addEventListener('drop', handleDrop);
|
||||||
});
|
|
||||||
|
// Radio selection initialization moved to consolidated DOMContentLoaded
|
||||||
|
|
||||||
function showMessage(message, type) {
|
function showMessage(message, type) {
|
||||||
// Create modern toast notification
|
// Create modern toast notification
|
||||||
@ -1872,13 +1926,7 @@ function downloadResults() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Initialize agent
|
// Agent initialization moved to consolidated DOMContentLoaded
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
selectRadio('summary'); // Set default selection
|
|
||||||
|
|
||||||
// Initialize agent widgets
|
|
||||||
initializeAgent();
|
|
||||||
});
|
|
||||||
|
|
||||||
function initializeAgent() {
|
function initializeAgent() {
|
||||||
// Any agent-specific initialization can go here
|
// Any agent-specific initialization can go here
|
||||||
@ -1898,12 +1946,20 @@ function toggleQuickAgents() {
|
|||||||
panel.classList.remove('active');
|
panel.classList.remove('active');
|
||||||
overlay.classList.remove('active');
|
overlay.classList.remove('active');
|
||||||
toggle.classList.remove('active');
|
toggle.classList.remove('active');
|
||||||
|
// Update ARIA attributes
|
||||||
|
toggle.setAttribute('aria-expanded', 'false');
|
||||||
|
panel.setAttribute('aria-hidden', 'true');
|
||||||
|
overlay.setAttribute('aria-hidden', 'true');
|
||||||
document.body.style.overflow = 'auto';
|
document.body.style.overflow = 'auto';
|
||||||
} else {
|
} else {
|
||||||
// Open panel
|
// Open panel
|
||||||
panel.classList.add('active');
|
panel.classList.add('active');
|
||||||
overlay.classList.add('active');
|
overlay.classList.add('active');
|
||||||
toggle.classList.add('active');
|
toggle.classList.add('active');
|
||||||
|
// Update ARIA attributes
|
||||||
|
toggle.setAttribute('aria-expanded', 'true');
|
||||||
|
panel.setAttribute('aria-hidden', 'false');
|
||||||
|
overlay.setAttribute('aria-hidden', 'false');
|
||||||
document.body.style.overflow = 'hidden';
|
document.body.style.overflow = 'hidden';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1916,6 +1972,10 @@ function closeQuickAgents() {
|
|||||||
panel.classList.remove('active');
|
panel.classList.remove('active');
|
||||||
overlay.classList.remove('active');
|
overlay.classList.remove('active');
|
||||||
toggle.classList.remove('active');
|
toggle.classList.remove('active');
|
||||||
|
// Update ARIA attributes
|
||||||
|
toggle.setAttribute('aria-expanded', 'false');
|
||||||
|
panel.setAttribute('aria-hidden', 'true');
|
||||||
|
overlay.setAttribute('aria-hidden', 'true');
|
||||||
document.body.style.overflow = 'auto';
|
document.body.style.overflow = 'auto';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user