diff --git a/agents/admin.py b/agents/admin.py index 9bcf368..e2f55c6 100644 --- a/agents/admin.py +++ b/agents/admin.py @@ -1,5 +1,5 @@ from django.contrib import admin -from .models import AgentCategory, Agent, AgentExecution +from .models import AgentCategory, Agent, AgentExecution, ChatSession, ChatMessage @admin.register(AgentCategory) class AgentCategoryAdmin(admin.ModelAdmin): @@ -10,8 +10,8 @@ class AgentCategoryAdmin(admin.ModelAdmin): @admin.register(Agent) class AgentAdmin(admin.ModelAdmin): - list_display = ['name', 'category', 'price', 'is_active', 'created_at'] - list_filter = ['category', 'is_active', 'created_at'] + list_display = ['name', 'category', 'agent_type', 'price', 'is_active', 'created_at'] + list_filter = ['category', 'agent_type', 'is_active', 'created_at'] search_fields = ['name', 'description', 'short_description'] prepopulated_fields = {'slug': ('name',)} readonly_fields = ['created_at', 'updated_at'] @@ -22,3 +22,21 @@ class AgentExecutionAdmin(admin.ModelAdmin): list_filter = ['status', 'created_at', 'agent__category'] search_fields = ['agent__name', 'user__email'] readonly_fields = ['created_at', 'completed_at'] + +@admin.register(ChatSession) +class ChatSessionAdmin(admin.ModelAdmin): + list_display = ['session_id', 'agent', 'user', 'status', 'fee_charged', 'created_at'] + list_filter = ['status', 'agent__category', 'created_at'] + search_fields = ['session_id', 'agent__name', 'user__email'] + readonly_fields = ['session_id', 'created_at', 'updated_at', 'completed_at'] + +@admin.register(ChatMessage) +class ChatMessageAdmin(admin.ModelAdmin): + list_display = ['session', 'message_type', 'content_preview', 'timestamp'] + list_filter = ['message_type', 'timestamp'] + search_fields = ['session__session_id', 'content'] + readonly_fields = ['timestamp'] + + def content_preview(self, obj): + return obj.content[:50] + "..." if len(obj.content) > 50 else obj.content + content_preview.short_description = 'Content Preview' diff --git a/agents/management/commands/create_five_whys_agent.py b/agents/management/commands/create_five_whys_agent.py new file mode 100644 index 0000000..dac2df0 --- /dev/null +++ b/agents/management/commands/create_five_whys_agent.py @@ -0,0 +1,76 @@ +from django.core.management.base import BaseCommand +from agents.models import AgentCategory, Agent + +class Command(BaseCommand): + help = 'Create the 5 Whys chat-based analysis agent' + + def handle(self, *args, **options): + # Create or get the Analysis category + category, created = AgentCategory.objects.get_or_create( + slug='analysis', + defaults={ + 'name': 'Analysis & Problem Solving', + 'description': 'Advanced analytical tools for problem-solving and decision making', + 'icon': '🧠', + 'is_active': True + } + ) + + if created: + self.stdout.write(f'✅ Created category: {category.name}') + else: + self.stdout.write(f'📂 Using existing category: {category.name}') + + # Create the 5 Whys agent + agent, created = Agent.objects.get_or_create( + slug='five-whys-analysis', + defaults={ + 'name': '5 Whys Analysis', + 'short_description': 'Interactive problem-solving using the proven 5 Whys methodology', + 'description': '''Discover the root cause of any problem through guided conversation using the 5 Whys technique. + +This interactive agent helps you systematically drill down to the core issue by asking "why" five times. Perfect for: +• Troubleshooting operational problems +• Understanding process failures +• Identifying systemic issues +• Improving quality and efficiency + +The conversation-based approach ensures you think deeply about each layer of the problem, leading to more effective solutions.''', + 'category': category, + 'price': 15.00, + 'agent_type': 'chat', # This is a chat-based agent + 'form_schema': None, # Chat agents don't use form schemas + 'webhook_url': 'http://localhost:5678/webhook/5-whys-web', # N8N webhook URL + 'is_active': True + } + ) + + if created: + self.stdout.write( + self.style.SUCCESS(f'🎉 Successfully created 5 Whys Analysis agent!') + ) + self.stdout.write(f' 💬 Agent Type: {agent.agent_type}') + self.stdout.write(f' 💰 Price: {agent.price} AED') + self.stdout.write(f' 🔗 Webhook: {agent.webhook_url}') + self.stdout.write(f' 📂 Category: {agent.category.name}') + else: + self.stdout.write( + self.style.WARNING(f'⚠️ 5 Whys Analysis agent already exists') + ) + + # Update existing agent to ensure it's chat-based + if agent.agent_type != 'chat': + agent.agent_type = 'chat' + agent.form_schema = None + agent.save() + self.stdout.write( + self.style.SUCCESS(f'✅ Updated existing agent to chat-based') + ) + + self.stdout.write('') + self.stdout.write('🚀 Next steps:') + self.stdout.write(' 1. Ensure N8N webhook is running on localhost:5678') + self.stdout.write(' 2. Visit /agents/five-whys-analysis/ to test the chat interface') + self.stdout.write(' 3. Start a conversation to test the 5 Whys methodology') + self.stdout.write('') + self.stdout.write('💡 The agent is now ready for interactive problem-solving!') \ No newline at end of file diff --git a/agents/migrations/0002_agent_agent_type_alter_agent_form_schema_chatsession_and_more.py b/agents/migrations/0002_agent_agent_type_alter_agent_form_schema_chatsession_and_more.py new file mode 100644 index 0000000..798c6ea --- /dev/null +++ b/agents/migrations/0002_agent_agent_type_alter_agent_form_schema_chatsession_and_more.py @@ -0,0 +1,160 @@ +# Generated by Django 5.2.4 on 2025-08-01 04:01 + +import django.db.models.deletion +import uuid +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("agents", "0001_initial"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AddField( + model_name="agent", + name="agent_type", + field=models.CharField( + choices=[("form", "Form-based"), ("chat", "Chat-based")], + default="form", + help_text="Agent interaction type", + max_length=10, + ), + ), + migrations.AlterField( + model_name="agent", + name="form_schema", + field=models.JSONField( + blank=True, help_text="JSON schema for agent input form", null=True + ), + ), + migrations.CreateModel( + name="ChatSession", + fields=[ + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ( + "session_id", + models.CharField( + help_text="Unique session identifier", + max_length=100, + unique=True, + ), + ), + ( + "status", + models.CharField( + choices=[ + ("active", "Active"), + ("completed", "Completed"), + ("abandoned", "Abandoned"), + ("failed", "Failed"), + ], + default="active", + max_length=20, + ), + ), + ( + "context_data", + models.JSONField( + default=dict, help_text="Session context and progress tracking" + ), + ), + ("fee_charged", models.DecimalField(decimal_places=2, max_digits=10)), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ("completed_at", models.DateTimeField(blank=True, null=True)), + ( + "agent", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="chat_sessions", + to="agents.agent", + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "ordering": ["-created_at"], + }, + ), + migrations.CreateModel( + name="ChatMessage", + fields=[ + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ( + "message_type", + models.CharField( + choices=[ + ("user", "User Message"), + ("agent", "Agent Response"), + ("system", "System Message"), + ], + max_length=10, + ), + ), + ("content", models.TextField()), + ( + "metadata", + models.JSONField( + default=dict, + help_text="Additional message data like webhook responses", + ), + ), + ("timestamp", models.DateTimeField(auto_now_add=True)), + ( + "session", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="messages", + to="agents.chatsession", + ), + ), + ], + options={ + "ordering": ["timestamp"], + }, + ), + migrations.AddIndex( + model_name="chatsession", + index=models.Index( + fields=["session_id"], name="agents_chat_session_0d9cb4_idx" + ), + ), + migrations.AddIndex( + model_name="chatsession", + index=models.Index( + fields=["user", "-created_at"], name="agents_chat_user_id_f8983d_idx" + ), + ), + migrations.AddIndex( + model_name="chatmessage", + index=models.Index( + fields=["session", "timestamp"], name="agents_chat_session_e8eaed_idx" + ), + ), + ] diff --git a/agents/models.py b/agents/models.py index e5537df..25ae543 100644 --- a/agents/models.py +++ b/agents/models.py @@ -17,6 +17,11 @@ class AgentCategory(models.Model): return self.name class Agent(models.Model): + AGENT_TYPE_CHOICES = [ + ('form', 'Form-based'), + ('chat', 'Chat-based'), + ] + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) name = models.CharField(max_length=200) slug = models.SlugField(unique=True) @@ -24,7 +29,8 @@ class Agent(models.Model): description = models.TextField() category = models.ForeignKey(AgentCategory, on_delete=models.CASCADE, related_name='agents') price = models.DecimalField(max_digits=10, decimal_places=2) - form_schema = models.JSONField(help_text="JSON schema for agent input form") + agent_type = models.CharField(max_length=10, choices=AGENT_TYPE_CHOICES, default='form', help_text="Agent interaction type") + form_schema = models.JSONField(help_text="JSON schema for agent input form", null=True, blank=True) webhook_url = models.URLField(help_text="n8n webhook URL for execution") is_active = models.BooleanField(default=True) created_at = models.DateTimeField(auto_now_add=True) @@ -62,3 +68,55 @@ class AgentExecution(models.Model): def __str__(self): return f"{self.agent.name} - {self.user.email} - {self.status}" + +class ChatSession(models.Model): + STATUS_CHOICES = [ + ('active', 'Active'), + ('completed', 'Completed'), + ('abandoned', 'Abandoned'), + ('failed', 'Failed'), + ] + + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + session_id = models.CharField(max_length=100, unique=True, help_text="Unique session identifier") + agent = models.ForeignKey(Agent, on_delete=models.CASCADE, related_name='chat_sessions') + user = models.ForeignKey('authentication.User', on_delete=models.CASCADE) + status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='active') + context_data = models.JSONField(default=dict, help_text="Session context and progress tracking") + fee_charged = models.DecimalField(max_digits=10, decimal_places=2) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + completed_at = models.DateTimeField(null=True, blank=True) + + class Meta: + ordering = ['-created_at'] + indexes = [ + models.Index(fields=['session_id']), + models.Index(fields=['user', '-created_at']), + ] + + def __str__(self): + return f"{self.agent.name} - {self.user.email} - {self.session_id}" + +class ChatMessage(models.Model): + MESSAGE_TYPE_CHOICES = [ + ('user', 'User Message'), + ('agent', 'Agent Response'), + ('system', 'System Message'), + ] + + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + session = models.ForeignKey(ChatSession, on_delete=models.CASCADE, related_name='messages') + message_type = models.CharField(max_length=10, choices=MESSAGE_TYPE_CHOICES) + content = models.TextField() + metadata = models.JSONField(default=dict, help_text="Additional message data like webhook responses") + timestamp = models.DateTimeField(auto_now_add=True) + + class Meta: + ordering = ['timestamp'] + indexes = [ + models.Index(fields=['session', 'timestamp']), + ] + + def __str__(self): + return f"{self.session.session_id} - {self.message_type} - {self.timestamp}" diff --git a/agents/templates/agents/agent_chat.html b/agents/templates/agents/agent_chat.html new file mode 100644 index 0000000..9a55fe6 --- /dev/null +++ b/agents/templates/agents/agent_chat.html @@ -0,0 +1,590 @@ +{% extends 'base.html' %} +{% load static %} + +{% block title %}{{ agent.name }} - Quantum Tasks AI{% endblock %} + +{% block extra_css %} + + +{% endblock %} + +{% block content %} + + +