mirror of
https://github.com/thecyberlearn/quantum-ai-v3.git
synced 2026-08-18 22:52:58 +00:00
Major fixes:
- Weather Reporter: Restore correct API-based processing pattern
* Fix form submission URL to use /agents/weather-reporter/process/
* Remove incorrect polling logic (API agents return immediate results)
* Fix response field mapping to use formatted_report instead of analysis_text
* Implement immediate response display without polling
- Agent CSS System: Enhance unified styling system
* Add missing CSS classes for status display (.status-title, .status-subtitle)
* Add enhanced typography for results content formatting
* Include modern info boxes (.key-points, .insights, .summary)
* Fix animation keyframes for proper loading states
- Template Consistency: Update agents to use shared components
* Job Posting Generator: Add cache-busted CSS and shared header/panel
* Social Ads Generator: Add unified CSS link and shared components
* Create component templates for consistent agent layouts
Key architectural insight: Weather Reporter is API-based (immediate response)
while Data Analyzer is webhook-based (async polling). Fixed Weather Reporter
to use correct pattern based on git history commit 82fc051.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
535 lines
22 KiB
HTML
535 lines
22 KiB
HTML
{% extends 'base.html' %}
|
||
{% load static %}
|
||
|
||
{% block title %}Weather Reporter Agent - NetCop AI Hub{% endblock %}
|
||
|
||
{% block extra_css %}
|
||
<link rel="stylesheet" href="{% static 'css/agent-base.css' %}?v={{ timestamp }}">
|
||
{% endblock %}
|
||
|
||
{% block content %}
|
||
<script>
|
||
// Consolidated DOMContentLoaded initialization
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
// Set data for JavaScript access
|
||
document.body.setAttribute('data-user-authenticated', '{{ user.is_authenticated|yesno:"true,false" }}');
|
||
document.body.setAttribute('data-agent-price', '2.00');
|
||
|
||
// Initialize form submission
|
||
const form = document.getElementById('weatherForm');
|
||
if (form) {
|
||
form.addEventListener('submit', handleFormSubmission);
|
||
}
|
||
|
||
// Set initial radio selection
|
||
const firstRadio = document.querySelector('.radio-card');
|
||
if (firstRadio && !document.querySelector('.radio-card.selected')) {
|
||
firstRadio.classList.add('selected');
|
||
const input = firstRadio.querySelector('input[type="radio"]');
|
||
if (input) input.checked = true;
|
||
}
|
||
});
|
||
|
||
// Weather Reporter Utils
|
||
const WeatherUtils = {
|
||
// Update wallet balance display
|
||
updateWalletBalance(newBalance) {
|
||
if (newBalance !== undefined) {
|
||
// Update header balance
|
||
const headerBalance = document.querySelector('a[data-wallet-balance]');
|
||
if (headerBalance) {
|
||
headerBalance.textContent = `💰 ${newBalance.toFixed(2)} AED`;
|
||
}
|
||
|
||
// Update page balance
|
||
const pageBalance = document.getElementById('walletBalance');
|
||
if (pageBalance) {
|
||
pageBalance.textContent = newBalance.toFixed(2);
|
||
}
|
||
|
||
// Update all data attributes
|
||
document.querySelectorAll('[data-wallet-balance]').forEach(element => {
|
||
element.textContent = `${newBalance.toFixed(2)} AED`;
|
||
});
|
||
|
||
// Store current balance globally
|
||
window.currentWalletBalance = newBalance;
|
||
}
|
||
},
|
||
|
||
// Show toast notification
|
||
showToast(message, type = 'info') {
|
||
// Remove existing toasts
|
||
document.querySelectorAll('.toast').forEach(toast => toast.remove());
|
||
|
||
// Create new toast
|
||
const toast = document.createElement('div');
|
||
toast.className = `toast ${type}`;
|
||
toast.textContent = message;
|
||
|
||
// Add to page
|
||
document.body.appendChild(toast);
|
||
|
||
// Show toast
|
||
setTimeout(() => toast.classList.add('show'), 100);
|
||
|
||
// Auto remove after 3 seconds
|
||
setTimeout(() => {
|
||
toast.classList.remove('show');
|
||
setTimeout(() => toast.remove(), 300);
|
||
}, 3000);
|
||
},
|
||
|
||
// Display weather results (API-based immediate response)
|
||
displayResults(result) {
|
||
const resultsContainer = document.getElementById('resultsContainer');
|
||
const resultsContent = document.getElementById('resultsContent');
|
||
const processingStatus = document.getElementById('processingStatus');
|
||
|
||
if (result.success) {
|
||
// Hide processing status
|
||
if (processingStatus) processingStatus.style.display = 'none';
|
||
|
||
// Show results using API response fields
|
||
const weatherReport = result.formatted_report || result.content || 'Weather data received successfully.';
|
||
resultsContent.innerHTML = this.parseMarkdown(weatherReport);
|
||
resultsContainer.style.display = 'block';
|
||
|
||
// Update wallet balance if provided
|
||
if (result.wallet_balance !== undefined) {
|
||
this.updateWalletBalance(result.wallet_balance);
|
||
}
|
||
|
||
this.showToast('✅ Weather report generated successfully!', 'success');
|
||
} else if (result.error) {
|
||
if (processingStatus) processingStatus.style.display = 'none';
|
||
this.showToast(`❌ Error: ${result.error}`, 'error');
|
||
} else {
|
||
if (processingStatus) processingStatus.style.display = 'none';
|
||
this.showToast('❌ Failed to get weather data. Please try again.', 'error');
|
||
}
|
||
},
|
||
|
||
// Simple text formatting
|
||
parseMarkdown(text) {
|
||
if (!text) return '';
|
||
return text
|
||
.replace(/\*\*/g, '') // Remove markdown bold syntax
|
||
.replace(/\#{1,3}\s/g, '') // Remove header syntax
|
||
.replace(/\n{3,}/g, '\n\n') // Reduce excessive line breaks
|
||
.replace(/\n/g, '<br>') // Convert line breaks to HTML
|
||
.trim();
|
||
},
|
||
|
||
// Weather Reporter uses immediate API response - no polling needed
|
||
};
|
||
|
||
// For backward compatibility
|
||
const AgentUtils = WeatherUtils;
|
||
|
||
// Modern Radio Selection
|
||
function selectRadio(value) {
|
||
// Remove selected class from all cards
|
||
document.querySelectorAll('.radio-card').forEach(card => {
|
||
card.classList.remove('selected');
|
||
});
|
||
|
||
// Add selected class to clicked card
|
||
const selectedCard = document.querySelector(`input[value="${value}"]`).closest('.radio-card');
|
||
if (selectedCard) {
|
||
selectedCard.classList.add('selected');
|
||
}
|
||
|
||
// Select the radio button
|
||
const radioInput = document.getElementById(value);
|
||
if (radioInput) {
|
||
radioInput.checked = true;
|
||
}
|
||
}
|
||
|
||
// Form validation
|
||
function isFormValid() {
|
||
const location = document.getElementById('location')?.value?.trim();
|
||
const reportType = document.querySelector('input[name="report_type"]:checked');
|
||
|
||
return location && reportType;
|
||
}
|
||
|
||
// Copy weather report
|
||
function copyResults() {
|
||
const content = document.getElementById('resultsContent');
|
||
if (content) {
|
||
const text = content.innerText || content.textContent || '';
|
||
navigator.clipboard.writeText(text).then(() => {
|
||
WeatherUtils.showToast('📋 Weather report copied to clipboard!', 'success');
|
||
}).catch(() => {
|
||
WeatherUtils.showToast('❌ Failed to copy to clipboard', 'error');
|
||
});
|
||
}
|
||
}
|
||
|
||
// Download weather report
|
||
function downloadResults() {
|
||
const content = document.getElementById('resultsContent');
|
||
if (content) {
|
||
const text = content.innerText || content.textContent || '';
|
||
const blob = new Blob([text], { type: 'text/plain' });
|
||
const url = URL.createObjectURL(blob);
|
||
const a = document.createElement('a');
|
||
a.href = url;
|
||
a.download = 'weather-report.txt';
|
||
a.click();
|
||
URL.revokeObjectURL(url);
|
||
WeatherUtils.showToast('💾 Weather report downloaded!', 'success');
|
||
}
|
||
}
|
||
|
||
// Reset form for new request
|
||
function resetForm() {
|
||
const form = document.getElementById('weatherForm');
|
||
if (form) {
|
||
form.reset();
|
||
}
|
||
|
||
const resultsContainer = document.getElementById('resultsContainer');
|
||
const processingStatus = document.getElementById('processingStatus');
|
||
|
||
if (resultsContainer) resultsContainer.style.display = 'none';
|
||
if (processingStatus) processingStatus.style.display = 'none';
|
||
|
||
// Reset radio selection
|
||
document.querySelectorAll('.radio-card').forEach(card => {
|
||
card.classList.remove('selected');
|
||
});
|
||
const firstCard = document.querySelector('.radio-card');
|
||
if (firstCard) {
|
||
firstCard.classList.add('selected');
|
||
const input = firstCard.querySelector('input[type="radio"]');
|
||
if (input) input.checked = true;
|
||
}
|
||
|
||
WeatherUtils.showToast('🔄 Form reset - ready for new request', 'success');
|
||
}
|
||
|
||
// Quick Agent Access Functions
|
||
function toggleQuickAgents() {
|
||
const panel = document.getElementById('quickAgentsPanel');
|
||
const overlay = document.getElementById('quickAgentsOverlay');
|
||
const toggle = document.querySelector('.quick-agent-toggle');
|
||
|
||
if (!panel || !overlay) return;
|
||
|
||
const isActive = panel.classList.contains('active');
|
||
|
||
if (isActive) {
|
||
// Close panel
|
||
panel.classList.remove('active');
|
||
overlay.classList.remove('active');
|
||
if (toggle) toggle.classList.remove('active');
|
||
// Update ARIA attributes
|
||
if (toggle) toggle.setAttribute('aria-expanded', 'false');
|
||
panel.setAttribute('aria-hidden', 'true');
|
||
overlay.setAttribute('aria-hidden', 'true');
|
||
} else {
|
||
// Open panel
|
||
panel.classList.add('active');
|
||
overlay.classList.add('active');
|
||
if (toggle) toggle.classList.add('active');
|
||
// Update ARIA attributes
|
||
if (toggle) toggle.setAttribute('aria-expanded', 'true');
|
||
panel.setAttribute('aria-hidden', 'false');
|
||
overlay.setAttribute('aria-hidden', 'false');
|
||
}
|
||
}
|
||
|
||
function closeQuickAgents() {
|
||
const panel = document.getElementById('quickAgentsPanel');
|
||
const overlay = document.getElementById('quickAgentsOverlay');
|
||
const toggle = document.querySelector('.quick-agent-toggle');
|
||
|
||
if (panel) panel.classList.remove('active');
|
||
if (overlay) overlay.classList.remove('active');
|
||
if (toggle) toggle.classList.remove('active');
|
||
|
||
// Update ARIA attributes
|
||
if (toggle) toggle.setAttribute('aria-expanded', 'false');
|
||
if (panel) panel.setAttribute('aria-hidden', 'true');
|
||
if (overlay) overlay.setAttribute('aria-hidden', 'true');
|
||
}
|
||
|
||
// Form submission handler
|
||
function handleFormSubmission(e) {
|
||
e.preventDefault();
|
||
|
||
if (!isFormValid()) {
|
||
WeatherUtils.showToast('Please fill in all required fields', 'error');
|
||
return;
|
||
}
|
||
|
||
// Check authentication and balance
|
||
const isAuthenticated = document.body.getAttribute('data-user-authenticated') === 'true';
|
||
if (!isAuthenticated) {
|
||
WeatherUtils.showToast('Please login to continue', 'error');
|
||
window.location.href = "{% url 'authentication:login' %}";
|
||
return;
|
||
}
|
||
|
||
const walletBalance = parseFloat(document.getElementById('walletBalance')?.textContent) || 0;
|
||
if (walletBalance < 2.00) {
|
||
WeatherUtils.showToast('Insufficient wallet balance', 'error');
|
||
setTimeout(() => {
|
||
window.location.href = "{% url 'core:wallet' %}";
|
||
}, 2000);
|
||
return;
|
||
}
|
||
|
||
// Show processing status
|
||
const processingStatus = document.getElementById('processingStatus');
|
||
const resultsContainer = document.getElementById('resultsContainer');
|
||
|
||
if (processingStatus) processingStatus.style.display = 'block';
|
||
if (resultsContainer) resultsContainer.style.display = 'none';
|
||
|
||
// Submit form
|
||
const formData = new FormData(e.target);
|
||
|
||
fetch('/agents/weather-reporter/process/', {
|
||
method: 'POST',
|
||
body: formData,
|
||
headers: {
|
||
'X-Requested-With': 'XMLHttpRequest'
|
||
}
|
||
})
|
||
.then(response => response.json())
|
||
.then(result => {
|
||
// Handle immediate response (API-based agent)
|
||
processingStatus.style.display = 'none';
|
||
|
||
if (result.error) {
|
||
WeatherUtils.showToast(`❌ ${result.error}`, 'error');
|
||
} else if (result.success) {
|
||
WeatherUtils.displayResults(result);
|
||
} else {
|
||
WeatherUtils.showToast('❌ Failed to generate weather report', 'error');
|
||
}
|
||
})
|
||
.catch(error => {
|
||
console.error('Form submission error:', error);
|
||
if (processingStatus) processingStatus.style.display = 'none';
|
||
WeatherUtils.showToast('❌ Connection error. Please try again.', 'error');
|
||
});
|
||
}
|
||
</script>
|
||
|
||
<div class="agent-container">
|
||
<!-- Agent Header -->
|
||
<div class="agent-header">
|
||
<div>
|
||
<h1 class="agent-title">Weather Reporter</h1>
|
||
<p class="agent-subtitle">Get real-time weather data from any location worldwide</p>
|
||
</div>
|
||
<div class="header-controls">
|
||
<div class="wallet-card widget-small" style="margin-bottom: 0;">
|
||
<div class="wallet-header">
|
||
<h3 class="wallet-title">Your Wallet</h3>
|
||
<div class="wallet-icon">💳</div>
|
||
</div>
|
||
<div class="balance-display">
|
||
<div class="balance-amount">
|
||
<span id="walletBalance">{{ user.wallet_balance|floatformat:2 }}</span> AED
|
||
</div>
|
||
<div class="balance-label">Available Balance</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Quick Agent Access Panel -->
|
||
<div class="quick-agents-overlay" id="quickAgentsOverlay" onclick="closeQuickAgents()" aria-hidden="true"></div>
|
||
<div class="quick-agents-panel" id="quickAgentsPanel" role="dialog" aria-labelledby="quickAgentsTitle" aria-hidden="true">
|
||
<div class="quick-agents-header">
|
||
<h3 id="quickAgentsTitle">Quick Access to Other Agents</h3>
|
||
<button class="close-panel" onclick="toggleQuickAgents()" aria-label="Close quick agents panel">×</button>
|
||
</div>
|
||
<div class="quick-agents-grid">
|
||
<a href="/agents/data-analyzer/" class="quick-agent-card">
|
||
<div class="agent-icon">📊</div>
|
||
<div class="agent-info">
|
||
<h4>Data Analyzer</h4>
|
||
<p>AI-powered data analysis</p>
|
||
<span class="agent-price">5.0 AED</span>
|
||
</div>
|
||
</a>
|
||
|
||
<a href="/agents/job-posting-generator/" class="quick-agent-card">
|
||
<div class="agent-icon">💼</div>
|
||
<div class="agent-info">
|
||
<h4>Job Posting Generator</h4>
|
||
<p>Create professional job posts</p>
|
||
<span class="agent-price">4.0 AED</span>
|
||
</div>
|
||
</a>
|
||
|
||
<a href="/agents/social-ads-generator/" class="quick-agent-card">
|
||
<div class="agent-icon">📢</div>
|
||
<div class="agent-info">
|
||
<h4>Social Ads Generator</h4>
|
||
<p>Create social media campaigns</p>
|
||
<span class="agent-price">7.0 AED</span>
|
||
</div>
|
||
</a>
|
||
|
||
<a href="/agents/five-whys-analyzer/" class="quick-agent-card">
|
||
<div class="agent-icon">🤔</div>
|
||
<div class="agent-info">
|
||
<h4>Five Whys Analyzer</h4>
|
||
<p>Problem analysis method</p>
|
||
<span class="agent-price">3.0 AED</span>
|
||
</div>
|
||
</a>
|
||
</div>
|
||
<div class="quick-agents-footer">
|
||
<a href="{% url 'core:marketplace' %}" class="view-all-agents">View All Agents →</a>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Agent Grid -->
|
||
<div class="agent-grid">
|
||
<!-- Weather Form Widget -->
|
||
<div class="agent-widget widget-large" style="flex: 1; margin-right: clamp(0px, var(--spacing-lg), 2vw);">
|
||
<div class="widget-header">
|
||
<h3 class="widget-title">
|
||
<span class="widget-icon">🌤️</span>
|
||
Weather Report Configuration
|
||
</h3>
|
||
</div>
|
||
<div class="widget-content">
|
||
<form id="weatherForm" method="POST">
|
||
{% csrf_token %}
|
||
|
||
<!-- Location Input -->
|
||
<div class="form-group">
|
||
<label for="location" class="form-label">📍 Enter Location</label>
|
||
<input type="text" name="location" id="location" class="form-input"
|
||
placeholder="Enter city name, address, or coordinates..." required>
|
||
<div class="form-help">Examples: "New York", "London, UK", "Tokyo, Japan", "37.7749,-122.4194"</div>
|
||
</div>
|
||
|
||
<!-- Report Type Selection -->
|
||
<div class="form-group">
|
||
<label class="form-label">📊 Report Type</label>
|
||
<div class="radio-grid">
|
||
<div class="radio-card selected" onclick="selectRadio('current')">
|
||
<input type="radio" id="current" name="report_type" value="current" checked>
|
||
<div class="radio-button"></div>
|
||
<label for="current" class="radio-label">🌡️ Current Weather</label>
|
||
</div>
|
||
<div class="radio-card" onclick="selectRadio('forecast')">
|
||
<input type="radio" id="forecast" name="report_type" value="forecast">
|
||
<div class="radio-button"></div>
|
||
<label for="forecast" class="radio-label">📅 5-Day Forecast</label>
|
||
</div>
|
||
<div class="radio-card" onclick="selectRadio('detailed')">
|
||
<input type="radio" id="detailed" name="report_type" value="detailed">
|
||
<div class="radio-button"></div>
|
||
<label for="detailed" class="radio-label">📋 Detailed Report</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Submit Button -->
|
||
<div style="margin-top: var(--spacing-lg);">
|
||
{% if user.is_authenticated %}
|
||
{% if user.wallet_balance >= 2.00 %}
|
||
<button type="submit" class="btn btn-primary btn-full" id="processButton">
|
||
🌤️ Get Weather Report (2.00 AED)
|
||
</button>
|
||
{% else %}
|
||
<div style="background: #fef2f2; color: #dc2626; padding: var(--spacing-md); border-radius: var(--radius-md); margin-bottom: var(--spacing-md); font-size: 14px; font-weight: 500; text-align: center;">
|
||
Insufficient balance! You need 2.00 AED.
|
||
</div>
|
||
<a href="{% url 'core:wallet' %}" class="btn btn-primary btn-full" style="text-decoration: none;">
|
||
💰 Top Up Wallet
|
||
</a>
|
||
{% endif %}
|
||
{% else %}
|
||
<a href="{% url 'authentication:login' %}" class="btn btn-primary btn-full">
|
||
🔐 Login to Continue
|
||
</a>
|
||
{% endif %}
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- How It Works Widget - Positioned on the right -->
|
||
<div class="agent-widget widget-small" style="min-width: min(280px, 100%); max-width: min(280px, 100%); margin-left: auto;">
|
||
<div class="widget-header">
|
||
<h3 class="widget-title">
|
||
<span class="widget-icon">ℹ️</span>
|
||
How It Works
|
||
</h3>
|
||
</div>
|
||
<div class="widget-content">
|
||
<ol class="info-list">
|
||
<li>Enter any city name worldwide</li>
|
||
<li>Choose your preferred report type</li>
|
||
<li>Get real-time weather data</li>
|
||
<li>Copy or download detailed reports</li>
|
||
</ol>
|
||
|
||
<!-- Other Agents Button -->
|
||
<button class="quick-agent-toggle btn btn-secondary btn-full" onclick="toggleQuickAgents()"
|
||
title="Quick access to other agents"
|
||
aria-label="Open quick access panel for other AI agents"
|
||
aria-expanded="false"
|
||
aria-controls="quickAgentsPanel"
|
||
style="margin-top: var(--spacing-md);">
|
||
<span class="toggle-icon" aria-hidden="true">🚀</span>
|
||
<span class="toggle-text">Explore Other Agents</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Main Content Grid -->
|
||
<div class="agent-grid">
|
||
<!-- Processing Status -->
|
||
<div id="processingStatus" class="agent-widget widget-wide processing-status">
|
||
<div class="widget-header">
|
||
<h3 class="widget-title">
|
||
<span class="widget-icon">⏳</span>
|
||
Processing Status
|
||
</h3>
|
||
</div>
|
||
<div class="widget-content" style="text-align: center;">
|
||
<div class="status-icon">⏳</div>
|
||
<div class="status-text">Getting Weather Data...</div>
|
||
<div class="status-detail" id="statusText">Fetching real-time weather information...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Results Widget -->
|
||
<div id="resultsContainer" class="agent-widget widget-wide results-container">
|
||
<div class="widget-header">
|
||
<h3 class="widget-title">
|
||
<span class="widget-icon">📊</span>
|
||
Weather Report
|
||
</h3>
|
||
<span class="status-badge" style="background: var(--success-color); color: white; padding: 4px 8px; border-radius: 4px; font-size: 12px;">Success</span>
|
||
</div>
|
||
<div class="widget-content">
|
||
<div class="results-content" id="resultsContent">
|
||
<!-- Results will be populated here by JavaScript -->
|
||
</div>
|
||
|
||
<div class="action-buttons" style="margin-top: var(--spacing-lg); padding-top: var(--spacing-lg); border-top: 1px solid var(--outline-variant);">
|
||
<button onclick="copyResults()" class="btn btn-primary">📋 Copy Report</button>
|
||
<button onclick="downloadResults()" class="btn btn-secondary">💾 Download</button>
|
||
<button onclick="resetForm()" class="btn btn-secondary">🔄 New Request</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endblock %} |