mirror of
https://github.com/thecyberlearn/quantum-ai.git
synced 2026-08-18 19:12:58 +00:00
TOAST IMPROVEMENTS: - Standardize success messages: "✅ [Action] completed successfully\!" - Standardize clipboard messages: "📋 Copied to clipboard\!" - Standardize error messages with ❌ emoji prefix - Remove download and reset toast notifications (feedback is file/visual) DYNAMIC PRICING: - Replace hardcoded prices with {{ agent.price }} template variables - Update JavaScript balance checks to use dynamic pricing - Fix button text and error messages to show correct pricing PRESERVED: - All existing displayResults function logic (no changes to result display) - All existing function names and calling patterns - All agent-specific utilities (DataAnalyzerUtils, SocialAdsUtils, etc.) - Result display functionality remains intact 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
452 lines
18 KiB
HTML
452 lines
18 KiB
HTML
{% extends 'base.html' %}
|
||
{% load static %}
|
||
|
||
{% block title %}Weather Reporter Agent - Quantum Tasks AI{% 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', '{{ agent.price }}');
|
||
|
||
// 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 - USE TEXTCONTENT FOR SECURITY
|
||
const weatherReport = result.formatted_report || result.content || 'Weather data received successfully.';
|
||
if (resultsContent) {
|
||
// Use textContent instead of innerHTML to prevent XSS
|
||
resultsContent.textContent = this.formatWeatherText(weatherReport);
|
||
}
|
||
|
||
// Show results container
|
||
if (resultsContainer) {
|
||
resultsContainer.style.display = 'block';
|
||
}
|
||
|
||
// Update wallet balance if provided
|
||
if (result.wallet_balance !== undefined) {
|
||
this.updateWalletBalance(result.wallet_balance);
|
||
}
|
||
|
||
this.showToast('✅ Weather report completed 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');
|
||
}
|
||
},
|
||
|
||
// Safe text formatting (no HTML generation for security)
|
||
formatWeatherText(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
|
||
.trim();
|
||
},
|
||
|
||
// Legacy function for backward compatibility (deprecated - use formatWeatherText)
|
||
parseMarkdown(text) {
|
||
return this.formatWeatherText(text);
|
||
},
|
||
|
||
// 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('📋 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);
|
||
// No toast for download - file download is confirmation enough
|
||
}
|
||
}
|
||
|
||
// 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;
|
||
}
|
||
|
||
// No toast for reset - visual feedback is enough
|
||
}
|
||
|
||
// 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 < {{ agent.price }}) {
|
||
WeatherUtils.showToast('Insufficient wallet balance', 'error');
|
||
setTimeout(() => {
|
||
window.location.href = "{% url 'wallet: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 -->
|
||
{% include "components/agent_header.html" with agent_title="Weather Reporter" agent_subtitle="Get real-time weather data from any location worldwide" %}
|
||
|
||
<!-- Quick Agent Access Panel -->
|
||
{% include "components/quick_agents_panel.html" %}
|
||
|
||
<!-- 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 >= agent.price %}
|
||
<button type="submit" class="btn btn-primary btn-full" id="processButton">
|
||
🌤️ Get Weather Report ({{ agent.price }} 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 {{ agent.price }} AED.
|
||
</div>
|
||
<a href="{% url 'wallet: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 -->
|
||
{% include "components/processing_status.html" with status_title="Getting Weather Data..." status_text="Fetching real-time weather information..." %}
|
||
|
||
<!-- Results Widget -->
|
||
{% include "components/results_container.html" with results_title="Weather Report" %}
|
||
</div>
|
||
</div>
|
||
{% endblock %} |