diff --git a/CLAUDE.md b/CLAUDE.md index 499f131..b99ad89 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,7 +8,7 @@ Quantum Tasks AI is a Django-based AI agent marketplace platform. Users can purc **Key Architecture:** - **Django Framework**: Main web application using Django 5.2.4 -- **Agent System**: Unified workflows app handling all AI agents via N8N webhooks +- **Agent System**: Database-driven agents app with marketplace and N8N webhook execution - **Authentication**: Custom user model with email verification - **Payments**: Stripe integration with wallet system - **Database**: SQLite for development, PostgreSQL for production (Railway) @@ -59,7 +59,7 @@ pytest # Run specific app tests python manage.py test authentication -python manage.py test workflows +python manage.py test agents python manage.py test wallet # Custom test scripts @@ -96,22 +96,22 @@ gunicorn netcop_hub.wsgi:application ### Apps Structure - **authentication/**: Custom user model, email verification, password reset - **core/**: Homepage, error handlers, utility functions -- **workflows/**: Unified agent system (marketplace, execution, models) +- **agents/**: Database-driven agent system (marketplace, execution, models, REST API) - **wallet/**: Stripe payments, wallet management, transactions -### Agent System (workflows app) +### Agent System (agents app) **Key Files:** -- `workflows/config/agents.py`: Agent definitions and configurations -- `workflows/models.py`: WorkflowRequest, WorkflowResponse, WorkflowAnalytics -- `workflows/views.py`: Marketplace and agent execution views -- `workflows/templates/workflows/`: Agent-specific templates +- `agents/models.py`: Agent, AgentCategory, AgentExecution models +- `agents/views.py`: REST API and web interface views +- `agents/templates/agents/`: Dynamic agent templates with form generation +- `agents/management/commands/`: Agent creation and management commands **Agent Flow:** -1. User selects agent from marketplace (`/agents/`) -2. Fills agent-specific form (`/agents/{slug}/`) -3. Form submission creates WorkflowRequest and calls N8N webhook +1. User browses marketplace (`/agents/`) +2. Selects agent and fills dynamic form (`/agents/{slug}/`) +3. Form submission creates AgentExecution and calls N8N webhook 4. N8N processes request and returns response via webhook -5. WorkflowResponse stores results for user retrieval +5. Results displayed with file upload support and real-time wallet updates ### Database Models **User Management:** @@ -119,10 +119,10 @@ gunicorn netcop_hub.wsgi:application - `authentication.PasswordResetToken`: Password reset tokens - `authentication.EmailVerificationToken`: Email verification tokens -**Workflows:** -- `workflows.WorkflowRequest`: Universal agent request model -- `workflows.WorkflowResponse`: Universal agent response model -- `workflows.WorkflowAnalytics`: Usage tracking and analytics +**Agents:** +- `agents.Agent`: Agent definitions with JSON form schemas and pricing +- `agents.AgentCategory`: Agent categories with icons and descriptions +- `agents.AgentExecution`: Execution history and results tracking **Payments:** - `wallet.Wallet`: User wallet with balance tracking @@ -137,49 +137,81 @@ gunicorn netcop_hub.wsgi:application - `DATABASE_URL`: PostgreSQL connection string (Railway) **N8N Webhook URLs:** -- `N8N_WEBHOOK_DATA_ANALYZER`: Data analysis agent webhook -- `N8N_WEBHOOK_FIVE_WHYS`: Five whys analysis webhook -- `N8N_WEBHOOK_JOB_POSTING`: Job posting generator webhook -- `N8N_WEBHOOK_FAQ_GENERATOR`: FAQ generator webhook -- `N8N_WEBHOOK_SOCIAL_ADS`: Social ads generator webhook +Agent-specific webhook URLs are stored in the database with each agent. Current working agents: +- Social Ads Generator: Creates compelling social media advertisements +- Job Posting Generator: Creates professional job postings +- PDF Summarizer: Analyzes and summarizes PDF documents with file upload ### URL Structure ``` / # Homepage (core app) /auth/ # Authentication (login, register, etc.) -/agents/ # Agent marketplace (workflows app) +/agents/ # Agent marketplace (agents app) /agents/{slug}/ # Individual agent pages /wallet/ # Wallet management /admin/ # Django admin ``` ### Key Components -**Agent Configuration (workflows/config/agents.py):** -- Centralizes all agent metadata (pricing, descriptions, webhooks) -- No database dependency for agent definitions -- Easy to add new agents by updating AGENT_CONFIGS +**Agent Configuration (Database-driven):** +- All agent metadata stored in database (pricing, descriptions, webhooks) +- JSON form schemas for dynamic form generation +- Easy to add new agents via management commands or admin interface **Templates:** - `templates/base.html`: Main layout with navigation - `templates/components/`: Reusable UI components -- `workflows/templates/workflows/`: Agent-specific forms and pages +- `agents/templates/agents/`: Dynamic agent forms and marketplace pages ## Adding New Agents -1. **Add agent config** in `workflows/config/agents.py`: +1. **Create management command** (recommended approach): ```python -'new-agent-slug': { - 'name': 'Agent Name', - 'description': 'Agent description', - 'price': 10.0, - 'icon': 'πŸ€–', - 'webhook_url': 'N8N_WEBHOOK_URL', -} +# agents/management/commands/create_new_agent.py +from django.core.management.base import BaseCommand +from agents.models import AgentCategory, Agent + +class Command(BaseCommand): + def handle(self, *args, **options): + category, _ = AgentCategory.objects.get_or_create( + slug='category-slug', + defaults={'name': 'Category Name', 'icon': 'πŸ€–'} + ) + + Agent.objects.get_or_create( + slug='agent-slug', + defaults={ + 'name': 'Agent Name', + 'short_description': 'Brief description', + 'description': 'Full description', + 'category': category, + 'price': 10.0, + 'form_schema': { + 'fields': [ + { + 'name': 'input_field', + 'type': 'text', + 'label': 'Input Field', + 'required': True + } + ] + }, + 'webhook_url': 'http://your-n8n-webhook-url' + } + ) ``` -2. **Create agent template** in `workflows/templates/workflows/{slug}.html` -3. **Add webhook URL** to environment variables -4. **Update N8N workflow** to handle new agent type +2. **Run the command**: `python manage.py create_new_agent` +3. **Update N8N workflow** to handle the new agent +4. **Agent will automatically appear** in marketplace with dynamic form generation + +**Supported Form Field Types:** +- `text`: Text input +- `textarea`: Multi-line text +- `select`: Dropdown with options +- `file`: File upload with drag-and-drop +- `url`: URL input with validation +- `checkbox`: Boolean checkbox ## Production Deployment @@ -220,9 +252,9 @@ gunicorn netcop_hub.wsgi:application **Testing agent webhooks locally:** 1. Use ngrok or similar to expose local server -2. Update webhook URLs in agent config +2. Update webhook URLs in agent database records 3. Test agent execution flow -4. Check WorkflowRequest/WorkflowResponse creation +4. Check AgentExecution records and results display --- -Last updated: Last updated: Last updated: Last updated: Last updated: Last updated: Last updated: Last updated: 2025-07-31 22:55:38 +Last updated: Last updated: Last updated: Last updated: Last updated: Last updated: Last updated: Last updated: Last updated: 2025-07-31 23:12:15 diff --git a/agents/templates/agents/agent_detail.html b/agents/templates/agents/agent_detail.html index f920a43..6ec39e5 100644 --- a/agents/templates/agents/agent_detail.html +++ b/agents/templates/agents/agent_detail.html @@ -312,10 +312,10 @@ document.body.setAttribute('data-user-balance', '{{ user.wallet_balance }}');
- {% include "workflows/components/agent_header.html" with agent_title=agent.name agent_subtitle=agent.short_description %} + {% include "components/agent_header.html" with agent_title=agent.name agent_subtitle=agent.short_description %} - {% include "workflows/components/quick_agents_panel.html" %} + {% include "components/quick_agents_panel.html" %}
@@ -460,14 +460,14 @@ document.body.setAttribute('data-user-balance', '{{ user.wallet_balance }}');
- {% include "workflows/components/how_it_works_widget.html" with steps="agents" %} + {% include "components/how_it_works_widget.html" with steps="agents" %}
- {% include "workflows/components/processing_status.html" with status_title="Processing..." status_text="Please wait while we execute your agent..." %} + {% include "components/processing_status.html" with status_title="Processing..." status_text="Please wait while we execute your agent..." %} - {% include "workflows/components/results_container.html" with results_title="Agent Results" %} + {% include "components/results_container.html" with results_title="Agent Results" %} {% endblock %} diff --git a/core/views.py b/core/views.py index 473884e..040ef91 100644 --- a/core/views.py +++ b/core/views.py @@ -6,7 +6,7 @@ from django.core.mail import send_mail from django.conf import settings from django_ratelimit.decorators import ratelimit from django_ratelimit import UNSAFE -from workflows.config.agents import get_all_agents +from agents.models import Agent from .models import ContactSubmission from django.db import connection import logging @@ -23,9 +23,8 @@ def homepage_view(request): messages.warning(request, 'Too many requests. Please wait a moment before refreshing.') try: - # Get featured agents for homepage from config - all_agents = get_all_agents() - featured_agents = list(all_agents.items())[:6] + # Get featured agents for homepage from database + featured_agents = Agent.objects.filter(is_active=True).select_related('category')[:6] context = { 'user_balance': request.user.wallet_balance if request.user.is_authenticated else 0, @@ -51,9 +50,8 @@ def pricing_view(request): return redirect('wallet:wallet_topup') try: - # Get sample agents to show pricing context from config - all_agents = get_all_agents() - sample_agents = list(all_agents.items())[:4] + # Get sample agents to show pricing context from database + sample_agents = Agent.objects.filter(is_active=True).select_related('category')[:4] context = { 'sample_agents': sample_agents, @@ -244,9 +242,9 @@ def health_check_view(request): 'response_time_ms': round((time.time() - start_time) * 1000, 2) } - # If database is working, get agent count from config + # If database is working, get agent count from database try: - agent_count = len(get_all_agents()) + agent_count = Agent.objects.filter(is_active=True).count() health_data['checks']['agents'] = { 'status': 'healthy', 'active_count': agent_count @@ -254,7 +252,7 @@ def health_check_view(request): except Exception as e: health_data['checks']['agents'] = { 'status': 'warning', - 'error': 'Could not load agent config', + 'error': 'Could not load agents', 'message': str(e)[:100] } diff --git a/docs_update_summary.txt b/docs_update_summary.txt index 47e99d9..df0a55c 100644 --- a/docs_update_summary.txt +++ b/docs_update_summary.txt @@ -1,14 +1,17 @@ === Documentation Auto-Update Summary === -Update Date: 2025-07-31 22:55:38 +Update Date: 2025-07-31 23:12:15 Recent Commits: + - c368bb5 ✨ Enhance marketplace with modern design and authentication - 4424780 🧹 Clean up marketplace to keep only 3 working agents - 400f2b7 πŸ“„ Add PDF Summarizer agent with advanced file upload system - - cf8583d πŸ’Ό Add Job Posting Generator agent with comprehensive form schema Agents Changes: - agents/templates/agents/marketplace.html + - agents/views.py - static/js/agents-core.js + - templates/components/quick_agents_panel.html + - workflows/templates/workflows/components/quick_agents_panel.html Documentation Changes: - CLAUDE.md diff --git a/manage_n8n_workflows.py b/manage_n8n_workflows.py deleted file mode 100755 index 7194498..0000000 --- a/manage_n8n_workflows.py +++ /dev/null @@ -1,268 +0,0 @@ -#!/usr/bin/env python3 -""" -N8N Workflow Management Script for Quantum Tasks AI - -This script helps manage N8N workflows for webhook-based agents: -- Import workflows to N8N instance -- Export workflows from N8N instance -- Sync workflows between local files and N8N -- Backup and restore workflows - -Usage: - python manage_n8n_workflows.py import [agent_name] - python manage_n8n_workflows.py export [agent_name] - python manage_n8n_workflows.py sync - python manage_n8n_workflows.py backup -""" - -import os -import sys -import json -import requests -from datetime import datetime -from pathlib import Path -import argparse - -# Configuration -N8N_BASE_URL = os.getenv('N8N_BASE_URL', 'http://localhost:5678') -N8N_API_KEY = os.getenv('N8N_API_KEY', '') - -# Webhook-based agents that need N8N workflows -WEBHOOK_AGENTS = [ - 'data_analyzer', - 'social_ads_generator', - 'job_posting_generator', - 'five_whys_analyzer' -] - -class N8NWorkflowManager: - def __init__(self): - self.base_url = N8N_BASE_URL - self.api_key = N8N_API_KEY - self.headers = { - 'Content-Type': 'application/json', - 'X-N8N-API-KEY': self.api_key - } if self.api_key else {'Content-Type': 'application/json'} - - def get_workflow_path(self, agent_name): - """Get the workflow directory path for an agent""" - return Path(f"{agent_name}/n8n_workflows") - - def load_workflow_json(self, agent_name, filename='workflow.json'): - """Load workflow JSON from agent directory""" - workflow_path = self.get_workflow_path(agent_name) / filename - if not workflow_path.exists(): - print(f"❌ Workflow file not found: {workflow_path}") - return None - - try: - with open(workflow_path, 'r') as f: - return json.load(f) - except json.JSONDecodeError as e: - print(f"❌ Invalid JSON in {workflow_path}: {e}") - return None - - def save_workflow_json(self, agent_name, workflow_data, filename='workflow.json'): - """Save workflow JSON to agent directory""" - workflow_path = self.get_workflow_path(agent_name) - workflow_path.mkdir(exist_ok=True) - - filepath = workflow_path / filename - with open(filepath, 'w') as f: - json.dump(workflow_data, f, indent=2) - - print(f"βœ… Workflow saved: {filepath}") - - def import_workflow_to_n8n(self, agent_name): - """Import workflow from local file to N8N instance""" - print(f"πŸ“₯ Importing workflow for {agent_name}...") - - workflow_data = self.load_workflow_json(agent_name) - if not workflow_data: - return False - - # Create workflow in N8N - try: - response = requests.post( - f"{self.base_url}/api/v1/workflows", - headers=self.headers, - json=workflow_data - ) - - if response.status_code == 201: - workflow_id = response.json().get('id') - print(f"βœ… Workflow imported successfully: ID {workflow_id}") - - # Activate the workflow - activate_response = requests.post( - f"{self.base_url}/api/v1/workflows/{workflow_id}/activate", - headers=self.headers - ) - - if activate_response.status_code == 200: - print(f"βœ… Workflow activated successfully") - else: - print(f"⚠️ Workflow imported but activation failed: {activate_response.text}") - - return True - else: - print(f"❌ Import failed: {response.status_code} - {response.text}") - return False - - except requests.RequestException as e: - print(f"❌ Connection error: {e}") - return False - - def export_workflow_from_n8n(self, agent_name, workflow_name=None): - """Export workflow from N8N instance to local file""" - print(f"πŸ“€ Exporting workflow for {agent_name}...") - - if not workflow_name: - workflow_name = f"{agent_name.replace('_', ' ').title()} Agent" - - try: - # Get all workflows - response = requests.get( - f"{self.base_url}/api/v1/workflows", - headers=self.headers - ) - - if response.status_code != 200: - print(f"❌ Failed to fetch workflows: {response.text}") - return False - - workflows = response.json() - - # Find workflow by name - target_workflow = None - for workflow in workflows: - if workflow.get('name', '').lower() == workflow_name.lower(): - target_workflow = workflow - break - - if not target_workflow: - print(f"❌ Workflow '{workflow_name}' not found in N8N") - print("Available workflows:") - for wf in workflows: - print(f" - {wf.get('name', 'Unnamed')}") - return False - - # Save workflow with timestamp - timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") - self.save_workflow_json(agent_name, target_workflow, f"workflow_exported_{timestamp}.json") - - # Also save as main workflow file - self.save_workflow_json(agent_name, target_workflow, "workflow.json") - - return True - - except requests.RequestException as e: - print(f"❌ Connection error: {e}") - return False - - def backup_all_workflows(self): - """Backup all workflows to timestamped files""" - print("πŸ”„ Backing up all workflows...") - timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") - - for agent_name in WEBHOOK_AGENTS: - workflow_path = self.get_workflow_path(agent_name) - if (workflow_path / "workflow.json").exists(): - workflow_data = self.load_workflow_json(agent_name) - if workflow_data: - self.save_workflow_json(agent_name, workflow_data, f"workflow_backup_{timestamp}.json") - print(f"βœ… Backed up {agent_name} workflow") - - def sync_workflows(self): - """Sync workflows between local files and N8N instance""" - print("πŸ”„ Syncing all workflows...") - - for agent_name in WEBHOOK_AGENTS: - print(f"\n--- {agent_name} ---") - - # Check if local workflow exists - if (self.get_workflow_path(agent_name) / "workflow.json").exists(): - print(f"πŸ“ Local workflow found for {agent_name}") - - # Try to import to N8N - success = self.import_workflow_to_n8n(agent_name) - if not success: - print(f"⚠️ Failed to sync {agent_name} to N8N") - else: - print(f"❌ No local workflow found for {agent_name}") - print(f"πŸ’‘ Place your workflow JSON file at: {self.get_workflow_path(agent_name)}/workflow.json") - - def list_workflows(self): - """List all workflows in N8N and local directories""" - print("πŸ“‹ Listing all workflows...\n") - - # List N8N workflows - try: - response = requests.get(f"{self.base_url}/api/v1/workflows", headers=self.headers) - if response.status_code == 200: - workflows = response.json() - print(f"🌐 N8N Instance ({len(workflows)} workflows):") - for wf in workflows: - status = "🟒 Active" if wf.get('active') else "πŸ”΄ Inactive" - print(f" - {wf.get('name', 'Unnamed')} ({status})") - else: - print("❌ Could not connect to N8N instance") - except requests.RequestException: - print("❌ Could not connect to N8N instance") - - print() - - # List local workflows - print("πŸ“ Local Workflows:") - for agent_name in WEBHOOK_AGENTS: - workflow_path = self.get_workflow_path(agent_name) - if workflow_path.exists(): - files = list(workflow_path.glob("*.json")) - if files: - print(f" {agent_name}: {len(files)} files") - for file in files: - print(f" - {file.name}") - else: - print(f" {agent_name}: No workflow files") - else: - print(f" {agent_name}: Directory not found") - -def main(): - parser = argparse.ArgumentParser(description='N8N Workflow Management for Quantum Tasks AI') - parser.add_argument('action', choices=['import', 'export', 'sync', 'backup', 'list'], - help='Action to perform') - parser.add_argument('agent', nargs='?', choices=WEBHOOK_AGENTS, - help='Specific agent to operate on (for import/export)') - parser.add_argument('--workflow-name', help='Workflow name in N8N (for export)') - - args = parser.parse_args() - - manager = N8NWorkflowManager() - - if args.action == 'import': - if not args.agent: - print("❌ Please specify an agent name for import") - print(f"Available agents: {', '.join(WEBHOOK_AGENTS)}") - sys.exit(1) - success = manager.import_workflow_to_n8n(args.agent) - sys.exit(0 if success else 1) - - elif args.action == 'export': - if not args.agent: - print("❌ Please specify an agent name for export") - print(f"Available agents: {', '.join(WEBHOOK_AGENTS)}") - sys.exit(1) - success = manager.export_workflow_from_n8n(args.agent, args.workflow_name) - sys.exit(0 if success else 1) - - elif args.action == 'sync': - manager.sync_workflows() - - elif args.action == 'backup': - manager.backup_all_workflows() - - elif args.action == 'list': - manager.list_workflows() - -if __name__ == '__main__': - main() \ No newline at end of file diff --git a/netcop_hub/settings.py b/netcop_hub/settings.py index c1c9fd8..e6c044a 100644 --- a/netcop_hub/settings.py +++ b/netcop_hub/settings.py @@ -75,8 +75,7 @@ INSTALLED_APPS = [ 'authentication', 'wallet', 'core', - 'workflows', # Unified workflows app (includes marketplace and agent execution) - 'agents', # New REST API-based agents system + 'agents', # REST API-based agents system ] # Development apps (only in DEBUG mode) diff --git a/netcop_hub/urls.py b/netcop_hub/urls.py index e28336b..97237b9 100644 --- a/netcop_hub/urls.py +++ b/netcop_hub/urls.py @@ -24,10 +24,7 @@ urlpatterns = [ path('auth/', include('authentication.urls')), path('wallet/', include('wallet.urls')), - # Unified workflows system for all agents (includes marketplace) - path('workflows/', include('workflows.urls')), - - # New REST API-based agents system (web interface + API) + # REST API-based agents system (web interface + API) path('agents/', include('agents.urls')), path('', include('core.urls')), diff --git a/templates/403.html b/templates/403.html index 55c8e4a..82e31a7 100644 --- a/templates/403.html +++ b/templates/403.html @@ -53,7 +53,7 @@ 🏠 Go Home - + πŸ€– Browse AI Agents @@ -98,7 +98,7 @@ πŸ“§ Contact Support - + πŸ€– View Available Agents diff --git a/templates/404.html b/templates/404.html index ca1869d..68a239e 100644 --- a/templates/404.html +++ b/templates/404.html @@ -43,7 +43,7 @@ 🏠 Go Home - + πŸ€– Browse AI Agents @@ -87,7 +87,7 @@

