From 56749b77f07adbc6f9e977e7903316a697777327 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 14 Aug 2025 09:28:34 +0530 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=80=20Implement=20file-based=20agent?= =?UTF-8?q?=20system=20for=20instant=20deployment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BREAKING CHANGE: Transform from database-driven to file-based agent management Major Features: • File-based agent definitions - agents load from JSON configs instantly • Hybrid architecture - files for agent data, database for relationships • Zero-command deployment - just add JSON file and git push • Automatic database sync - maintains foreign key compatibility • Template compatibility - enriched agent data works with existing UI Technical Implementation: • AgentFileService - comprehensive file-based agent management • JSON config loading with LRU caching for performance • Category enrichment for template compatibility • Slug-based queries for AgentExecution relationships • get_or_create_agent_db_record() for foreign key maintenance Benefits: • Ultra-simple agent creation (just add JSON file) • Instant Railway deployment (no commands needed) • Version control integration (agent changes in git) • Perfect scalability (ready for 100+ agents) • Zero maintenance overhead All 8 agents verified working: - 4 Webhook agents: Social Ads, Job Posting, PDF Summarizer, 5 Whys - 4 Direct access agents: Career Navigator, Brand Strategist, Lean Six Sigma, SWOT 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- agents/services.py | 347 +++++++++++++++++++++++++++++++++++++++++++++ agents/views.py | 309 +++++++++++++++++++++++++++++++--------- 2 files changed, 588 insertions(+), 68 deletions(-) create mode 100644 agents/services.py diff --git a/agents/services.py b/agents/services.py new file mode 100644 index 0000000..cb9641a --- /dev/null +++ b/agents/services.py @@ -0,0 +1,347 @@ +import json +import os +from functools import lru_cache +from pathlib import Path +from typing import Dict, List, Optional +from django.conf import settings +import logging + +logger = logging.getLogger(__name__) + +class AgentFileService: + """ + Service for loading agent configurations from JSON files instead of database. + Provides caching and error handling for file-based agent management. + """ + + BASE_DIR = Path(__file__).resolve().parent.parent + AGENTS_CONFIG_DIR = BASE_DIR / 'agents' / 'configs' / 'agents' + CATEGORIES_CONFIG_FILE = BASE_DIR / 'agents' / 'configs' / 'categories' / 'categories.json' + + @classmethod + def clear_cache(cls): + """Clear all cached data - useful for testing and development""" + cls.get_all_agents.cache_clear() + cls.get_all_categories.cache_clear() + + @classmethod + @lru_cache(maxsize=1) + def get_all_categories(cls) -> List[Dict]: + """ + Load all agent categories from categories.json file. + Returns list of category dictionaries with caching. + """ + try: + if not cls.CATEGORIES_CONFIG_FILE.exists(): + logger.warning(f"Categories file not found: {cls.CATEGORIES_CONFIG_FILE}") + return [] + + with open(cls.CATEGORIES_CONFIG_FILE, 'r', encoding='utf-8') as f: + categories = json.load(f) + + # Handle both array format and object format + if isinstance(categories, dict) and 'categories' in categories: + categories = categories['categories'] + elif not isinstance(categories, list): + logger.error("Categories file should contain an array of categories") + return [] + + logger.info(f"Loaded {len(categories)} categories from file") + return categories + + except Exception as e: + logger.error(f"Error loading categories: {str(e)}") + return [] + + @classmethod + @lru_cache(maxsize=1) + def get_all_agents(cls) -> List[Dict]: + """ + Load all agent configurations from JSON files in the agents config directory. + Returns list of agent dictionaries with caching. + """ + agents = [] + + try: + if not cls.AGENTS_CONFIG_DIR.exists(): + logger.warning(f"Agents config directory not found: {cls.AGENTS_CONFIG_DIR}") + return [] + + # Get all JSON files in the agents config directory + json_files = list(cls.AGENTS_CONFIG_DIR.glob('*.json')) + + for json_file in json_files: + try: + with open(json_file, 'r', encoding='utf-8') as f: + agent_data = json.load(f) + + # Add file-based metadata + agent_data['_source_file'] = str(json_file) + agent_data['_file_name'] = json_file.name + + # Ensure required fields exist with defaults + agent_data.setdefault('is_active', True) + agent_data.setdefault('agent_type', 'form') + agent_data.setdefault('system_type', 'webhook') + agent_data.setdefault('form_schema', {'fields': []}) + agent_data.setdefault('access_url_name', '') + agent_data.setdefault('display_url_name', '') + + agents.append(agent_data) + + except json.JSONDecodeError as e: + logger.error(f"Invalid JSON in {json_file}: {str(e)}") + continue + except Exception as e: + logger.error(f"Error loading agent from {json_file}: {str(e)}") + continue + + logger.info(f"Loaded {len(agents)} agents from {len(json_files)} files") + return agents + + except Exception as e: + logger.error(f"Error scanning agents directory: {str(e)}") + return [] + + @classmethod + def get_agent_by_slug(cls, slug: str) -> Optional[Dict]: + """ + Get a specific agent by its slug with category info enriched. + Returns agent dictionary or None if not found. + """ + agents = cls.get_all_agents() + for agent in agents: + if agent.get('slug') == slug: + enriched_agents = cls._enrich_agents_with_category_info([agent]) + return enriched_agents[0] if enriched_agents else agent + return None + + @classmethod + def get_agents_by_category(cls, category_slug: str) -> List[Dict]: + """ + Get all agents belonging to a specific category. + Returns list of agent dictionaries with category info enriched. + """ + agents = cls.get_all_agents() + category_agents = [agent for agent in agents if agent.get('category') == category_slug] + return cls._enrich_agents_with_category_info(category_agents) + + @classmethod + def get_active_agents(cls) -> List[Dict]: + """ + Get all active agents with category info enriched. + Returns list of active agent dictionaries. + """ + agents = cls.get_all_agents() + active_agents = [agent for agent in agents if agent.get('is_active', True)] + return cls._enrich_agents_with_category_info(active_agents) + + @classmethod + def search_agents(cls, query: str) -> List[Dict]: + """ + Search agents by name or description. + Returns list of matching agent dictionaries with category info enriched. + """ + if not query: + return cls.get_active_agents() + + query_lower = query.lower() + agents = cls.get_all_agents() + active_agents = [agent for agent in agents if agent.get('is_active', True)] + + matching_agents = [] + for agent in active_agents: + name = agent.get('name', '').lower() + description = agent.get('description', '').lower() + short_description = agent.get('short_description', '').lower() + + if (query_lower in name or + query_lower in description or + query_lower in short_description): + matching_agents.append(agent) + + return cls._enrich_agents_with_category_info(matching_agents) + + @classmethod + def get_category_by_slug(cls, slug: str) -> Optional[Dict]: + """ + Get a specific category by its slug. + Returns category dictionary or None if not found. + """ + categories = cls.get_all_categories() + for category in categories: + if category.get('slug') == slug: + return category + return None + + @classmethod + def validate_agent_config(cls, agent_data: Dict) -> List[str]: + """ + Validate an agent configuration dictionary. + Returns list of validation errors (empty if valid). + """ + errors = [] + required_fields = ['slug', 'name', 'short_description', 'description', 'category', 'price'] + + for field in required_fields: + if not agent_data.get(field): + errors.append(f"Missing required field: {field}") + + # Validate price is numeric + try: + float(agent_data.get('price', 0)) + except (ValueError, TypeError): + errors.append("Price must be a valid number") + + # Validate category exists + category_slug = agent_data.get('category') + if category_slug and not cls.get_category_by_slug(category_slug): + errors.append(f"Category '{category_slug}' does not exist") + + # Validate system_type + system_type = agent_data.get('system_type', 'webhook') + if system_type not in ['webhook', 'direct_access']: + errors.append("system_type must be 'webhook' or 'direct_access'") + + return errors + + @classmethod + def get_agent_stats(cls) -> Dict: + """ + Get statistics about agents and categories. + Returns dictionary with counts and breakdown. + """ + agents = cls.get_all_agents() + categories = cls.get_all_categories() + + active_agents = cls.get_active_agents() + webhook_agents = [a for a in active_agents if a.get('system_type') == 'webhook'] + direct_access_agents = [a for a in active_agents if a.get('system_type') == 'direct_access'] + + # Category breakdown (use raw agents to avoid category object issue) + category_counts = {} + raw_active_agents = [agent for agent in agents if agent.get('is_active', True)] + for agent in raw_active_agents: + category = agent.get('category', 'unknown') + category_counts[category] = category_counts.get(category, 0) + 1 + + return { + 'total_agents': len(agents), + 'active_agents': len(active_agents), + 'webhook_agents': len(webhook_agents), + 'direct_access_agents': len(direct_access_agents), + 'total_categories': len(categories), + 'category_breakdown': category_counts + } + + @classmethod + def _enrich_agents_with_category_info(cls, agents: List[Dict]) -> List[Dict]: + """ + Enrich agent dictionaries with category information for template compatibility. + Adds category object with icon, name, and slug for each agent. + """ + categories = cls.get_all_categories() + category_map = {cat['slug']: cat for cat in categories} + + enriched_agents = [] + for agent in agents: + agent_copy = agent.copy() + category_slug = agent.get('category') + + if category_slug and category_slug in category_map: + # Add category object for template compatibility + category_data = category_map[category_slug] + agent_copy['category'] = { + 'slug': category_data['slug'], + 'name': category_data['name'], + 'icon': category_data['icon'], + 'description': category_data.get('description', '') + } + else: + # Fallback category + agent_copy['category'] = { + 'slug': 'unknown', + 'name': 'Unknown', + 'icon': '❓', + 'description': 'Unknown category' + } + + enriched_agents.append(agent_copy) + + return enriched_agents + + @classmethod + def get_or_create_agent_db_record(cls, agent_data: Dict): + """ + Get or create a database Agent record for foreign key relationships. + This maintains compatibility with AgentExecution while using file-based configs. + """ + from .models import Agent, AgentCategory + + # Get or create category + category_data = cls.get_category_by_slug(agent_data.get('category', 'unknown')) + if category_data: + category, _ = AgentCategory.objects.get_or_create( + slug=category_data['slug'], + defaults={ + 'name': category_data['name'], + 'description': category_data.get('description', ''), + 'icon': category_data['icon'], + 'is_active': True + } + ) + else: + category, _ = AgentCategory.objects.get_or_create( + slug='unknown', + defaults={ + 'name': 'Unknown', + 'description': 'Unknown category', + 'icon': '❓', + 'is_active': True + } + ) + + # Get or create agent + agent, created = Agent.objects.get_or_create( + slug=agent_data['slug'], + defaults={ + 'name': agent_data['name'], + 'short_description': agent_data['short_description'], + 'description': agent_data['description'], + 'category': category, + 'price': agent_data['price'], + 'agent_type': agent_data.get('agent_type', 'form'), + 'form_schema': agent_data.get('form_schema', {}), + 'webhook_url': agent_data['webhook_url'], + 'message_limit': agent_data.get('message_limit', 50), + 'access_url_name': agent_data.get('access_url_name', ''), + 'display_url_name': agent_data.get('display_url_name', ''), + 'is_active': agent_data.get('is_active', True) + } + ) + + # Update existing record if needed (sync file data to db) + if not created: + updated = False + for field, value in { + 'name': agent_data['name'], + 'short_description': agent_data['short_description'], + 'description': agent_data['description'], + 'price': agent_data['price'], + 'agent_type': agent_data.get('agent_type', 'form'), + 'form_schema': agent_data.get('form_schema', {}), + 'webhook_url': agent_data['webhook_url'], + 'message_limit': agent_data.get('message_limit', 50), + 'access_url_name': agent_data.get('access_url_name', ''), + 'display_url_name': agent_data.get('display_url_name', ''), + 'is_active': agent_data.get('is_active', True), + 'category': category + }.items(): + if getattr(agent, field) != value: + setattr(agent, field, value) + updated = True + + if updated: + agent.save() + + return agent \ No newline at end of file diff --git a/agents/views.py b/agents/views.py index 67b5bf4..4204325 100644 --- a/agents/views.py +++ b/agents/views.py @@ -8,8 +8,9 @@ from django.utils import timezone from django.contrib.auth.decorators import login_required from django.contrib import messages from django.db import models -from .models import Agent, AgentExecution, AgentCategory, ChatSession, ChatMessage -from .serializers import AgentSerializer, AgentExecutionSerializer +from .models import Agent, AgentExecution, ChatSession, ChatMessage +from .serializers import AgentExecutionSerializer +from .services import AgentFileService import requests import json import time @@ -66,29 +67,30 @@ def validate_webhook_url(url): @permission_classes([IsAuthenticated]) def agent_list(request): """List all active agents with optional category filtering""" - agents = Agent.objects.filter(is_active=True) + agents = AgentFileService.get_active_agents() category = request.GET.get('category') if category: - agents = agents.filter(category__slug=category) + agents = AgentFileService.get_agents_by_category(category) search = request.GET.get('search') if search: - agents = agents.filter(name__icontains=search) + agents = AgentFileService.search_agents(search) paginator = PageNumberPagination() paginator.page_size = 20 result_page = paginator.paginate_queryset(agents, request) - serializer = AgentSerializer(result_page, many=True) - return paginator.get_paginated_response(serializer.data) + # Return the data directly since we're working with dictionaries + return paginator.get_paginated_response(result_page) @api_view(['GET']) @permission_classes([IsAuthenticated]) def agent_detail(request, slug): """Get detailed agent information""" - agent = get_object_or_404(Agent, slug=slug, is_active=True) - serializer = AgentSerializer(agent) - return Response(serializer.data) + agent = AgentFileService.get_agent_by_slug(slug) + if not agent or not agent.get('is_active', True): + return Response({'error': 'Agent not found'}, status=status.HTTP_404_NOT_FOUND) + return Response(agent) @api_view(['POST']) @permission_classes([IsAuthenticated]) @@ -100,15 +102,31 @@ def execute_agent(request): if not agent_slug: return Response({'error': 'agent_slug is required'}, status=status.HTTP_400_BAD_REQUEST) - agent = get_object_or_404(Agent, slug=agent_slug, is_active=True) + agent_data = AgentFileService.get_agent_by_slug(agent_slug) + if not agent_data or not agent_data.get('is_active', True): + return Response({'error': 'Agent not found'}, status=status.HTTP_404_NOT_FOUND) + + # Convert agent data to a simple object for compatibility + class AgentCompat: + def __init__(self, data): + self.slug = data['slug'] + self.name = data['name'] + self.price = float(data['price']) + self.webhook_url = data['webhook_url'] + self.id = data['slug'] # Use slug as ID for file-based agents + + agent = AgentCompat(agent_data) # Check if user has sufficient balance (using existing wallet system) if hasattr(request.user, 'has_sufficient_balance') and not request.user.has_sufficient_balance(agent.price): return Response({'error': 'Insufficient wallet balance'}, status=status.HTTP_400_BAD_REQUEST) + # Get or create database record for foreign key compatibility + agent_db_record = AgentFileService.get_or_create_agent_db_record(agent_data) + # Create execution record execution = AgentExecution.objects.create( - agent=agent, + agent=agent_db_record, user=request.user, input_data=input_data, fee_charged=agent.price, @@ -205,12 +223,22 @@ def career_navigator_access(request): return redirect('authentication:login') # Get the career navigator agent - try: - agent = Agent.objects.get(slug='cybersec-career-navigator', is_active=True) - except Agent.DoesNotExist: + agent_data = AgentFileService.get_agent_by_slug('cybersec-career-navigator') + if not agent_data or not agent_data.get('is_active', True): messages.error(request, 'Career Navigator is currently unavailable.') return redirect('agents:marketplace') + # Convert to compatible object + class AgentCompat: + def __init__(self, data): + self.slug = data['slug'] + self.name = data['name'] + self.price = float(data['price']) + self.webhook_url = data['webhook_url'] + self.id = data['slug'] + + agent = AgentCompat(agent_data) + # Check if user has sufficient balance if not request.user.has_sufficient_balance(agent.price): messages.error(request, f'Insufficient balance! You need {agent.price} AED to access the Career Navigator.') @@ -227,9 +255,12 @@ def career_navigator_access(request): messages.error(request, 'Failed to process payment. Please try again.') return redirect('agents:marketplace') + # Get or create database record for foreign key compatibility + agent_db_record = AgentFileService.get_or_create_agent_db_record(agent_data) + # Create execution record for tracking execution = AgentExecution.objects.create( - agent=agent, + agent=agent_db_record, user=request.user, input_data={'action': 'direct_access', 'source': 'try_now_button'}, fee_charged=agent.price, @@ -258,18 +289,28 @@ def career_navigator_view(request): return redirect('authentication:login') # Get the career navigator agent - try: - agent = Agent.objects.get(slug='cybersec-career-navigator', is_active=True) - except Agent.DoesNotExist: + agent_data = AgentFileService.get_agent_by_slug('cybersec-career-navigator') + if not agent_data or not agent_data.get('is_active', True): messages.error(request, 'Career Navigator is currently unavailable.') return redirect('agents:marketplace') + # Convert to compatible object + class AgentCompat: + def __init__(self, data): + self.slug = data['slug'] + self.name = data['name'] + self.price = float(data['price']) + self.webhook_url = data['webhook_url'] + self.id = data['slug'] + + agent = AgentCompat(agent_data) + # Check if user has a recent execution (within last 2 hours) or just redirect to payment from django.utils import timezone from datetime import timedelta recent_execution = AgentExecution.objects.filter( - agent=agent, + agent__slug=agent.slug, # Changed to slug-based lookup user=request.user, status='completed', created_at__gte=timezone.now() - timedelta(hours=2) @@ -301,18 +342,28 @@ def ai_brand_strategist_view(request): return redirect('authentication:login') # Get the AI Brand Strategist agent - try: - agent = Agent.objects.get(slug='ai-brand-strategist', is_active=True) - except Agent.DoesNotExist: + agent_data = AgentFileService.get_agent_by_slug('ai-brand-strategist') + if not agent_data or not agent_data.get('is_active', True): messages.error(request, 'AI Brand Strategist is currently unavailable.') return redirect('agents:marketplace') + # Convert to compatible object + class AgentCompat: + def __init__(self, data): + self.slug = data['slug'] + self.name = data['name'] + self.price = float(data['price']) + self.webhook_url = data['webhook_url'] + self.id = data['slug'] + + agent = AgentCompat(agent_data) + # Check if user has a recent execution (within last 2 hours) or just redirect to payment from django.utils import timezone from datetime import timedelta recent_execution = AgentExecution.objects.filter( - agent=agent, + agent__slug=agent.slug, # Changed to slug-based lookup user=request.user, status='completed', created_at__gte=timezone.now() - timedelta(hours=2) @@ -344,12 +395,22 @@ def ai_brand_strategist_access(request): return redirect('authentication:login') # Get the AI Brand Strategist agent - try: - agent = Agent.objects.get(slug='ai-brand-strategist', is_active=True) - except Agent.DoesNotExist: + agent_data = AgentFileService.get_agent_by_slug('ai-brand-strategist') + if not agent_data or not agent_data.get('is_active', True): messages.error(request, 'AI Brand Strategist is currently unavailable.') return redirect('agents:marketplace') + # Convert to compatible object + class AgentCompat: + def __init__(self, data): + self.slug = data['slug'] + self.name = data['name'] + self.price = float(data['price']) + self.webhook_url = data['webhook_url'] + self.id = data['slug'] + + agent = AgentCompat(agent_data) + # Check if user has sufficient balance if not request.user.has_sufficient_balance(agent.price): messages.error(request, f'Insufficient balance! You need {agent.price} AED to access the AI Brand Strategist.') @@ -366,9 +427,12 @@ def ai_brand_strategist_access(request): messages.error(request, 'Failed to process payment. Please try again.') return redirect('agents:marketplace') + # Get or create database record for foreign key compatibility + agent_db_record = AgentFileService.get_or_create_agent_db_record(agent_data) + # Create execution record for tracking execution = AgentExecution.objects.create( - agent=agent, + agent=agent_db_record, user=request.user, input_data={'action': 'direct_access', 'source': 'try_now_button'}, fee_charged=agent.price, @@ -436,15 +500,18 @@ def format_agent_message(agent_slug, input_data): @login_required def agent_detail_view(request, slug): """Render agent detail page with dynamic form or chat interface""" - agent = get_object_or_404(Agent, slug=slug, is_active=True) + agent = AgentFileService.get_agent_by_slug(slug) + if not agent or not agent.get('is_active', True): + from django.http import Http404 + raise Http404("Agent not found") # Handle chat-based agents - if agent.agent_type == 'chat': + if agent.get('agent_type') == 'chat': return chat_agent_view(request, agent) # Handle form-based agents (existing behavior) # Get all other active agents for quick access panel - all_agents = Agent.objects.filter(is_active=True).exclude(id=agent.id).select_related('category') + all_agents = [a for a in AgentFileService.get_active_agents() if a['slug'] != slug] context = { 'agent': agent, @@ -457,22 +524,19 @@ def agent_detail_view(request, slug): def agents_marketplace(request): """Agent marketplace view""" - agents = Agent.objects.filter(is_active=True).select_related('category') - categories = AgentCategory.objects.filter(is_active=True) + # Get agents from file service + agents = AgentFileService.get_active_agents() + categories = AgentFileService.get_all_categories() # Filter by category category_slug = request.GET.get('category') if category_slug: - agents = agents.filter(category__slug=category_slug) + agents = AgentFileService.get_agents_by_category(category_slug) # Search functionality search_query = request.GET.get('search', '').strip() if search_query: - agents = agents.filter( - models.Q(name__icontains=search_query) | - models.Q(short_description__icontains=search_query) | - models.Q(description__icontains=search_query) - ) + agents = AgentFileService.search_agents(search_query) context = { 'agents': agents, @@ -491,10 +555,25 @@ def chat_agent_view(request, agent): chat_session = None messages = [] + # Convert file-based agent data to compatible object if needed + if isinstance(agent, dict): + class AgentCompat: + def __init__(self, data): + self.slug = data['slug'] + self.name = data['name'] + self.price = float(data['price']) + self.webhook_url = data['webhook_url'] + self.id = data['slug'] # Use slug as ID for file-based agents + self.message_limit = data.get('message_limit', 50) + + agent_compat = AgentCompat(agent) + else: + agent_compat = agent + if request.user.is_authenticated: - # Get or create active chat session + # Get or create active chat session (using slug-based filter for file agents) chat_session = ChatSession.objects.filter( - agent=agent, + agent__slug=agent_compat.slug, # Changed to slug-based lookup user=request.user, status='active' ).first() @@ -504,7 +583,7 @@ def chat_agent_view(request, agent): if session_id and not chat_session: chat_session = ChatSession.objects.filter( session_id=session_id, - agent=agent, + agent__slug=agent_compat.slug, # Changed to slug-based lookup user=request.user ).first() @@ -513,11 +592,11 @@ def chat_agent_view(request, agent): messages = ChatMessage.objects.filter(session=chat_session).order_by('timestamp') # Get all other active agents for quick access panel - all_agents = Agent.objects.filter(is_active=True).exclude(id=agent.id).select_related('category') + all_agents = [a for a in AgentFileService.get_active_agents() if a['slug'] != agent_compat.slug] # Get previous sessions for this user and agent (excluding current active session) previous_sessions_query = ChatSession.objects.filter( - agent=agent, + agent__slug=agent_compat.slug, # Changed to slug-based lookup user=request.user ).exclude(status='active').order_by('-created_at')[:5] # Last 5 non-active sessions @@ -555,7 +634,7 @@ def chat_agent_view(request, agent): # Message calculations (only count user messages) user_message_count = messages.filter(message_type='user').count() - message_limit = agent.message_limit + message_limit = agent_compat.message_limit message_percentage = min(100, (user_message_count / message_limit) * 100) session_data = { @@ -567,7 +646,7 @@ def chat_agent_view(request, agent): } context = { - 'agent': agent, + 'agent': agent, # Keep original agent data for template compatibility 'chat_session': chat_session, 'messages': messages, 'all_agents': all_agents, @@ -588,15 +667,28 @@ def start_chat_session(request): if not agent_slug: return Response({'error': 'agent_slug is required'}, status=status.HTTP_400_BAD_REQUEST) - agent = get_object_or_404(Agent, slug=agent_slug, is_active=True, agent_type='chat') + agent_data = AgentFileService.get_agent_by_slug(agent_slug) + if not agent_data or not agent_data.get('is_active', True) or agent_data.get('agent_type') != 'chat': + return Response({'error': 'Chat agent not found'}, status=status.HTTP_404_NOT_FOUND) + + # Convert to compatible object + class AgentCompat: + def __init__(self, data): + self.slug = data['slug'] + self.name = data['name'] + self.price = float(data['price']) + self.webhook_url = data['webhook_url'] + self.id = data['slug'] + + agent = AgentCompat(agent_data) # Check wallet balance if hasattr(request.user, 'wallet_balance') and request.user.wallet_balance < agent.price: return Response({'error': 'Insufficient wallet balance'}, status=status.HTTP_400_BAD_REQUEST) - # Check for existing active session + # Check for existing active session (using slug-based lookup) existing_session = ChatSession.objects.filter( - agent=agent, + agent__slug=agent.slug, user=request.user, status='active' ).first() @@ -613,9 +705,12 @@ def start_chat_session(request): from django.utils import timezone from datetime import timedelta + # Get or create database record for foreign key compatibility + agent_db_record = AgentFileService.get_or_create_agent_db_record(agent_data) + chat_session = ChatSession.objects.create( session_id=session_id, - agent=agent, + agent=agent_db_record, user=request.user, fee_charged=agent.price, status='active', @@ -1099,7 +1194,23 @@ def direct_access_handler(request, slug): Generic handler for direct access agents (external forms like JotForm). Handles payment processing and grants access to external form. """ - agent = get_object_or_404(Agent, slug=slug, is_active=True) + agent_data = AgentFileService.get_agent_by_slug(slug) + if not agent_data or not agent_data.get('is_active', True): + from django.http import Http404 + raise Http404("Agent not found") + + # Convert to compatible object + class AgentCompat: + def __init__(self, data): + self.slug = data['slug'] + self.name = data['name'] + self.price = float(data['price']) + self.webhook_url = data['webhook_url'] + self.access_url_name = data.get('access_url_name', '') + self.display_url_name = data.get('display_url_name', '') + self.id = data['slug'] + + agent = AgentCompat(agent_data) # Verify this is a direct access agent if not agent.access_url_name or not agent.display_url_name: @@ -1137,7 +1248,23 @@ def direct_access_display(request, slug): Generic display handler for direct access agents. Shows external form (JotForm, Google Forms, etc.) in iframe or redirects directly. """ - agent = get_object_or_404(Agent, slug=slug, is_active=True) + agent_data = AgentFileService.get_agent_by_slug(slug) + if not agent_data or not agent_data.get('is_active', True): + from django.http import Http404 + raise Http404("Agent not found") + + # Convert to compatible object + class AgentCompat: + def __init__(self, data): + self.slug = data['slug'] + self.name = data['name'] + self.price = float(data['price']) + self.webhook_url = data['webhook_url'] + self.access_url_name = data.get('access_url_name', '') + self.display_url_name = data.get('display_url_name', '') + self.id = data['slug'] + + agent = AgentCompat(agent_data) # Verify this is a direct access agent if not agent.access_url_name or not agent.display_url_name: @@ -1161,18 +1288,28 @@ def lean_six_sigma_expert_view(request): return redirect('authentication:login') # Get the Lean Six Sigma Expert agent - try: - agent = Agent.objects.get(slug='lean-six-sigma-expert', is_active=True) - except Agent.DoesNotExist: + agent_data = AgentFileService.get_agent_by_slug('lean-six-sigma-expert') + if not agent_data or not agent_data.get('is_active', True): messages.error(request, 'Lean Six Sigma Expert is currently unavailable.') return redirect('agents:marketplace') + # Convert to compatible object + class AgentCompat: + def __init__(self, data): + self.slug = data['slug'] + self.name = data['name'] + self.price = float(data['price']) + self.webhook_url = data['webhook_url'] + self.id = data['slug'] + + agent = AgentCompat(agent_data) + # Check if user has a recent execution (within last 2 hours) or just redirect to payment from django.utils import timezone from datetime import timedelta recent_execution = AgentExecution.objects.filter( - agent=agent, + agent__slug=agent.slug, # Changed to slug-based lookup user=request.user, status='completed', created_at__gte=timezone.now() - timedelta(hours=2) @@ -1204,12 +1341,22 @@ def lean_six_sigma_expert_access(request): return redirect('authentication:login') # Get the Lean Six Sigma Expert agent - try: - agent = Agent.objects.get(slug='lean-six-sigma-expert', is_active=True) - except Agent.DoesNotExist: + agent_data = AgentFileService.get_agent_by_slug('lean-six-sigma-expert') + if not agent_data or not agent_data.get('is_active', True): messages.error(request, 'Lean Six Sigma Expert is currently unavailable.') return redirect('agents:marketplace') + # Convert to compatible object + class AgentCompat: + def __init__(self, data): + self.slug = data['slug'] + self.name = data['name'] + self.price = float(data['price']) + self.webhook_url = data['webhook_url'] + self.id = data['slug'] + + agent = AgentCompat(agent_data) + # Check if user has sufficient balance if not request.user.has_sufficient_balance(agent.price): messages.error(request, f'Insufficient balance! You need {agent.price} AED to access the Lean Six Sigma Expert.') @@ -1226,9 +1373,12 @@ def lean_six_sigma_expert_access(request): messages.error(request, 'Failed to process payment. Please try again.') return redirect('agents:marketplace') + # Get or create database record for foreign key compatibility + agent_db_record = AgentFileService.get_or_create_agent_db_record(agent_data) + # Create execution record for tracking execution = AgentExecution.objects.create( - agent=agent, + agent=agent_db_record, user=request.user, input_data={'action': 'direct_access', 'source': 'try_now_button'}, fee_charged=agent.price, @@ -1257,18 +1407,28 @@ def swot_analysis_expert_view(request): return redirect('authentication:login') # Get the SWOT Analysis Expert agent - try: - agent = Agent.objects.get(slug='swot-analysis-expert', is_active=True) - except Agent.DoesNotExist: + agent_data = AgentFileService.get_agent_by_slug('swot-analysis-expert') + if not agent_data or not agent_data.get('is_active', True): messages.error(request, 'SWOT Analysis Expert is currently unavailable.') return redirect('agents:marketplace') + # Convert to compatible object + class AgentCompat: + def __init__(self, data): + self.slug = data['slug'] + self.name = data['name'] + self.price = float(data['price']) + self.webhook_url = data['webhook_url'] + self.id = data['slug'] + + agent = AgentCompat(agent_data) + # Check if user has a recent execution (within last 2 hours) or just redirect to payment from django.utils import timezone from datetime import timedelta recent_execution = AgentExecution.objects.filter( - agent=agent, + agent__slug=agent.slug, # Changed to slug-based lookup user=request.user, status='completed', created_at__gte=timezone.now() - timedelta(hours=2) @@ -1300,12 +1460,22 @@ def swot_analysis_expert_access(request): return redirect('authentication:login') # Get the SWOT Analysis Expert agent - try: - agent = Agent.objects.get(slug='swot-analysis-expert', is_active=True) - except Agent.DoesNotExist: + agent_data = AgentFileService.get_agent_by_slug('swot-analysis-expert') + if not agent_data or not agent_data.get('is_active', True): messages.error(request, 'SWOT Analysis Expert is currently unavailable.') return redirect('agents:marketplace') + # Convert to compatible object + class AgentCompat: + def __init__(self, data): + self.slug = data['slug'] + self.name = data['name'] + self.price = float(data['price']) + self.webhook_url = data['webhook_url'] + self.id = data['slug'] + + agent = AgentCompat(agent_data) + # Check if user has sufficient balance if not request.user.has_sufficient_balance(agent.price): messages.error(request, f'Insufficient balance! You need {agent.price} AED to access the SWOT Analysis Expert.') @@ -1322,9 +1492,12 @@ def swot_analysis_expert_access(request): messages.error(request, 'Failed to process payment. Please try again.') return redirect('agents:marketplace') + # Get or create database record for foreign key compatibility + agent_db_record = AgentFileService.get_or_create_agent_db_record(agent_data) + # Create execution record for tracking execution = AgentExecution.objects.create( - agent=agent, + agent=agent_db_record, user=request.user, input_data={'action': 'direct_access', 'source': 'try_now_button'}, fee_charged=agent.price,