quantum-ai-v3/static/js/social-ads.js
Claude 9e6ec903fc 🚀 Implement simplified workflows system with shared components
## Major System Simplification
- **90% reduction** in agent configuration complexity (369 → 83 lines)
- Removed unused dynamic field system (174 lines eliminated)
- Deleted generic template fallback (dead code removal)
- Enhanced JavaScript utilities with 15+ shared functions

## New Workflows App Architecture
- Unified agent processing with hybrid N8N/Django approach
- Shared component system (6 reusable components)
- Individual templates with consistent UI patterns
- Configuration-driven agent definitions (metadata only)

## Enhanced Developer Experience
- 4-step agent creation process (vs complex multi-step before)
- Agent template starter file for easy copying
- Comprehensive documentation with before/after examples
- Enhanced WorkflowsCore utilities for common operations

## Files Added
- `workflows/` - Complete new Django app with simplified architecture
- `agent-template-starter.html` - Template for easy agent creation
- `workflows-core.js` - Enhanced JavaScript utilities (15+ functions)
- Shared components: header, quick-agents, processing, results, wallet
- Simplified agent configs (5 lines vs 50+ lines each)

## Benefits Achieved
 90% less configuration code per agent
 Shared component reusability with dynamic data
 Enhanced JavaScript utilities automatically available
 Consistent UI/UX across all agents
 Dramatically simplified maintenance

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-28 20:06:46 +05:30

494 lines
18 KiB
JavaScript