Can't find what you're looking for?

- + πŸ” Browse All Agents diff --git a/templates/500.html b/templates/500.html index 4f73bf9..886c9d7 100644 --- a/templates/500.html +++ b/templates/500.html @@ -50,7 +50,7 @@ 🏠 Go Home - + πŸ€– Browse AI Agents diff --git a/templates/base.html b/templates/base.html index 22ae978..bd473d0 100644 --- a/templates/base.html +++ b/templates/base.html @@ -30,7 +30,7 @@
diff --git a/templates/components/quick_agents_panel.html b/templates/components/quick_agents_panel.html index 952c853..d92708f 100644 --- a/templates/components/quick_agents_panel.html +++ b/templates/components/quick_agents_panel.html @@ -33,6 +33,6 @@ \ No newline at end of file diff --git a/templates/core/homepage.html b/templates/core/homepage.html index f9d93ce..292cc5f 100644 --- a/templates/core/homepage.html +++ b/templates/core/homepage.html @@ -41,7 +41,7 @@
{% if user.is_authenticated %} - + Explore AI Hub @@ -51,7 +51,7 @@ Get Protected Now - + Explore AI Hub {% endif %} diff --git a/templates/core/pricing.html b/templates/core/pricing.html index be4e5e1..453b00d 100644 --- a/templates/core/pricing.html +++ b/templates/core/pricing.html @@ -93,7 +93,7 @@ aria-label="Create account to get started"> πŸš€ Create Account - πŸ€– View Marketplace diff --git a/templates/wallet/wallet.html b/templates/wallet/wallet.html index 64c9fae..5726932 100644 --- a/templates/wallet/wallet.html +++ b/templates/wallet/wallet.html @@ -700,7 +700,7 @@
πŸ“­

