diff --git a/locales/en-US/startup.json b/locales/en-US/startup.json index 7482666b..a1199960 100644 --- a/locales/en-US/startup.json +++ b/locales/en-US/startup.json @@ -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}", diff --git a/locales/ja-JP/startup.json b/locales/ja-JP/startup.json index 6a855dc6..3312ae28 100644 --- a/locales/ja-JP/startup.json +++ b/locales/ja-JP/startup.json @@ -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}", diff --git a/locales/ko/startup.json b/locales/ko/startup.json index 2f7ee595..39928337 100644 --- a/locales/ko/startup.json +++ b/locales/ko/startup.json @@ -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}", diff --git a/locales/zh-CN/startup.json b/locales/zh-CN/startup.json index 2290b652..cd7e3c99 100644 --- a/locales/zh-CN/startup.json +++ b/locales/zh-CN/startup.json @@ -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}", diff --git a/pyproject.toml b/pyproject.toml index cde3b837..a0f67c0b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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", diff --git a/pytests/webui/test_app.py b/pytests/webui/test_app.py index 48bfaaf4..66bde37d 100644 --- a/pytests/webui/test_app.py +++ b/pytests/webui/test_app.py @@ -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("", 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("", 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: diff --git a/src/webui/app.py b/src/webui/app.py index d0b98e26..76434fa9 100644 --- a/src/webui/app.py +++ b/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():