diff --git a/agents/management/commands/create_five_whys_agent.py b/agents/management/commands/create_five_whys_agent.py
index dac2df0..20b616f 100644
--- a/agents/management/commands/create_five_whys_agent.py
+++ b/agents/management/commands/create_five_whys_agent.py
@@ -27,18 +27,11 @@ class Command(BaseCommand):
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.''',
+ 'description': '''Systematically find root causes through guided 5 Whys methodology. Perfect for troubleshooting operational problems, understanding failures, and identifying systemic issues.''',
'category': category,
'price': 15.00,
'agent_type': 'chat', # This is a chat-based agent
+ 'message_limit': 20, # Limit messages per session
'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
diff --git a/agents/migrations/0004_add_message_limit.py b/agents/migrations/0004_add_message_limit.py
new file mode 100644
index 0000000..dfb0dd9
--- /dev/null
+++ b/agents/migrations/0004_add_message_limit.py
@@ -0,0 +1,21 @@
+# Generated by Django 5.2.4 on 2025-08-03 04:57
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("agents", "0003_chatsession_expires_at_alter_chatsession_status"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="agent",
+ name="message_limit",
+ field=models.IntegerField(
+ default=50,
+ help_text="Maximum messages per chat session (for chat agents)",
+ ),
+ ),
+ ]
diff --git a/agents/models.py b/agents/models.py
index 97cae91..8041e8d 100644
--- a/agents/models.py
+++ b/agents/models.py
@@ -32,6 +32,7 @@ class Agent(models.Model):
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")
+ message_limit = models.IntegerField(default=50, help_text="Maximum messages per chat session (for chat agents)")
is_active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
@@ -85,17 +86,17 @@ 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)")
+ expires_at = models.DateTimeField(null=True, blank=True, help_text="Session expiration time (30 minutes 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
+ # Set expires_at to 30 minutes 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)
+ self.expires_at = timezone.now() + timedelta(minutes=30)
super().save(*args, **kwargs)
class Meta:
@@ -115,10 +116,10 @@ class ChatSession(models.Model):
return timezone.now() > self.expires_at
def extend_session(self):
- """Extend session by 2 hours from now"""
+ """Extend session by 30 minutes from now"""
from django.utils import timezone
from datetime import timedelta
- self.expires_at = timezone.now() + timedelta(hours=2)
+ self.expires_at = timezone.now() + timedelta(minutes=30)
self.updated_at = timezone.now()
self.save()
diff --git a/agents/templates/agents/agent_chat.html b/agents/templates/agents/agent_chat.html
index fde5629..5085ae9 100644
--- a/agents/templates/agents/agent_chat.html
+++ b/agents/templates/agents/agent_chat.html
@@ -83,7 +83,7 @@
border: 1px solid var(--outline);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-lg);
- height: 600px;
+ height: 800px;
display: flex;
flex-direction: column;
}
@@ -94,6 +94,9 @@
background: var(--gradient-primary);
color: white;
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
}
.chat-title {
@@ -265,7 +268,7 @@
}
.chat-widget {
- height: 500px;
+ height: 650px;
}
.message-bubble {
@@ -310,6 +313,92 @@
font-size: 14px;
opacity: 0.7;
}
+
+/* Download dropdown styles */
+.download-dropdown {
+ position: relative;
+}
+
+.download-btn {
+ background: rgba(255, 255, 255, 0.2);
+ color: white;
+ border: 1px solid rgba(255, 255, 255, 0.3);
+ padding: 8px 16px;
+ border-radius: 6px;
+ font-size: 14px;
+ cursor: pointer;
+ transition: all 0.2s ease;
+}
+
+.download-btn:hover {
+ background: rgba(255, 255, 255, 0.3);
+ transform: translateY(-1px);
+}
+
+.download-menu {
+ position: absolute;
+ top: 100%;
+ right: 0;
+ background: white;
+ border: 1px solid var(--outline);
+ border-radius: 6px;
+ box-shadow: var(--shadow-lg);
+ min-width: 160px;
+ z-index: 1000;
+ display: none;
+ margin-top: 4px;
+}
+
+.download-menu.show {
+ display: block;
+}
+
+.download-option {
+ display: block;
+ padding: 12px 16px;
+ color: var(--on-surface);
+ text-decoration: none;
+ font-size: 14px;
+ border-bottom: 1px solid var(--outline-variant);
+ transition: background 0.2s ease;
+}
+
+.download-option:last-child {
+ border-bottom: none;
+}
+
+.download-option:hover {
+ background: var(--surface-variant);
+ color: var(--on-surface);
+}
+
+/* Sidebar layout */
+.agent-sidebar {
+ display: flex;
+ flex-direction: column;
+ gap: var(--spacing-md);
+ flex: 0 0 auto;
+ min-width: 300px;
+}
+
+/* 5 Whys specific spacing adjustments */
+.agent-container {
+ padding: var(--spacing-sm) !important;
+}
+
+.agent-header {
+ margin-bottom: 20px !important;
+}
+
+.agent-grid {
+ padding: 0 !important;
+ margin: 0 !important;
+}
+
+.agent-widget.widget-large {
+ padding: 20px !important;
+ margin: 0 !important;
+}
{% endblock %}
@@ -327,10 +416,12 @@ document.body.setAttribute('data-user-id', '{{ user.id }}');
{% endif %}
{% if chat_session %}
document.body.setAttribute('data-session-id', '{{ chat_session.session_id }}');
+document.body.setAttribute('data-session-created', '{{ chat_session.created_at.isoformat }}');
+document.body.setAttribute('data-session-expires', '{{ chat_session.expires_at.isoformat }}');
{% endif %}
-
+
{% include "components/agent_header.html" with agent_title=agent.name agent_subtitle=agent.short_description %}
@@ -343,29 +434,34 @@ document.body.setAttribute('data-session-id', '{{ chat_session.session_id }}');
{% if user.is_authenticated %}
{% if user.wallet_balance >= agent.price or chat_session %}
-
- {% if chat_session %}
-
-
- 💬 Chat Session: {{ chat_session.get_status_display }}
- {% if chat_session.status == 'active' %}
- • Active
- {% elif chat_session.status == 'expired' %}
- • Expired
- {% endif %}
-
-
ID: {{ chat_session.session_id }}
-
- {% endif %}
{% endblock %}
@@ -585,6 +685,10 @@ class ChatInterface {
if (response.ok) {
// Add agent response
this.addMessage('agent', data.agent_message.content);
+ // Update message count indicators
+ if (typeof updateMessageCount === 'function') {
+ updateMessageCount();
+ }
} else {
this.showError(data.error || 'Failed to send message');
}
@@ -657,8 +761,206 @@ class ChatInterface {
// Initialize chat interface when page loads
document.addEventListener('DOMContentLoaded', function() {
new ChatInterface();
+
+ // Clear any old localStorage data for sessions
+ const sessionId = document.body.getAttribute('data-session-id');
+ if (sessionId) {
+ // Clear old session start time cache
+ localStorage.removeItem(`session_start_${sessionId}`);
+ }
+
+ // Initialize session indicators updates
+ if (document.getElementById('timeRemaining')) {
+ updateSessionIndicators();
+ setInterval(updateSessionIndicators, 30000); // Update every 30 seconds for better accuracy
+ }
});
+// Download functionality
+function toggleDownloadMenu() {
+ const menu = document.getElementById('downloadMenu');
+ menu.classList.toggle('show');
+}
+
+function downloadChat(format) {
+ const sessionId = document.body.getAttribute('data-session-id');
+ if (!sessionId) {
+ alert('No active session found');
+ return;
+ }
+
+ // Hide the dropdown menu
+ document.getElementById('downloadMenu').classList.remove('show');
+
+ // Show loading state
+ const downloadBtn = document.getElementById('downloadBtn');
+ const originalText = downloadBtn.textContent;
+ downloadBtn.textContent = '⏳ Preparing...';
+ downloadBtn.disabled = true;
+
+ // Create download URL
+ const downloadUrl = `/agents/api/chat/export/${sessionId}/?format=${format}`;
+
+ // Create a temporary link and trigger download
+ const link = document.createElement('a');
+ link.href = downloadUrl;
+ link.download = `5whys_chat_${sessionId}.${format}`;
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+
+ // Reset button state after a short delay
+ setTimeout(() => {
+ downloadBtn.textContent = originalText;
+ downloadBtn.disabled = false;
+ }, 2000);
+}
+
+// Close dropdown when clicking outside
+document.addEventListener('click', function(event) {
+ const dropdown = document.querySelector('.download-dropdown');
+ const menu = document.getElementById('downloadMenu');
+
+ if (dropdown && !dropdown.contains(event.target)) {
+ menu.classList.remove('show');
+ }
+});
+
+// Session indicators real-time updates
+function updateSessionIndicators() {
+ const sessionId = document.body.getAttribute('data-session-id');
+ if (!sessionId) return;
+
+ // Fetch real session data from server
+ fetch(`/agents/api/chat/session/${sessionId}/status/`, {
+ method: 'GET',
+ headers: {
+ 'X-CSRFToken': getCSRFToken(),
+ 'Content-Type': 'application/json'
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ if (data.success) {
+ updateTimeIndicator(data.time_remaining_seconds, data.time_remaining_str);
+ updateMessageIndicator(data.message_count, data.message_limit);
+ updateWarnings(data.time_percentage, data.message_percentage);
+ }
+ })
+ .catch(error => {
+ console.error('Error fetching session status:', error);
+ // Fallback to approximate calculation
+ updateSessionIndicatorsApproximate();
+ });
+}
+
+function updateSessionIndicatorsApproximate() {
+ const sessionId = document.body.getAttribute('data-session-id');
+ if (!sessionId) return;
+
+ // Use actual session expiration time from server
+ const sessionExpiresStr = document.body.getAttribute('data-session-expires');
+
+ if (sessionExpiresStr) {
+ const sessionExpires = new Date(sessionExpiresStr);
+ const now = new Date();
+ const timeRemaining = Math.max(0, sessionExpires - now);
+
+ const minutesRemaining = Math.floor(timeRemaining / (1000 * 60));
+ const timeRemainingStr = `${minutesRemaining}m`;
+
+ const sessionDuration = 30 * 60 * 1000; // 30 minutes in milliseconds
+ const timePercentage = Math.max(0, (timeRemaining / sessionDuration) * 100);
+
+ updateTimeIndicator(timeRemaining / 1000, timeRemainingStr);
+
+ // For messages, count current user messages
+ const messageElements = document.querySelectorAll('.message.user');
+ const currentCount = messageElements.length;
+ const maxMessages = 20;
+ updateMessageIndicator(currentCount, maxMessages);
+
+ updateWarnings(timePercentage, (currentCount / maxMessages) * 100);
+ } else {
+ console.warn('No session expiration data available');
+ }
+}
+
+function updateTimeIndicator(timeRemainingSeconds, timeRemainingStr) {
+ const timeElement = document.getElementById('timeRemaining');
+ const timeProgressElement = document.getElementById('timeProgress');
+
+ if (timeElement) {
+ timeElement.textContent = timeRemainingStr;
+ }
+
+ if (timeProgressElement) {
+ const totalTime = 30 * 60; // 30 minutes in seconds
+ const percentage = Math.max(0, (timeRemainingSeconds / totalTime) * 100);
+ timeProgressElement.style.width = percentage + '%';
+
+ // Update color based on time remaining
+ if (percentage <= 20) {
+ timeProgressElement.style.background = 'linear-gradient(90deg, #ef4444, #dc2626)';
+ } else if (percentage <= 40) {
+ timeProgressElement.style.background = 'linear-gradient(90deg, #f59e0b, #d97706)';
+ } else {
+ timeProgressElement.style.background = 'linear-gradient(90deg, #10b981, #059669)';
+ }
+ }
+}
+
+function updateMessageIndicator(currentCount, maxMessages) {
+ const messageCountElement = document.getElementById('messageCount');
+ const messageProgressElement = document.getElementById('messageProgress');
+
+ if (messageCountElement) {
+ messageCountElement.textContent = `${currentCount}/${maxMessages} used`;
+ }
+
+ if (messageProgressElement) {
+ const percentage = (currentCount / maxMessages) * 100;
+ messageProgressElement.style.width = percentage + '%';
+
+ // Update color based on usage
+ if (percentage >= 95) {
+ messageProgressElement.style.background = 'linear-gradient(90deg, #ef4444, #dc2626)';
+ } else if (percentage >= 80) {
+ messageProgressElement.style.background = 'linear-gradient(90deg, #f59e0b, #d97706)';
+ } else {
+ messageProgressElement.style.background = 'linear-gradient(90deg, #3b82f6, #2563eb)';
+ }
+ }
+}
+
+function updateWarnings(timePercentage, messagePercentage) {
+ const warningsElement = document.getElementById('sessionWarnings');
+ if (warningsElement) {
+ let warningHTML = '';
+
+ if (messagePercentage >= 80) {
+ warningHTML += '
⚠️ Approaching message limit!
';
+ }
+
+ if (timePercentage <= 20) {
+ warningHTML += '
⚠️ Session expires soon!
';
+ }
+
+ warningsElement.innerHTML = warningHTML;
+ }
+}
+
+function getCSRFToken() {
+ const token = document.querySelector('[name=csrfmiddlewaretoken]');
+ return token ? token.value : '';
+}
+
+// Update message count after sending a message
+function updateMessageCount() {
+ // Update session indicators to refresh all counters
+ updateSessionIndicators();
+}
+
// Handle wallet balance check
document.addEventListener('DOMContentLoaded', function() {
const userBalance = parseFloat(document.body.getAttribute('data-user-balance') || '0');
diff --git a/agents/urls.py b/agents/urls.py
index d1e055b..381d020 100644
--- a/agents/urls.py
+++ b/agents/urls.py
@@ -20,7 +20,9 @@ urlpatterns = [
path('api/chat/start/', views.start_chat_session, name='start_chat_session'),
path('api/chat/send/', views.send_chat_message, name='send_chat_message'),
path('api/chat/history/
/', views.get_chat_history, name='get_chat_history'),
+ path('api/chat/session//status/', views.get_session_status, name='get_session_status'),
path('api/chat/end/', views.end_chat_session, name='end_chat_session'),
+ path('api/chat/export//', views.export_chat, name='export_chat'),
path('api/', views.agent_list, name='agent_list'),
path('api//', views.agent_detail, name='agent_detail_api'),
diff --git a/agents/views.py b/agents/views.py
index 9800eda..2c953fb 100644
--- a/agents/views.py
+++ b/agents/views.py
@@ -16,6 +16,13 @@ import time
import uuid
import ipaddress
from urllib.parse import urlparse
+from django.http import HttpResponse
+from reportlab.pdfgen import canvas
+from reportlab.lib.pagesizes import letter
+from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
+from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer
+from reportlab.lib.units import inch
+from io import BytesIO
def validate_webhook_url(url):
"""
@@ -337,8 +344,12 @@ def agent_detail_view(request, slug):
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')
+
context = {
'agent': agent,
+ 'all_agents': all_agents,
'timestamp': int(time.time()) # For cache busting
}
@@ -402,11 +413,68 @@ def chat_agent_view(request, agent):
if chat_session:
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')
+
+ # Get previous sessions for this user and agent (excluding current active session)
+ previous_sessions_query = ChatSession.objects.filter(
+ agent=agent,
+ user=request.user
+ ).exclude(status='active').order_by('-created_at')[:5] # Last 5 non-active sessions
+
+ # Add user message count to each session
+ previous_sessions = []
+ for session in previous_sessions_query:
+ session.user_message_count = ChatMessage.objects.filter(
+ session=session,
+ message_type='user'
+ ).count()
+ previous_sessions.append(session)
+
+ # Calculate session indicators data
+ session_data = {}
+ if chat_session and messages.exists():
+ from django.utils import timezone
+ import math
+
+ # Time calculations
+ now = timezone.now()
+ time_elapsed = now - chat_session.created_at
+ time_remaining_seconds = max(0, (chat_session.expires_at - now).total_seconds())
+ time_remaining_minutes = int(time_remaining_seconds // 60)
+ time_remaining_hours = time_remaining_minutes // 60
+ time_remaining_minutes = time_remaining_minutes % 60
+
+ if time_remaining_hours > 0:
+ time_remaining_str = f"{time_remaining_hours}h {time_remaining_minutes}m"
+ else:
+ time_remaining_str = f"{time_remaining_minutes}m"
+
+ # Time percentage (how much time is left)
+ total_session_time = 30 * 60 # 30 minutes in seconds
+ time_percentage = max(0, min(100, (time_remaining_seconds / total_session_time) * 100))
+
+ # Message calculations (only count user messages)
+ user_message_count = messages.filter(message_type='user').count()
+ message_limit = agent.message_limit
+ message_percentage = min(100, (user_message_count / message_limit) * 100)
+
+ session_data = {
+ 'time_remaining': time_remaining_str,
+ 'time_percentage': int(time_percentage),
+ 'message_count': user_message_count,
+ 'message_limit': message_limit,
+ 'message_percentage': int(message_percentage),
+ }
+
context = {
'agent': agent,
'chat_session': chat_session,
'messages': messages,
- 'timestamp': int(time.time())
+ 'all_agents': all_agents,
+ 'previous_sessions': previous_sessions,
+ 'timestamp': int(time.time()),
+ **session_data # Unpack session data into context
}
return render(request, 'agents/agent_chat.html', context)
@@ -452,11 +520,28 @@ def start_chat_session(request):
user=request.user,
fee_charged=agent.price,
status='active',
- expires_at=timezone.now() + timedelta(hours=2)
+ expires_at=timezone.now() + timedelta(minutes=30)
)
- # Deduct fee from wallet (if wallet system is implemented)
- # This would integrate with the existing wallet system
+ # Deduct fee from wallet
+ try:
+ success = request.user.deduct_balance(
+ agent.price,
+ f'{agent.name} - Chat Session {session_id}',
+ agent.slug
+ )
+ if not success:
+ # Delete the created session if payment fails
+ chat_session.delete()
+ return Response({
+ 'error': 'Failed to process payment. Please check your wallet balance.'
+ }, status=status.HTTP_400_BAD_REQUEST)
+ except Exception as e:
+ # Delete the created session if payment processing fails
+ chat_session.delete()
+ return Response({
+ 'error': 'Payment processing error. Please try again.'
+ }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
# Send welcome message
welcome_message = f"Welcome to {agent.name}! I'm here to help you with 5 Whys analysis. What problem would you like to analyze?"
@@ -497,6 +582,18 @@ def send_chat_message(request):
chat_session.save()
return Response({'error': 'Chat session has expired'}, status=status.HTTP_400_BAD_REQUEST)
+ # Check message limit (only count user messages)
+ current_user_message_count = ChatMessage.objects.filter(session=chat_session, message_type='user').count()
+ if current_user_message_count >= chat_session.agent.message_limit:
+ # Auto-complete the session when message limit is reached
+ chat_session.status = 'completed'
+ chat_session.completed_at = timezone.now()
+ chat_session.save()
+
+ return Response({
+ 'error': f'Message limit reached ({chat_session.agent.message_limit} messages). Session completed. You can download your conversation or start a new session.'
+ }, status=status.HTTP_400_BAD_REQUEST)
+
# Save user message
user_message = ChatMessage.objects.create(
session=chat_session,
@@ -679,3 +776,185 @@ def end_chat_session(request):
chat_session.save()
return Response({'message': 'Chat session ended successfully'})
+
+
+@api_view(['GET'])
+@permission_classes([IsAuthenticated])
+def get_session_status(request, session_id):
+ """Get real-time session status data"""
+ chat_session = get_object_or_404(
+ ChatSession,
+ session_id=session_id,
+ user=request.user
+ )
+
+ from django.utils import timezone
+
+ # Time calculations
+ now = timezone.now()
+ time_remaining_seconds = max(0, (chat_session.expires_at - now).total_seconds())
+ time_remaining_minutes = int(time_remaining_seconds // 60)
+ time_remaining_hours = time_remaining_minutes // 60
+ time_remaining_minutes = time_remaining_minutes % 60
+
+ if time_remaining_hours > 0:
+ time_remaining_str = f"{time_remaining_hours}h {time_remaining_minutes}m"
+ else:
+ time_remaining_str = f"{time_remaining_minutes}m"
+
+ # Time percentage (how much time is left)
+ total_session_time = 30 * 60 # 30 minutes in seconds
+ time_percentage = max(0, min(100, (time_remaining_seconds / total_session_time) * 100))
+
+ # Message calculations (only count user messages)
+ message_count = ChatMessage.objects.filter(session=chat_session, message_type='user').count()
+ message_limit = chat_session.agent.message_limit
+ message_percentage = min(100, (message_count / message_limit) * 100)
+
+ return Response({
+ 'success': True,
+ 'session_id': session_id,
+ 'status': chat_session.status,
+ 'time_remaining_seconds': int(time_remaining_seconds),
+ 'time_remaining_str': time_remaining_str,
+ 'time_percentage': int(time_percentage),
+ 'message_count': message_count,
+ 'message_limit': message_limit,
+ 'message_percentage': int(message_percentage),
+ 'is_expired': chat_session.is_expired()
+ })
+
+
+@login_required
+def export_chat(request, session_id):
+ """Export chat session as PDF or TXT"""
+ format_type = request.GET.get('format', 'pdf').lower()
+
+ # Get chat session and verify ownership
+ chat_session = get_object_or_404(
+ ChatSession,
+ session_id=session_id,
+ user=request.user
+ )
+
+ # Get all messages for this session
+ messages = ChatMessage.objects.filter(session=chat_session).order_by('timestamp')
+
+ if not messages.exists():
+ return HttpResponse('No messages found in this chat session.', status=404)
+
+ if format_type == 'pdf':
+ return export_chat_pdf(chat_session, messages)
+ elif format_type == 'txt':
+ return export_chat_txt(chat_session, messages)
+ else:
+ return HttpResponse('Invalid format. Use pdf or txt.', status=400)
+
+
+def export_chat_pdf(chat_session, messages):
+ """Generate PDF export of chat session"""
+ buffer = BytesIO()
+ doc = SimpleDocTemplate(buffer, pagesize=letter)
+ styles = getSampleStyleSheet()
+ story = []
+
+ # Title
+ title_style = ParagraphStyle(
+ 'CustomTitle',
+ parent=styles['Heading1'],
+ fontSize=18,
+ spaceAfter=30,
+ alignment=1 # Center alignment
+ )
+
+ story.append(Paragraph(f"5 Whys Analysis - {chat_session.agent.name}", title_style))
+ story.append(Spacer(1, 12))
+
+ # Session info
+ info_style = styles['Normal']
+ story.append(Paragraph(f"Session ID: {chat_session.session_id}", info_style))
+ story.append(Paragraph(f"Date: {chat_session.created_at.strftime('%B %d, %Y at %I:%M %p')}", info_style))
+ story.append(Paragraph(f"Agent: {chat_session.agent.name}", info_style))
+ story.append(Paragraph(f"Total Messages: {messages.count()}", info_style))
+ story.append(Spacer(1, 20))
+
+ # Messages
+ user_style = ParagraphStyle(
+ 'UserMessage',
+ parent=styles['Normal'],
+ leftIndent=0,
+ rightIndent=50,
+ spaceBefore=12,
+ spaceAfter=6,
+ fontSize=10
+ )
+
+ agent_style = ParagraphStyle(
+ 'AgentMessage',
+ parent=styles['Normal'],
+ leftIndent=50,
+ rightIndent=0,
+ spaceBefore=12,
+ spaceAfter=6,
+ fontSize=10
+ )
+
+ for message in messages:
+ timestamp = message.timestamp.strftime('%I:%M %p')
+
+ if message.message_type == 'user':
+ story.append(Paragraph(f"You ({timestamp}):
{message.content}", user_style))
+ elif message.message_type == 'agent':
+ story.append(Paragraph(f"{chat_session.agent.name} ({timestamp}):
{message.content}", agent_style))
+ elif message.message_type == 'system':
+ story.append(Paragraph(f"System ({timestamp}): {message.content}", styles['Normal']))
+
+ # Build PDF
+ doc.build(story)
+ buffer.seek(0)
+
+ response = HttpResponse(buffer.getvalue(), content_type='application/pdf')
+ response['Content-Disposition'] = f'attachment; filename="5whys_chat_{chat_session.session_id}.pdf"'
+ return response
+
+
+def export_chat_txt(chat_session, messages):
+ """Generate TXT export of chat session"""
+ content = []
+ content.append("=" * 60)
+ content.append(f"5 Whys Analysis - {chat_session.agent.name}")
+ content.append("=" * 60)
+ content.append("")
+ content.append(f"Session ID: {chat_session.session_id}")
+ content.append(f"Date: {chat_session.created_at.strftime('%B %d, %Y at %I:%M %p')}")
+ content.append(f"Agent: {chat_session.agent.name}")
+ content.append(f"Total Messages: {messages.count()}")
+ content.append("")
+ content.append("-" * 60)
+ content.append("CONVERSATION")
+ content.append("-" * 60)
+ content.append("")
+
+ for message in messages:
+ timestamp = message.timestamp.strftime('%I:%M %p')
+
+ if message.message_type == 'user':
+ content.append(f"You ({timestamp}):")
+ content.append(message.content)
+ elif message.message_type == 'agent':
+ content.append(f"{chat_session.agent.name} ({timestamp}):")
+ content.append(message.content)
+ elif message.message_type == 'system':
+ content.append(f"System ({timestamp}): {message.content}")
+
+ content.append("") # Empty line between messages
+
+ content.append("-" * 60)
+ content.append("End of Conversation")
+ content.append("-" * 60)
+
+ text_content = "\n".join(content)
+
+ response = HttpResponse(text_content, content_type='text/plain')
+ response['Content-Disposition'] = f'attachment; filename="5whys_chat_{chat_session.session_id}.txt"'
+ return response
diff --git a/static/css/agent-base.css b/static/css/agent-base.css
index 22119ce..80ae9f0 100644
--- a/static/css/agent-base.css
+++ b/static/css/agent-base.css
@@ -89,7 +89,7 @@ html {
/* Layout - Data Analyzer Exact Copy */
.agent-container {
margin: 0 auto;
- padding: var(--spacing-lg);
+ padding: var(--spacing-sm);
max-width: 1600px;
}
@@ -99,7 +99,7 @@ html {
border-radius: var(--radius);
padding: var(--space-lg);
border: var(--agent-card-border);
- margin-bottom: var(--space-lg);
+ margin-bottom: var(--space-md);
backdrop-filter: var(--agent-backdrop-filter);
box-shadow: var(--agent-card-shadow);
}
@@ -1207,7 +1207,7 @@ html {
/* Agent Grid and Layout System */
.agent-grid {
display: flex;
- gap: var(--spacing-lg);
+ gap: var(--spacing-sm);
align-items: flex-start;
flex-wrap: wrap;
}
@@ -1216,7 +1216,7 @@ html {
display: flex;
justify-content: space-between;
align-items: center;
- margin-bottom: var(--spacing-xl);
+ margin-bottom: 20px;
}
/* Typography - Data Analyzer Exact Copy */
diff --git a/templates/components/current_session_widget.html b/templates/components/current_session_widget.html
new file mode 100644
index 0000000..f25aadf
--- /dev/null
+++ b/templates/components/current_session_widget.html
@@ -0,0 +1,119 @@
+{% if chat_session %}
+
+
+
+{% endif %}
\ No newline at end of file
diff --git a/templates/components/previous_sessions_widget.html b/templates/components/previous_sessions_widget.html
new file mode 100644
index 0000000..b65b083
--- /dev/null
+++ b/templates/components/previous_sessions_widget.html
@@ -0,0 +1,206 @@
+{% if previous_sessions %}
+
+
+
+{% endif %}
\ No newline at end of file
diff --git a/templates/components/quick_agents_panel.html b/templates/components/quick_agents_panel.html
index d92708f..9ec94c0 100644
--- a/templates/components/quick_agents_panel.html
+++ b/templates/components/quick_agents_panel.html
@@ -7,29 +7,23 @@