netcop-ai/agents/views.py
thecyberlearn 213486946e Add frontend, agents apps and enhance API with authentication
- Add new Django apps: agents and frontend with URL routing
- Configure static files and templates directories in settings
- Add CSRF exemption and authentication to user endpoints
- Switch Stripe currency from USD to AED
- Add user management script and static assets
- Include REST framework token authentication

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-30 23:22:59 +05:30

368 lines
13 KiB
Python

import uuid
import json
import requests
from decimal import Decimal
from django.shortcuts import get_object_or_404
from django.utils import timezone
from rest_framework import status
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator
from .models import Agent, AgentExecution
from users.models import User
@api_view(['POST'])
@permission_classes([IsAuthenticated])
@csrf_exempt
def execute_agent(request):
"""Execute an agent with user input data"""
try:
# Get request data
agent_slug = request.data.get('agent_slug')
input_data = request.data.get('input_data', {})
if not agent_slug:
return Response(
{'error': 'agent_slug is required'},
status=status.HTTP_400_BAD_REQUEST
)
# Get the agent
agent = get_object_or_404(Agent, slug=agent_slug, is_active=True)
# Check user wallet balance
user = request.user
if user.wallet_balance < agent.price:
return Response(
{'error': f'Insufficient balance. You need {agent.price} AED but have {user.wallet_balance} AED. Please top up your wallet.'},
status=status.HTTP_400_BAD_REQUEST
)
# Create agent execution record
execution = AgentExecution.objects.create(
user=user,
agent=agent,
input_data=input_data,
price_paid=agent.price,
status='processing'
)
# Deduct amount from wallet
user.deduct_balance(agent.price)
# Generate session ID for n8n
session_id = f"session_{int(timezone.now().timestamp() * 1000)}_{execution.id.hex[:8]}"
# Format input data as message text
message_text = format_input_data_as_text(input_data, agent)
# Prepare webhook payload matching n8n format
webhook_payload = {
"sessionId": session_id,
"message": {
"text": message_text
},
"webhookUrl": agent.n8n_webhook_url,
"executionMode": "production",
"agentId": str(agent.id),
"executionId": str(execution.id),
"userId": str(user.id)
}
try:
# Send request to n8n webhook
response = requests.post(
agent.n8n_webhook_url,
json=webhook_payload,
headers={
'Content-Type': 'application/json',
'User-Agent': 'NetCop-AI-Agent/1.0'
},
timeout=30
)
if response.status_code == 200:
# Parse n8n response
try:
response_data = response.json()
except:
response_data = {"raw_response": response.text}
# Update execution with success
execution.output_data = response_data
execution.status = 'success'
execution.completed_at = timezone.now()
execution.save()
# Increment agent usage count
agent.increment_usage()
return Response({
'status': 'success',
'execution_id': str(execution.id),
'output_data': response_data,
'price_paid': float(agent.price),
'remaining_balance': float(user.wallet_balance)
})
else:
# Handle n8n error with user-friendly message
try:
error_data = response.json()
if response.status_code == 404:
user_error = f"This AI agent is temporarily unavailable. Please try again later or contact support."
elif 'webhook' in error_data.get('message', '').lower():
user_error = f"AI service is currently offline. Please try again in a few minutes."
else:
user_error = f"AI processing failed. Please try again or contact support."
except:
user_error = f"AI service is currently unavailable. Please try again later."
# Log technical details for debugging
technical_error = f"n8n webhook failed with status {response.status_code}: {response.text}"
print(f"Agent execution error: {technical_error}")
# Update execution with technical error for admin
execution.status = 'failed'
execution.error_message = technical_error
execution.completed_at = timezone.now()
execution.save()
# Refund user wallet
user.add_balance(agent.price)
return Response(
{'error': user_error},
status=status.HTTP_502_BAD_GATEWAY
)
except requests.exceptions.Timeout:
user_error = "AI processing is taking longer than expected. Please try again."
technical_error = "n8n webhook request timed out"
print(f"Agent execution timeout: {technical_error}")
execution.status = 'failed'
execution.error_message = technical_error
execution.completed_at = timezone.now()
execution.save()
# Refund user wallet
user.add_balance(agent.price)
return Response(
{'error': user_error},
status=status.HTTP_504_GATEWAY_TIMEOUT
)
except requests.exceptions.RequestException as e:
user_error = "Unable to connect to AI service. Please check your internet connection and try again."
technical_error = f"Failed to connect to n8n webhook: {str(e)}"
print(f"Agent execution connection error: {technical_error}")
execution.status = 'failed'
execution.error_message = technical_error
execution.completed_at = timezone.now()
execution.save()
# Refund user wallet
user.add_balance(agent.price)
return Response(
{'error': user_error},
status=status.HTTP_502_BAD_GATEWAY
)
except Exception as e:
return Response(
{'error': f'Internal server error: {str(e)}'},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
def format_input_data_as_text(input_data, agent):
"""Format user input data as a natural text message for n8n"""
# Try to create a natural language description
if not input_data:
return f"Execute {agent.name} with default parameters."
# Get form schema for context
form_schema = agent.form_schema
field_labels = {}
if form_schema and 'fields' in form_schema:
for field in form_schema['fields']:
field_labels[field['name']] = field.get('label', field['name'])
# Build natural text from input data
text_parts = [f"Execute {agent.name} with the following parameters:"]
for key, value in input_data.items():
if value: # Only include non-empty values
label = field_labels.get(key, key.replace('_', ' ').title())
text_parts.append(f"{label}: {value}")
return ". ".join(text_parts) + "."
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def get_agent_executions(request):
"""Get user's agent execution history"""
try:
executions = AgentExecution.objects.filter(
user=request.user
).select_related('agent').order_by('-created_at')
# Pagination
page_size = 20
page = int(request.GET.get('page', 1))
start = (page - 1) * page_size
end = start + page_size
executions_data = []
for execution in executions[start:end]:
executions_data.append({
'id': str(execution.id),
'agent_name': execution.agent.name,
'agent_slug': execution.agent.slug,
'status': execution.status,
'price_paid': float(execution.price_paid),
'created_at': execution.created_at.isoformat(),
'completed_at': execution.completed_at.isoformat() if execution.completed_at else None,
'has_output': bool(execution.output_data),
'error_message': execution.error_message
})
return Response({
'executions': executions_data,
'page': page,
'has_next': len(executions) > end,
'total_count': executions.count()
})
except Exception as e:
return Response(
{'error': f'Failed to fetch executions: {str(e)}'},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def get_execution_detail(request, execution_id):
"""Get detailed information about a specific execution"""
try:
execution = get_object_or_404(
AgentExecution,
id=execution_id,
user=request.user
)
return Response({
'id': str(execution.id),
'agent': {
'name': execution.agent.name,
'slug': execution.agent.slug,
'description': execution.agent.description
},
'status': execution.status,
'price_paid': float(execution.price_paid),
'input_data': execution.input_data,
'output_data': execution.output_data,
'error_message': execution.error_message,
'created_at': execution.created_at.isoformat(),
'updated_at': execution.updated_at.isoformat(),
'completed_at': execution.completed_at.isoformat() if execution.completed_at else None
})
except Exception as e:
return Response(
{'error': f'Failed to fetch execution details: {str(e)}'},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def list_agents(request):
"""List all available agents with filtering"""
try:
agents = Agent.objects.filter(is_active=True)
# Filter by category
category_slug = request.GET.get('category')
if category_slug:
agents = agents.filter(category__slug=category_slug)
# Search
search = request.GET.get('search', '').strip()
if search:
agents = agents.filter(
name__icontains=search
) | agents.filter(
short_description__icontains=search
)
agents_data = []
for agent in agents:
agents_data.append({
'id': str(agent.id),
'name': agent.name,
'slug': agent.slug,
'short_description': agent.short_description,
'price': float(agent.price),
'category': {
'name': agent.category.name,
'slug': agent.category.slug
},
'usage_count': agent.usage_count,
'is_featured': agent.is_featured,
'icon': agent.icon
})
return Response({'agents': agents_data})
except Exception as e:
return Response(
{'error': f'Failed to fetch agents: {str(e)}'},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def get_agent_detail(request, slug):
"""Get detailed information about a specific agent"""
try:
agent = get_object_or_404(Agent, slug=slug, is_active=True)
return Response({
'id': str(agent.id),
'name': agent.name,
'slug': agent.slug,
'description': agent.description,
'short_description': agent.short_description,
'price': float(agent.price),
'category': {
'name': agent.category.name,
'slug': agent.category.slug
},
'form_schema': agent.form_schema,
'usage_count': agent.usage_count,
'is_featured': agent.is_featured,
'icon': agent.icon,
'created_at': agent.created_at.isoformat()
})
except Exception as e:
return Response(
{'error': f'Failed to fetch agent details: {str(e)}'},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)