add dashboard.py
parent
d7a05651b7
commit
2743c4d0f3
|
|
@ -0,0 +1,94 @@
|
|||
from __future__ import annotations
|
||||
import asyncio
|
||||
from aiohttp import web
|
||||
from loguru import logger
|
||||
from typing import Any
|
||||
import json
|
||||
|
||||
from nanobot.channels.base import BaseChannel
|
||||
from nanobot.bus.events import InboundMessage, OutboundMessage
|
||||
|
||||
class DashboardChannel(BaseChannel):
|
||||
"""
|
||||
专门为管理面板设计的渠道。
|
||||
它充当机器人内部总线 (Bus) 与宿主机面板之间的桥梁。
|
||||
"""
|
||||
|
||||
def __init__(self, config: Any, bus: Any, host: str = "0.0.0.0", port: int = 9000):
|
||||
super().__init__(config, bus)
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.runner = None
|
||||
|
||||
async def start(self) -> None:
|
||||
"""启动 Dashboard HTTP 服务"""
|
||||
app = web.Application()
|
||||
app.router.add_post("/chat", self._handle_chat)
|
||||
|
||||
self.runner = web.AppRunner(app)
|
||||
await self.runner.setup()
|
||||
site = web.TCPSite(self.runner, self.host, self.port)
|
||||
|
||||
await site.start()
|
||||
self._is_running = True
|
||||
logger.info(f"🚀 Dashboard Channel 代理已上线,监听端口: {self.port}")
|
||||
|
||||
async def stop(self) -> None:
|
||||
"""停止服务"""
|
||||
if self.runner:
|
||||
await self.runner.cleanup()
|
||||
self._is_running = False
|
||||
logger.info("Dashboard Channel 已下线")
|
||||
|
||||
async def send(self, message: OutboundMessage) -> None:
|
||||
"""
|
||||
从总线 (Bus) 接收机器人发出的所有消息,并结构化输出到 stdout。
|
||||
"""
|
||||
media = [str(v).strip().replace("\\", "/") for v in (message.media or []) if str(v).strip()]
|
||||
if not message.content and not media:
|
||||
return
|
||||
|
||||
# 核心:从元数据识别消息类型(进度更新 vs 最终回复)
|
||||
is_progress = message.metadata.get("_progress", False)
|
||||
is_tool_hint = message.metadata.get("_tool_hint", False)
|
||||
|
||||
payload = {
|
||||
"type": "BUS_EVENT",
|
||||
"source": "dashboard_channel",
|
||||
"is_progress": is_progress,
|
||||
"is_tool": is_tool_hint,
|
||||
"content": message.content,
|
||||
"media": media,
|
||||
}
|
||||
|
||||
# 使用 JSON 格式输出,方便面板后端精准解析,告别正则
|
||||
print(f"\n__DASHBOARD_DATA_START__{json.dumps(payload, ensure_ascii=False)}__DASHBOARD_DATA_END__\n", flush=True)
|
||||
|
||||
async def _handle_chat(self, request: web.Request) -> web.Response:
|
||||
"""处理来自面板的指令入站"""
|
||||
try:
|
||||
data = await request.json()
|
||||
user_message = data.get("message", "").strip()
|
||||
media = [str(v).strip().replace("\\", "/") for v in (data.get("media") or []) if str(v).strip()]
|
||||
|
||||
if not user_message and not media:
|
||||
return web.json_response({"status": "error", "reason": "empty message and media"}, status=400)
|
||||
if not user_message:
|
||||
user_message = "[attachment message]"
|
||||
|
||||
# 调试日志:打印收到的原始消息长度和前 20 个字符,确保中文未乱码
|
||||
logger.info(f"📥 [Dashboard Channel] 收到指令 (len={len(user_message)}): {user_message[:20]}...")
|
||||
|
||||
# 将消息塞入总线
|
||||
await self.bus.publish_inbound(InboundMessage(
|
||||
channel="dashboard",
|
||||
sender_id="user",
|
||||
chat_id="direct",
|
||||
content=user_message,
|
||||
media=media,
|
||||
))
|
||||
|
||||
return web.json_response({"status": "ok"})
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Dashboard Channel 接收指令失败: {e}")
|
||||
return web.json_response({"status": "error", "reason": str(e)}, status=500)
|
||||
Loading…
Reference in New Issue