feat: DeepSeek Web Adapter - Array platform integration with browser control panel
This commit is contained in:
@@ -0,0 +1,194 @@
|
||||
"""
|
||||
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)
|
||||
Reference in New Issue
Block a user