EL GHAFRAOUI AYOUB
C
04ad574
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>F5 Model Test Interface</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<style>
.loading {
display: none;
position: relative;
width: 80px;
height: 80px;
margin: 0 auto;
}
.loading div {
position: absolute;
width: 16px;
height: 16px;
border-radius: 50%;
background: #4B5563;
animation: loading 1.2s linear infinite;
}
.loading div:nth-child(1) {
top: 8px;
left: 8px;
animation-delay: 0s;
}
.loading div:nth-child(2) {
top: 8px;
left: 32px;
animation-delay: -0.4s;
}
.loading div:nth-child(3) {
top: 8px;
left: 56px;
animation-delay: -0.8s;
}
@keyframes loading {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
/* Chat Widget Styles */
.chat-widget {
position: fixed;
bottom: 20px;
right: 20px;
z-index: 1000;
}
.chat-button {
width: 60px;
height: 60px;
border-radius: 50%;
background-color: #3B82F6;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
}
.chat-button:hover {
transform: scale(1.1);
}
.chat-window {
position: fixed;
bottom: 90px;
right: 20px;
width: 350px;
height: 500px;
background: white;
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
display: none;
flex-direction: column;
overflow: hidden;
}
.chat-window.active {
display: flex;
}
.chat-header {
padding: 1rem;
background: #3B82F6;
color: white;
display: flex;
justify-content: space-between;
align-items: center;
}
.chat-messages {
flex-grow: 1;
overflow-y: auto;
padding: 1rem;
}
.chat-input-container {
padding: 1rem;
border-top: 1px solid #e5e7eb;
background: white;
}
</style>
</head>
<body class="bg-gray-100">
<div class="container mx-auto px-4 py-8">
<h1 class="text-3xl font-bold mb-8">F5 Model Test Interface</h1>
<!-- Project Plan Generation Section -->
<div class="bg-white rounded-lg shadow-md p-6 mb-8">
<h2 class="text-xl font-semibold mb-4">Project Plan Generation</h2>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Project Title</label>
<input type="text" id="projectTitle" class="w-full border rounded px-3 py-2" placeholder="Enter project title">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Requirements</label>
<textarea id="planRequirements" class="w-full border rounded px-3 py-2 h-32" placeholder="Enter project requirements"></textarea>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Features (comma-separated)</label>
<input type="text" id="planFeatures" class="w-full border rounded px-3 py-2" placeholder="Feature 1, Feature 2, Feature 3">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Platform</label>
<select id="platform" class="w-full border rounded px-3 py-2">
<option value="AWS">AWS</option>
<option value="Azure">Azure</option>
<option value="GCP">Google Cloud</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Additional Requirements</label>
<textarea id="additionalRequirements" class="w-full border rounded px-3 py-2 h-24" placeholder="Enter any additional requirements"></textarea>
</div>
<button onclick="generatePlan()" class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600">
Generate Plan
</button>
</div>
<div id="planLoading" class="loading mt-4">
<div></div>
<div></div>
<div></div>
</div>
<div id="planOutput" class="mt-6 bg-gray-50 rounded p-4 hidden">
<h3 class="text-lg font-semibold mb-2" id="planTitle"></h3>
<div id="planSections" class="space-y-4"></div>
</div>
<div id="debugPanel" class="mt-6 bg-gray-800 rounded-lg p-4 text-white">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-semibold">Debug Panel</h3>
<div class="space-x-2">
<button onclick="copyRawContent()" class="text-xs bg-blue-500 hover:bg-blue-600 px-2 py-1 rounded">
<i class="fas fa-copy mr-1"></i>Copy
</button>
<button onclick="toggleDebugPanel()" class="text-gray-400 hover:text-white">
<i class="fas fa-times"></i>
</button>
</div>
</div>
<div class="space-y-4">
<div class="border-b border-gray-700 pb-4">
<h4 class="text-sm font-semibold text-gray-400 mb-2">Request Data</h4>
<pre id="requestData" class="text-xs bg-gray-900 p-3 rounded overflow-auto max-h-40 font-mono"></pre>
</div>
<div class="border-b border-gray-700 pb-4">
<div class="flex justify-between items-center mb-2">
<h4 class="text-sm font-semibold text-gray-400">Raw Model Output</h4>
<span id="rawContentLength" class="text-xs text-gray-500"></span>
</div>
<pre id="rawContent" class="text-xs bg-gray-900 p-3 rounded overflow-auto max-h-96 font-mono whitespace-pre-wrap"></pre>
</div>
<div>
<h4 class="text-sm font-semibold text-gray-400 mb-2">Parsed Sections</h4>
<pre id="parsedSections" class="text-xs bg-gray-900 p-3 rounded overflow-auto max-h-40 font-mono"></pre>
</div>
</div>
</div>
</div>
<!-- Feature Generation Section -->
<div class="bg-white rounded-lg shadow-md p-6">
<h2 class="text-xl font-semibold mb-4">Feature Generation Test</h2>
<div class="mb-4">
<textarea id="requirementsInput"
class="w-full border rounded p-3 mb-4 h-32"
placeholder="Enter requirements..."></textarea>
<button onclick="generateFeatures()"
class="bg-green-500 text-white px-4 py-2 rounded hover:bg-green-600">
Generate Features
</button>
</div>
<div id="featuresLoading" class="loading mb-4">
<div></div>
<div></div>
<div></div>
</div>
<div id="featuresOutput" class="bg-gray-50 rounded p-4 min-h-32"></div>
</div>
<!-- Add this before the rawOutput div -->
<div id="progressBar" class="hidden mt-4 mb-8">
<div class="flex justify-between mb-1">
<span class="text-sm font-medium text-blue-700" id="progressText">0%</span>
<span class="text-sm font-medium text-blue-700" id="progressStep">Step 0/100</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-2.5">
<div class="bg-blue-600 h-2.5 rounded-full transition-all duration-150" id="progressFill" style="width: 0%"></div>
</div>
</div>
</div>
<!-- Chat Widget -->
<div class="chat-widget">
<div class="chat-button" onclick="toggleChat()">
<i class="fas fa-comments text-white text-2xl"></i>
</div>
<div class="chat-window" id="chatWindow">
<div class="chat-header">
<span class="font-semibold">Chat Assistant</span>
<button onclick="toggleChat()" class="text-white hover:text-gray-200">
<i class="fas fa-times"></i>
</button>
</div>
<div class="chat-messages" id="chatHistory">
<!-- Messages will be inserted here -->
</div>
<div id="chatLoading" class="loading mx-auto my-2" style="transform: scale(0.5);">
<div></div>
<div></div>
<div></div>
</div>
<div class="chat-input-container">
<div class="flex gap-2">
<input type="text" id="chatInput"
class="flex-1 border rounded px-3 py-2"
placeholder="Type your message...">
<button onclick="sendMessage()"
class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600">
<i class="fas fa-paper-plane"></i>
</button>
</div>
<div class="flex items-center gap-2 mt-2">
<label class="flex items-center gap-2 text-sm text-gray-600">
<input type="checkbox" id="streamToggle">
Stream responses
</label>
</div>
</div>
</div>
</div>
<script>
let chatHistory = [];
let chatWindowOpen = false;
let accumulatedContent = '';
function toggleChat() {
const chatWindow = document.getElementById('chatWindow');
chatWindowOpen = !chatWindowOpen;
chatWindow.classList.toggle('active');
}
async function sendMessage() {
const input = document.getElementById('chatInput');
const message = input.value.trim();
const streamMode = document.getElementById('streamToggle').checked;
if (!message) return;
input.value = '';
appendMessage('user', message);
const loading = document.getElementById('chatLoading');
loading.style.display = 'block';
try {
if (streamMode) {
const response = await fetch('/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
messages: chatHistory,
stream: true
})
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
let assistantMessage = '';
while (true) {
const { value, done } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split('\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
const content = line.slice(6);
assistantMessage += content;
updateLastMessage('assistant', assistantMessage);
}
}
}
} else {
const response = await fetch('/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
messages: chatHistory,
stream: false
})
});
const data = await response.json();
appendMessage('assistant', data.response);
}
} catch (error) {
console.error('Error:', error);
appendMessage('assistant', 'Sorry, there was an error processing your request.');
} finally {
loading.style.display = 'none';
}
}
async function generateFeatures() {
const requirements = document.getElementById('requirementsInput').value.trim();
if (!requirements) return;
// Show loading indicator
document.getElementById('featuresLoading').style.display = 'block';
document.getElementById('featuresOutput').innerHTML = '';
try {
const response = await fetch('/generate-features', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ requirements })
});
const data = await response.json();
displayFeatures(data.features);
} catch (error) {
console.error('Error:', error);
document.getElementById('featuresOutput').innerHTML =
'<p class="text-red-500">Error generating features</p>';
} finally {
// Hide loading indicator
document.getElementById('featuresLoading').style.display = 'none';
}
}
function appendMessage(role, content) {
chatHistory.push({ role, content });
updateChatDisplay();
}
function updateLastMessage(role, content) {
if (chatHistory.length > 0 && chatHistory[chatHistory.length - 1].role === role) {
chatHistory[chatHistory.length - 1].content = content;
} else {
chatHistory.push({ role, content });
}
updateChatDisplay();
}
function updateChatDisplay() {
const chatDiv = document.getElementById('chatHistory');
chatDiv.innerHTML = chatHistory.map(msg => `
<div class="mb-3">
<div class="flex items-start ${msg.role === 'user' ? 'justify-end' : 'justify-start'}">
<div class="max-w-[80%] ${msg.role === 'user' ? 'bg-blue-500 text-white' : 'bg-gray-200'} rounded-lg px-4 py-2">
<div class="text-sm">${msg.content}</div>
</div>
</div>
</div>
`).join('');
chatDiv.scrollTop = chatDiv.scrollHeight;
}
function displayFeatures(features) {
const output = document.getElementById('featuresOutput');
output.innerHTML = features.map(feature => `
<div class="mb-4 p-4 bg-white rounded shadow">
<h3 class="font-semibold text-lg mb-2">${feature.feature}</h3>
<p class="text-gray-600">${feature.short_description}</p>
</div>
`).join('');
}
// Handle Enter key in chat input
document.getElementById('chatInput').addEventListener('keypress', function(e) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
});
async function generatePlan() {
const projectTitle = document.getElementById('projectTitle').value.trim();
const requirements = document.getElementById('planRequirements').value.trim();
const features = document.getElementById('planFeatures').value.split(',').map(f => f.trim()).filter(f => f);
const platform = document.getElementById('platform').value;
const additionalRequirements = document.getElementById('additionalRequirements').value.trim();
if (!projectTitle || !requirements || features.length === 0) {
alert('Please fill in all required fields');
return;
}
const requestData = {
project_title: projectTitle,
requirements,
features,
platform,
additional_requirements: additionalRequirements
};
console.log('Request Data:', requestData);
const loading = document.getElementById('planLoading');
const output = document.getElementById('planOutput');
loading.style.display = 'block';
output.classList.add('hidden');
try {
console.log('Starting plan generation request...');
const response = await fetch('/generate-plan', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(requestData)
});
console.log('Got response, starting reader...');
const reader = response.body.getReader();
const decoder = new TextDecoder();
document.getElementById('progressBar').classList.remove('hidden');
console.log('Progress bar shown');
while (true) {
const { value, done } = await reader.read();
if (done) {
console.log('Stream complete');
break;
}
const chunk = decoder.decode(value);
console.log('Received chunk:', chunk);
const lines = chunk.split('\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = JSON.parse(line.slice(5));
console.log('Parsed data:', data);
if (data.type === 'progress') {
console.log('Progress update:', data.progress + '%');
const progressFill = document.getElementById('progressFill');
const progressText = document.getElementById('progressText');
const progressStep = document.getElementById('progressStep');
progressFill.style.width = `${data.progress}%`;
progressText.textContent = `${Math.round(data.progress)}%`;
progressStep.textContent = `Step ${data.step}/${data.total}`;
} else if (data.type === 'complete') {
accumulatedContent += data.raw_content;
data.sections = parseSections(accumulatedContent);
console.log('Generation complete, displaying plan with content:', accumulatedContent);
displayPlan(data);
} else if (data.type === 'error') {
console.error('Error in generation:', data.error);
alert(`Error: ${data.error}`);
}
}
}
}
} catch (error) {
console.error('Error in generatePlan:', error);
alert('Error generating plan');
} finally {
console.log('Cleaning up...');
loading.style.display = 'none';
document.getElementById('progressBar').classList.add('hidden');
}
}
function toggleDebugPanel() {
const debugPanel = document.getElementById('debugPanel');
debugPanel.classList.toggle('hidden');
}
function copyRawContent() {
const rawContent = document.getElementById('rawContent').textContent;
navigator.clipboard.writeText(rawContent).then(() => {
alert('Raw content copied to clipboard!');
});
}
function displayPlan(data) {
const output = document.getElementById('planOutput');
const title = document.getElementById('planTitle');
const sections = document.getElementById('planSections');
try {
// Update debug panel with raw data
document.getElementById('requestData').textContent = JSON.stringify({
project_title: data.project_title,
sections: data.sections
}, null, 2);
// Display raw content with line breaks preserved
const rawContent = document.getElementById('rawContent');
rawContent.textContent = data.raw_content;
// Show content length
const contentLength = data.raw_content.length;
document.getElementById('rawContentLength').textContent =
`Length: ${contentLength} characters`;
// Display parsed sections
document.getElementById('parsedSections').textContent =
JSON.stringify(data.sections, null, 2);
// Original plan display code
title.textContent = `Technical Project Plan: ${data.project_title}`;
sections.innerHTML = '';
output.classList.remove('hidden'); // Show the output section instead of hiding it
} catch (error) {
console.error('Error in displayPlan:', error);
alert('Error displaying plan. Check console for details.');
}
}
function parseSections(content) {
const sections = {
"executive_summary": "",
"scope_objectives": "",
"architecture_overview": "",
"component_design": "",
"security_compliance": "",
"deployment_testing": "",
"team_roles": "",
"cost_estimates": "",
"project_phases": ""
};
let currentSection = null;
const lines = content.split('\n');
for (const line of lines) {
if (line.startsWith('1. Executive Summary')) {
currentSection = "executive_summary";
} else if (line.startsWith('2. Project Scope and Objectives')) {
currentSection = "scope_objectives";
} else if (line.startsWith('3. Architecture Overview')) {
currentSection = "architecture_overview";
} else if (line.startsWith('4. Component Design')) {
currentSection = "component_design";
} else if (line.startsWith('5. Security and Compliance')) {
currentSection = "security_compliance";
} else if (line.startsWith('6. Deployment and Testing')) {
currentSection = "deployment_testing";
} else if (line.startsWith('7. Team Roles')) {
currentSection = "team_roles";
} else if (line.startsWith('8. Cost Estimates')) {
currentSection = "cost_estimates";
} else if (line.startsWith('9. Project Phases')) {
currentSection = "project_phases";
} else if (currentSection && line.trim()) {
sections[currentSection] += line + '\n';
}
}
// Trim any trailing whitespace from sections
Object.keys(sections).forEach(key => {
sections[key] = sections[key].trim();
if (!sections[key]) {
sections[key] = "No content generated";
}
});
return sections;
}
</script>
</body>
</html>