/**
* Social Ads Generator - Agent-Specific JavaScript
* Handles unique functionality for Social Ads Generator agent
*/
class SocialAdsProcessor extends WorkflowsCore {
constructor() {
super();
this.agentSlug = 'social-ads-generator';
this.webhookUrl = 'http://localhost:5678/webhook/2dc234d8-7217-454a-83e9-81afe5b4fe2d';
this.price = 5.0; // Will be overridden by template data
this.sessionId = this.constructor.generateSessionId();
// Initialize on page load
this.initialize();
}
initialize() {
// Set data attributes from page
const priceElement = document.body.getAttribute('data-agent-price');
if (priceElement) {
this.price = parseFloat(priceElement);
}
// Initialize form submission
const form = document.getElementById('agentForm');
if (form) {
form.addEventListener('submit', this.handleFormSubmission.bind(this));
}
// Initialize form validation
this.initializeFormValidation();
// Set initial radio selection if any exist
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;
}
}
/**
* Handle form submission with hybrid N8N/Django approach
*/
async handleFormSubmission(e) {
e.preventDefault();
if (!this.isFormValid()) {
this.constructor.showToast('Please fill in all required fields correctly', 'error');
return;
}
// Check authentication and balance
if (!this.constructor.checkAuthentication()) return;
if (!this.constructor.checkBalance(this.price)) return;
// Show processing status and disable submit button
this.constructor.showProcessing('Generating your social ads...');
const submitBtn = document.getElementById('generateBtn');
if (submitBtn) {
submitBtn.disabled = true;
submitBtn.textContent = '⏳ Generating...';
}
try {
// Try direct N8N integration for better performance (with Django fallback)
const useDirectN8N = true; // Feature flag for direct integration
if (useDirectN8N) {
await this.processViaDirectN8N(e.target);
} else {
await this.processViaDjango(e.target);
}
} catch (error) {
console.error('Form submission error:', error);
this.constructor.hideProcessing();
this.constructor.showToast('❌ Connection error. Please try again.', 'error');
this.resetSubmitButton();
}
}
/**
* Direct N8N processing for better performance
*/
async processViaDirectN8N(form) {
try {
const formData = new FormData(form);
// Extract form data
const description = formData.get('description').trim();
const platform = formData.get('social_platform');
const emoji = formData.get('include_emoji');
const language = formData.get('language');
// Create message for N8N
const messageText = `Create compelling social media ads for: ${description}. Target platform: ${platform}. Include emojis: ${emoji}. Language: ${language}. Make it engaging and professional.`;
const webhookData = {
sessionId: this.sessionId,
message: { text: messageText }
};
// Direct N8N webhook call
const response = await fetch(this.webhookUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(webhookData),
signal: AbortSignal.timeout(60000) // 60 second timeout
});
if (!response.ok) {
throw new Error(`N8N error: ${response.status}`);
}
const contentType = response.headers.get('content-type');
let data;
if (contentType && contentType.includes('application/json')) {
data = await response.json().catch(() => response.text());
} else {
data = await response.text();
}
// Process successful N8N response
this.constructor.hideProcessing();
// Deduct wallet balance via Django API
await this.constructor.deductBalance(
this.price,
`Social Ads Generator - ${description.substring(0, 50)}...`,
this.agentSlug
);
// Display results using the enhanced display function
this.displayDirectN8NResults(data, platform, language);
this.constructor.showToast('✅ Social ads generated successfully!', 'success');
} catch (error) {
console.error('Direct N8N error:', error);
this.constructor.showToast('❌ Direct processing failed, trying Django backend...', 'info');
// Fallback to Django processing
await this.processViaDjango(form);
}
}
/**
* Django processing fallback
*/
async processViaDjango(form) {
const formData = new FormData(form);
const response = await fetch(window.location.href, {
method: 'POST',
body: formData,
headers: { 'X-Requested-With': 'XMLHttpRequest' }
});
const result = await response.json();
if (result.success && result.request_id) {
// Start polling for results
this.checkResults(result.request_id);
if (result.wallet_balance !== undefined) {
this.constructor.updateWalletBalance(result.wallet_balance);
}
} else {
this.constructor.hideProcessing();
this.constructor.showToast(`${result.error || 'Processing failed'}`, 'error');
this.resetSubmitButton();
}
}
/**
* Display results from direct N8N call
*/
displayDirectN8NResults(data, platform, language) {
const resultsContainer = document.getElementById('resultsContainer');
const resultsContent = document.getElementById('resultsContent');
if (!resultsContainer || !resultsContent) return;
let content = '';
// Handle different N8N response formats
if (typeof data === 'string') {
content = data;
} else if (data && typeof data === 'object') {
content = data.output || data.text || data.content || data.ad_copy || data.result || data.message || JSON.stringify(data, null, 2);
} else {
content = 'Social ads generated successfully!';
}
// Clear and populate results securely
resultsContent.textContent = '';
this.renderSecureContent(resultsContent, content);
// Show results container
resultsContainer.style.display = 'block';
resultsContainer.scrollIntoView({ behavior: 'smooth', block: 'start' });
this.resetSubmitButton();
}
/**
* Secure content rendering without innerHTML to prevent XSS
*/
renderSecureContent(container, content) {
// Sanitize and validate content
if (!content || typeof content !== 'string') {
container.textContent = 'No content available';
return;
}
// Create wrapper paragraph
const wrapper = document.createElement('div');
wrapper.className = 'results-content';
// Split content into lines and process safely
const lines = content.split('\n');
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
if (!line) {
// Add line break for empty lines
if (i > 0) wrapper.appendChild(document.createElement('br'));
continue;
}
let element;
// Handle headers (but escape content)
if (line.startsWith('### ')) {
element = document.createElement('h3');
element.textContent = line.substring(4);
} else if (line.startsWith('## ')) {
element = document.createElement('h2');
element.textContent = line.substring(3);
} else if (line.startsWith('# ')) {
element = document.createElement('h1');
element.textContent = line.substring(2);
} else {
// Handle regular text with basic formatting
element = document.createElement('span');
this.formatTextSecurely(element, line);
}
wrapper.appendChild(element);
// Add line break if not the last line
if (i < lines.length - 1) {
wrapper.appendChild(document.createElement('br'));
}
}
container.appendChild(wrapper);
}
/**
* Format text with basic styling while preventing XSS
*/
formatTextSecurely(element, text) {
// Simple approach: handle bold and italic formatting securely
const parts = [];
let currentText = text;
// Process **bold** text
currentText = currentText.replace(/\*\*(.*?)\*\*/g, (match, content) => {
const placeholder = `__BOLD_${parts.length}__`;
parts.push({type: 'bold', content: content});
return placeholder;
});
// Process *italic* text
currentText = currentText.replace(/\*(.*?)\*/g, (match, content) => {
const placeholder = `__ITALIC_${parts.length}__`;
parts.push({type: 'italic', content: content});
return placeholder;
});
// Split by placeholders and create DOM elements
const segments = currentText.split(/(__(?:BOLD|ITALIC)_\d+__)/);
segments.forEach(segment => {
if (segment.startsWith('__BOLD_')) {
const index = parseInt(segment.match(/\d+/)[0]);
const strong = document.createElement('strong');
strong.textContent = parts[index].content;
element.appendChild(strong);
} else if (segment.startsWith('__ITALIC_')) {
const index = parseInt(segment.match(/\d+/)[0]);
const em = document.createElement('em');
em.textContent = parts[index].content;
element.appendChild(em);
} else if (segment) {
element.appendChild(document.createTextNode(segment));
}
});
}
/**
* Form validation specific to Social Ads Generator
*/
initializeFormValidation() {
const fields = ['description', 'social_platform', 'include_emoji'];
fields.forEach(fieldName => {
const field = document.getElementById(fieldName);
if (field) {
field.addEventListener('blur', () => this.validateField(fieldName));
field.addEventListener('input', () => this.constructor.clearFieldError(fieldName));
}
});
}
validateField(fieldName) {
const field = document.getElementById(fieldName);
const value = field.value.trim();
switch (fieldName) {
case 'description':
if (!value) {
this.constructor.showFieldError(fieldName, 'Please provide a description of your product or service');
return false;
} else if (value.length < 10) {
this.constructor.showFieldError(fieldName, 'Description must be at least 10 characters long');
return false;
}
break;
case 'social_platform':
if (!value) {
this.constructor.showFieldError(fieldName, 'Please select a social media platform');
return false;
}
break;
case 'include_emoji':
if (!value) {
this.constructor.showFieldError(fieldName, 'Please select whether to include emojis');
return false;
}
break;
}
this.constructor.clearFieldError(fieldName);
return true;
}
isFormValid() {
const fields = ['description', 'social_platform', 'include_emoji'];
let isValid = true;
fields.forEach(fieldName => {
if (!this.validateField(fieldName)) {
isValid = false;
}
});
return isValid;
}
/**
* Check results (polling for Django completion)
*/
checkResults(requestId) {
let pollCount = 0;
const maxPolls = 30; // 5 minutes max
const pollInterval = setInterval(() => {
pollCount++;
fetch(`/workflows/api/status/${requestId}/`)
.then(response => response.json())
.then(result => {
if (result.status === 'completed') {
clearInterval(pollInterval);
this.displayDjangoResults(result);
} else if (result.status === 'failed') {
clearInterval(pollInterval);
this.constructor.hideProcessing();
this.constructor.showToast('❌ Social ads generation failed. Please try again.', 'error');
this.resetSubmitButton();
} else if (pollCount >= maxPolls) {
clearInterval(pollInterval);
this.constructor.hideProcessing();
this.constructor.showToast('⏰ Processing is taking longer than expected. Please check back later.', 'error');
this.resetSubmitButton();
}
// Continue polling if still processing
})
.catch(error => {
console.error('Status check error:', error);
if (pollCount >= maxPolls) {
clearInterval(pollInterval);
this.constructor.hideProcessing();
this.constructor.showToast('❌ Connection error during processing.', 'error');
this.resetSubmitButton();
}
});
}, 10000); // Check every 10 seconds
}
/**
* Display results from Django processing
*/
displayDjangoResults(result) {
const resultsContainer = document.getElementById('resultsContainer');
const resultsContent = document.getElementById('resultsContent');
if (result.success || result.output) {
this.constructor.hideProcessing();
const adContent = result.output || result.ad_copy_content || result.content || 'Social ads generated successfully!';
if (resultsContent) {
resultsContent.textContent = '';
this.renderSecureContent(resultsContent, adContent);
}
if (resultsContainer) {
resultsContainer.style.display = 'block';
resultsContainer.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
this.constructor.showToast('✅ Social ads completed successfully!', 'success');
} else if (result.error) {
this.constructor.hideProcessing();
this.constructor.showToast(`❌ Error: ${result.error}`, 'error');
} else {
this.constructor.hideProcessing();
this.constructor.showToast('❌ Failed to generate social ads. Please try again.', 'error');
}
this.resetSubmitButton();
}
/**
* Reset submit button to original state
*/
resetSubmitButton() {
const submitBtn = document.getElementById('generateBtn');
if (submitBtn) {
submitBtn.disabled = false;
submitBtn.textContent = `📢 Generate Social Ads (${this.price} AED)`;
}
}
}
// Result action functions (global for button onclick handlers)
function copyResults() {
const content = document.getElementById('resultsContent');
if (content) {
const text = content.textContent || '';
WorkflowsCore.copyToClipboard(text, 'Social ads copied to clipboard!');
}
}
function downloadResults() {
const content = document.getElementById('resultsContent');
if (content) {
const text = content.textContent || '';
WorkflowsCore.downloadAsFile(text, 'social-ads-results.txt', 'Social ads downloaded!');
}
}
function resetForm() {
const form = document.getElementById('agentForm');
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';
// Clear validation errors
const fields = ['description', 'social_platform', 'include_emoji'];
fields.forEach(fieldName => WorkflowsCore.clearFieldError(fieldName));
// Scroll back to form
const formSection = document.getElementById('agentForm');
if (formSection) {
formSection.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
}
// Initialize Social Ads Processor when DOM is ready
document.addEventListener('DOMContentLoaded', function() {
// Initialize processor (data attributes set by template)
window.socialAdsProcessor = new SocialAdsProcessor();
});