🚀 Implement file-based agent system for instant deployment

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 <noreply@anthropic.com>
This commit is contained in:
Claude 2025-08-14 09:28:34 +05:30
parent 2ffd0aae50
commit 56749b77f0
2 changed files with 588 additions and 68 deletions

347
agents/services.py Normal file
View File

@ -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

View File

@ -8,8 +8,9 @@ from django.utils import timezone
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib import messages from django.contrib import messages
from django.db import models from django.db import models
from .models import Agent, AgentExecution, AgentCategory, ChatSession, ChatMessage from .models import Agent, AgentExecution, ChatSession, ChatMessage
from .serializers import AgentSerializer, AgentExecutionSerializer from .serializers import AgentExecutionSerializer
from .services import AgentFileService
import requests import requests
import json import json
import time import time
@ -66,29 +67,30 @@ def validate_webhook_url(url):
@permission_classes([IsAuthenticated]) @permission_classes([IsAuthenticated])
def agent_list(request): def agent_list(request):
"""List all active agents with optional category filtering""" """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') category = request.GET.get('category')
if category: if category:
agents = agents.filter(category__slug=category) agents = AgentFileService.get_agents_by_category(category)
search = request.GET.get('search') search = request.GET.get('search')
if search: if search:
agents = agents.filter(name__icontains=search) agents = AgentFileService.search_agents(search)
paginator = PageNumberPagination() paginator = PageNumberPagination()
paginator.page_size = 20 paginator.page_size = 20
result_page = paginator.paginate_queryset(agents, request) result_page = paginator.paginate_queryset(agents, request)
serializer = AgentSerializer(result_page, many=True) # Return the data directly since we're working with dictionaries
return paginator.get_paginated_response(serializer.data) return paginator.get_paginated_response(result_page)
@api_view(['GET']) @api_view(['GET'])
@permission_classes([IsAuthenticated]) @permission_classes([IsAuthenticated])
def agent_detail(request, slug): def agent_detail(request, slug):
"""Get detailed agent information""" """Get detailed agent information"""
agent = get_object_or_404(Agent, slug=slug, is_active=True) agent = AgentFileService.get_agent_by_slug(slug)
serializer = AgentSerializer(agent) if not agent or not agent.get('is_active', True):
return Response(serializer.data) return Response({'error': 'Agent not found'}, status=status.HTTP_404_NOT_FOUND)
return Response(agent)
@api_view(['POST']) @api_view(['POST'])
@permission_classes([IsAuthenticated]) @permission_classes([IsAuthenticated])
@ -100,15 +102,31 @@ def execute_agent(request):
if not agent_slug: if not agent_slug:
return Response({'error': 'agent_slug is required'}, status=status.HTTP_400_BAD_REQUEST) 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) # 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): 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) 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 # Create execution record
execution = AgentExecution.objects.create( execution = AgentExecution.objects.create(
agent=agent, agent=agent_db_record,
user=request.user, user=request.user,
input_data=input_data, input_data=input_data,
fee_charged=agent.price, fee_charged=agent.price,
@ -205,12 +223,22 @@ def career_navigator_access(request):
return redirect('authentication:login') return redirect('authentication:login')
# Get the career navigator agent # Get the career navigator agent
try: agent_data = AgentFileService.get_agent_by_slug('cybersec-career-navigator')
agent = Agent.objects.get(slug='cybersec-career-navigator', is_active=True) if not agent_data or not agent_data.get('is_active', True):
except Agent.DoesNotExist:
messages.error(request, 'Career Navigator is currently unavailable.') messages.error(request, 'Career Navigator is currently unavailable.')
return redirect('agents:marketplace') 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 # Check if user has sufficient balance
if not request.user.has_sufficient_balance(agent.price): 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.') 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.') messages.error(request, 'Failed to process payment. Please try again.')
return redirect('agents:marketplace') 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 # Create execution record for tracking
execution = AgentExecution.objects.create( execution = AgentExecution.objects.create(
agent=agent, agent=agent_db_record,
user=request.user, user=request.user,
input_data={'action': 'direct_access', 'source': 'try_now_button'}, input_data={'action': 'direct_access', 'source': 'try_now_button'},
fee_charged=agent.price, fee_charged=agent.price,
@ -258,18 +289,28 @@ def career_navigator_view(request):
return redirect('authentication:login') return redirect('authentication:login')
# Get the career navigator agent # Get the career navigator agent
try: agent_data = AgentFileService.get_agent_by_slug('cybersec-career-navigator')
agent = Agent.objects.get(slug='cybersec-career-navigator', is_active=True) if not agent_data or not agent_data.get('is_active', True):
except Agent.DoesNotExist:
messages.error(request, 'Career Navigator is currently unavailable.') messages.error(request, 'Career Navigator is currently unavailable.')
return redirect('agents:marketplace') 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 # Check if user has a recent execution (within last 2 hours) or just redirect to payment
from django.utils import timezone from django.utils import timezone
from datetime import timedelta from datetime import timedelta
recent_execution = AgentExecution.objects.filter( recent_execution = AgentExecution.objects.filter(
agent=agent, agent__slug=agent.slug, # Changed to slug-based lookup
user=request.user, user=request.user,
status='completed', status='completed',
created_at__gte=timezone.now() - timedelta(hours=2) created_at__gte=timezone.now() - timedelta(hours=2)
@ -301,18 +342,28 @@ def ai_brand_strategist_view(request):
return redirect('authentication:login') return redirect('authentication:login')
# Get the AI Brand Strategist agent # Get the AI Brand Strategist agent
try: agent_data = AgentFileService.get_agent_by_slug('ai-brand-strategist')
agent = Agent.objects.get(slug='ai-brand-strategist', is_active=True) if not agent_data or not agent_data.get('is_active', True):
except Agent.DoesNotExist:
messages.error(request, 'AI Brand Strategist is currently unavailable.') messages.error(request, 'AI Brand Strategist is currently unavailable.')
return redirect('agents:marketplace') 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 # Check if user has a recent execution (within last 2 hours) or just redirect to payment
from django.utils import timezone from django.utils import timezone
from datetime import timedelta from datetime import timedelta
recent_execution = AgentExecution.objects.filter( recent_execution = AgentExecution.objects.filter(
agent=agent, agent__slug=agent.slug, # Changed to slug-based lookup
user=request.user, user=request.user,
status='completed', status='completed',
created_at__gte=timezone.now() - timedelta(hours=2) created_at__gte=timezone.now() - timedelta(hours=2)
@ -344,12 +395,22 @@ def ai_brand_strategist_access(request):
return redirect('authentication:login') return redirect('authentication:login')
# Get the AI Brand Strategist agent # Get the AI Brand Strategist agent
try: agent_data = AgentFileService.get_agent_by_slug('ai-brand-strategist')
agent = Agent.objects.get(slug='ai-brand-strategist', is_active=True) if not agent_data or not agent_data.get('is_active', True):
except Agent.DoesNotExist:
messages.error(request, 'AI Brand Strategist is currently unavailable.') messages.error(request, 'AI Brand Strategist is currently unavailable.')
return redirect('agents:marketplace') 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 # Check if user has sufficient balance
if not request.user.has_sufficient_balance(agent.price): 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.') 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.') messages.error(request, 'Failed to process payment. Please try again.')
return redirect('agents:marketplace') 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 # Create execution record for tracking
execution = AgentExecution.objects.create( execution = AgentExecution.objects.create(
agent=agent, agent=agent_db_record,
user=request.user, user=request.user,
input_data={'action': 'direct_access', 'source': 'try_now_button'}, input_data={'action': 'direct_access', 'source': 'try_now_button'},
fee_charged=agent.price, fee_charged=agent.price,
@ -436,15 +500,18 @@ def format_agent_message(agent_slug, input_data):
@login_required @login_required
def agent_detail_view(request, slug): def agent_detail_view(request, slug):
"""Render agent detail page with dynamic form or chat interface""" """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 # Handle chat-based agents
if agent.agent_type == 'chat': if agent.get('agent_type') == 'chat':
return chat_agent_view(request, agent) return chat_agent_view(request, agent)
# Handle form-based agents (existing behavior) # Handle form-based agents (existing behavior)
# Get all other active agents for quick access panel # 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 = { context = {
'agent': agent, 'agent': agent,
@ -457,22 +524,19 @@ def agent_detail_view(request, slug):
def agents_marketplace(request): def agents_marketplace(request):
"""Agent marketplace view""" """Agent marketplace view"""
agents = Agent.objects.filter(is_active=True).select_related('category') # Get agents from file service
categories = AgentCategory.objects.filter(is_active=True) agents = AgentFileService.get_active_agents()
categories = AgentFileService.get_all_categories()
# Filter by category # Filter by category
category_slug = request.GET.get('category') category_slug = request.GET.get('category')
if category_slug: if category_slug:
agents = agents.filter(category__slug=category_slug) agents = AgentFileService.get_agents_by_category(category_slug)
# Search functionality # Search functionality
search_query = request.GET.get('search', '').strip() search_query = request.GET.get('search', '').strip()
if search_query: if search_query:
agents = agents.filter( agents = AgentFileService.search_agents(search_query)
models.Q(name__icontains=search_query) |
models.Q(short_description__icontains=search_query) |
models.Q(description__icontains=search_query)
)
context = { context = {
'agents': agents, 'agents': agents,
@ -491,10 +555,25 @@ def chat_agent_view(request, agent):
chat_session = None chat_session = None
messages = [] 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: 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( chat_session = ChatSession.objects.filter(
agent=agent, agent__slug=agent_compat.slug, # Changed to slug-based lookup
user=request.user, user=request.user,
status='active' status='active'
).first() ).first()
@ -504,7 +583,7 @@ def chat_agent_view(request, agent):
if session_id and not chat_session: if session_id and not chat_session:
chat_session = ChatSession.objects.filter( chat_session = ChatSession.objects.filter(
session_id=session_id, session_id=session_id,
agent=agent, agent__slug=agent_compat.slug, # Changed to slug-based lookup
user=request.user user=request.user
).first() ).first()
@ -513,11 +592,11 @@ def chat_agent_view(request, agent):
messages = ChatMessage.objects.filter(session=chat_session).order_by('timestamp') messages = ChatMessage.objects.filter(session=chat_session).order_by('timestamp')
# Get all other active agents for quick access panel # 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) # Get previous sessions for this user and agent (excluding current active session)
previous_sessions_query = ChatSession.objects.filter( previous_sessions_query = ChatSession.objects.filter(
agent=agent, agent__slug=agent_compat.slug, # Changed to slug-based lookup
user=request.user user=request.user
).exclude(status='active').order_by('-created_at')[:5] # Last 5 non-active sessions ).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) # Message calculations (only count user messages)
user_message_count = messages.filter(message_type='user').count() 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) message_percentage = min(100, (user_message_count / message_limit) * 100)
session_data = { session_data = {
@ -567,7 +646,7 @@ def chat_agent_view(request, agent):
} }
context = { context = {
'agent': agent, 'agent': agent, # Keep original agent data for template compatibility
'chat_session': chat_session, 'chat_session': chat_session,
'messages': messages, 'messages': messages,
'all_agents': all_agents, 'all_agents': all_agents,
@ -588,15 +667,28 @@ def start_chat_session(request):
if not agent_slug: if not agent_slug:
return Response({'error': 'agent_slug is required'}, status=status.HTTP_400_BAD_REQUEST) 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 # Check wallet balance
if hasattr(request.user, 'wallet_balance') and request.user.wallet_balance < agent.price: 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) 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( existing_session = ChatSession.objects.filter(
agent=agent, agent__slug=agent.slug,
user=request.user, user=request.user,
status='active' status='active'
).first() ).first()
@ -613,9 +705,12 @@ def start_chat_session(request):
from django.utils import timezone from django.utils import timezone
from datetime import timedelta 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( chat_session = ChatSession.objects.create(
session_id=session_id, session_id=session_id,
agent=agent, agent=agent_db_record,
user=request.user, user=request.user,
fee_charged=agent.price, fee_charged=agent.price,
status='active', status='active',
@ -1099,7 +1194,23 @@ def direct_access_handler(request, slug):
Generic handler for direct access agents (external forms like JotForm). Generic handler for direct access agents (external forms like JotForm).
Handles payment processing and grants access to external form. 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 # Verify this is a direct access agent
if not agent.access_url_name or not agent.display_url_name: 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. Generic display handler for direct access agents.
Shows external form (JotForm, Google Forms, etc.) in iframe or redirects directly. 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 # Verify this is a direct access agent
if not agent.access_url_name or not agent.display_url_name: 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') return redirect('authentication:login')
# Get the Lean Six Sigma Expert agent # Get the Lean Six Sigma Expert agent
try: agent_data = AgentFileService.get_agent_by_slug('lean-six-sigma-expert')
agent = Agent.objects.get(slug='lean-six-sigma-expert', is_active=True) if not agent_data or not agent_data.get('is_active', True):
except Agent.DoesNotExist:
messages.error(request, 'Lean Six Sigma Expert is currently unavailable.') messages.error(request, 'Lean Six Sigma Expert is currently unavailable.')
return redirect('agents:marketplace') 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 # Check if user has a recent execution (within last 2 hours) or just redirect to payment
from django.utils import timezone from django.utils import timezone
from datetime import timedelta from datetime import timedelta
recent_execution = AgentExecution.objects.filter( recent_execution = AgentExecution.objects.filter(
agent=agent, agent__slug=agent.slug, # Changed to slug-based lookup
user=request.user, user=request.user,
status='completed', status='completed',
created_at__gte=timezone.now() - timedelta(hours=2) created_at__gte=timezone.now() - timedelta(hours=2)
@ -1204,12 +1341,22 @@ def lean_six_sigma_expert_access(request):
return redirect('authentication:login') return redirect('authentication:login')
# Get the Lean Six Sigma Expert agent # Get the Lean Six Sigma Expert agent
try: agent_data = AgentFileService.get_agent_by_slug('lean-six-sigma-expert')
agent = Agent.objects.get(slug='lean-six-sigma-expert', is_active=True) if not agent_data or not agent_data.get('is_active', True):
except Agent.DoesNotExist:
messages.error(request, 'Lean Six Sigma Expert is currently unavailable.') messages.error(request, 'Lean Six Sigma Expert is currently unavailable.')
return redirect('agents:marketplace') 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 # Check if user has sufficient balance
if not request.user.has_sufficient_balance(agent.price): 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.') 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.') messages.error(request, 'Failed to process payment. Please try again.')
return redirect('agents:marketplace') 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 # Create execution record for tracking
execution = AgentExecution.objects.create( execution = AgentExecution.objects.create(
agent=agent, agent=agent_db_record,
user=request.user, user=request.user,
input_data={'action': 'direct_access', 'source': 'try_now_button'}, input_data={'action': 'direct_access', 'source': 'try_now_button'},
fee_charged=agent.price, fee_charged=agent.price,
@ -1257,18 +1407,28 @@ def swot_analysis_expert_view(request):
return redirect('authentication:login') return redirect('authentication:login')
# Get the SWOT Analysis Expert agent # Get the SWOT Analysis Expert agent
try: agent_data = AgentFileService.get_agent_by_slug('swot-analysis-expert')
agent = Agent.objects.get(slug='swot-analysis-expert', is_active=True) if not agent_data or not agent_data.get('is_active', True):
except Agent.DoesNotExist:
messages.error(request, 'SWOT Analysis Expert is currently unavailable.') messages.error(request, 'SWOT Analysis Expert is currently unavailable.')
return redirect('agents:marketplace') 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 # Check if user has a recent execution (within last 2 hours) or just redirect to payment
from django.utils import timezone from django.utils import timezone
from datetime import timedelta from datetime import timedelta
recent_execution = AgentExecution.objects.filter( recent_execution = AgentExecution.objects.filter(
agent=agent, agent__slug=agent.slug, # Changed to slug-based lookup
user=request.user, user=request.user,
status='completed', status='completed',
created_at__gte=timezone.now() - timedelta(hours=2) created_at__gte=timezone.now() - timedelta(hours=2)
@ -1300,12 +1460,22 @@ def swot_analysis_expert_access(request):
return redirect('authentication:login') return redirect('authentication:login')
# Get the SWOT Analysis Expert agent # Get the SWOT Analysis Expert agent
try: agent_data = AgentFileService.get_agent_by_slug('swot-analysis-expert')
agent = Agent.objects.get(slug='swot-analysis-expert', is_active=True) if not agent_data or not agent_data.get('is_active', True):
except Agent.DoesNotExist:
messages.error(request, 'SWOT Analysis Expert is currently unavailable.') messages.error(request, 'SWOT Analysis Expert is currently unavailable.')
return redirect('agents:marketplace') 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 # Check if user has sufficient balance
if not request.user.has_sufficient_balance(agent.price): 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.') 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.') messages.error(request, 'Failed to process payment. Please try again.')
return redirect('agents:marketplace') 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 # Create execution record for tracking
execution = AgentExecution.objects.create( execution = AgentExecution.objects.create(
agent=agent, agent=agent_db_record,
user=request.user, user=request.user,
input_data={'action': 'direct_access', 'source': 'try_now_button'}, input_data={'action': 'direct_access', 'source': 'try_now_button'},
fee_charged=agent.price, fee_charged=agent.price,