No transactions yet. Start using AI agents to see your transaction history!

-
πŸ€– Browse Agents + πŸ€– Browse Agents
{% endif %}
@@ -719,7 +719,7 @@ πŸ’³ Top Up Wallet - + πŸ€– Browse Agents diff --git a/templates/wallet/wallet_topup.html b/templates/wallet/wallet_topup.html index 0a51545..4273ff2 100644 --- a/templates/wallet/wallet_topup.html +++ b/templates/wallet/wallet_topup.html @@ -568,7 +568,7 @@ πŸ“Š View Transactions - + πŸ€– Browse Agents diff --git a/workflows/__init__.py b/workflows/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/workflows/admin.py b/workflows/admin.py deleted file mode 100644 index ff394b2..0000000 --- a/workflows/admin.py +++ /dev/null @@ -1,35 +0,0 @@ -from django.contrib import admin -from .models import WorkflowRequest, WorkflowResponse, WorkflowAnalytics - - -@admin.register(WorkflowRequest) -class WorkflowRequestAdmin(admin.ModelAdmin): - list_display = ['id', 'user', 'agent_slug', 'status', 'created_at'] - list_filter = ['status', 'agent_slug', 'created_at'] - search_fields = ['user__username', 'agent_slug', 'id'] - readonly_fields = ['id', 'created_at', 'updated_at'] - - def get_queryset(self, request): - return super().get_queryset(request).select_related('user') - - -@admin.register(WorkflowResponse) -class WorkflowResponseAdmin(admin.ModelAdmin): - list_display = ['request', 'success', 'processing_time', 'created_at'] - list_filter = ['success', 'created_at'] - search_fields = ['request__id', 'request__user__username'] - readonly_fields = ['created_at'] - - def get_queryset(self, request): - return super().get_queryset(request).select_related('request__user') - - -@admin.register(WorkflowAnalytics) -class WorkflowAnalyticsAdmin(admin.ModelAdmin): - list_display = ['agent_slug', 'user', 'success', 'processing_time', 'date'] - list_filter = ['success', 'agent_slug', 'date'] - search_fields = ['user__username', 'agent_slug'] - date_hierarchy = 'date' - - def get_queryset(self, request): - return super().get_queryset(request).select_related('user') diff --git a/workflows/apps.py b/workflows/apps.py deleted file mode 100644 index 44ec738..0000000 --- a/workflows/apps.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.apps import AppConfig - - -class WorkflowsConfig(AppConfig): - default_auto_field = "django.db.models.BigAutoField" - name = "workflows" diff --git a/workflows/config/__init__.py b/workflows/config/__init__.py deleted file mode 100644 index 87cb1e7..0000000 --- a/workflows/config/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Configuration package for workflows app \ No newline at end of file diff --git a/workflows/config/agents.py b/workflows/config/agents.py deleted file mode 100644 index 4b20cd5..0000000 --- a/workflows/config/agents.py +++ /dev/null @@ -1,63 +0,0 @@ -""" -Simplified agent configuration system for unified workflows app. -Each agent is defined by essential metadata only - forms are handled in individual templates. -""" - -AGENT_CONFIGS = { - 'social-ads-generator': { - 'name': 'Social Ads Generator', - 'description': 'Create engaging social media advertisements with AI-powered content generation', - 'price': 6.0, - 'icon': 'πŸ“±', - 'webhook_url': 'http://localhost:5678/webhook/2dc234d8-7217-454a-83e9-81afe5b4fe2d', - }, - - 'job-posting-generator': { - 'name': 'Job Posting Generator', - 'description': 'Create professional job postings that attract top talent', - 'price': 10.0, - 'icon': 'πŸ’Ό', - 'webhook_url': 'http://localhost:5678/webhook/43f84411-eaaa-488c-9b1f-856e90d0aaf6', - }, - - 'data-analyzer': { - 'name': 'Data Analyzer', - 'description': 'AI-powered analysis of your data files with comprehensive insights', - 'price': 8.0, - 'icon': 'πŸ“Š', - 'webhook_url': 'http://localhost:5678/webhook/simple-pdf-processor', - }, - -} - - -def get_agent_config(agent_slug): - """Get agent configuration by slug""" - return AGENT_CONFIGS.get(agent_slug) - - -def get_all_agents(): - """Get all available agent configurations""" - return AGENT_CONFIGS - - -def get_available_agents(): - """Get all agents formatted for navigation components""" - return { - slug: { - 'name': config['name'], - 'icon': config['icon'], - 'description': config['description'] - } - for slug, config in AGENT_CONFIGS.items() - } - - -def format_message_for_n8n(agent_slug, form_data): - """Format form data into message for N8N webhook""" - config = get_agent_config(agent_slug) - if not config: - return None - - # Simple format - just send the form data as is - return f"Process {config['name']} request: {str(form_data)}" \ No newline at end of file diff --git a/workflows/migrations/0001_initial.py b/workflows/migrations/0001_initial.py deleted file mode 100644 index b2e7de0..0000000 --- a/workflows/migrations/0001_initial.py +++ /dev/null @@ -1,145 +0,0 @@ -# Generated by Django 5.2.4 on 2025-07-28 10:08 - -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="WorkflowRequest", - fields=[ - ( - "id", - models.UUIDField( - default=uuid.uuid4, - editable=False, - primary_key=True, - serialize=False, - ), - ), - ("agent_slug", models.CharField(db_index=True, max_length=100)), - ("input_data", models.JSONField(default=dict)), - ( - "status", - models.CharField( - choices=[ - ("processing", "Processing"), - ("completed", "Completed"), - ("failed", "Failed"), - ], - default="processing", - max_length=20, - ), - ), - ("created_at", models.DateTimeField(auto_now_add=True)), - ("updated_at", models.DateTimeField(auto_now=True)), - ( - "user", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="workflow_requests", - to=settings.AUTH_USER_MODEL, - ), - ), - ], - options={ - "ordering": ["-created_at"], - }, - ), - migrations.CreateModel( - name="WorkflowResponse", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("output_data", models.JSONField(default=dict)), - ( - "processing_time", - models.DecimalField( - blank=True, decimal_places=2, max_digits=5, null=True - ), - ), - ("success", models.BooleanField(default=True)), - ("error_message", models.TextField(blank=True)), - ("n8n_session_id", models.CharField(blank=True, max_length=100)), - ("created_at", models.DateTimeField(auto_now_add=True)), - ( - "request", - models.OneToOneField( - on_delete=django.db.models.deletion.CASCADE, - related_name="response", - to="workflows.workflowrequest", - ), - ), - ], - ), - migrations.CreateModel( - name="WorkflowAnalytics", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("agent_slug", models.CharField(db_index=True, max_length=100)), - ( - "processing_time", - models.DecimalField(decimal_places=2, max_digits=5), - ), - ("success", models.BooleanField()), - ("date", models.DateField(auto_now_add=True)), - ("created_at", models.DateTimeField(auto_now_add=True)), - ( - "user", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - to=settings.AUTH_USER_MODEL, - ), - ), - ], - options={ - "indexes": [ - models.Index( - fields=["agent_slug", "date"], - name="workflows_w_agent_s_feee49_idx", - ), - models.Index( - fields=["user", "date"], name="workflows_w_user_id_94f7cd_idx" - ), - ], - }, - ), - migrations.AddIndex( - model_name="workflowrequest", - index=models.Index( - fields=["user", "-created_at"], name="workflows_w_user_id_29fa9d_idx" - ), - ), - migrations.AddIndex( - model_name="workflowrequest", - index=models.Index( - fields=["agent_slug", "-created_at"], - name="workflows_w_agent_s_c56767_idx", - ), - ), - ] diff --git a/workflows/migrations/__init__.py b/workflows/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/workflows/models.py b/workflows/models.py deleted file mode 100644 index 1945f18..0000000 --- a/workflows/models.py +++ /dev/null @@ -1,82 +0,0 @@ -from django.db import models -from django.contrib.auth import get_user_model -import uuid -import json - -User = get_user_model() - - -class WorkflowRequest(models.Model): - """Universal model for all agent workflow requests""" - STATUS_CHOICES = [ - ('processing', 'Processing'), - ('completed', 'Completed'), - ('failed', 'Failed'), - ] - - id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) - user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='workflow_requests') - agent_slug = models.CharField(max_length=100, db_index=True) # e.g., 'social-ads-generator' - input_data = models.JSONField(default=dict) # All form data as JSON - status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='processing') - created_at = models.DateTimeField(auto_now_add=True) - updated_at = models.DateTimeField(auto_now=True) - - class Meta: - ordering = ['-created_at'] - indexes = [ - models.Index(fields=['user', '-created_at']), - models.Index(fields=['agent_slug', '-created_at']), - ] - - def __str__(self): - return f"{self.agent_slug} - {self.user.username} ({self.created_at})" - - -class WorkflowResponse(models.Model): - """Universal model for all agent workflow responses""" - request = models.OneToOneField(WorkflowRequest, on_delete=models.CASCADE, related_name='response') - output_data = models.JSONField(default=dict) # N8N response as JSON - processing_time = models.DecimalField(max_digits=5, decimal_places=2, null=True, blank=True) # in seconds - success = models.BooleanField(default=True) - error_message = models.TextField(blank=True) - n8n_session_id = models.CharField(max_length=100, blank=True) # Track N8N session - created_at = models.DateTimeField(auto_now_add=True) - - def __str__(self): - status = "Success" if self.success else "Failed" - return f"{self.request.agent_slug} Response - {status}" - - @property - def formatted_output(self): - """Format output data for display""" - if not self.output_data: - return "No output data" - - # Handle different output formats based on agent type - if isinstance(self.output_data, dict): - if 'output' in self.output_data: - return self.output_data['output'] - elif 'result' in self.output_data: - return self.output_data['result'] - - return json.dumps(self.output_data, indent=2) - - -class WorkflowAnalytics(models.Model): - """Track workflow usage analytics""" - agent_slug = models.CharField(max_length=100, db_index=True) - user = models.ForeignKey(User, on_delete=models.CASCADE) - processing_time = models.DecimalField(max_digits=5, decimal_places=2) # in seconds - success = models.BooleanField() - date = models.DateField(auto_now_add=True) - created_at = models.DateTimeField(auto_now_add=True) - - class Meta: - indexes = [ - models.Index(fields=['agent_slug', 'date']), - models.Index(fields=['user', 'date']), - ] - - def __str__(self): - return f"{self.agent_slug} - {self.date}" diff --git a/workflows/templates/workflows/agent-template-starter.html b/workflows/templates/workflows/agent-template-starter.html deleted file mode 100644 index e208aa2..0000000 --- a/workflows/templates/workflows/agent-template-starter.html +++ /dev/null @@ -1,802 +0,0 @@ -{% extends 'base.html' %} -{% load static %} - -{# -AGENT TEMPLATE STARTER - Enhanced Version with Modern UX Patterns -Copy this file and customize for new agents - -REQUIRED CUSTOMIZATIONS: -1. File names: "agent-template-starter" -> "your-agent-slug" -2. Agent info: "Agent Template Starter" -> Your agent name -3. Form fields: Replace example fields with your agent's specific inputs -4. JavaScript: Update agent-template-starter.js with your agent logic -5. Agent config: Add your agent to workflows/config/agents.py - -TEMPLATE FEATURES INCLUDED: -βœ… Enhanced file upload with preview, progress, and validation -βœ… Real-time form validation with detailed error messages -βœ… Drag-and-drop file support with visual feedback -βœ… Class-based JavaScript architecture following data-analyzer pattern -βœ… Comprehensive CSS framework with all modern patterns -βœ… Mobile-responsive design with proper accessibility -βœ… Integration with workflows-core.js and existing components - -KEEP THE FOLLOWING UNCHANGED: -- All {% include %} statements for shared components -- Template structure (agent-container, agent-grid, etc.) -- Processing and results components -- Authentication and wallet balance checks -- CSS variable system and design tokens - -OPTIONAL CUSTOMIZATIONS: -- How it works steps (change steps parameter) -- Custom CSS in the designated section -- Additional form sections or validation rules -- Custom result formatting in JavaScript -#} - -{% block title %}Agent Template Starter - Quantum Tasks AI{% endblock %} - -{% block extra_css %} - - -{% endblock %} - -{% block content %} - - -
- - {% include "workflows/components/agent_header.html" with agent_title=agent_config.name agent_subtitle=agent_config.description %} - - - {% include "workflows/components/quick_agents_panel.html" %} - - -
- -
-
-

