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 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 == "zh":
logger.warning("未找到前端子模块,正在尝试初始化子模块...")
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 == "zh":
logger.info("👍 前端子模块(和其他子模块)初始化成功。")
else:
logger.info(
"👍 Frontend submodule (and other submodules) initialized successfully."
)
else:
if lang == "zh":
logger.critical(
'子模块初始化失败。\n你之后可能会在浏览器中看到 {{"detail":"Not Found"}} 的错误提示。请检查我们的快速入门指南和常见问题页面以获取更多信息。'
)
logger.error(
"初始化子模块后,前端文件仍然缺失。\n"
+ "你是否手动更改或删除了 `frontend` 文件夹?\n"
+ "它是一个 Git 子模块 - 你不应该直接修改它。\n"
+ "如果你这样做了,请使用 `git restore frontend` 丢弃你的更改,然后再试一次。\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 == "zh":
logger.critical(
f'初始化子模块失败: {e}。\n怀疑你跟 GitHub 之间有网络问题。你之后可能会在浏览器中看到 {{"detail":"Not Found"}} 的错误提示。请检查我们的快速入门指南和常见问题页面以获取更多信息。\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__":
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)