diff --git a/AGENT_CREATION_GUIDE.md b/AGENT_CREATION_GUIDE.md new file mode 100644 index 0000000..96fe56b --- /dev/null +++ b/AGENT_CREATION_GUIDE.md @@ -0,0 +1,487 @@ +# Agent Creation Guide using Template Prototype + +This guide provides step-by-step instructions for creating new Django agents using the `agent_template_prototype.html` template system. + +## Overview + +The NetCop Hub uses a standardized template prototype (`agent_template_prototype.html`) that provides: +- Complete CSS framework with custom properties +- JavaScript utilities for common functions +- Consistent UI components (wallet card, processing status, quick access panel) +- Responsive design and accessibility features +- Toast notifications and status management + +## Quick Start + +### 1. Create Django Agent Structure + +```bash +# Use the built-in Django command +python manage.py create_agent + +# Or create manually: +mkdir your_agent_name +cd your_agent_name +touch __init__.py models.py views.py processor.py urls.py admin.py +mkdir templates +mkdir templates/your_agent_name +``` + +### 2. Required Files Checklist + +- `__init__.py` - Empty Python package file +- `models.py` - Request model with base fields + agent-specific fields +- `processor.py` - Inherits from BaseAgentProcessor +- `views.py` - Detail view with form handling and status polling +- `urls.py` - URL patterns for detail and status endpoints +- `admin.py` - Django admin configuration +- `templates/your_agent_name/detail.html` - Converted template from prototype + +## Template Conversion Process + +### Step 1: Copy Base Structure from Prototype + +Start with the `agent_template_prototype.html` and convert to Django template format: + +```html +{% extends 'base.html' %} +{% load static %} + +{% block title %}Your Agent Name - NetCop Hub{% endblock %} + +{% block content %} +
+ + + + + +
+
+

Your Agent Name

+

Description of what your agent does

+
+
+ {% include 'components/wallet_card.html' %} +
+
+ + + +
+ + + +{% endblock %} +``` + +### Step 2: Replace Placeholder Sections + +Replace the placeholder sections with your agent-specific content: + +**Agent Grid Section (lines 704-714 in prototype):** +```html +
+
+
+

+ ๐ŸŽฏ + Your Agent Form +

+
+
+ {% if user.is_authenticated %} +
+ {% csrf_token %} + +
+ {% else %} +

Please login to use this agent.

