"""浏览器自动化服务:解析场景 + 启动 dev server + 执行步骤。""" from __future__ import annotations import asyncio import logging from urllib.parse import urlparse from browser_executor import run_browser_scenario_sync from browser_models import BrowserResult, BrowserScenario from browser_parser import parse_browser_request from preview_service import ( _package_dev_script, _preview_port, _project_cwd, _startup_timeout, _wait_for_port, ) logger = logging.getLogger(__name__) def _ensure_dev_server(base_url: str) -> bool: parsed = urlparse(base_url) host = parsed.hostname or "127.0.0.1" port = parsed.port or (443 if parsed.scheme == "https" else 80) if _wait_for_port(host, port, timeout=3): return False cwd = _project_cwd() dev_command = _package_dev_script(cwd) if not dev_command: raise RuntimeError( f"无法访问 {base_url},且未找到可启动的 dev 脚本。" "请先手动启动前端,或设置 PREVIEW_URL。" ) import subprocess logger.info("启动 dev server: %s (cwd=%s)", dev_command, cwd) proc = subprocess.Popen( dev_command, cwd=str(cwd), shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, ) if not _wait_for_port(host, port, timeout=_startup_timeout()): err = "" if proc.stderr: err = proc.stderr.read().decode("utf-8", errors="replace")[-1000:] proc.kill() raise RuntimeError( f"dev server 在 {_startup_timeout()}s 内未就绪 ({base_url})。" f"{(' 日志: ' + err) if err else ''}" ) return True async def run_browser_automation(text: str) -> BrowserResult: scenario = parse_browser_request(text) if scenario is None: raise RuntimeError("无法解析网页操作步骤") started = await asyncio.to_thread(_ensure_dev_server, scenario.base_url) result = await asyncio.to_thread(run_browser_scenario_sync, scenario) result.started_dev_server = started return result def format_browser_caption(result: BrowserResult) -> str: lines = [ "**网页操作完成**", f"> 场景:`{result.scenario_name or '自定义'}`", f"> 起始:`{result.base_url}`", f"> 最终:`{result.final_url}`", f"> 步骤数:{result.step_count}", ] if result.started_dev_server: lines.append("> dev server:已自动启动") if result.step_log: lines.append("") lines.append("执行记录:") for item in result.step_log[-8:]: lines.append(f"- {item}") return "\n".join(lines)