/** * Agents Core - Dynamic Agent Execution System * Handles form submission and N8N integration for any agent */ class AgentsCore extends WorkflowsCore { constructor() { super(); this.agentId = document.body.getAttribute('data-agent-id'); this.agentSlug = document.body.getAttribute('data-agent-slug'); this.webhookUrl = document.body.getAttribute('data-webhook-url'); this.price = parseFloat(document.body.getAttribute('data-agent-price') || '0'); this.sessionId = this.constructor.generateSessionId(); // Initialize on page load this.initialize(); } initialize() { // Initialize form submission const form = document.getElementById('agentForm'); if (form) { form.addEventListener('submit', this.handleFormSubmission.bind(this)); } // Initialize form validation this.initializeDynamicFormValidation(); } /** * Handle form submission with agents API integration */ 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('Executing agent...'); const submitBtn = document.getElementById('generateBtn'); if (submitBtn) { submitBtn.disabled = true; submitBtn.textContent = 'โณ Processing...'; } try { // Use agents API for execution await this.executeViaAgentsAPI(e.target); } catch (error) { console.error('Form submission error:', error); this.constructor.hideProcessing(); this.showErrorMessage('โŒ Agent is temporarily unavailable. Please try again later.'); this.resetSubmitButton(); } } /** * Execute agent via the agents API */ async executeViaAgentsAPI(form) { try { const formData = new FormData(form); // Check if form contains file uploads const hasFiles = Array.from(formData.entries()).some(([key, value]) => value instanceof File && key !== 'csrfmiddlewaretoken' ); if (hasFiles) { // Handle file upload via multipart form data await this.executeWithFileUpload(formData); } else { // Handle regular form data via JSON API await this.executeWithJsonAPI(formData); } } catch (error) { console.error('Agent execution error:', error); this.constructor.hideProcessing(); this.showErrorMessage('โŒ Agent is temporarily unavailable. Please try again later.'); this.resetSubmitButton(); } } /** * Execute agent with file upload using multipart form data */ async executeWithFileUpload(formData) { // Get CSRF token const csrfToken = formData.get('csrfmiddlewaretoken'); // Prepare multipart form data for direct webhook call (similar to workflows) const webhookFormData = new FormData(); // Add files and regular form fields for (let [key, value] of formData.entries()) { if (key !== 'csrfmiddlewaretoken') { if (value instanceof File) { webhookFormData.append('file', value); } else { // Map form fields to webhook expected format if (key === 'analysis_type') { webhookFormData.append('analysisType', value); } else { webhookFormData.append(key, value); } } } } // Call webhook directly for file uploads (similar to workflows approach) const response = await fetch(this.webhookUrl, { method: 'POST', body: webhookFormData, signal: AbortSignal.timeout(120000) // 2 minute timeout for file processing }); if (!response.ok) { throw new Error(`File processing failed: ${response.status}`); } // Parse response const data = await response.json(); // Deduct wallet balance manually since we bypassed the API await this.deductBalanceForFileUpload(); // Process successful execution this.constructor.hideProcessing(); // Display results this.displayFileProcessingResults(data); this.constructor.showToast('โœ… File processed successfully!', 'success'); } /** * Execute agent with JSON API (for non-file uploads) */ async executeWithJsonAPI(formData) { // Extract all form data dynamically const inputData = {}; for (let [key, value] of formData.entries()) { if (key !== 'csrfmiddlewaretoken') { inputData[key] = value; } } // Get CSRF token const csrfToken = formData.get('csrfmiddlewaretoken'); // Form validation passed, proceeding with execution // Call agents API const response = await fetch('/agents/api/execute/', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': csrfToken }, body: JSON.stringify({ agent_slug: this.agentSlug, input_data: inputData }), signal: AbortSignal.timeout(90000) // 90 second timeout }); if (!response.ok) { const errorData = await response.json().catch(() => ({ error: 'Unknown error' })); throw new Error(errorData.error || `API error: ${response.status}`); } const data = await response.json(); // Process successful execution this.constructor.hideProcessing(); // Update wallet balance ONLY after successful AI execution if (data.fee_charged) { const currentBalance = parseFloat(document.body.getAttribute('data-user-balance') || '0'); const newBalance = currentBalance - parseFloat(data.fee_charged); console.log('Charging wallet after successful execution:', { currentBalance, feeCharged: data.fee_charged, newBalance, executionStatus: data.status }); // Update the wallet balance display this.constructor.updateWalletBalance(newBalance); // Show notification about successful charge this.constructor.showToast(`๐Ÿ’ฐ Charged ${data.fee_charged} AED - Service completed!`, 'success'); } // Display results this.displayExecutionResults(data); this.constructor.showToast('โœ… Agent executed successfully!', 'success'); } /** * Deduct wallet balance for file upload (manual deduction) */ async deductBalanceForFileUpload() { try { // Call the wallet deduction API const response = await fetch('/wallet/api/deduct/', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value }, body: JSON.stringify({ amount: this.price, description: `${this.agentSlug.replace('-', ' ')} execution`, agent_slug: this.agentSlug }) }); if (response.ok) { const data = await response.json(); if (data.new_balance !== undefined) { // Update wallet balance display this.constructor.updateWalletBalance(data.new_balance); } } } catch (error) { console.error('Wallet deduction error:', error); } } /** * Display execution results */ displayExecutionResults(data) { const resultsContainer = document.getElementById('resultsContainer'); const resultsContent = document.getElementById('resultsContent'); if (!resultsContainer || !resultsContent) { console.log('Results containers not found'); return; } // Clear previous results resultsContent.innerHTML = ''; // Extract output content with better format handling and debugging let content = ''; // Add debugging to see what we're getting console.log('=== DEBUGGING EXECUTION RESULTS ==='); console.log('Full data object:', data); console.log('output_data type:', typeof data.output_data); console.log('output_data content:', data.output_data); console.log('Agent slug:', this.agentSlug); // Check for PDF analyzer direct response format FIRST if (data.sections && Array.isArray(data.sections)) { console.log('Using PDF direct response format'); content = this.formatPDFAnalysisResults(data.sections); } // Check for PDF analyzer array response format else if (Array.isArray(data) && data[0] && data[0].sections) { console.log('Using PDF array response format'); content = this.formatPDFAnalysisResults(data[0].sections); } // Then check for standard output_data format else if (data.output_data && typeof data.output_data === 'object') { // Handle PDF analyzer nested response format if (Array.isArray(data.output_data) && data.output_data[0] && data.output_data[0].sections) { console.log('Using PDF nested analysis format'); content = this.formatPDFAnalysisResults(data.output_data[0].sections); } // Handle standard webhook response format else if (data.output_data.output) { console.log('Using standard output format'); // Check if this is a job posting and format it specially if (this.agentSlug === 'job-posting-generator') { content = this.formatJobPostingResults(data.output_data.output); } else { content = data.output_data.output; } } // Handle brand presence finder results (regular version) else if (this.agentSlug === 'brand-digital-presence-finder' && data.output_data.data) { console.log('Using brand presence analysis format v2'); content = this.formatBrandPresenceResults_v2(data.output_data.data, data.output_data); } // Handle brand presence finder PRO results (enhanced version) else if (this.agentSlug === 'brand-digital-presence-finder-pro' && data.output_data.data) { console.log('Using brand presence PRO enhanced format with follower tracking'); content = this.formatBrandPresenceResults_v2(data.output_data.data, data.output_data); } // Handle other response formats else if (data.output_data.result || data.output_data.content) { console.log('Using result/content format'); content = data.output_data.result || data.output_data.content; } // Show the actual data structure instead of generic message else { console.log('Using JSON fallback format'); content = `
${JSON.stringify(data.output_data, null, 2)}
`; } } else if (data.output_data) { console.log('Using string format'); content = data.output_data.toString(); } else { console.log('No output_data found - using fallback'); // Show the full response to debug what's missing content = `

Execution Details:

Status: ${data.status || 'unknown'}

Agent: ${this.agentSlug}

Execution ID: ${data.id || 'unknown'}

Error: ${data.error_message || 'No error message'}

Full Response Data
${JSON.stringify(data, null, 2)}
`; } console.log('Final content length:', content.length); console.log('====================================='); // Create content element const contentDiv = document.createElement('div'); contentDiv.className = 'results-content'; // Use innerHTML for formatted PDF results, textContent for others if (content.includes(' { let content = section.content.replace(/\*\*(.*?)\*\*/g, '$1'); content = content.replace(/\n/g, '
'); html += `

${section.heading}

${content}
`; }); html += ''; // Simple, clean styling html += ` `; return html; } /** * Process markdown-like content and convert to HTML */ processMarkdownContent(content) { // Handle numbered lists (1. **Title**: Description) content = content.replace(/(\d+)\.\s\*\*(.*?)\*\*:\s*(.*?)(?=\n\d+\.|\n-|$)/g, '
  1. $2: $3
'); // Fix multiple consecutive ol tags content = content.replace(/<\/ol>\s*
    /g, ''); // Handle bullet points (- **Title**: Description) content = content.replace(/(?:^|\n)-\s\*\*(.*?)\*\*:\s*(.*?)(?=\n-|$)/g, ''); // Fix multiple consecutive ul tags content = content.replace(/<\/ul>\s*