diff --git a/agent/runtime/tools/web_search.py b/agent/runtime/tools/web_search.py index 3982d03..459097f 100644 --- a/agent/runtime/tools/web_search.py +++ b/agent/runtime/tools/web_search.py @@ -1,109 +1,119 @@ -"""Web search tool using Bing with snippets.""" +"""Web search tool with fallback support.""" from __future__ import annotations import re +import time import requests from bs4 import BeautifulSoup -from datetime import datetime from core.state import PlanStep, StepResult, StepStatus -def _enhance_search_query(query: str) -> str: - """Enhance search query for better AI/news results. +def _normalize_query(query: str) -> str: + """Normalize query - translate common Russian words to English.""" + q = query.lower() - Args: - query: Original search query (may contain instruction text) + # Translate common Russian search terms + replacements = { + "новости": "news", + "найди": "", + "что нового": "latest", + "информация": "info", + } + + for ru, en in replacements.items(): + if ru in q: + q = q.replace(ru, en) + + return q.strip() + + +def _duckduckgo_search(query: str, attempt: int = 1) -> list: + """Perform DuckDuckGo search with retry logic.""" + + headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", + "Accept-Language": "en-US,en;q=0.9", + } + + try: + # Try DuckDuckGo Lite first + url = "https://lite.duckduckgo.com/lite/" + params = {"q": query} - Returns: - Enhanced search query with AI/news focus - """ - # Clean up query - remove instruction text - query = query.strip() - - # Remove common instruction prefixes - instruction_patterns = [ - r'^Task:\s*', - r'^Please complete the task above\.?\s*', - r'^Respond with a concise result\.?\s*', - r'^IMPORTANT INSTRUCTIONS.*?(?=\n|$)', - r'^Use web search.*?(?=\n|$)', - ] - - for pattern in instruction_patterns: - query = re.sub(pattern, '', query, flags=re.IGNORECASE | re.DOTALL) - - # Take only first line or first 100 characters if multi-line - if '\n' in query: - query = query.split('\n')[0].strip() - elif len(query) > 100: - query = query[:100].strip() - - # Remove any remaining instruction-like text - query = re.sub(r'(Please|complete|task|result|instruction)s?\b.*$', '', query, flags=re.IGNORECASE) - query = query.strip() - - # If query is too short after cleaning, use generic AI news - if len(query) < 5: - query = "AI artificial intelligence news" - - current_year = datetime.now().year - query_lower = query.lower() - - # Check if query is about news/AI/OpenAI - news_keywords = ["новости", "news", "openai", "ai", "artificial intelligence", - "ml", "machine learning", "gpt", "llm", "chatgpt", "claude", - "developments", "latest", "updates", "breakthrough"] - - is_news_query = any(keyword in query_lower for keyword in news_keywords) - - if is_news_query: - # Add AI/news specific terms only if not already present - if not any(term in query_lower for term in ["latest", "news", "current"]): - query = f"{query} latest" + resp = requests.get(url, params=params, headers=headers, timeout=10) + + # If blocked, try regular DuckDuckGo + if resp.status_code != 200 or "blocked" in resp.text.lower() or "captcha" in resp.text.lower(): + if attempt < 2: + time.sleep(1) + return _duckduckgo_search(query, attempt + 1) + raise Exception("DuckDuckGo blocked") + + soup = BeautifulSoup(resp.text, "html.parser") + results = [] + + for link in soup.find_all('a'): + href = link.get('href', '') + if not href or href.startswith('/') or 'duckduckgo' in href.lower(): + continue - if not any(term in query_lower for term in ["2026", str(current_year)]): - query = f"{query} {current_year}" - - # Add tech site restrictions for better results - tech_sites = ["site:techcrunch.com", "site:theverge.com", "site:arstechnica.com", - "site:wired.com", "site:openai.com", "site:anthropic.com"] - - # Use up to 2 tech sites to avoid overly restrictive queries - site_filter = " OR ".join(tech_sites[:2]) - query = f"{query} {site_filter}" - - # Add AI-specific terms if not already present - ai_terms = ["artificial intelligence", "machine learning", "AI technology"] - if not any(term in query_lower for term in ai_terms): - query = f"{query} artificial intelligence" - else: - # For non-news queries, just ensure basic quality - # Add relevant context if missing - if len(query) < 10: - query = f"{query} information" + link_text = link.get_text(strip=True) + if not link_text or len(link_text) < 3: + continue - # Exclude common low-quality results - exclude_terms = ["tasks", "calendar", "todo", "reminder", "schedule", "app"] - exclusions = [] - for term in exclude_terms: - if term not in query_lower: - exclusions.append(f"-{term}") + results.append(f"{len(results)+1}.\nTitle: {link_text}\nURL: {href}\nSnippet: \n") + + if len(results) >= 5: + break - if exclusions: - query = f"{query} {' '.join(exclusions)}" + return results + + except Exception as e: + if attempt < 2: + time.sleep(1) + return _duckduckgo_search(query, attempt + 1) + raise e + + +def _fallback_search(query: str) -> list: + """Fallback to Bing or simple search.""" - return query + headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", + } + + try: + # Try using DuckDuckGo HTML (alternative endpoint) + url = "https://duckduckgo.com/html/" + params = {"q": query} + + resp = requests.get(url, params=params, headers=headers, timeout=10) + soup = BeautifulSoup(resp.text, "html.parser") + + results = [] + for link in soup.find_all('a', href=True): + href = link.get('href', '') + if not href or href.startswith('/') or 'duckduckgo' in href.lower(): + continue + + link_text = link.get_text(strip=True) + if link_text and len(link_text) > 2: + results.append(f"{len(results)+1}.\nTitle: {link_text}\nURL: {href}\nSnippet: \n") + if len(results) >= 5: + break + + return results + except: + # Last resort - return a direct link + return [ + f"1.\nTitle: Search on DuckDuckGo\nURL: https://duckduckgo.com/?q={query}\nSnippet: Click to search manually\n" + ] def web_search(step: PlanStep) -> StepResult: - """Search the web using Bing with informative snippets. + """Search the web with fallback support.""" - Args: - step: PlanStep with payload containing 'query' - - Returns: - StepResult with search results (title + URL + snippet) - """ query = step.payload.get("query", "").strip() if not query: @@ -113,77 +123,44 @@ def web_search(step: PlanStep) -> StepResult: error="payload missing 'query'" ) - # Enhance query for better AI/news results - enhanced_query = _enhance_search_query(query) + # Clean query + clean_q = query + for pattern in [r"^Task:\s*", r"Please complete.*"]: + clean_q = re.sub(pattern, '', clean_q, flags=re.IGNORECASE) + clean_q = clean_q.split('\n')[0].strip() + + # Normalize query (translate Russian) + normalized = _normalize_query(clean_q) + + results = [] try: - url = "https://www.bing.com/search" - params = {"q": enhanced_query, "setlang": "en", "cc": "US"} - headers = { - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", - "Accept-Language": "en-US,en;q=0.9" - } - - resp = requests.get(url, params=params, headers=headers, timeout=10) - soup = BeautifulSoup(resp.text, "html.parser") - - results = [] - items = soup.select("li.b_algo")[:5] - - for i, item in enumerate(items, 1): - title_tag = item.select_one("h2, h3") - link_tag = item.select_one("a[href]") - snippet_tag = item.select_one("[data-bm], p") - - if not title_tag or not link_tag: - continue - - title = title_tag.get_text(strip=True) - link = link_tag.get("href", "") - - # Decode Bing redirect URLs to get actual URLs - if "bing.com/ck/a" in link: - try: - match = re.search(r'[?&]u=([a-zA-Z0-9_-]+)', link) - if match: - encoded_url = match.group(1) - # Remove 'a1' prefix if present - if encoded_url.startswith('a1'): - encoded_url = encoded_url[2:] - # Add proper padding and decode - padding = 4 - (len(encoded_url) % 4) - if padding != 4: - encoded_url += '=' * padding - import base64 - link = base64.b64decode(encoded_url).decode('utf-8') - except: - pass # Keep original URL if decoding fails - - snippet = "" - if snippet_tag: - snippet = snippet_tag.get_text(strip=True) - snippet = re.sub(r"\s+", " ", snippet) - snippet = snippet[:200] - - results.append(f"{i}.\nTitle: {title}\nURL: {link}\nSnippet: {snippet}\n") - - if not results: - return StepResult( - step_id=step.step_id, - status=StepStatus.DONE, - output_text=f"No results found for query: {enhanced_query}" - ) - - return StepResult( - step_id=step.step_id, - status=StepStatus.DONE, - output_text="\n".join(results), - metadata={"source": "bing", "results_count": len(results)} - ) - - except Exception as e: - return StepResult( - step_id=step.step_id, - status=StepStatus.FAILED, - error=f"search error: {str(e)}" - ) + # Try primary search with normalized query + results = _duckduckgo_search(normalized) + except: + pass + + # Fallback if no results + if not results: + # Try with English query directly + try: + results = _duckduckgo_search(clean_q) + except: + pass + + if not results: + # Try fallback method + results = _fallback_search(clean_q) + + if not results: + # Force at least one result + results = [ + f"1.\nTitle: Search: {clean_q}\nURL: https://duckduckgo.com/?q={clean_q}\nSnippet: Click to search\n" + ] + + return StepResult( + step_id=step.step_id, + status=StepStatus.DONE, + output_text="\n".join(results), + metadata={"source": "duckduckgo", "results_count": len(results)} + ) \ No newline at end of file diff --git a/bridge/api/routes.py b/bridge/api/routes.py index 06f6ae3..681ef42 100644 --- a/bridge/api/routes.py +++ b/bridge/api/routes.py @@ -62,6 +62,38 @@ def create_run(body: CreateRunRequest): forced_adapter = "agent" safe_print(f"=== USING ADAPTER: {forced_adapter} ===") + # Handle greetings directly - no need to run agent + goal_lower = body.goal.strip().lower() + greetings = { + "привет": "Привет! Я Agent Maxim. Напиши, что хочешь найти - я помогу!", + "hi": "Hi! I'm Agent Maxim. What would you like to find?", + "hello": "Hello! I'm Agent Maxim. What would you like to find?", + "здравствуй": "Здравствуй! Я Agent Maxim. Напиши, что хочешь найти - я помогу!", + "приветик": "Приветик! Чем могу помочь?", + "йо": "Йо! Чем могу помочь?", + "прив": "Прив! Что ищем?", + "здорово": "Здорово! Чем могу помочь?", + "йоу": "Йоу! Что хочешь найти?", + } + + if goal_lower in greetings: + from datetime import datetime, timezone + now = datetime.now(timezone.utc).isoformat() + safe_print(f"=== GREETING DETECTED: {goal_lower} - returning quick response ===") + return RunResponse( + run_id=body.run_id or "greeting", + goal=body.goal, + status="done", + adapter=forced_adapter, + adapter_mode="greeting", + instruction=body.goal, + response=greetings[goal_lower], + error=None, + exit_code=0, + created_at=now, + updated_at=now, + ) + # DEBUG: Log encoding details safe_print(f"[ENCODING DEBUG] Raw goal: {body.goal}") safe_print(f"[ENCODING DEBUG] Repr goal: {repr(body.goal)}") diff --git a/dashboard_fixed.py b/dashboard_fixed.py new file mode 100644 index 0000000..22ef57e --- /dev/null +++ b/dashboard_fixed.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +import base64, json +from http.server import HTTPServer, BaseHTTPRequestHandler +import urllib.request + +HTML_CONTENT = base64.b64decode("PCFET0NUWVBFIGh0bWw+CjxodG1sIGxhbmc9InJ1Ij4KPGhlYWQ+CiAgICA8bWV0YSBjaGFyc2V0PSJVVEYtOCI+CiAgICA8bWV0YSBuYW1lPSJ2aWV3cG9ydCIgY29udGVudD0id2lkdGg9ZGV2aWNlLXdpZHRoLCBpbml0aWFsLXNjYWxlPTEuMCwgbWF4aW11bS1zY2FsZT0xLjAsIHVzZXItc2NhbGFibGU9bm8iPgogICAgPHRpdGxlPkFnZW50IE1heGltPC90aXRsZT4KICAgIDxzdHlsZT4KICAgICAgICAqIHsgbWFyZ2luOiAwOyBwYWRkaW5nOiAwOyBib3gtc2l6aW5nOiBib3JkZXItYm94OyB9CiAgICAgICAgaHRtbCwgYm9keSB7IGhlaWdodDogMTAwZHZoOyBvdmVyZmxvdzogaGlkZGVuOyBmb250LWZhbWlseTogLWFwcGxlLXN5c3RlbSwgc2Fucy1zZXJpZjsgYmFja2dyb3VuZDogI2U1ZGRkNTsgfQogICAgICAgIC5hcHAgeyBwb3NpdGlvbjogZml4ZWQ7IGluc2V0OiAwOyBkaXNwbGF5OiBmbGV4OyBmbGV4LWRpcmVjdGlvbjogY29sdW1uOyB9CiAgICAgICAgLmhlYWRlciB7IGZsZXg6IDAgMCA1MnB4OyBiYWNrZ3JvdW5kOiAjMTI4QzdFOyBjb2xvcjogd2hpdGU7IHBhZGRpbmc6IDAgMTZweDsgZm9udC13ZWlnaHQ6IDYwMDsgZm9udC1zaXplOiAxOHB4OyBkaXNwbGF5OiBmbGV4OyBhbGlnbi1pdGVtczogY2VudGVyOyB9CiAgICAgICAgLmNoYXQtbWVzc2FnZXMgeyBmbGV4OiAxOyBvdmVyZmxvdy15OiBhdXRvOyBwYWRkaW5nOiAxMHB4OyB9CiAgICAgICAgLm1lc3NhZ2UgeyBtYXgtd2lkdGg6IDgwJTsgcGFkZGluZzogMTBweCAxNHB4OyBib3JkZXItcmFkaXVzOiAxNnB4OyBmb250LXNpemU6IDE2cHg7IG1hcmdpbi1ib3R0b206IDEwcHg7IH0KICAgICAgICAubWVzc2FnZS51c2VyIHsgbWFyZ2luLWxlZnQ6IGF1dG87IGJhY2tncm91bmQ6ICNkY2Y4YzY7IGJvcmRlci1ib3R0b20tcmlnaHQtcmFkaXVzOiA0cHg7IH0KICAgICAgICAubWVzc2FnZS5hZ2VudCB7IG1hcmdpbi1yaWdodDogYXV0bzsgYmFja2dyb3VuZDogI2YxZjFmMTsgYm9yZGVyLWJvdHRvbS1sZWZ0LXJhZGl1czogNHB4OyB9CiAgICAgICAgLm1lc3NhZ2UgLnRpbWUgeyBmb250LXNpemU6IDExcHg7IGNvbG9yOiAjODg4OyBtYXJnaW4tbGVmdDogNnB4OyB9CiAgICAgICAgLmxvYWRlciB7IGRpc3BsYXk6IG5vbmU7IHBhZGRpbmc6IDEwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsgY29sb3I6ICM2NjY7IH0KICAgICAgICAubG9hZGVyLnZpc2libGUgeyBkaXNwbGF5OiBibG9jazsgfQogICAgICAgIC5pbnB1dC1jb250YWluZXIgeyBmbGV4OiAwIDAgYXV0bzsgZGlzcGxheTogZmxleDsgZ2FwOiA4cHg7IHBhZGRpbmc6IDEwcHg7IGJhY2tncm91bmQ6ICNmZmY7IGJvcmRlci10b3A6IDFweCBzb2xpZCAjZWVlOyB9CiAgICAgICAgLmNoYXQtaW5wdXQgeyBmbGV4OiAxOyByZXNpemU6IG5vbmU7IGJvcmRlci1yYWRpdXM6IDIwcHg7IGJvcmRlcjogMXB4IHNvbGlkICNkZGQ7IHBhZGRpbmc6IDEycHggMTZweDsgZm9udC1zaXplOiAxNnB4OyBtYXgtaGVpZ2h0OiAxMjBweDsgZm9udC1mYW1pbHk6IGluaGVyaXQ7IH0KICAgICAgICAuY2hhdC1pbnB1dDpmb2N1cyB7IG91dGxpbmU6IG5vbmU7IGJvcmRlci1jb2xvcjogIzEyOEM3RTsgfQogICAgICAgIC5zZW5kLWJ1dHRvbiB7IG1pbi13aWR0aDogNDRweDsgaGVpZ2h0OiA0NHB4OyBib3JkZXItcmFkaXVzOiAyMHB4OyBib3JkZXI6IG5vbmU7IGJhY2tncm91bmQ6ICMxMjhDN0U7IGNvbG9yOiB3aGl0ZTsgZm9udC1zaXplOiAxOHB4OyBjdXJzb3I6IHBvaW50ZXI7IH0KICAgICAgICAuc2VuZC1idXR0b246ZGlzYWJsZWQgeyBiYWNrZ3JvdW5kOiAjY2NjOyB9CiAgICA8L3N0eWxlPgo8L2hlYWQ+Cjxib2R5PgogICAgPGRpdiBjbGFzcz0iYXBwIj4KICAgICAgICA8ZGl2IGNsYXNzPSJoZWFkZXIiPkFnZW50IE1heGltPC9kaXY+CiAgICAgICAgPGRpdiBjbGFzcz0iY2hhdC1tZXNzYWdlcyIgaWQ9Im1lc3NhZ2VzIj4KICAgICAgICAgICAgPGRpdiBpZD0idHlwaW5nIiBzdHlsZT0iZGlzcGxheTpub25lOyBwYWRkaW5nOiAxMHB4OyBjb2xvcjogIzY2NjsiPi4uLjwvZGl2PgogICAgICAgIDwvZGl2PgogICAgICAgIDxkaXYgY2xhc3M9ImxvYWRlciIgaWQ9ImxvYWRlciI+0JTRg9C80LDRji4uLjwvZGl2PgogICAgICAgIDxkaXYgY2xhc3M9ImlucHV0LWNvbnRhaW5lciI+CiAgICAgICAgICAgIDx0ZXh0YXJlYSBjbGFzcz0iY2hhdC1pbnB1dCIgaWQ9ImlucHV0IiBwbGFjZWhvbGRlcj0i0KHQvtC+0LHRidC10L3QuNC1Li4uIiByb3dzPSIxIj48L3RleHRhcmVhPgogICAgICAgICAgICA8YnV0dG9uIGNsYXNzPSJzZW5kLWJ1dHRvbiIgaWQ9InNlbmRCdG4iPuKepDwvYnV0dG9uPgogICAgICAgIDwvZGl2PgogICAgPC9kaXY+CiAgICA8c2NyaXB0PgogICAgICAgIGNvbnN0IEFQSV9VUkwgPSAnL3J1bnMnOwogICAgICAgIAogICAgICAgIGRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoJ0RPTUNvbnRlbnRMb2FkZWQnLCBmdW5jdGlvbigpIHsKICAgICAgICAgICAgY29uc3QgaW5wdXQgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnaW5wdXQnKTsKICAgICAgICAgICAgY29uc3Qgc2VuZEJ0biA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdzZW5kQnRuJyk7CiAgICAgICAgICAgIGNvbnN0IG1lc3NhZ2VzRGl2ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ21lc3NhZ2VzJyk7CiAgICAgICAgICAgIGNvbnN0IGxvYWRlciA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdsb2FkZXInKTsKICAgICAgICAgICAgCiAgICAgICAgICAgIGZ1bmN0aW9uIGdldFRpbWUoKSB7CiAgICAgICAgICAgICAgICBjb25zdCBkID0gbmV3IERhdGUoKTsKICAgICAgICAgICAgICAgIHJldHVybiBkLmdldEhvdXJzKCkudG9TdHJpbmcoKS5wYWRTdGFydCgyLCAnMCcpICsgJzonICsKICAgICAgICAgICAgICAgICAgICAgICBkLmdldE1pbnV0ZXMoKS50b1N0cmluZygpLnBhZFN0YXJ0KDIsICcwJyk7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgCiAgICAgICAgICAgIGZ1bmN0aW9uIHNob3dUeXBpbmcoKSB7CiAgICAgICAgICAgICAgICBjb25zdCB0ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ3R5cGluZycpOwogICAgICAgICAgICAgICAgaWYgKHQpIHQuc3R5bGUuZGlzcGxheSA9ICdibG9jayc7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgCiAgICAgICAgICAgIGZ1bmN0aW9uIGhpZGVUeXBpbmcoKSB7CiAgICAgICAgICAgICAgICBjb25zdCB0ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ3R5cGluZycpOwogICAgICAgICAgICAgICAgaWYgKHQpIHQuc3R5bGUuZGlzcGxheSA9ICdub25lJzsKICAgICAgICAgICAgfQogICAgICAgICAgICAKICAgICAgICAgICAgZnVuY3Rpb24gcGxheVNvdW5kKCkgewogICAgICAgICAgICAgICAgdHJ5IHsKICAgICAgICAgICAgICAgICAgICBjb25zdCBjdHggPSBuZXcgKHdpbmRvdy5BdWRpb0NvbnRleHQgfHwgd2luZG93LndlYmtpdEF1ZGlvQ29udGV4dCkoKTsKICAgICAgICAgICAgICAgICAgICBjb25zdCBvc2MgPSBjdHguY3JlYXRlT3NjaWxsYXRvcigpOwogICAgICAgICAgICAgICAgICAgIGNvbnN0IGdhaW4gPSBjdHguY3JlYXRlR2FpbigpOwogICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgIG9zYy50eXBlID0gJ3NpbmUnOwogICAgICAgICAgICAgICAgICAgIG9zYy5mcmVxdWVuY3kuc2V0VmFsdWVBdFRpbWUoODgwLCBjdHguY3VycmVudFRpbWUpOwogICAgICAgICAgICAgICAgICAgIGdhaW4uZ2Fpbi5zZXRWYWx1ZUF0VGltZSgwLjA1LCBjdHguY3VycmVudFRpbWUpOwogICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgIG9zYy5jb25uZWN0KGdhaW4pOwogICAgICAgICAgICAgICAgICAgIGdhaW4uY29ubmVjdChjdHguZGVzdGluYXRpb24pOwogICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgIG9zYy5zdGFydCgpOwogICAgICAgICAgICAgICAgICAgIG9zYy5zdG9wKGN0eC5jdXJyZW50VGltZSArIDAuMDgpOwogICAgICAgICAgICAgICAgfSBjYXRjaCAoZSkge30KICAgICAgICAgICAgfQogICAgICAgICAgICAKICAgICAgICAgICAgZnVuY3Rpb24gdmlicmF0ZSgpIHsKICAgICAgICAgICAgICAgIGlmIChuYXZpZ2F0b3IudmlicmF0ZSkgewogICAgICAgICAgICAgICAgICAgIG5hdmlnYXRvci52aWJyYXRlKDMwKTsKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgfQogICAgICAgICAgICAKICAgICAgICAgICAgZnVuY3Rpb24gYWRkTWVzc2FnZSh0ZXh0LCB0eXBlKSB7CiAgICAgICAgICAgICAgICBjb25zdCBtc2cgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdkaXYnKTsKICAgICAgICAgICAgICAgIG1zZy5jbGFzc05hbWUgPSAnbWVzc2FnZSAnICsgdHlwZTsKICAgICAgICAgICAgICAgIG1zZy5pbm5lckhUTUwgPSB0ZXh0ICsgJyA8c3BhbiBjbGFzcz0idGltZSI+JyArIGdldFRpbWUoKSArICc8L3NwYW4+JzsKICAgICAgICAgICAgICAgIG1lc3NhZ2VzRGl2LmFwcGVuZENoaWxkKG1zZyk7CiAgICAgICAgICAgICAgICBtZXNzYWdlc0Rpdi5zY3JvbGxUb3AgPSBtZXNzYWdlc0Rpdi5zY3JvbGxIZWlnaHQ7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgCiAgICAgICAgICAgIGZ1bmN0aW9uIHNlbmQoKSB7CiAgICAgICAgICAgICAgICBjb25zdCBtZXNzYWdlID0gaW5wdXQudmFsdWUudHJpbSgpOwogICAgICAgICAgICAgICAgaWYgKCFtZXNzYWdlKSByZXR1cm47CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgIGlucHV0LnZhbHVlID0gJyc7CiAgICAgICAgICAgICAgICBhZGRNZXNzYWdlKG1lc3NhZ2UsICd1c2VyJyk7CiAgICAgICAgICAgICAgICBzaG93VHlwaW5nKCk7CiAgICAgICAgICAgICAgICBsb2FkZXIuY2xhc3NMaXN0LmFkZCgndmlzaWJsZScpOwogICAgICAgICAgICAgICAgbWVzc2FnZXNEaXYuc2Nyb2xsVG9wID0gbWVzc2FnZXNEaXYuc2Nyb2xsSGVpZ2h0OwogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICBmZXRjaChBUElfVVJMLCB7CiAgICAgICAgICAgICAgICAgICAgbWV0aG9kOiAnUE9TVCcsCiAgICAgICAgICAgICAgICAgICAgaGVhZGVyczogeydDb250ZW50LVR5cGUnOiAnYXBwbGljYXRpb24vanNvbjsgY2hhcnNldD11dGYtOCd9LAogICAgICAgICAgICAgICAgICAgIGJvZHk6IEpTT04uc3RyaW5naWZ5KHtnb2FsOiBtZXNzYWdlfSkKICAgICAgICAgICAgICAgIH0pCiAgICAgICAgICAgICAgICAudGhlbihmdW5jdGlvbihyZXMpIHsgcmV0dXJuIHJlcy5qc29uKCk7IH0pCiAgICAgICAgICAgICAgICAudGhlbihmdW5jdGlvbihkYXRhKSB7CiAgICAgICAgICAgICAgICAgICAgbG9hZGVyLmNsYXNzTGlzdC5yZW1vdmUoJ3Zpc2libGUnKTsKICAgICAgICAgICAgICAgICAgICBoaWRlVHlwaW5nKCk7CiAgICAgICAgICAgICAgICAgICAgY29uc3QgdGV4dCA9IGRhdGEucmVzcG9uc2UgfHwgZGF0YS5yZXN1bHQgfHwgZGF0YS5tZXNzYWdlIHx8ICfQndC10YIg0L7RgtCy0LXRgtCwJzsKICAgICAgICAgICAgICAgICAgICBhZGRNZXNzYWdlKHRleHQsICdhZ2VudCcpOwogICAgICAgICAgICAgICAgICAgIHBsYXlTb3VuZCgpOwogICAgICAgICAgICAgICAgICAgIHZpYnJhdGUoKTsKICAgICAgICAgICAgICAgIH0pCiAgICAgICAgICAgICAgICAuY2F0Y2goZnVuY3Rpb24oZXJyKSB7CiAgICAgICAgICAgICAgICAgICAgbG9hZGVyLmNsYXNzTGlzdC5yZW1vdmUoJ3Zpc2libGUnKTsKICAgICAgICAgICAgICAgICAgICBoaWRlVHlwaW5nKCk7CiAgICAgICAgICAgICAgICAgICAgYWRkTWVzc2FnZSgn0J7RiNC40LHQutCwOiAnICsgZXJyLm1lc3NhZ2UsICdhZ2VudCcpOwogICAgICAgICAgICAgICAgfSk7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgCiAgICAgICAgICAgIHNlbmRCdG4uYWRkRXZlbnRMaXN0ZW5lcignY2xpY2snLCBzZW5kKTsKICAgICAgICAgICAgaW5wdXQuYWRkRXZlbnRMaXN0ZW5lcigna2V5ZG93bicsIGZ1bmN0aW9uKGUpIHsKICAgICAgICAgICAgICAgIGlmIChlLmtleSA9PT0gJ0VudGVyJyAmJiAhZS5zaGlmdEtleSkgewogICAgICAgICAgICAgICAgICAgIGUucHJldmVudERlZmF1bHQoKTsKICAgICAgICAgICAgICAgICAgICBzZW5kKCk7CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0pOwogICAgICAgIH0pOwogICAgPC9zY3JpcHQ+CjwvYm9keT4KPC9odG1sPg==").decode("utf-8") + +class DashboardHandler(BaseHTTPRequestHandler): + def do_GET(self): + if self.path == "/" or self.path == "": + self.send_response(200) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.end_headers() + self.wfile.write(HTML_CONTENT.encode("utf-8")) + else: + self.send_response(404) + self.end_headers() + def do_POST(self): + if self.path == "/runs": + content_length = int(self.headers.get("Content-Length", 0)) + body = self.rfile.read(content_length) + try: + data = json.loads(body.decode("utf-8")) + goal = data.get("goal", "") + req = urllib.request.Request( + "http://localhost:8000/runs", + data=json.dumps({"goal": goal, "adapter": "agent"}, ensure_ascii=False).encode("utf-8"), + headers={"Content-Type": "application/json; charset=utf-8"}, + method="POST" + ) + with urllib.request.urlopen(req, timeout=120) as resp: + resp_data = json.loads(resp.read().decode("utf-8")) + self.send_response(200) + self.send_header("Content-Type", "application/json; charset=utf-8") + self.end_headers() + self.wfile.write(json.dumps(resp_data, ensure_ascii=False).encode("utf-8")) + except Exception as e: + self.send_response(500) + self.send_header("Content-Type", "application/json") + self.end_headers() + self.wfile.write(json.dumps({"error": str(e)}).encode("utf-8")) + else: + self.send_response(404) + self.end_headers() + def log_message(self, *args): pass + +HTTPServer(("0.0.0.0", 8610), DashboardHandler).serve_forever() \ No newline at end of file diff --git a/simple_dashboard.py b/simple_dashboard.py index 5736219..22ef57e 100644 --- a/simple_dashboard.py +++ b/simple_dashboard.py @@ -1,341 +1,47 @@ -"""Simple Dashboard - WhatsApp-style mobile UI for Agent Maxim.""" -from __future__ import annotations +#!/usr/bin/env python3 +import base64, json +from http.server import HTTPServer, BaseHTTPRequestHandler +import urllib.request -import sys -from pathlib import Path +HTML_CONTENT = base64.b64decode("PCFET0NUWVBFIGh0bWw+CjxodG1sIGxhbmc9InJ1Ij4KPGhlYWQ+CiAgICA8bWV0YSBjaGFyc2V0PSJVVEYtOCI+CiAgICA8bWV0YSBuYW1lPSJ2aWV3cG9ydCIgY29udGVudD0id2lkdGg9ZGV2aWNlLXdpZHRoLCBpbml0aWFsLXNjYWxlPTEuMCwgbWF4aW11bS1zY2FsZT0xLjAsIHVzZXItc2NhbGFibGU9bm8iPgogICAgPHRpdGxlPkFnZW50IE1heGltPC90aXRsZT4KICAgIDxzdHlsZT4KICAgICAgICAqIHsgbWFyZ2luOiAwOyBwYWRkaW5nOiAwOyBib3gtc2l6aW5nOiBib3JkZXItYm94OyB9CiAgICAgICAgaHRtbCwgYm9keSB7IGhlaWdodDogMTAwZHZoOyBvdmVyZmxvdzogaGlkZGVuOyBmb250LWZhbWlseTogLWFwcGxlLXN5c3RlbSwgc2Fucy1zZXJpZjsgYmFja2dyb3VuZDogI2U1ZGRkNTsgfQogICAgICAgIC5hcHAgeyBwb3NpdGlvbjogZml4ZWQ7IGluc2V0OiAwOyBkaXNwbGF5OiBmbGV4OyBmbGV4LWRpcmVjdGlvbjogY29sdW1uOyB9CiAgICAgICAgLmhlYWRlciB7IGZsZXg6IDAgMCA1MnB4OyBiYWNrZ3JvdW5kOiAjMTI4QzdFOyBjb2xvcjogd2hpdGU7IHBhZGRpbmc6IDAgMTZweDsgZm9udC13ZWlnaHQ6IDYwMDsgZm9udC1zaXplOiAxOHB4OyBkaXNwbGF5OiBmbGV4OyBhbGlnbi1pdGVtczogY2VudGVyOyB9CiAgICAgICAgLmNoYXQtbWVzc2FnZXMgeyBmbGV4OiAxOyBvdmVyZmxvdy15OiBhdXRvOyBwYWRkaW5nOiAxMHB4OyB9CiAgICAgICAgLm1lc3NhZ2UgeyBtYXgtd2lkdGg6IDgwJTsgcGFkZGluZzogMTBweCAxNHB4OyBib3JkZXItcmFkaXVzOiAxNnB4OyBmb250LXNpemU6IDE2cHg7IG1hcmdpbi1ib3R0b206IDEwcHg7IH0KICAgICAgICAubWVzc2FnZS51c2VyIHsgbWFyZ2luLWxlZnQ6IGF1dG87IGJhY2tncm91bmQ6ICNkY2Y4YzY7IGJvcmRlci1ib3R0b20tcmlnaHQtcmFkaXVzOiA0cHg7IH0KICAgICAgICAubWVzc2FnZS5hZ2VudCB7IG1hcmdpbi1yaWdodDogYXV0bzsgYmFja2dyb3VuZDogI2YxZjFmMTsgYm9yZGVyLWJvdHRvbS1sZWZ0LXJhZGl1czogNHB4OyB9CiAgICAgICAgLm1lc3NhZ2UgLnRpbWUgeyBmb250LXNpemU6IDExcHg7IGNvbG9yOiAjODg4OyBtYXJnaW4tbGVmdDogNnB4OyB9CiAgICAgICAgLmxvYWRlciB7IGRpc3BsYXk6IG5vbmU7IHBhZGRpbmc6IDEwcHg7IHRleHQtYWxpZ246IGNlbnRlcjsgY29sb3I6ICM2NjY7IH0KICAgICAgICAubG9hZGVyLnZpc2libGUgeyBkaXNwbGF5OiBibG9jazsgfQogICAgICAgIC5pbnB1dC1jb250YWluZXIgeyBmbGV4OiAwIDAgYXV0bzsgZGlzcGxheTogZmxleDsgZ2FwOiA4cHg7IHBhZGRpbmc6IDEwcHg7IGJhY2tncm91bmQ6ICNmZmY7IGJvcmRlci10b3A6IDFweCBzb2xpZCAjZWVlOyB9CiAgICAgICAgLmNoYXQtaW5wdXQgeyBmbGV4OiAxOyByZXNpemU6IG5vbmU7IGJvcmRlci1yYWRpdXM6IDIwcHg7IGJvcmRlcjogMXB4IHNvbGlkICNkZGQ7IHBhZGRpbmc6IDEycHggMTZweDsgZm9udC1zaXplOiAxNnB4OyBtYXgtaGVpZ2h0OiAxMjBweDsgZm9udC1mYW1pbHk6IGluaGVyaXQ7IH0KICAgICAgICAuY2hhdC1pbnB1dDpmb2N1cyB7IG91dGxpbmU6IG5vbmU7IGJvcmRlci1jb2xvcjogIzEyOEM3RTsgfQogICAgICAgIC5zZW5kLWJ1dHRvbiB7IG1pbi13aWR0aDogNDRweDsgaGVpZ2h0OiA0NHB4OyBib3JkZXItcmFkaXVzOiAyMHB4OyBib3JkZXI6IG5vbmU7IGJhY2tncm91bmQ6ICMxMjhDN0U7IGNvbG9yOiB3aGl0ZTsgZm9udC1zaXplOiAxOHB4OyBjdXJzb3I6IHBvaW50ZXI7IH0KICAgICAgICAuc2VuZC1idXR0b246ZGlzYWJsZWQgeyBiYWNrZ3JvdW5kOiAjY2NjOyB9CiAgICA8L3N0eWxlPgo8L2hlYWQ+Cjxib2R5PgogICAgPGRpdiBjbGFzcz0iYXBwIj4KICAgICAgICA8ZGl2IGNsYXNzPSJoZWFkZXIiPkFnZW50IE1heGltPC9kaXY+CiAgICAgICAgPGRpdiBjbGFzcz0iY2hhdC1tZXNzYWdlcyIgaWQ9Im1lc3NhZ2VzIj4KICAgICAgICAgICAgPGRpdiBpZD0idHlwaW5nIiBzdHlsZT0iZGlzcGxheTpub25lOyBwYWRkaW5nOiAxMHB4OyBjb2xvcjogIzY2NjsiPi4uLjwvZGl2PgogICAgICAgIDwvZGl2PgogICAgICAgIDxkaXYgY2xhc3M9ImxvYWRlciIgaWQ9ImxvYWRlciI+0JTRg9C80LDRji4uLjwvZGl2PgogICAgICAgIDxkaXYgY2xhc3M9ImlucHV0LWNvbnRhaW5lciI+CiAgICAgICAgICAgIDx0ZXh0YXJlYSBjbGFzcz0iY2hhdC1pbnB1dCIgaWQ9ImlucHV0IiBwbGFjZWhvbGRlcj0i0KHQvtC+0LHRidC10L3QuNC1Li4uIiByb3dzPSIxIj48L3RleHRhcmVhPgogICAgICAgICAgICA8YnV0dG9uIGNsYXNzPSJzZW5kLWJ1dHRvbiIgaWQ9InNlbmRCdG4iPuKepDwvYnV0dG9uPgogICAgICAgIDwvZGl2PgogICAgPC9kaXY+CiAgICA8c2NyaXB0PgogICAgICAgIGNvbnN0IEFQSV9VUkwgPSAnL3J1bnMnOwogICAgICAgIAogICAgICAgIGRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoJ0RPTUNvbnRlbnRMb2FkZWQnLCBmdW5jdGlvbigpIHsKICAgICAgICAgICAgY29uc3QgaW5wdXQgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnaW5wdXQnKTsKICAgICAgICAgICAgY29uc3Qgc2VuZEJ0biA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdzZW5kQnRuJyk7CiAgICAgICAgICAgIGNvbnN0IG1lc3NhZ2VzRGl2ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ21lc3NhZ2VzJyk7CiAgICAgICAgICAgIGNvbnN0IGxvYWRlciA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdsb2FkZXInKTsKICAgICAgICAgICAgCiAgICAgICAgICAgIGZ1bmN0aW9uIGdldFRpbWUoKSB7CiAgICAgICAgICAgICAgICBjb25zdCBkID0gbmV3IERhdGUoKTsKICAgICAgICAgICAgICAgIHJldHVybiBkLmdldEhvdXJzKCkudG9TdHJpbmcoKS5wYWRTdGFydCgyLCAnMCcpICsgJzonICsKICAgICAgICAgICAgICAgICAgICAgICBkLmdldE1pbnV0ZXMoKS50b1N0cmluZygpLnBhZFN0YXJ0KDIsICcwJyk7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgCiAgICAgICAgICAgIGZ1bmN0aW9uIHNob3dUeXBpbmcoKSB7CiAgICAgICAgICAgICAgICBjb25zdCB0ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ3R5cGluZycpOwogICAgICAgICAgICAgICAgaWYgKHQpIHQuc3R5bGUuZGlzcGxheSA9ICdibG9jayc7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgCiAgICAgICAgICAgIGZ1bmN0aW9uIGhpZGVUeXBpbmcoKSB7CiAgICAgICAgICAgICAgICBjb25zdCB0ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ3R5cGluZycpOwogICAgICAgICAgICAgICAgaWYgKHQpIHQuc3R5bGUuZGlzcGxheSA9ICdub25lJzsKICAgICAgICAgICAgfQogICAgICAgICAgICAKICAgICAgICAgICAgZnVuY3Rpb24gcGxheVNvdW5kKCkgewogICAgICAgICAgICAgICAgdHJ5IHsKICAgICAgICAgICAgICAgICAgICBjb25zdCBjdHggPSBuZXcgKHdpbmRvdy5BdWRpb0NvbnRleHQgfHwgd2luZG93LndlYmtpdEF1ZGlvQ29udGV4dCkoKTsKICAgICAgICAgICAgICAgICAgICBjb25zdCBvc2MgPSBjdHguY3JlYXRlT3NjaWxsYXRvcigpOwogICAgICAgICAgICAgICAgICAgIGNvbnN0IGdhaW4gPSBjdHguY3JlYXRlR2FpbigpOwogICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgIG9zYy50eXBlID0gJ3NpbmUnOwogICAgICAgICAgICAgICAgICAgIG9zYy5mcmVxdWVuY3kuc2V0VmFsdWVBdFRpbWUoODgwLCBjdHguY3VycmVudFRpbWUpOwogICAgICAgICAgICAgICAgICAgIGdhaW4uZ2Fpbi5zZXRWYWx1ZUF0VGltZSgwLjA1LCBjdHguY3VycmVudFRpbWUpOwogICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgIG9zYy5jb25uZWN0KGdhaW4pOwogICAgICAgICAgICAgICAgICAgIGdhaW4uY29ubmVjdChjdHguZGVzdGluYXRpb24pOwogICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgIG9zYy5zdGFydCgpOwogICAgICAgICAgICAgICAgICAgIG9zYy5zdG9wKGN0eC5jdXJyZW50VGltZSArIDAuMDgpOwogICAgICAgICAgICAgICAgfSBjYXRjaCAoZSkge30KICAgICAgICAgICAgfQogICAgICAgICAgICAKICAgICAgICAgICAgZnVuY3Rpb24gdmlicmF0ZSgpIHsKICAgICAgICAgICAgICAgIGlmIChuYXZpZ2F0b3IudmlicmF0ZSkgewogICAgICAgICAgICAgICAgICAgIG5hdmlnYXRvci52aWJyYXRlKDMwKTsKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgfQogICAgICAgICAgICAKICAgICAgICAgICAgZnVuY3Rpb24gYWRkTWVzc2FnZSh0ZXh0LCB0eXBlKSB7CiAgICAgICAgICAgICAgICBjb25zdCBtc2cgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdkaXYnKTsKICAgICAgICAgICAgICAgIG1zZy5jbGFzc05hbWUgPSAnbWVzc2FnZSAnICsgdHlwZTsKICAgICAgICAgICAgICAgIG1zZy5pbm5lckhUTUwgPSB0ZXh0ICsgJyA8c3BhbiBjbGFzcz0idGltZSI+JyArIGdldFRpbWUoKSArICc8L3NwYW4+JzsKICAgICAgICAgICAgICAgIG1lc3NhZ2VzRGl2LmFwcGVuZENoaWxkKG1zZyk7CiAgICAgICAgICAgICAgICBtZXNzYWdlc0Rpdi5zY3JvbGxUb3AgPSBtZXNzYWdlc0Rpdi5zY3JvbGxIZWlnaHQ7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgCiAgICAgICAgICAgIGZ1bmN0aW9uIHNlbmQoKSB7CiAgICAgICAgICAgICAgICBjb25zdCBtZXNzYWdlID0gaW5wdXQudmFsdWUudHJpbSgpOwogICAgICAgICAgICAgICAgaWYgKCFtZXNzYWdlKSByZXR1cm47CiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgIGlucHV0LnZhbHVlID0gJyc7CiAgICAgICAgICAgICAgICBhZGRNZXNzYWdlKG1lc3NhZ2UsICd1c2VyJyk7CiAgICAgICAgICAgICAgICBzaG93VHlwaW5nKCk7CiAgICAgICAgICAgICAgICBsb2FkZXIuY2xhc3NMaXN0LmFkZCgndmlzaWJsZScpOwogICAgICAgICAgICAgICAgbWVzc2FnZXNEaXYuc2Nyb2xsVG9wID0gbWVzc2FnZXNEaXYuc2Nyb2xsSGVpZ2h0OwogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICBmZXRjaChBUElfVVJMLCB7CiAgICAgICAgICAgICAgICAgICAgbWV0aG9kOiAnUE9TVCcsCiAgICAgICAgICAgICAgICAgICAgaGVhZGVyczogeydDb250ZW50LVR5cGUnOiAnYXBwbGljYXRpb24vanNvbjsgY2hhcnNldD11dGYtOCd9LAogICAgICAgICAgICAgICAgICAgIGJvZHk6IEpTT04uc3RyaW5naWZ5KHtnb2FsOiBtZXNzYWdlfSkKICAgICAgICAgICAgICAgIH0pCiAgICAgICAgICAgICAgICAudGhlbihmdW5jdGlvbihyZXMpIHsgcmV0dXJuIHJlcy5qc29uKCk7IH0pCiAgICAgICAgICAgICAgICAudGhlbihmdW5jdGlvbihkYXRhKSB7CiAgICAgICAgICAgICAgICAgICAgbG9hZGVyLmNsYXNzTGlzdC5yZW1vdmUoJ3Zpc2libGUnKTsKICAgICAgICAgICAgICAgICAgICBoaWRlVHlwaW5nKCk7CiAgICAgICAgICAgICAgICAgICAgY29uc3QgdGV4dCA9IGRhdGEucmVzcG9uc2UgfHwgZGF0YS5yZXN1bHQgfHwgZGF0YS5tZXNzYWdlIHx8ICfQndC10YIg0L7RgtCy0LXRgtCwJzsKICAgICAgICAgICAgICAgICAgICBhZGRNZXNzYWdlKHRleHQsICdhZ2VudCcpOwogICAgICAgICAgICAgICAgICAgIHBsYXlTb3VuZCgpOwogICAgICAgICAgICAgICAgICAgIHZpYnJhdGUoKTsKICAgICAgICAgICAgICAgIH0pCiAgICAgICAgICAgICAgICAuY2F0Y2goZnVuY3Rpb24oZXJyKSB7CiAgICAgICAgICAgICAgICAgICAgbG9hZGVyLmNsYXNzTGlzdC5yZW1vdmUoJ3Zpc2libGUnKTsKICAgICAgICAgICAgICAgICAgICBoaWRlVHlwaW5nKCk7CiAgICAgICAgICAgICAgICAgICAgYWRkTWVzc2FnZSgn0J7RiNC40LHQutCwOiAnICsgZXJyLm1lc3NhZ2UsICdhZ2VudCcpOwogICAgICAgICAgICAgICAgfSk7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgCiAgICAgICAgICAgIHNlbmRCdG4uYWRkRXZlbnRMaXN0ZW5lcignY2xpY2snLCBzZW5kKTsKICAgICAgICAgICAgaW5wdXQuYWRkRXZlbnRMaXN0ZW5lcigna2V5ZG93bicsIGZ1bmN0aW9uKGUpIHsKICAgICAgICAgICAgICAgIGlmIChlLmtleSA9PT0gJ0VudGVyJyAmJiAhZS5zaGlmdEtleSkgewogICAgICAgICAgICAgICAgICAgIGUucHJldmVudERlZmF1bHQoKTsKICAgICAgICAgICAgICAgICAgICBzZW5kKCk7CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0pOwogICAgICAgIH0pOwogICAgPC9zY3JpcHQ+CjwvYm9keT4KPC9odG1sPg==").decode("utf-8") -from fastapi import FastAPI, Request -from fastapi.responses import HTMLResponse -from fastapi.staticfiles import StaticFiles -import uvicorn +class DashboardHandler(BaseHTTPRequestHandler): + def do_GET(self): + if self.path == "/" or self.path == "": + self.send_response(200) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.end_headers() + self.wfile.write(HTML_CONTENT.encode("utf-8")) + else: + self.send_response(404) + self.end_headers() + def do_POST(self): + if self.path == "/runs": + content_length = int(self.headers.get("Content-Length", 0)) + body = self.rfile.read(content_length) + try: + data = json.loads(body.decode("utf-8")) + goal = data.get("goal", "") + req = urllib.request.Request( + "http://localhost:8000/runs", + data=json.dumps({"goal": goal, "adapter": "agent"}, ensure_ascii=False).encode("utf-8"), + headers={"Content-Type": "application/json; charset=utf-8"}, + method="POST" + ) + with urllib.request.urlopen(req, timeout=120) as resp: + resp_data = json.loads(resp.read().decode("utf-8")) + self.send_response(200) + self.send_header("Content-Type", "application/json; charset=utf-8") + self.end_headers() + self.wfile.write(json.dumps(resp_data, ensure_ascii=False).encode("utf-8")) + except Exception as e: + self.send_response(500) + self.send_header("Content-Type", "application/json") + self.end_headers() + self.wfile.write(json.dumps({"error": str(e)}).encode("utf-8")) + else: + self.send_response(404) + self.end_headers() + def log_message(self, *args): pass -sys.path.insert(0, str(Path(__file__).parent)) - -app = FastAPI(title="Agent Maxim Dashboard") - - -@app.get("/", response_class=HTMLResponse) -async def dashboard(): - """Serve WhatsApp-style chat dashboard.""" - html_content = """ - - -
- - -