195 lines
7.5 KiB
Python
195 lines
7.5 KiB
Python
"""
|
|||
|
|
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)
|