quantum-ai/static/js/agent-utils.js
Claude aeaf0be031 Implement unified CSS system and fix data analyzer network errors
## Unified Agent Architecture
- Create agent-base.css with theme support (professional, creative, minimal)
- Implement shared JavaScript utilities in agent-utils.js
- Standardize form submission patterns across all agents
- Reduce code duplication by ~600 lines

## CSS Theme System
- Professional theme: Job posting, data analyzer, weather reporter
- Creative theme: Social ads generator (glassmorphism effects)
- Minimal theme: Available for future agents
- Consistent component library with CSS custom properties

## Text Display Standardization
- Replace complex markdown parsing with simple text formatting
- Remove external dependencies (Marked.js + DOMPurify)
- Implement basic text cleaning: remove **, convert line breaks to <br>
- Fix layout stretching issues in job posting results

## Data Analyzer Fixes
- Fix network errors by converting from button click to form submission
- Update to use unified CSS system and professional theme
- Improve file upload handling with conditional checks
- Standardize template structure with other agents

## Form Submission Improvements
- Unified form submission pattern across all agents
- Automatic CSRF token handling via FormData(form)
- Consistent error handling and user feedback
- Reliable polling mechanism for webhook-based agents

## Documentation Updates
- Document unified CSS system and theme architecture
- Add text display standardization guidelines
- Update agent examples with current features
- Include form submission best practices

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-13 19:30:04 +05:30

225 lines
7.5 KiB
JavaScript

/**
* NetCop AI Agents - Shared Utilities
* Contains common functions used across all agent templates
*/
window.AgentUtils = {
/**
* Simple Text Formatter
* Cleans AI-generated text and converts to readable HTML
*/
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();
},
/**
* Update wallet balance display across all agents
*/
updateWalletBalance(newBalance) {
const balanceElement = document.querySelector('[data-wallet-balance]') ||
document.getElementById('walletBalance');
if (balanceElement) {
balanceElement.textContent = `${newBalance.toFixed(2)} AED`;
}
window.currentWalletBalance = newBalance;
},
/**
* Reset UI to initial state
*/
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';
}
},
/**
* Show processing status
*/
showProcessing(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 = 'block';
}
if (elements.processButton) {
elements.processButton.disabled = true;
elements.processButton.classList.add('loading');
elements.processButton.innerHTML = config.processingText || '⏳ Processing...';
}
if (elements.results) {
elements.results.style.display = 'none';
}
},
/**
* Show toast notification with duplicate prevention
*/
showToast(message, type = 'info') {
// Prevent duplicate toasts
const existingToast = document.querySelector('.agent-toast');
if (existingToast) {
existingToast.remove();
}
const toast = document.createElement('div');
toast.className = 'agent-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);
},
/**
* Display results with markdown parsing
* Standardized across all agents
*/
displayResults(config) {
const resultsContainer = document.getElementById(config.resultsId);
const contentContainer = document.getElementById(config.contentId);
if (config.result.success && config.result.status === 'completed') {
// Get content from various possible fields
const content = config.result.content ||
config.result.job_posting_content ||
config.result.ad_copy_content ||
config.result.analysis_results ||
config.result.insights_summary ||
config.result.report_text ||
config.result.weather_data ||
config.result.formatted_report ||
config.result.output_text ||
config.defaultMessage ||
'Content generated successfully!';
// Parse markdown and display as HTML
const formattedContent = this.parseMarkdown(content);
contentContainer.innerHTML = formattedContent;
resultsContainer.style.display = 'block';
// Update wallet balance if provided
if (config.result.wallet_balance !== undefined) {
this.updateWalletBalance(config.result.wallet_balance);
}
this.showToast(config.successMessage || '✅ Content generated and payment processed!', 'success');
} else {
this.showToast(config.errorMessage || '❌ Failed to generate content - no charge applied', 'error');
}
},
/**
* 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');
}
};
/**
* Progressive status steps for better UX
*/
window.StatusStepper = class {
constructor(steps, statusTextElementId, interval = 800) {
this.steps = steps;
this.statusTextElement = document.getElementById(statusTextElementId);
this.interval = interval;
this.currentStep = 0;
this.stepInterval = null;
}
start() {
this.currentStep = 0;
this.stepInterval = setInterval(() => {
if (this.currentStep < this.steps.length && this.statusTextElement) {
this.statusTextElement.textContent = this.steps[this.currentStep];
this.currentStep++;
} else {
this.stop();
}
}, this.interval);
}
stop() {
if (this.stepInterval) {
clearInterval(this.stepInterval);
this.stepInterval = null;
}
}
};