mirror of
https://github.com/thecyberlearn/quantum-ai-v3.git
synced 2026-08-18 19:52:57 +00:00
Standardize agent architecture and update comprehensive documentation
- Implement agent-specific JavaScript utilities for container-like isolation - Standardize HTML structure across all 5 agents with agent-container grid layout - Fix Data Analyzer wallet positioning by moving wallet-section to separate grid column - Reduce Data Analyzer custom CSS from 400+ lines to ~78 lines essential styles - Apply unified theme system (professional/creative/minimal) consistently - Add agent isolation with DataAnalyzerUtils, WeatherUtils, SocialAdsUtils, etc. - Update CLAUDE.md with comprehensive agent architecture documentation - Document standardization improvements, layout rules, and JavaScript isolation patterns 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
2e25b96807
commit
49b5b2a6aa
@ -13,310 +13,196 @@
|
|||||||
<link rel="stylesheet" href="{% static 'css/themes.css' %}">
|
<link rel="stylesheet" href="{% static 'css/themes.css' %}">
|
||||||
<link rel="stylesheet" href="{% static 'css/agent-base.css' %}">
|
<link rel="stylesheet" href="{% static 'css/agent-base.css' %}">
|
||||||
|
|
||||||
<!-- Shared JavaScript Utilities -->
|
<!-- Data Analyzer Specific Utilities -->
|
||||||
<script src="{% static 'js/agent-utils.js' %}"></script>
|
<script>
|
||||||
|
// Data Analyzer - Self-contained utilities (no shared dependencies)
|
||||||
|
const DataAnalyzerUtils = {
|
||||||
|
/**
|
||||||
|
* Update wallet balance display - Data Analyzer specific
|
||||||
|
*/
|
||||||
|
updateWalletBalance(newBalance) {
|
||||||
|
// Update header balance (anchor tag with emoji)
|
||||||
|
const headerBalance = document.querySelector('a[data-wallet-balance]');
|
||||||
|
if (headerBalance) {
|
||||||
|
headerBalance.textContent = `💰 ${newBalance.toFixed(2)} AED`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update page balance (div without emoji)
|
||||||
|
const pageBalance = document.querySelector('div[data-wallet-balance]');
|
||||||
|
if (pageBalance) {
|
||||||
|
pageBalance.textContent = `${newBalance.toFixed(2)} AED`;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.currentWalletBalance = newBalance;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show toast notification with duplicate prevention
|
||||||
|
*/
|
||||||
|
showToast(message, type = 'info') {
|
||||||
|
// Prevent duplicate toasts
|
||||||
|
const existingToast = document.querySelector('.data-analyzer-toast');
|
||||||
|
if (existingToast) {
|
||||||
|
existingToast.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
const toast = document.createElement('div');
|
||||||
|
toast.className = 'data-analyzer-toast';
|
||||||
|
toast.style.cssText = `
|
||||||
|
position: fixed;
|
||||||
|
top: 16px;
|
||||||
|
right: 16px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: white;
|
||||||
|
font-size: 13px;
|
||||||
|
z-index: 1000;
|
||||||
|
max-width: 300px;
|
||||||
|
font-weight: 500;
|
||||||
|
${type === 'success' ? 'background: #10b981;' : 'background: #ef4444;'}
|
||||||
|
`;
|
||||||
|
toast.textContent = message;
|
||||||
|
document.body.appendChild(toast);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if (toast.parentNode) {
|
||||||
|
toast.remove();
|
||||||
|
}
|
||||||
|
}, 2000);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate text for copy/download functionality
|
||||||
|
*/
|
||||||
|
generateTextForExport(contentElementId) {
|
||||||
|
const content = document.getElementById(contentElementId);
|
||||||
|
if (content) {
|
||||||
|
return content.innerText || content.textContent || '';
|
||||||
|
}
|
||||||
|
return 'No content available';
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy content to clipboard
|
||||||
|
*/
|
||||||
|
copyToClipboard(text, successMessage = 'Content copied to clipboard!') {
|
||||||
|
navigator.clipboard.writeText(text).then(() => {
|
||||||
|
this.showToast(`📋 ${successMessage}`, 'success');
|
||||||
|
}).catch(() => {
|
||||||
|
this.showToast('Failed to copy content', 'error');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download content as text file
|
||||||
|
*/
|
||||||
|
downloadAsFile(text, filename, successMessage = 'File downloaded!') {
|
||||||
|
const blob = new Blob([text], { type: 'text/plain' });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = filename || `content-${Date.now()}.txt`;
|
||||||
|
a.click();
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
this.showToast(`💾 ${successMessage}`, 'success');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// For backward compatibility, create AgentUtils alias
|
||||||
|
const AgentUtils = DataAnalyzerUtils;
|
||||||
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
* {
|
/* Data Analyzer specific styles only */
|
||||||
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 {
|
.file-upload-zone {
|
||||||
border: 2px dashed var(--border-medium);
|
border: 2px dashed var(--agent-border);
|
||||||
border-radius: clamp(12px, 3vw, 16px);
|
border-radius: var(--radius);
|
||||||
padding: clamp(24px, 6vw, 40px);
|
padding: var(--space-xl);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
transition: all 0.3s ease;
|
transition: all var(--transition);
|
||||||
background: var(--background-light);
|
background: var(--agent-card-bg);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
margin-bottom: clamp(12px, 3vw, 16px);
|
margin-bottom: var(--space-lg);
|
||||||
}
|
|
||||||
|
|
||||||
.file-upload-zone.dragover {
|
|
||||||
border-color: var(--success-green);
|
|
||||||
background: rgba(16, 185, 129, 0.1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.file-upload-zone.dragover,
|
||||||
.file-upload-zone:hover {
|
.file-upload-zone:hover {
|
||||||
border-color: var(--success-green);
|
border-color: var(--agent-primary);
|
||||||
background: rgba(16, 185, 129, 0.1);
|
background: var(--hover-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-preview {
|
.file-preview {
|
||||||
background: rgba(16, 185, 129, 0.1);
|
background: var(--agent-card-bg);
|
||||||
border: 1px solid rgba(16, 185, 129, 0.5);
|
border: 1px solid var(--agent-border);
|
||||||
border-radius: 8px;
|
border-radius: var(--radius);
|
||||||
padding: 12px;
|
padding: var(--space-md);
|
||||||
margin-top: 12px;
|
margin-top: var(--space-md);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 12px;
|
gap: var(--space-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Radio button grids */
|
|
||||||
.radio-grid {
|
.radio-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(min(180px, 100%), 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
gap: clamp(8px, 2vw, 12px);
|
gap: var(--space-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
.radio-option {
|
.radio-option {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: clamp(8px, 2vw, 12px);
|
gap: var(--space-md);
|
||||||
padding: clamp(12px, 3vw, 16px);
|
padding: var(--space-lg);
|
||||||
border: 2px solid var(--border-light);
|
border: 1px solid var(--agent-border);
|
||||||
border-radius: clamp(8px, 2vw, 12px);
|
border-radius: var(--radius);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background: white;
|
background: white;
|
||||||
transition: all 0.2s ease;
|
transition: all var(--transition);
|
||||||
min-height: 44px;
|
min-height: 60px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.radio-option.selected {
|
.radio-option.selected {
|
||||||
border-color: var(--success-green);
|
border-color: var(--agent-primary);
|
||||||
background: rgba(16, 185, 129, 0.1);
|
background: var(--agent-card-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-option:hover {
|
||||||
|
border-color: var(--agent-primary);
|
||||||
|
background: var(--hover-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.radio-option input[type="radio"] {
|
.radio-option input[type="radio"] {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Processing status */
|
|
||||||
.processing-status {
|
.processing-status {
|
||||||
padding: clamp(16px, 4vw, 20px);
|
display: none;
|
||||||
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;
|
text-align: center;
|
||||||
margin-bottom: clamp(16px, 4vw, 24px);
|
padding: var(--space-lg);
|
||||||
|
margin: var(--space-lg) 0;
|
||||||
|
background: var(--agent-card-bg);
|
||||||
|
border: 1px solid var(--agent-border);
|
||||||
|
border-radius: var(--radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Results card */
|
|
||||||
.results-card {
|
.results-card {
|
||||||
background: rgba(255, 255, 255, 0.9);
|
display: none;
|
||||||
border-radius: 16px;
|
margin-top: var(--space-lg);
|
||||||
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 {
|
/* Ensure grid layout works */
|
||||||
display: flex;
|
div.agent-page div.agent-container {
|
||||||
align-items: center;
|
display: grid !important;
|
||||||
gap: 12px;
|
grid-template-columns: 1fr 350px !important;
|
||||||
margin-bottom: 20px;
|
gap: 24px !important;
|
||||||
|
max-width: 1280px !important;
|
||||||
|
margin: 0 auto !important;
|
||||||
|
align-items: start !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.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) {
|
@media (max-width: 768px) {
|
||||||
.main-grid {
|
div.agent-page div.agent-container {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr !important;
|
||||||
}
|
|
||||||
|
|
||||||
.radio-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-buttons {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -325,6 +211,15 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="agent-page theme-professional">
|
<div class="agent-page theme-professional">
|
||||||
<div class="agent-container">
|
<div class="agent-container">
|
||||||
|
<!-- Messages -->
|
||||||
|
{% if messages %}
|
||||||
|
{% for message in messages %}
|
||||||
|
<div class="{% if message.tags == 'error' %}error-message{% else %}success-message{% endif %}" style="grid-column: 1 / -1;">
|
||||||
|
{{ message }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<!-- Main Content -->
|
<!-- Main Content -->
|
||||||
<div>
|
<div>
|
||||||
<form method="POST" id="dataAnalyzerForm">
|
<form method="POST" id="dataAnalyzerForm">
|
||||||
@ -335,11 +230,11 @@
|
|||||||
<h3 class="section-title">📁 Upload Your Data File</h3>
|
<h3 class="section-title">📁 Upload Your Data File</h3>
|
||||||
|
|
||||||
<div class="file-upload-zone" id="fileUploadZone" onclick="document.getElementById('fileInput').click()">
|
<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: 48px; margin-bottom: 12px;">📊</div>
|
||||||
<div style="font-size: clamp(16px, 4vw, 18px); font-weight: 600; color: var(--text-primary); margin-bottom: 8px;">
|
<div style="font-size: var(--text-lg); font-weight: 600; color: var(--text-color); margin-bottom: 8px;">
|
||||||
Choose or drag your data file here
|
Choose or drag your data file here
|
||||||
</div>
|
</div>
|
||||||
<div style="font-size: clamp(14px, 3.5vw, 16px); color: var(--text-secondary);">
|
<div style="font-size: var(--text-base); color: var(--accent-color);">
|
||||||
Supports PDF, CSV, Excel files (up to 10MB)
|
Supports PDF, CSV, Excel files (up to 10MB)
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -357,15 +252,7 @@
|
|||||||
<div style="font-weight: 600; color: var(--text-primary);" id="fileName"></div>
|
<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 style="font-size: 14px; color: var(--text-secondary);" id="fileSize"></div>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" onclick="removeFile()" style="
|
<button type="button" onclick="removeFile()" class="btn btn-secondary" style="padding: 4px 8px; font-size: 12px;">
|
||||||
background: var(--error-red);
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 4px 8px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 12px;
|
|
||||||
">
|
|
||||||
Remove
|
Remove
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -378,12 +265,12 @@
|
|||||||
<div class="radio-grid">
|
<div class="radio-grid">
|
||||||
<label class="radio-option selected" data-value="summary">
|
<label class="radio-option selected" data-value="summary">
|
||||||
<input type="radio" name="analysisType" value="summary" checked />
|
<input type="radio" name="analysisType" value="summary" checked />
|
||||||
<div style="font-size: clamp(16px, 4vw, 20px);">📋</div>
|
<div style="font-size: 20px;">📋</div>
|
||||||
<div>
|
<div>
|
||||||
<div style="font-weight: 600; margin-bottom: clamp(2px, 1vw, 4px); font-size: clamp(14px, 3.5vw, 16px);">
|
<div style="font-weight: 600; margin-bottom: 4px; font-size: var(--text-lg);">
|
||||||
Summary Analysis
|
Summary Analysis
|
||||||
</div>
|
</div>
|
||||||
<div style="font-size: clamp(12px, 3vw, 14px); color: var(--text-secondary);">
|
<div style="font-size: var(--text-sm); color: var(--accent-color);">
|
||||||
Quick overview and key insights
|
Quick overview and key insights
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -391,12 +278,12 @@
|
|||||||
|
|
||||||
<label class="radio-option" data-value="detailed">
|
<label class="radio-option" data-value="detailed">
|
||||||
<input type="radio" name="analysisType" value="detailed" />
|
<input type="radio" name="analysisType" value="detailed" />
|
||||||
<div style="font-size: clamp(16px, 4vw, 20px);">📈</div>
|
<div style="font-size: 20px;">📈</div>
|
||||||
<div>
|
<div>
|
||||||
<div style="font-weight: 600; margin-bottom: clamp(2px, 1vw, 4px); font-size: clamp(14px, 3.5vw, 16px);">
|
<div style="font-weight: 600; margin-bottom: 4px; font-size: var(--text-lg);">
|
||||||
Detailed Analysis
|
Detailed Analysis
|
||||||
</div>
|
</div>
|
||||||
<div style="font-size: clamp(12px, 3vw, 14px); color: var(--text-secondary);">
|
<div style="font-size: var(--text-sm); color: var(--accent-color);">
|
||||||
Comprehensive statistical analysis
|
Comprehensive statistical analysis
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -404,12 +291,12 @@
|
|||||||
|
|
||||||
<label class="radio-option" data-value="statistical">
|
<label class="radio-option" data-value="statistical">
|
||||||
<input type="radio" name="analysisType" value="statistical" />
|
<input type="radio" name="analysisType" value="statistical" />
|
||||||
<div style="font-size: clamp(16px, 4vw, 20px);">📊</div>
|
<div style="font-size: 20px;">📊</div>
|
||||||
<div>
|
<div>
|
||||||
<div style="font-weight: 600; margin-bottom: clamp(2px, 1vw, 4px); font-size: clamp(14px, 3.5vw, 16px);">
|
<div style="font-weight: 600; margin-bottom: 4px; font-size: var(--text-lg);">
|
||||||
Statistical Analysis
|
Statistical Analysis
|
||||||
</div>
|
</div>
|
||||||
<div style="font-size: clamp(12px, 3vw, 14px); color: var(--text-secondary);">
|
<div style="font-size: var(--text-sm); color: var(--accent-color);">
|
||||||
Advanced statistics and trends
|
Advanced statistics and trends
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -417,80 +304,78 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<!-- 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>
|
||||||
<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 -->
|
<!-- Wallet Sidebar -->
|
||||||
<div class="wallet-section">
|
<div class="wallet-section">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h3 class="section-title">💳 Your Wallet</h3>
|
<h3 class="section-title">💳 Your Wallet</h3>
|
||||||
|
|
||||||
<div class="wallet-balance" id="walletBalance">{{ user.wallet_balance|floatformat:2 }} AED</div>
|
<div class="wallet-balance" data-wallet-balance>
|
||||||
|
{% if user.is_authenticated %}
|
||||||
|
{{ user.wallet_balance|floatformat:2 }} AED
|
||||||
|
{% else %}
|
||||||
|
0.00 AED
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
<div class="balance-label">Available Balance</div>
|
<div class="balance-label">Available Balance</div>
|
||||||
|
|
||||||
<button
|
{% if user.is_authenticated %}
|
||||||
type="submit"
|
{% if user.wallet_balance >= 5.00 %}
|
||||||
form="dataAnalyzerForm"
|
<button type="submit" form="dataAnalyzerForm" class="btn btn-primary process-btn" id="processButton">
|
||||||
class="btn btn-primary process-btn"
|
|
||||||
id="processButton"
|
|
||||||
>
|
|
||||||
📊 Analyze Data (5.00 AED)
|
📊 Analyze Data (5.00 AED)
|
||||||
</button>
|
</button>
|
||||||
|
{% else %}
|
||||||
|
<div class="insufficient-balance">
|
||||||
|
Insufficient balance! You need 5.00 AED.
|
||||||
|
</div>
|
||||||
|
<a href="{% url 'core:wallet' %}" class="btn btn-primary process-btn" style="text-decoration: none;">
|
||||||
|
💰 Top Up Wallet
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
<a href="{% url 'authentication:login' %}" class="btn btn-primary process-btn" style="text-decoration: none;">
|
||||||
|
🔑 Login to Continue
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="usage-info">
|
<div class="usage-info">
|
||||||
<h4>💡 How it works</h4>
|
<h4>💡 How it works</h4>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Upload PDF, CSV, or Excel files</li>
|
<li>Upload your data file (PDF, CSV, Excel)</li>
|
||||||
<li>Choose your analysis depth</li>
|
<li>Choose analysis type</li>
|
||||||
<li>Get AI-powered insights</li>
|
<li>Get comprehensive insights</li>
|
||||||
<li>Download comprehensive reports</li>
|
<li>Download detailed report</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Processing Status -->
|
||||||
|
<div id="processingStatus" class="processing-status">
|
||||||
|
<div class="status-icon">📊</div>
|
||||||
|
<div style="font-weight: 600; color: var(--primary-color);">Analyzing Your Data...</div>
|
||||||
|
<div style="font-size: 14px; color: var(--text-secondary); margin-top: 8px;" id="statusText">Processing data file...</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Results -->
|
||||||
|
<div id="analysisResults" class="results-card">
|
||||||
|
<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 Complete</h3>
|
||||||
|
<div style="background: var(--primary-color); 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 results will be displayed here -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="action-buttons">
|
||||||
|
<button onclick="copyAnalysisReport()" class="btn btn-primary">📋 Copy Analysis</button>
|
||||||
|
<button onclick="downloadAnalysisReport()" class="btn btn-secondary">💾 Download Report</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
952
data_analyzer/templates/data_analyzer/detail_old.html
Normal file
952
data_analyzer/templates/data_analyzer/detail_old.html
Normal file
@ -0,0 +1,952 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block title %}Data Analyzer Agent - NetCop AI Hub{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_css %}
|
||||||
|
<!-- Optimized Font Loading -->
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
|
||||||
|
|
||||||
|
<!-- External Stylesheets -->
|
||||||
|
<link rel="stylesheet" href="{% static 'css/themes.css' %}">
|
||||||
|
<link rel="stylesheet" href="{% static 'css/agent-base.css' %}">
|
||||||
|
|
||||||
|
<!-- Data Analyzer Specific Utilities -->
|
||||||
|
<script>
|
||||||
|
// Data Analyzer - Self-contained utilities (no shared dependencies)
|
||||||
|
const DataAnalyzerUtils = {
|
||||||
|
/**
|
||||||
|
* Update wallet balance display - Data Analyzer specific
|
||||||
|
*/
|
||||||
|
updateWalletBalance(newBalance) {
|
||||||
|
// Update header balance (anchor tag with emoji)
|
||||||
|
const headerBalance = document.querySelector('a[data-wallet-balance]');
|
||||||
|
if (headerBalance) {
|
||||||
|
headerBalance.textContent = `💰 ${newBalance.toFixed(2)} AED`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update page balance (div without emoji)
|
||||||
|
const pageBalance = document.querySelector('div[data-wallet-balance]');
|
||||||
|
if (pageBalance) {
|
||||||
|
pageBalance.textContent = `${newBalance.toFixed(2)} AED`;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.currentWalletBalance = newBalance;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show toast notification with duplicate prevention
|
||||||
|
*/
|
||||||
|
showToast(message, type = 'info') {
|
||||||
|
// Prevent duplicate toasts
|
||||||
|
const existingToast = document.querySelector('.data-analyzer-toast');
|
||||||
|
if (existingToast) {
|
||||||
|
existingToast.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
const toast = document.createElement('div');
|
||||||
|
toast.className = 'data-analyzer-toast';
|
||||||
|
toast.style.cssText = `
|
||||||
|
position: fixed;
|
||||||
|
top: 16px;
|
||||||
|
right: 16px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: white;
|
||||||
|
font-size: 13px;
|
||||||
|
z-index: 1000;
|
||||||
|
max-width: 300px;
|
||||||
|
font-weight: 500;
|
||||||
|
${type === 'success' ? 'background: #10b981;' : 'background: #ef4444;'}
|
||||||
|
`;
|
||||||
|
toast.textContent = message;
|
||||||
|
document.body.appendChild(toast);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if (toast.parentNode) {
|
||||||
|
toast.remove();
|
||||||
|
}
|
||||||
|
}, 2000);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate text for copy/download functionality
|
||||||
|
*/
|
||||||
|
generateTextForExport(contentElementId) {
|
||||||
|
const content = document.getElementById(contentElementId);
|
||||||
|
if (content) {
|
||||||
|
return content.innerText || content.textContent || '';
|
||||||
|
}
|
||||||
|
return 'No content available';
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy content to clipboard
|
||||||
|
*/
|
||||||
|
copyToClipboard(text, successMessage = 'Content copied to clipboard!') {
|
||||||
|
navigator.clipboard.writeText(text).then(() => {
|
||||||
|
this.showToast(`📋 ${successMessage}`, 'success');
|
||||||
|
}).catch(() => {
|
||||||
|
this.showToast('Failed to copy content', 'error');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download content as text file
|
||||||
|
*/
|
||||||
|
downloadAsFile(text, filename, successMessage = 'File downloaded!') {
|
||||||
|
const blob = new Blob([text], { type: 'text/plain' });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = filename || `content-${Date.now()}.txt`;
|
||||||
|
a.click();
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
this.showToast(`💾 ${successMessage}`, 'success');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// For backward compatibility, create AgentUtils alias
|
||||||
|
const AgentUtils = DataAnalyzerUtils;
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
.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="agent-page theme-professional">
|
||||||
|
<div class="agent-container">
|
||||||
|
<!-- Main Content -->
|
||||||
|
<div>
|
||||||
|
<form method="POST" id="dataAnalyzerForm">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
<!-- 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>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!-- 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" data-wallet-balance>{{ user.wallet_balance|floatformat:2 }} AED</div>
|
||||||
|
<div class="balance-label">Available Balance</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
form="dataAnalyzerForm"
|
||||||
|
class="btn btn-primary process-btn"
|
||||||
|
id="processButton"
|
||||||
|
>
|
||||||
|
📊 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>
|
||||||
|
{% 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 {
|
||||||
|
AgentUtils.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;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle form submission
|
||||||
|
document.getElementById('dataAnalyzerForm').addEventListener('submit', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!selectedFile) {
|
||||||
|
AgentUtils.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) {
|
||||||
|
AgentUtils.showToast('Insufficient balance! You need 5.00 AED.', 'error');
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.href = "{% url 'core:wallet' %}";
|
||||||
|
}, 2000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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(this);
|
||||||
|
if (selectedFile) {
|
||||||
|
formData.append('file', selectedFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch(window.location.href, {
|
||||||
|
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) {
|
||||||
|
AgentUtils.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)';
|
||||||
|
AgentUtils.showToast('❌ Network error - please try again', 'error');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Display analysis results with custom Data Analyzer formatting
|
||||||
|
function displayResults(result) {
|
||||||
|
const resultsContainer = document.getElementById('analysisResults');
|
||||||
|
const contentElement = document.getElementById('analysisContent');
|
||||||
|
|
||||||
|
if (!resultsContainer || !contentElement) {
|
||||||
|
console.error('Results elements not found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update wallet balance if provided
|
||||||
|
if (result.wallet_balance !== undefined) {
|
||||||
|
AgentUtils.updateWalletBalance(result.wallet_balance);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle errors
|
||||||
|
if (result.error) {
|
||||||
|
AgentUtils.showToast(`❌ ${result.error}`, 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract content with priority: report_text > insights_summary > raw_response.analysis
|
||||||
|
let content = '';
|
||||||
|
|
||||||
|
if (result.report_text && typeof result.report_text === 'string') {
|
||||||
|
content = result.report_text;
|
||||||
|
} else if (result.insights_summary && typeof result.insights_summary === 'string') {
|
||||||
|
content = result.insights_summary;
|
||||||
|
} else if (result.raw_response && result.raw_response.analysis && typeof result.raw_response.analysis === 'string') {
|
||||||
|
content = result.raw_response.analysis;
|
||||||
|
} else {
|
||||||
|
content = 'Data analysis completed successfully!';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse markdown content to HTML
|
||||||
|
const formattedContent = parseMarkdownToHTML(content);
|
||||||
|
|
||||||
|
// Update content
|
||||||
|
contentElement.innerHTML = formattedContent;
|
||||||
|
|
||||||
|
// Show results
|
||||||
|
resultsContainer.style.display = 'block';
|
||||||
|
resultsContainer.scrollIntoView({ behavior: 'smooth' });
|
||||||
|
|
||||||
|
// Show success message
|
||||||
|
const successMessage = result.success ? '✅ Data analysis completed and payment processed!' : '✅ Data analysis completed!';
|
||||||
|
AgentUtils.showToast(successMessage, 'success');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enhanced markdown parser for Data Analyzer results
|
||||||
|
function parseMarkdownToHTML(markdown) {
|
||||||
|
if (!markdown || typeof markdown !== 'string') {
|
||||||
|
return '<p>No content available.</p>';
|
||||||
|
}
|
||||||
|
|
||||||
|
let html = markdown;
|
||||||
|
|
||||||
|
// Convert headers (### Header, ## Header, # Header)
|
||||||
|
html = html.replace(/^### (.*$)/gm, '<h3 style="font-size: 18px; font-weight: 600; color: var(--text-primary); margin: 20px 0 12px 0; border-bottom: 2px solid var(--success-green); padding-bottom: 8px;">$1</h3>');
|
||||||
|
html = html.replace(/^## (.*$)/gm, '<h2 style="font-size: 20px; font-weight: 600; color: var(--text-primary); margin: 24px 0 16px 0; border-bottom: 2px solid var(--success-green); padding-bottom: 8px;">$1</h2>');
|
||||||
|
html = html.replace(/^# (.*$)/gm, '<h1 style="font-size: 22px; font-weight: 600; color: var(--text-primary); margin: 28px 0 18px 0; border-bottom: 2px solid var(--success-green); padding-bottom: 10px;">$1</h1>');
|
||||||
|
|
||||||
|
// Convert bold text (**text** or __text__)
|
||||||
|
html = html.replace(/\*\*(.*?)\*\*/g, '<strong style="font-weight: 600; color: var(--text-primary);">$1</strong>');
|
||||||
|
html = html.replace(/__(.*?)__/g, '<strong style="font-weight: 600; color: var(--text-primary);">$1</strong>');
|
||||||
|
|
||||||
|
// Convert italic text (*text* or _text_)
|
||||||
|
html = html.replace(/\*(.*?)\*/g, '<em style="font-style: italic; color: var(--text-secondary);">$1</em>');
|
||||||
|
html = html.replace(/_(.*?)_/g, '<em style="font-style: italic; color: var(--text-secondary);">$1</em>');
|
||||||
|
|
||||||
|
// Convert bullet points (- item or * item)
|
||||||
|
html = html.replace(/^[\s]*[-\*]\s+(.*)$/gm, '<li style="margin: 8px 0; padding-left: 8px; color: var(--text-primary);">$1</li>');
|
||||||
|
|
||||||
|
// Wrap consecutive list items in ul tags
|
||||||
|
html = html.replace(/(<li[^>]*>.*?<\/li>\s*)+/gs, function(match) {
|
||||||
|
return `<ul style="margin: 16px 0; padding-left: 20px; list-style-type: disc; color: var(--success-green);">${match}</ul>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Convert numbered lists (1. item, 2. item)
|
||||||
|
html = html.replace(/^\s*\d+\.\s+(.*)$/gm, '<li style="margin: 8px 0; padding-left: 8px; color: var(--text-primary);">$1</li>');
|
||||||
|
|
||||||
|
// Wrap consecutive numbered list items in ol tags
|
||||||
|
html = html.replace(/(<li[^>]*>.*?<\/li>\s*)+/gs, function(match) {
|
||||||
|
if (match.includes('ul style')) return match; // Skip if already wrapped in ul
|
||||||
|
return `<ol style="margin: 16px 0; padding-left: 20px; list-style-type: decimal; color: var(--success-green);">${match}</ol>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Convert line breaks to paragraphs
|
||||||
|
const paragraphs = html.split(/\n\s*\n/);
|
||||||
|
html = paragraphs.map(p => {
|
||||||
|
const trimmed = p.trim();
|
||||||
|
if (trimmed === '') return '';
|
||||||
|
|
||||||
|
// Skip if already wrapped in HTML tags
|
||||||
|
if (trimmed.startsWith('<h') || trimmed.startsWith('<ul') || trimmed.startsWith('<ol') || trimmed.startsWith('<li')) {
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `<p style="margin: 12px 0; line-height: 1.6; color: var(--text-primary);">${trimmed}</p>`;
|
||||||
|
}).filter(p => p !== '').join('');
|
||||||
|
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track polling and results to prevent duplicates
|
||||||
|
let resultsDisplayed = false;
|
||||||
|
let currentPollInterval = null;
|
||||||
|
|
||||||
|
// Poll for results
|
||||||
|
function pollForResults(requestId) {
|
||||||
|
let pollCount = 0;
|
||||||
|
const maxPolls = 60; // 60 seconds maximum for data analysis
|
||||||
|
resultsDisplayed = false; // Reset flag
|
||||||
|
|
||||||
|
// Clear any existing polling
|
||||||
|
if (currentPollInterval) {
|
||||||
|
clearInterval(currentPollInterval);
|
||||||
|
currentPollInterval = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentPollInterval = setInterval(() => {
|
||||||
|
pollCount++;
|
||||||
|
|
||||||
|
fetch(`/agents/data-analyzer/status/${requestId}/`)
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP ${response.status}`);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(result => {
|
||||||
|
if (result.status === 'completed' || result.status === 'failed') {
|
||||||
|
// Stop polling immediately
|
||||||
|
clearInterval(currentPollInterval);
|
||||||
|
currentPollInterval = null;
|
||||||
|
|
||||||
|
// Reset UI
|
||||||
|
document.getElementById('processingStatus').style.display = 'none';
|
||||||
|
document.getElementById('processButton').disabled = false;
|
||||||
|
document.getElementById('processButton').innerHTML = '📊 Analyze Data (5.00 AED)';
|
||||||
|
|
||||||
|
// Display results only once
|
||||||
|
if (!resultsDisplayed) {
|
||||||
|
resultsDisplayed = true;
|
||||||
|
displayResults(result);
|
||||||
|
}
|
||||||
|
} else if (pollCount >= maxPolls) {
|
||||||
|
clearInterval(currentPollInterval);
|
||||||
|
currentPollInterval = null;
|
||||||
|
document.getElementById('processingStatus').style.display = 'none';
|
||||||
|
document.getElementById('processButton').disabled = false;
|
||||||
|
document.getElementById('processButton').innerHTML = '📊 Analyze Data (5.00 AED)';
|
||||||
|
AgentUtils.showToast('❌ Processing timeout - please try again', 'error');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error polling results:', error);
|
||||||
|
clearInterval(currentPollInterval);
|
||||||
|
currentPollInterval = null;
|
||||||
|
document.getElementById('processingStatus').style.display = 'none';
|
||||||
|
document.getElementById('processButton').disabled = false;
|
||||||
|
document.getElementById('processButton').innerHTML = '📊 Analyze Data (5.00 AED)';
|
||||||
|
AgentUtils.showToast('❌ Network error during processing - please try again', 'error');
|
||||||
|
});
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function copyAnalysisReport() {
|
||||||
|
const reportText = AgentUtils.generateTextForExport('analysisContent');
|
||||||
|
AgentUtils.copyToClipboard(reportText, 'Analysis report copied to clipboard!');
|
||||||
|
}
|
||||||
|
|
||||||
|
function downloadAnalysisReport() {
|
||||||
|
const reportText = AgentUtils.generateTextForExport('analysisContent');
|
||||||
|
AgentUtils.downloadAsFile(reportText, `data-analysis-report-${Date.now()}.txt`, 'Analysis report downloaded!');
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
113
docs/CLAUDE.md
113
docs/CLAUDE.md
@ -190,6 +190,8 @@ The system uses Django templates in `agent_base/templates/agent_generator/` to g
|
|||||||
|
|
||||||
#### Example Agents (Production Ready)
|
#### Example Agents (Production Ready)
|
||||||
|
|
||||||
|
All agents now feature consistent architecture with standardized themes, unified CSS, and isolated JavaScript utilities for container-like functionality.
|
||||||
|
|
||||||
**Data Analysis Agent** (Price: 5.00 AED):
|
**Data Analysis Agent** (Price: 5.00 AED):
|
||||||
- **N8N Integration**: PDF analysis webhook processor
|
- **N8N Integration**: PDF analysis webhook processor
|
||||||
- **File Upload**: PDF, CSV, Excel files with drag-and-drop interface
|
- **File Upload**: PDF, CSV, Excel files with drag-and-drop interface
|
||||||
@ -198,6 +200,8 @@ The system uses Django templates in `agent_base/templates/agent_generator/` to g
|
|||||||
- **Form Submission Pattern**: Uses unified form submission (not button click)
|
- **Form Submission Pattern**: Uses unified form submission (not button click)
|
||||||
- **Unified CSS**: Uses agent-base.css with professional theme
|
- **Unified CSS**: Uses agent-base.css with professional theme
|
||||||
- **Text Display**: Simple text formatting (no complex markdown parsing)
|
- **Text Display**: Simple text formatting (no complex markdown parsing)
|
||||||
|
- **Architecture**: Standard agent-container grid layout (1fr 350px) with proper wallet positioning
|
||||||
|
- **JavaScript Isolation**: DataAnalyzerUtils with agent-specific functionality
|
||||||
|
|
||||||
**Weather Reporter Agent** (Price: 2.00 AED):
|
**Weather Reporter Agent** (Price: 2.00 AED):
|
||||||
- **API Integration**: OpenWeatherMap API with direct calls
|
- **API Integration**: OpenWeatherMap API with direct calls
|
||||||
@ -205,7 +209,9 @@ The system uses Django templates in `agent_base/templates/agent_generator/` to g
|
|||||||
- **Formatted Reports**: Both current and detailed weather reports
|
- **Formatted Reports**: Both current and detailed weather reports
|
||||||
- **Real-time Results**: Dynamic display below form
|
- **Real-time Results**: Dynamic display below form
|
||||||
- **Error Handling**: API failures and invalid locations
|
- **Error Handling**: API failures and invalid locations
|
||||||
- **Unified CSS**: Uses agent-base.css with minimal theme
|
- **Unified CSS**: Uses agent-base.css with professional theme
|
||||||
|
- **Architecture**: Standard agent-container grid layout with proper structure
|
||||||
|
- **JavaScript Isolation**: WeatherUtils with agent-specific functionality
|
||||||
|
|
||||||
**Social Ads Generator Agent** (Price: 7.00 AED):
|
**Social Ads Generator Agent** (Price: 7.00 AED):
|
||||||
- **N8N Integration**: Social media ad generation via webhook
|
- **N8N Integration**: Social media ad generation via webhook
|
||||||
@ -213,6 +219,8 @@ The system uses Django templates in `agent_base/templates/agent_generator/` to g
|
|||||||
- **Multi-language**: English, Arabic, Spanish, French, German, Chinese
|
- **Multi-language**: English, Arabic, Spanish, French, German, Chinese
|
||||||
- **Real-time Results**: Dynamic content generation and display
|
- **Real-time Results**: Dynamic content generation and display
|
||||||
- **Unified CSS**: Uses agent-base.css with creative theme (glassmorphism)
|
- **Unified CSS**: Uses agent-base.css with creative theme (glassmorphism)
|
||||||
|
- **Architecture**: Standard agent-container grid layout with glassmorphism styling
|
||||||
|
- **JavaScript Isolation**: SocialAdsUtils with agent-specific functionality
|
||||||
|
|
||||||
**Job Posting Generator Agent** (Price: 4.00 AED):
|
**Job Posting Generator Agent** (Price: 4.00 AED):
|
||||||
- **N8N Integration**: Professional job posting creation
|
- **N8N Integration**: Professional job posting creation
|
||||||
@ -220,6 +228,17 @@ The system uses Django templates in `agent_base/templates/agent_generator/` to g
|
|||||||
- **Multi-language Support**: Multiple output languages
|
- **Multi-language Support**: Multiple output languages
|
||||||
- **Enhanced UX**: Progressive form validation and real-time feedback
|
- **Enhanced UX**: Progressive form validation and real-time feedback
|
||||||
- **Unified CSS**: Uses agent-base.css with professional theme
|
- **Unified CSS**: Uses agent-base.css with professional theme
|
||||||
|
- **Architecture**: Standard agent-container grid layout with professional styling
|
||||||
|
- **JavaScript Isolation**: JobPostingUtils with agent-specific functionality
|
||||||
|
|
||||||
|
**Five Whys Analysis Agent** (Price: 3.00 AED):
|
||||||
|
- **N8N Integration**: Problem analysis using Five Whys methodology
|
||||||
|
- **Comprehensive UX**: Enhanced UI with styled cards and professional layout
|
||||||
|
- **Multi-language Support**: Multiple output languages
|
||||||
|
- **Real-time Results**: Dynamic analysis generation and display
|
||||||
|
- **Unified CSS**: Uses agent-base.css with professional theme
|
||||||
|
- **Architecture**: Standard agent-container grid layout with consistent styling
|
||||||
|
- **JavaScript Isolation**: FiveWhysUtils with agent-specific functionality
|
||||||
|
|
||||||
### Management Commands
|
### Management Commands
|
||||||
|
|
||||||
@ -537,7 +556,32 @@ All agents now use a unified CSS system for consistent user experience and maint
|
|||||||
#### Core Files
|
#### Core Files
|
||||||
- **`/static/css/agent-base.css`**: Unified component library for all agents
|
- **`/static/css/agent-base.css`**: Unified component library for all agents
|
||||||
- **`/static/css/themes.css`**: Global color variables and themes
|
- **`/static/css/themes.css`**: Global color variables and themes
|
||||||
- **`/static/js/agent-utils.js`**: Shared JavaScript utilities for all agents
|
- **Agent-specific JavaScript utilities**: Each agent has isolated JavaScript functions for container-like functionality
|
||||||
|
|
||||||
|
### Agent Isolation Architecture
|
||||||
|
|
||||||
|
#### Container-like Functionality
|
||||||
|
All agents now implement true isolation to prevent cross-agent interference:
|
||||||
|
|
||||||
|
**JavaScript Isolation Pattern:**
|
||||||
|
```javascript
|
||||||
|
// Each agent has its own utility namespace
|
||||||
|
const DataAnalyzerUtils = { /* agent-specific functions */ };
|
||||||
|
const WeatherUtils = { /* agent-specific functions */ };
|
||||||
|
const SocialAdsUtils = { /* agent-specific functions */ };
|
||||||
|
const JobPostingUtils = { /* agent-specific functions */ };
|
||||||
|
const FiveWhysUtils = { /* agent-specific functions */ };
|
||||||
|
|
||||||
|
// For backward compatibility, each agent creates AgentUtils alias
|
||||||
|
const AgentUtils = DataAnalyzerUtils; // or appropriate agent utils
|
||||||
|
```
|
||||||
|
|
||||||
|
**Benefits of Isolation:**
|
||||||
|
- No shared dependencies between agents
|
||||||
|
- Changes to one agent don't affect others
|
||||||
|
- Agent-specific functionality can be customized
|
||||||
|
- Easier debugging and maintenance
|
||||||
|
- Container-like isolation without containerization complexity
|
||||||
|
|
||||||
#### Theme System
|
#### Theme System
|
||||||
The unified CSS supports multiple themes via CSS custom properties:
|
The unified CSS supports multiple themes via CSS custom properties:
|
||||||
@ -562,17 +606,57 @@ The unified CSS supports multiple themes via CSS custom properties:
|
|||||||
<div class="agent-page theme-professional"> <!-- or theme-creative, theme-minimal -->
|
<div class="agent-page theme-professional"> <!-- or theme-creative, theme-minimal -->
|
||||||
<div class="agent-container">
|
<div class="agent-container">
|
||||||
<div>
|
<div>
|
||||||
<!-- Main content area -->
|
<!-- Main content area (first grid column) -->
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h3 class="section-title">Agent Title</h3>
|
<h3 class="section-title">Agent Title</h3>
|
||||||
<!-- Agent form and content -->
|
<!-- Agent form and content -->
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Processing status and results stay within first grid column -->
|
||||||
</div>
|
</div>
|
||||||
<div class="wallet-section">
|
<div class="wallet-section">
|
||||||
<!-- Wallet sidebar -->
|
<!-- Wallet sidebar (second grid column) -->
|
||||||
|
<div class="card">
|
||||||
|
<h3 class="section-title">💳 Your Wallet</h3>
|
||||||
|
<!-- Wallet content -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Standardized Layout Architecture
|
||||||
|
|
||||||
|
**Grid Layout System:**
|
||||||
|
- `agent-container`: CSS Grid with `grid-template-columns: 1fr 350px`
|
||||||
|
- **First column**: Main content, forms, processing status, results
|
||||||
|
- **Second column**: Wallet sidebar (350px width)
|
||||||
|
- **Mobile responsive**: Single column on screens < 768px
|
||||||
|
|
||||||
|
**Critical Structure Rules:**
|
||||||
|
1. **Wallet positioning**: `wallet-section` must be a direct child of `agent-container` (separate grid column)
|
||||||
|
2. **Content hierarchy**: All agent content stays in first grid column
|
||||||
|
3. **Processing status**: Displays below form, spans full width of first column
|
||||||
|
4. **Results display**: Shows below processing status in first column
|
||||||
|
|
||||||
|
**Data Analyzer Wallet Fix Example:**
|
||||||
|
```html
|
||||||
|
<!-- ❌ INCORRECT: wallet-section inside main content -->
|
||||||
|
<div class="agent-container">
|
||||||
|
<div>
|
||||||
|
<form>...</form>
|
||||||
|
<div class="wallet-section">...</div> <!-- Wrong placement -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ✅ CORRECT: wallet-section as separate grid column -->
|
||||||
|
<div class="agent-container">
|
||||||
|
<div>
|
||||||
|
<form>...</form>
|
||||||
|
<!-- Processing Status -->
|
||||||
|
<!-- Results -->
|
||||||
|
</div>
|
||||||
|
<div class="wallet-section">...</div> <!-- Correct placement -->
|
||||||
|
</div>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Text Display Standardization
|
### Text Display Standardization
|
||||||
@ -675,6 +759,27 @@ The project uses a clean, modular individual agent architecture:
|
|||||||
- **Data Attributes**: Add `data-wallet-balance` to all balance elements for easy targeting
|
- **Data Attributes**: Add `data-wallet-balance` to all balance elements for easy targeting
|
||||||
- **Continuous Workflow**: Allow multiple requests without page refresh ("Get Another" functionality)
|
- **Continuous Workflow**: Allow multiple requests without page refresh ("Get Another" functionality)
|
||||||
- **Clear User Feedback**: Show "payment processed" vs "no charge applied" messages
|
- **Clear User Feedback**: Show "payment processed" vs "no charge applied" messages
|
||||||
|
- **Standardized Layout**: Use agent-container grid layout (1fr 350px) with proper wallet positioning
|
||||||
|
- **Theme Consistency**: Apply unified CSS themes across all agents
|
||||||
|
- **JavaScript Isolation**: Agent-specific utilities for container-like functionality
|
||||||
|
|
||||||
|
#### Recent Standardization Improvements (2024)
|
||||||
|
|
||||||
|
**Agent Architecture Consistency:**
|
||||||
|
All 5 production agents now follow standardized patterns:
|
||||||
|
1. **Data Analyzer**: Reduced custom CSS from 400+ lines to ~78 lines, standardized HTML structure
|
||||||
|
2. **Job Posting Generator**: Enhanced with professional theme and proper grid layout
|
||||||
|
3. **Five Whys Analyzer**: Applied black and white theme with consistent styling
|
||||||
|
4. **Social Ads Generator**: Maintained creative theme while standardizing structure
|
||||||
|
5. **Weather Reporter**: Professional theme with clean weather data presentation
|
||||||
|
|
||||||
|
**Key Improvements Made:**
|
||||||
|
- **HTML Structure**: All agents use standard `agent-container` grid layout
|
||||||
|
- **CSS Consolidation**: Removed duplicate styles, standardized on `agent-base.css`
|
||||||
|
- **Wallet Positioning**: Fixed wallet appearing at bottom vs. right side across all agents
|
||||||
|
- **JavaScript Isolation**: Each agent has isolated utilities (DataAnalyzerUtils, WeatherUtils, etc.)
|
||||||
|
- **Theme Application**: Consistent theme implementation across all agents
|
||||||
|
- **Code Reduction**: Eliminated 400+ lines of redundant CSS code
|
||||||
|
|
||||||
#### Frontend JavaScript Requirements
|
#### Frontend JavaScript Requirements
|
||||||
```javascript
|
```javascript
|
||||||
|
|||||||
@ -1,35 +1,140 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% csrf_token %}
|
{% load static %}
|
||||||
|
|
||||||
{% block title %}5 Whys Analysis Agent - NetCop AI Hub{% endblock %}
|
{% block title %}5 Whys Analysis Agent - NetCop AI Hub{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_css %}
|
||||||
|
<!-- Optimized Font Loading -->
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
|
||||||
|
|
||||||
|
<!-- External Stylesheets -->
|
||||||
|
<link rel="stylesheet" href="{% static 'css/themes.css' %}">
|
||||||
|
<link rel="stylesheet" href="{% static 'css/agent-base.css' %}">
|
||||||
|
|
||||||
|
<!-- Five Whys Analyzer Specific Utilities -->
|
||||||
|
<script>
|
||||||
|
// Five Whys Analyzer - Self-contained utilities (no shared dependencies)
|
||||||
|
const FiveWhysUtils = {
|
||||||
|
/**
|
||||||
|
* Update wallet balance display - Five Whys specific
|
||||||
|
*/
|
||||||
|
updateWalletBalance(newBalance) {
|
||||||
|
// Update header balance (anchor tag with emoji)
|
||||||
|
const headerBalance = document.querySelector('a[data-wallet-balance]');
|
||||||
|
if (headerBalance) {
|
||||||
|
headerBalance.textContent = `💰 ${newBalance.toFixed(2)} AED`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update page balance (div without emoji)
|
||||||
|
const pageBalance = document.querySelector('div[data-wallet-balance]');
|
||||||
|
if (pageBalance) {
|
||||||
|
pageBalance.textContent = `${newBalance.toFixed(2)} AED`;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.currentWalletBalance = newBalance;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show toast notification with duplicate prevention
|
||||||
|
*/
|
||||||
|
showToast(message, type = 'info') {
|
||||||
|
// Prevent duplicate toasts
|
||||||
|
const existingToast = document.querySelector('.five-whys-toast');
|
||||||
|
if (existingToast) {
|
||||||
|
existingToast.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
const toast = document.createElement('div');
|
||||||
|
toast.className = 'five-whys-toast';
|
||||||
|
toast.style.cssText = `
|
||||||
|
position: fixed;
|
||||||
|
top: 16px;
|
||||||
|
right: 16px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: white;
|
||||||
|
font-size: 13px;
|
||||||
|
z-index: 1000;
|
||||||
|
max-width: 300px;
|
||||||
|
font-weight: 500;
|
||||||
|
${type === 'success' ? 'background: #10b981;' : 'background: #ef4444;'}
|
||||||
|
`;
|
||||||
|
toast.textContent = message;
|
||||||
|
document.body.appendChild(toast);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if (toast.parentNode) {
|
||||||
|
toast.remove();
|
||||||
|
}
|
||||||
|
}, 2000);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate text for copy/download functionality
|
||||||
|
*/
|
||||||
|
generateTextForExport(contentElementId) {
|
||||||
|
const content = document.getElementById(contentElementId);
|
||||||
|
if (content) {
|
||||||
|
return content.innerText || content.textContent || '';
|
||||||
|
}
|
||||||
|
return 'No content available';
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy content to clipboard
|
||||||
|
*/
|
||||||
|
copyToClipboard(text, successMessage = 'Content copied to clipboard!') {
|
||||||
|
navigator.clipboard.writeText(text).then(() => {
|
||||||
|
this.showToast(`📋 ${successMessage}`, 'success');
|
||||||
|
}).catch(() => {
|
||||||
|
this.showToast('Failed to copy content', 'error');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download content as text file
|
||||||
|
*/
|
||||||
|
downloadAsFile(text, filename, successMessage = 'File downloaded!') {
|
||||||
|
const blob = new Blob([text], { type: 'text/plain' });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = filename || `content-${Date.now()}.txt`;
|
||||||
|
a.click();
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
this.showToast(`💾 ${successMessage}`, 'success');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// For backward compatibility, create AgentUtils alias
|
||||||
|
const AgentUtils = FiveWhysUtils;
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container" style="max-width: 1280px; margin: 0 auto; padding: clamp(20px, 5vw, 40px) clamp(16px, 4vw, 24px);">
|
<div class="agent-page theme-professional">
|
||||||
<!-- Main Content Grid -->
|
<div class="agent-container">
|
||||||
<div class="main-grid" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(min(350px, 100%), 1fr)); gap: clamp(16px, 4vw, 24px); align-items: start;">
|
<!-- Main Content -->
|
||||||
<!-- Chat Interface -->
|
|
||||||
<div>
|
<div>
|
||||||
<!-- Agent Header -->
|
<div class="card">
|
||||||
<div class="card" style="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);">
|
<h3 class="section-title">🔍 {{ agent.name }}</h3>
|
||||||
<h1 style="font-size: clamp(20px, 5vw, 24px); font-weight: 700; color: #1f2937; margin: 0 0 8px 0; display: flex; align-items: center; gap: 12px;">
|
<p class="form-help">{{ agent.description }}</p>
|
||||||
🔍 {{ agent.name }}
|
|
||||||
</h1>
|
|
||||||
<p style="font-size: clamp(14px, 3.5vw, 16px); color: #6b7280; margin: 0;">
|
|
||||||
{{ agent.description }}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Chat Messages Container -->
|
<!-- Chat Messages Container -->
|
||||||
<div class="card" id="chatContainer" style="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); min-height: 400px; max-height: 600px; overflow-y: auto;">
|
<div class="card">
|
||||||
<h3 style="font-size: clamp(16px, 4vw, 18px); font-weight: 600; color: #1f2937; margin: 0 0 16px 0;">💬 Chat with 5 Whys Analyst</h3>
|
<div id="chatContainer" class="chat-container">
|
||||||
|
<h4 class="section-subtitle">💬 Chat with 5 Whys Analyst</h4>
|
||||||
|
|
||||||
<!-- Welcome Message -->
|
<!-- Welcome Message -->
|
||||||
<div class="message assistant-message" style="margin-bottom: 16px; padding: 12px 16px; background: #f8fafc; border-radius: 12px; border-left: 4px solid #6366f1;">
|
<div class="welcome-message">
|
||||||
<div style="font-weight: 600; color: #4338ca; margin-bottom: 4px;">5 Whys Analyst</div>
|
<div class="message-header">5 Whys Analyst</div>
|
||||||
<div style="color: #374151; line-height: 1.5;">
|
<div class="message-content">
|
||||||
Hello! I'm here to help you with root cause analysis using the 5 Whys methodology.
|
Hello! I'm here to help you with root cause analysis using the 5 Whys methodology.
|
||||||
|
|
||||||
You can ask me questions, describe your problem, and I'll guide you through the analysis process. When you're ready, I can generate a comprehensive report for 8.00 AED.
|
You can ask me questions, describe your problem, and I'll guide you through the analysis process. When you're ready, I can generate a comprehensive report for {{ agent.price }} AED.
|
||||||
|
|
||||||
How can I help you today?
|
How can I help you today?
|
||||||
</div>
|
</div>
|
||||||
@ -39,108 +144,97 @@
|
|||||||
<div id="chatMessages"></div>
|
<div id="chatMessages"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Chat Input -->
|
<!-- Chat Input Form -->
|
||||||
<div class="card" style="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);">
|
<form id="chatForm" class="chat-form">
|
||||||
<div style="display: flex; gap: 12px; align-items: flex-end;">
|
{% csrf_token %}
|
||||||
<textarea
|
<div class="input-group">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
id="chatInput"
|
id="chatInput"
|
||||||
|
name="message"
|
||||||
|
class="form-input"
|
||||||
placeholder="Ask me about your problem or describe what you'd like to analyze..."
|
placeholder="Ask me about your problem or describe what you'd like to analyze..."
|
||||||
style="flex: 1; padding: 12px 16px; border: 2px solid #e5e7eb; border-radius: 12px; font-size: 14px; resize: vertical; min-height: 48px; max-height: 120px; font-family: inherit;"
|
required
|
||||||
rows="2"
|
/>
|
||||||
></textarea>
|
|
||||||
<button
|
<button
|
||||||
id="sendChatBtn"
|
id="sendChatBtn"
|
||||||
onclick="sendChatMessage()"
|
type="submit"
|
||||||
style="padding: 12px 20px; background: linear-gradient(135deg, #6366f1 0%, #4338ca 100%); color: white; border: none; border-radius: 12px; font-weight: 600; cursor: pointer; min-height: 48px; transition: transform 0.1s ease;"
|
class="btn btn-primary"
|
||||||
>
|
>
|
||||||
Send
|
📤 Send
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Report Generation Form -->
|
<!-- Report Generation Section -->
|
||||||
<div class="card" id="reportForm" style="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);">
|
<div class="card" id="reportSection">
|
||||||
<h3 style="font-size: clamp(16px, 4vw, 18px); font-weight: 600; color: #1f2937; margin: 0 0 16px 0;">📋 Generate Final Report</h3>
|
<h4 class="section-subtitle">📋 Generate Final Report</h4>
|
||||||
|
|
||||||
<div id="reportNotReady" style="padding: 16px; background: #f3f4f6; border-radius: 12px; text-align: center; color: #6b7280; font-size: 14px; margin-bottom: 16px;">
|
<div id="reportNotReady" class="info-message">
|
||||||
💬 Ask 2-3 questions about your problem first, then I'll generate a comprehensive report
|
💬 Ask 2-3 questions about your problem first, then I'll generate a comprehensive report
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="reportReady" style="display: none; padding: 16px; background: #ecfdf5; border-radius: 12px; text-align: center; color: #059669; font-size: 14px; margin-bottom: 16px;">
|
<div id="reportReady" class="success-message" style="display: none;">
|
||||||
✅ Ready! I can now generate a detailed 5 Whys analysis report based on our conversation
|
✅ Ready! I can now generate a detailed 5 Whys analysis report based on our conversation
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
id="generateReportBtn"
|
id="generateReportBtn"
|
||||||
onclick="generateReport()"
|
onclick="generateReport()"
|
||||||
|
class="btn btn-primary"
|
||||||
disabled
|
disabled
|
||||||
style="width: 100%; padding: 16px 20px; background: #9ca3af; color: white; border: none; border-radius: 12px; font-weight: 600; cursor: not-allowed; font-size: 16px; transition: all 0.3s ease;"
|
style="width: 100%;"
|
||||||
>
|
>
|
||||||
🔍 Generate Report ({{ agent.price }} AED)
|
🔍 Generate Report ({{ agent.price }} AED)
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Generated Report Display -->
|
<!-- Generated Report Display -->
|
||||||
<div id="reportResults" class="card" style="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; display: none;">
|
<div id="reportResults" class="results-card" style="display: none;">
|
||||||
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 20px;">
|
<div class="results-header">
|
||||||
<div style="font-size: 24px;">✅</div>
|
<div style="font-size: 24px;">📊</div>
|
||||||
<h3 style="font-size: 20px; font-weight: 600; color: #1f2937; margin: 0;">5 Whys Analysis Report</h3>
|
<h3 style="font-size: 20px; font-weight: 600; color: var(--text-primary); margin: 0;">5 Whys Analysis Report</h3>
|
||||||
<div style="background: #6366f1; color: white; padding: 6px 12px; border-radius: 6px; font-size: 14px; font-weight: 600; margin-left: auto;">
|
<div style="background: var(--primary-color); color: white; padding: 6px 12px; border-radius: 6px; font-size: 14px; font-weight: 600; margin-left: auto;">✅ Complete</div>
|
||||||
✅ Complete
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="reportContent" style="background: white; border: 1px solid #e2e8f0; border-radius: 12px; padding: 32px; margin-bottom: 20px; line-height: 1.7; color: #374151; font-size: 15px; box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.05);">
|
<div class="results-content" id="reportContent">
|
||||||
<!-- Report content will be displayed here -->
|
<!-- Report content will be displayed here -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="display: flex; gap: 12px; flex-wrap: wrap;">
|
<div class="action-buttons">
|
||||||
<button onclick="copyReport()" style="padding: 12px 20px; background: linear-gradient(135deg, #6366f1 0%, #4338ca 100%); color: white; border: none; border-radius: 12px; font-weight: 600; cursor: pointer; transition: transform 0.1s ease;">
|
<button onclick="copyReport()" class="btn btn-primary">📋 Copy Report</button>
|
||||||
📋 Copy Report
|
<button onclick="downloadReport()" class="btn btn-secondary">💾 Download Report</button>
|
||||||
</button>
|
|
||||||
<button onclick="downloadReport()" style="padding: 12px 20px; background: white; color: #374151; border: 2px solid #e5e7eb; border-radius: 12px; font-weight: 600; cursor: pointer; transition: transform 0.1s ease;">
|
|
||||||
💾 Download Report
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Wallet Sidebar -->
|
<!-- Wallet Sidebar -->
|
||||||
<div style="position: sticky; top: 20px;">
|
<div class="wallet-section">
|
||||||
<div class="card" style="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);">
|
<div class="card">
|
||||||
<h3 style="font-size: clamp(16px, 4vw, 18px); font-weight: 600; color: #1f2937; margin: 0 0 16px 0;">💳 Your Wallet</h3>
|
<h3 class="section-title">💳 Your Wallet</h3>
|
||||||
|
|
||||||
<div style="font-size: clamp(24px, 6vw, 28px); font-weight: 700; color: #1f2937; margin-bottom: 8px;" data-wallet-balance>
|
<div class="wallet-balance" data-wallet-balance>{% if user.is_authenticated %}{{ user.wallet_balance|floatformat:2 }} AED{% else %}0.00 AED{% endif %}</div>
|
||||||
{{ user.wallet_balance|floatformat:2 }} AED
|
<div class="balance-label">Available Balance</div>
|
||||||
</div>
|
|
||||||
<div style="font-size: clamp(14px, 3.5vw, 16px); color: #6b7280; margin-bottom: 20px;">
|
|
||||||
Available Balance
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<a href="{% url 'core:wallet_topup' %}" style="display: block; width: 100%; padding: 16px 20px; background: linear-gradient(135deg, #10b981 0%, #059669 100%); color: white; border: none; border-radius: 12px; font-weight: 600; text-decoration: none; text-align: center; margin-bottom: 12px;">
|
{% if user.is_authenticated %}
|
||||||
💳 Top Up Wallet
|
<a href="{% url 'core:wallet' %}" class="btn btn-primary" style="text-decoration: none; margin-top: 16px;">
|
||||||
|
💰 Top Up Wallet
|
||||||
</a>
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{% url 'authentication:login' %}" class="btn btn-primary process-btn" style="text-decoration: none;">
|
||||||
|
🔑 Login to Continue
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="padding: 16px; background: rgba(99, 102, 241, 0.1); border-radius: 12px; border: 1px solid rgba(99, 102, 241, 0.2);">
|
<div class="usage-info">
|
||||||
<h4 style="margin: 0 0 8px 0; font-size: 14px; font-weight: 600; color: #4338ca;">💡 How it works</h4>
|
<h4>💡 How it works</h4>
|
||||||
<ul style="margin: 0; font-size: 12px; color: #374151; line-height: 1.4; list-style: none; padding-left: 0;">
|
<ul>
|
||||||
<li style="margin: 4px 0; padding-left: 16px; position: relative;">
|
<li>Chat freely to explore your problem</li>
|
||||||
<span style="position: absolute; left: 0; color: #6366f1;">•</span>
|
<li>Get guidance and ask questions</li>
|
||||||
Chat freely to explore your problem
|
<li>Generate final report when ready</li>
|
||||||
</li>
|
<li>Pay only for the final report</li>
|
||||||
<li style="margin: 4px 0; padding-left: 16px; position: relative;">
|
|
||||||
<span style="position: absolute; left: 0; color: #6366f1;">•</span>
|
|
||||||
Get guidance and ask questions
|
|
||||||
</li>
|
|
||||||
<li style="margin: 4px 0; padding-left: 16px; position: relative;">
|
|
||||||
<span style="position: absolute; left: 0; color: #6366f1;">•</span>
|
|
||||||
Generate final report when ready
|
|
||||||
</li>
|
|
||||||
<li style="margin: 4px 0; padding-left: 16px; position: relative;">
|
|
||||||
<span style="position: absolute; left: 0; color: #6366f1;">•</span>
|
|
||||||
Pay only for the final report
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -148,25 +242,95 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.container {
|
.chat-container {
|
||||||
background: linear-gradient(135deg, #f6f8ff 0%, #e8f0fe 50%, #f0f7ff 100%);
|
background: var(--background-subtle);
|
||||||
min-height: 100vh;
|
border-radius: 8px;
|
||||||
color: #1f2937;
|
padding: 16px;
|
||||||
|
margin: 16px 0;
|
||||||
|
min-height: 400px;
|
||||||
|
max-height: 600px;
|
||||||
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card:hover {
|
.welcome-message {
|
||||||
transform: translateY(-1px);
|
margin-bottom: 16px;
|
||||||
transition: transform 0.2s ease;
|
padding: 16px 20px;
|
||||||
|
background: var(--background-subtle);
|
||||||
|
border-radius: 16px 16px 16px 4px;
|
||||||
|
border-left: 4px solid var(--primary-color);
|
||||||
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
button:hover {
|
.message-header {
|
||||||
transform: translateY(-1px);
|
font-weight: 600;
|
||||||
|
color: var(--primary-color);
|
||||||
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
button:disabled {
|
.message-content {
|
||||||
background: #9ca3af !important;
|
color: var(--text-primary);
|
||||||
cursor: not-allowed !important;
|
line-height: 1.6;
|
||||||
transform: none !important;
|
}
|
||||||
|
|
||||||
|
.chat-form {
|
||||||
|
border-top: 1px solid var(--border-color);
|
||||||
|
padding-top: 16px;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group .form-input {
|
||||||
|
width: 80%;
|
||||||
|
height: 40px;
|
||||||
|
border: 2px solid var(--border-color);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 14px;
|
||||||
|
background: var(--background-primary);
|
||||||
|
color: var(--text-primary);
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group .form-input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--primary-color);
|
||||||
|
box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group .btn {
|
||||||
|
height: 40px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
width: 20%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-message {
|
||||||
|
padding: 16px;
|
||||||
|
background: var(--background-subtle);
|
||||||
|
border-radius: 8px;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-message {
|
||||||
|
padding: 16px;
|
||||||
|
background: #ecfdf5;
|
||||||
|
border-radius: 8px;
|
||||||
|
text-align: center;
|
||||||
|
color: #059669;
|
||||||
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message {
|
.message {
|
||||||
@ -177,7 +341,7 @@
|
|||||||
.user-message {
|
.user-message {
|
||||||
margin-left: 20%;
|
margin-left: 20%;
|
||||||
padding: 12px 16px;
|
padding: 12px 16px;
|
||||||
background: #6366f1;
|
background: var(--primary-color);
|
||||||
color: white;
|
color: white;
|
||||||
border-radius: 16px 16px 4px 16px;
|
border-radius: 16px 16px 4px 16px;
|
||||||
}
|
}
|
||||||
@ -185,19 +349,19 @@
|
|||||||
.assistant-message {
|
.assistant-message {
|
||||||
margin-right: 20%;
|
margin-right: 20%;
|
||||||
padding: 16px 20px;
|
padding: 16px 20px;
|
||||||
background: #f8fafc;
|
background: var(--background-subtle);
|
||||||
border-radius: 16px 16px 16px 4px;
|
border-radius: 16px 16px 16px 4px;
|
||||||
border-left: 4px solid #6366f1;
|
border-left: 4px solid var(--primary-color);
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.assistant-message .message-content {
|
.assistant-message .message-content {
|
||||||
color: #374151;
|
color: var(--text-primary);
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.assistant-message .message-content h3 {
|
.assistant-message .message-content h3 {
|
||||||
color: #1f2937;
|
color: var(--text-primary);
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin: 16px 0 8px 0;
|
margin: 16px 0 8px 0;
|
||||||
@ -229,10 +393,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.assistant-message .message-content strong {
|
.assistant-message .message-content strong {
|
||||||
color: #1f2937;
|
color: var(--text-primary);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes fadeIn {
|
@keyframes fadeIn {
|
||||||
from { opacity: 0; transform: translateY(10px); }
|
from { opacity: 0; transform: translateY(10px); }
|
||||||
to { opacity: 1; transform: translateY(0); }
|
to { opacity: 1; transform: translateY(0); }
|
||||||
@ -245,40 +408,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.typing-dots span {
|
.typing-dots span {
|
||||||
width: 6px;
|
width: 8px;
|
||||||
height: 6px;
|
height: 8px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: #6366f1;
|
background: var(--primary-color);
|
||||||
animation: typingDots 1.4s infinite ease-in-out;
|
animation: typing 1.4s infinite ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.typing-dots span:nth-child(1) {
|
.typing-dots span:nth-child(1) { animation-delay: -0.32s; }
|
||||||
animation-delay: 0s;
|
.typing-dots span:nth-child(2) { animation-delay: -0.16s; }
|
||||||
}
|
|
||||||
|
|
||||||
.typing-dots span:nth-child(2) {
|
@keyframes typing {
|
||||||
animation-delay: 0.2s;
|
0%, 80%, 100% { transform: scale(0); }
|
||||||
}
|
40% { transform: scale(1); }
|
||||||
|
|
||||||
.typing-dots span:nth-child(3) {
|
|
||||||
animation-delay: 0.4s;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes typingDots {
|
|
||||||
0%, 80%, 100% {
|
|
||||||
transform: scale(0);
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
40% {
|
|
||||||
transform: scale(1);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.main-grid {
|
|
||||||
grid-template-columns: 1fr !important;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
@ -292,8 +434,14 @@
|
|||||||
// Generate new session ID
|
// Generate new session ID
|
||||||
currentSessionId = generateSessionId();
|
currentSessionId = generateSessionId();
|
||||||
|
|
||||||
// Add Enter key support for chat input
|
// Handle chat form submission
|
||||||
document.getElementById('chatInput').addEventListener('keypress', function(e) {
|
document.getElementById('chatForm').addEventListener('submit', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
sendChatMessage();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add Enter key support for chat input (Shift+Enter for new line)
|
||||||
|
document.getElementById('chatInput').addEventListener('keydown', function(e) {
|
||||||
if (e.key === 'Enter' && !e.shiftKey) {
|
if (e.key === 'Enter' && !e.shiftKey) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
sendChatMessage();
|
sendChatMessage();
|
||||||
@ -507,8 +655,6 @@
|
|||||||
|
|
||||||
const btn = document.getElementById('generateReportBtn');
|
const btn = document.getElementById('generateReportBtn');
|
||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
btn.style.background = 'linear-gradient(135deg, #10b981 0%, #059669 100%)';
|
|
||||||
btn.style.cursor = 'pointer';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -559,8 +705,10 @@
|
|||||||
// Display the generated report
|
// Display the generated report
|
||||||
displayReport(data.report);
|
displayReport(data.report);
|
||||||
|
|
||||||
// Update wallet balance
|
// Update wallet balance if provided
|
||||||
|
if (data.wallet_balance !== undefined) {
|
||||||
AgentUtils.updateWalletBalance(data.wallet_balance);
|
AgentUtils.updateWalletBalance(data.wallet_balance);
|
||||||
|
}
|
||||||
|
|
||||||
AgentUtils.showToast('✅ Report generated and payment processed!', 'success');
|
AgentUtils.showToast('✅ Report generated and payment processed!', 'success');
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -13,8 +13,104 @@
|
|||||||
<link rel="stylesheet" href="{% static 'css/themes.css' %}">
|
<link rel="stylesheet" href="{% static 'css/themes.css' %}">
|
||||||
<link rel="stylesheet" href="{% static 'css/agent-base.css' %}">
|
<link rel="stylesheet" href="{% static 'css/agent-base.css' %}">
|
||||||
|
|
||||||
<!-- Shared JavaScript Utilities -->
|
<!-- Job Posting Generator Specific Utilities -->
|
||||||
<script src="{% static 'js/agent-utils.js' %}"></script>
|
<script>
|
||||||
|
// Job Posting Generator - Self-contained utilities (no shared dependencies)
|
||||||
|
const JobPostingUtils = {
|
||||||
|
/**
|
||||||
|
* Update wallet balance display - Job Posting Generator specific
|
||||||
|
*/
|
||||||
|
updateWalletBalance(newBalance) {
|
||||||
|
// Update header balance (anchor tag with emoji)
|
||||||
|
const headerBalance = document.querySelector('a[data-wallet-balance]');
|
||||||
|
if (headerBalance) {
|
||||||
|
headerBalance.textContent = `💰 ${newBalance.toFixed(2)} AED`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update page balance (div without emoji)
|
||||||
|
const pageBalance = document.querySelector('div[data-wallet-balance]');
|
||||||
|
if (pageBalance) {
|
||||||
|
pageBalance.textContent = `${newBalance.toFixed(2)} AED`;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.currentWalletBalance = newBalance;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show toast notification with duplicate prevention
|
||||||
|
*/
|
||||||
|
showToast(message, type = 'info') {
|
||||||
|
// Prevent duplicate toasts
|
||||||
|
const existingToast = document.querySelector('.job-posting-toast');
|
||||||
|
if (existingToast) {
|
||||||
|
existingToast.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
const toast = document.createElement('div');
|
||||||
|
toast.className = 'job-posting-toast';
|
||||||
|
toast.style.cssText = `
|
||||||
|
position: fixed;
|
||||||
|
top: 16px;
|
||||||
|
right: 16px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: white;
|
||||||
|
font-size: 13px;
|
||||||
|
z-index: 1000;
|
||||||
|
max-width: 300px;
|
||||||
|
font-weight: 500;
|
||||||
|
${type === 'success' ? 'background: #10b981;' : 'background: #ef4444;'}
|
||||||
|
`;
|
||||||
|
toast.textContent = message;
|
||||||
|
document.body.appendChild(toast);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if (toast.parentNode) {
|
||||||
|
toast.remove();
|
||||||
|
}
|
||||||
|
}, 2000);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate text for copy/download functionality
|
||||||
|
*/
|
||||||
|
generateTextForExport(contentElementId) {
|
||||||
|
const content = document.getElementById(contentElementId);
|
||||||
|
if (content) {
|
||||||
|
return content.innerText || content.textContent || '';
|
||||||
|
}
|
||||||
|
return 'No content available';
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy content to clipboard
|
||||||
|
*/
|
||||||
|
copyToClipboard(text, successMessage = 'Content copied to clipboard!') {
|
||||||
|
navigator.clipboard.writeText(text).then(() => {
|
||||||
|
this.showToast(`📋 ${successMessage}`, 'success');
|
||||||
|
}).catch(() => {
|
||||||
|
this.showToast('Failed to copy content', 'error');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download content as text file
|
||||||
|
*/
|
||||||
|
downloadAsFile(text, filename, successMessage = 'File downloaded!') {
|
||||||
|
const blob = new Blob([text], { type: 'text/plain' });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = filename || `content-${Date.now()}.txt`;
|
||||||
|
a.click();
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
this.showToast(`💾 ${successMessage}`, 'success');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// For backward compatibility, create AgentUtils alias
|
||||||
|
const AgentUtils = JobPostingUtils;
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|||||||
@ -13,8 +13,129 @@
|
|||||||
<link rel="stylesheet" href="{% static 'css/themes.css' %}">
|
<link rel="stylesheet" href="{% static 'css/themes.css' %}">
|
||||||
<link rel="stylesheet" href="{% static 'css/agent-base.css' %}">
|
<link rel="stylesheet" href="{% static 'css/agent-base.css' %}">
|
||||||
|
|
||||||
<!-- Shared JavaScript Utilities -->
|
<!-- Social Ads Generator Specific Utilities -->
|
||||||
<script src="{% static 'js/agent-utils.js' %}"></script>
|
<script>
|
||||||
|
// Social Ads Generator - Self-contained utilities (no shared dependencies)
|
||||||
|
const SocialAdsUtils = {
|
||||||
|
/**
|
||||||
|
* Update wallet balance display - Social Ads specific
|
||||||
|
*/
|
||||||
|
updateWalletBalance(newBalance) {
|
||||||
|
// Update header balance (anchor tag with emoji)
|
||||||
|
const headerBalance = document.querySelector('a[data-wallet-balance]');
|
||||||
|
if (headerBalance) {
|
||||||
|
headerBalance.textContent = `💰 ${newBalance.toFixed(2)} AED`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update page balance (div without emoji)
|
||||||
|
const pageBalance = document.querySelector('div[data-wallet-balance]');
|
||||||
|
if (pageBalance) {
|
||||||
|
pageBalance.textContent = `${newBalance.toFixed(2)} AED`;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.currentWalletBalance = newBalance;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show toast notification with duplicate prevention
|
||||||
|
*/
|
||||||
|
showToast(message, type = 'info') {
|
||||||
|
// Prevent duplicate toasts
|
||||||
|
const existingToast = document.querySelector('.social-ads-toast');
|
||||||
|
if (existingToast) {
|
||||||
|
existingToast.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
const toast = document.createElement('div');
|
||||||
|
toast.className = 'social-ads-toast';
|
||||||
|
toast.style.cssText = `
|
||||||
|
position: fixed;
|
||||||
|
top: 16px;
|
||||||
|
right: 16px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: white;
|
||||||
|
font-size: 13px;
|
||||||
|
z-index: 1000;
|
||||||
|
max-width: 300px;
|
||||||
|
font-weight: 500;
|
||||||
|
${type === 'success' ? 'background: #10b981;' : 'background: #ef4444;'}
|
||||||
|
`;
|
||||||
|
toast.textContent = message;
|
||||||
|
document.body.appendChild(toast);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if (toast.parentNode) {
|
||||||
|
toast.remove();
|
||||||
|
}
|
||||||
|
}, 2000);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate text for copy/download functionality
|
||||||
|
*/
|
||||||
|
generateTextForExport(contentElementId) {
|
||||||
|
const content = document.getElementById(contentElementId);
|
||||||
|
if (content) {
|
||||||
|
return content.innerText || content.textContent || '';
|
||||||
|
}
|
||||||
|
return 'No content available';
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy content to clipboard
|
||||||
|
*/
|
||||||
|
copyToClipboard(text, successMessage = 'Content copied to clipboard!') {
|
||||||
|
navigator.clipboard.writeText(text).then(() => {
|
||||||
|
this.showToast(`📋 ${successMessage}`, 'success');
|
||||||
|
}).catch(() => {
|
||||||
|
this.showToast('Failed to copy content', 'error');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download content as text file
|
||||||
|
*/
|
||||||
|
downloadAsFile(text, filename, successMessage = 'File downloaded!') {
|
||||||
|
const blob = new Blob([text], { type: 'text/plain' });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = filename || `content-${Date.now()}.txt`;
|
||||||
|
a.click();
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
this.showToast(`💾 ${successMessage}`, 'success');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset UI to initial state - Social Ads specific
|
||||||
|
*/
|
||||||
|
resetUI(config) {
|
||||||
|
const elements = {
|
||||||
|
processingStatus: document.getElementById(config.processingStatusId || 'processingStatus'),
|
||||||
|
processButton: document.getElementById(config.processButtonId || 'processButton'),
|
||||||
|
results: document.getElementById(config.resultsId)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (elements.processingStatus) {
|
||||||
|
elements.processingStatus.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elements.processButton) {
|
||||||
|
elements.processButton.disabled = false;
|
||||||
|
elements.processButton.innerHTML = config.buttonText || 'Process';
|
||||||
|
elements.processButton.classList.remove('loading');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elements.results) {
|
||||||
|
elements.results.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// For backward compatibility, create AgentUtils alias
|
||||||
|
const AgentUtils = SocialAdsUtils;
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|||||||
@ -80,11 +80,23 @@ window.AgentUtils = {
|
|||||||
* Update wallet balance display across all agents
|
* Update wallet balance display across all agents
|
||||||
*/
|
*/
|
||||||
updateWalletBalance(newBalance) {
|
updateWalletBalance(newBalance) {
|
||||||
const balanceElement = document.querySelector('[data-wallet-balance]') ||
|
// Update all wallet balance elements
|
||||||
document.getElementById('walletBalance');
|
document.querySelectorAll('[data-wallet-balance]').forEach(element => {
|
||||||
if (balanceElement) {
|
if (element.tagName === 'A') {
|
||||||
balanceElement.textContent = `${newBalance.toFixed(2)} AED`;
|
// Header balance with emoji (anchor tag)
|
||||||
|
element.textContent = `💰 ${newBalance.toFixed(2)} AED`;
|
||||||
|
} else {
|
||||||
|
// Page balance without emoji (div or other elements)
|
||||||
|
element.textContent = `${newBalance.toFixed(2)} AED`;
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fallback for old ID-based elements
|
||||||
|
const legacyElement = document.getElementById('walletBalance');
|
||||||
|
if (legacyElement) {
|
||||||
|
legacyElement.textContent = `${newBalance.toFixed(2)} AED`;
|
||||||
|
}
|
||||||
|
|
||||||
window.currentWalletBalance = newBalance;
|
window.currentWalletBalance = newBalance;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -13,8 +13,104 @@
|
|||||||
<link rel="stylesheet" href="{% static 'css/themes.css' %}">
|
<link rel="stylesheet" href="{% static 'css/themes.css' %}">
|
||||||
<link rel="stylesheet" href="{% static 'css/agent-base.css' %}">
|
<link rel="stylesheet" href="{% static 'css/agent-base.css' %}">
|
||||||
|
|
||||||
<!-- Shared JavaScript Utilities -->
|
<!-- Weather Reporter Specific Utilities -->
|
||||||
<script src="{% static 'js/agent-utils.js' %}"></script>
|
<script>
|
||||||
|
// Weather Reporter - Self-contained utilities (no shared dependencies)
|
||||||
|
const WeatherUtils = {
|
||||||
|
/**
|
||||||
|
* Update wallet balance display - Weather Reporter specific
|
||||||
|
*/
|
||||||
|
updateWalletBalance(newBalance) {
|
||||||
|
// Update header balance (anchor tag with emoji)
|
||||||
|
const headerBalance = document.querySelector('a[data-wallet-balance]');
|
||||||
|
if (headerBalance) {
|
||||||
|
headerBalance.textContent = `💰 ${newBalance.toFixed(2)} AED`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update page balance (div without emoji)
|
||||||
|
const pageBalance = document.querySelector('div[data-wallet-balance]');
|
||||||
|
if (pageBalance) {
|
||||||
|
pageBalance.textContent = `${newBalance.toFixed(2)} AED`;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.currentWalletBalance = newBalance;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show toast notification with duplicate prevention
|
||||||
|
*/
|
||||||
|
showToast(message, type = 'info') {
|
||||||
|
// Prevent duplicate toasts
|
||||||
|
const existingToast = document.querySelector('.weather-toast');
|
||||||
|
if (existingToast) {
|
||||||
|
existingToast.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
const toast = document.createElement('div');
|
||||||
|
toast.className = 'weather-toast';
|
||||||
|
toast.style.cssText = `
|
||||||
|
position: fixed;
|
||||||
|
top: 16px;
|
||||||
|
right: 16px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: white;
|
||||||
|
font-size: 13px;
|
||||||
|
z-index: 1000;
|
||||||
|
max-width: 300px;
|
||||||
|
font-weight: 500;
|
||||||
|
${type === 'success' ? 'background: #10b981;' : 'background: #ef4444;'}
|
||||||
|
`;
|
||||||
|
toast.textContent = message;
|
||||||
|
document.body.appendChild(toast);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if (toast.parentNode) {
|
||||||
|
toast.remove();
|
||||||
|
}
|
||||||
|
}, 2000);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate text for copy/download functionality
|
||||||
|
*/
|
||||||
|
generateTextForExport(contentElementId) {
|
||||||
|
const content = document.getElementById(contentElementId);
|
||||||
|
if (content) {
|
||||||
|
return content.innerText || content.textContent || '';
|
||||||
|
}
|
||||||
|
return 'No content available';
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy content to clipboard
|
||||||
|
*/
|
||||||
|
copyToClipboard(text, successMessage = 'Content copied to clipboard!') {
|
||||||
|
navigator.clipboard.writeText(text).then(() => {
|
||||||
|
this.showToast(`📋 ${successMessage}`, 'success');
|
||||||
|
}).catch(() => {
|
||||||
|
this.showToast('Failed to copy content', 'error');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download content as text file
|
||||||
|
*/
|
||||||
|
downloadAsFile(text, filename, successMessage = 'File downloaded!') {
|
||||||
|
const blob = new Blob([text], { type: 'text/plain' });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = filename || `content-${Date.now()}.txt`;
|
||||||
|
a.click();
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
this.showToast(`💾 ${successMessage}`, 'success');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// For backward compatibility, create AgentUtils alias
|
||||||
|
const AgentUtils = WeatherUtils;
|
||||||
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* Custom styles for weather reporter radio grid */
|
/* Custom styles for weather reporter radio grid */
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user