From 0dccc23e767f79a2900509169eaf48ee5c4b3f3e Mon Sep 17 00:00:00 2001 From: DrSmoothl <1787882683@qq.com> Date: Sun, 15 Feb 2026 16:19:13 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20GitHub=20Actions=20?= =?UTF-8?q?=E5=B7=A5=E4=BD=9C=E6=B5=81=EF=BC=8C=E7=BB=9F=E4=B8=80=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=20ubuntu-24.04=20=E4=BD=9C=E4=B8=BA=E8=BF=90=E8=A1=8C?= =?UTF-8?q?=E7=8E=AF=E5=A2=83=EF=BC=9B=E6=96=B0=E5=A2=9E=E5=8F=91=E5=B8=83?= =?UTF-8?q?=20WebUI=20=E4=BA=A7=E7=89=A9=E7=9A=84=E5=B7=A5=E4=BD=9C?= =?UTF-8?q?=E6=B5=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/precheck.yml | 4 +- .github/workflows/publish-webui-dist.yml | 86 ++++++++++++++++++++++++ .github/workflows/ruff-pr.yml | 2 +- .github/workflows/ruff.yml | 7 +- src/config/config.py | 6 ++ src/webui/app.py | 30 +++++++-- src/webui/webui_server.py | 27 +++++++- 7 files changed, 147 insertions(+), 15 deletions(-) create mode 100644 .github/workflows/publish-webui-dist.yml diff --git a/.github/workflows/precheck.yml b/.github/workflows/precheck.yml index 39f7096f..f678cb17 100644 --- a/.github/workflows/precheck.yml +++ b/.github/workflows/precheck.yml @@ -4,7 +4,7 @@ on: [pull_request] jobs: conflict-check: - runs-on: [self-hosted, Windows, X64] + runs-on: ubuntu-24.04 outputs: conflict: ${{ steps.check-conflicts.outputs.conflict }} steps: @@ -25,7 +25,7 @@ jobs: } shell: pwsh labeler: - runs-on: [self-hosted, Windows, X64] + runs-on: ubuntu-24.04 needs: conflict-check if: needs.conflict-check.outputs.conflict == 'true' steps: diff --git a/.github/workflows/publish-webui-dist.yml b/.github/workflows/publish-webui-dist.yml new file mode 100644 index 00000000..0df8ddc0 --- /dev/null +++ b/.github/workflows/publish-webui-dist.yml @@ -0,0 +1,86 @@ +name: Publish WebUI Dist + +on: + push: + branches: + - main + paths: + - "dashboard/**" + workflow_dispatch: + +permissions: + contents: read + +jobs: + build-and-publish: + runs-on: ubuntu-24.04 + environment: webui + steps: + - uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Build dashboard + working-directory: dashboard + run: | + npm install + npm run build + + - name: Prepare dist package + run: | + rm -rf .webui_dist_pkg + mkdir -p .webui_dist_pkg/maibot_dashboard/dist + WEBUI_VERSION=$(python -c "import json; print(json.load(open('dashboard/package.json'))['version'])") + cat > .webui_dist_pkg/pyproject.toml <<'EOF' + [project] + name = "maibot-dashboard" + version = "${WEBUI_VERSION}" + description = "MaiBot WebUI static assets" + readme = "README.md" + requires-python = ">=3.10" + + [build-system] + requires = ["setuptools>=80.9.0", "wheel"] + build-backend = "setuptools.build_meta" + + [tool.setuptools] + packages = ["maibot_dashboard"] + include-package-data = true + + [tool.setuptools.package-data] + maibot_dashboard = ["dist/**"] + EOF + cat > .webui_dist_pkg/README.md <<'EOF' + # MaiBot WebUI Dist + + 该包仅包含 MaiBot WebUI 的前端构建产物(dist)。 + EOF + cat > .webui_dist_pkg/maibot_dashboard/__init__.py <<'EOF' + from .resources import get_dist_path + + __all__ = ["get_dist_path"] + EOF + cat > .webui_dist_pkg/maibot_dashboard/resources.py <<'EOF' + from pathlib import Path + + def get_dist_path() -> Path: + return Path(__file__).parent / "dist" + EOF + cp -a dashboard/dist/. .webui_dist_pkg/maibot_dashboard/dist/ + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Build and publish + working-directory: .webui_dist_pkg + env: + PYPI_API_TOKEN: ${{ secrets.PYPI_API_TOKEN }} + run: | + python -m pip install --upgrade build twine + python -m build + python -m twine upload -u __token__ -p "$PYPI_API_TOKEN" dist/* diff --git a/.github/workflows/ruff-pr.yml b/.github/workflows/ruff-pr.yml index 5dd9a456..1b6f795b 100644 --- a/.github/workflows/ruff-pr.yml +++ b/.github/workflows/ruff-pr.yml @@ -2,7 +2,7 @@ name: Ruff PR Check on: [ pull_request ] jobs: ruff: - runs-on: [self-hosted, Windows, X64] + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 with: diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml index 3d2e7d1f..22401da3 100644 --- a/.github/workflows/ruff.yml +++ b/.github/workflows/ruff.yml @@ -8,17 +8,12 @@ on: # - dev-refactor # 例如:匹配所有以 feature/ 开头的分支 # # 添加你希望触发此 workflow 的其他分支 workflow_dispatch: # 允许手动触发工作流 - branches: - - main - - dev - - dev-refactor - permissions: contents: write jobs: ruff: - runs-on: [self-hosted, Windows, X64] + runs-on: ubuntu-24.04 # 关键修改:添加条件判断 # 确保只有在 event_name 是 'push' 且不是由 Pull Request 引起的 push 时才运行 if: github.event_name == 'push' && !startsWith(github.ref, 'refs/pull/') diff --git a/src/config/config.py b/src/config/config.py index c1d23018..c2ebd345 100644 --- a/src/config/config.py +++ b/src/config/config.py @@ -215,6 +215,12 @@ class ConfigManager: def register_reload_callback(self, callback: Callable[[], object]) -> None: self._reload_callbacks.append(callback) + def unregister_reload_callback(self, callback: Callable[[], object]) -> None: + try: + self._reload_callbacks.remove(callback) + except ValueError: + return + async def reload_config(self) -> bool: async with self._reload_lock: try: diff --git a/src/webui/app.py b/src/webui/app.py index 3731f962..6f3f7e74 100644 --- a/src/webui/app.py +++ b/src/webui/app.py @@ -1,7 +1,8 @@ """FastAPI 应用工厂 - 创建和配置 WebUI 应用实例""" -import mimetypes +from importlib import import_module from pathlib import Path +import mimetypes from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import FileResponse @@ -113,12 +114,15 @@ def _setup_static_files(app: FastAPI): mimetypes.add_type("text/css", ".css") mimetypes.add_type("application/json", ".json") - base_dir = Path(__file__).parent.parent.parent - static_path = base_dir / "webui" / "dist" + static_path = _resolve_static_path() + if static_path is None: + logger.warning("❌ WebUI 静态文件目录不存在") + logger.warning("💡 请先构建前端: cd dashboard && npm run build") + return if not static_path.exists(): logger.warning(f"❌ WebUI 静态文件目录不存在: {static_path}") - logger.warning("💡 请先构建前端: cd webui && npm run build") + logger.warning("💡 请先构建前端: cd dashboard && npm run build") return if not (static_path / "index.html").exists(): @@ -148,6 +152,24 @@ def _setup_static_files(app: FastAPI): logger.info(f"✅ WebUI 静态文件服务已配置: {static_path}") +def _resolve_static_path() -> Path | None: + try: + module = import_module("maibot_dashboard") + get_dist_path = getattr(module, "get_dist_path", None) + if callable(get_dist_path): + package_path = get_dist_path() + if isinstance(package_path, Path) and package_path.exists(): + return package_path + except Exception: + pass + + base_dir = Path(__file__).parent.parent.parent + static_path = base_dir / "webui" / "dist" + if static_path.exists(): + return static_path + return None + + def show_access_token(): """显示 WebUI Access Token(供启动时调用)""" try: diff --git a/src/webui/webui_server.py b/src/webui/webui_server.py index 7e2afbb5..34b2d353 100644 --- a/src/webui/webui_server.py +++ b/src/webui/webui_server.py @@ -1,23 +1,44 @@ """独立的 WebUI 服务器 - 运行在 0.0.0.0:8001""" -import asyncio from uvicorn import Config, Server as UvicornServer + +import asyncio + from src.common.logger import get_logger +from src.config.config import config_manager from src.webui.app import create_app, show_access_token logger = get_logger("webui_server") +class _ASGIProxy: + def __init__(self, app): + self._app = app + + def set_app(self, app) -> None: + self._app = app + + async def __call__(self, scope, receive, send): + await self._app(scope, receive, send) + + class WebUIServer: """独立的 WebUI 服务器""" def __init__(self, host: str = "0.0.0.0", port: int = 8001): self.host = host self.port = port - self.app = create_app(host=host, port=port, enable_static=True) + self._app = create_app(host=host, port=port, enable_static=True) + self.app = _ASGIProxy(self._app) self._server = None show_access_token() + config_manager.register_reload_callback(self.reload_app) + + async def reload_app(self) -> None: + self._app = create_app(host=self.host, port=self.port, enable_static=True) + self.app.set_app(self._app) + logger.info("WebUI 应用已热重载") async def start(self): """启动服务器""" @@ -71,6 +92,8 @@ class WebUIServer: except Exception as e: logger.error(f"❌ WebUI 服务器运行错误: {e}", exc_info=True) raise + finally: + config_manager.unregister_reload_callback(self.reload_app) def _check_port_available(self) -> bool: """检查端口是否可用(支持 IPv4 和 IPv6)"""