Mayank2027 commited on
Commit
8a0e102
·
verified ·
1 Parent(s): 44fdaa0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +125 -563
app.py CHANGED
@@ -1,8 +1,6 @@
1
  """
2
- ULTIMATE MULTI‑AGENT SYSTEM – SINGLE FILE
3
- All keys hardcoded – no Pollinations – production ready.
4
  """
5
-
6
  import os
7
  import sys
8
  import json
@@ -96,7 +94,7 @@ TEMP_DIR = "/tmp/agent_workspace"
96
  os.makedirs(TEMP_DIR, exist_ok=True)
97
 
98
  # =====================================================================
99
- # LLM CLIENT (No Pollinations)
100
  # =====================================================================
101
  class LLMClient:
102
  _groq_idx = 0
@@ -105,7 +103,6 @@ class LLMClient:
105
 
106
  @classmethod
107
  async def chat(cls, messages, temp=0.1, max_tokens=2048, response_format=None):
108
- # Try Groq
109
  try:
110
  async with cls._lock:
111
  key = GROQ_KEYS[cls._groq_idx % len(GROQ_KEYS)]
@@ -124,7 +121,6 @@ class LLMClient:
124
  except Exception as e:
125
  logger.warning(f"Groq failed: {e}")
126
 
127
- # Fallback OpenRouter
128
  try:
129
  async with cls._lock:
130
  key = OPENROUTER_KEYS[cls._or_idx % len(OPENROUTER_KEYS)]
@@ -147,7 +143,6 @@ class LLMClient:
147
  except Exception as e:
148
  logger.warning(f"OpenRouter failed: {e}")
149
 
150
- # Hugging Face (if key provided)
151
  if HF_API_KEY:
152
  try:
153
  prompt = "\n".join([f"{m['role']}: {m['content']}" for m in messages])
@@ -163,7 +158,6 @@ class LLMClient:
163
  except Exception as e:
164
  logger.error(f"HF failed: {e}")
165
 
166
- # Ultimate regex fallback
167
  return cls._regex_fallback(messages)
168
 
169
  @classmethod
@@ -178,26 +172,7 @@ class LLMClient:
178
  return json.dumps({"thought": "Fallback done", "action": {"tool": "done", "params": {"result": "No action"}}})
179
 
180
  # =====================================================================
181
- # MEMORY (Redis + ChromaDB)
182
- # =====================================================================
183
- redis_client = None
184
- try:
185
- redis_client = redis.from_url(REDIS_URL, decode_responses=True)
186
- logger.info("✅ Redis connected.")
187
- except:
188
- logger.warning("⚠️ Redis connection failed.")
189
-
190
- chroma_client = chromadb.Client()
191
- embedding_fn = embedding_functions.SentenceTransformerEmbeddingFunction(
192
- model_name="all-MiniLM-L6-v2"
193
- )
194
- memory_collection = chroma_client.get_or_create_collection(
195
- name="agent_memory",
196
- embedding_function=embedding_fn
197
- )
198
-
199
- # =====================================================================
200
- # TOOL REGISTRY (80+ tools)
201
  # =====================================================================
202
  class ToolRegistry:
203
  def __init__(self, page: Page, state: Any):
@@ -930,7 +905,6 @@ class ToolRegistry:
930
  return {"result": "Summary", "data": result[0]['summary_text']}
931
 
932
  async def translate_text(self, text: str, target_language: str = "fr"):
933
- # Placeholder – real translation would need a dedicated model
934
  return {"result": "Translation placeholder", "data": text}
935
 
936
  async def extract_entities(self, text: str):
@@ -1195,26 +1169,10 @@ class ToolRegistry:
1195
  return {"error": str(e)}
1196
 
1197
  # =====================================================================
1198
- # AGENT STATE, BASE AGENT, SPECIALIST AGENTS
1199
  # =====================================================================
1200
- class AgentState:
1201
- def __init__(self, task: str = ""):
1202
- self.task = task
1203
- self.steps = 0
1204
- self.done = False
1205
- self.last_result = ""
1206
- self.variables = {}
1207
- self.plan = []
1208
- self.action_history = []
1209
- self.created_at = datetime.now().isoformat()
1210
- self.session_id = None
1211
- self.approval_future = None
1212
-
1213
- class BaseAgent:
1214
- def __init__(self, name: str, role: str, system_prompt: str, tools: ToolRegistry, state: AgentState):
1215
- self.name = name
1216
- self.role = role
1217
- self.base_system_prompt = system_prompt
1218
  self.tools = tools
1219
  self.state = state
1220
  self.history = []
@@ -1222,8 +1180,8 @@ class BaseAgent:
1222
  self.result = None
1223
 
1224
  async def think(self, context: str) -> Dict:
1225
- prompt = self.base_system_prompt + """
1226
- Output JSON only with "thought" and "action".
1227
  Available tools: navigate, click, fill, select, scroll, press, hover, back, wait, wait_for_selector, screenshot, extract_table, get_text, execute_script, get_url, get_title, get_page_source, take_full_page_screenshot, upload_file, download_file, open_new_tab, switch_tab, close_tab, list_tabs, evaluate, wait_for_navigation, get_cookies, set_cookies, clear_cookies, get_local_storage, set_local_storage, get_session_storage, set_session_storage, read_file, write_file, append_file, read_binary_file, write_binary_file, list_dir, make_dir, delete_file, copy_file, move_file, find_files, unzip, zip, get_file_info, read_csv, write_csv, read_json, write_json, shell, shell_background, shell_with_timeout, get_env, set_env, call_api, call_api_async, call_graphql, call_rest, generate_image, edit_image, resize_image, crop_image, rotate_image, filter_image, convert_image, create_collage, execute_python, install_package, get_python_version, list_installed_packages, vector_search, vector_store, vector_delete, list_memories, clear_memories, get_memory_count, store_variable, get_variable, list_variables, delete_variable, clear_variables, analyze_csv, analyze_json, plot_data, sentiment_analysis, summarize_text, translate_text, extract_entities, keyword_extraction, send_email, send_email_with_attachment, generate_pdf, generate_pdf_from_html, extract_pdf_text, schedule_task, list_scheduled_tasks, cancel_task, set_task, done, log, ask_user, wait_for_user_input, get_time, sleep, add_tool, remove_tool, list_tools, modify_prompt, save_state, load_state, reset_state, get_system_info, apply_stealth, set_viewport, compare_images, extract_links, find_element_by_text.
1228
  """