+ {% endif %} +
+
+ + +
+ +
+
+``` + +**Results Section (lines 763-774 in prototype):** +```html +
+ +
+``` + +## Django Implementation Patterns + +### Models Structure + +Follow this pattern for all agent models: + +```python +from django.db import models +from django.contrib.auth import get_user_model +import uuid + +User = get_user_model() + +class YourAgentRequest(models.Model): + """Your Agent request model""" + + # Base request fields (required for all agents) + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + user = models.ForeignKey(User, on_delete=models.CASCADE) + status = models.CharField(max_length=20, choices=[ + ('pending', 'Pending'), + ('processing', 'Processing'), + ('completed', 'Completed'), + ('failed', 'Failed'), + ], default='pending') + cost = models.DecimalField(max_digits=10, decimal_places=2, default=3.00) + created_at = models.DateTimeField(auto_now_add=True) + processed_at = models.DateTimeField(null=True, blank=True) + + # Agent-specific fields + your_field = models.CharField(max_length=200, help_text="Field description") + # ... more fields + + # Result fields + result_content = models.TextField(blank=True, help_text="Generated results") + + class Meta: + verbose_name = "Your Agent Request" + verbose_name_plural = "Your Agent Requests" + ordering = ['-created_at'] + + def __str__(self): + return f"Your Agent - {self.your_field}" +``` + +### Views Pattern + +```python +from django.shortcuts import render +from django.contrib.auth.decorators import login_required +from django.http import JsonResponse +from django.views.decorators.http import require_http_methods +from .models import YourAgentRequest +from .processor import YourAgentProcessor + +@login_required +def your_agent_detail(request): + if request.method == 'POST': + # Form validation + if not request.POST.get('required_field'): + return JsonResponse({'error': 'Required field is missing'}, status=400) + + # Check wallet balance + if request.user.wallet_balance < 3.00: + return JsonResponse({'error': 'Insufficient balance'}, status=400) + + # Create request + agent_request = YourAgentRequest.objects.create( + user=request.user, + your_field=request.POST.get('your_field'), + # ... other fields + ) + + # Process with agent + processor = YourAgentProcessor() + processor.process_request(agent_request) + + return JsonResponse({'success': True, 'request_id': str(agent_request.id)}) + + return render(request, 'your_agent/detail.html', { + 'agent_cost': 3.00 + }) + +@require_http_methods(["GET"]) +def your_agent_status(request, request_id): + try: + agent_request = YourAgentRequest.objects.get(id=request_id, user=request.user) + return JsonResponse({ + 'status': agent_request.status, + 'result': agent_request.result_content if agent_request.status == 'completed' else None + }) + except YourAgentRequest.DoesNotExist: + return JsonResponse({'error': 'Request not found'}, status=404) +``` + +### Processor Pattern + +```python +from agent_base.processors import BaseAgentProcessor + +class YourAgentProcessor(BaseAgentProcessor): + def get_cost(self): + return 3.00 + + def prepare_webhook_data(self, request_obj): + return { + 'your_field': request_obj.your_field, + # ... other fields + } + + def process_webhook_response(self, request_obj, response_data): + if response_data.get('success'): + request_obj.result_content = response_data.get('result', '') + request_obj.status = 'completed' + else: + request_obj.status = 'failed' + + request_obj.save() +``` + +## Form Integration with Template + +### HTML Form Structure + +```html +
+ {% csrf_token %} + +
+
+ + +
+ + + + +
+
+``` + +### JavaScript Form Handling + +Add this to your template's JavaScript section: + +```javascript +// Form submission handling +document.getElementById('agentForm').addEventListener('submit', function(e) { + e.preventDefault(); + + const formData = new FormData(this); + + // Show processing status + showProcessing(); + + fetch('', { + method: 'POST', + body: formData, + headers: { + 'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value + } + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + pollStatus(data.request_id); + } else { + hideProcessing(); + showToast(data.error || 'Request failed', 'error'); + } + }) + .catch(error => { + hideProcessing(); + showToast('Network error occurred', 'error'); + }); +}); + +// Status polling +function pollStatus(requestId) { + const poll = setInterval(() => { + fetch(`status/${requestId}/`) + .then(response => response.json()) + .then(data => { + if (data.status === 'completed') { + clearInterval(poll); + hideProcessing(); + displayResults(data.result); + } else if (data.status === 'failed') { + clearInterval(poll); + hideProcessing(); + showToast('Processing failed', 'error'); + } + }); + }, 2000); +} + +// Display results +function displayResults(result) { + document.getElementById('resultsContent').textContent = result; + document.getElementById('resultsSection').style.display = 'block'; + showToast('Results ready!', 'success'); +} +``` + +## URL Configuration + +### App URLs (`your_agent/urls.py`) + +```python +from django.urls import path +from . import views + +app_name = 'your_agent' + +urlpatterns = [ + path('', views.your_agent_detail, name='detail'), + path('status//', views.your_agent_status, name='status'), +] +``` + +### Main URLs (add to `netcop_hub/urls.py`) + +```python +path('agents/your-agent-slug/', include('your_agent.urls')), +``` + +### Settings (add to `INSTALLED_APPS`) + +```python +INSTALLED_APPS = [ + # ... existing apps + 'your_agent', +] +``` + +## Database and Marketplace Integration + +### 1. Create and Apply Migrations + +```bash +python manage.py makemigrations your_agent +python manage.py migrate +``` + +### 2. Add to Marketplace Catalog + +```bash +python manage.py shell +``` + +```python +from agent_base.models import BaseAgent + +BaseAgent.objects.create( + name="Your Agent Name", + slug="your-agent-slug", + description="Description of what your agent does...", + category="content", # or appropriate category + price=3.00, + icon="๐ŸŽฏ", # appropriate emoji + agent_type="webhook" +) +``` + +## Common Components Reference + +### Available CSS Components + +- **Layout**: `.agent-container`, `.agent-header`, `.agent-grid` +- **Widgets**: `.agent-widget`, `.widget-header`, `.widget-title`, `.widget-content` +- **Sizes**: `.widget-large`, `.widget-small`, `.widget-wide` +- **Wallet**: `.wallet-card` (use `{% include 'components/wallet_card.html' %}`) +- **Status**: `.processing-status`, `.status-icon`, `.status-title` +- **Utilities**: `.info-list`, `.placeholder-section` + +### Available JavaScript Functions + +- `showToast(message, type)` - Display notifications +- `showProcessing()` / `hideProcessing()` - Processing status +- `copyToClipboard(text, message)` - Copy functionality +- `downloadAsFile(text, filename, message)` - Download functionality +- `toggleQuickAgents()` - Quick access panel +- `updateWalletBalance(balance)` - Update wallet display + +## Testing and Verification + +### 1. Django Check + +```bash +python manage.py check +``` + +### 2. URL Resolution Test + +```bash +python manage.py shell +``` + +```python +from django.urls import reverse +print(reverse('your_agent:detail')) +``` + +### 3. Agent Marketplace Test + +Visit `/marketplace/` to verify your agent appears in the catalog. + +## Best Practices + +1. **Always inherit CSS and JavaScript** from the prototype - don't modify the template utilities +2. **Use consistent naming** - follow the existing patterns for models, views, and URLs +3. **Preserve accessibility** - keep ARIA attributes and keyboard navigation +4. **Test responsive design** - verify mobile functionality +5. **Follow security practices** - use CSRF tokens, validate inputs, check permissions +6. **Maintain consistent pricing** - use decimal values with 2 places +7. **Handle errors gracefully** - provide meaningful error messages via toast notifications + +## Troubleshooting + +### Common Issues + +1. **CSS not loading**: Ensure all CSS from prototype is copied within ` + + +
+ +
+
+

