feat: 添加 WebUI 静态资源包提示和相关错误处理逻辑
This commit is contained in:
@@ -80,6 +80,7 @@
|
||||
"startup.webui_auto_build_tool_missing": "❌ No supported frontend build tool was found, cannot auto-build WebUI",
|
||||
"startup.webui_cors_configured": "✅ CORS middleware configured",
|
||||
"startup.webui_dashboard_source_missing": "❌ WebUI frontend source directory not found: {dashboard_root}",
|
||||
"startup.webui_dashboard_package_hint": "💡 Install the prebuilt WebUI static asset package first, for example: {command}",
|
||||
"startup.webui_disabled": "WebUI is disabled",
|
||||
"startup.webui_index_missing": "❌ index.html not found: {index_path}",
|
||||
"startup.webui_manual_build_hint": "💡 Automatic recovery could not restore the frontend assets. Install dependencies and build manually in the dashboard directory: {command}",
|
||||
@@ -87,6 +88,7 @@
|
||||
"startup.webui_robots_route_register_failed": "❌ Failed to register robots.txt route: {error}",
|
||||
"startup.webui_robots_route_registered": "✅ robots.txt route registered",
|
||||
"startup.webui_server_init_failed": "Failed to initialize WebUI server: {error}",
|
||||
"startup.webui_static_assets_unavailable": "❌ No usable WebUI static assets were found (installed maibot-dashboard package or local dashboard/dist)",
|
||||
"startup.webui_static_assets_try_auto_build": "⚠️ WebUI static assets are unavailable, attempting to auto-build the frontend...",
|
||||
"startup.webui_static_dir_missing": "❌ WebUI static directory does not exist",
|
||||
"startup.webui_static_dir_missing_with_path": "❌ WebUI static directory does not exist: {static_path}",
|
||||
|
||||
@@ -80,6 +80,7 @@
|
||||
"startup.webui_auto_build_tool_missing": "❌ 利用可能なフロントエンドビルドツールが見つからず、WebUI を自動ビルドできません",
|
||||
"startup.webui_cors_configured": "✅ CORS ミドルウェアを設定しました",
|
||||
"startup.webui_dashboard_source_missing": "❌ WebUI フロントエンドのソースディレクトリが見つかりません: {dashboard_root}",
|
||||
"startup.webui_dashboard_package_hint": "💡 事前ビルド済みの WebUI 静的リソースパッケージを先にインストールしてください。例: {command}",
|
||||
"startup.webui_disabled": "WebUI は無効です",
|
||||
"startup.webui_index_missing": "❌ index.html が見つかりません: {index_path}",
|
||||
"startup.webui_manual_build_hint": "💡 自動復旧でフロントエンド資産を復旧できなかったため、dashboard ディレクトリで依存関係をインストールして手動ビルドしてください: {command}",
|
||||
@@ -87,6 +88,7 @@
|
||||
"startup.webui_robots_route_register_failed": "❌ robots.txt ルートの登録に失敗しました: {error}",
|
||||
"startup.webui_robots_route_registered": "✅ robots.txt ルートを登録しました",
|
||||
"startup.webui_server_init_failed": "WebUI サーバーの初期化に失敗しました: {error}",
|
||||
"startup.webui_static_assets_unavailable": "❌ 利用可能な WebUI 静的アセットが見つかりません(インストール済みの maibot-dashboard パッケージまたはローカルの dashboard/dist)",
|
||||
"startup.webui_static_assets_try_auto_build": "⚠️ WebUI の静的アセットが利用できないため、フロントエンドの自動ビルドを試行します...",
|
||||
"startup.webui_static_dir_missing": "❌ WebUI の静的ディレクトリが存在しません",
|
||||
"startup.webui_static_dir_missing_with_path": "❌ WebUI の静的ディレクトリが存在しません: {static_path}",
|
||||
|
||||
@@ -80,6 +80,7 @@
|
||||
"startup.webui_auto_build_tool_missing": "❌ 사용 가능한 프론트엔드 빌드 도구를 찾을 수 없어 WebUI를 자동으로 빌드할 수 없습니다",
|
||||
"startup.webui_cors_configured": "✅ CORS 미들웨어 설정 완료",
|
||||
"startup.webui_dashboard_source_missing": "❌ WebUI 프론트엔드 소스 디렉터리를 찾을 수 없습니다: {dashboard_root}",
|
||||
"startup.webui_dashboard_package_hint": "💡 미리 빌드된 WebUI 정적 리소스 패키지를 먼저 설치하세요. 예: {command}",
|
||||
"startup.webui_disabled": "WebUI가 비활성화되었습니다",
|
||||
"startup.webui_index_missing": "❌ index.html을 찾을 수 없습니다: {index_path}",
|
||||
"startup.webui_manual_build_hint": "💡 자동 복구로 프론트엔드 리소스를 수정하지 못했습니다. dashboard 디렉터리에서 의존성을 설치하고 수동으로 빌드하세요: {command}",
|
||||
@@ -87,6 +88,7 @@
|
||||
"startup.webui_robots_route_register_failed": "❌ robots.txt 라우트 등록 실패: {error}",
|
||||
"startup.webui_robots_route_registered": "✅ robots.txt 라우트 등록 완료",
|
||||
"startup.webui_server_init_failed": "WebUI 서버 초기화 실패: {error}",
|
||||
"startup.webui_static_assets_unavailable": "❌ 사용할 수 있는 WebUI 정적 리소스를 찾을 수 없습니다(설치된 maibot-dashboard 패키지 또는 로컬 dashboard/dist)",
|
||||
"startup.webui_static_assets_try_auto_build": "⚠️ WebUI 정적 리소스를 사용할 수 없어 프론트엔드 자동 빌드를 시도합니다...",
|
||||
"startup.webui_static_dir_missing": "❌ WebUI 정적 파일 디렉터리가 존재하지 않습니다",
|
||||
"startup.webui_static_dir_missing_with_path": "❌ WebUI 정적 파일 디렉터리가 존재하지 않습니다: {static_path}",
|
||||
|
||||
@@ -80,6 +80,7 @@
|
||||
"startup.webui_auto_build_tool_missing": "❌ 未找到可用的前端构建工具,无法自动构建 WebUI",
|
||||
"startup.webui_cors_configured": "✅ CORS 中间件已配置",
|
||||
"startup.webui_dashboard_source_missing": "❌ 未找到 WebUI 前端源码目录: {dashboard_root}",
|
||||
"startup.webui_dashboard_package_hint": "💡 请安装预构建的 WebUI 静态资源包,例如: {command}",
|
||||
"startup.webui_disabled": "WebUI 已禁用",
|
||||
"startup.webui_index_missing": "❌ 未找到 index.html: {index_path}",
|
||||
"startup.webui_manual_build_hint": "💡 自动恢复未能修复前端资源,请在 dashboard 目录安装依赖并手动构建: {command}",
|
||||
@@ -87,6 +88,7 @@
|
||||
"startup.webui_robots_route_register_failed": "❌ 注册 robots.txt 路由失败: {error}",
|
||||
"startup.webui_robots_route_registered": "✅ robots.txt 路由已注册",
|
||||
"startup.webui_server_init_failed": "初始化 WebUI 服务器失败: {error}",
|
||||
"startup.webui_static_assets_unavailable": "❌ 未找到可用的 WebUI 静态资源(已安装的 maibot-dashboard 包或本地 dashboard/dist)",
|
||||
"startup.webui_static_assets_try_auto_build": "⚠️ WebUI 静态资源不可用,尝试自动构建前端...",
|
||||
"startup.webui_static_dir_missing": "❌ WebUI 静态文件目录不存在",
|
||||
"startup.webui_static_dir_missing_with_path": "❌ WebUI 静态文件目录不存在: {static_path}",
|
||||
|
||||
@@ -19,6 +19,7 @@ dependencies = [
|
||||
"jieba>=0.42.1",
|
||||
"json-repair>=0.47.6",
|
||||
"maim-message>=0.6.2",
|
||||
"maibot-dashboard==1.0.0.dev2026040439",
|
||||
"maibot-plugin-sdk>=2.3.0",
|
||||
"mcp",
|
||||
"msgpack>=1.1.2",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
@@ -10,54 +11,42 @@ def test_ensure_static_path_ready_uses_existing_static_path(tmp_path) -> None:
|
||||
static_path.mkdir()
|
||||
(static_path / "index.html").write_text("<html></html>", encoding="utf-8")
|
||||
|
||||
with (
|
||||
patch.object(webui_app, "_resolve_static_path", return_value=static_path),
|
||||
patch.object(webui_app, "_try_build_dashboard") as build_mock,
|
||||
):
|
||||
with patch.object(webui_app, "_resolve_static_path", return_value=static_path):
|
||||
result = webui_app._ensure_static_path_ready()
|
||||
|
||||
assert result == static_path
|
||||
build_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_ensure_static_path_ready_retries_after_auto_build(tmp_path) -> None:
|
||||
static_path = tmp_path / "dist"
|
||||
static_path.mkdir()
|
||||
(static_path / "index.html").write_text("<html></html>", encoding="utf-8")
|
||||
|
||||
with (
|
||||
patch.object(webui_app, "_resolve_static_path", side_effect=[None, static_path]),
|
||||
patch.object(
|
||||
webui_app,
|
||||
"_try_build_dashboard",
|
||||
return_value=webui_app.DashboardAutoRecoveryResult(succeeded=True),
|
||||
) as build_mock,
|
||||
):
|
||||
result = webui_app._ensure_static_path_ready()
|
||||
|
||||
assert result == static_path
|
||||
build_mock.assert_called_once_with()
|
||||
|
||||
|
||||
def test_ensure_static_path_ready_logs_manual_hint_when_auto_build_fails() -> None:
|
||||
def test_ensure_static_path_ready_logs_install_hint_when_static_assets_are_missing() -> None:
|
||||
with (
|
||||
patch.object(webui_app, "_resolve_static_path", return_value=None),
|
||||
patch.object(
|
||||
webui_app,
|
||||
"_try_build_dashboard",
|
||||
return_value=webui_app.DashboardAutoRecoveryResult(
|
||||
succeeded=False,
|
||||
manual_recovery_command=webui_app._MANUAL_BUILD_COMMAND,
|
||||
),
|
||||
),
|
||||
patch.object(webui_app.logger, "warning") as warning_mock,
|
||||
):
|
||||
result = webui_app._ensure_static_path_ready()
|
||||
|
||||
assert result is None
|
||||
warning_mock.assert_any_call(webui_app.t("startup.webui_auto_recovery_failed"))
|
||||
warning_mock.assert_any_call(webui_app.t("startup.webui_static_assets_unavailable"))
|
||||
warning_mock.assert_any_call(
|
||||
webui_app.t("startup.webui_manual_build_hint", command=webui_app._MANUAL_BUILD_COMMAND)
|
||||
webui_app.t("startup.webui_dashboard_package_hint", command=webui_app._MANUAL_INSTALL_COMMAND)
|
||||
)
|
||||
|
||||
|
||||
def test_ensure_static_path_ready_logs_index_error_when_static_path_is_invalid(tmp_path) -> None:
|
||||
static_path = tmp_path / "dist"
|
||||
static_path.mkdir()
|
||||
|
||||
with (
|
||||
patch.object(webui_app, "_resolve_static_path", return_value=static_path),
|
||||
patch.object(webui_app.logger, "warning") as warning_mock,
|
||||
):
|
||||
result = webui_app._ensure_static_path_ready()
|
||||
|
||||
assert result is None
|
||||
warning_mock.assert_any_call(
|
||||
webui_app.t("startup.webui_index_missing", index_path=static_path / "index.html")
|
||||
)
|
||||
warning_mock.assert_any_call(
|
||||
webui_app.t("startup.webui_dashboard_package_hint", command=webui_app._MANUAL_INSTALL_COMMAND)
|
||||
)
|
||||
|
||||
|
||||
@@ -73,43 +62,21 @@ def test_setup_static_files_does_not_duplicate_warning_when_static_path_is_unava
|
||||
warning_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_get_dashboard_build_command_defaults_to_npm(tmp_path) -> None:
|
||||
(tmp_path / "package.json").write_text("{}", encoding="utf-8")
|
||||
def test_resolve_static_path_prefers_installed_dashboard_package(monkeypatch, tmp_path) -> None:
|
||||
package_dist = tmp_path / "site-packages" / "maibot_dashboard" / "dist"
|
||||
package_dist.mkdir(parents=True)
|
||||
|
||||
with patch.object(
|
||||
webui_app.shutil,
|
||||
"which",
|
||||
side_effect=lambda tool_name: "/usr/bin/npm" if tool_name == "npm" else None,
|
||||
):
|
||||
command = webui_app._get_dashboard_build_command(tmp_path)
|
||||
class _DashboardModule:
|
||||
@staticmethod
|
||||
def get_dist_path() -> Path:
|
||||
return package_dist
|
||||
|
||||
assert command == ["npm", "run", "build"]
|
||||
monkeypatch.setattr(webui_app, "_get_project_root", lambda: tmp_path)
|
||||
|
||||
with patch.object(webui_app, "import_module", return_value=_DashboardModule()):
|
||||
resolved_path = webui_app._resolve_static_path()
|
||||
|
||||
def test_try_build_dashboard_installs_missing_dependencies_before_build(monkeypatch, tmp_path) -> None:
|
||||
(tmp_path / "package.json").write_text("{}", encoding="utf-8")
|
||||
run_results = [
|
||||
webui_app.CompletedProcess(args=["npm", "install", "--no-package-lock"], returncode=0, stdout="", stderr=""),
|
||||
webui_app.CompletedProcess(args=["npm", "run", "build"], returncode=0, stdout="", stderr=""),
|
||||
]
|
||||
|
||||
monkeypatch.setattr(webui_app, "_get_dashboard_root", lambda: tmp_path)
|
||||
monkeypatch.setattr(webui_app, "_should_auto_install_dashboard_dependencies", lambda dashboard_root: True)
|
||||
|
||||
with (
|
||||
patch.object(webui_app, "_get_dashboard_build_command", return_value=["npm", "run", "build"]),
|
||||
patch.object(webui_app, "run", side_effect=run_results) as run_mock,
|
||||
):
|
||||
result = webui_app._try_build_dashboard()
|
||||
|
||||
assert result.succeeded is True
|
||||
assert run_mock.call_count == 2
|
||||
install_call = run_mock.call_args_list[0]
|
||||
build_call = run_mock.call_args_list[1]
|
||||
assert install_call.args[0] == ["npm", "install", "--no-package-lock"]
|
||||
assert install_call.kwargs["cwd"] == tmp_path
|
||||
assert build_call.args[0] == ["npm", "run", "build"]
|
||||
assert build_call.kwargs["cwd"] == tmp_path
|
||||
assert resolved_path == package_dist
|
||||
|
||||
|
||||
def test_resolve_static_path_uses_dashboard_dist(monkeypatch, tmp_path) -> None:
|
||||
|
||||
223
src/webui/app.py
223
src/webui/app.py
@@ -1,12 +1,10 @@
|
||||
"""FastAPI 应用工厂 - 创建和配置 WebUI 应用实例"""
|
||||
|
||||
import mimetypes
|
||||
import shutil
|
||||
from dataclasses import dataclass
|
||||
from importlib import import_module
|
||||
from pathlib import Path
|
||||
from subprocess import CompletedProcess, TimeoutExpired, run
|
||||
from typing import Any, Dict, List, Tuple
|
||||
from typing import Any, Dict, Tuple
|
||||
|
||||
import mimetypes
|
||||
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
@@ -17,28 +15,8 @@ from src.common.logger import get_logger
|
||||
|
||||
logger = get_logger("webui.app")
|
||||
|
||||
_DASHBOARD_BUILD_TIMEOUT_SECONDS = 300
|
||||
_DASHBOARD_INSTALL_TIMEOUT_SECONDS = 600
|
||||
_MANUAL_BUILD_COMMAND = "cd dashboard && npm install && npm run build"
|
||||
|
||||
_DASHBOARD_BUILD_COMMANDS = {
|
||||
"bun": ["bun", "run", "build"],
|
||||
"npm": ["npm", "run", "build"],
|
||||
"pnpm": ["pnpm", "build"],
|
||||
"yarn": ["yarn", "build"],
|
||||
}
|
||||
_DASHBOARD_INSTALL_COMMANDS = {
|
||||
"bun": ["bun", "install"],
|
||||
"npm": ["npm", "install", "--no-package-lock"],
|
||||
"pnpm": ["pnpm", "install"],
|
||||
"yarn": ["yarn", "install"],
|
||||
}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DashboardAutoRecoveryResult:
|
||||
succeeded: bool
|
||||
manual_recovery_command: str | None = None
|
||||
_DASHBOARD_PACKAGE_NAME = "maibot-dashboard"
|
||||
_MANUAL_INSTALL_COMMAND = f"pip install {_DASHBOARD_PACKAGE_NAME}"
|
||||
|
||||
|
||||
def _resolve_safe_static_file_path(static_path: Path, full_path: str) -> Path | None:
|
||||
@@ -58,15 +36,6 @@ def _get_project_root() -> Path:
|
||||
return Path(__file__).resolve().parents[2]
|
||||
|
||||
|
||||
def _get_dashboard_root() -> Path:
|
||||
return _get_project_root() / "dashboard"
|
||||
|
||||
|
||||
def _format_dashboard_shell_commands(*commands: List[str]) -> str:
|
||||
formatted_commands = " && ".join(" ".join(command) for command in commands)
|
||||
return f"cd dashboard && {formatted_commands}"
|
||||
|
||||
|
||||
def _validate_static_path(static_path: Path | None) -> Tuple[str, Dict[str, Any]] | None:
|
||||
if static_path is None:
|
||||
return "startup.webui_static_dir_missing", {}
|
||||
@@ -81,185 +50,16 @@ def _validate_static_path(static_path: Path | None) -> Tuple[str, Dict[str, Any]
|
||||
return None
|
||||
|
||||
|
||||
def _summarize_command_output(command_result: CompletedProcess[str] | TimeoutExpired) -> str:
|
||||
output_chunks: List[str] = []
|
||||
stdout = command_result.stdout
|
||||
stderr = command_result.stderr
|
||||
|
||||
if isinstance(stdout, str) and stdout.strip():
|
||||
output_chunks.append(stdout.strip())
|
||||
if isinstance(stderr, str) and stderr.strip():
|
||||
output_chunks.append(stderr.strip())
|
||||
|
||||
if not output_chunks:
|
||||
return ""
|
||||
|
||||
combined_output = "\n".join(output_chunks)
|
||||
max_output_length = 2000
|
||||
if len(combined_output) <= max_output_length:
|
||||
return combined_output
|
||||
|
||||
return combined_output[-max_output_length:]
|
||||
|
||||
|
||||
def _get_preferred_dashboard_package_manager(dashboard_root: Path) -> str:
|
||||
if (dashboard_root / "pnpm-lock.yaml").exists():
|
||||
return "pnpm"
|
||||
if (dashboard_root / "yarn.lock").exists():
|
||||
return "yarn"
|
||||
if (dashboard_root / "bun.lock").exists() or (dashboard_root / "bun.lockb").exists():
|
||||
return "bun"
|
||||
return "npm"
|
||||
|
||||
|
||||
def _get_dashboard_build_command(dashboard_root: Path) -> List[str] | None:
|
||||
if not (dashboard_root / "package.json").exists():
|
||||
return None
|
||||
|
||||
preferred_package_manager = _get_preferred_dashboard_package_manager(dashboard_root)
|
||||
package_managers = [
|
||||
preferred_package_manager,
|
||||
*[
|
||||
package_manager
|
||||
for package_manager in _DASHBOARD_BUILD_COMMANDS
|
||||
if package_manager != preferred_package_manager
|
||||
],
|
||||
]
|
||||
|
||||
for package_manager in package_managers:
|
||||
if shutil.which(package_manager):
|
||||
return _DASHBOARD_BUILD_COMMANDS[package_manager]
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _get_dashboard_manual_recovery_command(dashboard_root: Path, build_command: List[str] | None = None) -> str:
|
||||
package_manager = (
|
||||
build_command[0] if build_command is not None else _get_preferred_dashboard_package_manager(dashboard_root)
|
||||
)
|
||||
install_command = _DASHBOARD_INSTALL_COMMANDS.get(package_manager)
|
||||
selected_build_command = _DASHBOARD_BUILD_COMMANDS.get(package_manager)
|
||||
|
||||
if install_command is None or selected_build_command is None:
|
||||
return _MANUAL_BUILD_COMMAND
|
||||
|
||||
return _format_dashboard_shell_commands(install_command, selected_build_command)
|
||||
|
||||
|
||||
def _should_auto_install_dashboard_dependencies(dashboard_root: Path) -> bool:
|
||||
return not (dashboard_root / "node_modules").exists()
|
||||
|
||||
|
||||
def _try_build_dashboard() -> DashboardAutoRecoveryResult:
|
||||
dashboard_root = _get_dashboard_root()
|
||||
if not dashboard_root.exists():
|
||||
logger.warning(t("startup.webui_dashboard_source_missing", dashboard_root=dashboard_root))
|
||||
return DashboardAutoRecoveryResult(succeeded=False)
|
||||
|
||||
build_command = _get_dashboard_build_command(dashboard_root)
|
||||
if build_command is None:
|
||||
logger.warning(t("startup.webui_auto_build_tool_missing"))
|
||||
manual_recovery_command = _get_dashboard_manual_recovery_command(dashboard_root)
|
||||
return DashboardAutoRecoveryResult(succeeded=False, manual_recovery_command=manual_recovery_command)
|
||||
|
||||
manual_recovery_command = _get_dashboard_manual_recovery_command(dashboard_root, build_command)
|
||||
|
||||
if _should_auto_install_dashboard_dependencies(dashboard_root):
|
||||
install_command = _DASHBOARD_INSTALL_COMMANDS[build_command[0]]
|
||||
logger.info(t("startup.webui_auto_install_started", command=" ".join(install_command)))
|
||||
|
||||
try:
|
||||
install_result = run(
|
||||
install_command,
|
||||
capture_output=True,
|
||||
check=False,
|
||||
cwd=dashboard_root,
|
||||
text=True,
|
||||
timeout=_DASHBOARD_INSTALL_TIMEOUT_SECONDS,
|
||||
)
|
||||
except TimeoutExpired as exc:
|
||||
logger.warning(
|
||||
t("startup.webui_auto_install_timeout", timeout_seconds=_DASHBOARD_INSTALL_TIMEOUT_SECONDS),
|
||||
)
|
||||
install_output = _summarize_command_output(exc)
|
||||
if install_output:
|
||||
logger.warning(t("startup.webui_auto_install_failed_output", output=install_output))
|
||||
return DashboardAutoRecoveryResult(succeeded=False, manual_recovery_command=manual_recovery_command)
|
||||
except OSError as exc:
|
||||
logger.warning(t("startup.webui_auto_install_exec_failed", error=exc))
|
||||
return DashboardAutoRecoveryResult(succeeded=False, manual_recovery_command=manual_recovery_command)
|
||||
|
||||
if install_result.returncode != 0:
|
||||
logger.warning(t("startup.webui_auto_install_failed", return_code=install_result.returncode))
|
||||
install_output = _summarize_command_output(install_result)
|
||||
if install_output:
|
||||
logger.warning(t("startup.webui_auto_install_failed_output", output=install_output))
|
||||
return DashboardAutoRecoveryResult(succeeded=False, manual_recovery_command=manual_recovery_command)
|
||||
|
||||
logger.info(t("startup.webui_auto_install_succeeded"))
|
||||
|
||||
logger.info(t("startup.webui_auto_build_started", command=" ".join(build_command)))
|
||||
|
||||
try:
|
||||
build_result = run(
|
||||
build_command,
|
||||
capture_output=True,
|
||||
check=False,
|
||||
cwd=dashboard_root,
|
||||
text=True,
|
||||
timeout=_DASHBOARD_BUILD_TIMEOUT_SECONDS,
|
||||
)
|
||||
except TimeoutExpired as exc:
|
||||
logger.warning(
|
||||
t("startup.webui_auto_build_timeout", timeout_seconds=_DASHBOARD_BUILD_TIMEOUT_SECONDS),
|
||||
)
|
||||
build_output = _summarize_command_output(exc)
|
||||
if build_output:
|
||||
logger.warning(t("startup.webui_auto_build_failed_output", output=build_output))
|
||||
return DashboardAutoRecoveryResult(succeeded=False, manual_recovery_command=manual_recovery_command)
|
||||
except OSError as exc:
|
||||
logger.warning(t("startup.webui_auto_build_exec_failed", error=exc))
|
||||
return DashboardAutoRecoveryResult(succeeded=False, manual_recovery_command=manual_recovery_command)
|
||||
|
||||
if build_result.returncode != 0:
|
||||
logger.warning(t("startup.webui_auto_build_failed", return_code=build_result.returncode))
|
||||
build_output = _summarize_command_output(build_result)
|
||||
if build_output:
|
||||
logger.warning(t("startup.webui_auto_build_failed_output", output=build_output))
|
||||
return DashboardAutoRecoveryResult(succeeded=False, manual_recovery_command=manual_recovery_command)
|
||||
|
||||
logger.info(t("startup.webui_auto_build_succeeded"))
|
||||
return DashboardAutoRecoveryResult(succeeded=True, manual_recovery_command=manual_recovery_command)
|
||||
|
||||
|
||||
def _ensure_static_path_ready() -> Path | None:
|
||||
static_path = _resolve_static_path()
|
||||
validation_error = _validate_static_path(static_path)
|
||||
if validation_error is None:
|
||||
return static_path
|
||||
|
||||
logger.info(t("startup.webui_static_assets_try_auto_build"))
|
||||
|
||||
auto_recovery_result = _try_build_dashboard()
|
||||
if auto_recovery_result.succeeded:
|
||||
static_path = _resolve_static_path()
|
||||
validation_error = _validate_static_path(static_path)
|
||||
if validation_error is None:
|
||||
return static_path
|
||||
logger.warning(t("startup.webui_auto_build_artifacts_invalid"))
|
||||
error_key, error_kwargs = validation_error
|
||||
logger.warning(t(error_key, **error_kwargs))
|
||||
logger.warning(
|
||||
t(
|
||||
"startup.webui_manual_build_hint",
|
||||
command=auto_recovery_result.manual_recovery_command or _MANUAL_BUILD_COMMAND,
|
||||
)
|
||||
)
|
||||
return None
|
||||
|
||||
if auto_recovery_result.manual_recovery_command is not None:
|
||||
logger.warning(t("startup.webui_auto_recovery_failed"))
|
||||
logger.warning(t("startup.webui_manual_build_hint", command=auto_recovery_result.manual_recovery_command))
|
||||
logger.warning(t("startup.webui_static_assets_unavailable"))
|
||||
error_key, error_kwargs = validation_error
|
||||
logger.warning(t(error_key, **error_kwargs))
|
||||
logger.warning(t("startup.webui_dashboard_package_hint", command=_MANUAL_INSTALL_COMMAND))
|
||||
return None
|
||||
|
||||
|
||||
@@ -371,12 +171,12 @@ def _setup_static_files(app: FastAPI):
|
||||
|
||||
if not static_path.exists():
|
||||
logger.warning(t("startup.webui_static_dir_missing_with_path", static_path=static_path))
|
||||
logger.warning(t("startup.webui_manual_build_hint", command=_MANUAL_BUILD_COMMAND))
|
||||
logger.warning(t("startup.webui_dashboard_package_hint", command=_MANUAL_INSTALL_COMMAND))
|
||||
return
|
||||
|
||||
if not (static_path / "index.html").exists():
|
||||
logger.warning(t("startup.webui_index_missing", index_path=static_path / "index.html"))
|
||||
logger.warning(t("startup.webui_manual_build_hint", command=_MANUAL_BUILD_COMMAND))
|
||||
logger.warning(t("startup.webui_dashboard_package_hint", command=_MANUAL_INSTALL_COMMAND))
|
||||
return
|
||||
|
||||
@app.get("/{full_path:path}", include_in_schema=False)
|
||||
@@ -415,6 +215,7 @@ def _resolve_static_path() -> Path | None:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 开发环境允许复用仓库里的现成 dist,但不再在用户机器上触发任何前端自愈构建。
|
||||
base_dir = _get_project_root()
|
||||
static_path = base_dir / "dashboard" / "dist"
|
||||
if static_path.exists():
|
||||
|
||||
Reference in New Issue
Block a user