diff --git a/job_posting_generator/__init__.py b/job_posting_generator/__init__.py
new file mode 100644
index 0000000..3b51fe4
--- /dev/null
+++ b/job_posting_generator/__init__.py
@@ -0,0 +1 @@
+# Job Posting Generator Agent App
\ No newline at end of file
diff --git a/job_posting_generator/admin.py b/job_posting_generator/admin.py
new file mode 100644
index 0000000..9185711
--- /dev/null
+++ b/job_posting_generator/admin.py
@@ -0,0 +1,19 @@
+from django.contrib import admin
+from .models import JobPostingGeneratorRequest, JobPostingGeneratorResponse
+
+
+@admin.register(JobPostingGeneratorRequest)
+class JobPostingGeneratorRequestAdmin(admin.ModelAdmin):
+ list_display = ['id', 'user', 'status', 'created_at', 'cost']
+ list_filter = ['status', 'created_at']
+ search_fields = ['user__email', 'user__username']
+ readonly_fields = ['id', 'created_at', 'processed_at']
+ ordering = ['-created_at']
+
+
+@admin.register(JobPostingGeneratorResponse)
+class JobPostingGeneratorResponseAdmin(admin.ModelAdmin):
+ list_display = ['id', 'request', 'success', 'created_at']
+ list_filter = ['success', 'created_at']
+ readonly_fields = ['id', 'created_at']
+ ordering = ['-created_at']
\ No newline at end of file
diff --git a/job_posting_generator/apps.py b/job_posting_generator/apps.py
new file mode 100644
index 0000000..b3514ef
--- /dev/null
+++ b/job_posting_generator/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class JobPostingGeneratorConfig(AppConfig):
+ default_auto_field = 'django.db.models.BigAutoField'
+ name = 'job_posting_generator'
\ No newline at end of file
diff --git a/job_posting_generator/migrations/0001_initial.py b/job_posting_generator/migrations/0001_initial.py
new file mode 100644
index 0000000..6321888
--- /dev/null
+++ b/job_posting_generator/migrations/0001_initial.py
@@ -0,0 +1,64 @@
+# Generated by Django 5.2.4 on 2025-07-10 11:15
+
+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 = [
+ ('agent_base', '0001_initial'),
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='JobPostingGeneratorRequest',
+ 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, max_digits=10)),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('processed_at', models.DateTimeField(blank=True, null=True)),
+ ('job_title', models.CharField(max_length=200)),
+ ('company_name', models.CharField(max_length=200)),
+ ('job_description', models.TextField()),
+ ('seniority_level', models.CharField(choices=[('entry', 'Entry Level (0-2 years)'), ('mid', 'Mid Level (2-5 years)'), ('senior', 'Senior Level (5-8 years)'), ('lead', 'Lead/Principal (8+ years)'), ('executive', 'Executive/C-Level')], max_length=20)),
+ ('contract_type', models.CharField(choices=[('full-time', 'Full-time'), ('part-time', 'Part-time'), ('contract', 'Contract'), ('freelance', 'Freelance'), ('internship', 'Internship')], max_length=20)),
+ ('location', models.CharField(max_length=200)),
+ ('language', models.CharField(choices=[('English', 'English'), ('Arabic', 'Arabic (العربية)'), ('Spanish', 'Spanish (Español)'), ('French', 'French (Français)'), ('German', 'German (Deutsch)')], default='English', max_length=20)),
+ ('company_website', models.URLField(blank=True)),
+ ('how_to_apply', models.TextField(blank=True)),
+ ('agent', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='agent_base.baseagent')),
+ ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ],
+ options={
+ 'verbose_name': 'Job Posting Generator Request',
+ 'verbose_name_plural': 'Job Posting Generator Requests',
+ 'db_table': 'job_posting_generator_requests',
+ },
+ ),
+ migrations.CreateModel(
+ name='JobPostingGeneratorResponse',
+ fields=[
+ ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
+ ('success', models.BooleanField(default=False)),
+ ('error_message', models.TextField(blank=True)),
+ ('processing_time', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('job_posting_content', models.TextField(blank=True)),
+ ('formatted_posting', models.TextField(blank=True)),
+ ('raw_response', models.JSONField(blank=True, default=dict)),
+ ('request', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='response', to='job_posting_generator.jobpostinggeneratorrequest')),
+ ],
+ options={
+ 'verbose_name': 'Job Posting Generator Response',
+ 'verbose_name_plural': 'Job Posting Generator Responses',
+ 'db_table': 'job_posting_generator_responses',
+ },
+ ),
+ ]
diff --git a/job_posting_generator/migrations/__init__.py b/job_posting_generator/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/job_posting_generator/models.py b/job_posting_generator/models.py
new file mode 100644
index 0000000..a013bfb
--- /dev/null
+++ b/job_posting_generator/models.py
@@ -0,0 +1,75 @@
+from django.db import models
+from decimal import Decimal
+from agent_base.models import BaseAgentRequest, BaseAgentResponse
+
+
+class JobPostingGeneratorRequest(BaseAgentRequest):
+ """Job Posting Generator request tracking"""
+
+ # Required job details
+ job_title = models.CharField(max_length=200)
+ company_name = models.CharField(max_length=200)
+ job_description = models.TextField()
+ seniority_level = models.CharField(
+ max_length=20,
+ choices=[
+ ('entry', 'Entry Level (0-2 years)'),
+ ('mid', 'Mid Level (2-5 years)'),
+ ('senior', 'Senior Level (5-8 years)'),
+ ('lead', 'Lead/Principal (8+ years)'),
+ ('executive', 'Executive/C-Level'),
+ ]
+ )
+ contract_type = models.CharField(
+ max_length=20,
+ choices=[
+ ('full-time', 'Full-time'),
+ ('part-time', 'Part-time'),
+ ('contract', 'Contract'),
+ ('freelance', 'Freelance'),
+ ('internship', 'Internship'),
+ ]
+ )
+ location = models.CharField(max_length=200)
+
+ # Optional fields
+ language = models.CharField(
+ max_length=20,
+ choices=[
+ ('English', 'English'),
+ ('Arabic', 'Arabic (العربية)'),
+ ('Spanish', 'Spanish (Español)'),
+ ('French', 'French (Français)'),
+ ('German', 'German (Deutsch)'),
+ ],
+ default='English'
+ )
+ company_website = models.URLField(blank=True)
+ how_to_apply = models.TextField(blank=True)
+
+
+ class Meta:
+ db_table = 'job_posting_generator_requests'
+ verbose_name = 'Job Posting Generator Request'
+ verbose_name_plural = 'Job Posting Generator Requests'
+
+
+class JobPostingGeneratorResponse(BaseAgentResponse):
+ """Job Posting Generator response storage"""
+
+ request = models.OneToOneField(
+ JobPostingGeneratorRequest,
+ on_delete=models.CASCADE,
+ related_name='response'
+ )
+
+ # Agent-specific response fields
+ job_posting_content = models.TextField(blank=True)
+ formatted_posting = models.TextField(blank=True)
+ raw_response = models.JSONField(default=dict, blank=True)
+
+
+ class Meta:
+ db_table = 'job_posting_generator_responses'
+ verbose_name = 'Job Posting Generator Response'
+ verbose_name_plural = 'Job Posting Generator Responses'
\ No newline at end of file
diff --git a/job_posting_generator/processor.py b/job_posting_generator/processor.py
new file mode 100644
index 0000000..0cf062c
--- /dev/null
+++ b/job_posting_generator/processor.py
@@ -0,0 +1,105 @@
+from agent_base.processors import StandardWebhookProcessor
+from django.utils import timezone
+from django.conf import settings
+from .models import JobPostingGeneratorRequest, JobPostingGeneratorResponse
+import json
+
+
+class JobPostingGeneratorProcessor(StandardWebhookProcessor):
+ """Webhook processor for Job Posting Generator agent"""
+
+ agent_slug = 'job-posting-generator'
+ webhook_url = settings.N8N_WEBHOOK_JOB_POSTING
+ agent_id = 'job-posting'
+
+ def prepare_message_text(self, **kwargs):
+ """Prepare detailed job posting prompt for N8N webhook"""
+ request_obj = kwargs.get('request_obj')
+ if not request_obj:
+ return "Create a professional job posting"
+
+ # Build comprehensive job posting prompt
+ prompt = f"""
+Create a professional job posting for the following position:
+
+Job Title: {request_obj.job_title}
+Company: {request_obj.company_name}
+Location: {request_obj.location}
+Contract Type: {request_obj.get_contract_type_display()}
+Seniority Level: {request_obj.get_seniority_level_display()}
+Language: {request_obj.language}
+
+Job Description:
+{request_obj.job_description}
+"""
+
+ if request_obj.company_website:
+ prompt += f"\nCompany Website: {request_obj.company_website}"
+
+ if request_obj.how_to_apply:
+ prompt += f"\n\nApplication Instructions:\n{request_obj.how_to_apply}"
+
+ prompt += "\n\nPlease create a comprehensive, professional job posting that includes all necessary sections such as job overview, responsibilities, qualifications, benefits, and clear application instructions."
+
+ return prompt
+
+ def process_response(self, response_data, request_obj):
+ """Process webhook response"""
+ try:
+ request_obj.status = 'processing'
+ request_obj.save()
+
+ # Handle array response from N8N (extract first item)
+ if isinstance(response_data, list) and len(response_data) > 0:
+ response_data = response_data[0]
+
+ # Extract job posting content
+ job_posting_content = ""
+ if isinstance(response_data, dict):
+ job_posting_content = response_data.get('output', response_data.get('text', response_data.get('content', '')))
+ elif isinstance(response_data, str):
+ job_posting_content = response_data
+
+ # Determine success based on response
+ success = bool(job_posting_content.strip()) and len(job_posting_content.strip()) > 50
+
+ # Create response object
+ response_obj = JobPostingGeneratorResponse.objects.create(
+ request=request_obj,
+ success=success,
+ processing_time=response_data.get('processing_time', 0) if isinstance(response_data, dict) else 0,
+ job_posting_content=job_posting_content,
+ formatted_posting=job_posting_content, # Same content for now
+ raw_response=response_data if isinstance(response_data, dict) else {'content': response_data}
+ )
+
+ # Only deduct wallet balance after successful processing
+ if success:
+ request_obj.user.deduct_balance(
+ request_obj.cost,
+ f"Job Posting Generator - {request_obj.job_title} at {request_obj.company_name}",
+ 'job-posting-generator'
+ )
+ print(f"{self.agent_slug}: Wallet deducted {request_obj.cost} AED for successful processing")
+
+ # Update request as completed
+ request_obj.status = 'completed' if success else 'failed'
+ request_obj.processed_at = timezone.now()
+ request_obj.save()
+
+ return response_obj
+
+ except Exception as e:
+ # Handle error
+ request_obj.status = 'failed'
+ request_obj.save()
+
+ # Create error response
+ error_response = JobPostingGeneratorResponse.objects.create(
+ request=request_obj,
+ success=False,
+ error_message=str(e),
+ processing_time=0
+ )
+
+ raise Exception(f"Failed to process Job Posting Generator response: {e}")
\ No newline at end of file
diff --git a/job_posting_generator/templates/job_posting_generator/detail.html b/job_posting_generator/templates/job_posting_generator/detail.html
new file mode 100644
index 0000000..b2fc5c4
--- /dev/null
+++ b/job_posting_generator/templates/job_posting_generator/detail.html
@@ -0,0 +1,850 @@
+{% load static %}
+
+
+
+
+
+ Job Posting Generator Agent - NetCop AI Hub
+
+
+
+
+
+
+
+
+
+
+
+ 💼 Job Posting Generator Agent
+
+
+ Create professional job postings with detailed requirements, company culture integration, and SEO optimization.
+
+
+ 💰 Cost: 4.00 AED
+
+
+
+
+ {% if messages %}
+ {% for message in messages %}
+
+ {{ message }}
+
+ {% endfor %}
+ {% endif %}
+
+
+
+
+
+
+
+
+
+
+
💳 Your Wallet
+
+
+
+ {% if user.is_authenticated %}
+ {{ user.wallet_balance|floatformat:2 }} AED
+ {% else %}
+ 0.00 AED
+ {% endif %}
+
+
+ Available Balance
+
+
+
+ {% if user.is_authenticated %}
+ {% if user.wallet_balance >= 4.00 %}
+
+ {% else %}
+
+ Insufficient balance! You need 4.00 AED.
+
+
+ 💰 Top Up Wallet
+
+ {% endif %}
+ {% else %}
+
+ 🔑 Login to Continue
+
+ {% endif %}
+
+
+
+
+
+ 💡 How it works
+
+
+ -
+ •
+ Enter job and company details
+
+ -
+ •
+ AI generates professional posting
+
+ -
+ •
+ Includes requirements & culture
+
+ -
+ •
+ SEO optimized for job boards
+
+
+
+
+
+
+
+
+
+
+
✅
+
+ Professional Job Posting
+
+
+ ✅ Complete
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/job_posting_generator/urls.py b/job_posting_generator/urls.py
new file mode 100644
index 0000000..8087a1d
--- /dev/null
+++ b/job_posting_generator/urls.py
@@ -0,0 +1,9 @@
+from django.urls import path
+from . import views
+
+app_name = 'job_posting_generator'
+
+urlpatterns = [
+ path('', views.job_posting_generator_detail, name='detail'),
+ path('status//', views.job_posting_generator_result, name='status'),
+]
\ No newline at end of file
diff --git a/job_posting_generator/views.py b/job_posting_generator/views.py
new file mode 100644
index 0000000..a5f1074
--- /dev/null
+++ b/job_posting_generator/views.py
@@ -0,0 +1,172 @@
+from django.shortcuts import render, redirect
+from django.contrib.auth.decorators import login_required
+from django.contrib import messages
+from django.http import JsonResponse
+from django.views.decorators.csrf import csrf_exempt
+from django.utils.decorators import method_decorator
+from django.views import View
+from agent_base.models import BaseAgent
+from .models import JobPostingGeneratorRequest, JobPostingGeneratorResponse
+from .processor import JobPostingGeneratorProcessor
+import json
+
+
+def job_posting_generator_detail(request):
+ """Detail page for Job Posting Generator agent"""
+ try:
+ agent = BaseAgent.objects.get(slug='job-posting-generator')
+ except BaseAgent.DoesNotExist:
+ messages.error(request, 'Job Posting Generator agent not found.')
+ return redirect('core:homepage')
+
+ if request.method == 'POST':
+ # Handle AJAX requests
+ if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
+ if not request.user.is_authenticated:
+ return JsonResponse({'error': 'Authentication required'}, status=401)
+
+ # Check wallet balance
+ if not request.user.has_sufficient_balance(agent.price):
+ return JsonResponse({'error': 'Insufficient wallet balance'}, status=400)
+
+ try:
+ # Create request object (no wallet deduction yet)
+ agent_request = JobPostingGeneratorRequest.objects.create(
+ user=request.user,
+ agent=agent,
+ cost=agent.price,
+ job_title=request.POST.get('job_title'),
+ company_name=request.POST.get('company_name'),
+ job_description=request.POST.get('job_description'),
+ seniority_level=request.POST.get('seniority_level'),
+ contract_type=request.POST.get('contract_type'),
+ location=request.POST.get('location'),
+ language=request.POST.get('language', 'English'),
+ company_website=request.POST.get('company_website', ''),
+ how_to_apply=request.POST.get('how_to_apply', ''),
+ )
+
+ # Process request
+ processor = JobPostingGeneratorProcessor()
+ result = processor.process_request(
+ request_obj=agent_request,
+ user_id=request.user.id,
+ )
+
+ # Refresh user from database to get updated wallet balance
+ request.user.refresh_from_db()
+
+ return JsonResponse({
+ 'success': True,
+ 'request_id': str(agent_request.id),
+ 'message': 'Job posting generation started',
+ 'wallet_balance': float(request.user.wallet_balance)
+ })
+
+ except Exception as e:
+ return JsonResponse({'error': str(e)}, status=500)
+
+ # Regular form submission (redirect to avoid resubmission)
+ return redirect('job_posting_generator:detail')
+
+ # GET request - show form
+ context = {
+ 'agent': agent,
+ }
+ return render(request, 'job_posting_generator/detail.html', context)
+
+
+@method_decorator(csrf_exempt, name='dispatch')
+class JobPostingGeneratorProcessView(View):
+ """Process Job Posting Generator requests"""
+
+ def post(self, request):
+ if not request.user.is_authenticated:
+ return JsonResponse({'error': 'Authentication required'}, status=401)
+
+ try:
+ # Parse request data
+ data = json.loads(request.body)
+
+ # Get agent
+ agent = BaseAgent.objects.get(slug='job-posting-generator')
+
+ # Check wallet balance
+ if not request.user.has_sufficient_balance(agent.price):
+ return JsonResponse({'error': 'Insufficient wallet balance'}, status=400)
+
+ # Create request object (no wallet deduction yet - only after successful processing)
+ agent_request = JobPostingGeneratorRequest.objects.create(
+ user=request.user,
+ agent=agent,
+ cost=agent.price,
+ job_title=data.get('job_title'),
+ company_name=data.get('company_name'),
+ job_description=data.get('job_description'),
+ seniority_level=data.get('seniority_level'),
+ contract_type=data.get('contract_type'),
+ location=data.get('location'),
+ language=data.get('language', 'English'),
+ company_website=data.get('company_website', ''),
+ how_to_apply=data.get('how_to_apply', ''),
+ )
+
+ # Process request
+ processor = JobPostingGeneratorProcessor()
+ result = processor.process_request(
+ request_obj=agent_request,
+ user_id=request.user.id,
+ )
+
+ # Refresh user from database to get updated wallet balance
+ request.user.refresh_from_db()
+
+ return JsonResponse({
+ 'success': True,
+ 'request_id': str(agent_request.id),
+ 'message': 'Job Posting Generator request processed successfully',
+ 'wallet_balance': float(request.user.wallet_balance)
+ })
+
+ except BaseAgent.DoesNotExist:
+ return JsonResponse({'error': 'Job Posting Generator agent not found'}, status=404)
+ except Exception as e:
+ return JsonResponse({'error': str(e)}, status=500)
+
+
+@login_required
+def job_posting_generator_result(request, request_id):
+ """Get result for a specific request"""
+ try:
+ agent_request = JobPostingGeneratorRequest.objects.get(
+ id=request_id,
+ user=request.user
+ )
+
+ if hasattr(agent_request, 'response'):
+ response = agent_request.response
+ # Refresh user to get current wallet balance
+ request.user.refresh_from_db()
+
+ return JsonResponse({
+ 'success': response.success,
+ 'status': agent_request.status,
+ 'content': getattr(response, 'job_posting_content', None),
+ 'job_posting_content': getattr(response, 'job_posting_content', None),
+ 'formatted_posting': getattr(response, 'formatted_posting', None),
+ 'raw_response': getattr(response, 'raw_response', None),
+ 'processing_time': float(response.processing_time) if response.processing_time else None,
+ 'error_message': response.error_message,
+ 'wallet_balance': float(request.user.wallet_balance)
+ })
+ else:
+ return JsonResponse({
+ 'success': False,
+ 'status': agent_request.status,
+ 'message': 'Processing in progress...'
+ })
+
+ except JobPostingGeneratorRequest.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 741544b..ef64ea0 100644
--- a/netcop_hub/settings.py
+++ b/netcop_hub/settings.py
@@ -49,6 +49,8 @@ INSTALLED_APPS = [
'agent_base',
'weather_reporter',
'data_analyzer',
+ 'job_posting_generator',
+ 'social_ads_generator',
]
MIDDLEWARE = [
diff --git a/netcop_hub/urls.py b/netcop_hub/urls.py
index feabd57..1d7a776 100644
--- a/netcop_hub/urls.py
+++ b/netcop_hub/urls.py
@@ -24,6 +24,8 @@ urlpatterns = [
path('auth/', include('authentication.urls')),
path('agents/weather-reporter/', include('weather_reporter.urls')),
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('', include('core.urls')),
]
diff --git a/social_ads_generator/__init__.py b/social_ads_generator/__init__.py
new file mode 100644
index 0000000..e0610d4
--- /dev/null
+++ b/social_ads_generator/__init__.py
@@ -0,0 +1 @@
+# Social Ads Generator Agent App
\ No newline at end of file
diff --git a/social_ads_generator/admin.py b/social_ads_generator/admin.py
new file mode 100644
index 0000000..3526be7
--- /dev/null
+++ b/social_ads_generator/admin.py
@@ -0,0 +1,19 @@
+from django.contrib import admin
+from .models import SocialAdsGeneratorRequest, SocialAdsGeneratorResponse
+
+
+@admin.register(SocialAdsGeneratorRequest)
+class SocialAdsGeneratorRequestAdmin(admin.ModelAdmin):
+ list_display = ['id', 'user', 'status', 'created_at', 'cost']
+ list_filter = ['status', 'created_at']
+ search_fields = ['user__email', 'user__username']
+ readonly_fields = ['id', 'created_at', 'processed_at']
+ ordering = ['-created_at']
+
+
+@admin.register(SocialAdsGeneratorResponse)
+class SocialAdsGeneratorResponseAdmin(admin.ModelAdmin):
+ list_display = ['id', 'request', 'success', 'created_at']
+ list_filter = ['success', 'created_at']
+ readonly_fields = ['id', 'created_at']
+ ordering = ['-created_at']
\ No newline at end of file
diff --git a/social_ads_generator/apps.py b/social_ads_generator/apps.py
new file mode 100644
index 0000000..b929035
--- /dev/null
+++ b/social_ads_generator/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class SocialAdsGeneratorConfig(AppConfig):
+ default_auto_field = 'django.db.models.BigAutoField'
+ name = 'social_ads_generator'
\ No newline at end of file
diff --git a/social_ads_generator/migrations/0001_initial.py b/social_ads_generator/migrations/0001_initial.py
new file mode 100644
index 0000000..c819990
--- /dev/null
+++ b/social_ads_generator/migrations/0001_initial.py
@@ -0,0 +1,61 @@
+# Generated by Django 5.2.4 on 2025-07-10 12:33
+
+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 = [
+ ('agent_base', '0001_initial'),
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='SocialAdsGeneratorRequest',
+ 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, max_digits=10)),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('processed_at', models.DateTimeField(blank=True, null=True)),
+ ('description', models.TextField(help_text='Product/service description')),
+ ('social_platform', models.CharField(choices=[('facebook', 'Facebook'), ('instagram', 'Instagram'), ('twitter', 'Twitter'), ('linkedin', 'LinkedIn'), ('tiktok', 'TikTok'), ('youtube', 'YouTube')], default='facebook', max_length=20)),
+ ('include_emoji', models.BooleanField(default=False, help_text='Include emojis in ad copy')),
+ ('language', models.CharField(choices=[('English', 'English'), ('Arabic', 'Arabic (العربية)'), ('Spanish', 'Spanish (Español)'), ('French', 'French (Français)'), ('German', 'German (Deutsch)'), ('Chinese', 'Chinese (中文)')], default='English', max_length=20)),
+ ('agent', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='agent_base.baseagent')),
+ ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ],
+ options={
+ 'verbose_name': 'Social Ads Generator Request',
+ 'verbose_name_plural': 'Social Ads Generator Requests',
+ 'db_table': 'social_ads_generator_requests',
+ },
+ ),
+ migrations.CreateModel(
+ name='SocialAdsGeneratorResponse',
+ fields=[
+ ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
+ ('success', models.BooleanField(default=False)),
+ ('error_message', models.TextField(blank=True)),
+ ('processing_time', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('ad_copy', models.TextField(blank=True, help_text='Generated ad copy')),
+ ('hashtags', models.TextField(blank=True, help_text='Suggested hashtags')),
+ ('targeting_suggestions', models.TextField(blank=True, help_text='Audience targeting suggestions')),
+ ('formatted_ad', models.TextField(blank=True, help_text='Formatted ad content')),
+ ('raw_response', models.JSONField(blank=True, default=dict)),
+ ('request', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='response', to='social_ads_generator.socialadsgeneratorrequest')),
+ ],
+ options={
+ 'verbose_name': 'Social Ads Generator Response',
+ 'verbose_name_plural': 'Social Ads Generator Responses',
+ 'db_table': 'social_ads_generator_responses',
+ },
+ ),
+ ]
diff --git a/social_ads_generator/migrations/__init__.py b/social_ads_generator/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/social_ads_generator/models.py b/social_ads_generator/models.py
new file mode 100644
index 0000000..0cce58b
--- /dev/null
+++ b/social_ads_generator/models.py
@@ -0,0 +1,66 @@
+from django.db import models
+from decimal import Decimal
+from agent_base.models import BaseAgentRequest, BaseAgentResponse
+
+
+class SocialAdsGeneratorRequest(BaseAgentRequest):
+ """Social Ads Generator request tracking"""
+
+ # Required fields
+ description = models.TextField(help_text="Product/service description")
+ social_platform = models.CharField(
+ max_length=20,
+ choices=[
+ ('facebook', 'Facebook'),
+ ('instagram', 'Instagram'),
+ ('twitter', 'Twitter'),
+ ('linkedin', 'LinkedIn'),
+ ('tiktok', 'TikTok'),
+ ('youtube', 'YouTube'),
+ ],
+ default='facebook'
+ )
+
+ # Optional fields
+ include_emoji = models.BooleanField(default=False, help_text="Include emojis in ad copy")
+ language = models.CharField(
+ max_length=20,
+ choices=[
+ ('English', 'English'),
+ ('Arabic', 'Arabic (العربية)'),
+ ('Spanish', 'Spanish (Español)'),
+ ('French', 'French (Français)'),
+ ('German', 'German (Deutsch)'),
+ ('Chinese', 'Chinese (中文)'),
+ ],
+ default='English'
+ )
+
+
+ class Meta:
+ db_table = 'social_ads_generator_requests'
+ verbose_name = 'Social Ads Generator Request'
+ verbose_name_plural = 'Social Ads Generator Requests'
+
+
+class SocialAdsGeneratorResponse(BaseAgentResponse):
+ """Social Ads Generator response storage"""
+
+ request = models.OneToOneField(
+ SocialAdsGeneratorRequest,
+ on_delete=models.CASCADE,
+ related_name='response'
+ )
+
+ # Agent-specific response fields
+ ad_copy = models.TextField(blank=True, help_text="Generated ad copy")
+ hashtags = models.TextField(blank=True, help_text="Suggested hashtags")
+ targeting_suggestions = models.TextField(blank=True, help_text="Audience targeting suggestions")
+ formatted_ad = models.TextField(blank=True, help_text="Formatted ad content")
+ raw_response = models.JSONField(default=dict, blank=True)
+
+
+ class Meta:
+ db_table = 'social_ads_generator_responses'
+ verbose_name = 'Social Ads Generator Response'
+ verbose_name_plural = 'Social Ads Generator Responses'
\ No newline at end of file
diff --git a/social_ads_generator/processor.py b/social_ads_generator/processor.py
new file mode 100644
index 0000000..bb24ca7
--- /dev/null
+++ b/social_ads_generator/processor.py
@@ -0,0 +1,121 @@
+from agent_base.processors import StandardWebhookProcessor
+from django.utils import timezone
+from django.conf import settings
+from .models import SocialAdsGeneratorRequest, SocialAdsGeneratorResponse
+import json
+
+
+class SocialAdsGeneratorProcessor(StandardWebhookProcessor):
+ """Webhook processor for Social Ads Generator agent"""
+
+ agent_slug = 'social-ads-generator'
+ webhook_url = settings.N8N_WEBHOOK_SOCIAL_ADS
+ agent_id = 'social-ads'
+
+ def prepare_message_text(self, **kwargs):
+ """Prepare detailed social ads prompt for N8N webhook"""
+ request_obj = kwargs.get('request_obj')
+ if not request_obj:
+ return "Create a social media advertisement"
+
+ # Build comprehensive social ads prompt
+ prompt = f"""
+Create a compelling social media advertisement for the following:
+
+Product/Service Description:
+{request_obj.description}
+
+Target Platform: {request_obj.get_social_platform_display()}
+Language: {request_obj.language}
+Include Emojis: {'Yes' if request_obj.include_emoji else 'No'}
+
+Please create platform-optimized ad copy that:
+- Captures attention instantly
+- Highlights key benefits and unique selling points
+- Uses persuasive messaging that motivates action
+- Includes a strong call-to-action
+- Is tailored to {request_obj.get_social_platform_display()} audience
+- Uses {request_obj.language} language
+"""
+
+ if request_obj.include_emoji:
+ prompt += "\n- Incorporates relevant emojis for engagement"
+
+ prompt += "\n\nFormat the response as professional ad copy ready for social media posting."
+
+ return prompt
+
+ def process_response(self, response_data, request_obj):
+ """Process webhook response"""
+ try:
+ request_obj.status = 'processing'
+ request_obj.save()
+
+ # Handle array response from N8N (extract first item)
+ if isinstance(response_data, list) and len(response_data) > 0:
+ response_data = response_data[0]
+
+ # Extract ad copy content
+ ad_copy = ""
+ if isinstance(response_data, dict):
+ ad_copy = response_data.get('output', response_data.get('text', response_data.get('content', '')))
+ elif isinstance(response_data, str):
+ ad_copy = response_data
+
+ # Parse ad copy for different components (basic parsing)
+ hashtags = ""
+ targeting_suggestions = ""
+ formatted_ad = ad_copy
+
+ # Simple extraction of hashtags if present
+ if '#' in ad_copy:
+ lines = ad_copy.split('\n')
+ hashtag_lines = [line for line in lines if line.strip().startswith('#')]
+ if hashtag_lines:
+ hashtags = ' '.join(hashtag_lines)
+
+ # Determine success based on response
+ success = bool(ad_copy.strip()) and len(ad_copy.strip()) > 20
+
+ # Create response object
+ response_obj = SocialAdsGeneratorResponse.objects.create(
+ request=request_obj,
+ success=success,
+ processing_time=response_data.get('processing_time', 0) if isinstance(response_data, dict) else 0,
+ ad_copy=ad_copy,
+ hashtags=hashtags,
+ targeting_suggestions=targeting_suggestions,
+ formatted_ad=formatted_ad,
+ raw_response=response_data if isinstance(response_data, dict) else {'content': response_data}
+ )
+
+ # Only deduct wallet balance after successful processing
+ if success:
+ request_obj.user.deduct_balance(
+ request_obj.cost,
+ f"Social Ads Generator - {request_obj.get_social_platform_display()} ad for {request_obj.description[:50]}...",
+ 'social-ads-generator'
+ )
+ print(f"{self.agent_slug}: Wallet deducted {request_obj.cost} AED for successful processing")
+
+ # Update request as completed
+ request_obj.status = 'completed' if success else 'failed'
+ request_obj.processed_at = timezone.now()
+ request_obj.save()
+
+ return response_obj
+
+ except Exception as e:
+ # Handle error
+ request_obj.status = 'failed'
+ request_obj.save()
+
+ # Create error response
+ error_response = SocialAdsGeneratorResponse.objects.create(
+ request=request_obj,
+ success=False,
+ error_message=str(e),
+ processing_time=0
+ )
+
+ raise Exception(f"Failed to process Social Ads Generator response: {e}")
\ No newline at end of file
diff --git a/social_ads_generator/templates/social_ads_generator/detail.html b/social_ads_generator/templates/social_ads_generator/detail.html
new file mode 100644
index 0000000..03e29ad
--- /dev/null
+++ b/social_ads_generator/templates/social_ads_generator/detail.html
@@ -0,0 +1,941 @@
+{% load static %}
+
+
+
+
+
+ Social Ads Generator Agent - NetCop AI Hub
+
+
+
+
+
+
+
+
+
+
+
+ 📱 Social Ads Generator Agent
+
+
+ Create compelling social media advertisements with platform-optimized copy, audience targeting suggestions, and A/B test variants.
+
+
+ 💰 Cost: 7.00 AED
+
+
+
+
+ {% if messages %}
+ {% for message in messages %}
+
+ {{ message }}
+
+ {% endfor %}
+ {% endif %}
+
+
+
+
+
+
+
+
+
+
+
+
+
💳 Your Wallet
+
+
+
+ {% if user.is_authenticated %}
+ {{ user.wallet_balance|floatformat:2 }} AED
+ {% else %}
+ 0.00 AED
+ {% endif %}
+
+
+ Available Balance
+
+
+
+ {% if user.is_authenticated %}
+ {% if user.wallet_balance >= 7.00 %}
+
+ {% else %}
+
+ Insufficient balance! You need 7.00 AED.
+
+
+ 💰 Top Up Wallet
+
+ {% endif %}
+ {% else %}
+
+ 🔑 Login to Continue
+
+ {% endif %}
+
+
+
+
+
+ 💡 How it works
+
+
+ -
+ •
+ Describe your product/service
+
+ -
+ •
+ Choose target platform
+
+ -
+ •
+ Get optimized ad copy
+
+ -
+ •
+ Includes A/B test variants
+
+
+
+
+
+
+
+
+
+
+
✅
+
+ Social Media Ad Copy
+
+
+ ✅ Complete
+
+
+
+
+
+
+
+
+
+ 📝 Generated Ad Copy
+
+
+
+
+
+
+
+
+
+
+ Generated: Just now
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/social_ads_generator/urls.py b/social_ads_generator/urls.py
new file mode 100644
index 0000000..a272331
--- /dev/null
+++ b/social_ads_generator/urls.py
@@ -0,0 +1,9 @@
+from django.urls import path
+from . import views
+
+app_name = 'social_ads_generator'
+
+urlpatterns = [
+ path('', views.social_ads_generator_detail, name='detail'),
+ path('status//', views.social_ads_generator_result, name='status'),
+]
\ No newline at end of file
diff --git a/social_ads_generator/views.py b/social_ads_generator/views.py
new file mode 100644
index 0000000..de39b7c
--- /dev/null
+++ b/social_ads_generator/views.py
@@ -0,0 +1,164 @@
+from django.shortcuts import render, redirect
+from django.contrib.auth.decorators import login_required
+from django.contrib import messages
+from django.http import JsonResponse
+from django.views.decorators.csrf import csrf_exempt
+from django.utils.decorators import method_decorator
+from django.views import View
+from agent_base.models import BaseAgent
+from .models import SocialAdsGeneratorRequest, SocialAdsGeneratorResponse
+from .processor import SocialAdsGeneratorProcessor
+import json
+
+
+def social_ads_generator_detail(request):
+ """Detail page for Social Ads Generator agent"""
+ try:
+ agent = BaseAgent.objects.get(slug='social-ads-generator')
+ except BaseAgent.DoesNotExist:
+ messages.error(request, 'Social Ads Generator agent not found.')
+ return redirect('core:homepage')
+
+ if request.method == 'POST':
+ # Handle AJAX requests
+ if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
+ if not request.user.is_authenticated:
+ return JsonResponse({'error': 'Authentication required'}, status=401)
+
+ # Check wallet balance
+ if not request.user.has_sufficient_balance(agent.price):
+ return JsonResponse({'error': 'Insufficient wallet balance'}, status=400)
+
+ try:
+ # Create request object (no wallet deduction yet)
+ agent_request = SocialAdsGeneratorRequest.objects.create(
+ user=request.user,
+ agent=agent,
+ cost=agent.price,
+ description=request.POST.get('description'),
+ social_platform=request.POST.get('social_platform', 'facebook'),
+ include_emoji=request.POST.get('include_emoji') == 'on',
+ language=request.POST.get('language', 'English'),
+ )
+
+ # Process request
+ processor = SocialAdsGeneratorProcessor()
+ result = processor.process_request(
+ request_obj=agent_request,
+ user_id=request.user.id,
+ )
+
+ # Refresh user from database to get updated wallet balance
+ request.user.refresh_from_db()
+
+ return JsonResponse({
+ 'success': True,
+ 'request_id': str(agent_request.id),
+ 'message': 'Social ads generation started',
+ 'wallet_balance': float(request.user.wallet_balance)
+ })
+
+ except Exception as e:
+ return JsonResponse({'error': str(e)}, status=500)
+
+ # Regular form submission (redirect to avoid resubmission)
+ return redirect('social_ads_generator:detail')
+
+ # GET request - show form
+ context = {
+ 'agent': agent,
+ }
+ return render(request, 'social_ads_generator/detail.html', context)
+
+
+@method_decorator(csrf_exempt, name='dispatch')
+class SocialAdsGeneratorProcessView(View):
+ """Process Social Ads Generator requests"""
+
+ def post(self, request):
+ if not request.user.is_authenticated:
+ return JsonResponse({'error': 'Authentication required'}, status=401)
+
+ try:
+ # Parse request data
+ data = json.loads(request.body)
+
+ # Get agent
+ agent = BaseAgent.objects.get(slug='social-ads-generator')
+
+ # Check wallet balance
+ if not request.user.has_sufficient_balance(agent.price):
+ return JsonResponse({'error': 'Insufficient wallet balance'}, status=400)
+
+ # Create request object (no wallet deduction yet - only after successful processing)
+ agent_request = SocialAdsGeneratorRequest.objects.create(
+ user=request.user,
+ agent=agent,
+ cost=agent.price,
+ description=data.get('description'),
+ social_platform=data.get('social_platform', 'facebook'),
+ include_emoji=data.get('include_emoji', False),
+ language=data.get('language', 'English'),
+ )
+
+ # Process request
+ processor = SocialAdsGeneratorProcessor()
+ result = processor.process_request(
+ request_obj=agent_request,
+ user_id=request.user.id,
+ )
+
+ # Refresh user from database to get updated wallet balance
+ request.user.refresh_from_db()
+
+ return JsonResponse({
+ 'success': True,
+ 'request_id': str(agent_request.id),
+ 'message': 'Social Ads Generator request processed successfully',
+ 'wallet_balance': float(request.user.wallet_balance)
+ })
+
+ except BaseAgent.DoesNotExist:
+ return JsonResponse({'error': 'Social Ads Generator agent not found'}, status=404)
+ except Exception as e:
+ return JsonResponse({'error': str(e)}, status=500)
+
+
+@login_required
+def social_ads_generator_result(request, request_id):
+ """Get result for a specific request"""
+ try:
+ agent_request = SocialAdsGeneratorRequest.objects.get(
+ id=request_id,
+ user=request.user
+ )
+
+ if hasattr(agent_request, 'response'):
+ response = agent_request.response
+ # Refresh user to get current wallet balance
+ request.user.refresh_from_db()
+
+ return JsonResponse({
+ 'success': response.success,
+ 'status': agent_request.status,
+ 'content': getattr(response, 'ad_copy', None),
+ 'ad_copy': getattr(response, 'ad_copy', None),
+ 'hashtags': getattr(response, 'hashtags', None),
+ 'targeting_suggestions': getattr(response, 'targeting_suggestions', None),
+ 'formatted_ad': getattr(response, 'formatted_ad', None),
+ 'raw_response': getattr(response, 'raw_response', None),
+ 'processing_time': float(response.processing_time) if response.processing_time else None,
+ 'error_message': response.error_message,
+ 'wallet_balance': float(request.user.wallet_balance)
+ })
+ else:
+ return JsonResponse({
+ 'success': False,
+ 'status': agent_request.status,
+ 'message': 'Processing in progress...'
+ })
+
+ except SocialAdsGeneratorRequest.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