Sample Agent Title

+

This is the agent subtitle describing what this agent does

+
+
+ +
+
+

Your Wallet

+
๐Ÿ’ณ
+
+
+
+ 25.50 AED +
+
Available Balance
+
+ +
+
+
+ + +
+ + + + + +
+
+
๐ŸŽฏ Agent Grid Section
+
+ This section is where each agent will place their unique form inputs, configuration options, and interaction elements. +
+
+ Examples: File upload forms, text inputs, dropdowns, radio buttons, etc. +
+
+ + +
+
+

+ โ„น๏ธ + How It Works +

+
+
+
    +
  1. Enter your requirements
  2. +
  3. Configure options
  4. +
  5. Process with AI
  6. +
  7. Get results instantly
  8. +
+ + + +
+
+
+ + +
+
+
+

+ โณ + Processing Status +

+
+
+
โณ
+
Processing your request...
+
Please wait while we analyze your data...
+
+
+
+ + +
+
+
๐Ÿ“Š Results Section
+
+ This section is where each agent will display their specific results format, content structure, and action buttons. +
+
+ Examples: Formatted reports, analysis results, generated content, download buttons, etc. +
+
+
+ + +
+
+
+

+ ๐Ÿงช + Demo Controls +

+
+
+

+ Test the common components and interactions: +

