mirror of
https://github.com/thecyberlearn/quantum-ai-v2.git
synced 2026-08-18 09:52:59 +00:00
Add 5 Whys Analysis Agent with enhanced UX features
- Create complete 5 Whys Analysis Agent with chat-based interaction - Implement dual-mode processor (free chat vs paid report generation) - Add typing indicators with animated dots for better user feedback - Enable report generation only after 2+ questions for smart activation - Simplify report form to button-only interface using chat history - Add basic formatting for professional report presentation - Configure conditional wallet deduction (charge only for final reports) - Set up N8N webhook integration for analysis processing 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
8d1149e0af
commit
7da309e018
3
.gitignore
vendored
3
.gitignore
vendored
@ -230,4 +230,5 @@ secrets.json
|
||||
# NextJS frontend directory
|
||||
nextjs/
|
||||
netcop-ai-hub/
|
||||
temp/
|
||||
temp/
|
||||
five-whys-agent-new.html
|
||||
|
||||
@ -78,6 +78,15 @@ class Command(BaseCommand):
|
||||
'icon': '📱',
|
||||
'agent_type': 'webhook',
|
||||
},
|
||||
{
|
||||
'name': '5 Whys Analysis Agent',
|
||||
'slug': 'five-whys-analyzer',
|
||||
'description': 'Systematic root cause analysis using the proven 5 Whys methodology to identify and solve business problems effectively.',
|
||||
'category': 'analytics',
|
||||
'price': 8.0,
|
||||
'icon': '🔍',
|
||||
'agent_type': 'webhook',
|
||||
},
|
||||
]
|
||||
|
||||
created_count = 0
|
||||
|
||||
1
five_whys_analyzer/__init__.py
Normal file
1
five_whys_analyzer/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
# 5 Whys Analysis Agent Agent App
|
||||
43
five_whys_analyzer/admin.py
Normal file
43
five_whys_analyzer/admin.py
Normal file
@ -0,0 +1,43 @@
|
||||
from django.contrib import admin
|
||||
from .models import FiveWhysAnalyzerRequest, FiveWhysAnalyzerResponse
|
||||
|
||||
|
||||
@admin.register(FiveWhysAnalyzerRequest)
|
||||
class FiveWhysAnalyzerRequestAdmin(admin.ModelAdmin):
|
||||
list_display = ['id', 'user', 'session_id', 'status', 'report_generated', 'chat_active', 'created_at', 'cost']
|
||||
list_filter = ['status', 'report_generated', 'chat_active', 'analysis_depth', 'created_at']
|
||||
search_fields = ['user__email', 'user__username', 'session_id', 'problem_statement']
|
||||
readonly_fields = ['id', 'created_at', 'processed_at', 'session_id']
|
||||
ordering = ['-created_at']
|
||||
|
||||
fieldsets = (
|
||||
('Basic Info', {
|
||||
'fields': ('id', 'user', 'session_id', 'status', 'created_at', 'processed_at')
|
||||
}),
|
||||
('Chat Session', {
|
||||
'fields': ('chat_active', 'chat_messages')
|
||||
}),
|
||||
('Report Generation', {
|
||||
'fields': ('report_generated', 'problem_statement', 'context_info', 'analysis_depth', 'cost')
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
@admin.register(FiveWhysAnalyzerResponse)
|
||||
class FiveWhysAnalyzerResponseAdmin(admin.ModelAdmin):
|
||||
list_display = ['id', 'request', 'success', 'created_at']
|
||||
list_filter = ['success', 'created_at']
|
||||
readonly_fields = ['id', 'created_at']
|
||||
ordering = ['-created_at']
|
||||
|
||||
fieldsets = (
|
||||
('Basic Info', {
|
||||
'fields': ('id', 'request', 'success', 'created_at', 'processing_time', 'error_message')
|
||||
}),
|
||||
('Chat Response', {
|
||||
'fields': ('chat_response', 'chat_history')
|
||||
}),
|
||||
('Final Report', {
|
||||
'fields': ('final_report', 'report_metadata')
|
||||
}),
|
||||
)
|
||||
6
five_whys_analyzer/apps.py
Normal file
6
five_whys_analyzer/apps.py
Normal file
@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class FiveWhysAnalyzerConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'five_whys_analyzer'
|
||||
74
five_whys_analyzer/migrations/0001_initial.py
Normal file
74
five_whys_analyzer/migrations/0001_initial.py
Normal file
@ -0,0 +1,74 @@
|
||||
# Generated by Django 5.2.4 on 2025-07-12 16:08
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('agent_base', '0001_initial'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='FiveWhysAnalyzerRequest',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('status', models.CharField(choices=[('pending', 'Pending'), ('processing', 'Processing'), ('completed', 'Completed'), ('failed', 'Failed')], default='pending', max_length=20)),
|
||||
('cost', models.DecimalField(decimal_places=2, max_digits=10)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('processed_at', models.DateTimeField(blank=True, null=True)),
|
||||
('session_id', models.CharField(db_index=True, default=uuid.uuid4, max_length=100)),
|
||||
('chat_messages', models.JSONField(default=list, help_text='Store chat history as list of messages')),
|
||||
('problem_statement', models.TextField(blank=True, help_text='Main problem to analyze')),
|
||||
('context_info', models.TextField(blank=True, help_text='Additional context information')),
|
||||
('analysis_depth', models.CharField(blank=True, choices=[('standard', 'Standard 5 Whys'), ('detailed', 'Extended Analysis'), ('comprehensive', 'Comprehensive Report')], default='standard', max_length=20)),
|
||||
('report_generated', models.BooleanField(default=False, help_text='Has final report been generated and paid for')),
|
||||
('chat_active', models.BooleanField(default=True, help_text='Is chat session still active')),
|
||||
('input_text', models.TextField(blank=True)),
|
||||
('agent', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='agent_base.baseagent')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '5 Whys Analysis Agent Request',
|
||||
'verbose_name_plural': '5 Whys Analysis Agent Requests',
|
||||
'db_table': 'five_whys_analyzer_requests',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='FiveWhysAnalyzerResponse',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('success', models.BooleanField(default=False)),
|
||||
('error_message', models.TextField(blank=True)),
|
||||
('processing_time', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('chat_response', models.TextField(blank=True, help_text='Latest chat response')),
|
||||
('chat_history', models.JSONField(default=list, help_text='Full chat response history')),
|
||||
('final_report', models.TextField(blank=True, help_text='Generated 5 Whys analysis report')),
|
||||
('report_metadata', models.JSONField(default=dict, help_text='Report generation metadata')),
|
||||
('output_text', models.TextField(blank=True)),
|
||||
('raw_response', models.JSONField(blank=True, default=dict)),
|
||||
('request', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='response', to='five_whys_analyzer.fivewhysanalyzerrequest')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '5 Whys Analysis Agent Response',
|
||||
'verbose_name_plural': '5 Whys Analysis Agent Responses',
|
||||
'db_table': 'five_whys_analyzer_responses',
|
||||
},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='fivewhysanalyzerrequest',
|
||||
index=models.Index(fields=['session_id'], name='five_whys_a_session_0dd791_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='fivewhysanalyzerrequest',
|
||||
index=models.Index(fields=['user', 'chat_active'], name='five_whys_a_user_id_810315_idx'),
|
||||
),
|
||||
]
|
||||
0
five_whys_analyzer/migrations/__init__.py
Normal file
0
five_whys_analyzer/migrations/__init__.py
Normal file
71
five_whys_analyzer/models.py
Normal file
71
five_whys_analyzer/models.py
Normal file
@ -0,0 +1,71 @@
|
||||
from django.db import models
|
||||
from decimal import Decimal
|
||||
from agent_base.models import BaseAgentRequest, BaseAgentResponse
|
||||
import uuid
|
||||
|
||||
|
||||
class FiveWhysAnalyzerRequest(BaseAgentRequest):
|
||||
"""5 Whys Analysis Agent request tracking with chat support"""
|
||||
|
||||
# Chat session management
|
||||
session_id = models.CharField(max_length=100, default=uuid.uuid4, db_index=True)
|
||||
|
||||
# Chat interaction tracking
|
||||
chat_messages = models.JSONField(default=list, help_text="Store chat history as list of messages")
|
||||
|
||||
# Final report fields (only filled when report is generated)
|
||||
problem_statement = models.TextField(blank=True, help_text="Main problem to analyze")
|
||||
context_info = models.TextField(blank=True, help_text="Additional context information")
|
||||
analysis_depth = models.CharField(
|
||||
max_length=20,
|
||||
blank=True,
|
||||
choices=[
|
||||
('standard', 'Standard 5 Whys'),
|
||||
('detailed', 'Extended Analysis'),
|
||||
('comprehensive', 'Comprehensive Report')
|
||||
],
|
||||
default='standard'
|
||||
)
|
||||
|
||||
# Chat vs Report tracking
|
||||
report_generated = models.BooleanField(default=False, help_text="Has final report been generated and paid for")
|
||||
chat_active = models.BooleanField(default=True, help_text="Is chat session still active")
|
||||
|
||||
# Legacy field for compatibility
|
||||
input_text = models.TextField(blank=True)
|
||||
|
||||
class Meta:
|
||||
db_table = 'five_whys_analyzer_requests'
|
||||
verbose_name = '5 Whys Analysis Agent Request'
|
||||
verbose_name_plural = '5 Whys Analysis Agent Requests'
|
||||
indexes = [
|
||||
models.Index(fields=['session_id']),
|
||||
models.Index(fields=['user', 'chat_active']),
|
||||
]
|
||||
|
||||
|
||||
class FiveWhysAnalyzerResponse(BaseAgentResponse):
|
||||
"""5 Whys Analysis Agent response storage"""
|
||||
|
||||
request = models.OneToOneField(
|
||||
FiveWhysAnalyzerRequest,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='response'
|
||||
)
|
||||
|
||||
# Chat responses (free interactions)
|
||||
chat_response = models.TextField(blank=True, help_text="Latest chat response")
|
||||
chat_history = models.JSONField(default=list, help_text="Full chat response history")
|
||||
|
||||
# Final report (paid interaction)
|
||||
final_report = models.TextField(blank=True, help_text="Generated 5 Whys analysis report")
|
||||
report_metadata = models.JSONField(default=dict, help_text="Report generation metadata")
|
||||
|
||||
# Legacy fields for compatibility
|
||||
output_text = models.TextField(blank=True)
|
||||
raw_response = models.JSONField(default=dict, blank=True)
|
||||
|
||||
class Meta:
|
||||
db_table = 'five_whys_analyzer_responses'
|
||||
verbose_name = '5 Whys Analysis Agent Response'
|
||||
verbose_name_plural = '5 Whys Analysis Agent Responses'
|
||||
247
five_whys_analyzer/processor.py
Normal file
247
five_whys_analyzer/processor.py
Normal file
@ -0,0 +1,247 @@
|
||||
from agent_base.processors import StandardWebhookProcessor
|
||||
from django.utils import timezone
|
||||
from django.conf import settings
|
||||
from .models import FiveWhysAnalyzerRequest, FiveWhysAnalyzerResponse
|
||||
import json
|
||||
import uuid
|
||||
|
||||
|
||||
class FiveWhysAnalyzerProcessor(StandardWebhookProcessor):
|
||||
"""Dual-mode webhook processor for 5 Whys Analysis Agent - supports chat and report generation"""
|
||||
|
||||
agent_slug = 'five-whys-analyzer'
|
||||
webhook_url = 'https://m8taq6tk.rpcld.cc/webhook/5-whys-web'
|
||||
agent_id = 'five-whys-001'
|
||||
|
||||
def process_response(self, response_data, request_obj):
|
||||
"""Required implementation of abstract method - delegates to specific handlers"""
|
||||
# This method is required by the base class but we handle responses
|
||||
# differently in our dual-mode approach
|
||||
return self.process_report_response(response_data, request_obj)
|
||||
|
||||
def process_request(self, **kwargs):
|
||||
"""Handle both chat messages (free) and report generation (paid)"""
|
||||
message_type = kwargs.get('message_type', 'chat')
|
||||
|
||||
if message_type == 'chat':
|
||||
return self.handle_chat_message(**kwargs)
|
||||
elif message_type == 'generate_report':
|
||||
return self.handle_report_generation(**kwargs)
|
||||
else:
|
||||
raise ValueError(f"Unknown message type: {message_type}")
|
||||
|
||||
def handle_chat_message(self, **kwargs):
|
||||
"""Handle free chat interactions - no wallet deduction"""
|
||||
user = kwargs.get('user')
|
||||
session_id = kwargs.get('session_id', str(uuid.uuid4()))
|
||||
user_message = kwargs.get('message', '')
|
||||
|
||||
# Get the agent object
|
||||
from agent_base.models import BaseAgent
|
||||
try:
|
||||
agent = BaseAgent.objects.get(slug=self.agent_slug)
|
||||
except BaseAgent.DoesNotExist:
|
||||
raise Exception(f"Agent with slug '{self.agent_slug}' not found")
|
||||
|
||||
# Get or create request object for this session
|
||||
request_obj, created = FiveWhysAnalyzerRequest.objects.get_or_create(
|
||||
user=user,
|
||||
session_id=session_id,
|
||||
chat_active=True,
|
||||
defaults={
|
||||
'agent': agent,
|
||||
'cost': 0, # No cost for chat
|
||||
'status': 'pending'
|
||||
}
|
||||
)
|
||||
|
||||
# Add user message to chat history
|
||||
chat_messages = request_obj.chat_messages
|
||||
chat_messages.append({
|
||||
'role': 'user',
|
||||
'message': user_message,
|
||||
'timestamp': timezone.now().isoformat()
|
||||
})
|
||||
request_obj.chat_messages = chat_messages
|
||||
request_obj.save()
|
||||
|
||||
# Prepare chat payload for webhook
|
||||
chat_payload = {
|
||||
'message': {
|
||||
'text': f"Chat message: {user_message}. Provide helpful guidance about 5 Whys analysis. Do not generate the final report - just chat and help the user understand their problem."
|
||||
},
|
||||
'sessionId': session_id,
|
||||
'userId': str(user.id),
|
||||
'agentId': self.agent_id,
|
||||
'messageType': 'chat'
|
||||
}
|
||||
|
||||
# Send to webhook
|
||||
response_data = self.make_request(chat_payload)
|
||||
|
||||
# Process chat response (no wallet deduction)
|
||||
return self.process_chat_response(response_data, request_obj, user_message)
|
||||
|
||||
def handle_report_generation(self, **kwargs):
|
||||
"""Handle paid report generation - deduct wallet after success"""
|
||||
user = kwargs.get('user')
|
||||
session_id = kwargs.get('session_id')
|
||||
problem_statement = kwargs.get('problem_statement', '')
|
||||
context_info = kwargs.get('context_info', '')
|
||||
analysis_depth = kwargs.get('analysis_depth', 'standard')
|
||||
|
||||
# Get the agent object
|
||||
from agent_base.models import BaseAgent
|
||||
try:
|
||||
agent = BaseAgent.objects.get(slug=self.agent_slug)
|
||||
except BaseAgent.DoesNotExist:
|
||||
raise Exception(f"Agent with slug '{self.agent_slug}' not found")
|
||||
|
||||
# Get existing session or create new one
|
||||
try:
|
||||
request_obj = FiveWhysAnalyzerRequest.objects.get(
|
||||
user=user,
|
||||
session_id=session_id,
|
||||
chat_active=True
|
||||
)
|
||||
except FiveWhysAnalyzerRequest.DoesNotExist:
|
||||
# Create new request for report generation
|
||||
request_obj = FiveWhysAnalyzerRequest.objects.create(
|
||||
user=user,
|
||||
session_id=session_id,
|
||||
agent=agent,
|
||||
cost=8.0, # Cost for report generation
|
||||
status='pending'
|
||||
)
|
||||
|
||||
# Update request with report details
|
||||
request_obj.problem_statement = problem_statement
|
||||
request_obj.context_info = context_info
|
||||
request_obj.analysis_depth = analysis_depth
|
||||
request_obj.cost = 8.0 # Ensure cost is set for report
|
||||
request_obj.save()
|
||||
|
||||
# Prepare report generation payload
|
||||
report_payload = {
|
||||
'message': {
|
||||
'text': f"Generate comprehensive 5 Whys analysis report.\nProblem: {problem_statement}\nContext: {context_info}\nDepth: {analysis_depth}\nChat History: {json.dumps(request_obj.chat_messages[-10:])}"
|
||||
},
|
||||
'sessionId': session_id,
|
||||
'userId': str(user.id),
|
||||
'agentId': self.agent_id,
|
||||
'messageType': 'report',
|
||||
'analysisDepth': analysis_depth
|
||||
}
|
||||
|
||||
# Send to webhook
|
||||
response_data = self.make_request(report_payload)
|
||||
|
||||
# Process report response (with wallet deduction)
|
||||
return self.process_report_response(response_data, request_obj)
|
||||
|
||||
def process_chat_response(self, response_data, request_obj, user_message):
|
||||
"""Process chat response - no wallet deduction"""
|
||||
try:
|
||||
# Extract chat response
|
||||
chat_response = response_data.get('output', response_data.get('message', 'I\'m here to help with 5 Whys analysis. What would you like to know?'))
|
||||
|
||||
# Add assistant response to chat history
|
||||
chat_messages = request_obj.chat_messages
|
||||
chat_messages.append({
|
||||
'role': 'assistant',
|
||||
'message': chat_response,
|
||||
'timestamp': timezone.now().isoformat()
|
||||
})
|
||||
request_obj.chat_messages = chat_messages
|
||||
request_obj.status = 'completed' # Chat message completed
|
||||
request_obj.save()
|
||||
|
||||
# Get or create response object
|
||||
response_obj, created = FiveWhysAnalyzerResponse.objects.get_or_create(
|
||||
request=request_obj,
|
||||
defaults={
|
||||
'success': True,
|
||||
'processing_time': response_data.get('processing_time', 0)
|
||||
}
|
||||
)
|
||||
|
||||
# Update response with chat data
|
||||
response_obj.chat_response = chat_response
|
||||
chat_history = response_obj.chat_history
|
||||
chat_history.append({
|
||||
'user_message': user_message,
|
||||
'assistant_response': chat_response,
|
||||
'timestamp': timezone.now().isoformat()
|
||||
})
|
||||
response_obj.chat_history = chat_history
|
||||
response_obj.save()
|
||||
|
||||
print(f"{self.agent_slug}: Chat message processed - no wallet deduction")
|
||||
return response_obj
|
||||
|
||||
except Exception as e:
|
||||
request_obj.status = 'failed'
|
||||
request_obj.save()
|
||||
raise Exception(f"Failed to process chat response: {e}")
|
||||
|
||||
def process_report_response(self, response_data, request_obj):
|
||||
"""Process report generation response - deduct wallet after success"""
|
||||
try:
|
||||
request_obj.status = 'processing'
|
||||
request_obj.save()
|
||||
|
||||
# Extract report data
|
||||
final_report = response_data.get('output', response_data.get('report', ''))
|
||||
success = bool(final_report) and response_data.get('success', True)
|
||||
|
||||
# Get or create response object
|
||||
response_obj, created = FiveWhysAnalyzerResponse.objects.get_or_create(
|
||||
request=request_obj,
|
||||
defaults={
|
||||
'success': success,
|
||||
'processing_time': response_data.get('processing_time', 0)
|
||||
}
|
||||
)
|
||||
|
||||
if success:
|
||||
# Update with final report
|
||||
response_obj.final_report = final_report
|
||||
response_obj.report_metadata = {
|
||||
'analysis_depth': request_obj.analysis_depth,
|
||||
'generated_at': timezone.now().isoformat(),
|
||||
'problem_statement': request_obj.problem_statement,
|
||||
'context_info': request_obj.context_info
|
||||
}
|
||||
response_obj.save()
|
||||
|
||||
# Mark request as report generated
|
||||
request_obj.report_generated = True
|
||||
request_obj.chat_active = False # End chat session
|
||||
|
||||
# ONLY deduct wallet balance after successful report generation
|
||||
request_obj.user.deduct_balance(
|
||||
request_obj.cost,
|
||||
f"5 Whys Analysis Agent - Final Report ({request_obj.analysis_depth})",
|
||||
'five-whys-analyzer'
|
||||
)
|
||||
print(f"{self.agent_slug}: Wallet deducted {request_obj.cost} AED for successful report generation")
|
||||
|
||||
request_obj.status = 'completed'
|
||||
else:
|
||||
request_obj.status = 'failed'
|
||||
response_obj.error_message = "Failed to generate report"
|
||||
response_obj.save()
|
||||
|
||||
request_obj.processed_at = timezone.now()
|
||||
request_obj.save()
|
||||
|
||||
return response_obj
|
||||
|
||||
except Exception as e:
|
||||
request_obj.status = 'failed'
|
||||
request_obj.save()
|
||||
raise Exception(f"Failed to process report response: {e}")
|
||||
|
||||
def prepare_message_text(self, **kwargs):
|
||||
"""Legacy method for compatibility"""
|
||||
return kwargs.get('message', 'Process 5 Whys analysis')
|
||||
671
five_whys_analyzer/templates/five_whys_analyzer/detail.html
Normal file
671
five_whys_analyzer/templates/five_whys_analyzer/detail.html
Normal file
@ -0,0 +1,671 @@
|
||||
{% extends "base.html" %}
|
||||
{% csrf_token %}
|
||||
|
||||
{% block title %}5 Whys Analysis Agent - NetCop AI Hub{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container" style="max-width: 1280px; margin: 0 auto; padding: clamp(20px, 5vw, 40px) clamp(16px, 4vw, 24px);">
|
||||
<!-- Main Content Grid -->
|
||||
<div class="main-grid" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(min(350px, 100%), 1fr)); gap: clamp(16px, 4vw, 24px); align-items: start;">
|
||||
<!-- Chat Interface -->
|
||||
<div>
|
||||
<!-- Agent Header -->
|
||||
<div class="card" style="background: rgba(255, 255, 255, 0.9); border-radius: clamp(12px, 3vw, 16px); padding: clamp(16px, 4vw, 24px); border: 1px solid rgba(255, 255, 255, 0.3); backdrop-filter: blur(20px); box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1); margin-bottom: clamp(16px, 4vw, 24px);">
|
||||
<h1 style="font-size: clamp(20px, 5vw, 24px); font-weight: 700; color: #1f2937; margin: 0 0 8px 0; display: flex; align-items: center; gap: 12px;">
|
||||
🔍 {{ agent.name }}
|
||||
</h1>
|
||||
<p style="font-size: clamp(14px, 3.5vw, 16px); color: #6b7280; margin: 0;">
|
||||
{{ agent.description }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Chat Messages Container -->
|
||||
<div class="card" id="chatContainer" style="background: rgba(255, 255, 255, 0.9); border-radius: clamp(12px, 3vw, 16px); padding: clamp(16px, 4vw, 24px); border: 1px solid rgba(255, 255, 255, 0.3); backdrop-filter: blur(20px); box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1); margin-bottom: clamp(16px, 4vw, 24px); min-height: 400px; max-height: 600px; overflow-y: auto;">
|
||||
<h3 style="font-size: clamp(16px, 4vw, 18px); font-weight: 600; color: #1f2937; margin: 0 0 16px 0;">💬 Chat with 5 Whys Analyst</h3>
|
||||
|
||||
<!-- Welcome Message -->
|
||||
<div class="message assistant-message" style="margin-bottom: 16px; padding: 12px 16px; background: #f8fafc; border-radius: 12px; border-left: 4px solid #6366f1;">
|
||||
<div style="font-weight: 600; color: #4338ca; margin-bottom: 4px;">5 Whys Analyst</div>
|
||||
<div style="color: #374151; line-height: 1.5;">
|
||||
Hello! I'm here to help you with root cause analysis using the 5 Whys methodology.
|
||||
|
||||
You can ask me questions, describe your problem, and I'll guide you through the analysis process. When you're ready, I can generate a comprehensive report for 8.00 AED.
|
||||
|
||||
How can I help you today?
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Chat messages will be dynamically added here -->
|
||||
<div id="chatMessages"></div>
|
||||
</div>
|
||||
|
||||
<!-- Chat Input -->
|
||||
<div class="card" style="background: rgba(255, 255, 255, 0.9); border-radius: clamp(12px, 3vw, 16px); padding: clamp(16px, 4vw, 24px); border: 1px solid rgba(255, 255, 255, 0.3); backdrop-filter: blur(20px); box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1); margin-bottom: clamp(16px, 4vw, 24px);">
|
||||
<div style="display: flex; gap: 12px; align-items: flex-end;">
|
||||
<textarea
|
||||
id="chatInput"
|
||||
placeholder="Ask me about your problem or describe what you'd like to analyze..."
|
||||
style="flex: 1; padding: 12px 16px; border: 2px solid #e5e7eb; border-radius: 12px; font-size: 14px; resize: vertical; min-height: 48px; max-height: 120px; font-family: inherit;"
|
||||
rows="2"
|
||||
></textarea>
|
||||
<button
|
||||
id="sendChatBtn"
|
||||
onclick="sendChatMessage()"
|
||||
style="padding: 12px 20px; background: linear-gradient(135deg, #6366f1 0%, #4338ca 100%); color: white; border: none; border-radius: 12px; font-weight: 600; cursor: pointer; min-height: 48px; transition: transform 0.1s ease;"
|
||||
>
|
||||
Send
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Report Generation Form -->
|
||||
<div class="card" id="reportForm" style="background: rgba(255, 255, 255, 0.9); border-radius: clamp(12px, 3vw, 16px); padding: clamp(16px, 4vw, 24px); border: 1px solid rgba(255, 255, 255, 0.3); backdrop-filter: blur(20px); box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1); margin-bottom: clamp(16px, 4vw, 24px);">
|
||||
<h3 style="font-size: clamp(16px, 4vw, 18px); font-weight: 600; color: #1f2937; margin: 0 0 16px 0;">📋 Generate Final Report</h3>
|
||||
|
||||
<div id="reportNotReady" style="padding: 16px; background: #f3f4f6; border-radius: 12px; text-align: center; color: #6b7280; font-size: 14px; margin-bottom: 16px;">
|
||||
💬 Ask 2-3 questions about your problem first, then I'll generate a comprehensive report
|
||||
</div>
|
||||
|
||||
<div id="reportReady" style="display: none; padding: 16px; background: #ecfdf5; border-radius: 12px; text-align: center; color: #059669; font-size: 14px; margin-bottom: 16px;">
|
||||
✅ Ready! I can now generate a detailed 5 Whys analysis report based on our conversation
|
||||
</div>
|
||||
|
||||
<button
|
||||
id="generateReportBtn"
|
||||
onclick="generateReport()"
|
||||
disabled
|
||||
style="width: 100%; padding: 16px 20px; background: #9ca3af; color: white; border: none; border-radius: 12px; font-weight: 600; cursor: not-allowed; font-size: 16px; transition: all 0.3s ease;"
|
||||
>
|
||||
🔍 Generate Report ({{ agent.price }} AED)
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Generated Report Display -->
|
||||
<div id="reportResults" class="card" style="background: rgba(255, 255, 255, 0.9); border-radius: 16px; padding: 24px; border: 1px solid rgba(255, 255, 255, 0.3); backdrop-filter: blur(20px); box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1); margin-top: 24px; display: none;">
|
||||
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 20px;">
|
||||
<div style="font-size: 24px;">✅</div>
|
||||
<h3 style="font-size: 20px; font-weight: 600; color: #1f2937; margin: 0;">5 Whys Analysis Report</h3>
|
||||
<div style="background: #6366f1; color: white; padding: 6px 12px; border-radius: 6px; font-size: 14px; font-weight: 600; margin-left: auto;">
|
||||
✅ Complete
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="reportContent" style="background: white; border: 1px solid #e2e8f0; border-radius: 12px; padding: 32px; margin-bottom: 20px; line-height: 1.7; color: #374151; font-size: 15px; box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.05);">
|
||||
<!-- Report content will be displayed here -->
|
||||
</div>
|
||||
|
||||
<div style="display: flex; gap: 12px; flex-wrap: wrap;">
|
||||
<button onclick="copyReport()" style="padding: 12px 20px; background: linear-gradient(135deg, #6366f1 0%, #4338ca 100%); color: white; border: none; border-radius: 12px; font-weight: 600; cursor: pointer; transition: transform 0.1s ease;">
|
||||
📋 Copy Report
|
||||
</button>
|
||||
<button onclick="downloadReport()" style="padding: 12px 20px; background: white; color: #374151; border: 2px solid #e5e7eb; border-radius: 12px; font-weight: 600; cursor: pointer; transition: transform 0.1s ease;">
|
||||
💾 Download Report
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Wallet Sidebar -->
|
||||
<div style="position: sticky; top: 20px;">
|
||||
<div class="card" style="background: rgba(255, 255, 255, 0.9); border-radius: clamp(12px, 3vw, 16px); padding: clamp(16px, 4vw, 24px); border: 1px solid rgba(255, 255, 255, 0.3); backdrop-filter: blur(20px); box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1); margin-bottom: clamp(16px, 4vw, 24px);">
|
||||
<h3 style="font-size: clamp(16px, 4vw, 18px); font-weight: 600; color: #1f2937; margin: 0 0 16px 0;">💳 Your Wallet</h3>
|
||||
|
||||
<div style="font-size: clamp(24px, 6vw, 28px); font-weight: 700; color: #1f2937; margin-bottom: 8px;" data-wallet-balance>
|
||||
{{ user.wallet_balance|floatformat:2 }} AED
|
||||
</div>
|
||||
<div style="font-size: clamp(14px, 3.5vw, 16px); color: #6b7280; margin-bottom: 20px;">
|
||||
Available Balance
|
||||
</div>
|
||||
|
||||
<a href="{% url 'core:wallet_topup' %}" style="display: block; width: 100%; padding: 16px 20px; background: linear-gradient(135deg, #10b981 0%, #059669 100%); color: white; border: none; border-radius: 12px; font-weight: 600; text-decoration: none; text-align: center; margin-bottom: 12px;">
|
||||
💳 Top Up Wallet
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div style="padding: 16px; background: rgba(99, 102, 241, 0.1); border-radius: 12px; border: 1px solid rgba(99, 102, 241, 0.2);">
|
||||
<h4 style="margin: 0 0 8px 0; font-size: 14px; font-weight: 600; color: #4338ca;">💡 How it works</h4>
|
||||
<ul style="margin: 0; font-size: 12px; color: #374151; line-height: 1.4; list-style: none; padding-left: 0;">
|
||||
<li style="margin: 4px 0; padding-left: 16px; position: relative;">
|
||||
<span style="position: absolute; left: 0; color: #6366f1;">•</span>
|
||||
Chat freely to explore your problem
|
||||
</li>
|
||||
<li style="margin: 4px 0; padding-left: 16px; position: relative;">
|
||||
<span style="position: absolute; left: 0; color: #6366f1;">•</span>
|
||||
Get guidance and ask questions
|
||||
</li>
|
||||
<li style="margin: 4px 0; padding-left: 16px; position: relative;">
|
||||
<span style="position: absolute; left: 0; color: #6366f1;">•</span>
|
||||
Generate final report when ready
|
||||
</li>
|
||||
<li style="margin: 4px 0; padding-left: 16px; position: relative;">
|
||||
<span style="position: absolute; left: 0; color: #6366f1;">•</span>
|
||||
Pay only for the final report
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
background: linear-gradient(135deg, #f6f8ff 0%, #e8f0fe 50%, #f0f7ff 100%);
|
||||
min-height: 100vh;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-1px);
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
background: #9ca3af !important;
|
||||
cursor: not-allowed !important;
|
||||
transform: none !important;
|
||||
}
|
||||
|
||||
.message {
|
||||
margin-bottom: 16px;
|
||||
animation: fadeIn 0.3s ease;
|
||||
}
|
||||
|
||||
.user-message {
|
||||
margin-left: 20%;
|
||||
padding: 12px 16px;
|
||||
background: #6366f1;
|
||||
color: white;
|
||||
border-radius: 16px 16px 4px 16px;
|
||||
}
|
||||
|
||||
.assistant-message {
|
||||
margin-right: 20%;
|
||||
padding: 16px 20px;
|
||||
background: #f8fafc;
|
||||
border-radius: 16px 16px 16px 4px;
|
||||
border-left: 4px solid #6366f1;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.assistant-message .message-content {
|
||||
color: #374151;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.assistant-message .message-content h3 {
|
||||
color: #1f2937;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin: 16px 0 8px 0;
|
||||
}
|
||||
|
||||
.assistant-message .message-content h3:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.assistant-message .message-content ul {
|
||||
margin: 8px 0;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.assistant-message .message-content li {
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.assistant-message .message-content p {
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.assistant-message .message-content p:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.assistant-message .message-content p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.assistant-message .message-content strong {
|
||||
color: #1f2937;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.typing-dots {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.typing-dots span {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background: #6366f1;
|
||||
animation: typingDots 1.4s infinite ease-in-out;
|
||||
}
|
||||
|
||||
.typing-dots span:nth-child(1) {
|
||||
animation-delay: 0s;
|
||||
}
|
||||
|
||||
.typing-dots span:nth-child(2) {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
.typing-dots span:nth-child(3) {
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
|
||||
@keyframes typingDots {
|
||||
0%, 80%, 100% {
|
||||
transform: scale(0);
|
||||
opacity: 0.5;
|
||||
}
|
||||
40% {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.main-grid {
|
||||
grid-template-columns: 1fr !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
let currentSessionId = null;
|
||||
let isProcessing = false;
|
||||
let messageCount = 0;
|
||||
|
||||
// Initialize session
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Generate new session ID
|
||||
currentSessionId = generateSessionId();
|
||||
|
||||
// Add Enter key support for chat input
|
||||
document.getElementById('chatInput').addEventListener('keypress', function(e) {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
sendChatMessage();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function generateSessionId() {
|
||||
return 'session_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
|
||||
}
|
||||
|
||||
function sendChatMessage() {
|
||||
if (isProcessing) return;
|
||||
|
||||
const input = document.getElementById('chatInput');
|
||||
const message = input.value.trim();
|
||||
|
||||
if (!message) {
|
||||
showToast('Please enter a message', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
isProcessing = true;
|
||||
document.getElementById('sendChatBtn').disabled = true;
|
||||
document.getElementById('sendChatBtn').textContent = 'Sending...';
|
||||
|
||||
// Add user message to chat
|
||||
addMessageToChat(message, 'user');
|
||||
input.value = '';
|
||||
messageCount++;
|
||||
|
||||
// Show typing indicator
|
||||
showTypingIndicator();
|
||||
|
||||
// Send chat message to backend
|
||||
fetch("{% url 'five_whys_analyzer:chat' %}", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
session_id: currentSessionId,
|
||||
message: message
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
// Hide typing indicator
|
||||
hideTypingIndicator();
|
||||
|
||||
if (data.success) {
|
||||
// Add assistant response to chat
|
||||
addMessageToChat(data.response, 'assistant');
|
||||
currentSessionId = data.session_id;
|
||||
|
||||
// Check if report button should be enabled
|
||||
checkReportReadiness();
|
||||
} else {
|
||||
showToast(data.error || 'Failed to send message', 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
// Hide typing indicator on error
|
||||
hideTypingIndicator();
|
||||
console.error('Error:', error);
|
||||
showToast('Network error occurred', 'error');
|
||||
})
|
||||
.finally(() => {
|
||||
isProcessing = false;
|
||||
document.getElementById('sendChatBtn').disabled = false;
|
||||
document.getElementById('sendChatBtn').textContent = 'Send';
|
||||
});
|
||||
}
|
||||
|
||||
function addMessageToChat(message, role) {
|
||||
const messagesContainer = document.getElementById('chatMessages');
|
||||
const messageDiv = document.createElement('div');
|
||||
|
||||
if (role === 'user') {
|
||||
messageDiv.className = 'message user-message';
|
||||
messageDiv.innerHTML = `
|
||||
<div style="font-weight: 600; margin-bottom: 4px;">You</div>
|
||||
<div style="line-height: 1.5;">${escapeHtml(message)}</div>
|
||||
`;
|
||||
} else {
|
||||
messageDiv.className = 'message assistant-message';
|
||||
const formattedMessage = formatAssistantMessage(message);
|
||||
messageDiv.innerHTML = `
|
||||
<div style="font-weight: 600; color: #4338ca; margin-bottom: 8px;">🔍 5 Whys Analyst</div>
|
||||
<div class="message-content">${formattedMessage}</div>
|
||||
`;
|
||||
}
|
||||
|
||||
messagesContainer.appendChild(messageDiv);
|
||||
|
||||
// Scroll to bottom
|
||||
const chatContainer = document.getElementById('chatContainer');
|
||||
chatContainer.scrollTop = chatContainer.scrollHeight;
|
||||
}
|
||||
|
||||
function formatAssistantMessage(message) {
|
||||
// Trim and clean up the message
|
||||
let cleaned = message.trim();
|
||||
|
||||
// Remove excessive spacing and normalize line breaks
|
||||
cleaned = cleaned.replace(/\n\s*\n\s*\n/g, '\n\n'); // Max 2 line breaks
|
||||
|
||||
// Escape HTML first
|
||||
let formatted = escapeHtml(cleaned);
|
||||
|
||||
// Convert markdown-style formatting to HTML
|
||||
// Convert ### headers to h3
|
||||
formatted = formatted.replace(/### (.*?)(?=\n|$)/g, '<h3>$1</h3>');
|
||||
|
||||
// Convert ** bold ** to <strong>
|
||||
formatted = formatted.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
|
||||
|
||||
// Convert - bullet points to proper lists
|
||||
const lines = formatted.split('\n');
|
||||
let inList = false;
|
||||
let result = [];
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i].trim();
|
||||
|
||||
if (line.startsWith('- ')) {
|
||||
if (!inList) {
|
||||
result.push('<ul>');
|
||||
inList = true;
|
||||
}
|
||||
result.push(`<li>${line.substring(2)}</li>`);
|
||||
} else {
|
||||
if (inList) {
|
||||
result.push('</ul>');
|
||||
inList = false;
|
||||
}
|
||||
if (line) {
|
||||
// Split long paragraphs for better readability
|
||||
if (line.length > 200) {
|
||||
const sentences = line.split('. ');
|
||||
let currentParagraph = '';
|
||||
for (const sentence of sentences) {
|
||||
if (currentParagraph.length + sentence.length > 200 && currentParagraph) {
|
||||
result.push(`<p>${currentParagraph.trim()}.</p>`);
|
||||
currentParagraph = sentence;
|
||||
} else {
|
||||
currentParagraph += (currentParagraph ? '. ' : '') + sentence;
|
||||
}
|
||||
}
|
||||
if (currentParagraph) {
|
||||
result.push(`<p>${currentParagraph}</p>`);
|
||||
}
|
||||
} else {
|
||||
result.push(`<p>${line}</p>`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (inList) {
|
||||
result.push('</ul>');
|
||||
}
|
||||
|
||||
return result.join('');
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
function showTypingIndicator() {
|
||||
const messagesContainer = document.getElementById('chatMessages');
|
||||
const typingDiv = document.createElement('div');
|
||||
typingDiv.id = 'typingIndicator';
|
||||
typingDiv.className = 'message assistant-message';
|
||||
typingDiv.innerHTML = `
|
||||
<div style="font-weight: 600; color: #4338ca; margin-bottom: 8px;">🔍 5 Whys Analyst</div>
|
||||
<div class="message-content">
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<div class="typing-dots">
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</div>
|
||||
<span style="color: #6b7280; font-style: italic;">Analyzing your problem...</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
messagesContainer.appendChild(typingDiv);
|
||||
|
||||
// Scroll to bottom
|
||||
const chatContainer = document.getElementById('chatContainer');
|
||||
chatContainer.scrollTop = chatContainer.scrollHeight;
|
||||
}
|
||||
|
||||
function hideTypingIndicator() {
|
||||
const typingIndicator = document.getElementById('typingIndicator');
|
||||
if (typingIndicator) {
|
||||
typingIndicator.remove();
|
||||
}
|
||||
}
|
||||
|
||||
function checkReportReadiness() {
|
||||
if (messageCount >= 2) {
|
||||
// Enable report generation
|
||||
document.getElementById('reportNotReady').style.display = 'none';
|
||||
document.getElementById('reportReady').style.display = 'block';
|
||||
|
||||
const btn = document.getElementById('generateReportBtn');
|
||||
btn.disabled = false;
|
||||
btn.style.background = 'linear-gradient(135deg, #10b981 0%, #059669 100%)';
|
||||
btn.style.cursor = 'pointer';
|
||||
}
|
||||
}
|
||||
|
||||
function generateReport() {
|
||||
if (isProcessing) return;
|
||||
|
||||
if (!currentSessionId) {
|
||||
showToast('Please start a chat session first', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (messageCount < 2) {
|
||||
showToast('Please ask at least 2 questions before generating a report', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
isProcessing = true;
|
||||
const btn = document.getElementById('generateReportBtn');
|
||||
btn.disabled = true;
|
||||
btn.textContent = '🔍 Generating Report...';
|
||||
|
||||
// Extract problem statement from first user message in chat
|
||||
const chatMessages = document.querySelectorAll('.user-message');
|
||||
let problemStatement = 'Problem analysis based on chat conversation';
|
||||
if (chatMessages.length > 0) {
|
||||
const firstMessage = chatMessages[0].querySelector('div:last-child');
|
||||
if (firstMessage) {
|
||||
problemStatement = firstMessage.textContent.trim();
|
||||
}
|
||||
}
|
||||
|
||||
// Send report generation request using chat history
|
||||
fetch("{% url 'five_whys_analyzer:report' %}", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
session_id: currentSessionId,
|
||||
problem_statement: problemStatement,
|
||||
context_info: 'Generated from chat conversation',
|
||||
analysis_depth: 'comprehensive'
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// Display the generated report
|
||||
displayReport(data.report);
|
||||
|
||||
// Update wallet balance
|
||||
updateWalletBalance(data.wallet_balance);
|
||||
|
||||
showToast('✅ Report generated and payment processed!', 'success');
|
||||
} else {
|
||||
showToast(data.error || 'Failed to generate report', 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
showToast('Network error occurred', 'error');
|
||||
})
|
||||
.finally(() => {
|
||||
isProcessing = false;
|
||||
btn.disabled = false;
|
||||
btn.textContent = '🔍 Generate Report ({{ agent.price }} AED)';
|
||||
});
|
||||
}
|
||||
|
||||
function displayReport(reportContent) {
|
||||
const formattedReport = formatReportContent(reportContent);
|
||||
document.getElementById('reportContent').innerHTML = formattedReport;
|
||||
document.getElementById('reportResults').style.display = 'block';
|
||||
|
||||
// Scroll to report
|
||||
document.getElementById('reportResults').scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
|
||||
function formatReportContent(content) {
|
||||
// Basic formatting for better readability
|
||||
let formatted = content;
|
||||
|
||||
// Escape HTML first
|
||||
const div = document.createElement('div');
|
||||
div.textContent = formatted;
|
||||
formatted = div.innerHTML;
|
||||
|
||||
// Format main headers
|
||||
formatted = formatted.replace(/^# (.*?)$/gm, '<h1 style="font-size: 24px; font-weight: bold; margin: 20px 0 16px 0; color: #1f2937; border-bottom: 2px solid #6366f1; padding-bottom: 8px;">$1</h1>');
|
||||
|
||||
// Format section headers
|
||||
formatted = formatted.replace(/^## (.*?)$/gm, '<h2 style="font-size: 18px; font-weight: 600; margin: 24px 0 12px 0; color: #374151;">$1</h2>');
|
||||
|
||||
// Format bold text
|
||||
formatted = formatted.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
|
||||
|
||||
// Format bullet points
|
||||
formatted = formatted.replace(/^- (.*?)$/gm, '<div style="margin: 6px 0; padding-left: 16px;">• $1</div>');
|
||||
|
||||
// Format numbered lists
|
||||
formatted = formatted.replace(/^\d+\.\s+(.*?)$/gm, '<div style="margin: 6px 0;">$&</div>');
|
||||
|
||||
// Add line breaks for paragraphs
|
||||
formatted = formatted.replace(/\n\n/g, '<br><br>');
|
||||
formatted = formatted.replace(/\n/g, '<br>');
|
||||
|
||||
return formatted;
|
||||
}
|
||||
|
||||
function updateWalletBalance(newBalance) {
|
||||
document.querySelectorAll('[data-wallet-balance]').forEach(element => {
|
||||
element.textContent = `${newBalance.toFixed(2)} AED`;
|
||||
});
|
||||
}
|
||||
|
||||
function copyReport() {
|
||||
const reportElement = document.getElementById('reportContent');
|
||||
const reportText = reportElement.innerText || reportElement.textContent;
|
||||
navigator.clipboard.writeText(reportText).then(() => {
|
||||
showToast('📋 Report copied to clipboard!', 'success');
|
||||
}).catch(() => {
|
||||
showToast('Failed to copy report', 'error');
|
||||
});
|
||||
}
|
||||
|
||||
function downloadReport() {
|
||||
const reportElement = document.getElementById('reportContent');
|
||||
const reportText = reportElement.innerText || reportElement.textContent;
|
||||
const blob = new Blob([reportText], { type: 'text/plain' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `five-whys-analysis-${Date.now()}.txt`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
showToast('💾 Report downloaded!', 'success');
|
||||
}
|
||||
|
||||
function showToast(message, type = 'info') {
|
||||
const toast = document.createElement('div');
|
||||
toast.style.cssText = `
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
padding: 12px 20px;
|
||||
border-radius: 8px;
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
z-index: 1000;
|
||||
${type === 'success' ? 'background: #10b981;' : 'background: #ef4444;'}
|
||||
`;
|
||||
toast.textContent = message;
|
||||
document.body.appendChild(toast);
|
||||
|
||||
setTimeout(() => {
|
||||
toast.remove();
|
||||
}, 3000);
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
13
five_whys_analyzer/urls.py
Normal file
13
five_whys_analyzer/urls.py
Normal file
@ -0,0 +1,13 @@
|
||||
from django.urls import path
|
||||
from . import views
|
||||
|
||||
app_name = 'five_whys_analyzer'
|
||||
|
||||
urlpatterns = [
|
||||
path('', views.five_whys_analyzer_detail, name='detail'),
|
||||
path('chat/', views.FiveWhysAnalyzerChatView.as_view(), name='chat'),
|
||||
path('report/', views.FiveWhysAnalyzerReportView.as_view(), name='report'),
|
||||
path('session/<str:session_id>/', views.five_whys_analyzer_session, name='session'),
|
||||
# Legacy compatibility
|
||||
path('process/', views.FiveWhysAnalyzerProcessView.as_view(), name='process'),
|
||||
]
|
||||
186
five_whys_analyzer/views.py
Normal file
186
five_whys_analyzer/views.py
Normal file
@ -0,0 +1,186 @@
|
||||
from django.shortcuts import render, redirect
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib import messages
|
||||
from django.http import JsonResponse
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views import View
|
||||
from agent_base.models import BaseAgent
|
||||
from .models import FiveWhysAnalyzerRequest, FiveWhysAnalyzerResponse
|
||||
from .processor import FiveWhysAnalyzerProcessor
|
||||
import json
|
||||
import uuid
|
||||
|
||||
|
||||
@login_required
|
||||
def five_whys_analyzer_detail(request):
|
||||
"""Detail page for 5 Whys Analysis Agent with chat interface"""
|
||||
try:
|
||||
agent = BaseAgent.objects.get(slug='five-whys-analyzer')
|
||||
except BaseAgent.DoesNotExist:
|
||||
messages.error(request, '5 Whys Analysis Agent agent not found.')
|
||||
return redirect('core:homepage')
|
||||
|
||||
# Get user's active chat sessions
|
||||
active_sessions = FiveWhysAnalyzerRequest.objects.filter(
|
||||
user=request.user,
|
||||
chat_active=True
|
||||
).order_by('-created_at')[:5]
|
||||
|
||||
# Get user's completed reports
|
||||
completed_reports = FiveWhysAnalyzerRequest.objects.filter(
|
||||
user=request.user,
|
||||
report_generated=True
|
||||
).order_by('-created_at')[:10]
|
||||
|
||||
context = {
|
||||
'agent': agent,
|
||||
'active_sessions': active_sessions,
|
||||
'completed_reports': completed_reports
|
||||
}
|
||||
return render(request, 'five_whys_analyzer/detail.html', context)
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name='dispatch')
|
||||
class FiveWhysAnalyzerChatView(View):
|
||||
"""Handle chat messages - free interactions"""
|
||||
|
||||
def post(self, request):
|
||||
if not request.user.is_authenticated:
|
||||
return JsonResponse({'error': 'Authentication required'}, status=401)
|
||||
|
||||
try:
|
||||
# Parse request data
|
||||
data = json.loads(request.body)
|
||||
|
||||
# Get session ID or create new one
|
||||
session_id = data.get('session_id', str(uuid.uuid4()))
|
||||
user_message = data.get('message', '').strip()
|
||||
|
||||
if not user_message:
|
||||
return JsonResponse({'error': 'Message cannot be empty'}, status=400)
|
||||
|
||||
# Process chat message (no wallet deduction)
|
||||
processor = FiveWhysAnalyzerProcessor()
|
||||
result = processor.handle_chat_message(
|
||||
user=request.user,
|
||||
session_id=session_id,
|
||||
message=user_message
|
||||
)
|
||||
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'session_id': session_id,
|
||||
'response': result.chat_response,
|
||||
'message_type': 'chat',
|
||||
'wallet_balance': float(request.user.wallet_balance) # No change expected
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return JsonResponse({'error': str(e)}, status=500)
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name='dispatch')
|
||||
class FiveWhysAnalyzerReportView(View):
|
||||
"""Generate final report - paid interaction"""
|
||||
|
||||
def post(self, request):
|
||||
if not request.user.is_authenticated:
|
||||
return JsonResponse({'error': 'Authentication required'}, status=401)
|
||||
|
||||
try:
|
||||
# Parse request data
|
||||
data = json.loads(request.body)
|
||||
|
||||
# Get report parameters
|
||||
session_id = data.get('session_id')
|
||||
problem_statement = data.get('problem_statement', '').strip()
|
||||
context_info = data.get('context_info', '').strip()
|
||||
analysis_depth = data.get('analysis_depth', 'standard')
|
||||
|
||||
if not session_id:
|
||||
return JsonResponse({'error': 'Session ID required'}, status=400)
|
||||
|
||||
if not problem_statement:
|
||||
return JsonResponse({'error': 'Problem statement required'}, status=400)
|
||||
|
||||
# Get agent for price checking
|
||||
agent = BaseAgent.objects.get(slug='five-whys-analyzer')
|
||||
|
||||
# Check wallet balance
|
||||
if not request.user.has_sufficient_balance(agent.price):
|
||||
return JsonResponse({'error': 'Insufficient wallet balance'}, status=400)
|
||||
|
||||
# Process report generation (wallet deduction after success)
|
||||
processor = FiveWhysAnalyzerProcessor()
|
||||
result = processor.handle_report_generation(
|
||||
user=request.user,
|
||||
session_id=session_id,
|
||||
problem_statement=problem_statement,
|
||||
context_info=context_info,
|
||||
analysis_depth=analysis_depth
|
||||
)
|
||||
|
||||
# Refresh user to get updated wallet balance
|
||||
request.user.refresh_from_db()
|
||||
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'session_id': session_id,
|
||||
'report': result.final_report,
|
||||
'message_type': 'report',
|
||||
'analysis_depth': analysis_depth,
|
||||
'wallet_balance': float(request.user.wallet_balance)
|
||||
})
|
||||
|
||||
except BaseAgent.DoesNotExist:
|
||||
return JsonResponse({'error': '5 Whys Analysis Agent not found'}, status=404)
|
||||
except Exception as e:
|
||||
return JsonResponse({'error': str(e)}, status=500)
|
||||
|
||||
|
||||
@login_required
|
||||
def five_whys_analyzer_session(request, session_id):
|
||||
"""Get chat session data"""
|
||||
try:
|
||||
session_request = FiveWhysAnalyzerRequest.objects.get(
|
||||
session_id=session_id,
|
||||
user=request.user
|
||||
)
|
||||
|
||||
session_data = {
|
||||
'session_id': session_id,
|
||||
'chat_messages': session_request.chat_messages,
|
||||
'chat_active': session_request.chat_active,
|
||||
'report_generated': session_request.report_generated,
|
||||
'problem_statement': session_request.problem_statement,
|
||||
'context_info': session_request.context_info,
|
||||
'analysis_depth': session_request.analysis_depth
|
||||
}
|
||||
|
||||
# Add final report if generated
|
||||
if session_request.report_generated and hasattr(session_request, 'response'):
|
||||
session_data['final_report'] = session_request.response.final_report
|
||||
session_data['report_metadata'] = session_request.response.report_metadata
|
||||
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'session': session_data
|
||||
})
|
||||
|
||||
except FiveWhysAnalyzerRequest.DoesNotExist:
|
||||
return JsonResponse({'error': 'Session not found'}, status=404)
|
||||
except Exception as e:
|
||||
return JsonResponse({'error': str(e)}, status=500)
|
||||
|
||||
|
||||
# Legacy view for compatibility
|
||||
@method_decorator(csrf_exempt, name='dispatch')
|
||||
class FiveWhysAnalyzerProcessView(View):
|
||||
"""Legacy process view - redirects to chat interface"""
|
||||
|
||||
def post(self, request):
|
||||
return JsonResponse({
|
||||
'error': 'This endpoint is deprecated. Use the chat interface instead.',
|
||||
'redirect': '/agents/five-whys-analyzer/'
|
||||
}, status=410)
|
||||
@ -51,6 +51,7 @@ INSTALLED_APPS = [
|
||||
'data_analyzer',
|
||||
'job_posting_generator',
|
||||
'social_ads_generator',
|
||||
'five_whys_analyzer',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
|
||||
@ -26,6 +26,7 @@ urlpatterns = [
|
||||
path('agents/data-analyzer/', include('data_analyzer.urls')),
|
||||
path('agents/job-posting-generator/', include('job_posting_generator.urls')),
|
||||
path('agents/social-ads-generator/', include('social_ads_generator.urls')),
|
||||
path('agents/five-whys-analyzer/', include('five_whys_analyzer.urls')),
|
||||
path('', include('core.urls')),
|
||||
]
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user