quantum-ai/weather_reporter/templates/weather_reporter/detail.html
Claude fa38fbd978 🎨 Toast standardization and dynamic pricing - safe approach
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>
2025-07-27 13:41:02 +05:30

452 lines
18 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters

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

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

{% extends 'base.html' %}
{% load static %}
{% block title %}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 %}