diff --git a/.gitignore b/.gitignore index 283e424..479d71f 100644 --- a/.gitignore +++ b/.gitignore @@ -41,6 +41,8 @@ Thumbs.db # Config files with sensitive data config.yaml +config.yaml.bak +*.yaml.bak .env # Logs diff --git a/config.example.yaml b/config.example.yaml index 1d9e11d..dfa560e 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -1,108 +1,29 @@ -# ReelForge Configuration Example -# 复制此文件为 config.yaml 并填入你的配置 +# ReelForge Configuration +# Copy this file to config.yaml and fill in your settings +# ⚠️ Never commit config.yaml to Git! -# Project name project_name: ReelForge # ==================== LLM Configuration ==================== -# Simple 3-field configuration - works with any OpenAI SDK compatible LLM -# -# Popular choices (copy one of these): -# -# Qwen Max (推荐中文): -# api_key: "sk-xxx" -# base_url: "https://dashscope.aliyuncs.com/compatible-mode/v1" -# model: "qwen-max" -# # Get API key: https://dashscope.console.aliyun.com/apiKey -# -# OpenAI GPT-4o: -# api_key: "sk-xxx" -# base_url: "https://api.openai.com/v1" -# model: "gpt-4o" -# # Get API key: https://platform.openai.com/api-keys -# -# Claude Sonnet 4: -# api_key: "sk-ant-xxx" -# base_url: "https://api.anthropic.com/v1/" -# model: "claude-sonnet-4-5" -# # Get API key: https://console.anthropic.com/settings/keys -# -# DeepSeek (超高性价比): -# api_key: "sk-xxx" -# base_url: "https://api.deepseek.com" -# model: "deepseek-chat" -# # Get API key: https://platform.deepseek.com/api_keys -# -# Ollama (本地免费): -# api_key: "ollama" # Any value works -# base_url: "http://localhost:11434/v1" -# model: "llama3.2" -# # Install: https://ollama.com/download -# # Then: ollama pull llama3.2 -# +# Supports any OpenAI SDK compatible API llm: - api_key: "" # Fill in your API key - base_url: "" # LLM API endpoint - model: "" # Model name + api_key: "" + base_url: "" + model: "" + +# Popular presets: +# Qwen Max: base_url: "https://dashscope.aliyuncs.com/compatible-mode/v1" model: "qwen-max" +# OpenAI GPT-4o: base_url: "https://api.openai.com/v1" model: "gpt-4o" +# DeepSeek: base_url: "https://api.deepseek.com" model: "deepseek-chat" +# Ollama (Local): base_url: "http://localhost:11434/v1" model: "llama3.2" # ==================== TTS Configuration ==================== -# TTS supports two modes: -# 1. Edge TTS (default) - Free local SDK, no setup needed -# 2. ComfyUI Workflow - Workflow-based, requires ComfyUI -# -# Configuration (optional): tts: - default_workflow: "edge" # Default: "edge" (Edge TTS) or "tts_default.json" (ComfyUI workflow) - # comfyui_url: http://127.0.0.1:8188 # Only needed for ComfyUI workflows - -# Usage in code: -# await reelforge.tts(text="hello") # Uses default (edge) -# await reelforge.tts(text="hello", workflow="edge") # Explicitly use Edge TTS -# await reelforge.tts(text="hello", workflow="tts_custom.json") # Use ComfyUI workflow + default: edge # "edge" (free) or "tts_xxx.json" (ComfyUI workflow) # ==================== Image Generation Configuration ==================== -# Image generation uses ComfyUI workflows -# Workflows are auto-discovered from workflows/image_*.json files image: - default: default # Default preset name (uses workflows/image_default.json) - comfyui_url: http://127.0.0.1:8188 # Local ComfyUI server - # runninghub_api_key: "" # Optional: RunningHub cloud API key - - # Prompt prefix - automatically added to all image prompts - # Leave empty ("") if you don't want any prefix + default: image_default.json + comfyui_url: http://127.0.0.1:8188 + runninghub_api_key: "" prompt_prefix: "Pure white background, minimalist illustration, matchstick figure style, black and white line drawing, simple clean lines" - - # Common examples: - # prompt_prefix: "" # No prefix - # prompt_prefix: "anime style, vibrant colors, cel shading" # Anime style - # prompt_prefix: "watercolor painting, soft edges, artistic" # Watercolor - # prompt_prefix: "photorealistic, 8k, professional photography" # Realistic - -# ==================== Notes ==================== -# 1. LLM Configuration: -# - Simple 3-field config: api_key, base_url, model -# - Works with ANY OpenAI SDK compatible LLM -# - Popular choices listed in comments above -# - Switch LLM: just copy-paste different values from comments -# - WebUI provides quick preset selection -# -# 2. TTS Configuration: -# - Two modes: Edge TTS (default, free) or ComfyUI Workflow -# - Edge TTS: No setup needed, just use default -# - ComfyUI: Create workflow files in workflows/tts_*.json -# - Override in code: await reelforge.tts(text="...", workflow="edge" or "tts_xxx.json") -# -# 3. Image Generation: -# - Add workflow files: workflows/image_*.json -# - Auto-discovered presets: workflows/image_flux.json -> preset="flux" -# - Default preset: workflows/image_default.json -# -# 4. Ollama (Recommended for Privacy): -# - FREE: No API costs -# - PRIVATE: Data never leaves your machine -# - Install: https://ollama.com/download -# - Usage: ollama pull llama3.2 -# -# 5. Security: -# - Never commit config.yaml to version control -# - All sensitive data (API keys) should stay local diff --git a/reelforge/__init__.py b/reelforge/__init__.py index 2dc3b5f..8b81634 100644 --- a/reelforge/__init__.py +++ b/reelforge/__init__.py @@ -1,7 +1,7 @@ """ -ReelForge - AI-powered video generator with pluggable capabilities +ReelForge - AI-powered video generator -Convention-based capability system using FastMCP and LiteLLM. +Convention-based system with unified configuration management. Usage: from reelforge import reelforge @@ -18,8 +18,9 @@ Usage: """ from reelforge.service import ReelForgeCore, reelforge +from reelforge.config import config_manager __version__ = "0.1.0" -__all__ = ["ReelForgeCore", "reelforge"] +__all__ = ["ReelForgeCore", "reelforge", "config_manager"] diff --git a/reelforge/config.py b/reelforge/config.py deleted file mode 100644 index 976ee42..0000000 --- a/reelforge/config.py +++ /dev/null @@ -1,80 +0,0 @@ -""" -Configuration system for ReelForge -""" - -import os -from pathlib import Path -from typing import Any - -import yaml -from loguru import logger - - -def load_config(config_path: str = "config.yaml") -> dict[str, Any]: - """ - Load configuration from YAML file - - User-friendly flat format (no internal conversion): - project_name: ReelForge - llm: - api_key: xxx - base_url: xxx - model: xxx - tts: - default: edge - edge: null - image: - default: comfykit - comfykit: - comfyui_url: http://xxx - - Args: - config_path: Path to config file (default: config.yaml) - - Returns: - Configuration dict (as-is from YAML) - """ - # Check if config file exists - config_file = Path(config_path) - if not config_file.exists(): - logger.warning(f"Config file not found: {config_path}") - logger.info("Using default configuration") - return _get_default_config() - - # Load config - logger.info(f"Loading config from: {config_path}") - with open(config_file, 'r', encoding='utf-8') as f: - config = yaml.safe_load(f) - - # Handle None (empty YAML file) - if config is None: - config = {} - - # Ensure project_name exists - if "project_name" not in config: - config["project_name"] = "ReelForge" - - return config - - -def _get_default_config() -> dict[str, Any]: - """Get default configuration""" - return { - "project_name": "ReelForge", - "llm": { - "api_key": "", - "base_url": "", - "model": "" - }, - "tts": { - "default": "edge", - "edge": None - }, - "image": { - "default": "comfykit", - "comfykit": { - "comfyui_url": "http://127.0.0.1:8188" - } - } - } - diff --git a/reelforge/config/__init__.py b/reelforge/config/__init__.py new file mode 100644 index 0000000..c8ded09 --- /dev/null +++ b/reelforge/config/__init__.py @@ -0,0 +1,37 @@ +""" +ReelForge Configuration System + +Unified configuration management with Pydantic validation. + +Usage: + from reelforge.config import config_manager + + # Access config (type-safe) + api_key = config_manager.config.llm.api_key + + # Update config + config_manager.update({"llm": {"api_key": "xxx"}}) + config_manager.save() + + # Validate + if config_manager.validate(): + print("Config is valid!") +""" +from .schema import ReelForgeConfig, LLMConfig, TTSConfig, ImageConfig +from .manager import ConfigManager +from .loader import load_config_dict, save_config_dict + +# Global singleton instance +config_manager = ConfigManager() + +__all__ = [ + "ReelForgeConfig", + "LLMConfig", + "TTSConfig", + "ImageConfig", + "ConfigManager", + "config_manager", + "load_config_dict", + "save_config_dict", +] + diff --git a/reelforge/config/loader.py b/reelforge/config/loader.py new file mode 100644 index 0000000..3d40940 --- /dev/null +++ b/reelforge/config/loader.py @@ -0,0 +1,53 @@ +""" +Configuration loader - Pure YAML + +Handles loading and saving configuration from/to YAML files. +""" +from pathlib import Path +import yaml +from loguru import logger + + +def load_config_dict(config_path: str = "config.yaml") -> dict: + """ + Load configuration from YAML file + + Args: + config_path: Path to config file + + Returns: + Configuration dictionary + """ + config_file = Path(config_path) + + if not config_file.exists(): + logger.warning(f"Config file not found: {config_path}") + logger.info("Using default configuration") + return {} + + try: + with open(config_file, 'r', encoding='utf-8') as f: + data = yaml.safe_load(f) or {} + logger.info(f"Configuration loaded from {config_path}") + return data + except Exception as e: + logger.error(f"Failed to load config: {e}") + return {} + + +def save_config_dict(config: dict, config_path: str = "config.yaml"): + """ + Save configuration to YAML file + + Args: + config: Configuration dictionary + config_path: Path to config file + """ + try: + with open(config_path, 'w', encoding='utf-8') as f: + yaml.dump(config, f, allow_unicode=True, default_flow_style=False, sort_keys=False) + logger.info(f"Configuration saved to {config_path}") + except Exception as e: + logger.error(f"Failed to save config: {e}") + raise + diff --git a/reelforge/config/manager.py b/reelforge/config/manager.py new file mode 100644 index 0000000..48528e9 --- /dev/null +++ b/reelforge/config/manager.py @@ -0,0 +1,119 @@ +""" +Configuration Manager - Singleton pattern + +Provides unified access to configuration with automatic validation. +""" +from pathlib import Path +from typing import Any, Optional +from loguru import logger +from .schema import ReelForgeConfig +from .loader import load_config_dict, save_config_dict + + +class ConfigManager: + """ + Configuration Manager (Singleton) + + Provides unified access to configuration with automatic validation. + """ + _instance: Optional['ConfigManager'] = None + + def __new__(cls, config_path: str = "config.yaml"): + if cls._instance is None: + cls._instance = super().__new__(cls) + return cls._instance + + def __init__(self, config_path: str = "config.yaml"): + # Only initialize once + if hasattr(self, '_initialized'): + return + + self.config_path = Path(config_path) + self.config: ReelForgeConfig = self._load() + self._initialized = True + + def _load(self) -> ReelForgeConfig: + """Load configuration from file""" + data = load_config_dict(str(self.config_path)) + return ReelForgeConfig(**data) + + def reload(self): + """Reload configuration from file""" + self.config = self._load() + logger.info("Configuration reloaded") + + def save(self): + """Save current configuration to file""" + save_config_dict(self.config.to_dict(), str(self.config_path)) + + def update(self, updates: dict): + """ + Update configuration with new values + + Args: + updates: Dictionary of updates (e.g., {"llm": {"api_key": "xxx"}}) + """ + current = self.config.to_dict() + + # Deep merge + def deep_merge(base: dict, updates: dict) -> dict: + for key, value in updates.items(): + if key in base and isinstance(base[key], dict) and isinstance(value, dict): + deep_merge(base[key], value) + else: + base[key] = value + return base + + merged = deep_merge(current, updates) + self.config = ReelForgeConfig(**merged) + + def get(self, key: str, default: Any = None) -> Any: + """Dict-like access (for backward compatibility)""" + return self.config.to_dict().get(key, default) + + def validate(self) -> bool: + """Validate configuration completeness""" + return self.config.validate_required() + + def get_llm_config(self) -> dict: + """Get LLM configuration as dict""" + return { + "api_key": self.config.llm.api_key, + "base_url": self.config.llm.base_url, + "model": self.config.llm.model, + } + + def set_llm_config(self, api_key: str, base_url: str, model: str): + """Set LLM configuration""" + self.update({ + "llm": { + "api_key": api_key, + "base_url": base_url, + "model": model, + } + }) + + def get_image_config(self) -> dict: + """Get image configuration as dict""" + return { + "default": self.config.image.default, + "comfyui_url": self.config.image.comfyui_url, + "runninghub_api_key": self.config.image.runninghub_api_key, + "prompt_prefix": self.config.image.prompt_prefix, + } + + def set_image_config( + self, + comfyui_url: Optional[str] = None, + runninghub_api_key: Optional[str] = None + ): + """Set image configuration""" + updates = {} + if comfyui_url is not None: + updates["comfyui_url"] = comfyui_url + if runninghub_api_key is not None: + updates["runninghub_api_key"] = runninghub_api_key + + if updates: + self.update({"image": updates}) + diff --git a/reelforge/config/schema.py b/reelforge/config/schema.py new file mode 100644 index 0000000..537570f --- /dev/null +++ b/reelforge/config/schema.py @@ -0,0 +1,54 @@ +""" +Configuration schema with Pydantic models + +Single source of truth for all configuration defaults and validation. +""" +from pydantic import BaseModel, Field + + +class LLMConfig(BaseModel): + """LLM configuration""" + api_key: str = Field(default="", description="LLM API Key") + base_url: str = Field(default="", description="LLM API Base URL") + model: str = Field(default="", description="LLM Model Name") + + +class TTSConfig(BaseModel): + """TTS configuration""" + default: str = Field(default="edge", description="Default TTS workflow") + + +class ImageConfig(BaseModel): + """Image generation configuration""" + default: str = Field(default="image_default.json", description="Default image workflow") + comfyui_url: str = Field(default="http://127.0.0.1:8188", description="ComfyUI Server URL") + runninghub_api_key: str = Field(default="", description="RunningHub API Key (optional)") + prompt_prefix: str = Field( + default="Pure white background, minimalist illustration, matchstick figure style, black and white line drawing, simple clean lines", + description="Prompt prefix for all image generation" + ) + + +class ReelForgeConfig(BaseModel): + """ReelForge main configuration""" + project_name: str = Field(default="ReelForge", description="Project name") + llm: LLMConfig = Field(default_factory=LLMConfig) + tts: TTSConfig = Field(default_factory=TTSConfig) + image: ImageConfig = Field(default_factory=ImageConfig) + + def is_llm_configured(self) -> bool: + """Check if LLM is properly configured""" + return bool( + self.llm.api_key and self.llm.api_key.strip() and + self.llm.base_url and self.llm.base_url.strip() and + self.llm.model and self.llm.model.strip() + ) + + def validate_required(self) -> bool: + """Validate required configuration""" + return self.is_llm_configured() + + def to_dict(self) -> dict: + """Convert to dictionary (for backward compatibility)""" + return self.model_dump() + diff --git a/reelforge/config_manager.py b/reelforge/config_manager.py deleted file mode 100644 index e373c48..0000000 --- a/reelforge/config_manager.py +++ /dev/null @@ -1,269 +0,0 @@ -""" -Configuration manager for WebUI - -Handles loading, saving, and validating configuration -from the web interface. -""" - -from pathlib import Path -from typing import Dict, Any, Optional, List -import yaml -from loguru import logger - - -class ConfigStatus: - """Configuration validation status""" - - def __init__(self): - self.is_complete: bool = True - self.missing_fields: List[str] = [] - self.warnings: List[str] = [] - - def add_missing(self, field: str): - """Add a missing required field""" - self.is_complete = False - self.missing_fields.append(field) - - def add_warning(self, message: str): - """Add a warning message""" - self.warnings.append(message) - - -class ConfigManager: - """Manage configuration for WebUI""" - - def __init__(self, config_path: str = "config.yaml"): - self.config_path = Path(config_path) - self.config: Optional[Dict[str, Any]] = None - - def load_or_create_default(self) -> Dict[str, Any]: - """ - Load config from file, or create default if not exists - - Returns: - Configuration dictionary - """ - if not self.config_path.exists(): - logger.warning("Config file not found, creating default") - self.config = self._create_default_config() - self.save() - else: - with open(self.config_path, "r", encoding="utf-8") as f: - self.config = yaml.safe_load(f) - - logger.info(f"Configuration loaded from {self.config_path}") - return self.config - - def save(self): - """Save config to file""" - with open(self.config_path, "w", encoding="utf-8") as f: - yaml.dump(self.config, f, allow_unicode=True, default_flow_style=False, sort_keys=False) - logger.info(f"Configuration saved to {self.config_path}") - - def update_from_ui(self, ui_values: Dict[str, Any]): - """ - Update config from UI form values - - Args: - ui_values: Dictionary of form values from Streamlit - """ - # Update LLM configuration - if "llm_provider" in ui_values: - provider = ui_values["llm_provider"] - self.config["llm"]["default"] = provider - - # Ensure provider config exists - if provider not in self.config["llm"]: - self.config["llm"][provider] = {} - - # Update provider-specific config - if f"llm_{provider}_api_key" in ui_values and ui_values[f"llm_{provider}_api_key"]: - self.config["llm"][provider]["api_key"] = ui_values[f"llm_{provider}_api_key"] - - if f"llm_{provider}_base_url" in ui_values and ui_values[f"llm_{provider}_base_url"]: - self.config["llm"][provider]["base_url"] = ui_values[f"llm_{provider}_base_url"] - - if f"llm_{provider}_model" in ui_values and ui_values[f"llm_{provider}_model"]: - self.config["llm"][provider]["model"] = ui_values[f"llm_{provider}_model"] - - # Update TTS configuration - if "tts_provider" in ui_values: - self.config["tts"]["default"] = ui_values["tts_provider"] - - # Update Image configuration - if "image_provider" in ui_values: - self.config["image"]["default"] = ui_values["image_provider"] - - # Update ComfyKit configuration - if "comfykit_mode" in ui_values: - if "comfykit" not in self.config["image"]: - self.config["image"]["comfykit"] = {} - - mode = ui_values["comfykit_mode"] - if mode == "local": - if "comfyui_url" in ui_values: - self.config["image"]["comfykit"]["comfyui_url"] = ui_values["comfyui_url"] - # Remove cloud config - self.config["image"]["comfykit"].pop("runninghub_api_key", None) - else: # cloud - if "runninghub_api_key" in ui_values: - self.config["image"]["comfykit"]["runninghub_api_key"] = ui_values["runninghub_api_key"] - # Remove local config - self.config["image"]["comfykit"].pop("comfyui_url", None) - - self.save() - - def _is_valid_api_key(self, api_key: str) -> bool: - """ - Check if an API key is valid (not empty or placeholder) - - Args: - api_key: API key to validate - - Returns: - True if valid, False if empty or placeholder - """ - if not api_key: - return False - - # Remove whitespace - api_key = api_key.strip() - - if not api_key: - return False - - # Common placeholders to reject - invalid_patterns = [ - "your_", # your_openai_api_key_here, your_dashscope_api_key_here - "replace", # replace_me, replace_with_your_key - "placeholder", - "example_key", - ] - - api_key_lower = api_key.lower() - for pattern in invalid_patterns: - if pattern in api_key_lower: - return False - - # Accept any non-empty string (allow short keys for testing/custom services) - return True - - def validate(self) -> ConfigStatus: - """ - Validate configuration completeness (simple 3-field format) - - Returns: - ConfigStatus with validation results - """ - status = ConfigStatus() - - # Check LLM configuration (simple format) - llm_config = self.config.get("llm", {}) - - # Check API key - api_key = llm_config.get("api_key", "") - if not self._is_valid_api_key(api_key): - status.add_missing("llm.api_key") - - # Check base_url - base_url = llm_config.get("base_url", "") - if not base_url or not base_url.strip(): - status.add_missing("llm.base_url") - - # Check model - model = llm_config.get("model", "") - if not model or not model.strip(): - status.add_missing("llm.model") - - return status - - def get_comfykit_mode(self) -> str: - """ - Get current ComfyKit mode - - Returns: - "local" or "cloud" - """ - comfykit_config = self.config.get("image", {}).get("comfykit", {}) - - if comfykit_config.get("runninghub_api_key"): - return "cloud" - else: - return "local" - - def get_llm_config(self) -> Dict[str, str]: - """ - Get LLM configuration (simple 3-field format) - - Returns: - Dict with api_key, base_url, model - """ - if self.config is None: - return {"api_key": "", "base_url": "", "model": ""} - - llm_config = self.config.get("llm", {}) - return { - "api_key": llm_config.get("api_key", ""), - "base_url": llm_config.get("base_url", ""), - "model": llm_config.get("model", ""), - } - - def set_llm_config(self, api_key: str, base_url: str, model: str): - """ - Set LLM configuration - - Args: - api_key: API key - base_url: Base URL - model: Model name - """ - self.config["llm"] = { - "api_key": api_key, - "base_url": base_url, - "model": model, - } - - def is_llm_configured(self) -> bool: - """ - Check if LLM is configured - - Returns: - True if all three fields are non-empty - """ - llm_config = self.get_llm_config() - return bool( - llm_config["api_key"] and llm_config["api_key"].strip() and - llm_config["base_url"] and llm_config["base_url"].strip() and - llm_config["model"] and llm_config["model"].strip() - ) - - def get_tts_providers(self) -> List[str]: - """Get list of available TTS providers""" - return ["edge"] - - def get_image_providers(self) -> List[str]: - """Get list of available image providers""" - return ["comfykit"] - - def _create_default_config(self) -> Dict[str, Any]: - """Create default configuration""" - return { - "project_name": "ReelForge", - "llm": { - "api_key": "", # User must fill in - "base_url": "", # User must fill in - "model": "", # User must fill in - }, - "tts": { - "default": "edge", # Edge TTS is free - "edge": {} - }, - "image": { - "default": "comfykit", - "comfykit": { - "comfyui_url": "http://127.0.0.1:8188" - } - }, - "mcp_servers": [] - } - diff --git a/reelforge/service.py b/reelforge/service.py index 2e2e5e6..512a475 100644 --- a/reelforge/service.py +++ b/reelforge/service.py @@ -8,7 +8,7 @@ from typing import Optional from loguru import logger -from reelforge.config import load_config +from reelforge.config import config_manager from reelforge.services.llm_service import LLMService from reelforge.services.tts_service import TTSService from reelforge.services.image import ImageService @@ -55,7 +55,8 @@ class ReelForgeCore: Args: config_path: Path to configuration file """ - self.config = load_config(config_path) + # Use global config manager singleton + self.config = config_manager.config.to_dict() self._initialized = False # Core services (initialized in initialize()) diff --git a/reelforge/utils/web_config.py b/reelforge/utils/web_config.py deleted file mode 100644 index 8bf5ebb..0000000 --- a/reelforge/utils/web_config.py +++ /dev/null @@ -1,114 +0,0 @@ -""" -Lightweight configuration utility for Web UI - -Simple wrapper around config.yaml without heavy dependencies. -""" - -from pathlib import Path -from typing import Dict, Any, Optional -import yaml -from loguru import logger - - -class WebConfig: - """Lightweight configuration manager for Web UI""" - - def __init__(self, config_path: str = "config.yaml"): - self.config_path = Path(config_path) - self.config: Dict[str, Any] = {} - self.load() - - def load(self): - """Load configuration from file""" - if not self.config_path.exists(): - logger.warning(f"Config file not found: {self.config_path}, using default") - self.config = self._create_default_config() - self.save() - else: - try: - with open(self.config_path, "r", encoding="utf-8") as f: - self.config = yaml.safe_load(f) or {} - logger.info(f"Configuration loaded from {self.config_path}") - except Exception as e: - logger.error(f"Failed to load config: {e}") - self.config = self._create_default_config() - - def save(self): - """Save configuration to file""" - try: - with open(self.config_path, "w", encoding="utf-8") as f: - yaml.dump(self.config, f, allow_unicode=True, default_flow_style=False, sort_keys=False) - logger.info(f"Configuration saved to {self.config_path}") - except Exception as e: - logger.error(f"Failed to save config: {e}") - - def validate(self) -> bool: - """ - Validate configuration completeness - - Returns: - True if required fields are present - """ - # Check LLM configuration (required) - llm_config = self.config.get("llm", {}) - if not all([ - llm_config.get("api_key"), - llm_config.get("base_url"), - llm_config.get("model") - ]): - return False - - return True - - def get_llm_config(self) -> Dict[str, str]: - """Get LLM configuration""" - llm = self.config.get("llm", {}) - return { - "api_key": llm.get("api_key", ""), - "base_url": llm.get("base_url", ""), - "model": llm.get("model", "") - } - - def set_llm_config(self, api_key: str, base_url: str, model: str): - """Set LLM configuration""" - if "llm" not in self.config: - self.config["llm"] = {} - - self.config["llm"]["api_key"] = api_key - self.config["llm"]["base_url"] = base_url - self.config["llm"]["model"] = model - - def get_image_config(self) -> Dict[str, Any]: - """Get image generation configuration""" - return self.config.get("image", {}) - - def set_image_config(self, comfyui_url: Optional[str] = None, runninghub_api_key: Optional[str] = None): - """Set image generation configuration""" - if "image" not in self.config: - self.config["image"] = {} - - if comfyui_url is not None: - self.config["image"]["comfyui_url"] = comfyui_url - - if runninghub_api_key is not None: - self.config["image"]["runninghub_api_key"] = runninghub_api_key - - def _create_default_config(self) -> Dict[str, Any]: - """Create default configuration""" - return { - "project_name": "ReelForge", - "llm": { - "api_key": "", - "base_url": "", - "model": "" - }, - "tts": { - "default_workflow": "edge" - }, - "image": { - "comfyui_url": "http://127.0.0.1:8188", - "runninghub_api_key": "", - "prompt_prefix": "Pure white background, minimalist illustration, matchstick figure style, black and white line drawing, simple clean lines" - } - } - diff --git a/web.py b/web.py index 46cf7ba..3b2d26d 100644 --- a/web.py +++ b/web.py @@ -14,7 +14,7 @@ from loguru import logger # Import i18n and config manager from reelforge.i18n import load_locales, set_language, tr, get_available_languages -from reelforge.utils.web_config import WebConfig +from reelforge.config import config_manager from reelforge.models.progress import ProgressEvent # Setup page config (must be first) @@ -47,9 +47,7 @@ def safe_rerun(): # Configuration & i18n Initialization # ============================================================================ -def get_config_manager(): - """Get WebConfig instance (no caching - always fresh)""" - return WebConfig() +# Config manager is already a global singleton, use it directly def init_i18n(): @@ -127,7 +125,7 @@ def init_session_state(): # System Configuration (Required) # ============================================================================ -def render_advanced_settings(config_manager: WebConfig): +def render_advanced_settings(): """Render system configuration (required) with 2-column layout""" # Check if system is configured is_configured = config_manager.validate() @@ -300,17 +298,9 @@ def render_advanced_settings(config_manager: WebConfig): with col2: if st.button(tr("btn.reset_config"), use_container_width=True, key="reset_config_btn"): - # Reset to default by creating new config - config_manager.config = { - "project_name": "ReelForge", - "llm": {"api_key": "", "base_url": "", "model": ""}, - "tts": {"default_workflow": "edge"}, - "image": { - "comfyui_url": "http://127.0.0.1:8188", - "runninghub_api_key": "", - "prompt_prefix": "Pure white background, minimalist illustration, matchstick figure style, black and white line drawing, simple clean lines" - } - } + # Reset to default + from reelforge.config.schema import ReelForgeConfig + config_manager.config = ReelForgeConfig() config_manager.save() st.success(tr("status.config_reset")) safe_rerun() @@ -351,8 +341,6 @@ def main(): init_session_state() init_i18n() - config_manager = get_config_manager() - # Top bar: Title + Language selector col1, col2 = st.columns([4, 1]) with col1: @@ -367,7 +355,7 @@ def main(): # System Configuration (Required) # Auto-expands if not configured, collapses if configured # ======================================================================== - render_advanced_settings(config_manager) + render_advanced_settings() # Three-column layout left_col, middle_col, right_col = st.columns([1, 1, 1])