Spaces:
Sleeping
Sleeping
| <html lang="ar" dir="ltr"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>AISA Workflow Engine</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <style> | |
| /* Custom Animations & Glows */ | |
| @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&display=swap'); | |
| body { font-family: 'JetBrains Mono', monospace; background-color: #0B1120; } | |
| .node { | |
| transition: all 0.4s ease; | |
| box-shadow: 0 0 10px rgba(0,0,0,0.5); | |
| } | |
| .node.active { | |
| border-color: #38bdf8; | |
| box-shadow: 0 0 20px #38bdf8, inset 0 0 10px rgba(56, 189, 248, 0.2); | |
| transform: scale(1.05); | |
| color: #fff; | |
| } | |
| .connector { | |
| width: 2px; | |
| background-color: #334155; | |
| transition: background-color 0.3s; | |
| } | |
| .connector.active { | |
| background-color: #38bdf8; | |
| box-shadow: 0 0 8px #38bdf8; | |
| } | |
| /* Scrollbar Styling */ | |
| ::-webkit-scrollbar { width: 8px; } | |
| ::-webkit-scrollbar-track { background: #1e293b; } | |
| ::-webkit-scrollbar-thumb { background: #475569; border-radius: 4px; } | |
| ::-webkit-scrollbar-thumb:hover { background: #64748b; } | |
| </style> | |
| </head> | |
| <body class="text-slate-200 h-screen flex flex-col overflow-hidden"> | |
| <header class="bg-slate-900 border-b border-slate-800 p-4 flex justify-between items-center shadow-lg z-10"> | |
| <div class="flex items-center gap-3"> | |
| <div class="w-3 h-3 rounded-full bg-blue-500 animate-pulse"></div> | |
| <h1 class="text-xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-blue-400 to-cyan-300"> | |
| AISA Orchestrator | |
| </h1> | |
| </div> | |
| <div id="status-badge" class="px-3 py-1 text-xs rounded-full bg-slate-800 text-slate-400 border border-slate-700"> | |
| System Idle | |
| </div> | |
| </header> | |
| <main class="flex flex-1 relative"> | |
| <div class="w-1/2 flex flex-col items-center justify-center bg-slate-900/50 p-10 relative"> | |
| <div class="absolute inset-0 bg-[url('https://www.transparenttextures.com/patterns/carbon-fibre.png')] opacity-10 pointer-events-none"></div> | |
| <div id="node-start" class="node w-32 h-12 rounded-full border-2 border-slate-600 bg-slate-800 flex items-center justify-center text-sm mb-2 z-10"> | |
| Start | |
| </div> | |
| <div class="connector h-8 mb-2"></div> | |
| <div id="node-planner" class="node w-48 h-16 rounded-lg border-2 border-slate-600 bg-slate-800 flex items-center justify-center gap-2 mb-2 z-10"> | |
| <span>🧠</span> Planner | |
| </div> | |
| <div class="connector h-8 mb-2"></div> | |
| <div class="p-6 rounded-xl border border-dashed border-slate-700 bg-slate-800/30 flex flex-col items-center w-full max-w-sm relative"> | |
| <span class="absolute -top-3 left-4 bg-slate-900 px-2 text-xs text-slate-500">Execution Cycle</span> | |
| <div id="node-executor" class="node w-48 h-16 rounded-lg border-2 border-slate-600 bg-slate-800 flex items-center justify-center gap-2 mb-4 z-10"> | |
| <span>🔨</span> Executor | |
| </div> | |
| <div class="connector h-6 mb-4"></div> | |
| <div id="node-validator" class="node w-48 h-16 rounded-lg border-2 border-slate-600 bg-slate-800 flex items-center justify-center gap-2 mb-4 z-10"> | |
| <span>🛡️</span> Validator | |
| </div> | |
| <div class="connector h-6 mb-4"></div> | |
| <div id="node-decision" class="node w-12 h-12 rotate-45 border-2 border-slate-600 bg-slate-800 flex items-center justify-center z-10"> | |
| <span class="-rotate-45 text-xs">⚖️</span> | |
| </div> | |
| </div> | |
| <div class="connector h-8 mt-2 mb-2"></div> | |
| <div id="node-end" class="node w-32 h-12 rounded-full border-2 border-slate-600 bg-slate-800 flex items-center justify-center text-sm z-10"> | |
| 🚀 Output | |
| </div> | |
| </div> | |
| <div class="w-1/2 flex flex-col border-l border-slate-800 bg-slate-900 overflow-y-auto custom-scrollbar"> | |
| <div class="p-6 space-y-6"> | |
| <div> | |
| <label class="block text-xs text-blue-400 mb-2 font-bold uppercase tracking-wider">Mission Objective</label> | |
| <div class="flex gap-2"> | |
| <input type="text" id="taskInput" value="Search about KSA Vision 2030" | |
| class="flex-1 bg-slate-800 border border-slate-700 rounded-lg px-4 py-3 focus:outline-none focus:border-blue-500 text-sm transition"> | |
| <button onclick="startWorkflow()" id="startBtn" | |
| class="bg-blue-600 hover:bg-blue-500 text-white px-6 py-3 rounded-lg font-bold transition shadow-lg shadow-blue-900/50 flex items-center gap-2"> | |
| <span>RUN</span> | |
| </button> | |
| </div> | |
| </div> | |
| <div class="flex flex-col bg-slate-950 rounded-lg border border-slate-800 overflow-hidden shadow-inner h-80 shrink-0"> | |
| <div class="bg-slate-900 px-4 py-2 border-b border-slate-800 flex justify-between items-center"> | |
| <span class="text-xs text-slate-400">System Logs</span> | |
| <div class="flex gap-1"> | |
| <span class="w-2 h-2 rounded-full bg-red-500"></span> | |
| <span class="w-2 h-2 rounded-full bg-yellow-500"></span> | |
| <span class="w-2 h-2 rounded-full bg-green-500"></span> | |
| </div> | |
| </div> | |
| <div id="logs" class="flex-1 p-4 overflow-y-auto font-mono text-sm space-y-2 scroll-smooth"> | |
| <div class="text-slate-600 italic">> System ready. Waiting for input...</div> | |
| </div> | |
| </div> | |
| <div id="resultBox" class="hidden bg-slate-800/80 border border-green-500/30 rounded-lg p-6 shadow-lg shadow-green-900/20 backdrop-blur-sm flex flex-col max-h-[60vh]"> | |
| <h3 class="text-green-400 font-bold text-lg mb-4 flex items-center gap-2 border-b border-slate-700 pb-2 shrink-0"> | |
| <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg> | |
| Final Report Generated | |
| </h3> | |
| <div class="overflow-y-auto custom-scrollbar pr-2"> | |
| <div id="finalReport" class="text-sm text-slate-300 whitespace-pre-wrap font-sans leading-relaxed"></div> | |
| </div> | |
| </div> | |
| <div class="h-10"></div> | |
| </div> | |
| </div> | |
| </main> | |
| <script> | |
| function startWorkflow() { | |
| const task = document.getElementById('taskInput').value; | |
| const btn = document.getElementById('startBtn'); | |
| const logs = document.getElementById('logs'); | |
| const resultBox = document.getElementById('resultBox'); | |
| // 1. Reset UI | |
| logs.innerHTML = ''; | |
| resultBox.classList.add('hidden'); | |
| document.getElementById('finalReport').innerHTML = ''; | |
| // Disable Button | |
| btn.disabled = true; | |
| btn.classList.add('opacity-50', 'cursor-not-allowed'); | |
| btn.innerHTML = '<span>⏳ Processing...</span>'; | |
| resetNodes(); | |
| // 2. Connect to Stream | |
| const eventSource = new EventSource(`/stream_workflow?task=${encodeURIComponent(task)}`); | |
| // Helper to clean up state | |
| function stopExecution(success = true) { | |
| eventSource.close(); | |
| btn.disabled = false; | |
| btn.classList.remove('opacity-50', 'cursor-not-allowed'); | |
| btn.innerHTML = '<span>RUN</span>'; | |
| if (success) { | |
| updateStatus("Mission Completed"); | |
| document.getElementById('node-end').classList.add('active'); | |
| } else { | |
| updateStatus("Stopped / Error"); | |
| } | |
| } | |
| eventSource.onmessage = function(e) { | |
| let data; | |
| try { | |
| data = JSON.parse(e.data); | |
| } catch (err) { | |
| console.error("Parse Error", err); | |
| return; | |
| } | |
| if (data.type === 'activate') { | |
| highlightNode(data.node); | |
| addLog(data.msg, 'info'); | |
| updateStatus(data.msg); | |
| } | |
| else if (data.type === 'log') { | |
| addLog(data.msg, data.role); | |
| } | |
| else if (data.type === 'retry_animation') { | |
| flashDecisionNode(); | |
| } | |
| else if (data.type === 'finish') { | |
| // Format Markdown | |
| let formattedReport = data.report | |
| .replace(/### (.*?)\n/g, '<h3 class="text-blue-400 font-bold text-lg mt-6 mb-2 border-b border-slate-700 pb-1">$1</h3>') | |
| .replace(/\*\*(.*?)\*\*/g, '<strong class="text-white font-semibold">$1</strong>') | |
| .replace(/- (.*?)\n/g, '<li class="ml-4 text-slate-300 list-disc marker:text-blue-500 mb-1">$1</li>') | |
| .replace(/\n/g, '<br>'); | |
| document.getElementById('finalReport').innerHTML = formattedReport; | |
| resultBox.classList.remove('hidden'); | |
| addLog("Workflow Finished Successfully.", "success"); | |
| stopExecution(true); // Close safely | |
| } | |
| }; | |
| eventSource.onerror = function() { | |
| addLog("Stream connection closed or interrupted.", "warning"); | |
| stopExecution(false); // Force button enable | |
| }; | |
| } | |
| // --- Helper Functions --- | |
| function highlightNode(nodeId) { | |
| document.querySelectorAll('.node').forEach(n => n.classList.remove('active')); | |
| document.querySelectorAll('.connector').forEach(c => c.classList.remove('active')); | |
| const el = document.getElementById(`node-${nodeId}`); | |
| if (el) { | |
| el.classList.add('active'); | |
| if (el.previousElementSibling && el.previousElementSibling.classList.contains('connector')) { | |
| el.previousElementSibling.classList.add('active'); | |
| } | |
| } | |
| } | |
| function resetNodes() { | |
| document.querySelectorAll('.node').forEach(n => n.classList.remove('active')); | |
| document.querySelectorAll('.connector').forEach(c => c.classList.remove('active')); | |
| } | |
| function flashDecisionNode() { | |
| const decision = document.getElementById('node-decision'); | |
| decision.style.borderColor = '#ef4444'; | |
| decision.style.boxShadow = '0 0 15px #ef4444'; | |
| setTimeout(() => { | |
| decision.style.borderColor = ''; | |
| decision.style.boxShadow = ''; | |
| }, 500); | |
| } | |
| function updateStatus(text) { | |
| const badge = document.getElementById('status-badge'); | |
| badge.textContent = text; | |
| badge.classList.remove('text-slate-400'); | |
| badge.classList.add('text-blue-400', 'border-blue-500'); | |
| } | |
| function addLog(msg, type) { | |
| const logs = document.getElementById('logs'); | |
| const div = document.createElement('div'); | |
| div.className = 'border-l-2 pl-2 text-xs py-1 animate-fade-in'; | |
| if (type === 'error') { div.classList.add('border-red-500', 'text-red-400'); } | |
| else if (type === 'success') { div.classList.add('border-green-500', 'text-green-400'); } | |
| else if (type === 'warning') { div.classList.add('border-yellow-500', 'text-yellow-400'); } | |
| else { div.classList.add('border-blue-500', 'text-slate-300'); } | |
| div.innerHTML = `> ${msg}`; | |
| logs.appendChild(div); | |
| logs.scrollTop = logs.scrollHeight; | |
| } | |
| </script> | |
| </body> |