quantum-ai-v2/data_analyzer/templates/data_analyzer/detail.html
2025-07-18 10:15:45 +05:30

1989 lines
55 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% extends 'base.html' %}
{% load static %}
{% block title %}Data Analyzer - NetCop AI Hub{% endblock %}
{% block extra_css %}
<style>
/* Data Analyzer Agent - Optimized Styles */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
* {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
box-sizing: border-box;
}
:root {
--primary: #000000;
--surface: #ffffff;
--surface-variant: #f8fafc;
--background: #f3f4f6;
--outline: #e4e7eb;
--outline-variant: #e1e4e7;
--on-surface: #1a1a1a;
--on-surface-variant: #6b7280;
--success: #10b981;
--error: #ef4444;
--radius-sm: 8px;
--radius-md: 12px;
--radius-lg: 16px;
--spacing-xs: 4px;
--spacing-sm: 8px;
--spacing-md: 16px;
--spacing-lg: 24px;
--spacing-xl: 32px;
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.1);
--shadow-md: 0 4px 8px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 10px 20px rgba(0, 0, 0, 0.15);
}
body {
background: var(--background);
color: var(--on-surface);
line-height: 1.5;
font-weight: 400;
}
/* Layout */
.agent-container {
margin: 0 auto;
padding: var(--spacing-lg);
max-width: 1600px;
}
.agent-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--spacing-xl);
}
.header-controls {
display: flex;
align-items: center;
gap: var(--spacing-lg);
}
.agent-grid {
display: flex;
gap: var(--spacing-lg);
align-items: flex-start;
flex-wrap: wrap;
}
/* Typography */
.agent-title {
font-size: 32px;
font-weight: 700;
color: var(--on-surface);
margin: 0;
letter-spacing: -0.5px;
}
.agent-subtitle {
font-size: 16px;
color: var(--on-surface-variant);
margin: 4px 0 0 0;
}
/* Widgets */
.agent-widget {
background: var(--surface);
border-radius: var(--radius-lg);
padding: var(--spacing-xl);
border: 1px solid var(--outline-variant);
box-shadow: var(--shadow-sm);
transition: all 0.2s ease;
display: flex;
flex-direction: column;
}
.dashboard-widget:hover {
box-shadow: var(--shadow-md);
border-color: var(--outline);
transform: translateY(-2px);
}
.widget-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--spacing-lg);
padding-bottom: var(--spacing-md);
border-bottom: 1px solid var(--outline-variant);
}
.widget-title {
font-size: 18px;
font-weight: 600;
color: var(--on-surface);
margin: 0;
display: flex;
align-items: center;
gap: var(--spacing-sm);
}
.widget-icon {
font-size: 20px;
padding: 6px;
border-radius: var(--radius-sm);
background: var(--surface-variant);
}
.widget-content {
flex: 1;
display: flex;
flex-direction: column;
gap: var(--spacing-md);
}
/* Widget Sizes */
.widget-large {
flex: 1;
min-width: 400px;
}
.widget-small {
flex: 0 0 280px;
}
.widget-wide {
flex: 1 1 100%;
width: 100%;
}
/* Forms */
.form-group {
margin-bottom: var(--spacing-lg);
}
.form-label {
display: block;
font-size: 14px;
font-weight: 500;
color: var(--on-surface);
margin-bottom: var(--spacing-sm);
}
.file-upload-area {
border: 2px dashed var(--outline);
border-radius: var(--radius-md);
padding: var(--spacing-xl);
text-align: center;
background: #fafbfc;
transition: all 0.2s ease;
cursor: pointer;
}
.file-upload-area:hover,
.file-upload-area.active {
border-color: var(--primary);
background: var(--surface);
box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.05);
}
.upload-icon {
font-size: 32px;
margin-bottom: var(--spacing-sm);
color: var(--on-surface-variant);
}
.upload-text {
font-size: 16px;
font-weight: 500;
color: var(--on-surface);
margin-bottom: var(--spacing-xs);
}
.upload-hint {
font-size: 14px;
color: var(--on-surface-variant);
}
/* Radio Buttons */
.radio-grid {
display: flex;
gap: var(--spacing-md);
}
.radio-card {
position: relative;
padding: var(--spacing-md);
border: 1px solid var(--outline-variant);
border-radius: var(--radius-md);
cursor: pointer;
transition: all 0.2s ease;
background: var(--surface);
display: flex;
align-items: center;
gap: var(--spacing-md);
}
.radio-card:hover {
border-color: var(--primary);
box-shadow: var(--shadow-sm);
}
.radio-card.selected {
border-color: var(--primary);
}
.radio-card input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.radio-button {
width: 20px;
height: 20px;
border: 2px solid var(--outline);
border-radius: 50%;
position: relative;
transition: all 0.2s ease;
flex-shrink: 0;
}
.radio-card:hover .radio-button,
.radio-card.selected .radio-button {
border-color: var(--primary);
}
.radio-card.selected .radio-button::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 10px;
height: 10px;
background: var(--primary);
border-radius: 50%;
transform: translate(-50%, -50%);
}
.radio-label {
font-size: 14px;
font-weight: 500;
color: var(--on-surface);
display: flex;
align-items: center;
gap: var(--spacing-sm);
pointer-events: none;
}
/* Buttons */
.btn {
padding: 12px 24px;
border-radius: var(--radius-md);
font-size: 14px;
font-weight: 600;
border: none;
cursor: pointer;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--spacing-sm);
text-decoration: none;
min-height: 44px;
}
.btn-primary {
background: var(--primary);
color: white;
}
.btn-primary:hover:not(:disabled) {
background: #333333;
transform: translateY(-1px);
box-shadow: var(--shadow-md);
}
.btn-primary:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none;
}
.btn-secondary {
background: var(--surface);
color: var(--on-surface);
border: 2px solid var(--outline);
}
.btn-secondary:hover {
background: var(--surface-variant);
border-color: var(--outline);
}
.btn-full {
width: 100%;
}
/* Wallet Card - Compact Design */
.wallet-card {
background: linear-gradient(135deg, #000000 0%, #333333 100%);
color: white;
border-radius: var(--radius-md);
padding: var(--spacing-md);
border: none;
box-shadow: var(--shadow-md);
position: relative;
overflow: hidden;
margin-bottom: 0;
min-height: auto;
}
.wallet-card::before {
content: '';
position: absolute;
top: 0;
right: 0;
width: 100px;
height: 100px;
background: rgba(255, 255, 255, 0.1);
border-radius: 50%;
transform: translate(30px, -30px);
}
.wallet-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--spacing-sm);
}
.wallet-title {
font-size: 14px;
font-weight: 600;
margin: 0;
opacity: 0.9;
}
.wallet-icon {
font-size: 20px;
}
.balance-display {
margin-bottom: 0;
}
.balance-amount {
font-size: 24px;
font-weight: 700;
line-height: 1;
margin-bottom: 0;
letter-spacing: -0.5px;
}
.balance-label {
font-size: 12px;
opacity: 0.8;
font-weight: 400;
}
/* Info Card */
.info-card {
background: var(--surface);
border-radius: var(--radius-lg);
padding: var(--spacing-lg);
border: 1px solid var(--outline-variant);
}
/* Sidebar Navigation */
.nav-section {
margin-bottom: var(--spacing-xl);
}
.nav-section-title {
font-size: 12px;
font-weight: 600;
color: var(--on-surface-variant);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: var(--spacing-md);
padding: 0 var(--spacing-sm);
}
.nav-list {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: var(--spacing-xs);
}
.nav-item {
position: relative;
}
.nav-link {
display: flex;
align-items: center;
gap: var(--spacing-md);
padding: var(--spacing-md);
border-radius: var(--radius-sm);
text-decoration: none;
color: var(--on-surface);
transition: all 0.2s ease;
position: relative;
}
.nav-link:hover {
background: var(--surface-variant);
color: var(--primary);
transform: translateX(2px);
}
.nav-link.active {
background: var(--primary);
color: white;
box-shadow: var(--shadow-sm);
}
.nav-link.active::before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 3px;
background: white;
border-radius: 0 2px 2px 0;
}
.nav-icon {
font-size: 18px;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.nav-text {
flex: 1;
font-size: 14px;
font-weight: 500;
}
.nav-price {
font-size: 11px;
font-weight: 600;
color: var(--primary);
background: rgba(0, 0, 0, 0.1);
padding: 2px 6px;
border-radius: 10px;
flex-shrink: 0;
}
.nav-link.active .nav-price {
color: white;
background: rgba(255, 255, 255, 0.2);
}
/* Quick Actions in Sidebar */
.sidebar-actions {
margin-top: auto;
padding-top: var(--spacing-lg);
border-top: 1px solid var(--outline-variant);
}
.sidebar-btn {
display: flex;
align-items: center;
gap: var(--spacing-sm);
width: 100%;
padding: var(--spacing-md);
background: var(--surface-variant);
border: 1px solid var(--outline-variant);
border-radius: var(--radius-sm);
text-decoration: none;
color: var(--on-surface);
font-size: 14px;
font-weight: 500;
transition: all 0.2s ease;
margin-bottom: var(--spacing-sm);
}
.sidebar-btn:hover {
background: var(--primary);
color: white;
border-color: var(--primary);
transform: translateY(-1px);
}
.info-title {
font-size: 16px;
font-weight: 600;
color: var(--on-surface);
margin: 0 0 var(--spacing-md) 0;
}
.info-list {
list-style: none;
padding: 0;
margin: 0;
}
.info-list li {
padding: var(--spacing-sm) 0;
color: var(--on-surface-variant);
font-size: 14px;
border-bottom: 1px solid var(--outline-variant);
position: relative;
padding-left: var(--spacing-lg);
}
.info-list li:last-child {
border-bottom: none;
}
.info-list li::before {
content: counter(step-counter);
counter-increment: step-counter;
position: absolute;
left: 0;
top: var(--spacing-sm);
background: var(--primary);
color: white;
width: 20px;
height: 20px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: 600;
}
.info-list {
counter-reset: step-counter;
}
/* Processing Status */
.processing-status {
background: var(--surface);
border: 1px solid var(--outline-variant);
border-radius: var(--radius-lg);
padding: var(--spacing-xl);
text-align: center;
display: none;
}
.status-icon {
font-size: 48px;
margin-bottom: var(--spacing-md);
animation: pulse 2s infinite;
}
.status-title {
font-size: 18px;
font-weight: 600;
color: var(--on-surface);
margin-bottom: var(--spacing-sm);
}
.status-subtitle {
font-size: 14px;
color: var(--on-surface-variant);
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
/* Results Container */
.results-container {
background: var(--surface);
border: 1px solid var(--outline-variant);
border-radius: var(--radius-lg);
padding: var(--spacing-xl);
display: none;
}
.results-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: var(--spacing-lg);
padding-bottom: var(--spacing-md);
border-bottom: 1px solid var(--outline-variant);
}
.results-title {
font-size: 20px;
font-weight: 600;
color: var(--on-surface);
margin: 0;
}
.status-badge {
background: var(--success);
color: white;
padding: 4px 12px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.results-content {
background: var(--surface-variant);
border-radius: var(--radius-md);
padding: var(--spacing-xl);
margin-bottom: var(--spacing-lg);
line-height: 1.7;
color: var(--on-surface);
font-size: 15px;
}
/* Enhanced Results Typography */
.results-content h1,
.results-content h2,
.results-content h3 {
color: var(--primary);
font-weight: 700;
margin: var(--spacing-xl) 0 var(--spacing-md) 0;
line-height: 1.3;
}
.results-content h1 {
font-size: 24px;
border-bottom: 3px solid var(--primary);
padding-bottom: var(--spacing-sm);
margin-bottom: var(--spacing-lg);
}
.results-content h2 {
font-size: 20px;
margin-top: var(--spacing-xl);
position: relative;
padding-left: var(--spacing-md);
}
.results-content h2::before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 4px;
background: var(--primary);
border-radius: 2px;
}
.results-content h3 {
font-size: 18px;
color: var(--on-surface);
font-weight: 600;
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
padding: var(--spacing-md) var(--spacing-lg);
border-radius: var(--radius-sm);
border-left: 4px solid var(--primary);
margin: var(--spacing-lg) 0 var(--spacing-md) 0;
}
.results-content p {
margin: var(--spacing-md) 0;
text-align: justify;
}
.results-content ul,
.results-content ol {
margin: var(--spacing-md) 0;
padding-left: var(--spacing-xl);
}
.results-content li {
margin: var(--spacing-sm) 0;
position: relative;
}
.results-content ul li::marker {
color: var(--primary);
font-weight: bold;
}
.results-content ol li::marker {
color: var(--primary);
font-weight: bold;
}
/* Modern Info Boxes */
.results-content .key-points,
.results-content .insights,
.results-content .summary {
background: var(--surface);
border: 1px solid var(--outline-variant);
border-radius: var(--radius-md);
padding: var(--spacing-lg);
margin: var(--spacing-lg) 0;
box-shadow: var(--shadow-sm);
}
.results-content .key-points {
border-left: 4px solid #3b82f6;
}
.results-content .insights {
border-left: 4px solid #10b981;
}
.results-content .summary {
border-left: 4px solid #f59e0b;
}
/* Strong text styling */
.results-content strong {
color: var(--primary);
font-weight: 600;
}
/* Code and technical terms */
.results-content code {
background: #f1f5f9;
padding: 2px 6px;
border-radius: 4px;
font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
font-size: 14px;
color: #1e293b;
}
.results-actions {
display: flex;
gap: var(--spacing-md);
flex-wrap: wrap;
}
/* Alerts */
.alert {
padding: var(--spacing-md);
border-radius: var(--radius-md);
margin-bottom: var(--spacing-md);
font-size: 14px;
font-weight: 500;
}
.alert-error {
background: #fef2f2;
color: #dc2626;
border: 1px solid #fecaca;
}
/* Quick Agent Access Toggle & Panel */
.quick-agent-toggle {
display: flex;
align-items: center;
gap: var(--spacing-sm);
padding: var(--spacing-md) var(--spacing-lg);
background: var(--surface);
border: 1px solid var(--outline-variant);
border-radius: var(--radius-md);
color: var(--on-surface);
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
position: relative;
}
.quick-agent-toggle:hover {
background: var(--surface-variant);
border-color: var(--primary);
transform: translateY(-1px);
box-shadow: var(--shadow-sm);
}
.quick-agent-toggle.active {
background: var(--primary);
color: white;
border-color: var(--primary);
}
.toggle-icon {
font-size: 16px;
transition: transform 0.2s ease;
}
.quick-agent-toggle.active .toggle-icon {
transform: rotate(180deg);
}
.quick-agents-panel {
position: fixed;
top: 0;
right: 0;
width: min(400px, 90vw);
height: 100vh;
background: var(--surface);
border-left: 1px solid var(--outline);
box-shadow: var(--shadow-lg);
z-index: 1000;
transform: translateX(100%);
transition: transform 0.3s ease;
overflow-y: auto;
}
.quick-agents-panel.active {
transform: translateX(0);
}
.quick-agents-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--spacing-lg);
border-bottom: 1px solid var(--outline-variant);
background: var(--surface-variant);
}
.quick-agents-header h3 {
margin: 0;
font-size: 18px;
font-weight: 600;
color: var(--on-surface);
}
.close-panel {
background: none;
border: none;
font-size: 24px;
color: var(--on-surface-variant);
cursor: pointer;
padding: 4px;
border-radius: var(--radius-sm);
transition: all 0.2s ease;
}
.close-panel:hover {
background: var(--surface);
color: var(--on-surface);
}
.quick-agents-grid {
padding: var(--spacing-lg);
display: flex;
flex-direction: column;
gap: var(--spacing-md);
}
.quick-agent-card {
display: flex;
align-items: center;
gap: var(--spacing-md);
padding: var(--spacing-md);
border: 1px solid var(--outline-variant);
border-radius: var(--radius-md);
text-decoration: none;
color: var(--on-surface);
transition: all 0.2s ease;
background: var(--surface);
}
.quick-agent-card:hover {
background: var(--surface-variant);
border-color: var(--primary);
transform: translateY(-1px);
box-shadow: var(--shadow-sm);
}
.agent-icon {
font-size: 24px;
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
background: var(--surface-variant);
border-radius: var(--radius-md);
flex-shrink: 0;
}
.agent-info {
flex: 1;
}
.agent-info h4 {
margin: 0 0 4px 0;
font-size: 14px;
font-weight: 600;
color: var(--on-surface);
}
.agent-info p {
margin: 0;
font-size: 12px;
color: var(--on-surface-variant);
line-height: 1.4;
}
.agent-price {
font-size: 12px;
font-weight: 600;
color: var(--primary);
background: rgba(0, 0, 0, 0.05);
padding: 2px 8px;
border-radius: var(--radius-sm);
margin-top: 4px;
display: inline-block;
}
.quick-agents-footer {
padding: var(--spacing-md) var(--spacing-lg);
border-top: 1px solid var(--outline-variant);
background: var(--surface-variant);
}
.view-all-agents {
display: block;
text-align: center;
padding: var(--spacing-md);
background: var(--primary);
color: white;
text-decoration: none;
border-radius: var(--radius-md);
font-size: 14px;
font-weight: 500;
transition: all 0.2s ease;
}
.view-all-agents:hover {
background: #333333;
transform: translateY(-1px);
box-shadow: var(--shadow-sm);
}
/* Overlay for mobile */
.quick-agents-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 999;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
}
.quick-agents-overlay.active {
opacity: 1;
visibility: visible;
}
/* 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;
}
/* Responsive Dashboard Design */
@media (max-width: 1200px) {
.agent-index-sidebar {
width: 240px;
}
.agent-container.with-sidebar {
margin-left: 240px;
max-width: 1000px; /* Responsive max-width */
margin-right: auto;
}
.collapsed ~ .agent-container.with-sidebar {
margin-left: 240px; /* Keep same margin */
max-width: 1000px; /* Keep same max-width */
margin-right: auto;
}
.agent-grid {
grid-template-columns: repeat(8, 1fr);
}
.widget-large {
grid-column: span 8;
}
.widget-medium {
grid-column: span 4;
}
.widget-small {
grid-column: span 4;
}
.widget-wide {
grid-column: span 8;
}
.stats-row {
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
}
}
@media (max-width: 900px) {
.agent-index-sidebar {
transform: translateX(-100%);
width: 280px;
top: 66px; /* Smaller header height on mobile */
height: calc(100vh - 66px);
}
.agent-index-sidebar.mobile-open {
transform: translateX(0);
}
.agent-container.with-sidebar {
margin-left: 0;
padding: var(--spacing-md);
}
.mobile-agent-menu-btn {
display: flex;
top: 86px; /* Position below header */
}
.agent-grid {
grid-template-columns: repeat(4, 1fr);
gap: var(--spacing-md);
}
.widget-large,
.widget-medium,
.widget-small,
.widget-wide {
grid-column: span 4;
}
.stats-row {
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: var(--spacing-sm);
}
}
@media (max-width: 768px) {
.agent-index-sidebar {
top: 66px; /* Mobile header height */
height: calc(100vh - 66px);
}
.mobile-agent-menu-btn {
top: 76px; /* Position below mobile header */
}
.agent-container.with-sidebar {
padding: var(--spacing-md);
}
.agent-header {
flex-direction: column;
align-items: flex-start;
gap: var(--spacing-sm);
margin-top: 0; /* Remove margin since we're positioned below header */
}
.header-controls {
width: 100%;
justify-content: space-between;
}
.quick-agent-toggle {
flex: 1;
margin-right: var(--spacing-md);
justify-content: center;
}
.quick-agents-panel {
width: 100%;
max-width: 100%;
right: 0;
left: 0;
}
.agent-title {
font-size: 24px;
}
.agent-grid {
grid-template-columns: 1fr;
gap: var(--spacing-md);
}
.widget-large,
.widget-medium,
.widget-small,
.widget-wide {
grid-column: span 1;
}
.stats-row {
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: var(--spacing-sm);
}
.radio-grid {
flex-direction: column;
}
.wallet-card {
padding: var(--spacing-lg);
}
.balance-amount {
font-size: 28px;
}
}
</style>
{% endblock %}
{% 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">
<!-- Agent Header -->
<div class="agent-header">
<div>
<h1 class="agent-title">Data Analyzer</h1>
<p class="agent-subtitle">Upload your data file and get AI-powered analysis with advanced insights</p>
</div>
<div class="header-controls">
<div class="wallet-card widget-small" style="margin-bottom: 0;">
<div class="wallet-header">
<h3 class="wallet-title">Your Wallet</h3>
<div class="wallet-icon">💳</div>
</div>
<div class="balance-display">
<div class="balance-amount">
<span id="walletBalance">{{ user.wallet_balance|floatformat:2 }}</span> AED
</div>
<div class="balance-label">Available Balance</div>
</div>
</div>
</div>
</div>
<!-- Quick Agent Access Panel -->
<div class="quick-agents-overlay" id="quickAgentsOverlay" onclick="closeQuickAgents()" aria-hidden="true"></div>
<div class="quick-agents-panel" id="quickAgentsPanel" role="dialog" aria-labelledby="quickAgentsTitle" aria-hidden="true">
<div class="quick-agents-header">
<h3 id="quickAgentsTitle">Quick Access to Other Agents</h3>
<button class="close-panel" onclick="toggleQuickAgents()" aria-label="Close quick agents panel">×</button>
</div>
<div class="quick-agents-grid">
<a href="/agents/weather-reporter/" class="quick-agent-card">
<div class="agent-icon">🌤️</div>
<div class="agent-info">
<h4>Weather Reporter</h4>
<p>Real-time weather info</p>
<span class="agent-price">2.0 AED</span>
</div>
</a>
<a href="/agents/job-posting-generator/" class="quick-agent-card">
<div class="agent-icon">💼</div>
<div class="agent-info">
<h4>Job Posting Generator</h4>
<p>Create professional job posts</p>
<span class="agent-price">3.0 AED</span>
</div>
</a>
<a href="/agents/social-ads-generator/" class="quick-agent-card">
<div class="agent-icon">📱</div>
<div class="agent-info">
<h4>Social Ads Generator</h4>
<p>Engaging social media ads</p>
<span class="agent-price">4.0 AED</span>
</div>
</a>
<a href="/agents/five-whys-analyzer/" class="quick-agent-card">
<div class="agent-icon">🔍</div>
<div class="agent-info">
<h4>Five Whys Analyzer</h4>
<p>Root cause analysis</p>
<span class="agent-price">8.0 AED</span>
</div>
</a>
</div>
<div class="quick-agents-footer">
<a href="{% url 'core:marketplace' %}" class="view-all-agents">View All Agents →</a>
</div>
</div>
<!-- Agent Grid -->
<div class="agent-grid">
<!-- Upload Widget -->
<div class="agent-widget widget-large" style="flex: 1; margin-right: clamp(0px, var(--spacing-lg), 2vw);">
<div class="widget-header">
<h3 class="widget-title">
<span class="widget-icon">📤</span>
Upload & Analyze
</h3>
</div>
<div class="widget-content">
<form id="agentForm" method="POST" enctype="multipart/form-data">
{% csrf_token %}
<!-- File Upload -->
<div class="form-group">
<label class="form-label">Upload Data File</label>
<div class="file-upload-area" onclick="document.getElementById('dataFile').click()"
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-hint" id="uploadHint">Supports PDF, CSV, Excel files (max 10MB)</div>
<input type="file" id="dataFile" name="file" accept=".pdf,.csv,.xlsx,.xls"
style="display: none;" required aria-describedby="uploadHint">
</div>
</div>
<!-- Analysis Type -->
<div class="form-group">
<label class="form-label">Analysis Type</label>
<div class="radio-grid">
<div class="radio-card" onclick="selectRadio('summary')">
<input type="radio" id="summary" name="analysisType" value="summary" checked>
<div class="radio-button"></div>
<label for="summary" class="radio-label">📋 Summary</label>
</div>
<div class="radio-card" onclick="selectRadio('detailed')">
<input type="radio" id="detailed" name="analysisType" value="detailed">
<div class="radio-button"></div>
<label for="detailed" class="radio-label">📈 Detailed</label>
</div>
<div class="radio-card" onclick="selectRadio('statistical')">
<input type="radio" id="statistical" name="analysisType" value="statistical">
<div class="radio-button"></div>
<label for="statistical" class="radio-label">📊 Statistical</label>
</div>
</div>
</div>
<!-- Action Button -->
<div style="margin-top: auto;">
{% if user.is_authenticated %}
{% if user.wallet_balance >= 5.00 %}
<button type="submit" class="btn btn-primary btn-full" id="analyzeBtn">
🚀 Analyze Data (5.00 AED)
</button>
{% else %}
<div class="alert alert-error">
Insufficient balance! You need 5.00 AED.
</div>
<a href="{% url 'core:wallet' %}" class="btn btn-primary btn-full">
💳 Top Up Wallet
</a>
{% endif %}
{% else %}
<a href="{% url 'authentication:login' %}" class="btn btn-primary btn-full">
🔐 Login to Continue
</a>
{% endif %}
</div>
</form>
</div>
</div>
<!-- How It Works Widget - Positioned on the right -->
<div class="agent-widget widget-small" style="min-width: min(280px, 100%); max-width: min(280px, 100%); margin-left: auto;">
<div class="widget-header">
<h3 class="widget-title">
<span class="widget-icon"></span>
How It Works
</h3>
</div>
<div class="widget-content">
<ol class="info-list">
<li>Upload your data file</li>
<li>Choose analysis type</li>
<li>Get AI-powered insights</li>
<li>Copy or download results</li>
</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>
<!-- Main Content Grid -->
<div class="agent-grid">
<!-- Processing Status -->
<div id="processingStatus" class="agent-widget widget-wide processing-status">
<div class="widget-header">
<h3 class="widget-title">
<span class="widget-icon"></span>
Processing Status
</h3>
</div>
<div class="widget-content" style="text-align: center;">
<div class="status-icon"></div>
<div class="status-title">Analyzing your data...</div>
<div class="status-subtitle" id="statusText">Processing file...</div>
</div>
</div>
<!-- Results Widget -->
<div id="resultsContainer" class="agent-widget widget-wide results-container">
<div class="widget-header">
<h3 class="widget-title">
<span class="widget-icon">📊</span>
Analysis Results
</h3>
<span class="status-badge">Success</span>
</div>
<div class="widget-content">
<div id="resultsContent" class="results-content"></div>
<div class="results-actions">
<button onclick="copyResults()" class="btn btn-primary">
📋 Copy Results
</button>
<button onclick="downloadResults()" class="btn btn-secondary">
💾 Download Report
</button>
</div>
</div>
</div>
</div>
</div>
<script>
// Simple JavaScript - beginner friendly
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
document.getElementById('agentForm').addEventListener('submit', function(e) {
e.preventDefault();
// Check if file is selected
const fileInput = document.getElementById('dataFile');
if (!fileInput.files || fileInput.files.length === 0) {
showMessage('Please select a data file', 'error');
return;
}
// Check authentication
const isAuthenticated = document.body.getAttribute('data-user-authenticated') === 'true';
if (!isAuthenticated) {
window.location.href = document.body.getAttribute('data-login-url');
return;
}
// Check wallet balance
const balance = parseFloat(document.body.getAttribute('data-wallet-balance') || 0);
if (balance < 5.00) {
showMessage('Insufficient balance! You need 5.00 AED.', 'error');
setTimeout(function() {
window.location.href = document.body.getAttribute('data-wallet-url');
}, 1500);
return;
}
// Show loading
document.getElementById('processingStatus').style.display = 'block';
document.getElementById('resultsContainer').style.display = 'none';
document.getElementById('analyzeBtn').disabled = true;
document.getElementById('analyzeBtn').textContent = '⏳ Processing...';
// Submit form
const formData = new FormData(this);
fetch(window.location.href, {
method: 'POST',
body: formData,
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(response => response.json())
.then(result => {
if (result.success && result.request_id) {
// Start checking for results
checkResults(result.request_id);
} else {
showError(result.error || 'Processing failed');
}
})
.catch(error => {
console.error('Error:', error);
showError('Network error - please try again');
});
});
// Check results (simplified polling)
function checkResults(requestId) {
fetch(`/agents/data-analyzer/status/${requestId}/`)
.then(response => response.json())
.then(result => {
console.log('Status check result:', result); // Debug log
if (result.status === 'completed') {
hideLoading();
if (result.success) {
showResults(result);
updateWalletBalance(result.wallet_balance);
} else {
// Show more detailed error message
const errorMsg = result.error_message || 'Analysis completed but failed to process results';
console.error('Analysis failed:', errorMsg);
showError(`Analysis failed: ${errorMsg}`);
}
} else if (result.status === 'failed') {
hideLoading();
const errorMsg = result.error_message || result.message || 'Processing failed';
console.error('Request failed:', errorMsg);
showError(`Request failed: ${errorMsg}`);
} else {
// Still processing, check again in 2 seconds
console.log('Still processing, status:', result.status);
setTimeout(() => checkResults(requestId), 2000);
}
})
.catch(error => {
console.error('Error checking results:', error);
hideLoading();
showError('Network error - please try again');
});
}
// Show results - handles new structured format
function showResults(result) {
let content = '';
let rawText = '';
// Check for new structured format from webhook
if (result.raw_response && result.raw_response.sections) {
content = formatStructuredSections(result.raw_response.sections);
rawText = extractRawTextFromSections(result.raw_response.sections);
}
// Legacy formats
else if (result.raw_response && result.raw_response.formatted_html) {
content = result.raw_response.formatted_html;
rawText = result.raw_response.analysis || content;
}
else {
const textContent = result.report_text || result.insights_summary ||
(result.raw_response && result.raw_response.analysis) ||
'Analysis completed successfully!';
content = formatAnalysisResults(textContent);
rawText = textContent;
}
currentResults = rawText; // For copy/download functionality
safeSetHTML(document.getElementById('resultsContent'), content);
document.getElementById('resultsContainer').style.display = 'block';
// Show success message
showMessage('✅ Analysis completed and payment processed!', 'success');
}
// Format new structured sections format
function formatStructuredSections(sections) {
let formattedContent = '';
sections.forEach(section => {
const heading = section.heading;
const content = section.content;
// Add section header
formattedContent += `<h3>${heading}</h3>`;
// Determine section type and apply appropriate styling
const lowerHeading = heading.toLowerCase();
let sectionClass = '';
if (lowerHeading.includes('summary')) {
sectionClass = 'summary';
} else if (lowerHeading.includes('key points')) {
sectionClass = 'key-points';
} else if (lowerHeading.includes('insights')) {
sectionClass = 'insights';
}
// Process content
let processedContent = content;
// Handle numbered lists (1. **Text**: Description)
if (/\d+\.\s\*\*/.test(content)) {
const lines = content.split('\n');
const listItems = [];
let beforeList = '';
lines.forEach(line => {
line = line.trim();
if (/^\d+\.\s\*\*/.test(line)) {
const cleanItem = line.replace(/^\d+\.\s/, '');
listItems.push(cleanItem);
} else if (line && listItems.length === 0) {
beforeList += line + ' ';
}
});
processedContent = '';
if (beforeList.trim()) {
processedContent += `<p>${beforeList.trim()}</p>`;
}
if (listItems.length > 0) {
processedContent += '<ol>';
listItems.forEach(item => {
processedContent += `<li>${item}</li>`;
});
processedContent += '</ol>';
}
}
// Handle bullet lists (- Text)
else if (/^-\s/.test(content.trim()) || content.includes('\n- ')) {
const lines = content.split('\n');
const listItems = [];
let beforeList = '';
lines.forEach(line => {
line = line.trim();
if (line.startsWith('- ')) {
const cleanItem = line.replace(/^- /, '');
listItems.push(cleanItem);
} else if (line && listItems.length === 0) {
beforeList += line + ' ';
}
});
processedContent = '';
if (beforeList.trim()) {
processedContent += `<p>${beforeList.trim()}</p>`;
}
if (listItems.length > 0) {
processedContent += '<ul>';
listItems.forEach(item => {
processedContent += `<li>${item}</li>`;
});
processedContent += '</ul>';
}
}
// Regular paragraph content
else {
processedContent = `<p>${content.replace(/\n/g, '<br>')}</p>`;
}
// Apply section styling
if (sectionClass) {
formattedContent += `<div class="${sectionClass}">${processedContent}</div>`;
} else {
formattedContent += processedContent;
}
});
// Enhance strong text and italic
formattedContent = formattedContent.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
formattedContent = formattedContent.replace(/\*(.+?)\*/g, '<em>$1</em>');
return formattedContent;
}
// Extract raw text from structured sections for copy/download
function extractRawTextFromSections(sections) {
let rawText = '';
sections.forEach(section => {
rawText += `### ${section.heading}\n`;
rawText += `${section.content}\n\n`;
});
return rawText.trim();
}
// Enhanced formatting function for analysis results
function formatAnalysisResults(text) {
// Convert markdown-style headers to HTML
text = text.replace(/### (.+)/g, '<h3>$1</h3>');
text = text.replace(/## (.+)/g, '<h2>$1</h2>');
text = text.replace(/# (.+)/g, '<h1>$1</h1>');
// First, handle numbered lists that are embedded in paragraphs
text = text.replace(/(\d+\.\s[^\.]+\.)\s*/g, '$1\n');
// Convert line breaks to proper paragraphs
let paragraphs = text.split('\n\n');
let formattedContent = '';
paragraphs.forEach(paragraph => {
paragraph = paragraph.trim();
if (paragraph) {
// Check if it's a header
if (paragraph.includes('<h1>') || paragraph.includes('<h2>') || paragraph.includes('<h3>')) {
formattedContent += paragraph;
}
// Check if paragraph contains numbered list items (even if not on separate lines)
else if (/\d+\.\s/.test(paragraph)) {
// Split the paragraph by numbered items
let parts = paragraph.split(/(?=\d+\.\s)/);
let beforeList = parts[0].trim();
// Handle any content before the list
if (beforeList && !beforeList.match(/^\d+\./)) {
if (beforeList.toLowerCase().includes('key points')) {
formattedContent += `<div class="key-points"><p>${beforeList}</p>`;
} else if (beforeList.toLowerCase().includes('summary')) {
formattedContent += `<div class="summary"><p>${beforeList}</p>`;
} else if (beforeList.toLowerCase().includes('insights')) {
formattedContent += `<div class="insights"><p>${beforeList}</p>`;
} else {
formattedContent += `<p>${beforeList}</p>`;
}
}
// Process the numbered items
let hasNumberedItems = false;
let listItems = [];
for (let i = 0; i < parts.length; i++) {
let part = parts[i].trim();
if (part && part.match(/^\d+\./)) {
hasNumberedItems = true;
let cleanItem = part.replace(/^\d+\.\s*/, '').trim();
listItems.push(cleanItem);
}
}
if (hasNumberedItems && listItems.length > 0) {
formattedContent += '<ol>';
listItems.forEach(item => {
formattedContent += `<li>${item}</li>`;
});
formattedContent += '</ol>';
}
// Close the special div if we opened one
if (beforeList && (beforeList.toLowerCase().includes('key points') ||
beforeList.toLowerCase().includes('summary') ||
beforeList.toLowerCase().includes('insights'))) {
formattedContent += '</div>';
}
}
// Check for bullet lists
else if (paragraph.includes('- ')) {
let listItems = paragraph.split('\n').filter(item => item.trim().startsWith('-'));
if (listItems.length > 0) {
formattedContent += '<ul>';
listItems.forEach(item => {
let cleanItem = item.replace(/^- /, '').trim();
formattedContent += `<li>${cleanItem}</li>`;
});
formattedContent += '</ul>';
}
}
// Regular paragraph
else {
// Convert single line breaks to <br>
paragraph = paragraph.replace(/\n/g, '<br>');
// Add special styling for known sections
if (paragraph.toLowerCase().includes('summary')) {
formattedContent += `<div class="summary">${paragraph}</div>`;
} else if (paragraph.toLowerCase().includes('key points')) {
formattedContent += `<div class="key-points">${paragraph}</div>`;
} else if (paragraph.toLowerCase().includes('insights')) {
formattedContent += `<div class="insights">${paragraph}</div>`;
} else {
formattedContent += `<p>${paragraph}</p>`;
}
}
}
});
// Enhance strong text
formattedContent = formattedContent.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
formattedContent = formattedContent.replace(/\*(.+?)\*/g, '<em>$1</em>');
return formattedContent;
}
// Helper functions
function hideLoading() {
document.getElementById('processingStatus').style.display = 'none';
document.getElementById('analyzeBtn').disabled = false;
document.getElementById('analyzeBtn').textContent = '📊 Analyze Data (5.00 AED)';
}
function showError(message) {
hideLoading();
showMessage('❌ ' + message, 'error');
}
// Modern Radio Selection
function selectRadio(value) {
// Remove selected class from all cards
document.querySelectorAll('.radio-card').forEach(card => {
card.classList.remove('selected');
});
// Add selected class to clicked card
const selectedCard = document.querySelector(`input[value="${value}"]`).closest('.radio-card');
selectedCard.classList.add('selected');
// Select the radio button
document.getElementById(value).checked = true;
}
// File Upload Interactions with debouncing
const handleFileChange = debounce(function(e) {
const uploadArea = document.querySelector('.file-upload-area');
const uploadText = document.querySelector('.upload-text');
const uploadHint = document.querySelector('.upload-hint');
if (e.target.files.length > 0) {
const fileName = e.target.files[0].name;
uploadText.textContent = fileName;
uploadHint.textContent = 'File selected successfully';
uploadArea.classList.add('active');
} else {
uploadText.textContent = 'Click to upload or drag and drop';
uploadHint.textContent = 'Supports PDF, CSV, Excel files (max 10MB)';
uploadArea.classList.remove('active');
}
}, 150);
document.getElementById('dataFile').addEventListener('change', handleFileChange);
// Drag and Drop with debouncing
const uploadArea = document.querySelector('.file-upload-area');
const handleDragOver = debounce(function(e) {
e.preventDefault();
uploadArea.classList.add('active');
}, 50);
const handleDragLeave = debounce(function(e) {
e.preventDefault();
uploadArea.classList.remove('active');
}, 100);
const handleDrop = function(e) {
e.preventDefault();
uploadArea.classList.remove('active');
const files = e.dataTransfer.files;
if (files.length > 0) {
document.getElementById('dataFile').files = files;
// Trigger change event
const event = new Event('change', { bubbles: true });
document.getElementById('dataFile').dispatchEvent(event);
}
};
uploadArea.addEventListener('dragover', handleDragOver);
uploadArea.addEventListener('dragleave', handleDragLeave);
uploadArea.addEventListener('drop', handleDrop);
// Radio selection initialization moved to consolidated DOMContentLoaded
function showMessage(message, type) {
// Create modern toast notification
const toast = document.createElement('div');
toast.className = `toast ${type}`;
toast.textContent = message;
document.body.appendChild(toast);
// Remove toast after 4 seconds
setTimeout(() => {
if (toast.parentNode) {
toast.remove();
}
}, 4000);
}
function updateWalletBalance(newBalance) {
if (newBalance !== undefined) {
document.getElementById('walletBalance').textContent = newBalance.toFixed(2);
}
}
function copyResults() {
if (currentResults) {
navigator.clipboard.writeText(currentResults.replace(/<br>/g, '\n'))
.then(() => showMessage('📋 Results copied to clipboard!', 'success'))
.catch(() => showMessage('Failed to copy results', 'error'));
}
}
function downloadResults() {
if (currentResults) {
const blob = new Blob([currentResults.replace(/<br>/g, '\n')], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `data-analysis-${Date.now()}.txt`;
a.click();
URL.revokeObjectURL(url);
showMessage('💾 Results downloaded!', 'success');
}
}
// Agent initialization moved to consolidated DOMContentLoaded
function initializeAgent() {
// Any agent-specific initialization can go here
console.log('Agent initialized');
}
// Quick Agent Access Toggle Functions
function toggleQuickAgents() {
const panel = document.getElementById('quickAgentsPanel');
const overlay = document.getElementById('quickAgentsOverlay');
const toggle = document.querySelector('.quick-agent-toggle');
const isActive = panel.classList.contains('active');
if (isActive) {
// Close panel
panel.classList.remove('active');
overlay.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';
} else {
// Open panel
panel.classList.add('active');
overlay.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';
}
}
function closeQuickAgents() {
const panel = document.getElementById('quickAgentsPanel');
const overlay = document.getElementById('quickAgentsOverlay');
const toggle = document.querySelector('.quick-agent-toggle');
panel.classList.remove('active');
overlay.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';
}
// Close panel with Escape key
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
closeQuickAgents();
}
});
</script>
{% endblock %}