diff --git a/agents/management/commands/cleanup_expired_sessions.py b/agents/management/commands/cleanup_expired_sessions.py new file mode 100644 index 0000000..bdc1b3a --- /dev/null +++ b/agents/management/commands/cleanup_expired_sessions.py @@ -0,0 +1,30 @@ +from django.core.management.base import BaseCommand +from django.utils import timezone +from agents.models import ChatSession + +class Command(BaseCommand): + help = 'Mark expired chat sessions as expired' + + def handle(self, *args, **options): + now = timezone.now() + + # Find active sessions that have expired + expired_sessions = ChatSession.objects.filter( + status='active', + expires_at__lt=now + ) + + count = expired_sessions.count() + + if count > 0: + # Mark them as expired + expired_sessions.update( + status='expired', + completed_at=now + ) + + self.stdout.write( + self.style.SUCCESS(f'✅ Marked {count} expired sessions as expired') + ) + else: + self.stdout.write('✅ No expired sessions found') \ No newline at end of file diff --git a/agents/migrations/0003_chatsession_expires_at_alter_chatsession_status.py b/agents/migrations/0003_chatsession_expires_at_alter_chatsession_status.py new file mode 100644 index 0000000..e14e53c --- /dev/null +++ b/agents/migrations/0003_chatsession_expires_at_alter_chatsession_status.py @@ -0,0 +1,40 @@ +# Generated by Django 5.2.4 on 2025-08-01 10:08 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ( + "agents", + "0002_agent_agent_type_alter_agent_form_schema_chatsession_and_more", + ), + ] + + operations = [ + migrations.AddField( + model_name="chatsession", + name="expires_at", + field=models.DateTimeField( + blank=True, + help_text="Session expiration time (2 hours from last activity)", + null=True, + ), + ), + migrations.AlterField( + model_name="chatsession", + name="status", + field=models.CharField( + choices=[ + ("active", "Active"), + ("completed", "Completed"), + ("expired", "Expired"), + ("abandoned", "Abandoned"), + ("failed", "Failed"), + ], + default="active", + max_length=20, + ), + ), + ] diff --git a/agents/models.py b/agents/models.py index 25ae543..97cae91 100644 --- a/agents/models.py +++ b/agents/models.py @@ -73,6 +73,7 @@ class ChatSession(models.Model): STATUS_CHOICES = [ ('active', 'Active'), ('completed', 'Completed'), + ('expired', 'Expired'), ('abandoned', 'Abandoned'), ('failed', 'Failed'), ] @@ -84,10 +85,19 @@ class ChatSession(models.Model): 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) + expires_at = models.DateTimeField(null=True, blank=True, help_text="Session expiration time (2 hours from last activity)") created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) completed_at = models.DateTimeField(null=True, blank=True) + def save(self, *args, **kwargs): + # Set expires_at to 2 hours from now if not set + if not self.expires_at: + from django.utils import timezone + from datetime import timedelta + self.expires_at = timezone.now() + timedelta(hours=2) + super().save(*args, **kwargs) + class Meta: ordering = ['-created_at'] indexes = [ @@ -97,6 +107,20 @@ class ChatSession(models.Model): def __str__(self): return f"{self.agent.name} - {self.user.email} - {self.session_id}" + + def is_expired(self): + from django.utils import timezone + if not self.expires_at: + return False # Sessions without expiration date are considered active + return timezone.now() > self.expires_at + + def extend_session(self): + """Extend session by 2 hours from now""" + from django.utils import timezone + from datetime import timedelta + self.expires_at = timezone.now() + timedelta(hours=2) + self.updated_at = timezone.now() + self.save() class ChatMessage(models.Model): MESSAGE_TYPE_CHOICES = [ diff --git a/agents/templates/agents/agent_chat.html b/agents/templates/agents/agent_chat.html index 9a55fe6..50db12c 100644 --- a/agents/templates/agents/agent_chat.html +++ b/agents/templates/agents/agent_chat.html @@ -13,6 +13,71 @@ padding: 20px; } +/* Enhanced message formatting */ +.message-bubble h1, +.message-bubble h2, +.message-bubble h3, +.message-bubble h4 { + margin: 16px 0 8px 0; + font-weight: 600; + line-height: 1.3; +} + +.message-bubble h1 { font-size: 18px; color: var(--primary); } +.message-bubble h2 { font-size: 16px; color: var(--primary); } +.message-bubble h3 { font-size: 15px; color: var(--on-surface); } +.message-bubble h4 { font-size: 14px; color: var(--on-surface); } + +.message-bubble ul, +.message-bubble ol { + margin: 12px 0; + padding-left: 20px; +} + +.message-bubble li { + margin: 6px 0; + line-height: 1.5; +} + +.message-bubble strong { + font-weight: 600; + color: var(--on-surface); +} + +.message-bubble em { + font-style: italic; + color: var(--on-surface-variant); +} + +.message-bubble p { + margin: 10px 0; + line-height: 1.6; +} + +.message-bubble code { + background: var(--surface-variant); + padding: 2px 6px; + border-radius: 4px; + font-family: var(--font-mono); + font-size: 13px; +} + +.message-bubble pre { + background: var(--surface-variant); + padding: 12px; + border-radius: 8px; + overflow-x: auto; + margin: 12px 0; +} + +.message-bubble blockquote { + border-left: 3px solid var(--primary); + margin: 12px 0; + padding-left: 12px; + color: var(--on-surface-variant); + font-style: italic; +} + .chat-widget { background: var(--surface); border: 1px solid var(--outline); @@ -279,6 +344,11 @@ document.body.setAttribute('data-session-id', '{{ chat_session.session_id }}');