""" DeepSeek Web Adapter 控制面板 提供 Web UI 管理:配置、Worker 注册、浏览器控制、日志查看 """ import asyncio import json import logging import threading from pathlib import Path from typing import Optional from flask import Flask, render_template, request, jsonify from flask_socketio import SocketIO from .config import config, DeepSeekWebConfig, CONFIG_FILE logger = logging.getLogger(__name__) TEMPLATE_DIR = str(Path(__file__).parent.parent / "templates") class AdminServer: """控制面板 HTTP 服务""" def __init__(self, adapter=None): self.adapter = adapter self.app = Flask(__name__, template_folder=TEMPLATE_DIR, static_folder=None) self.socketio = SocketIO(self.app, cors_allowed_origins="*", async_mode="threading") self._adapter_loop = None self._setup_routes() def _setup_routes(self): @self.app.route("/") def index(): return render_template("admin.html") @self.app.route("/api/config", methods=["GET"]) def get_config(): from dataclasses import asdict return jsonify(asdict(config)) @self.app.route("/api/config", methods=["POST"]) def save_config(): try: data = request.json for key, value in data.items(): if hasattr(config, key): setattr(config, key, value) config.save() return jsonify({"success": True}) except Exception as e: return jsonify({"success": False, "error": str(e)}) @self.app.route("/api/register", methods=["POST"]) def register_worker(): try: data = request.json import urllib.request import ssl base_url = config.array_base_url.rstrip("/") url = f"{base_url}/api/v1/workers/register" reg_data = { "name": data.get("name", "DeepSeek-Web"), "type": data.get("type", "ai"), "description": data.get("description", ""), "invite_code": data.get("invite_code") or None, "capabilities": config.profile.get("capabilities", {}), "organization": "Array", "timezone": "Asia/Shanghai", "tags": ["AI", "DeepSeek", "Agent", "Web"], "max_concurrent_tasks": 1, } req = urllib.request.Request( url, data=json.dumps(reg_data).encode("utf-8"), headers={"Content-Type": "application/json"}, method="POST", ) ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE try: with urllib.request.urlopen(req, context=ctx, timeout=10) as resp: result = json.loads(resp.read().decode("utf-8")) except urllib.error.HTTPError as e: error_body = e.read().decode("utf-8", errors="replace") return jsonify({"error": f"HTTP {e.code}: {error_body}"}) worker_id = result.get("worker", {}).get("id", "") api_key = result.get("api_key", "") if api_key: config.api_key = api_key config.worker_id = worker_id config.worker_name = data.get("name", config.worker_name) config.save() return jsonify({"worker_id": worker_id, "api_key": api_key}) except Exception as e: return jsonify({"error": str(e)}) @self.app.route("/api/status") def get_status(): if self.adapter: status = self.adapter.get_status() status["worker_name"] = config.worker_name return jsonify(status) return jsonify({ "connected": False, "deepseek_ready": False, "processing": False, "current_task": None, "worker_id": None, "worker_name": config.worker_name, }) @self.app.route("/api/browser/start", methods=["POST"]) def start_browser(): if not self.adapter or not self._adapter_loop: return jsonify({"success": False, "error": "Not ready"}) future = asyncio.run_coroutine_threadsafe( self.adapter.start_deepseek_session(), self._adapter_loop ) try: result = future.result(timeout=30) return jsonify({"success": result}) except Exception as e: return jsonify({"success": False, "error": str(e)}) @self.app.route("/api/browser/stop", methods=["POST"]) def stop_browser(): if not self.adapter or not self._adapter_loop: return jsonify({"success": False, "error": "Not ready"}) future = asyncio.run_coroutine_threadsafe( self.adapter.stop_deepseek_session(), self._adapter_loop ) try: future.result(timeout=10) return jsonify({"success": True}) except Exception as e: return jsonify({"success": False, "error": str(e)}) @self.app.route("/api/browser/reinit", methods=["POST"]) def reinit_prompt(): if not self.adapter or not self.adapter.session or not self._adapter_loop: return jsonify({"success": False, "error": "DeepSeek session not started"}) from .deepseek_agent_adapter import ADAPTER_SYSTEM_PROMPT async def _reinit(): await self.adapter.session.controller.send( f"[系统重新初始化]\n\n{ADAPTER_SYSTEM_PROMPT}" ) return True future = asyncio.run_coroutine_threadsafe(_reinit(), self._adapter_loop) try: future.result(timeout=15) return jsonify({"success": True}) except Exception as e: return jsonify({"success": False, "error": str(e)}) @self.app.route("/api/logs/deepseek") def get_deepseek_logs(): if self.adapter and self.adapter.session: return jsonify({"logs": self.adapter.session.get_logs(100)}) return jsonify({"logs": None}) @self.app.route("/api/logs/deepseek", methods=["DELETE"]) def clear_deepseek_logs(): if self.adapter and self.adapter.session: self.adapter.session._log_buffer.clear() return jsonify({"success": True}) @self.app.route("/api/logs/system") def get_system_logs(): log_file = Path("adapter_debug.log") if log_file.exists(): try: content = log_file.read_text(encoding="utf-8", errors="replace") lines = content.split("\n") return jsonify({"logs": "\n".join(lines[-200:])}) except: pass return jsonify({"logs": None}) def set_adapter(self, adapter, loop): self.adapter = adapter self._adapter_loop = loop def run(self, host="0.0.0.0", port=None): if port is None: port = config.admin_port logger.info(f"控制面板启动: http://{host}:{port}") self.socketio.run(self.app, host=host, port=port, debug=False, allow_unsafe_werkzeug=True)