Spaces:
Runtime error
Runtime error
<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> |