import os import sys import atexit import asyncio import argparse import subprocess from pathlib import Path import tomli import uvicorn from loguru import logger from upgrade_codes.upgrade_manager import UpgradeManager from src.open_llm_vtuber.server import WebSocketServer from src.open_llm_vtuber.config_manager import Config, read_yaml, validate_config os.environ["HF_HOME"] = str(Path(__file__).parent / "models") os.environ["MODELSCOPE_CACHE"] = str(Path(__file__).parent / "models") upgrade_manager = UpgradeManager() def load_env_file(env_path: str = ".env") -> None: """Load key-value pairs from a .env file into environment variables. Existing environment variables are preserved and not overridden. """ path = Path(env_path) if not path.exists(): return loaded_count = 0 for raw_line in path.read_text(encoding="utf-8").splitlines(): line = raw_line.strip() if not line or line.startswith("#"): continue if line.startswith("export "): line = line[len("export ") :].strip() if "=" not in line: continue key, value = line.split("=", 1) key = key.strip() value = value.strip() if not key: continue if ( len(value) >= 2 and value[0] == value[-1] and value[0] in {'"', "'"} ): value = value[1:-1] if key not in os.environ: os.environ[key] = value loaded_count += 1 logger.info(f"Loaded {loaded_count} variables from {path}") def get_version() -> str: with open("pyproject.toml", "rb") as f: pyproject = tomli.load(f) return pyproject["project"]["version"] def init_logger(console_log_level: str = "INFO") -> None: logger.remove() # Console output logger.add( sys.stderr, level=console_log_level, format="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {name}:{function}:{line} | {message}", colorize=True, ) # File output logger.add( "logs/debug_{time:YYYY-MM-DD}.log", rotation="10 MB", retention="30 days", level="DEBUG", format="{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {name}:{function}:{line} | {message} | {extra}", backtrace=True, diagnose=True, ) def check_frontend_submodule(lang=None): """ Check if the frontend submodule is initialized. If not, attempt to initialize it. If initialization fails, log an error message. """ if lang is None: lang = upgrade_manager.lang frontend_path = Path(__file__).parent / "frontend" / "index.html" if not frontend_path.exists(): if lang == "id": logger.warning("Tidak ditemukan submodul front-end. Mencoba menginisialisasi submodul...") else: logger.warning( "Frontend submodule not found, attempting to initialize submodules..." ) try: subprocess.run( ["git", "submodule", "update", "--init", "--recursive"], check=True ) if frontend_path.exists(): if lang == "id": logger.info("👍 Submodul front-end (dan submodul lainnya) berhasil diinisialisasi.") else: logger.info( "👍 Frontend submodule (and other submodules) initialized successfully." ) else: if lang == "id": logger.critical( 'Gagal menginisialisasi submodul.\nAnda mungkin akan melihat pesan kesalahan {{"detail":"Not Found"}} di browser. Silakan periksa panduan cepat memulai dan halaman masalah umum untuk informasi lebih lanjut.' ) logger.error( "Did you manually change or delete the `frontend` folder? \n" + "It's a Git submodule — you shouldn't modify it directly. \n" + "If you did, discard your changes with `git restore frontend`, then try again.\n" ) else: logger.critical( 'Failed to initialize submodules. \nYou might see {{"detail":"Not Found"}} in your browser. Please check our quick start guide and common issues page from our documentation.' ) logger.error( "Frontend files are still missing after submodule initialization.\n" + "Did you manually change or delete the `frontend` folder? \n" + "It's a Git submodule — you shouldn't modify it directly. \n" + "If you did, discard your changes with `git restore frontend`, then try again.\n" ) except Exception as e: if lang == "id": logger.critical( f'Gagal menginisialisasi submodul: {e}。\nAnda mungkin mengalami masalah jaringan dengan GitHub. Anda mungkin akan melihat pesan kesalahan {{"detail":"Not Found"}} di browser. Silakan periksa panduan cepat memulai dan halaman masalah umum untuk informasi lebih lanjut.\n' ) else: logger.critical( f'Failed to initialize submodules: {e}. \nYou might see {{"detail":"Not Found"}} in your browser. Please check our quick start guide and common issues page from our documentation.\n' ) def parse_args(): parser = argparse.ArgumentParser(description="Open-LLM-VTuber Server") parser.add_argument("--verbose", action="store_true", help="Enable verbose logging") parser.add_argument( "--hf_mirror", action="store_true", help="Use Hugging Face mirror" ) return parser.parse_args() @logger.catch def run(console_log_level: str): init_logger(console_log_level) logger.info(f"Open-LLM-VTuber, version v{get_version()}") # Get selected language lang = upgrade_manager.lang # Check if the frontend submodule is initialized check_frontend_submodule(lang) # Sync user config with default config try: upgrade_manager.sync_user_config() except Exception as e: logger.error(f"Error syncing user config: {e}") atexit.register(WebSocketServer.clean_cache) # Load configurations from yaml file config: Config = validate_config(read_yaml("conf.yaml")) server_config = config.system_config if server_config.enable_proxy: logger.info("Proxy mode enabled - /proxy-ws endpoint will be available") # Initialize the WebSocket server (synchronous part) server = WebSocketServer(config=config) # Perform asynchronous initialization (loading context, etc.) logger.info("Initializing server context...") try: asyncio.run(server.initialize()) logger.info("Server context initialized successfully.") except Exception as e: logger.error(f"Failed to initialize server context: {e}") sys.exit(1) # Exit if initialization fails # Run the Uvicorn server logger.info(f"Starting server on {server_config.host}:{server_config.port}") uvicorn.run( app=server.app, host=server_config.host, port=server_config.port, log_level=console_log_level.lower(), ) if __name__ == "__main__": load_env_file() args = parse_args() console_log_level = "DEBUG" if args.verbose else "INFO" if args.verbose: logger.info("Running in verbose mode") else: logger.info( "Running in standard mode. For detailed debug logs, use: uv run run_server.py --verbose" ) if args.hf_mirror: os.environ["HF_ENDPOINT"] = "https://hf-mirror.com" run(console_log_level=console_log_level)