feat(web): 任务中心全局 Provider / 设置·布局·路由调整 / 视频解析页适配

- tasks.tsx:顶栏任务中心全局 Provider(SSE 任务进度,供各插件页共享)
- 设置页与 AppLayout / client / router 调整,BF2042 插件页路由注册
- web_hub:dashboard / bot_settings 同步更新
- 视频解析页配合后端(缓存 tab / 任务槽进度)

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
sansenhoshiandClaude Code committed 2026-10-13 17:53:27 +08:00
1 parent 6af60a7d55
commit 968b3d1cfe
10 files changed
+804 -9

No files matched your search

+58 -1
View File
@@ -41,6 +41,7 @@ from .web_plugin_registry import get_web_plugin_builder, list_web_plugins
from .dashboard import collect_dashboard
# hexi/web_hub 为机器人核心模块(统一 Web 管理台),非 NoneBot 插件
basic_path = Path(__file__).resolve().parent
# hexi/web 是统一 Web 管理台前端(hexi/web/dist),不是插件目录下的 web
@@ -333,6 +334,10 @@ async def _first_bot():
def build_hub_app() -> FastAPI:
# 延迟导入:hexi.core 是插件侧基础设施,模块级导入会触发它整套初始化
# (见本模块文档"对 core 的引用一律函数内导入"的约定)
from hexi.core.task_slots import TASKS
sync_admin()
app = FastAPI(title="HeXi Web Hub")
@@ -575,6 +580,58 @@ def build_hub_app() -> FastAPI:
return StreamingResponse(gen(), media_type="text/event-stream")
@app.get("/api/tasks")
async def list_tasks(_: dict = Depends(get_current_user)):
"""任务槽的当前快照(新的在前);SSE 首帧同款,作轮询兜底用。"""
return {"ok": True, "tasks": TASKS.snapshot()}
@app.get("/api/tasks/stream")
async def tasks_stream(_: dict = Depends(get_current_user)):
"""任务槽 SSE:连上先发一帧全量快照,之后每次任务变化再发一帧。
变化不频繁(按百分比桶节流),所以直接推送全量快照,不做增量协议。
"""
queue = TASKS.subscribe()
async def gen():
try:
yield f"data: {json.dumps({'ok': True, 'tasks': TASKS.snapshot()}, ensure_ascii=False)}\n\n"
while True:
try:
tasks = await asyncio.wait_for(queue.get(), timeout=15)
except asyncio.TimeoutError:
yield ": ping\n\n" # ~15s 心跳保活
continue
yield f"data: {json.dumps({'ok': True, 'tasks': tasks}, ensure_ascii=False)}\n\n"
finally:
TASKS.unsubscribe(queue)
return StreamingResponse(gen(), media_type="text/event-stream")
@app.post("/api/tasks/clear")
async def clear_tasks(_: dict = Depends(get_current_user)):
"""清掉所有已结束的任务(在跑的不动)。"""
removed = TASKS.clear_finished()
return {"ok": True, "removed": removed}
@app.post("/api/tasks/{task_id}/dismiss")
async def dismiss_task(task_id: str, _: dict = Depends(get_current_user)):
"""从列表里删掉一条已结束的任务(在跑的先取消,删不了)。"""
if not TASKS.dismiss(task_id):
return {"ok": False, "msg": "任务不存在,或还在运行(在跑的只能先取消)"}
return {"ok": True}
@app.post("/api/tasks/{task_id}/cancel")
async def cancel_task(task_id: str, _: dict = Depends(get_current_user)):
"""请求取消一个在跑的任务。
协作式:只置标志,任务体在下一个检查点收尾(见 task_slots 模块文档),
所以接口马上返回不代表任务已经停了 —— 状态变化会从 SSE 推出去。
"""
if not TASKS.cancel(task_id):
return {"ok": False, "msg": "任务不存在或已结束"}
return {"ok": True}
@app.get("/api/logs/stream")
async def logs_stream(
since: int = 0,
@@ -726,4 +783,4 @@ async def _startup_mount_web_plugins() -> None:
@get_driver().on_startup
async def _downgrade_uvicorn_access_logs() -> None:
"""把 uvicorn.access 日志降级为 DEBUG(经 loguru 后按日志等级过滤)。"""
_logging.getLogger("uvicorn.access").addFilter(_uvicorn_access_debug)
_logging.getLogger("uvicorn.access").addFilter(_uvicorn_access_debug)