- {{ agent_config.icon }} - {# CUSTOMIZE: Change "Details" to something specific like "Configuration", "Input", etc. #} - {{ agent_config.name }} Details -

-
-
-
- {% csrf_token %} - - {# - CUSTOMIZE: Replace this entire section with your agent-specific form fields - - FORM FIELD EXAMPLES INCLUDED: - - Text input with validation - - Textarea with character limits - - Select dropdown with options - - Enhanced file upload with preview - - VALIDATION FEATURES: - - Required field validation - - Real-time error display - - File type and size validation - - Success/error visual feedback - - CUSTOMIZE FOR YOUR AGENT: - 1. Replace field names (example_input -> your_field_name) - 2. Update validation rules in JavaScript - 3. Modify form labels and help text - 4. Add/remove fields as needed - 5. Update placeholder text and options - #} - -
-

πŸ“ Input Section

- -
- - -
Provide a helpful description for this field
- -
- -
- - -
Describe what kind of content goes here
- -
- -
- - -
Choose the appropriate option
- -
-
- - {# - OPTIONAL: Enhanced File Upload Section - - FEATURES INCLUDED: - βœ… Drag and drop file upload - βœ… File preview with metadata (name, size, timestamp) - βœ… Replace/remove file actions - βœ… Real-time validation (file type, size limits) - βœ… Progress indicator during upload - βœ… Visual feedback for different states - βœ… Mobile-responsive design - βœ… Accessibility features (keyboard navigation, ARIA labels) - - CUSTOMIZATION OPTIONS: - 1. Change accepted file types in 'accept' attribute - 2. Modify file size limits in JavaScript validation - 3. Update help text and file format descriptions - 4. Customize validation messages - 5. Remove entire section if file upload not needed - - REMOVE THIS SECTION IF YOUR AGENT DOESN'T NEED FILE UPLOAD - #} - -
-

πŸ“ File Upload (Optional)

- -
- -
-
-
πŸ“
-
Click to upload or drag and drop
-
Supported formats: PDF, DOC, TXT, etc.
-
-
- - - -
-
-
-
-
Preparing upload...
-
- - -
-
-
πŸ“„
-
-
-
- - Document - -
-
-
- - -
-
-
- - -
- -
Upload a file if needed for processing (Max size: 10MB)
- -
-
- {# END CUSTOMIZE SECTION #} - - {# - SUBMIT BUTTON SECTION - KEEP THIS STRUCTURE UNCHANGED - - FEATURES INCLUDED: - βœ… Authentication check (redirects to login if not authenticated) - βœ… Wallet balance validation (shows top-up if insufficient funds) - βœ… Proper form submission handling - βœ… Loading states and user feedback - βœ… Price display from agent configuration - βœ… Consistent styling across all agents - - CUSTOMIZATION OPTIONS: - - Change action verb in button text ("Generate" -> "Analyze", "Create", etc.) - - Update button icon emoji to match your agent - - DO NOT CHANGE: - - Authentication and balance check logic - - Template structure and Django template tags - - CSS classes and styling - - Error message formatting - #} - -
-
-
-
- - - {# CUSTOMIZE: Change "generic" to your agent-specific steps or keep as is #} - {% include "workflows/components/how_it_works_widget.html" with steps="generic" %} -
- - - {# CUSTOMIZE: Change status messages to match your agent's processing #} - {% include "workflows/components/processing_status.html" with status_title="Processing your request..." status_text="Please wait while we generate your content." %} - - - {# CUSTOMIZE: Change results_title to match your agent's output #} - {% include "workflows/components/results_container.html" with results_title="Generated Results" %} -
-{% endblock %} - -{% block extra_js %} - -{# CUSTOMIZE: Replace 'agent-template-starter' with your agent slug #} - -{% endblock %} - -{# -=============================================================================== -SETUP INSTRUCTIONS FOR NEW AGENTS -=============================================================================== - -1. COPY FILES: - - Copy this template: agent-template-starter.html -> your-agent-name.html - - Copy JavaScript: agent-template-starter.js -> your-agent-name.js - - Place in: workflows/templates/workflows/ and static/js/ respectively - -2. UPDATE AGENT CONFIG: - - Add your agent to workflows/config/agents.py - - Set name, description, price, icon, and webhook_url - - Example: - 'your-agent-slug': { - 'name': 'Your Agent Name', - 'description': 'Description of what your agent does', - 'price': 10.0, - 'icon': 'πŸ€–', - 'webhook_url': 'your-n8n-webhook-url', - } - -3. CUSTOMIZE TEMPLATE: - - Update page title and meta information - - Replace example form fields with your agent's inputs - - Modify section titles and descriptions - - Update How It Works steps if needed - - Remove file upload section if not needed - -4. CUSTOMIZE JAVASCRIPT: - - Change class name: AgentTemplateProcessor -> YourAgentProcessor - - Update agent slug and webhook URL - - Modify form validation rules for your fields - - Customize result formatting for your agent's output - - Update file validation if using file upload - -5. ADD URL ROUTE: - - Add URL pattern in workflows/urls.py - - Point to your agent's view function - - Example: path('your-agent-slug/', views.your_agent_view, name='your_agent') - -6. CREATE VIEW: - - Add view function in workflows/views.py - - Handle form processing and N8N integration - - Return appropriate JSON responses - - Follow existing agent patterns - -7. TESTING: - - Test form validation and submission - - Verify file upload functionality (if used) - - Check responsive design on mobile - - Test authentication and wallet balance flows - - Verify N8N webhook integration - -8. PRODUCTION: - - Update N8N webhook URLs to production - - Test with real user accounts - - Monitor error logs and performance - - Update documentation if needed - -=============================================================================== -AVAILABLE WORKFLOWSCORE FUNCTIONS (from workflows-core.js) -=============================================================================== - -Authentication & Balance: -- WorkflowsCore.checkAuthentication() -- WorkflowsCore.checkBalance(price) -- WorkflowsCore.updateWalletBalance(balance) - -UI Feedback: -- WorkflowsCore.showToast(message, type) -- WorkflowsCore.showProcessing(title) -- WorkflowsCore.hideProcessing() -- WorkflowsCore.showResults(content, title) - -Form Validation: -- WorkflowsCore.showFieldError(fieldName, message) -- WorkflowsCore.clearFieldError(fieldName) -- WorkflowsCore.clearAllFieldErrors() - -Utilities: -- WorkflowsCore.copyToClipboard(text, message) -- WorkflowsCore.downloadAsFile(content, filename, message) -- WorkflowsCore.generateSessionId() -- WorkflowsCore.formatFileSize(bytes) - -Processing: -- WorkflowsCore.startPolling(requestId) -- WorkflowsCore.stopPolling() -- WorkflowsCore.pollForResults(requestId, callback) - -=============================================================================== -#} \ No newline at end of file diff --git a/workflows/templates/workflows/components/agent_header.html b/workflows/templates/workflows/components/agent_header.html deleted file mode 100644 index 61752ef..0000000 --- a/workflows/templates/workflows/components/agent_header.html +++ /dev/null @@ -1,9 +0,0 @@ -
-
-

{{ agent_title }}

-

{{ agent_subtitle }}

-
-
- {% include "workflows/components/wallet_card.html" %} -
-
\ No newline at end of file diff --git a/workflows/templates/workflows/components/how_it_works_widget.html b/workflows/templates/workflows/components/how_it_works_widget.html deleted file mode 100644 index 5b5f834..0000000 --- a/workflows/templates/workflows/components/how_it_works_widget.html +++ /dev/null @@ -1,59 +0,0 @@ -
-
-

- ℹ️ - How It Works -

-
-
- {% if steps == "data" %} -
    -
  1. Upload your data file
  2. -
  3. Choose analysis type
  4. -
  5. Get AI-powered insights
  6. -
  7. Copy or download results
  8. -
- {% elif steps == "weather" %} -
    -
  1. Enter any city name worldwide
  2. -
  3. Choose your preferred report type
  4. -
  5. Get real-time weather data
  6. -
  7. Copy or download detailed reports
  8. -
- {% elif steps == "social_ads" %} -
    -
  1. Choose your platform and language
  2. -
  3. Describe your content and audience
  4. -
  5. Get AI-generated social ads
  6. -
  7. Copy or download your campaigns
  8. -
- {% elif steps == "job_posting" %} -
    -
  1. Enter job title and company details
  2. -
  3. Describe role and requirements
  4. -
  5. Get professional job posting
  6. -
  7. Copy or download the posting
  8. -
- {% elif steps == "five_whys" %} -
    -
  1. Describe your problem clearly
  2. -
  3. Choose analysis language
  4. -
  5. Get Five Whys analysis
  6. -
  7. Copy or download the results
  8. -
- {% else %} -
    -
  1. Fill in the required information
  2. -
  3. Choose your preferences
  4. -
  5. Get AI-powered results
  6. -
  7. Copy or download output
  8. -
- {% endif %} - - -
-
\ No newline at end of file diff --git a/workflows/templates/workflows/components/processing_status.html b/workflows/templates/workflows/components/processing_status.html deleted file mode 100644 index a2c216c..0000000 --- a/workflows/templates/workflows/components/processing_status.html +++ /dev/null @@ -1,13 +0,0 @@ -
-
-

- ⏳ - Processing Status -

-
-
-
⏳
-
{{ status_title|default:"Processing your request..." }}
-
{{ status_text|default:"Please wait while we analyze your data..." }}
-
-
\ No newline at end of file diff --git a/workflows/templates/workflows/components/quick_agents_panel.html b/workflows/templates/workflows/components/quick_agents_panel.html deleted file mode 100644 index c6f3206..0000000 --- a/workflows/templates/workflows/components/quick_agents_panel.html +++ /dev/null @@ -1,51 +0,0 @@ - - - \ No newline at end of file diff --git a/workflows/templates/workflows/components/results_container.html b/workflows/templates/workflows/components/results_container.html deleted file mode 100644 index c97db3a..0000000 --- a/workflows/templates/workflows/components/results_container.html +++ /dev/null @@ -1,21 +0,0 @@ - \ No newline at end of file diff --git a/workflows/templates/workflows/components/wallet_card.html b/workflows/templates/workflows/components/wallet_card.html deleted file mode 100644 index 7f0345c..0000000 --- a/workflows/templates/workflows/components/wallet_card.html +++ /dev/null @@ -1,17 +0,0 @@ -
-
-

Your Wallet

-
πŸ’³
-
-
-
- {{ user.wallet_balance|floatformat:2 }} AED -
-
Available Balance
-
-
- - πŸ’³ Top Up Wallet - -
-
\ No newline at end of file diff --git a/workflows/templates/workflows/data-analyzer.html b/workflows/templates/workflows/data-analyzer.html deleted file mode 100644 index c111f39..0000000 --- a/workflows/templates/workflows/data-analyzer.html +++ /dev/null @@ -1,468 +0,0 @@ -{% extends 'base.html' %} -{% load static %} - -{% block title %}Data Analyzer - Quantum Tasks AI{% endblock %} - -{% block extra_css %} - - -{% endblock %} - -{% block content %} - - -
- - {% include "workflows/components/agent_header.html" with agent_title="Data Analyzer" agent_subtitle="AI-powered analysis of your data files with comprehensive insights" %} - - - {% include "workflows/components/quick_agents_panel.html" %} - - -
- -
-
-

- πŸ“Š - Data Analysis Configuration -

-
-
-
- {% csrf_token %} - - -
- -
-
-
πŸ“
-
Click to upload or drag and drop
-
PDF files only
-
-
- - - -
-
-
-
-
Preparing upload...
-
- - -
-
-
πŸ“„
-
-
-
- - PDF Document - -
-
-
- - -
-
-
- - -
- -
Supported format: PDF files only. Max size: 10MB
- -
- - -
- -
-
- -
- -
-
- -
- -
-
- -
- -
-
-
Choose the type of analysis for your data file
- -
- - -
- {% if user.is_authenticated %} - {% if user.wallet_balance >= agent_config.price %} - - {% else %} -
- Insufficient balance! You need {{ agent_config.price }} AED. -
- - πŸ’° Top Up Wallet - - {% endif %} - {% else %} - - πŸ” Login to Continue - - {% endif %} -
-
-
-
- - -
-
-

- ℹ️ - How It Works -

-
-
-
    -
  1. Upload your PDF file
  2. -
  3. Choose analysis type and preferences
  4. -
  5. Our AI analyzes your data
  6. -
  7. Get comprehensive insights and reports
  8. -
- - - -
-
-
- - - {% include "workflows/components/processing_status.html" with status_title="Analyzing Your Data..." status_text="Please wait while our AI processes your file..." %} - - - {% include "workflows/components/results_container.html" with results_title="Analysis Results" %} -
-{% endblock %} - -{% block extra_js %} - - -{% endblock %} \ No newline at end of file diff --git a/workflows/templates/workflows/job-posting-generator.html b/workflows/templates/workflows/job-posting-generator.html deleted file mode 100644 index 40547bb..0000000 --- a/workflows/templates/workflows/job-posting-generator.html +++ /dev/null @@ -1,289 +0,0 @@ -{% extends 'base.html' %} -{% load static %} - -{% block title %}Job Posting Generator - Quantum Tasks AI{% endblock %} - -{% block extra_css %} - - -{% endblock %} - -{% block content %} - - -
- - {% include "workflows/components/agent_header.html" with agent_title="Job Posting Generator" agent_subtitle="Create professional job postings that attract top talent" %} - - - {% include "workflows/components/quick_agents_panel.html" %} - - -
- -
-
-

- πŸ’Ό - Job Posting Configuration -

-
-
-
- {% csrf_token %} - - -
-

- πŸ“ - Basic Information -

- -
- - - -
- -
- - - -
- -
- - -
Provide a detailed description of the role and requirements
- -
-
- - -
-

- 🎯 - Position Details -

- -
- - - -
- -
- - - -
- -
- - - -
- -
- - -
-
- - -
- {% if user.is_authenticated %} - {% if user.wallet_balance >= agent_config.price %} - - {% else %} -
- Insufficient balance! You need {{ agent_config.price }} AED. -
- - πŸ’° Top Up Wallet - - {% endif %} - {% else %} - - πŸ” Login to Continue - - {% endif %} -
-
-
-
- - -
-
-

- ℹ️ - How It Works -

-
-
-
    -
  1. Enter job requirements
  2. -
  3. Configure position details
  4. -
  5. AI processes your information
  6. -
  7. Get professional job posting
  8. -
- - - -
-
-
- - - {% include "workflows/components/processing_status.html" with status_title="Creating Job Posting..." status_text="Please wait while we generate your professional job posting..." %} - - - {% include "workflows/components/results_container.html" with results_title="Generated Job Posting" %} -
-{% endblock %} - -{% block extra_js %} - - -{% endblock %} \ No newline at end of file diff --git a/workflows/templates/workflows/marketplace.html b/workflows/templates/workflows/marketplace.html deleted file mode 100644 index ddff2d5..0000000 --- a/workflows/templates/workflows/marketplace.html +++ /dev/null @@ -1,376 +0,0 @@ -{% extends 'base.html' %} -{% load static %} - -{% block title %}AI Agent Marketplace - Quantum Tasks AI{% endblock %} - -{% block extra_css %} - - -{% endblock %} - -{% block content %} -
- -
-

πŸ€– AI Agent Marketplace

-

- Discover powerful AI agents to automate your tasks, boost productivity, and streamline your workflow -

-
- - - - - -
- - All - - {% for category in all_categories %} - - {{ category }} - - {% endfor %} -
- - -
- {% if search_query %} - Search results for "{{ search_query }}" β€’ - {% endif %} - {% if selected_category %} - {{ selected_category|capfirst }} category β€’ - {% endif %} - {{ total_agents }} agent{{ total_agents|pluralize }} available -
- - - {% if agents_by_category %} - {% if not selected_category %} - -
- {% for category, agents in agents_by_category.items %} - {% for agent in agents %} - -
-
{{ agent.icon }}
-
-

{{ agent.name }}

-

{{ agent.price }} AED

-
-
-

{{ agent.description }}

- -
- {% endfor %} - {% endfor %} -
- {% else %} - - {% for category, agents in agents_by_category.items %} -
-

{{ category }}

- -
- {% endfor %} - {% endif %} - {% else %} -
-
πŸ”
-

No agents found

-

Try adjusting your search or browse all categories

- Browse All Agents -
- {% endif %} -
-{% endblock %} \ No newline at end of file diff --git a/workflows/templates/workflows/social-ads-generator.html b/workflows/templates/workflows/social-ads-generator.html deleted file mode 100644 index a9ee4dc..0000000 --- a/workflows/templates/workflows/social-ads-generator.html +++ /dev/null @@ -1,341 +0,0 @@ -{% extends 'base.html' %} -{% load static %} - -{% block title %}Social Ads Generator - Quantum Tasks AI{% endblock %} - -{% block extra_css %} - - -{% endblock %} - -{% block content %} - - -
- - {% include "workflows/components/agent_header.html" with agent_title="Social Ads Generator" agent_subtitle="Create compelling social media advertisements optimized for different platforms" %} - - - {% include "workflows/components/quick_agents_panel.html" %} - - -
- -
-
-

- πŸ“’ - Social Ads Details -

-
-
-
- {% csrf_token %} - - - - - -
-

πŸ“± Platform & Formatting

- -
- - -
Choose the social media platform for optimization
- -
- -
- - -
Whether to include emojis in the ad copy
- -
-
- - -
- {% if user.is_authenticated %} - {% if user.wallet_balance >= agent_config.price %} - - {% else %} -
- Insufficient balance! You need {{ agent_config.price }} AED. -
- - πŸ’° Top Up Wallet - - {% endif %} - {% else %} - - πŸ” Login to Continue - - {% endif %} -
-
-
-
- - - {% include "workflows/components/how_it_works_widget.html" with steps="social_ads" %} -
- - - {% include "workflows/components/processing_status.html" with status_title="Creating Social Ads..." status_text="Please wait while we generate your ad copy..." %} - - - {% include "workflows/components/results_container.html" with results_title="Generated Social Ads" %} -
-{% endblock %} - -{% block extra_js %} - - -{% endblock %} \ No newline at end of file diff --git a/workflows/tests.py b/workflows/tests.py deleted file mode 100644 index 7ce503c..0000000 --- a/workflows/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/workflows/urls.py b/workflows/urls.py deleted file mode 100644 index 0c4ad76..0000000 --- a/workflows/urls.py +++ /dev/null @@ -1,20 +0,0 @@ -from django.urls import path, re_path -from . import views - -app_name = 'workflows' - -urlpatterns = [ - # Marketplace view at /agents/ - path('', views.marketplace_view, name='marketplace'), - - # Universal agent handler - matches any agent slug - re_path(r'^(?P[\w-]+)/$', views.workflow_handler, name='agent'), - - # API endpoints - path('api/process/', views.process_workflow_api, name='process_api'), - path('api/status//', views.workflow_status, name='status'), - - # User workflow management - path('history/', views.user_workflows, name='history'), - path('analytics/', views.workflow_analytics, name='analytics'), -] \ No newline at end of file diff --git a/workflows/views.py b/workflows/views.py deleted file mode 100644 index cd0d4c5..0000000 --- a/workflows/views.py +++ /dev/null @@ -1,484 +0,0 @@ -from django.shortcuts import render, get_object_or_404 -from django.contrib.auth.decorators import login_required -from django.http import JsonResponse, Http404 -from django.views.decorators.csrf import csrf_exempt -from django.views.decorators.http import require_http_methods -from django_ratelimit.decorators import ratelimit -from django_ratelimit import UNSAFE -import json -import time -import requests -from datetime import datetime - -from .models import WorkflowRequest, WorkflowResponse, WorkflowAnalytics -from .config.agents import get_agent_config, format_message_for_n8n, get_available_agents, get_all_agents -import logging - -logger = logging.getLogger(__name__) - - -def marketplace_view(request): - """Agent marketplace using AGENT_CONFIGS - no database dependency""" - agents_data = get_all_agents() - - # Group agents by category - agents_by_category = {} - all_categories = set() - - for agent_slug, agent_config in agents_data.items(): - category = agent_config.get('category', 'utilities') - all_categories.add(category) - - if category not in agents_by_category: - agents_by_category[category] = [] - - # Add slug to agent data for URL generation - agent_data = agent_config.copy() - agent_data['slug'] = agent_slug - agents_by_category[category].append(agent_data) - - # Filter by category if specified - selected_category = request.GET.get('category') - if selected_category and selected_category in all_categories: - agents_by_category = {selected_category: agents_by_category[selected_category]} - - # Search functionality - search_query = request.GET.get('search', '').strip() - if search_query: - filtered_agents = {} - for category, agents in agents_by_category.items(): - filtered_agents[category] = [ - agent for agent in agents - if search_query.lower() in agent['name'].lower() or - search_query.lower() in agent['description'].lower() - ] - agents_by_category = {k: v for k, v in filtered_agents.items() if v} - - context = { - 'agents_by_category': agents_by_category, - 'all_categories': sorted(all_categories), - 'selected_category': selected_category, - 'search_query': search_query, - 'total_agents': len(agents_data) - } - - return render(request, 'workflows/marketplace.html', context) - - -def send_file_to_webhook(webhook_url, uploaded_file, form_data, timeout=60): - """Send file to N8N webhook endpoint""" - logger.info(f"πŸ” DEBUG: send_file_to_webhook called!") - logger.info(f"πŸ” DEBUG: webhook_url={webhook_url}") - logger.info(f"πŸ” DEBUG: uploaded_file={uploaded_file}") - logger.info(f"πŸ” DEBUG: form_data={form_data}") - - try: - # Reset file pointer to beginning - uploaded_file.seek(0) - file_content = uploaded_file.read() - - logger.info(f"βœ… Sending file to webhook: {webhook_url}") - logger.info(f"βœ… File size: {len(file_content)} bytes") - logger.info(f"βœ… File name: {uploaded_file.name}") - - # Prepare multipart form data - files = { - 'file': (uploaded_file.name, file_content, 'application/pdf') - } - - # Add analysis type if provided - data = {} - if 'analysisType' in form_data: - data['analysisType'] = form_data['analysisType'] - - start_time = time.time() - response = requests.post(webhook_url, files=files, data=data, timeout=timeout) - processing_time = time.time() - start_time - - logger.info(f"Webhook response status: {response.status_code}") - logger.info(f"Processing time: {processing_time:.2f}s") - logger.info(f"Response preview: {response.text[:200]}...") - - response.raise_for_status() - - # Parse JSON response from webhook - if response.text.strip(): - try: - result_data = response.json() - logger.info(f"Webhook returned JSON: {result_data}") - - # Store the analysis results in the workflow request - if 'sections' in result_data: - # Success response with analysis sections - return {'success': True, 'data': result_data} - elif result_data.get('status') == 'error': - # Error response from N8N - logger.error(f"N8N webhook error: {result_data.get('error_message', 'Unknown error')}") - return {'success': False, 'error': result_data.get('error_message', 'Analysis failed')} - else: - # Unexpected response format - logger.warning(f"Unexpected webhook response format: {result_data}") - return {'success': True, 'data': result_data} - - except ValueError as e: - logger.error(f"Invalid JSON response from webhook: {e}") - logger.info(f"Raw response: {response.text[:500]}") - return {'success': False, 'error': 'Invalid response from analysis service'} - else: - logger.warning("Webhook returned empty response") - return {'success': False, 'error': 'Empty response from analysis service'} - - except requests.exceptions.Timeout: - logger.error(f"Webhook timeout after {timeout}s") - return False - except requests.exceptions.ConnectionError: - logger.error(f"Cannot connect to webhook: {webhook_url}") - return False - except requests.exceptions.RequestException as e: - logger.error(f"Webhook request failed: {e}") - return False - except Exception as e: - logger.error(f"Unexpected error sending to webhook: {e}") - return False - - -@login_required -def workflow_handler(request, agent_slug): - """Universal handler for all workflow agents with individual templates""" - - # Get agent configuration - agent_config = get_agent_config(agent_slug) - if not agent_config: - raise Http404("Agent configuration not found") - - # Agent config serves as the agent data (no database dependency) - agent = { - 'slug': agent_slug, - 'name': agent_config['name'], - 'price': agent_config['price'], - 'icon': agent_config['icon'], - 'description': agent_config['description'] - } - - if request.method == 'POST': - return process_workflow_request(request, agent_slug, agent_config, agent) - - # Determine template path - use individual templates - template_mapping = { - 'social-ads-generator': 'workflows/social-ads-generator.html', - 'job-posting-generator': 'workflows/job-posting-generator.html', - 'five-whys-analyzer': 'workflows/five-whys-analyzer.html', - 'weather-reporter': 'workflows/weather-reporter.html', - 'template-demo': 'workflows/agent-template-starter.html', - 'data-analyzer': 'workflows/data-analyzer.html', - } - - template_name = template_mapping.get(agent_slug) - if not template_name: - raise Http404("Template not found for agent") - - # Add timestamp for cache busting and available agents for navigation - context = { - 'agent': agent, - 'agent_config': agent_config, - 'available_agents': get_available_agents(), - 'timestamp': int(time.time()), - } - return render(request, template_name, context) - - -def process_workflow_request(request, agent_slug, agent_config, agent): - """Process workflow request (called from workflow_handler)""" - logger.info(f"πŸ” DEBUG: process_workflow_request called with agent_slug={agent_slug}") - logger.info(f"πŸ” DEBUG: request.method={request.method}") - logger.info(f"πŸ” DEBUG: request.FILES={dict(request.FILES)}") - logger.info(f"πŸ” DEBUG: request.POST={dict(request.POST)}") - - # Template mapping for error returns - template_mapping = { - 'social-ads-generator': 'workflows/social-ads-generator.html', - 'job-posting-generator': 'workflows/job-posting-generator.html', - 'five-whys-analyzer': 'workflows/five-whys-analyzer.html', - 'weather-reporter': 'workflows/weather-reporter.html', - 'template-demo': 'workflows/agent-template-starter.html', - 'data-analyzer': 'workflows/data-analyzer.html', - } - - try: - # Extract form data - form_data = {} - for key, value in request.POST.items(): - if key != 'csrfmiddlewaretoken': - form_data[key] = value - - # Handle file uploads (but don't store file objects in form_data for JSON serialization) - logger.info(f"DEBUG: request.FILES = {request.FILES}") - logger.info(f"DEBUG: request.FILES.keys() = {list(request.FILES.keys())}") - - uploaded_files = {} - for key, file in request.FILES.items(): - logger.info(f"DEBUG: Found file - {key}: {file.name} ({file.size} bytes)") - # Store file metadata only (not the file object itself) - form_data[f"{key}_name"] = file.name - form_data[f"{key}_size"] = file.size - # Keep actual file object separate for processing - uploaded_files[key] = file - - logger.info(f"DEBUG: Final form_data keys = {list(form_data.keys())}") - logger.info(f"DEBUG: Uploaded files = {list(uploaded_files.keys())}") - - # Basic validation - ensure we have form data - if not form_data: - context = { - 'agent': agent, - 'agent_config': agent_config, - 'error': 'No form data provided.', - 'timestamp': int(time.time()), - } - template_name = template_mapping.get(agent_slug) - return render(request, template_name, context) - - # Check user balance - if not request.user.has_sufficient_balance(agent_config['price']): - context = { - 'agent': agent, - 'agent_config': agent_config, - 'balance_error': f'Insufficient balance. You need {agent_config["price"]} AED.', - 'form_data': form_data, - 'timestamp': int(time.time()), - } - template_name = template_mapping.get(agent_slug) - return render(request, template_name, context) - - # Create workflow request record - workflow_request = WorkflowRequest.objects.create( - user=request.user, - agent_slug=agent_slug, - input_data=form_data, - status='processing' - ) - - # Actually send request to webhook (for agents that support file upload) - logger.info(f"πŸ” DEBUG: Checking webhook conditions - agent_slug={agent_slug}, has_file={'document_file' in uploaded_files or 'file' in uploaded_files}") - file_upload_agents = ['data-analyzer'] - has_file = 'file' in uploaded_files or 'document_file' in uploaded_files - - if agent_slug in file_upload_agents and has_file: - try: - # Check if this is an AJAX request (like the original system) - if request.headers.get('X-Requested-With') == 'XMLHttpRequest': - # Send file to webhook in background and return JSON response - # Get the uploaded file (different field names for different agents) - uploaded_file = uploaded_files.get('file') or uploaded_files.get('document_file') - webhook_result = send_file_to_webhook( - agent_config['webhook_url'], - uploaded_file, - form_data - ) - - if webhook_result and webhook_result.get('success'): - # Deduct user balance for successful processing - request.user.deduct_balance(agent_config['price']) - workflow_request.status = 'completed' - workflow_request.save() - - # Store analysis results in the workflow request - analysis_data = webhook_result.get('data', {}) - try: - # Create WorkflowResponse with analysis data - WorkflowResponse.objects.create( - request=workflow_request, - formatted_output=analysis_data, - success=True, - processing_time=1.53 # Could get this from webhook timing - ) - except Exception as resp_error: - logger.warning(f"Could not save response data: {resp_error}") - # Continue anyway - the main processing worked - - return JsonResponse({ - 'success': True, - 'request_id': str(workflow_request.id), - 'wallet_balance': float(request.user.wallet_balance), - 'report_text': analysis_data.get('sections', [{}])[0].get('content', 'Analysis completed'), - 'analysis_results': analysis_data - }) - else: - workflow_request.status = 'failed' - workflow_request.save() - error_msg = webhook_result.get('error', 'Failed to process file') if webhook_result else 'Connection failed' - return JsonResponse({ - 'success': False, - 'error': error_msg - }) - else: - # Non-AJAX request, return HTML template - context = { - 'agent': agent, - 'agent_config': agent_config, - 'processing': True, - 'request_id': workflow_request.id, - 'timestamp': int(time.time()), - } - except Exception as e: - logger.error(f"Webhook error for {agent_slug}: {e}") - workflow_request.status = 'failed' - workflow_request.save() - if request.headers.get('X-Requested-With') == 'XMLHttpRequest': - return JsonResponse({ - 'success': False, - 'error': 'Service temporarily unavailable. Please try again later.' - }) - else: - context = { - 'agent': agent, - 'agent_config': agent_config, - 'error': 'Service temporarily unavailable. Please try again later.', - 'timestamp': int(time.time()), - } - else: - # For other agents or no file upload, show processing message - context = { - 'agent': agent, - 'agent_config': agent_config, - 'processing': True, - 'request_id': workflow_request.id, - 'timestamp': int(time.time()), - } - - template_name = template_mapping.get(agent_slug) - return render(request, template_name, context) - - except Exception as e: - logger.error(f"Workflow processing error for {agent_slug}: {e}", exc_info=True) - context = { - 'agent': agent, - 'agent_config': agent_config, - 'error': 'An error occurred while processing your request. Please try again.', - 'timestamp': int(time.time()), - } - template_name = template_mapping.get(agent_slug) - return render(request, template_name, context) - - -@login_required -@require_http_methods(["POST"]) -@ratelimit(key='user', rate='20/m', method='POST', block=False) -def process_workflow_api(request): - """API endpoint for processing workflows (alternative to direct N8N calls)""" - - # Check if rate limited - if getattr(request, 'limited', False): - logger.warning(f"Workflow API rate limit exceeded for user {request.user.id}") - return JsonResponse({'error': 'Too many requests. Please wait a moment.'}, status=429) - - try: - # Parse JSON request - data = json.loads(request.body) - agent_slug = data.get('agent_slug') - form_data = data.get('form_data', {}) - - if not agent_slug: - return JsonResponse({'error': 'Agent slug is required'}, status=400) - - # Get agent configuration - agent_config = get_agent_config(agent_slug) - if not agent_config: - return JsonResponse({'error': 'Agent not found'}, status=404) - - # Basic validation - ensure we have form data - if not form_data: - return JsonResponse({'error': 'No form data provided'}, status=400) - - # Check user balance - if not request.user.has_sufficient_balance(agent_config['price']): - return JsonResponse({ - 'error': 'Insufficient balance', - 'required': float(agent_config['price']), - 'current': float(request.user.wallet_balance) - }, status=400) - - # Create workflow request - workflow_request = WorkflowRequest.objects.create( - user=request.user, - agent_slug=agent_slug, - input_data=form_data, - status='processing' - ) - - # In a real implementation, this would call N8N - # For now, return processing status - return JsonResponse({ - 'success': True, - 'request_id': str(workflow_request.id), - 'status': 'processing', - 'message': 'Request received and processing' - }) - - except json.JSONDecodeError: - return JsonResponse({'error': 'Invalid JSON payload'}, status=400) - except Exception as e: - logger.error(f"Workflow API error: {e}", exc_info=True) - return JsonResponse({'error': 'Internal server error'}, status=500) - - -@login_required -def workflow_status(request, request_id): - """Get workflow processing status""" - try: - workflow_request = get_object_or_404( - WorkflowRequest, - id=request_id, - user=request.user - ) - - response_data = { - 'request_id': str(workflow_request.id), - 'status': workflow_request.status, - 'created_at': workflow_request.created_at.isoformat(), - } - - # Include response data if completed - if hasattr(workflow_request, 'response') and workflow_request.response: - response_data['output'] = workflow_request.response.formatted_output - response_data['processing_time'] = float(workflow_request.response.processing_time or 0) - response_data['success'] = workflow_request.response.success - - return JsonResponse(response_data) - - except Exception as e: - logger.error(f"Status check error: {e}", exc_info=True) - return JsonResponse({'error': 'Failed to get status'}, status=500) - - -@login_required -def user_workflows(request): - """Show user's workflow history""" - workflows = WorkflowRequest.objects.filter(user=request.user).order_by('-created_at')[:50] - - context = { - 'workflows': workflows, - } - return render(request, 'workflows/history.html', context) - - -@login_required -def workflow_analytics(request): - """Show workflow analytics for the user""" - - # Get user's workflow analytics - analytics = WorkflowAnalytics.objects.filter(user=request.user).order_by('-date')[:30] - - # Calculate summary statistics - total_workflows = WorkflowRequest.objects.filter(user=request.user).count() - successful_workflows = WorkflowAnalytics.objects.filter(user=request.user, success=True).count() - - success_rate = (successful_workflows / total_workflows * 100) if total_workflows > 0 else 0 - - context = { - 'analytics': analytics, - 'total_workflows': total_workflows, - 'successful_workflows': successful_workflows, - 'success_rate': success_rate, - } - return render(request, 'workflows/analytics.html', context)