+
+ + + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/email_writer/__init__.py b/email_writer/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/email_writer/admin.py b/email_writer/admin.py new file mode 100644 index 0000000..058f2ba --- /dev/null +++ b/email_writer/admin.py @@ -0,0 +1,29 @@ +from django.contrib import admin +from .models import EmailWriterRequest + + +@admin.register(EmailWriterRequest) +class EmailWriterRequestAdmin(admin.ModelAdmin): + list_display = ['id', 'user', 'email_type', 'recipient', 'tone', 'status', 'created_at'] + list_filter = ['email_type', 'tone', 'length', 'status', 'created_at'] + search_fields = ['user__username', 'recipient', 'main_message'] + readonly_fields = ['id', 'created_at', 'processed_at'] + + fieldsets = ( + ('Request Information', { + 'fields': ('id', 'user', 'status', 'cost', 'created_at', 'processed_at') + }), + ('Email Details', { + 'fields': ('email_type', 'recipient', 'subject', 'main_message', 'tone', 'length') + }), + ('Results', { + 'fields': ('email_content',), + 'classes': ('collapse',) + }) + ) + + def get_readonly_fields(self, request, obj=None): + readonly = list(self.readonly_fields) + if obj: # editing an existing object + readonly.extend(['user', 'email_type', 'recipient', 'main_message']) + return readonly \ No newline at end of file diff --git a/email_writer/migrations/0001_initial.py b/email_writer/migrations/0001_initial.py new file mode 100644 index 0000000..d084358 --- /dev/null +++ b/email_writer/migrations/0001_initial.py @@ -0,0 +1,129 @@ +# Generated by Django 5.2.4 on 2025-07-24 20:50 + +import django.db.models.deletion +import uuid +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="EmailWriterRequest", + fields=[ + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ( + "status", + models.CharField( + choices=[ + ("pending", "Pending"), + ("processing", "Processing"), + ("completed", "Completed"), + ("failed", "Failed"), + ], + default="pending", + max_length=20, + ), + ), + ( + "cost", + models.DecimalField(decimal_places=2, default=3.0, max_digits=10), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("processed_at", models.DateTimeField(blank=True, null=True)), + ( + "email_type", + models.CharField( + choices=[ + ("business", "Business Email"), + ("follow_up", "Follow-up Email"), + ("complaint", "Complaint Email"), + ("thank_you", "Thank You Email"), + ("introduction", "Introduction Email"), + ("meeting_request", "Meeting Request"), + ("apology", "Apology Email"), + ("announcement", "Announcement"), + ], + help_text="Type of email to generate", + max_length=50, + ), + ), + ( + "recipient", + models.CharField( + help_text="Who the email is being sent to", max_length=200 + ), + ), + ( + "subject", + models.CharField( + blank=True, + help_text="Email subject (optional - can be auto-generated)", + max_length=200, + ), + ), + ( + "main_message", + models.TextField(help_text="Main content/purpose of the email"), + ), + ( + "tone", + models.CharField( + choices=[ + ("professional", "Professional"), + ("friendly", "Friendly"), + ("formal", "Formal"), + ("casual", "Casual"), + ], + default="professional", + help_text="Tone of the email", + max_length=30, + ), + ), + ( + "length", + models.CharField( + choices=[ + ("short", "Short (1-2 paragraphs)"), + ("medium", "Medium (3-4 paragraphs)"), + ("long", "Long (5+ paragraphs)"), + ], + default="medium", + help_text="Desired length of the email", + max_length=20, + ), + ), + ( + "email_content", + models.TextField(blank=True, help_text="Generated email content"), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "verbose_name": "Email Writer Request", + "verbose_name_plural": "Email Writer Requests", + "ordering": ["-created_at"], + }, + ), + ] diff --git a/email_writer/migrations/__init__.py b/email_writer/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/email_writer/models.py b/email_writer/models.py new file mode 100644 index 0000000..927657b --- /dev/null +++ b/email_writer/models.py @@ -0,0 +1,90 @@ +from django.db import models +from django.contrib.auth import get_user_model +import uuid + +User = get_user_model() + + +class EmailWriterRequest(models.Model): + """Email Writer agent request model""" + + # Base request fields + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + user = models.ForeignKey(User, on_delete=models.CASCADE) + status = models.CharField(max_length=20, choices=[ + ('pending', 'Pending'), + ('processing', 'Processing'), + ('completed', 'Completed'), + ('failed', 'Failed'), + ], default='pending') + cost = models.DecimalField(max_digits=10, decimal_places=2, default=3.00) + created_at = models.DateTimeField(auto_now_add=True) + processed_at = models.DateTimeField(null=True, blank=True) + + # Email content fields + email_type = models.CharField( + max_length=50, + choices=[ + ('business', 'Business Email'), + ('follow_up', 'Follow-up Email'), + ('complaint', 'Complaint Email'), + ('thank_you', 'Thank You Email'), + ('introduction', 'Introduction Email'), + ('meeting_request', 'Meeting Request'), + ('apology', 'Apology Email'), + ('announcement', 'Announcement'), + ], + help_text="Type of email to generate" + ) + + recipient = models.CharField( + max_length=200, + help_text="Who the email is being sent to" + ) + + subject = models.CharField( + max_length=200, + blank=True, + help_text="Email subject (optional - can be auto-generated)" + ) + + main_message = models.TextField( + help_text="Main content/purpose of the email" + ) + + tone = models.CharField( + max_length=30, + choices=[ + ('professional', 'Professional'), + ('friendly', 'Friendly'), + ('formal', 'Formal'), + ('casual', 'Casual'), + ], + default='professional', + help_text="Tone of the email" + ) + + length = models.CharField( + max_length=20, + choices=[ + ('short', 'Short (1-2 paragraphs)'), + ('medium', 'Medium (3-4 paragraphs)'), + ('long', 'Long (5+ paragraphs)'), + ], + default='medium', + help_text="Desired length of the email" + ) + + # Result fields + email_content = models.TextField( + blank=True, + help_text="Generated email content" + ) + + class Meta: + verbose_name = "Email Writer Request" + verbose_name_plural = "Email Writer Requests" + ordering = ['-created_at'] + + def __str__(self): + return f"Email Writer - {self.email_type} for {self.recipient}" \ No newline at end of file diff --git a/email_writer/processor.py b/email_writer/processor.py new file mode 100644 index 0000000..5aca61b --- /dev/null +++ b/email_writer/processor.py @@ -0,0 +1,79 @@ +import json +from agent_base.processors import BaseAgentProcessor +from .models import EmailWriterRequest + + +class EmailWriterProcessor(BaseAgentProcessor): + """Email Writer agent processor""" + + model_class = EmailWriterRequest + agent_name = "Email Writer" + cost = 3.00 # AED per request + + def prepare_webhook_data(self, request_obj): + """Prepare data for webhook processing""" + return { + 'email_type': request_obj.email_type, + 'recipient': request_obj.recipient, + 'subject': request_obj.subject, + 'main_message': request_obj.main_message, + 'tone': request_obj.tone, + 'length': request_obj.length, + } + + def process_webhook_response(self, request_obj, webhook_response): + """Process webhook response and update request object""" + try: + if isinstance(webhook_response, str): + response_data = json.loads(webhook_response) + else: + response_data = webhook_response + + # Extract email content from response + email_content = "" + + # Try different possible response formats + if 'email_content' in response_data: + email_content = response_data['email_content'] + elif 'content' in response_data: + email_content = response_data['content'] + elif 'output' in response_data: + email_content = response_data['output'] + elif 'generated_email' in response_data: + email_content = response_data['generated_email'] + elif isinstance(response_data, str): + email_content = response_data + else: + # If no specific field found, try to extract text + email_content = str(response_data) + + # Update request object + request_obj.email_content = email_content + request_obj.save() + + return { + 'success': True, + 'email_content': email_content, + 'status': 'completed' + } + + except Exception as e: + return { + 'success': False, + 'error': f"Failed to process email generation: {str(e)}", + 'status': 'failed' + } + + def get_result_summary(self, request_obj): + """Get a summary of the results for display""" + if request_obj.email_content: + return { + 'email_type': request_obj.get_email_type_display(), + 'recipient': request_obj.recipient, + 'tone': request_obj.get_tone_display(), + 'length': request_obj.get_length_display(), + 'email_content': request_obj.email_content, + 'has_subject': bool(request_obj.subject), + 'subject': request_obj.subject + } + return None \ No newline at end of file diff --git a/email_writer/templates/email_writer/detail.html b/email_writer/templates/email_writer/detail.html new file mode 100644 index 0000000..e434768 --- /dev/null +++ b/email_writer/templates/email_writer/detail.html @@ -0,0 +1,1334 @@ +{% extends 'base.html' %} +{% load static %} + +{% block title %}Email Writer Agent - NetCop AI Hub{% endblock %} + +{% block extra_css %} + + + + +{% endblock %} + +{% block content %} +
+ + {% include "components/agent_header.html" with agent_title="Email Writer" agent_subtitle="Generate professional emails with AI-powered content creation" %} + + + {% include "components/quick_agents_panel.html" %} + + + {% if messages %} + {% for message in messages %} +
+ {{ message }} +
+ {% endfor %} + {% endif %} + + +
+ +
+
+

