e4dcfc49aa
Tests / Import Check (Python 3.13) (push) Has been cancelled
Tests / Import Check (Python 3.14) (push) Has been cancelled
Tests / Python Tests (Python 3.11) (push) Has been cancelled
Tests / Python Tests (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.14) (push) Has been cancelled
Tests / Test Summary (push) Has been cancelled
Tests / Lint and Format (push) Has been cancelled
Tests / Web Node Tests (push) Has been cancelled
Tests / Import Check (Python 3.11) (push) Has been cancelled
Tests / Import Check (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.13) (push) Has been cancelled
124 lines
4.0 KiB
Python
124 lines
4.0 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from deeptutor.runtime.home import get_runtime_home
|
|
|
|
from .runtime_settings import RuntimeSettingsService
|
|
|
|
PROJECT_ROOT = get_runtime_home()
|
|
DEFAULT_BACKEND_PORT = 8001
|
|
DEFAULT_FRONTEND_PORT = 3782
|
|
DEFAULT_LANGUAGE = "en"
|
|
|
|
|
|
@dataclass(frozen=True, slots=True)
|
|
class LaunchSettings:
|
|
backend_port: int
|
|
frontend_port: int
|
|
language: str
|
|
source: str
|
|
settings_dir: Path
|
|
interface_json_path: Path
|
|
system_json_path: Path
|
|
|
|
|
|
def _load_json_object(path: Path) -> dict[str, Any]:
|
|
if not path.exists() or path.stat().st_size == 0:
|
|
return {}
|
|
try:
|
|
loaded = json.loads(path.read_text(encoding="utf-8"))
|
|
except Exception:
|
|
return {}
|
|
return loaded if isinstance(loaded, dict) else {}
|
|
|
|
|
|
def _coerce_port(value: Any) -> int | None:
|
|
try:
|
|
port = int(value)
|
|
except (TypeError, ValueError):
|
|
return None
|
|
if 1 <= port <= 65535:
|
|
return port
|
|
return None
|
|
|
|
|
|
def _normalize_language(value: Any) -> str | None:
|
|
if not isinstance(value, str):
|
|
return None
|
|
language = value.strip().lower()
|
|
if language in {"en", "english"} or language.startswith("en_"):
|
|
return "en"
|
|
if language in {"zh", "cn", "chinese"} or language.startswith("zh_"):
|
|
return "zh"
|
|
return None
|
|
|
|
|
|
def load_launch_settings(project_root: Path | None = None) -> LaunchSettings:
|
|
"""Load ports and UI language for launcher-style entry points.
|
|
|
|
Launch ports come from ``data/user/settings/system.json``. Environment
|
|
variables are treated as explicit deployment overrides (for example Docker
|
|
port mapping), not as a second application configuration file. UI language
|
|
remains in ``data/user/settings/interface.json``.
|
|
"""
|
|
|
|
root = project_root or PROJECT_ROOT
|
|
settings_dir = root / "data" / "user" / "settings"
|
|
interface_json_path = settings_dir / "interface.json"
|
|
system_json_path = settings_dir / "system.json"
|
|
|
|
interface_settings = _load_json_object(interface_json_path)
|
|
service = RuntimeSettingsService.get_instance(settings_dir)
|
|
system_from_file = service.load_system(include_process_overrides=False)
|
|
system_settings = service.load_system(include_process_overrides=True)
|
|
|
|
settings_backend_port = _coerce_port(system_from_file.get("backend_port"))
|
|
settings_frontend_port = _coerce_port(system_from_file.get("frontend_port"))
|
|
process_backend_port = _coerce_port(os.getenv("BACKEND_PORT"))
|
|
process_frontend_port = _coerce_port(os.getenv("FRONTEND_PORT"))
|
|
interface_language = _normalize_language(interface_settings.get("language"))
|
|
process_language = _normalize_language(os.getenv("UI_LANGUAGE")) or _normalize_language(
|
|
os.getenv("LANGUAGE")
|
|
)
|
|
backend_port = _coerce_port(system_settings.get("backend_port")) or DEFAULT_BACKEND_PORT
|
|
frontend_port = _coerce_port(system_settings.get("frontend_port")) or DEFAULT_FRONTEND_PORT
|
|
language = interface_language or process_language or DEFAULT_LANGUAGE
|
|
|
|
sources: list[str] = []
|
|
if settings_backend_port is not None or settings_frontend_port is not None:
|
|
sources.append("data/user/settings/system.json")
|
|
if process_backend_port is not None or process_frontend_port is not None:
|
|
sources.append("environment")
|
|
if not sources:
|
|
sources.append("defaults")
|
|
if process_language is not None:
|
|
sources.append("environment")
|
|
if interface_language is not None:
|
|
sources.append("data/user/settings/interface.json")
|
|
else:
|
|
sources.append("default language")
|
|
|
|
return LaunchSettings(
|
|
backend_port=backend_port,
|
|
frontend_port=frontend_port,
|
|
language=language,
|
|
source=" + ".join(sources),
|
|
settings_dir=settings_dir,
|
|
interface_json_path=interface_json_path,
|
|
system_json_path=system_json_path,
|
|
)
|
|
|
|
|
|
__all__ = [
|
|
"DEFAULT_BACKEND_PORT",
|
|
"DEFAULT_FRONTEND_PORT",
|
|
"DEFAULT_LANGUAGE",
|
|
"LaunchSettings",
|
|
"load_launch_settings",
|
|
]
|