更新 GitHub Actions 工作流,统一使用 ubuntu-24.04 作为运行环境;新增发布 WebUI 产物的工作流

pull/1496/head
DrSmoothl 2026-02-15 16:19:13 +08:00
parent 5799ce7efe
commit 0dccc23e76
No known key found for this signature in database
7 changed files with 147 additions and 15 deletions

View File

@ -4,7 +4,7 @@ on: [pull_request]
jobs: jobs:
conflict-check: conflict-check:
runs-on: [self-hosted, Windows, X64] runs-on: ubuntu-24.04
outputs: outputs:
conflict: ${{ steps.check-conflicts.outputs.conflict }} conflict: ${{ steps.check-conflicts.outputs.conflict }}
steps: steps:
@ -25,7 +25,7 @@ jobs:
} }
shell: pwsh shell: pwsh
labeler: labeler:
runs-on: [self-hosted, Windows, X64] runs-on: ubuntu-24.04
needs: conflict-check needs: conflict-check
if: needs.conflict-check.outputs.conflict == 'true' if: needs.conflict-check.outputs.conflict == 'true'
steps: steps:

View File

@ -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/*

View File

@ -2,7 +2,7 @@ name: Ruff PR Check
on: [ pull_request ] on: [ pull_request ]
jobs: jobs:
ruff: ruff:
runs-on: [self-hosted, Windows, X64] runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:

View File

@ -8,17 +8,12 @@ on:
# - dev-refactor # 例如:匹配所有以 feature/ 开头的分支 # - dev-refactor # 例如:匹配所有以 feature/ 开头的分支
# # 添加你希望触发此 workflow 的其他分支 # # 添加你希望触发此 workflow 的其他分支
workflow_dispatch: # 允许手动触发工作流 workflow_dispatch: # 允许手动触发工作流
branches:
- main
- dev
- dev-refactor
permissions: permissions:
contents: write contents: write
jobs: jobs:
ruff: ruff:
runs-on: [self-hosted, Windows, X64] runs-on: ubuntu-24.04
# 关键修改:添加条件判断 # 关键修改:添加条件判断
# 确保只有在 event_name 是 'push' 且不是由 Pull Request 引起的 push 时才运行 # 确保只有在 event_name 是 'push' 且不是由 Pull Request 引起的 push 时才运行
if: github.event_name == 'push' && !startsWith(github.ref, 'refs/pull/') if: github.event_name == 'push' && !startsWith(github.ref, 'refs/pull/')

View File

@ -215,6 +215,12 @@ class ConfigManager:
def register_reload_callback(self, callback: Callable[[], object]) -> None: def register_reload_callback(self, callback: Callable[[], object]) -> None:
self._reload_callbacks.append(callback) 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 def reload_config(self) -> bool:
async with self._reload_lock: async with self._reload_lock:
try: try:

View File

@ -1,7 +1,8 @@
"""FastAPI 应用工厂 - 创建和配置 WebUI 应用实例""" """FastAPI 应用工厂 - 创建和配置 WebUI 应用实例"""
import mimetypes from importlib import import_module
from pathlib import Path from pathlib import Path
import mimetypes
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse from fastapi.responses import FileResponse
@ -113,12 +114,15 @@ def _setup_static_files(app: FastAPI):
mimetypes.add_type("text/css", ".css") mimetypes.add_type("text/css", ".css")
mimetypes.add_type("application/json", ".json") mimetypes.add_type("application/json", ".json")
base_dir = Path(__file__).parent.parent.parent static_path = _resolve_static_path()
static_path = base_dir / "webui" / "dist" if static_path is None:
logger.warning("❌ WebUI 静态文件目录不存在")
logger.warning("💡 请先构建前端: cd dashboard && npm run build")
return
if not static_path.exists(): if not static_path.exists():
logger.warning(f"❌ WebUI 静态文件目录不存在: {static_path}") logger.warning(f"❌ WebUI 静态文件目录不存在: {static_path}")
logger.warning("💡 请先构建前端: cd webui && npm run build") logger.warning("💡 请先构建前端: cd dashboard && npm run build")
return return
if not (static_path / "index.html").exists(): if not (static_path / "index.html").exists():
@ -148,6 +152,24 @@ def _setup_static_files(app: FastAPI):
logger.info(f"✅ WebUI 静态文件服务已配置: {static_path}") 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(): def show_access_token():
"""显示 WebUI Access Token供启动时调用""" """显示 WebUI Access Token供启动时调用"""
try: try:

View File

@ -1,23 +1,44 @@
"""独立的 WebUI 服务器 - 运行在 0.0.0.0:8001""" """独立的 WebUI 服务器 - 运行在 0.0.0.0:8001"""
import asyncio
from uvicorn import Config, Server as UvicornServer from uvicorn import Config, Server as UvicornServer
import asyncio
from src.common.logger import get_logger from src.common.logger import get_logger
from src.config.config import config_manager
from src.webui.app import create_app, show_access_token from src.webui.app import create_app, show_access_token
logger = get_logger("webui_server") 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: class WebUIServer:
"""独立的 WebUI 服务器""" """独立的 WebUI 服务器"""
def __init__(self, host: str = "0.0.0.0", port: int = 8001): def __init__(self, host: str = "0.0.0.0", port: int = 8001):
self.host = host self.host = host
self.port = port 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 self._server = None
show_access_token() 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): async def start(self):
"""启动服务器""" """启动服务器"""
@ -71,6 +92,8 @@ class WebUIServer:
except Exception as e: except Exception as e:
logger.error(f"❌ WebUI 服务器运行错误: {e}", exc_info=True) logger.error(f"❌ WebUI 服务器运行错误: {e}", exc_info=True)
raise raise
finally:
config_manager.unregister_reload_callback(self.reload_app)
def _check_port_available(self) -> bool: def _check_port_available(self) -> bool:
"""检查端口是否可用(支持 IPv4 和 IPv6""" """检查端口是否可用(支持 IPv4 和 IPv6"""