+ ๐Ÿ“ง + Email Details +

+
+
+ +
+ {% csrf_token %} + + +
+

๐Ÿ“ฌ Email Information

+ +
+ + +
Choose the type of email you want to generate
+ +
+ +
+ + +
Enter the name or title of the person/team you're writing to
+ +
+ +
+ + +
Optional - we can generate an appropriate subject line if left blank
+
+
+ + +
+

โœ๏ธ Content & Style

+ +
+ + +
Describe the key points and purpose of your email
+ +
+ +
+ + +
Choose the appropriate tone for your email
+
+ +
+ + +
How detailed should the email be?
+
+
+ + +
+ {% if user.is_authenticated %} + {% if user.wallet_balance >= 3.00 %} + + {% else %} +
+ Insufficient balance! You need 3.00 AED. +
+ + ๐Ÿ’ฐ Top Up Wallet + + {% endif %} + {% else %} + + ๐Ÿ”‘ Login to Continue + + {% endif %} +
+
+ +
+
+ + +
+
+

+ โ„น๏ธ + How It Works +

+
+
+
    +
  1. Choose email type and recipient
  2. +
  3. Describe your message and purpose
  4. +
  5. Select tone and length preferences
  6. +
  7. Get professional AI-generated email
  8. +
+ + + +
+
+
+ + + {% include "components/processing_status.html" with status_title="Generating Email..." status_text="Creating professional email content..." %} + + + {% include "components/results_container.html" with results_title="Generated Email" %} +
+{% endblock %} + +{% block extra_js %} + +{% endblock %} \ No newline at end of file diff --git a/email_writer/urls.py b/email_writer/urls.py new file mode 100644 index 0000000..d705f90 --- /dev/null +++ b/email_writer/urls.py @@ -0,0 +1,9 @@ +from django.urls import path +from . import views + +app_name = 'email_writer' + +urlpatterns = [ + path('', views.email_writer_detail, name='detail'), + path('status//', views.email_writer_status, name='status'), +] \ No newline at end of file diff --git a/email_writer/views.py b/email_writer/views.py new file mode 100644 index 0000000..8db5934 --- /dev/null +++ b/email_writer/views.py @@ -0,0 +1,140 @@ +import json +from django.shortcuts import render +from django.contrib.auth.decorators import login_required +from django.http import JsonResponse +from django.views.decorators.csrf import csrf_exempt +from django.views.decorators.http import require_http_methods +from django.shortcuts import get_object_or_404 +from django.contrib import messages + +from .models import EmailWriterRequest +from .processor import EmailWriterProcessor + + +def email_writer_detail(request): + """Email Writer agent detail page""" + context = { + 'agent_title': 'Email Writer', + 'agent_subtitle': 'Generate professional emails with AI-powered content creation', + 'page_title': 'Email Writer Agent - NetCop AI Hub' + } + + if request.method == 'POST': + if not request.user.is_authenticated: + return JsonResponse({'error': 'Authentication required'}, status=401) + + # Check if this is an AJAX request + if request.headers.get('X-Requested-With') == 'XMLHttpRequest': + try: + # Validate form data + email_type = request.POST.get('email_type', '').strip() + recipient = request.POST.get('recipient', '').strip() + main_message = request.POST.get('main_message', '').strip() + tone = request.POST.get('tone', 'professional') + length = request.POST.get('length', 'medium') + subject = request.POST.get('subject', '').strip() + + # Basic validation + if not email_type or not recipient or not main_message: + return JsonResponse({ + 'error': 'Please fill in all required fields', + 'success': False + }) + + if len(main_message) < 10: + return JsonResponse({ + 'error': 'Main message must be at least 10 characters long', + 'success': False + }) + + # Initialize processor + processor = EmailWriterProcessor() + + # Check wallet balance + if not processor.check_wallet_balance(request.user): + return JsonResponse({ + 'error': f'Insufficient wallet balance. You need {processor.cost:.2f} AED.', + 'success': False + }) + + # Create request object + email_request = EmailWriterRequest.objects.create( + user=request.user, + email_type=email_type, + recipient=recipient, + subject=subject, + main_message=main_message, + tone=tone, + length=length, + status='pending' + ) + + # Process the request + try: + result = processor.process_request(email_request) + + if result.get('success'): + return JsonResponse({ + 'success': True, + 'request_id': email_request.id, + 'message': 'Email generation started successfully', + 'wallet_balance': float(request.user.wallet_balance) + }) + else: + return JsonResponse({ + 'error': result.get('error', 'Failed to process email generation'), + 'success': False + }) + + except Exception as e: + return JsonResponse({ + 'error': f'Processing error: {str(e)}', + 'success': False + }) + + except Exception as e: + return JsonResponse({ + 'error': f'Request error: {str(e)}', + 'success': False + }) + else: + # Handle regular form submission (non-AJAX) + messages.error(request, 'Please enable JavaScript for the best experience.') + + return render(request, 'email_writer/detail.html', context) + + +@require_http_methods(["GET"]) +def email_writer_status(request, request_id): + """Check status of email generation request""" + if not request.user.is_authenticated: + return JsonResponse({'error': 'Authentication required'}, status=401) + + try: + email_request = get_object_or_404( + EmailWriterRequest, + id=request_id, + user=request.user + ) + + processor = EmailWriterProcessor() + status_data = processor.get_request_status(email_request) + + # Add wallet balance to response + status_data['wallet_balance'] = float(request.user.wallet_balance) + + # If completed, include the email content + if status_data.get('status') == 'completed' and email_request.email_content: + status_data['email_content'] = email_request.email_content + status_data['email_type'] = email_request.get_email_type_display() + status_data['recipient'] = email_request.recipient + status_data['tone'] = email_request.get_tone_display() + status_data['length'] = email_request.get_length_display() + status_data['subject'] = email_request.subject + + return JsonResponse(status_data) + + except EmailWriterRequest.DoesNotExist: + return JsonResponse({'error': 'Request not found'}, status=404) + except Exception as e: + return JsonResponse({'error': str(e)}, status=500) \ No newline at end of file diff --git a/netcop_hub/settings.py b/netcop_hub/settings.py index af74794..f6624ab 100644 --- a/netcop_hub/settings.py +++ b/netcop_hub/settings.py @@ -67,6 +67,7 @@ INSTALLED_APPS = [ 'data_analyzer', 'job_posting_generator', 'social_ads_generator', + 'email_writer', 'five_whys_analyzer', ] diff --git a/netcop_hub/urls.py b/netcop_hub/urls.py index cfcba3a..22b04ac 100644 --- a/netcop_hub/urls.py +++ b/netcop_hub/urls.py @@ -28,6 +28,7 @@ urlpatterns = [ path('agents/data-analyzer/', include('data_analyzer.urls')), path('agents/job-posting-generator/', include('job_posting_generator.urls')), path('agents/social-ads-generator/', include('social_ads_generator.urls')), + path('agents/email-writer/', include('email_writer.urls')), path('agents/five-whys-analyzer/', include('five_whys_analyzer.urls')), path('', include('core.urls')), ]