diff --git a/CLAUDE.md b/CLAUDE.md index f3d04c8..af2b7bd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -225,4 +225,4 @@ gunicorn netcop_hub.wsgi:application 4. Check WorkflowRequest/WorkflowResponse creation --- -Last updated: Last updated: Last updated: Last updated: Last updated: 2025-07-31 19:05:10 +Last updated: Last updated: Last updated: Last updated: Last updated: Last updated: 2025-07-31 19:10:15 diff --git a/agents/management/commands/create_pdf_summarizer_agent.py b/agents/management/commands/create_pdf_summarizer_agent.py new file mode 100644 index 0000000..583cd51 --- /dev/null +++ b/agents/management/commands/create_pdf_summarizer_agent.py @@ -0,0 +1,104 @@ +from django.core.management.base import BaseCommand +from agents.models import AgentCategory, Agent + +class Command(BaseCommand): + help = 'Create PDF summarizer agent' + + def handle(self, *args, **options): + # Get or create Document Processing category + doc_category, created = AgentCategory.objects.get_or_create( + slug='document-processing', + defaults={ + 'name': 'Document Processing', + 'description': 'AI-powered document analysis and processing tools', + 'icon': '📄' + } + ) + + if created: + self.stdout.write(self.style.SUCCESS(f'Created category: {doc_category.name}')) + else: + self.stdout.write(f'Category already exists: {doc_category.name}') + + # Create PDF Summarizer agent + pdf_summarizer_agent, created = Agent.objects.get_or_create( + slug='pdf-summarizer', + defaults={ + 'name': 'PDF Summarizer', + 'short_description': 'Extract and summarize content from PDF documents with AI analysis', + 'description': 'Upload PDF documents and get comprehensive AI-powered summaries, key insights, and analysis. Perfect for processing reports, research papers, contracts, and other documents. Supports multiple analysis types including summary, key points extraction, and sentiment analysis.', + 'category': doc_category, + 'price': 8.0, + 'form_schema': { + 'fields': [ + { + 'name': 'pdf_file', + 'type': 'file', + 'label': 'Upload PDF Document', + 'required': True, + 'accept': '.pdf', + 'max_size': '10MB', + 'help_text': 'Select a PDF file to analyze (max 10MB)' + }, + { + 'name': 'analysis_type', + 'type': 'select', + 'label': 'Analysis Type', + 'required': True, + 'default': 'summary', + 'options': [ + {'value': '', 'label': 'Select analysis type...'}, + {'value': 'summary', 'label': 'Document Summary'}, + {'value': 'key_points', 'label': 'Key Points Extraction'}, + {'value': 'detailed_analysis', 'label': 'Detailed Analysis'}, + {'value': 'sentiment', 'label': 'Sentiment Analysis'}, + {'value': 'questions', 'label': 'Generate Questions'}, + {'value': 'action_items', 'label': 'Extract Action Items'} + ], + 'help_text': 'Choose the type of analysis to perform on the document' + }, + { + 'name': 'language', + 'type': 'select', + 'label': 'Document Language', + 'required': False, + 'default': 'auto', + 'options': [ + {'value': 'auto', 'label': 'Auto-detect'}, + {'value': 'English', 'label': 'English'}, + {'value': 'Arabic', 'label': 'Arabic (العربية)'}, + {'value': 'Spanish', 'label': 'Spanish (Español)'}, + {'value': 'French', 'label': 'French (Français)'}, + {'value': 'German', 'label': 'German (Deutsch)'}, + {'value': 'Chinese', 'label': 'Chinese (中文)'} + ], + 'help_text': 'Specify document language for better analysis accuracy' + }, + { + 'name': 'output_length', + 'type': 'select', + 'label': 'Summary Length', + 'required': False, + 'default': 'medium', + 'options': [ + {'value': 'short', 'label': 'Short (1-2 paragraphs)'}, + {'value': 'medium', 'label': 'Medium (3-5 paragraphs)'}, + {'value': 'long', 'label': 'Long (detailed summary)'} + ], + 'help_text': 'Choose the desired length of the analysis output' + } + ] + }, + 'webhook_url': 'http://localhost:5678/webhook/simple-pdf-processor' + } + ) + + if created: + self.stdout.write(self.style.SUCCESS(f'Created agent: {pdf_summarizer_agent.name}')) + else: + self.stdout.write(f'Agent already exists: {pdf_summarizer_agent.name}') + + self.stdout.write(self.style.SUCCESS('PDF Summarizer setup completed successfully')) + self.stdout.write(f'Agent ID: {pdf_summarizer_agent.id}') + self.stdout.write(f'Agent Slug: {pdf_summarizer_agent.slug}') + self.stdout.write(f'Price: {pdf_summarizer_agent.price} AED') \ No newline at end of file diff --git a/agents/templates/agents/agent_detail.html b/agents/templates/agents/agent_detail.html index 09d8dd9..f920a43 100644 --- a/agents/templates/agents/agent_detail.html +++ b/agents/templates/agents/agent_detail.html @@ -174,6 +174,91 @@ color: #0369a1; } +/* File Upload Styles */ +.file-upload-container { + position: relative; +} + +.form-file-input { + display: none; +} + +.file-upload-label { + display: block; + padding: var(--spacing-lg); + border: 2px dashed var(--outline-variant); + border-radius: var(--radius-md); + text-align: center; + cursor: pointer; + transition: all 0.2s ease; + background: var(--surface-variant); + color: var(--on-surface-variant); +} + +.file-upload-label:hover { + border-color: var(--primary); + background: rgba(0, 0, 0, 0.02); +} + +.file-upload-label.dragover { + border-color: var(--primary); + background: rgba(0, 0, 0, 0.05); + transform: scale(1.02); +} + +.file-upload-icon { + font-size: 2rem; + display: block; + margin-bottom: var(--spacing-sm); +} + +.file-upload-text { + display: block; + font-weight: 500; + margin-bottom: var(--spacing-xs); + color: var(--on-surface); +} + +.file-upload-info { + font-size: 12px; + color: var(--on-surface-variant); +} + +.file-selected { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--spacing-md); + background: var(--surface); + border: 1px solid var(--success); + border-radius: var(--spacing-sm); + margin-top: var(--spacing-sm); +} + +.file-name { + font-size: 14px; + color: var(--on-surface); + font-weight: 500; +} + +.file-remove { + background: var(--error); + color: white; + border: none; + border-radius: 50%; + width: 24px; + height: 24px; + cursor: pointer; + font-size: 16px; + display: flex; + align-items: center; + justify-content: center; +} + +.file-remove:hover { + background: #dc2626; +} + /* Responsive Design */ @media (max-width: 768px) { .toast { @@ -243,7 +328,7 @@ document.body.setAttribute('data-user-balance', '{{ user.wallet_balance }}');
-
+ {% csrf_token %} @@ -315,6 +400,30 @@ document.body.setAttribute('data-user-balance', '{{ user.wallet_balance }}'); {{ field.label }} + + {% elif field.type == 'file' %} +
+ + + +
{% endif %} {% if field.help_text %} @@ -365,4 +474,81 @@ document.body.setAttribute('data-user-balance', '{{ user.wallet_balance }}'); {% block extra_js %} + {% endblock %} \ No newline at end of file diff --git a/docs_update_summary.txt b/docs_update_summary.txt index ebe52b8..8485d24 100644 --- a/docs_update_summary.txt +++ b/docs_update_summary.txt @@ -1,17 +1,19 @@ === Documentation Auto-Update Summary === -Update Date: 2025-07-31 19:05:23 +Update Date: 2025-07-31 19:10:15 Recent Commits: + - cf8583d 💼 Add Job Posting Generator agent with comprehensive form schema - 27a1c7d 📚 Auto-update documentation after agents app implementation - 5eba8fe 🚀 Complete agents app implementation with social ads frontend - - 8097f6f ✨ Complete agent template enhancement and repository cleanup -Documentation Changes: - - CLAUDE.md +Agents Changes: + - agents/management/commands/create_job_posting_agent.py + - agents/views.py Backend Changes: - docs_update_summary.txt -No documentation files required updates. +Updated Documentation Files: + - /home/amit/projects/quantum_ai_v2/CLAUDE.md === End Summary === \ No newline at end of file diff --git a/static/js/agents-core.js b/static/js/agents-core.js index 4378293..9db4f60 100644 --- a/static/js/agents-core.js +++ b/static/js/agents-core.js @@ -69,58 +69,18 @@ class AgentsCore extends WorkflowsCore { try { const formData = new FormData(form); - // Extract all form data dynamically - const inputData = {}; - for (let [key, value] of formData.entries()) { - if (key !== 'csrfmiddlewaretoken') { - inputData[key] = value; - } + // 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); } - - // Get CSRF token - const csrfToken = formData.get('csrfmiddlewaretoken'); - - // 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 if fee was charged - if (data.fee_charged) { - const currentBalance = parseFloat(document.body.getAttribute('data-user-balance') || '0'); - const newBalance = currentBalance - parseFloat(data.fee_charged); - - // Update the wallet balance display - this.constructor.updateWalletBalance(newBalance); - - // Update the data attribute for future calculations - document.body.setAttribute('data-user-balance', newBalance.toString()); - } - - // Display results - this.displayExecutionResults(data); - - this.constructor.showToast('✅ Agent executed successfully!', 'success'); - } catch (error) { console.error('Agent execution error:', error); this.constructor.hideProcessing(); @@ -129,6 +89,190 @@ class AgentsCore extends WorkflowsCore { } } + /** + * 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'); + + // 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 if fee was charged + if (data.fee_charged) { + const currentBalance = parseFloat(document.body.getAttribute('data-user-balance') || '0'); + const newBalance = currentBalance - parseFloat(data.fee_charged); + + // Update the wallet balance display + this.constructor.updateWalletBalance(newBalance); + + // Update the data attribute for future calculations + document.body.setAttribute('data-user-balance', newBalance.toString()); + } + + // 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); + document.body.setAttribute('data-user-balance', data.new_balance.toString()); + } + } + } catch (error) { + console.error('Wallet deduction error:', error); + // Continue execution even if wallet update fails + } + } + + /** + * Display results from file processing + */ + displayFileProcessingResults(data) { + const resultsContainer = document.getElementById('resultsContainer'); + const resultsContent = document.getElementById('resultsContent'); + + if (!resultsContainer || !resultsContent) return; + + let content = ''; + + // Handle different response formats from file processing + if (data && typeof data === 'object') { + if (data.sections) { + // Multi-section response + content = Object.entries(data.sections).map(([section, text]) => { + return `## ${section.replace('_', ' ').toUpperCase()}\n\n${text}`; + }).join('\n\n'); + } else if (data.output || data.result || data.summary) { + content = data.output || data.result || data.summary; + } else if (data.error) { + content = `Error: ${data.error}`; + } else { + content = JSON.stringify(data, null, 2); + } + } else if (typeof data === 'string') { + content = data; + } else { + content = 'File processed 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(); + } + /** * Display results from agent execution */