quantum-ai/five_whys_analyzer/templates/five_whys_analyzer/detail.html
Claude 49b5b2a6aa Standardize agent architecture and update comprehensive documentation
- Implement agent-specific JavaScript utilities for container-like isolation
- Standardize HTML structure across all 5 agents with agent-container grid layout
- Fix Data Analyzer wallet positioning by moving wallet-section to separate grid column
- Reduce Data Analyzer custom CSS from 400+ lines to ~78 lines essential styles
- Apply unified theme system (professional/creative/minimal) consistently
- Add agent isolation with DataAnalyzerUtils, WeatherUtils, SocialAdsUtils, etc.
- Update CLAUDE.md with comprehensive agent architecture documentation
- Document standardization improvements, layout rules, and JavaScript isolation patterns

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-14 16:28:37 +05:30

781 lines
26 KiB
HTML

{% extends "base.html" %}
{% load static %}
{% block title %}5 Whys Analysis Agent - NetCop AI Hub{% endblock %}
{% block extra_css %}
<!-- Optimized Font Loading -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
<!-- External Stylesheets -->
<link rel="stylesheet" href="{% static 'css/themes.css' %}">
<link rel="stylesheet" href="{% static 'css/agent-base.css' %}">
<!-- Five Whys Analyzer Specific Utilities -->
<script>
// Five Whys Analyzer - Self-contained utilities (no shared dependencies)
const FiveWhysUtils = {
/**
* Update wallet balance display - Five Whys specific
*/
updateWalletBalance(newBalance) {
// Update header balance (anchor tag with emoji)
const headerBalance = document.querySelector('a[data-wallet-balance]');
if (headerBalance) {
headerBalance.textContent = `💰 ${newBalance.toFixed(2)} AED`;
}
// Update page balance (div without emoji)
const pageBalance = document.querySelector('div[data-wallet-balance]');
if (pageBalance) {
pageBalance.textContent = `${newBalance.toFixed(2)} AED`;
}
window.currentWalletBalance = newBalance;
},
/**
* Show toast notification with duplicate prevention
*/
showToast(message, type = 'info') {
// Prevent duplicate toasts
const existingToast = document.querySelector('.five-whys-toast');
if (existingToast) {
existingToast.remove();
}
const toast = document.createElement('div');
toast.className = 'five-whys-toast';
toast.style.cssText = `
position: fixed;
top: 16px;
right: 16px;
padding: 8px 12px;
border-radius: 4px;
color: white;
font-size: 13px;
z-index: 1000;
max-width: 300px;
font-weight: 500;
${type === 'success' ? 'background: #10b981;' : 'background: #ef4444;'}
`;
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(() => {
if (toast.parentNode) {
toast.remove();
}
}, 2000);
},
/**
* Generate text for copy/download functionality
*/
generateTextForExport(contentElementId) {
const content = document.getElementById(contentElementId);
if (content) {
return content.innerText || content.textContent || '';
}
return 'No content available';
},
/**
* Copy content to clipboard
*/
copyToClipboard(text, successMessage = 'Content copied to clipboard!') {
navigator.clipboard.writeText(text).then(() => {
this.showToast(`📋 ${successMessage}`, 'success');
}).catch(() => {
this.showToast('Failed to copy content', 'error');
});
},
/**
* Download content as text file
*/
downloadAsFile(text, filename, successMessage = 'File downloaded!') {
const blob = new Blob([text], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename || `content-${Date.now()}.txt`;
a.click();
URL.revokeObjectURL(url);
this.showToast(`💾 ${successMessage}`, 'success');
}
};
// For backward compatibility, create AgentUtils alias
const AgentUtils = FiveWhysUtils;
</script>
{% endblock %}
{% block content %}
<div class="agent-page theme-professional">
<div class="agent-container">
<!-- Main Content -->
<div>
<div class="card">
<h3 class="section-title">🔍 {{ agent.name }}</h3>
<p class="form-help">{{ agent.description }}</p>
</div>
<!-- Chat Messages Container -->
<div class="card">
<div id="chatContainer" class="chat-container">
<h4 class="section-subtitle">💬 Chat with 5 Whys Analyst</h4>
<!-- Welcome Message -->
<div class="welcome-message">
<div class="message-header">5 Whys Analyst</div>
<div class="message-content">
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 {{ agent.price }} AED.
How can I help you today?
</div>
</div>
<!-- Chat messages will be dynamically added here -->
<div id="chatMessages"></div>
</div>
<!-- Chat Input Form -->
<form id="chatForm" class="chat-form">
{% csrf_token %}
<div class="input-group">
<input
type="text"
id="chatInput"
name="message"
class="form-input"
placeholder="Ask me about your problem or describe what you'd like to analyze..."
required
/>
<button
id="sendChatBtn"
type="submit"
class="btn btn-primary"
>
📤 Send
</button>
</div>
</form>
</div>
<!-- Report Generation Section -->
<div class="card" id="reportSection">
<h4 class="section-subtitle">📋 Generate Final Report</h4>
<div id="reportNotReady" class="info-message">
💬 Ask 2-3 questions about your problem first, then I'll generate a comprehensive report
</div>
<div id="reportReady" class="success-message" style="display: none;">
✅ Ready! I can now generate a detailed 5 Whys analysis report based on our conversation
</div>
<button
id="generateReportBtn"
onclick="generateReport()"
class="btn btn-primary"
disabled
style="width: 100%;"
>
🔍 Generate Report ({{ agent.price }} AED)
</button>
</div>
<!-- Generated Report Display -->
<div id="reportResults" class="results-card" style="display: none;">
<div class="results-header">
<div style="font-size: 24px;">📊</div>
<h3 style="font-size: 20px; font-weight: 600; color: var(--text-primary); margin: 0;">5 Whys Analysis Report</h3>
<div style="background: var(--primary-color); color: white; padding: 6px 12px; border-radius: 6px; font-size: 14px; font-weight: 600; margin-left: auto;">✅ Complete</div>
</div>
<div class="results-content" id="reportContent">
<!-- Report content will be displayed here -->
</div>
<div class="action-buttons">
<button onclick="copyReport()" class="btn btn-primary">📋 Copy Report</button>
<button onclick="downloadReport()" class="btn btn-secondary">💾 Download Report</button>
</div>
</div>
</div>
<!-- Wallet Sidebar -->
<div class="wallet-section">
<div class="card">
<h3 class="section-title">💳 Your Wallet</h3>
<div class="wallet-balance" data-wallet-balance>{% if user.is_authenticated %}{{ user.wallet_balance|floatformat:2 }} AED{% else %}0.00 AED{% endif %}</div>
<div class="balance-label">Available Balance</div>
{% if user.is_authenticated %}
<a href="{% url 'core:wallet' %}" class="btn btn-primary" style="text-decoration: none; margin-top: 16px;">
💰 Top Up Wallet
</a>
{% else %}
<a href="{% url 'authentication:login' %}" class="btn btn-primary process-btn" style="text-decoration: none;">
🔑 Login to Continue
</a>
{% endif %}
</div>
<div class="usage-info">
<h4>💡 How it works</h4>
<ul>
<li>Chat freely to explore your problem</li>
<li>Get guidance and ask questions</li>
<li>Generate final report when ready</li>
<li>Pay only for the final report</li>
</ul>
</div>
</div>
</div>
</div>
<style>
.chat-container {
background: var(--background-subtle);
border-radius: 8px;
padding: 16px;
margin: 16px 0;
min-height: 400px;
max-height: 600px;
overflow-y: auto;
}
.welcome-message {
margin-bottom: 16px;
padding: 16px 20px;
background: var(--background-subtle);
border-radius: 16px 16px 16px 4px;
border-left: 4px solid var(--primary-color);
line-height: 1.6;
}
.message-header {
font-weight: 600;
color: var(--primary-color);
margin-bottom: 8px;
}
.message-content {
color: var(--text-primary);
line-height: 1.6;
}
.chat-form {
border-top: 1px solid var(--border-color);
padding-top: 16px;
margin-top: 16px;
}
.input-group {
display: flex;
gap: 12px;
align-items: stretch;
}
.input-group .form-input {
width: 80%;
height: 40px;
border: 2px solid var(--border-color);
border-radius: 8px;
padding: 8px 12px;
font-family: inherit;
font-size: 14px;
background: var(--background-primary);
color: var(--text-primary);
box-sizing: border-box;
}
.input-group .form-input:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.1);
}
.input-group .btn {
height: 40px;
padding: 8px 12px;
width: 20%;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
box-sizing: border-box;
font-size: 13px;
}
.info-message {
padding: 16px;
background: var(--background-subtle);
border-radius: 8px;
text-align: center;
color: var(--text-secondary);
margin-bottom: 16px;
}
.success-message {
padding: 16px;
background: #ecfdf5;
border-radius: 8px;
text-align: center;
color: #059669;
margin-bottom: 16px;
}
.message {
margin-bottom: 16px;
animation: fadeIn 0.3s ease;
}
.user-message {
margin-left: 20%;
padding: 12px 16px;
background: var(--primary-color);
color: white;
border-radius: 16px 16px 4px 16px;
}
.assistant-message {
margin-right: 20%;
padding: 16px 20px;
background: var(--background-subtle);
border-radius: 16px 16px 16px 4px;
border-left: 4px solid var(--primary-color);
line-height: 1.6;
}
.assistant-message .message-content {
color: var(--text-primary);
line-height: 1.6;
}
.assistant-message .message-content h3 {
color: var(--text-primary);
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: var(--text-primary);
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: 8px;
height: 8px;
border-radius: 50%;
background: var(--primary-color);
animation: typing 1.4s infinite ease-in-out;
}
.typing-dots span:nth-child(1) { animation-delay: -0.32s; }
.typing-dots span:nth-child(2) { animation-delay: -0.16s; }
@keyframes typing {
0%, 80%, 100% { transform: scale(0); }
40% { transform: scale(1); }
}
</style>
<script>
let currentSessionId = null;
let isProcessing = false;
let messageCount = 0;
// Initialize session
document.addEventListener('DOMContentLoaded', function() {
// Generate new session ID
currentSessionId = generateSessionId();
// Handle chat form submission
document.getElementById('chatForm').addEventListener('submit', function(e) {
e.preventDefault();
sendChatMessage();
});
// Add Enter key support for chat input (Shift+Enter for new line)
document.getElementById('chatInput').addEventListener('keydown', 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) {
AgentUtils.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 {
AgentUtils.showToast(data.error || 'Failed to send message', 'error');
}
})
.catch(error => {
// Hide typing indicator on error
hideTypingIndicator();
console.error('Error:', error);
AgentUtils.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;
}
}
function generateReport() {
if (isProcessing) return;
if (!currentSessionId) {
AgentUtils.showToast('Please start a chat session first', 'error');
return;
}
if (messageCount < 2) {
AgentUtils.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 if provided
if (data.wallet_balance !== undefined) {
AgentUtils.updateWalletBalance(data.wallet_balance);
}
AgentUtils.showToast('✅ Report generated and payment processed!', 'success');
} else {
AgentUtils.showToast(data.error || 'Failed to generate report', 'error');
}
})
.catch(error => {
console.error('Error:', error);
AgentUtils.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 copyReport() {
const reportText = AgentUtils.generateTextForExport('reportContent');
AgentUtils.copyToClipboard(reportText, '📋 Report copied to clipboard!');
}
function downloadReport() {
const reportText = AgentUtils.generateTextForExport('reportContent');
AgentUtils.downloadAsFile(reportText, `five-whys-analysis-${Date.now()}.txt`, '💾 Report downloaded!');
}
</script>
{% endblock %}