1229
  messages = [
@@ -1247,7 +1205,6 @@ Available tools: navigate, click, fill, select, scroll, press, hover, back, wait
1247
  if tool_name == "done":
1248
  self.done = True
1249
  self.result = params.get("result", "Task completed.")
1250
- # final screenshot
1251
  try:
1252
  ss = await self.tools("screenshot")
1253
  return {"result": self.result, "screenshot": ss.get("result", "")}
@@ -1255,20 +1212,21 @@ Available tools: navigate, click, fill, select, scroll, press, hover, back, wait
1255
  return {"result": self.result}
1256
  try:
1257
  result = await self.tools(tool_name, **params)
1258
- # screenshot after action
1259
  ss = await self.tools("screenshot")
1260
  result["screenshot"] = ss.get("result", "")
1261
  return result
1262
  except Exception as e:
1263
  return {"error": str(e)}
1264
 
1265
- async def run(self, context: str) -> Dict:
 
1266
  self.history = []
1267
  self.done = False
1268
  self.result = None
1269
  steps = 0
1270
  max_steps = 20
1271
  while not self.done and steps < max_steps:
 
1272
  data = await self.think(context)
1273
  thought = data.get("thought", "")
1274
  action = data.get("action", {"tool": "done", "params": {"result": "No action"}})
@@ -1282,174 +1240,12 @@ Available tools: navigate, click, fill, select, scroll, press, hover, back, wait
1282
  steps += 1
1283
  if self.done:
1284
  break
1285
- context = f"Task: {self.state.task}\nLast action result: {result}"
1286
  return {
1287
- "agent": self.name,
1288
- "result": self.result,
1289
  "history": self.history,
1290
- "steps": len(self.history)
1291
- }
1292
-
1293
- def get_status(self):
1294
- return {
1295
- "name": self.name,
1296
- "role": self.role,
1297
- "done": self.done,
1298
  "result": self.result,
1299
- "history": self.history[-5:],
1300
  "steps": len(self.history)
1301
  }
1302
 
1303
- # Specialist agents
1304
- class ResearchAgent(BaseAgent):
1305
- def __init__(self, tools: ToolRegistry, state: AgentState):
1306
- prompt = "You are a Research Agent. Gather information from the web. Use tools step by step. Output JSON only."
1307
- super().__init__("Research", "Gather info", prompt, tools, state)
1308
-
1309
- class SalesAgent(BaseAgent):
1310
- def __init__(self, tools: ToolRegistry, state: AgentState):
1311
- prompt = "You are a Sales Agent. Create proposals, pitches, and competitor analysis. Output JSON only."
1312
- super().__init__("Sales", "Sales", prompt, tools, state)
1313
-
1314
- class SupportAgent(BaseAgent):
1315
- def __init__(self, tools: ToolRegistry, state: AgentState):
1316
- prompt = "You are a Support Agent. Answer FAQs, troubleshoot, generate documentation. Output JSON only."
1317
- super().__init__("Support", "Support", prompt, tools, state)
1318
-
1319
- class DataAgent(BaseAgent):
1320
- def __init__(self, tools: ToolRegistry, state: AgentState):
1321
- prompt = "You are a Data Agent. Analyse data, create charts, extract insights. Output JSON only."
1322
- super().__init__("Data", "Data analysis", prompt, tools, state)
1323
-
1324
- class CodeAgent(BaseAgent):
1325
- def __init__(self, tools: ToolRegistry, state: AgentState):
1326
- prompt = "You are a Code Agent. Write, review, execute Python code. Output JSON only."
1327
- super().__init__("Code", "Code", prompt, tools, state)
1328
-
1329
- class ContentAgent(BaseAgent):
1330
- def __init__(self, tools: ToolRegistry, state: AgentState):
1331
- prompt = "You are a Content Agent. Summarise, translate, create content. Output JSON only."
1332
- super().__init__("Content", "Content", prompt, tools, state)
1333
-
1334
- # =====================================================================
1335
- # SUPERVISOR AGENT
1336
- # =====================================================================
1337
- class SupervisorAgent:
1338
- def __init__(self, tools: ToolRegistry, state: AgentState):
1339
- self.tools = tools
1340
- self.state = state
1341
- self.specialists = {}
1342
- self.plan = []
1343
- self.agent_results = {}
1344
- self.done = False
1345
- self.result = None
1346
-
1347
- def register_specialist(self, name: str, agent: BaseAgent):
1348
- self.specialists[name] = agent
1349
-
1350
- async def plan_task(self, task: str) -> List[Dict]:
1351
- prompt = f"""You are the Supervisor. Decompose the user request into sub-tasks and assign to specialists.
1352
- Available agents: {list(self.specialists.keys())}
1353
- User request: {task}
1354
- Return a JSON array of steps with 'step', 'agent', 'task', 'depends_on'.
1355
- Example: [{{"step":1, "agent":"Research", "task":"Search Wikipedia for AI", "depends_on":[]}}]
1356
- """
1357
- messages = [
1358
- {"role": "system", "content": "You are a task decomposition expert. Output JSON only."},
1359
- {"role": "user", "content": prompt}
1360
- ]
1361
- raw = await LLMClient.chat(messages, temp=0.2, response_format="json")
1362
- try:
1363
- plan = json.loads(raw)
1364
- if isinstance(plan, list):
1365
- return plan
1366
- except:
1367
- pass
1368
- return [{"step": 1, "agent": "Research", "task": task, "depends_on": []}]
1369
-
1370
- async def execute_plan(self, plan: List[Dict]) -> Dict:
1371
- results = {}
1372
- for step in plan:
1373
- agent_name = step.get("agent")
1374
- sub_task = step.get("task")
1375
- if agent_name in self.specialists:
1376
- agent = self.specialists[agent_name]
1377
- context = f"Sub-task: {sub_task}\nOverall goal: {self.state.task}"
1378
- result = await agent.run(context)
1379
- results[step["step"]] = {"agent": agent_name, "task": sub_task, "result": result}
1380
- else:
1381
- results[step["step"]] = {"error": f"Unknown agent: {agent_name}"}
1382
- return results
1383
-
1384
- async def synthesize(self, task: str, results: Dict) -> str:
1385
- prompt = f"Task: {task}\nResults: {json.dumps(results, indent=2, default=str)}\nSynthesise a final answer."
1386
- messages = [
1387
- {"role": "system", "content": "Synthesis expert."},
1388
- {"role": "user", "content": prompt}
1389
- ]
1390
- solution = await LLMClient.chat(messages, temp=0.3)
1391
- return solution
1392
-
1393
- async def run(self, task: str) -> Dict:
1394
- self.state.task = task
1395
- self.plan = await self.plan_task(task)
1396
- self.agent_results = await self.execute_plan(self.plan)
1397
- self.result = await self.synthesize(task, self.agent_results)
1398
- self.done = True
1399
- return {
1400
- "plan": self.plan,
1401
- "agent_results": self.agent_results,
1402
- "solution": self.result
1403
- }
1404
-
1405
- # =====================================================================
1406
- # ORCHESTRATOR
1407
- # =====================================================================
1408
- class MultiAgentOrchestrator:
1409
- def __init__(self, page: Page, state: AgentState):
1410
- self.page = page
1411
- self.state = state
1412
- self.tools = ToolRegistry(page, state)
1413
- self.supervisor = SupervisorAgent(self.tools, state)
1414
- self.agents = {}
1415
- self.setup_agents()
1416
- self.progress = 0
1417
-
1418
- def setup_agents(self):
1419
- self.agents = {
1420
- "Research": ResearchAgent(self.tools, self.state),
1421
- "Sales": SalesAgent(self.tools, self.state),
1422
- "Support": SupportAgent(self.tools, self.state),
1423
- "Data": DataAgent(self.tools, self.state),
1424
- "Code": CodeAgent(self.tools, self.state),
1425
- "Content": ContentAgent(self.tools, self.state),
1426
- }
1427
- for name, agent in self.agents.items():
1428
- self.supervisor.register_specialist(name, agent)
1429
-
1430
- async def run(self, task: str) -> Dict:
1431
- self.state.task = task
1432
- result = await self.supervisor.run(task)
1433
- agent_status = {name: agent.get_status() for name, agent in self.agents.items()}
1434
- result["agent_status"] = agent_status
1435
- return result
1436
-
1437
- def get_status(self):
1438
- agent_status = {name: agent.get_status() for name, agent in self.agents.items()}
1439
- agent_status["Supervisor"] = {
1440
- "done": self.supervisor.done,
1441
- "result": self.supervisor.result,
1442
- "plan": self.supervisor.plan,
1443
- }
1444
- return {
1445
- "task": self.state.task,
1446
- "steps": self.state.steps,
1447
- "done": self.state.done,
1448
- "last_result": self.state.last_result,
1449
- "progress": self.progress,
1450
- "agent_status": agent_status,
1451
- }
1452
-
1453
  # =====================================================================
1454
  # FASTAPI APP
1455
  # =====================================================================
@@ -1466,7 +1262,7 @@ sessions = {}
1466
  session_status = {}
1467
  ws_connections = {}
1468
 
1469
- async def run_orchestrator(sid: str, task: str, headless: bool):
1470
  try:
1471
  playwright = await async_playwright().start()
1472
  browser = await playwright.chromium.launch(headless=headless)
@@ -1476,11 +1272,11 @@ async def run_orchestrator(sid: str, task: str, headless: bool):
1476
  # apply stealth
1477
  temp_tools = ToolRegistry(page, None)
1478
  await temp_tools.apply_stealth()
1479
- state = AgentState(task)
1480
- state.session_id = sid
1481
- orch = MultiAgentOrchestrator(page, state)
1482
  session_status[sid]["status"] = "running"
1483
- result = await orch.run(task)
1484
  session_status[sid]["status"] = "completed"
1485
  session_status[sid]["result"] = result
1486
  session_status[sid]["progress"] = 100
@@ -1488,7 +1284,7 @@ async def run_orchestrator(sid: str, task: str, headless: bool):
1488
  except Exception as e:
1489
  session_status[sid]["status"] = "error"
1490
  session_status[sid]["error"] = str(e)
1491
- logger.error(f"Orchestrator failed: {e}")
1492
 
1493
  class StartRequest(BaseModel):
1494
  task: str
@@ -1499,7 +1295,7 @@ async def start(req: StartRequest):
1499
  sid = str(uuid.uuid4())
1500
  sessions[sid] = None
1501
  session_status[sid] = {"status": "initialized", "progress": 0, "result": None, "error": None}
1502
- asyncio.create_task(run_orchestrator(sid, req.task, req.headless))
1503
  return {"session_id": sid, "status": "started"}
1504
 
1505
  @app.get("/status/{sid}")
@@ -1524,7 +1320,6 @@ async def websocket_endpoint(websocket: WebSocket, sid: str):
1524
  try:
1525
  while True:
1526
  data = await websocket.receive_json()
1527
- # Handle incoming messages (e.g., override, approval, ping)
1528
  if data.get("type") == "ping":
1529
  await websocket.send_json({"type": "pong"})
1530
  elif data.get("type") == "stop":
@@ -1536,363 +1331,130 @@ async def websocket_endpoint(websocket: WebSocket, sid: str):
1536
  ws_connections[sid].remove(websocket)
1537
 
1538
  # =====================================================================
1539
- # UI – STUNNING DASHBOARD
1540
  # =====================================================================
1541
  HTML = """
1542
  <!DOCTYPE html>
1543
- <html lang="en">
1544
  <head>
1545
- <meta charset="UTF-8">
1546
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
1547
- <title>Ultimate Multi‑Agent System</title>
1548
  <style>
1549
- * { margin:0; padding:0; box-sizing:border-box; }
1550
- body { font-family: 'Inter', system-ui, sans-serif; background: #080c14; color: #e8edf5; padding: 24px; background-image: radial-gradient(circle at 20% 30%, rgba(79,124,255,0.06) 0%, transparent 60%), radial-gradient(circle at 80% 70%, rgba(167,139,250,0.05) 0%, transparent 60%); }
1551
- .container { max-width: 1600px; margin: 0 auto; }
1552
- .header { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 16px; margin-bottom: 28px; }
1553
- .header h1 { font-size: 34px; font-weight: 800; background: linear-gradient(135deg, #4f7cff, #a78bfa); -webkit-background-clip: text; -webkit-text-fill-color: transparent; display: flex; align-items: center; gap: 12px; }
1554
- .header h1 .version { font-size: 14px; font-weight: 500; color: #8899b0; -webkit-text-fill-color: #8899b0; background: none; border: 1px solid #1a2a3a; padding: 4px 12px; border-radius: 20px; }
1555
- .badge { background: #1a2535; padding: 6px 18px; border-radius: 40px; font-size: 12px; color: #a0b4d0; border: 1px solid #2a3a50; font-weight: 500; }
1556
- .connection-status { display: flex; align-items: center; gap: 8px; font-size: 13px; color: #8899b0; }
1557
- .connection-dot { width: 8px; height: 8px; border-radius: 50%; background: #e74c3c; transition: 0.3s; }
1558
- .connection-dot.connected { background: #2ecc71; box-shadow: 0 0 8px #2ecc71; }
1559
- .sub { color: #8899b0; font-size: 15px; padding: 14px 22px; background: #111c2a; border-radius: 16px; border-left: 4px solid #4f7cff; margin-bottom: 28px; }
1560
- .row { display: flex; flex-wrap: wrap; gap: 28px; }
1561
- .left { flex: 1; min-width: 320px; }
1562
- .right { flex: 2; min-width: 500px; }
1563
- .card { background: #111c2a; border-radius: 20px; padding: 20px 24px; margin-bottom: 20px; border: 1px solid #1a2a3a; box-shadow: 0 8px 24px rgba(0,0,0,0.4); }
1564
- .card h3 { font-weight: 600; font-size: 16px; margin-bottom: 14px; color: #b0c4de; display: flex; align-items: center; gap: 8px; }
1565
- textarea, input[type="text"] { width: 100%; background: #0a1420; border: 1px solid #1a2a3a; border-radius: 14px; padding: 12px 16px; color: #e0e8f0; font-size: 15px; font-family: inherit; transition: 0.2s; resize: vertical; }
1566
- textarea:focus, input:focus { outline: none; border-color: #4f7cff; box-shadow: 0 0 0 3px rgba(79,124,255,0.15); }
1567
- .btn { background: #4f7cff; border: none; color: white; font-weight: 600; font-size: 15px; padding: 12px 28px; border-radius: 40px; cursor: pointer; transition: 0.2s; display: inline-flex; align-items: center; gap: 8px; }
1568
- .btn:hover { background: #5f8aff; transform: scale(1.02); }
1569
- .btn:disabled { opacity: 0.4; pointer-events: none; }
1570
- .btn-danger { background: #e74c3c; }
1571
- .btn-gold { background: #f1c40f; color: #000; }
1572
- .btn-success { background: #2ecc71; }
1573
- .btn-outline { background: transparent; border: 1px solid #1a2a3a; color: #8899b0; }
1574
- .flex-row { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; margin-top: 12px; }
1575
- .status { margin-top: 12px; padding: 10px 16px; border-radius: 12px; background: #0a1420; font-size: 14px; color: #8899b0; border: 1px solid #1a2a3a; }
1576
- .status.error { color: #ff6b6b; background: #2d1a1a; border-color: #ff6b6b40; }
1577
- .status.success { color: #2ecc71; background: #1a2d1a; border-color: #2ecc7140; }
1578
- .status.running { color: #f1c40f; background: #1e2d1a; border-color: #f1c40f40; }
1579
- .progress-bar { width: 100%; height: 6px; background: #1a2a3a; border-radius: 4px; margin-top: 12px; overflow: hidden; }
1580
- .progress-bar .fill { height: 100%; background: linear-gradient(90deg, #4f7cff, #a78bfa); transition: width 0.5s; }
1581
- .agent-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 16px; margin-top: 12px; }
1582
- .agent-card { background: #0a1420; border-radius: 14px; padding: 16px; border-left: 4px solid #4f7cff; transition: 0.3s; position: relative; overflow: hidden; }
1583
- .agent-card:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.3); }
1584
- .agent-card .name { font-weight: 600; font-size: 16px; color: #b0c4de; }
1585
- .agent-card .role { font-size: 12px; color: #8899b0; margin-top: 2px; }
1586
- .agent-card .thought { font-size: 13px; color: #fbbf24; margin-top: 8px; }
1587
- .agent-card .result { font-size: 12px; color: #34d399; margin-top: 4px; }
1588
- .agent-card .step-info { font-size: 12px; color: #8899b0; margin-top: 4px; }
1589
- .agent-card.done { border-left-color: #2ecc71; background: #0f2018; }
1590
- .agent-card.running { border-left-color: #f1c40f; background: #1a1f10; }
1591
- .agent-card.error { border-left-color: #e74c3c; background: #201010; }
1592
- .timeline { margin-top: 12px; max-height: 500px; overflow-y: auto; }
1593
- .timeline-item { display: flex; gap: 16px; margin-bottom: 14px; background: #0a1420; padding: 14px; border-radius: 12px; border-left: 4px solid #4f7cff; align-items: center; transition: 0.2s; }
1594
- .timeline-item:hover { background: #111f2e; }
1595
- .timeline-item .step-num { color: #8899b0; font-size: 13px; min-width: 32px; font-weight: 600; }
1596
- .timeline-item .step-screenshot { flex: 0 0 180px; cursor: pointer; }
1597
- .timeline-item .step-screenshot img { width: 100%; border-radius: 8px; border: 1px solid #1a2a3a; max-height: 120px; object-fit: cover; }
1598
- .timeline-item .step-details { flex: 1; }
1599
- .timeline-item .step-thought { color: #fbbf24; font-size: 13px; }
1600
- .timeline-item .step-result { color: #34d399; font-size: 12px; margin-top: 4px; }
1601
- .timeline-item .step-tool { color: #60a5fa; font-size: 12px; }
1602
- .modal { display: none; position: fixed; z-index: 1000; left: 0; top: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.92); justify-content: center; align-items: center; }
1603
- .modal img { max-width: 95%; max-height: 95%; border-radius: 12px; border: 1px solid #2a4055; }
1604
- .modal-close { position: absolute; top: 20px; right: 40px; color: white; font-size: 40px; cursor: pointer; }
1605
- .log-area { background: #0a1420; padding: 12px; border-radius: 12px; max-height: 200px; overflow-y: auto; font-family: monospace; font-size: 13px; color: #34d399; border: 1px solid #1a2a3a; }
1606
- .log-area .err { color: #ff6b6b; }
1607
- .log-area .info { color: #60a5fa; }
1608
- .solution-box { background: #0a1420; padding: 16px; border-radius: 12px; white-space: pre-wrap; max-height: 400px; overflow-y: auto; font-size: 14px; line-height: 1.7; border: 1px solid #1a2a3a; }
1609
- @media (max-width:800px) { body { padding: 16px; } .left, .right { min-width: 100%; } .timeline-item { flex-wrap: wrap; } .timeline-item .step-screenshot { flex: 1; } .header h1 { font-size: 26px; } }
1610
  </style>
1611
  </head>
1612
  <body>
1613
  <div class="container">
1614
- <div class="header">
1615
- <h1>🧠 Multi‑Agent System <span class="version">v12.0</span></h1>
1616
- <div style="display:flex; gap:12px; align-items:center;">
1617
- <span class="badge">⚡ Supervisor · 6 Specialists · WebSocket</span>
1618
- <div class="connection-status">
1619
- <div class="connection-dot" id="connDot"></div>
1620
- <span id="connText">Disconnected</span>
1621
- </div>
1622
- </div>
1623
- </div>
1624
- <div class="sub">🚀 The most advanced multi‑agent orchestration system with real‑time streaming, interactive plan editing, human‑in‑the‑loop approval, and live agent reasoning.</div>
1625
-
1626
- <div class="row">
1627
- <div class="left">
1628
  <div class="card">
1629
- <h3>📝 Task</h3>
1630
- <textarea id="taskInput" rows="3">Go to wikipedia and search for AI</textarea>
1631
- <div class="flex-row">
1632
- <label style="display:flex;align-items:center;gap:6px;color:#8899b0;font-size:14px;cursor:pointer;"><input type="checkbox" id="headlessCheck" checked> Headless</label>
1633
- <button class="btn btn-gold" id="runBtn">🚀 Run Multi‑Agent</button>
1634
- <button class="btn btn-success" id="continueBtn" disabled>▶ Continue</button>
1635
- <button class="btn btn-danger" id="stopBtn" disabled>⏹ Stop</button>
1636
  </div>
1637
- <div id="status" class="status">⚡ Ready</div>
1638
- <div id="progressText"></div>
1639
- <div class="progress-bar"><div class="fill" id="progressFill" style="width:0%"></div></div>
1640
  </div>
1641
- <div class="card" id="planBox" style="display:none;"><h3>📋 Plan</h3><div id="planList"></div></div>
1642
- <div class="card" id="solutionBox" style="display:none;"><h3>📊 Final Solution</h3><div class="solution-box" id="solutionContent"></div></div>
1643
- </div>
1644
-
1645
- <div class="right">
1646
- <div class="card">
1647
- <h3>🤖 Agent Team</h3>
1648
- <div id="agentGrid" class="agent-grid"><div style="color:#8899b0; grid-column:1/-1; text-align:center; padding:20px;">Waiting for task...</div></div>
1649
- </div>
1650
- <div class="card" id="timelineBox" style="display:none;"><h3>📸 Step Timeline</h3><div class="timeline" id="timelineContainer"></div></div>
1651
- <div class="card" id="logBox" style="display:none;"><h3>📟 System Log</h3><div class="log-area" id="logArea"></div></div>
1652
- </div>
1653
- </div>
1654
- </div>
1655
-
1656
- <div id="modal" class="modal" onclick="this.style.display='none'"><span class="modal-close">&times;</span><img id="modalImg" src=""></div>
1657
-
1658
  <script>
1659
- let sessionId = null, ws = null, pollInterval = null;
1660
- const statusDiv = document.getElementById('status');
1661
- const progressText = document.getElementById('progressText');
1662
- const progressFill = document.getElementById('progressFill');
1663
- const planBox = document.getElementById('planBox');
1664
- const planList = document.getElementById('planList');
1665
- const solutionBox = document.getElementById('solutionBox');
1666
- const solutionContent = document.getElementById('solutionContent');
1667
- const agentGrid = document.getElementById('agentGrid');
1668
- const logArea = document.getElementById('logArea');
1669
- const logBox = document.getElementById('logBox');
1670
- const timelineBox = document.getElementById('timelineBox');
1671
- const timelineContainer = document.getElementById('timelineContainer');
1672
- const modal = document.getElementById('modal');
1673
- const modalImg = document.getElementById('modalImg');
1674
- const connDot = document.getElementById('connDot');
1675
- const connText = document.getElementById('connText');
1676
- const runBtn = document.getElementById('runBtn');
1677
- const continueBtn = document.getElementById('continueBtn');
1678
- const stopBtn = document.getElementById('stopBtn');
1679
-
1680
- function setStatus(msg, type='info') {
1681
- statusDiv.textContent = msg;
1682
- statusDiv.className = 'status ' + type;
1683
- }
1684
- function setProgress(pct) {
1685
- const v = Math.min(100, Math.max(0, pct));
1686
- progressFill.style.width = v + '%';
1687
- progressText.textContent = `Progress: ${Math.round(v)}%`;
1688
  }
1689
- function addLog(msg, type='info') {
1690
- logBox.style.display = 'block';
1691
- const entry = document.createElement('div');
1692
- entry.className = type;
1693
- entry.textContent = `[${new Date().toLocaleTimeString()}] ${msg}`;
1694
- logArea.appendChild(entry);
1695
- logArea.scrollTop = logArea.scrollHeight;
 
 
 
 
 
1696
  }
1697
- function renderAgents(agentStatus) {
1698
- if (!agentStatus || Object.keys(agentStatus).length === 0) {
1699
- agentGrid.innerHTML = '<div style="color:#8899b0; grid-column:1/-1; text-align:center; padding:20px;">No agents active yet</div>';
1700
- return;
1701
- }
1702
- let html = '';
1703
- for (const [name, data] of Object.entries(agentStatus)) {
1704
- if (name === 'Supervisor') continue;
1705
- const statusClass = data.done ? 'done' : (data.history && data.history.length > 0 ? 'running' : 'idle');
1706
- const last = data.history && data.history.length > 0 ? data.history[data.history.length-1] : {};
1707
- const thought = last.thought || data.result || '—';
1708
- const result = last.result && last.result.result ? last.result.result : '';
1709
- const steps = data.steps || 0;
1710
- html += `
1711
- <div class="agent-card ${statusClass}">
1712
- <div class="name">${name}</div>
1713
- <div class="role">${data.role || ''}</div>
1714
- <div class="step-info">Steps: ${steps}</div>
1715
- <div class="thought">💭 ${thought.substring(0, 120)}${thought.length > 120 ? '...' : ''}</div>
1716
- ${result ? `<div class="result">✅ ${result.substring(0, 100)}${result.length > 100 ? '...' : ''}</div>` : ''}
1717
- </div>
1718
- `;
1719
- }
1720
- agentGrid.innerHTML = html || '<div style="color:#8899b0; grid-column:1/-1; text-align:center; padding:20px;">No agents active</div>';
1721
  }
1722
- function updatePlan(plan) {
1723
- if (!plan || plan.length === 0) { planBox.style.display = 'none'; return; }
1724
- planBox.style.display = 'block';
1725
- planList.innerHTML = plan.map((p, i) =>
1726
- `<div class="plan-item">${i+1}. ${p.agent}: ${p.task}</div>`
1727
- ).join('');
 
 
 
 
1728
  }
1729
- function updateSolution(solution) {
1730
- if (!solution) return;
1731
- solutionBox.style.display = 'block';
1732
- solutionContent.textContent = typeof solution === 'string' ? solution : JSON.stringify(solution, null, 2);
1733
- }
1734
- function updateTimeline(timeline) {
1735
- if (!timeline || timeline.length === 0) { timelineBox.style.display = 'none'; return; }
1736
- timelineBox.style.display = 'block';
1737
- timelineContainer.innerHTML = timeline.map((item, i) => {
1738
- const imgSrc = item.screenshot ? `data:image/png;base64,${item.screenshot}` : '';
1739
- const imgTag = imgSrc
1740
- ? `<img src="${imgSrc}" onclick="document.getElementById('modalImg').src=this.src; document.getElementById('modal').style.display='flex';" />`
1741
- : `<div style="color:#8899b0; padding:20px; background:#0a1420; border-radius:8px; text-align:center;">No screenshot</div>`;
1742
- return `
1743
- <div class="timeline-item">
1744
- <div class="step-num">#${i+1}</div>
1745
- <div class="step-screenshot">${imgTag}</div>
1746
- <div class="step-details">
1747
- <div class="step-thought">💭 ${item.thought || ''}</div>
1748
- <div class="step-tool">🔧 ${item.tool || ''}</div>
1749
- <div class="step-result">✅ ${item.result || ''}</div>
1750
- </div>
1751
- </div>
1752
- `;
1753
- }).join('');
1754
- timelineContainer.scrollTop = timelineContainer.scrollHeight;
1755
- }
1756
- function connectWebSocket() {
1757
- if (!sessionId) return;
1758
- const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
1759
- ws = new WebSocket(`${protocol}//${window.location.host}/ws/${sessionId}`);
1760
- ws.onopen = () => { connDot.className = 'connection-dot connected'; connText.textContent = 'Live'; addLog('WebSocket connected', 'success'); };
1761
- ws.onmessage = (e) => {
1762
- const data = JSON.parse(e.data);
1763
- if (data.type === 'completed') {
1764
- setStatus('✅ Completed', 'success');
1765
- setProgress(100);
1766
- stopPolling();
1767
- runBtn.disabled = false;
1768
- stopBtn.disabled = true;
1769
- if (data.result) {
1770
- updateSolution(data.result.solution);
1771
- updatePlan(data.result.plan);
1772
- renderAgents(data.result.agent_status);
1773
- }
1774
- } else if (data.type === 'error') {
1775
- setStatus('❌ Error: ' + data.error, 'error');
1776
- stopPolling();
1777
- runBtn.disabled = false;
1778
- stopBtn.disabled = true;
1779
- } else if (data.type === 'stopped') {
1780
- setStatus('⏹️ Stopped', 'info');
1781
- stopPolling();
1782
- runBtn.disabled = false;
1783
- stopBtn.disabled = true;
1784
- } else if (data.type === 'agent_status') {
1785
- renderAgents(data.agent_status);
1786
- } else if (data.type === 'progress') {
1787
- setProgress(data.progress);
1788
- } else if (data.type === 'screenshot') {
1789
- // add to timeline
1790
- }
1791
- };
1792
- ws.onclose = () => { connDot.className = 'connection-dot'; connText.textContent = 'Disconnected'; };
1793
- }
1794
- async function pollStatus() {
1795
- if (!sessionId) return;
1796
- try {
1797
- const res = await fetch(`/status/${sessionId}`);
1798
- const data = await res.json();
1799
- if (data.status === 'running') {
1800
- setStatus('🔄 Running...', 'running');
1801
- setProgress(data.progress || 0);
1802
- } else if (data.status === 'completed') {
1803
- setStatus('✅ Completed', 'success');
1804
- setProgress(100);
1805
- stopPolling();
1806
- runBtn.disabled = false;
1807
- stopBtn.disabled = true;
1808
- if (data.result) {
1809
- updateSolution(data.result.solution);
1810
- updatePlan(data.result.plan);
1811
- renderAgents(data.result.agent_status);
1812
- // build timeline from agent histories
1813
- let allScreenshots = [];
1814
- if (data.result.agent_status) {
1815
- for (const [name, agent] of Object.entries(data.result.agent_status)) {
1816
- if (name === 'Supervisor') continue;
1817
- if (agent.history) {
1818
- for (const entry of agent.history) {
1819
- if (entry.result && entry.result.screenshot) {
1820
- allScreenshots.push({
1821
- screenshot: entry.result.screenshot,
1822
- thought: entry.thought || '',
1823
- result: entry.result.result || '',
1824
- tool: entry.action ? entry.action.tool : ''
1825
- });
1826
- }
1827
- }
1828
- }
1829
- }
1830
- }
1831
- if (allScreenshots.length > 0) updateTimeline(allScreenshots);
1832
- }
1833
- } else if (data.status === 'error') {
1834
- setStatus('❌ Error: ' + data.error, 'error');
1835
- stopPolling();
1836
- runBtn.disabled = false;
1837
- stopBtn.disabled = true;
1838
- } else if (data.status === 'stopped') {
1839
- setStatus('⏹️ Stopped', 'info');
1840
- stopPolling();
1841
- runBtn.disabled = false;
1842
- stopBtn.disabled = true;
1843
- }
1844
- } catch (e) { console.error(e); }
1845
- }
1846
- function startPolling() { if (pollInterval) clearInterval(pollInterval); pollInterval = setInterval(pollStatus, 1500); }
1847
- function stopPolling() { if (pollInterval) { clearInterval(pollInterval); pollInterval = null; } }
1848
-
1849
- runBtn.addEventListener('click', async () => {
1850
- const task = document.getElementById('taskInput').value.trim();
1851
- if (!task) { setStatus('Please enter a task.', 'error'); return; }
1852
- const headless = document.getElementById('headlessCheck').checked;
1853
- runBtn.disabled = true;
1854
- stopBtn.disabled = false;
1855
- setStatus('🚀 Initializing...', 'info');
1856
- setProgress(0);
1857
- addLog('Starting multi-agent system...', 'info');
1858
- try {
1859
- const res = await fetch('/start', {
1860
- method: 'POST',
1861
- headers: { 'Content-Type': 'application/json' },
1862
- body: JSON.stringify({ task, headless })
1863
- });
1864
- const data = await res.json();
1865
- if (!res.ok) throw new Error(data.detail || 'Unknown error');
1866
- sessionId = data.session_id;
1867
- addLog('Session started: ' + sessionId, 'success');
1868
- connectWebSocket();
1869
- startPolling();
1870
- } catch (e) {
1871
- setStatus('Error: ' + e.message, 'error');
1872
- addLog('Error: ' + e.message, 'err');
1873
- runBtn.disabled = false;
1874
- stopBtn.disabled = true;
1875
- }
1876
- });
1877
-
1878
- stopBtn.addEventListener('click', async () => {
1879
- if (!sessionId) return;
1880
- if (ws && ws.readyState === WebSocket.OPEN) {
1881
- ws.send(JSON.stringify({ type: 'stop' }));
1882
- } else {
1883
- try {
1884
- const res = await fetch(`/stop/${sessionId}`, { method: 'POST' });
1885
- const data = await res.json();
1886
- setStatus('⏹️ Stopped', 'info');
1887
- } catch (e) { setStatus('Stop error: ' + e.message, 'error'); }
1888
- }
1889
- addLog('Session stopped', 'warn');
1890
- stopBtn.disabled = true;
1891
- runBtn.disabled = false;
1892
- if (ws) ws.close();
1893
- stopPolling();
1894
- });
1895
-
1896
  </script>
1897
  </body>
1898
  </html>
 
1
  """
2
+ ULTIMATE SINGLE AGENT – All Tools, WebSocket, Stunning UI
 
3
  """
 
4
  import os
5
  import sys
6
  import json
 
94
  os.makedirs(TEMP_DIR, exist_ok=True)
95
 
96
  # =====================================================================
97
+ # LLM CLIENT
98
  # =====================================================================
99
  class LLMClient:
100
  _groq_idx = 0
 
103
 
104
  @classmethod
105
  async def chat(cls, messages, temp=0.1, max_tokens=2048, response_format=None):
 
106
  try:
107
  async with cls._lock:
108
  key = GROQ_KEYS[cls._groq_idx % len(GROQ_KEYS)]
 
121
  except Exception as e:
122
  logger.warning(f"Groq failed: {e}")
123
 
 
124
  try:
125
  async with cls._lock:
126
  key = OPENROUTER_KEYS[cls._or_idx % len(OPENROUTER_KEYS)]
 
143
  except Exception as e:
144
  logger.warning(f"OpenRouter failed: {e}")
145
 
 
146
  if HF_API_KEY:
147
  try:
148
  prompt = "\n".join([f"{m['role']}: {m['content']}" for m in messages])
 
158
  except Exception as e:
159
  logger.error(f"HF failed: {e}")
160
 
 
161
  return cls._regex_fallback(messages)
162
 
163
  @classmethod
 
172
  return json.dumps({"thought": "Fallback done", "action": {"tool": "done", "params": {"result": "No action"}}})
173
 
174
  # =====================================================================
175
+ # TOOL REGISTRY
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
  # =====================================================================
177
  class ToolRegistry:
178
  def __init__(self, page: Page, state: Any):
 
905
  return {"result": "Summary", "data": result[0]['summary_text']}
906
 
907
  async def translate_text(self, text: str, target_language: str = "fr"):
 
908
  return {"result": "Translation placeholder", "data": text}
909
 
910
  async def extract_entities(self, text: str):
 
1169
  return {"error": str(e)}
1170
 
1171
  # =====================================================================
1172
+ # AGENT
1173
  # =====================================================================
1174
+ class Agent:
1175
+ def __init__(self, tools: ToolRegistry, state: Any):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1176
  self.tools = tools
1177
  self.state = state
1178
  self.history = []
 
1180
  self.result = None
1181
 
1182
  async def think(self, context: str) -> Dict:
1183
+ prompt = """
1184
+ You are a web automation agent. Output JSON only with "thought" and "action".
1185
  Available tools: navigate, click, fill, select, scroll, press, hover, back, wait, wait_for_selector, screenshot, extract_table, get_text, execute_script, get_url, get_title, get_page_source, take_full_page_screenshot, upload_file, download_file, open_new_tab, switch_tab, close_tab, list_tabs, evaluate, wait_for_navigation, get_cookies, set_cookies, clear_cookies, get_local_storage, set_local_storage, get_session_storage, set_session_storage, read_file, write_file, append_file, read_binary_file, write_binary_file, list_dir, make_dir, delete_file, copy_file, move_file, find_files, unzip, zip, get_file_info, read_csv, write_csv, read_json, write_json, shell, shell_background, shell_with_timeout, get_env, set_env, call_api, call_api_async, call_graphql, call_rest, generate_image, edit_image, resize_image, crop_image, rotate_image, filter_image, convert_image, create_collage, execute_python, install_package, get_python_version, list_installed_packages, vector_search, vector_store, vector_delete, list_memories, clear_memories, get_memory_count, store_variable, get_variable, list_variables, delete_variable, clear_variables, analyze_csv, analyze_json, plot_data, sentiment_analysis, summarize_text, translate_text, extract_entities, keyword_extraction, send_email, send_email_with_attachment, generate_pdf, generate_pdf_from_html, extract_pdf_text, schedule_task, list_scheduled_tasks, cancel_task, set_task, done, log, ask_user, wait_for_user_input, get_time, sleep, add_tool, remove_tool, list_tools, modify_prompt, save_state, load_state, reset_state, get_system_info, apply_stealth, set_viewport, compare_images, extract_links, find_element_by_text.
1186
  """
1187
  messages = [
 
1205
  if tool_name == "done":
1206
  self.done = True
1207
  self.result = params.get("result", "Task completed.")
 
1208
  try:
1209
  ss = await self.tools("screenshot")
1210
  return {"result": self.result, "screenshot": ss.get("result", "")}
 
1212
  return {"result": self.result}
1213
  try:
1214
  result = await self.tools(tool_name, **params)
 
1215
  ss = await self.tools("screenshot")
1216
  result["screenshot"] = ss.get("result", "")
1217
  return result
1218
  except Exception as e:
1219
  return {"error": str(e)}
1220
 
1221
+ async def run(self, task: str) -> Dict:
1222
+ self.state.task = task
1223
  self.history = []
1224
  self.done = False
1225
  self.result = None
1226
  steps = 0
1227
  max_steps = 20
1228
  while not self.done and steps < max_steps:
1229
+ context = f"Task: {task}\nHistory: {self.history[-1]['result'] if self.history else 'None'}"
1230
  data = await self.think(context)
1231
  thought = data.get("thought", "")
1232
  action = data.get("action", {"tool": "done", "params": {"result": "No action"}})
 
1240
  steps += 1
1241
  if self.done:
1242
  break
 
1243
  return {
 
 
1244
  "history": self.history,
 
 
 
 
 
 
 
 
1245
  "result": self.result,
 
1246
  "steps": len(self.history)
1247
  }
1248
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1249
  # =====================================================================
1250
  # FASTAPI APP
1251
  # =====================================================================
 
1262
  session_status = {}
1263
  ws_connections = {}
1264
 
1265
+ async def run_agent(sid: str, task: str, headless: bool):
1266
  try:
1267
  playwright = await async_playwright().start()
1268
  browser = await playwright.chromium.launch(headless=headless)
 
1272
  # apply stealth
1273
  temp_tools = ToolRegistry(page, None)
1274
  await temp_tools.apply_stealth()
1275
+ state = type('State', (), {'variables': {}, 'done': False, 'result': None, 'task': task})()
1276
+ tools = ToolRegistry(page, state)
1277
+ agent = Agent(tools, state)
1278
  session_status[sid]["status"] = "running"
1279
+ result = await agent.run(task)
1280
  session_status[sid]["status"] = "completed"
1281
  session_status[sid]["result"] = result
1282
  session_status[sid]["progress"] = 100
 
1284
  except Exception as e:
1285
  session_status[sid]["status"] = "error"
1286
  session_status[sid]["error"] = str(e)
1287
+ logger.error(f"Agent failed: {e}")
1288
 
1289
  class StartRequest(BaseModel):
1290
  task: str
 
1295
  sid = str(uuid.uuid4())
1296
  sessions[sid] = None
1297
  session_status[sid] = {"status": "initialized", "progress": 0, "result": None, "error": None}
1298
+ asyncio.create_task(run_agent(sid, req.task, req.headless))
1299
  return {"session_id": sid, "status": "started"}
1300
 
1301
  @app.get("/status/{sid}")
 
1320
  try:
1321
  while True:
1322
  data = await websocket.receive_json()
 
1323
  if data.get("type") == "ping":
1324
  await websocket.send_json({"type": "pong"})
1325
  elif data.get("type") == "stop":
 
1331
  ws_connections[sid].remove(websocket)
1332
 
1333
  # =====================================================================
1334
+ # UI – Compact but Beautiful
1335
  # =====================================================================
1336
  HTML = """
1337
  <!DOCTYPE html>
1338
+ <html>
1339
  <head>
1340
+ <title>Ultimate Agent</title>
 
 
1341
  <style>
1342
+ *{margin:0;padding:0;box-sizing:border-box}
1343
+ body{background:#0b0e14;color:#e8edf5;font-family:'Inter',system-ui,sans-serif;padding:20px}
1344
+ .container{max-width:1200px;margin:auto;background:#141a24;border-radius:20px;padding:30px}
1345
+ .card{background:#1a212d;border-radius:16px;padding:20px;margin-bottom:20px;border:1px solid #2a3340}
1346
+ .btn{background:#4f7cff;border:none;color:#fff;padding:10px 20px;border-radius:30px;cursor:pointer}
1347
+ .btn:disabled{opacity:.5}
1348
+ .btn-danger{background:#e74c3c}
1349
+ .btn-gold{background:#f1c40f;color:#000}
1350
+ textarea{width:100%;background:#0f141d;border:1px solid #2a3340;border-radius:12px;padding:12px;color:#fff}
1351
+ .progress-bar{width:100%;height:6px;background:#2a3340;border-radius:4px;margin:10px 0;overflow:hidden}
1352
+ .progress-bar .fill{height:100%;background:linear-gradient(90deg,#4f7cff,#a78bfa);transition:width .5s}
1353
+ .timeline-item{display:flex;gap:16px;background:#0f141d;padding:14px;border-radius:12px;margin-bottom:14px;border-left:4px solid #4f7cff}
1354
+ .timeline-item img{max-width:180px;border-radius:8px;cursor:pointer}
1355
+ .agent-card{background:#0f141d;padding:14px;border-radius:12px;border-left:4px solid #4f7cff}
1356
+ .modal{display:none;position:fixed;inset:0;background:rgba(0,0,0,.9);justify-content:center;align-items:center;z-index:1000}
1357
+ .modal img{max-width:95%;max-height:95%;border-radius:12px}
1358
+ .modal-close{position:absolute;top:20px;right:40px;color:#fff;font-size:40px;cursor:pointer}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1359
  </style>
1360
  </head>
1361
  <body>
1362
  <div class="container">
1363
+ <h1>🤖 Ultimate Agent</h1>
 
 
 
 
 
 
 
 
 
 
 
 
 
1364
  <div class="card">
1365
+ <textarea id="task" rows="2">Go to wikipedia and search for AI</textarea>
1366
+ <button class="btn btn-gold" id="run">🚀 Run</button>
1367
+ <button class="btn btn-danger" id="stop" disabled>⏹ Stop</button>
1368
+ <label><input type="checkbox" id="headless" checked> Headless</label>
1369
+ <div id="status" class="status">Ready</div>
1370
+ <div class="progress-bar"><div class="fill" id="progress" style="width:0%"></div></div>
1371
+ <div id="progresstext"></div>
1372
  </div>
1373
+ <div class="card" id="resultbox" style="display:none"><h3>✅ Result</h3><pre id="resultcontent"></pre></div>
1374
+ <div class="card" id="agentcard" style="display:none"><h3>🤖 Agent</h3><div class="agent-card"><div><strong>Thought:</strong> <span id="thought">—</span></div><div><strong>Action:</strong> <span id="action">—</span></div><div><strong>Result:</strong> <span id="result">—</span></div><div><strong>Steps:</strong> <span id="steps">0</span></div></div></div>
1375
+ <div class="card" id="timelinebox" style="display:none"><h3>📸 Timeline</h3><div id="timeline"></div></div>
1376
  </div>
1377
+ <div class="modal" onclick="this.style.display='none'"><span class="modal-close">&times;</span><img id="modalimg" src=""></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1378
  <script>
1379
+ let sid=null, interval=null, ws=null;
1380
+ const runBtn=document.getElementById('run'), stopBtn=document.getElementById('stop');
1381
+ const statusDiv=document.getElementById('status'), progress=document.getElementById('progress'), progresstext=document.getElementById('progresstext');
1382
+ const resultbox=document.getElementById('resultbox'), resultcontent=document.getElementById('resultcontent');
1383
+ const agentcard=document.getElementById('agentcard'), thought=document.getElementById('thought'), action=document.getElementById('action'), result=document.getElementById('result'), steps=document.getElementById('steps');
1384
+ const timeline=document.getElementById('timeline'), timelinebox=document.getElementById('timelinebox');
1385
+ const modal=document.getElementById('modal'), modalimg=document.getElementById('modalimg');
1386
+
1387
+ function setStatus(msg,type='info'){statusDiv.textContent=msg;statusDiv.className='status '+type}
1388
+ function setProgress(p){progress.style.width=Math.min(100,p)+'%';progresstext.textContent=`Progress: ${Math.round(p)}%`}
1389
+ function addTimeline(history){
1390
+ if(!history||!history.length) return;
1391
+ timelinebox.style.display='block';
1392
+ timeline.innerHTML=history.map((item,i)=>{
1393
+ const img=item.result?.screenshot ? `<img src="data:image/png;base64,${item.result.screenshot}" onclick="document.getElementById('modalimg').src=this.src;document.getElementById('modal').style.display='flex'">` : '<div style="color:#8899b0;">No screenshot</div>';
1394
+ return `<div class="timeline-item"><div>${img}</div><div><div>💭 ${item.thought||''}</div><div>🔧 ${item.action?.tool||''}</div><div>✅ ${item.result?.result||item.result?.error||''}</div></div></div>`;
1395
+ }).join('');
 
 
 
 
 
 
 
 
 
 
 
 
1396
  }
1397
+ function connectWS(){
1398
+ if(!sid) return;
1399
+ const protocol=location.protocol==='https:'?'wss:':'ws:';
1400
+ ws=new WebSocket(`${protocol}//${location.host}/ws/${sid}`);
1401
+ ws.onmessage=(e)=>{
1402
+ const data=JSON.parse(e.data);
1403
+ if(data.type==='progress') setProgress(data.progress);
1404
+ else if(data.type==='completed'){setStatus('✅ Completed','success');setProgress(100);stopPolling();runBtn.disabled=false;stopBtn.disabled=true;if(data.result) updateResult(data.result);}
1405
+ else if(data.type==='error'){setStatus('❌ Error: '+data.error,'error');stopPolling();runBtn.disabled=false;stopBtn.disabled=true;}
1406
+ else if(data.type==='stopped'){setStatus('⏹️ Stopped','info');stopPolling();runBtn.disabled=false;stopBtn.disabled=true;}
1407
+ };
1408
+ ws.onclose=()=>{ ws=null; };
1409
  }
1410
+ function updateResult(res){
1411
+ resultbox.style.display='block';
1412
+ resultcontent.textContent=res.result||JSON.stringify(res,null,2);
1413
+ if(res.history){
1414
+ const last=res.history[res.history.length-1];
1415
+ agentcard.style.display='block';
1416
+ thought.textContent=last.thought||'—';
1417
+ action.textContent=last.action?.tool||'—';
1418
+ result.textContent=last.result?.result||last.result?.error||'—';
1419
+ steps.textContent=res.steps||0;
1420
+ addTimeline(res.history);
1421
+ }
 
 
 
 
 
 
 
 
 
 
 
 
1422
  }
1423
+ async function poll(){
1424
+ if(!sid) return;
1425
+ try{
1426
+ const res=await fetch(`/status/${sid}`);
1427
+ const data=await res.json();
1428
+ if(data.status==='running'){setStatus('🔄 Running...','running');setProgress(data.progress||0);}
1429
+ else if(data.status==='completed'){setStatus('✅ Completed','success');setProgress(100);stopPolling();runBtn.disabled=false;stopBtn.disabled=true;if(data.result) updateResult(data.result);}
1430
+ else if(data.status==='error'){setStatus('❌ Error: '+data.error,'error');stopPolling();runBtn.disabled=false;stopBtn.disabled=true;}
1431
+ else if(data.status==='stopped'){setStatus('⏹️ Stopped','info');stopPolling();runBtn.disabled=false;stopBtn.disabled=true;}
1432
+ }catch(e){console.error(e)}
1433
  }
1434
+ function startPolling(){if(interval)clearInterval(interval);interval=setInterval(poll,1500);}
1435
+ function stopPolling(){if(interval){clearInterval(interval);interval=null;}}
1436
+ runBtn.onclick=async ()=>{
1437
+ const task=document.getElementById('task').value.trim();
1438
+ if(!task){setStatus('Please enter a task.','error');return;}
1439
+ const headless=document.getElementById('headless').checked;
1440
+ runBtn.disabled=true;stopBtn.disabled=false;
1441
+ setStatus('🚀 Starting...','info');setProgress(0);
1442
+ resultbox.style.display='none';agentcard.style.display='none';timelinebox.style.display='none';
1443
+ try{
1444
+ const res=await fetch('/start',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({task,headless})});
1445
+ const data=await res.json();
1446
+ if(!res.ok) throw new Error(data.detail||'Error');
1447
+ sid=data.session_id;
1448
+ connectWS();
1449
+ startPolling();
1450
+ }catch(e){setStatus('Error: '+e.message,'error');runBtn.disabled=false;stopBtn.disabled=true;}
1451
+ };
1452
+ stopBtn.onclick=async ()=>{
1453
+ if(!sid) return;
1454
+ if(ws && ws.readyState===WebSocket.OPEN){ws.send(JSON.stringify({type:'stop'}));}
1455
+ else{try{await fetch(`/stop/${sid}`,{method:'POST'});setStatus('⏹️ Stopping...','info');}catch(e){setStatus('Stop error: '+e.message,'error');}}
1456
+ stopPolling();runBtn.disabled=false;stopBtn.disabled=true;
1457
+ };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1458
  </script>
1459
  </body>
1460
  </html>