Spaces:
Sleeping
Sleeping
adeem turky commited on
Upload 4 files
Browse files- .gitattributes +1 -0
- Assigment Reflection.pdf +3 -0
- app-2.py +78 -0
- templates/index.html +274 -0
- workflow_orchestrator.py +191 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
Assigment[[:space:]]Reflection.pdf filter=lfs diff=lfs merge=lfs -text
|
Assigment Reflection.pdf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:b75a3e0ee0412fe45b3640911cab75be84f69ba7f7bc325fec366289328615eb
|
| 3 |
+
size 1060397
|
app-2.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from flask import Flask, render_template, request, Response, stream_with_context
|
| 2 |
+
import json
|
| 3 |
+
import time
|
| 4 |
+
from workflow_orchestrator import WorkflowOrchestrator
|
| 5 |
+
|
| 6 |
+
app = Flask(__name__)
|
| 7 |
+
|
| 8 |
+
# ==================== CONFIGURATION ====================
|
| 9 |
+
COHERE_API_KEY = "KEY"
|
| 10 |
+
|
| 11 |
+
# ==================== FLASK ROUTES ====================
|
| 12 |
+
|
| 13 |
+
@app.route('/')
|
| 14 |
+
def index():
|
| 15 |
+
return render_template('index.html')
|
| 16 |
+
|
| 17 |
+
@app.route('/stream_workflow')
|
| 18 |
+
def stream_workflow():
|
| 19 |
+
task = request.args.get('task', 'Search about KSA Vision 2030')
|
| 20 |
+
|
| 21 |
+
def generate():
|
| 22 |
+
orchestrator = WorkflowOrchestrator(COHERE_API_KEY)
|
| 23 |
+
|
| 24 |
+
def on_event(event_type, payload):
|
| 25 |
+
data = {
|
| 26 |
+
'type': event_type,
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
if isinstance(payload, dict):
|
| 30 |
+
data.update(payload)
|
| 31 |
+
|
| 32 |
+
# 1. Start
|
| 33 |
+
yield f"data: {json.dumps({'type': 'status', 'node': 'start', 'msg': 'Connecting to Neural Network...'})}\n\n"
|
| 34 |
+
|
| 35 |
+
yield f"data: {json.dumps({'type': 'activate', 'node': 'planner', 'msg': 'Planner: Analyzing complexity...'})}\n\n"
|
| 36 |
+
plan = orchestrator.agents["planner"].execute(task, {})
|
| 37 |
+
steps = plan['steps']
|
| 38 |
+
yield f"data: {json.dumps({'type': 'log', 'msg': f'Strategized {len(steps)} execution steps based on real analysis.', 'role': 'planner'})}\n\n"
|
| 39 |
+
|
| 40 |
+
accumulated_data = ""
|
| 41 |
+
|
| 42 |
+
# 2. Execution Loop
|
| 43 |
+
for i, step in enumerate(steps):
|
| 44 |
+
yield f"data: {json.dumps({'type': 'activate', 'node': 'executor', 'msg': f'Researching: {step}'})}\n\n"
|
| 45 |
+
|
| 46 |
+
exec_res = orchestrator.agents["executor"].execute(step, {})
|
| 47 |
+
accumulated_data += f"\nSection {i+1}: {step}\n{exec_res['output']}\n"
|
| 48 |
+
|
| 49 |
+
yield f"data: {json.dumps({'type': 'activate', 'node': 'validator', 'msg': 'Verifying sources...'})}\n\n"
|
| 50 |
+
val_res = orchestrator.agents["validator"].execute(exec_res, {})
|
| 51 |
+
|
| 52 |
+
yield f"data: {json.dumps({'type': 'activate', 'node': 'decision', 'msg': 'Quality Gate'})}\n\n"
|
| 53 |
+
|
| 54 |
+
if val_res['is_valid']:
|
| 55 |
+
citations_count = len(exec_res.get('citations', []))
|
| 56 |
+
yield f"data: {json.dumps({'type': 'log', 'msg': f'✅ Validated with {citations_count} citations.', 'role': 'success'})}\n\n"
|
| 57 |
+
else:
|
| 58 |
+
yield f"data: {json.dumps({'type': 'log', 'msg': f'⚠️ Low confidence data.', 'role': 'warning'})}\n\n"
|
| 59 |
+
|
| 60 |
+
time.sleep(0.5)
|
| 61 |
+
|
| 62 |
+
# 3. Final Report
|
| 63 |
+
yield f"data: {json.dumps({'type': 'activate', 'node': 'end', 'msg': 'Generating Final Report...'})}\n\n"
|
| 64 |
+
|
| 65 |
+
final_prompt = f"""
|
| 66 |
+
You are an AI analyst. The user asked: "{task}".
|
| 67 |
+
Based on the following research data, write a comprehensive executive summary in Markdown:
|
| 68 |
+
|
| 69 |
+
{accumulated_data}
|
| 70 |
+
"""
|
| 71 |
+
final_report = orchestrator.co.chat(message=final_prompt, model="command-a-03-2025", temperature=0.3).text
|
| 72 |
+
|
| 73 |
+
yield f"data: {json.dumps({'type': 'finish', 'report': final_report})}\n\n"
|
| 74 |
+
|
| 75 |
+
return Response(stream_with_context(generate()), mimetype='text/event-stream')
|
| 76 |
+
|
| 77 |
+
if __name__ == '__main__':
|
| 78 |
+
app.run(debug=True, port=5000)
|
templates/index.html
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="ar" dir="ltr">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>AISA Workflow Engine</title>
|
| 7 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 8 |
+
<style>
|
| 9 |
+
/* Custom Animations & Glows */
|
| 10 |
+
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&display=swap');
|
| 11 |
+
|
| 12 |
+
body { font-family: 'JetBrains Mono', monospace; background-color: #0B1120; }
|
| 13 |
+
|
| 14 |
+
.node {
|
| 15 |
+
transition: all 0.4s ease;
|
| 16 |
+
box-shadow: 0 0 10px rgba(0,0,0,0.5);
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
.node.active {
|
| 20 |
+
border-color: #38bdf8;
|
| 21 |
+
box-shadow: 0 0 20px #38bdf8, inset 0 0 10px rgba(56, 189, 248, 0.2);
|
| 22 |
+
transform: scale(1.05);
|
| 23 |
+
color: #fff;
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
.connector {
|
| 27 |
+
width: 2px;
|
| 28 |
+
background-color: #334155;
|
| 29 |
+
transition: background-color 0.3s;
|
| 30 |
+
}
|
| 31 |
+
.connector.active {
|
| 32 |
+
background-color: #38bdf8;
|
| 33 |
+
box-shadow: 0 0 8px #38bdf8;
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
/* Scrollbar Styling */
|
| 37 |
+
::-webkit-scrollbar { width: 8px; }
|
| 38 |
+
::-webkit-scrollbar-track { background: #1e293b; }
|
| 39 |
+
::-webkit-scrollbar-thumb { background: #475569; border-radius: 4px; }
|
| 40 |
+
::-webkit-scrollbar-thumb:hover { background: #64748b; }
|
| 41 |
+
</style>
|
| 42 |
+
</head>
|
| 43 |
+
<body class="text-slate-200 h-screen flex flex-col overflow-hidden">
|
| 44 |
+
|
| 45 |
+
<header class="bg-slate-900 border-b border-slate-800 p-4 flex justify-between items-center shadow-lg z-10">
|
| 46 |
+
<div class="flex items-center gap-3">
|
| 47 |
+
<div class="w-3 h-3 rounded-full bg-blue-500 animate-pulse"></div>
|
| 48 |
+
<h1 class="text-xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-blue-400 to-cyan-300">
|
| 49 |
+
AISA Orchestrator
|
| 50 |
+
</h1>
|
| 51 |
+
</div>
|
| 52 |
+
<div id="status-badge" class="px-3 py-1 text-xs rounded-full bg-slate-800 text-slate-400 border border-slate-700">
|
| 53 |
+
System Idle
|
| 54 |
+
</div>
|
| 55 |
+
</header>
|
| 56 |
+
|
| 57 |
+
<main class="flex flex-1 relative">
|
| 58 |
+
|
| 59 |
+
<div class="w-1/2 flex flex-col items-center justify-center bg-slate-900/50 p-10 relative">
|
| 60 |
+
<div class="absolute inset-0 bg-[url('https://www.transparenttextures.com/patterns/carbon-fibre.png')] opacity-10 pointer-events-none"></div>
|
| 61 |
+
|
| 62 |
+
<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">
|
| 63 |
+
Start
|
| 64 |
+
</div>
|
| 65 |
+
<div class="connector h-8 mb-2"></div>
|
| 66 |
+
|
| 67 |
+
<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">
|
| 68 |
+
<span>🧠</span> Planner
|
| 69 |
+
</div>
|
| 70 |
+
<div class="connector h-8 mb-2"></div>
|
| 71 |
+
|
| 72 |
+
<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">
|
| 73 |
+
<span class="absolute -top-3 left-4 bg-slate-900 px-2 text-xs text-slate-500">Execution Cycle</span>
|
| 74 |
+
|
| 75 |
+
<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">
|
| 76 |
+
<span>🔨</span> Executor
|
| 77 |
+
</div>
|
| 78 |
+
|
| 79 |
+
<div class="connector h-6 mb-4"></div>
|
| 80 |
+
|
| 81 |
+
<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">
|
| 82 |
+
<span>🛡️</span> Validator
|
| 83 |
+
</div>
|
| 84 |
+
|
| 85 |
+
<div class="connector h-6 mb-4"></div>
|
| 86 |
+
|
| 87 |
+
<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">
|
| 88 |
+
<span class="-rotate-45 text-xs">⚖️</span>
|
| 89 |
+
</div>
|
| 90 |
+
</div>
|
| 91 |
+
|
| 92 |
+
<div class="connector h-8 mt-2 mb-2"></div>
|
| 93 |
+
|
| 94 |
+
<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">
|
| 95 |
+
🚀 Output
|
| 96 |
+
</div>
|
| 97 |
+
</div>
|
| 98 |
+
|
| 99 |
+
<div class="w-1/2 flex flex-col border-l border-slate-800 bg-slate-900 overflow-y-auto custom-scrollbar">
|
| 100 |
+
|
| 101 |
+
<div class="p-6 space-y-6">
|
| 102 |
+
<div>
|
| 103 |
+
<label class="block text-xs text-blue-400 mb-2 font-bold uppercase tracking-wider">Mission Objective</label>
|
| 104 |
+
<div class="flex gap-2">
|
| 105 |
+
<input type="text" id="taskInput" value="Search about KSA Vision 2030"
|
| 106 |
+
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">
|
| 107 |
+
<button onclick="startWorkflow()" id="startBtn"
|
| 108 |
+
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">
|
| 109 |
+
<span>RUN</span>
|
| 110 |
+
</button>
|
| 111 |
+
</div>
|
| 112 |
+
</div>
|
| 113 |
+
|
| 114 |
+
<div class="flex flex-col bg-slate-950 rounded-lg border border-slate-800 overflow-hidden shadow-inner h-80 shrink-0">
|
| 115 |
+
<div class="bg-slate-900 px-4 py-2 border-b border-slate-800 flex justify-between items-center">
|
| 116 |
+
<span class="text-xs text-slate-400">System Logs</span>
|
| 117 |
+
<div class="flex gap-1">
|
| 118 |
+
<span class="w-2 h-2 rounded-full bg-red-500"></span>
|
| 119 |
+
<span class="w-2 h-2 rounded-full bg-yellow-500"></span>
|
| 120 |
+
<span class="w-2 h-2 rounded-full bg-green-500"></span>
|
| 121 |
+
</div>
|
| 122 |
+
</div>
|
| 123 |
+
<div id="logs" class="flex-1 p-4 overflow-y-auto font-mono text-sm space-y-2 scroll-smooth">
|
| 124 |
+
<div class="text-slate-600 italic">> System ready. Waiting for input...</div>
|
| 125 |
+
</div>
|
| 126 |
+
</div>
|
| 127 |
+
|
| 128 |
+
<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]">
|
| 129 |
+
<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">
|
| 130 |
+
<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>
|
| 131 |
+
Final Report Generated
|
| 132 |
+
</h3>
|
| 133 |
+
|
| 134 |
+
<div class="overflow-y-auto custom-scrollbar pr-2">
|
| 135 |
+
<div id="finalReport" class="text-sm text-slate-300 whitespace-pre-wrap font-sans leading-relaxed"></div>
|
| 136 |
+
</div>
|
| 137 |
+
</div>
|
| 138 |
+
|
| 139 |
+
<div class="h-10"></div>
|
| 140 |
+
</div>
|
| 141 |
+
</div>
|
| 142 |
+
</main>
|
| 143 |
+
|
| 144 |
+
<script>
|
| 145 |
+
function startWorkflow() {
|
| 146 |
+
const task = document.getElementById('taskInput').value;
|
| 147 |
+
const btn = document.getElementById('startBtn');
|
| 148 |
+
const logs = document.getElementById('logs');
|
| 149 |
+
const resultBox = document.getElementById('resultBox');
|
| 150 |
+
|
| 151 |
+
// 1. Reset UI
|
| 152 |
+
logs.innerHTML = '';
|
| 153 |
+
resultBox.classList.add('hidden');
|
| 154 |
+
document.getElementById('finalReport').innerHTML = '';
|
| 155 |
+
|
| 156 |
+
// Disable Button
|
| 157 |
+
btn.disabled = true;
|
| 158 |
+
btn.classList.add('opacity-50', 'cursor-not-allowed');
|
| 159 |
+
btn.innerHTML = '<span>⏳ Processing...</span>';
|
| 160 |
+
resetNodes();
|
| 161 |
+
|
| 162 |
+
// 2. Connect to Stream
|
| 163 |
+
const eventSource = new EventSource(`/stream_workflow?task=${encodeURIComponent(task)}`);
|
| 164 |
+
|
| 165 |
+
// Helper to clean up state
|
| 166 |
+
function stopExecution(success = true) {
|
| 167 |
+
eventSource.close();
|
| 168 |
+
btn.disabled = false;
|
| 169 |
+
btn.classList.remove('opacity-50', 'cursor-not-allowed');
|
| 170 |
+
btn.innerHTML = '<span>RUN</span>';
|
| 171 |
+
|
| 172 |
+
if (success) {
|
| 173 |
+
updateStatus("Mission Completed");
|
| 174 |
+
document.getElementById('node-end').classList.add('active');
|
| 175 |
+
} else {
|
| 176 |
+
updateStatus("Stopped / Error");
|
| 177 |
+
}
|
| 178 |
+
}
|
| 179 |
+
|
| 180 |
+
eventSource.onmessage = function(e) {
|
| 181 |
+
let data;
|
| 182 |
+
try {
|
| 183 |
+
data = JSON.parse(e.data);
|
| 184 |
+
} catch (err) {
|
| 185 |
+
console.error("Parse Error", err);
|
| 186 |
+
return;
|
| 187 |
+
}
|
| 188 |
+
|
| 189 |
+
if (data.type === 'activate') {
|
| 190 |
+
highlightNode(data.node);
|
| 191 |
+
addLog(data.msg, 'info');
|
| 192 |
+
updateStatus(data.msg);
|
| 193 |
+
}
|
| 194 |
+
else if (data.type === 'log') {
|
| 195 |
+
addLog(data.msg, data.role);
|
| 196 |
+
}
|
| 197 |
+
else if (data.type === 'retry_animation') {
|
| 198 |
+
flashDecisionNode();
|
| 199 |
+
}
|
| 200 |
+
else if (data.type === 'finish') {
|
| 201 |
+
// Format Markdown
|
| 202 |
+
let formattedReport = data.report
|
| 203 |
+
.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>')
|
| 204 |
+
.replace(/\*\*(.*?)\*\*/g, '<strong class="text-white font-semibold">$1</strong>')
|
| 205 |
+
.replace(/- (.*?)\n/g, '<li class="ml-4 text-slate-300 list-disc marker:text-blue-500 mb-1">$1</li>')
|
| 206 |
+
.replace(/\n/g, '<br>');
|
| 207 |
+
|
| 208 |
+
document.getElementById('finalReport').innerHTML = formattedReport;
|
| 209 |
+
resultBox.classList.remove('hidden');
|
| 210 |
+
addLog("Workflow Finished Successfully.", "success");
|
| 211 |
+
|
| 212 |
+
stopExecution(true); // Close safely
|
| 213 |
+
}
|
| 214 |
+
};
|
| 215 |
+
|
| 216 |
+
eventSource.onerror = function() {
|
| 217 |
+
addLog("Stream connection closed or interrupted.", "warning");
|
| 218 |
+
stopExecution(false); // Force button enable
|
| 219 |
+
};
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
// --- Helper Functions ---
|
| 223 |
+
|
| 224 |
+
function highlightNode(nodeId) {
|
| 225 |
+
document.querySelectorAll('.node').forEach(n => n.classList.remove('active'));
|
| 226 |
+
document.querySelectorAll('.connector').forEach(c => c.classList.remove('active'));
|
| 227 |
+
|
| 228 |
+
const el = document.getElementById(`node-${nodeId}`);
|
| 229 |
+
if (el) {
|
| 230 |
+
el.classList.add('active');
|
| 231 |
+
if (el.previousElementSibling && el.previousElementSibling.classList.contains('connector')) {
|
| 232 |
+
el.previousElementSibling.classList.add('active');
|
| 233 |
+
}
|
| 234 |
+
}
|
| 235 |
+
}
|
| 236 |
+
|
| 237 |
+
function resetNodes() {
|
| 238 |
+
document.querySelectorAll('.node').forEach(n => n.classList.remove('active'));
|
| 239 |
+
document.querySelectorAll('.connector').forEach(c => c.classList.remove('active'));
|
| 240 |
+
}
|
| 241 |
+
|
| 242 |
+
function flashDecisionNode() {
|
| 243 |
+
const decision = document.getElementById('node-decision');
|
| 244 |
+
decision.style.borderColor = '#ef4444';
|
| 245 |
+
decision.style.boxShadow = '0 0 15px #ef4444';
|
| 246 |
+
setTimeout(() => {
|
| 247 |
+
decision.style.borderColor = '';
|
| 248 |
+
decision.style.boxShadow = '';
|
| 249 |
+
}, 500);
|
| 250 |
+
}
|
| 251 |
+
|
| 252 |
+
function updateStatus(text) {
|
| 253 |
+
const badge = document.getElementById('status-badge');
|
| 254 |
+
badge.textContent = text;
|
| 255 |
+
badge.classList.remove('text-slate-400');
|
| 256 |
+
badge.classList.add('text-blue-400', 'border-blue-500');
|
| 257 |
+
}
|
| 258 |
+
|
| 259 |
+
function addLog(msg, type) {
|
| 260 |
+
const logs = document.getElementById('logs');
|
| 261 |
+
const div = document.createElement('div');
|
| 262 |
+
div.className = 'border-l-2 pl-2 text-xs py-1 animate-fade-in';
|
| 263 |
+
|
| 264 |
+
if (type === 'error') { div.classList.add('border-red-500', 'text-red-400'); }
|
| 265 |
+
else if (type === 'success') { div.classList.add('border-green-500', 'text-green-400'); }
|
| 266 |
+
else if (type === 'warning') { div.classList.add('border-yellow-500', 'text-yellow-400'); }
|
| 267 |
+
else { div.classList.add('border-blue-500', 'text-slate-300'); }
|
| 268 |
+
|
| 269 |
+
div.innerHTML = `> ${msg}`;
|
| 270 |
+
logs.appendChild(div);
|
| 271 |
+
logs.scrollTop = logs.scrollHeight;
|
| 272 |
+
}
|
| 273 |
+
</script>
|
| 274 |
+
</body>
|
workflow_orchestrator.py
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Dict, List, Any, Optional, Callable
|
| 2 |
+
from dataclasses import dataclass, field
|
| 3 |
+
from enum import Enum
|
| 4 |
+
from datetime import datetime
|
| 5 |
+
import time
|
| 6 |
+
import cohere
|
| 7 |
+
|
| 8 |
+
# ==================== AISA: State Coordination Layer ====================
|
| 9 |
+
class WorkflowStatus(Enum):
|
| 10 |
+
PENDING = "pending"
|
| 11 |
+
IN_PROGRESS = "in_progress"
|
| 12 |
+
COMPLETED = "completed"
|
| 13 |
+
FAILED = "failed"
|
| 14 |
+
|
| 15 |
+
@dataclass
|
| 16 |
+
class WorkflowState:
|
| 17 |
+
workflow_id: str
|
| 18 |
+
task: str
|
| 19 |
+
status: WorkflowStatus
|
| 20 |
+
current_step: int = 0
|
| 21 |
+
total_steps: int = 0
|
| 22 |
+
steps_completed: List[str] = field(default_factory=list)
|
| 23 |
+
step_results: Dict[str, Any] = field(default_factory=dict)
|
| 24 |
+
execution_log: List[Dict[str, Any]] = field(default_factory=list)
|
| 25 |
+
start_time: Optional[datetime] = None
|
| 26 |
+
end_time: Optional[datetime] = None
|
| 27 |
+
|
| 28 |
+
def log_event(self, event_type: str, message: str, data: Optional[Dict] = None):
|
| 29 |
+
if len(self.execution_log) >= 500:
|
| 30 |
+
self.execution_log = self.execution_log[-400:]
|
| 31 |
+
self.execution_log.append({
|
| 32 |
+
"timestamp": datetime.now().isoformat(),
|
| 33 |
+
"event_type": event_type,
|
| 34 |
+
"message": message,
|
| 35 |
+
"data": data or {}
|
| 36 |
+
})
|
| 37 |
+
|
| 38 |
+
def get_execution_time(self) -> Optional[str]:
|
| 39 |
+
if self.start_time and self.end_time:
|
| 40 |
+
duration = self.end_time - self.start_time
|
| 41 |
+
seconds = duration.total_seconds()
|
| 42 |
+
return f"{seconds:.1f}s" if seconds < 60 else f"{seconds/60:.1f}m"
|
| 43 |
+
return None
|
| 44 |
+
|
| 45 |
+
# ==================== AISA: Cognitive Agent Layer ====================
|
| 46 |
+
class BaseAgent:
|
| 47 |
+
def __init__(self, name: str, cohere_client=None):
|
| 48 |
+
self.name = name
|
| 49 |
+
self.agent_id = f"{name}_{id(self)}"
|
| 50 |
+
self.co = cohere_client
|
| 51 |
+
|
| 52 |
+
def execute(self, input_data: Any, context: Dict[str, Any]) -> Dict[str, Any]:
|
| 53 |
+
raise NotImplementedError
|
| 54 |
+
|
| 55 |
+
class PlannerAgent(BaseAgent):
|
| 56 |
+
def execute(self, task: str, context: Dict[str, Any]) -> Dict[str, Any]:
|
| 57 |
+
prompt = f"""You are a Strategic Planner Agent. Break down this task: "{task}"
|
| 58 |
+
into 3 to 4 sequential, actionable search/analysis steps.
|
| 59 |
+
Format: Return ONLY the steps separated by newlines."""
|
| 60 |
+
|
| 61 |
+
try:
|
| 62 |
+
response = self.co.chat(message=prompt, temperature=0.3)
|
| 63 |
+
steps = [s.strip('- ').strip() for s in response.text.split('\n') if s.strip()]
|
| 64 |
+
complexity = "high" if len(steps) > 3 else "medium"
|
| 65 |
+
except:
|
| 66 |
+
# Fallback if API fails
|
| 67 |
+
steps = ["Research the topic overview", "Analyze key trends and data", "Synthesize findings"]
|
| 68 |
+
complexity = "medium"
|
| 69 |
+
|
| 70 |
+
return {
|
| 71 |
+
"steps": steps,
|
| 72 |
+
"complexity": complexity,
|
| 73 |
+
"output_type": "plan"
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
class ExecutorAgent(BaseAgent):
|
| 77 |
+
def execute(self, step: str, context: Dict[str, Any]) -> Dict[str, Any]:
|
| 78 |
+
try:
|
| 79 |
+
response = self.co.chat(
|
| 80 |
+
message=f"Perform this task in detail: {step}",
|
| 81 |
+
connectors=[{"id": "web-search"}],
|
| 82 |
+
temperature=0.3
|
| 83 |
+
)
|
| 84 |
+
|
| 85 |
+
output_text = response.text
|
| 86 |
+
has_citations = hasattr(response, 'citations') and len(response.citations) > 0
|
| 87 |
+
confidence = 0.95 if has_citations else 0.75
|
| 88 |
+
|
| 89 |
+
return {
|
| 90 |
+
"status": "success",
|
| 91 |
+
"output": output_text,
|
| 92 |
+
"confidence": confidence,
|
| 93 |
+
"citations": [c for c in response.citations] if has_citations else []
|
| 94 |
+
}
|
| 95 |
+
except Exception as e:
|
| 96 |
+
return {
|
| 97 |
+
"status": "failed",
|
| 98 |
+
"output": str(e),
|
| 99 |
+
"confidence": 0.0
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
class ValidatorAgent(BaseAgent):
|
| 103 |
+
def execute(self, result: Dict[str, Any], context: Dict[str, Any]) -> Dict[str, Any]:
|
| 104 |
+
output_len = len(result.get("output", ""))
|
| 105 |
+
base_conf = result.get("confidence", 0.5)
|
| 106 |
+
|
| 107 |
+
is_valid = output_len > 50 and base_conf > 0.6
|
| 108 |
+
|
| 109 |
+
if is_valid:
|
| 110 |
+
feedback = "Content verified successfully."
|
| 111 |
+
else:
|
| 112 |
+
feedback = "Content too short or lacks citations."
|
| 113 |
+
|
| 114 |
+
return {
|
| 115 |
+
"is_valid": is_valid,
|
| 116 |
+
"confidence": base_conf,
|
| 117 |
+
"feedback": feedback
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
# ==================== AISA: Agentic Infrastructure Layer ====================
|
| 121 |
+
class WorkflowOrchestrator:
|
| 122 |
+
def __init__(self, api_key: str):
|
| 123 |
+
self.co = cohere.Client(api_key)
|
| 124 |
+
self.agents = {
|
| 125 |
+
"planner": PlannerAgent("Planner", self.co),
|
| 126 |
+
"executor": ExecutorAgent("Executor", self.co),
|
| 127 |
+
"validator": ValidatorAgent("Validator", self.co)
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
def execute_workflow(self, task: str, event_callback: Callable[[str, Dict], None]):
|
| 131 |
+
workflow_id = f"wf_{int(time.time())}"
|
| 132 |
+
state = WorkflowState(workflow_id, task, WorkflowStatus.PENDING)
|
| 133 |
+
|
| 134 |
+
# Helper to send events to UI
|
| 135 |
+
def emit(type_, msg, role='info', node=None):
|
| 136 |
+
event_callback(type_, {"msg": msg, "role": role, "node": node})
|
| 137 |
+
|
| 138 |
+
try:
|
| 139 |
+
emit('status', 'System Initialized.', node='start')
|
| 140 |
+
state.start_time = datetime.now()
|
| 141 |
+
|
| 142 |
+
# 1. Planning
|
| 143 |
+
emit('activate', 'Analyzing Task Strategy...', node='planner')
|
| 144 |
+
plan = self.agents["planner"].execute(task, {})
|
| 145 |
+
steps = plan['steps']
|
| 146 |
+
state.total_steps = len(steps)
|
| 147 |
+
|
| 148 |
+
emit('log', f"Strategy formed with {len(steps)} phases.", role='planner')
|
| 149 |
+
time.sleep(1)
|
| 150 |
+
|
| 151 |
+
# 2. Execution Loop
|
| 152 |
+
accumulated_report = []
|
| 153 |
+
|
| 154 |
+
for i, step in enumerate(steps):
|
| 155 |
+
emit('activate', f"Executing: {step}", node='executor')
|
| 156 |
+
|
| 157 |
+
# Execution (Real Search)
|
| 158 |
+
exec_res = self.agents["executor"].execute(step, {})
|
| 159 |
+
|
| 160 |
+
if exec_res['status'] == 'failed':
|
| 161 |
+
emit('log', f"⚠️ Step failed: {exec_res['output']}", role='error')
|
| 162 |
+
continue
|
| 163 |
+
|
| 164 |
+
# Validation
|
| 165 |
+
emit('activate', 'Verifying Data Integrity...', node='validator')
|
| 166 |
+
val_res = self.agents["validator"].execute(exec_res, {})
|
| 167 |
+
|
| 168 |
+
emit('activate', 'Quality Gate Decision', node='decision')
|
| 169 |
+
time.sleep(0.5)
|
| 170 |
+
|
| 171 |
+
if val_res['is_valid']:
|
| 172 |
+
emit('log', f"✅ Phase {i+1} Verified (Confidence: {exec_res['confidence']:.0%})", role='success')
|
| 173 |
+
accumulated_report.append(f"### {step}\n{exec_res['output']}\n")
|
| 174 |
+
else:
|
| 175 |
+
emit('log', f"⚠️ Quality Warning: {val_res['feedback']}", role='warning')
|
| 176 |
+
accumulated_report.append(f"### {step}\n{exec_res['output']}\n")
|
| 177 |
+
|
| 178 |
+
# 3. Final Generation
|
| 179 |
+
emit('activate', 'Synthesizing Final Intelligence Report...', node='end')
|
| 180 |
+
|
| 181 |
+
full_context = "\n".join(accumulated_report)
|
| 182 |
+
final_prompt = f"""Based on the following research segments about '{task}', write a cohesive, professional markdown report:\n\n{full_context}"""
|
| 183 |
+
|
| 184 |
+
final_response = self.co.chat(message=final_prompt, model="command-r", temperature=0.3)
|
| 185 |
+
|
| 186 |
+
emit('finish', {'report': final_response.text})
|
| 187 |
+
state.status = WorkflowStatus.COMPLETED
|
| 188 |
+
|
| 189 |
+
except Exception as e:
|
| 190 |
+
emit('log', f"Critical System Failure: {str(e)}", role='error')
|
| 191 |
+
state.status = WorkflowStatus.FAILED
|