Spaces:
Paused
Paused
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,8 +1,6 @@
|
|
| 1 |
"""
|
| 2 |
-
ULTIMATE
|
| 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
|
| 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 |
-
#
|
| 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
|
| 1199 |
# =====================================================================
|
| 1200 |
-
class
|
| 1201 |
-
def __init__(self,
|
| 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 =
|
| 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,
|
|
|
|
| 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
|
| 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 =
|
| 1480 |
-
|
| 1481 |
-
|
| 1482 |
session_status[sid]["status"] = "running"
|
| 1483 |
-
result = await
|
| 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"
|
| 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(
|
| 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 –
|
| 1540 |
# =====================================================================
|
| 1541 |
HTML = """
|
| 1542 |
<!DOCTYPE html>
|
| 1543 |
-
<html
|
| 1544 |
<head>
|
| 1545 |
-
<
|
| 1546 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 1547 |
-
<title>Ultimate Multi‑Agent System</title>
|
| 1548 |
<style>
|
| 1549 |
-
*
|
| 1550 |
-
body
|
| 1551 |
-
.container
|
| 1552 |
-
.
|
| 1553 |
-
.
|
| 1554 |
-
.
|
| 1555 |
-
.
|
| 1556 |
-
.
|
| 1557 |
-
|
| 1558 |
-
.
|
| 1559 |
-
.
|
| 1560 |
-
.
|
| 1561 |
-
.
|
| 1562 |
-
.
|
| 1563 |
-
.
|
| 1564 |
-
.
|
| 1565 |
-
|
| 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 |
-
<
|
| 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 |
-
<
|
| 1630 |
-
<
|
| 1631 |
-
<
|
| 1632 |
-
<label
|
| 1633 |
-
<
|
| 1634 |
-
<
|
| 1635 |
-
<
|
| 1636 |
</div>
|
| 1637 |
-
<div id="
|
| 1638 |
-
<div id="
|
| 1639 |
-
<div class="
|
| 1640 |
</div>
|
| 1641 |
-
<div class="
|
| 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">×</span><img id="modalImg" src=""></div>
|
| 1657 |
-
|
| 1658 |
<script>
|
| 1659 |
-
let
|
| 1660 |
-
const
|
| 1661 |
-
const
|
| 1662 |
-
const
|
| 1663 |
-
const
|
| 1664 |
-
const
|
| 1665 |
-
const
|
| 1666 |
-
|
| 1667 |
-
|
| 1668 |
-
|
| 1669 |
-
|
| 1670 |
-
|
| 1671 |
-
|
| 1672 |
-
|
| 1673 |
-
const
|
| 1674 |
-
|
| 1675 |
-
|
| 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
|
| 1690 |
-
|
| 1691 |
-
|
| 1692 |
-
|
| 1693 |
-
|
| 1694 |
-
|
| 1695 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1696 |
}
|
| 1697 |
-
function
|
| 1698 |
-
|
| 1699 |
-
|
| 1700 |
-
|
| 1701 |
-
|
| 1702 |
-
|
| 1703 |
-
|
| 1704 |
-
|
| 1705 |
-
|
| 1706 |
-
|
| 1707 |
-
|
| 1708 |
-
|
| 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
|
| 1723 |
-
|
| 1724 |
-
|
| 1725 |
-
|
| 1726 |
-
|
| 1727 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1728 |
}
|
| 1729 |
-
function
|
| 1730 |
-
|
| 1731 |
-
|
| 1732 |
-
|
| 1733 |
-
}
|
| 1734 |
-
|
| 1735 |
-
|
| 1736 |
-
|
| 1737 |
-
|
| 1738 |
-
|
| 1739 |
-
|
| 1740 |
-
|
| 1741 |
-
|
| 1742 |
-
|
| 1743 |
-
|
| 1744 |
-
|
| 1745 |
-
|
| 1746 |
-
|
| 1747 |
-
|
| 1748 |
-
|
| 1749 |
-
|
| 1750 |
-
|
| 1751 |
-
|
| 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">×</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>
|