diff --git a/reelforge/models/storyboard.py b/reelforge/models/storyboard.py index 48e0727..94622a6 100644 --- a/reelforge/models/storyboard.py +++ b/reelforge/models/storyboard.py @@ -27,7 +27,7 @@ class StoryboardConfig: # Image parameters image_width: int = 1024 image_height: int = 1024 - image_preset: Optional[str] = None # Image workflow preset (None = use default) + image_workflow: Optional[str] = None # Image workflow filename (None = use default) # Frame template frame_template: Optional[str] = None # HTML template name or path (None = use PIL) diff --git a/reelforge/services/storyboard_processor.py b/reelforge/services/storyboard_processor.py index 9e1f921..aa29c14 100644 --- a/reelforge/services/storyboard_processor.py +++ b/reelforge/services/storyboard_processor.py @@ -145,7 +145,7 @@ class StoryboardProcessorService: # Call Image generation (with optional preset) image_url = await self.core.image( prompt=frame.image_prompt, - preset=config.image_preset, # Pass preset from config (None = use default) + workflow=config.image_workflow, # Pass workflow from config (None = use default) width=config.image_width, height=config.image_height ) diff --git a/reelforge/services/video_generator.py b/reelforge/services/video_generator.py index 49922ef..bd285d1 100644 --- a/reelforge/services/video_generator.py +++ b/reelforge/services/video_generator.py @@ -66,7 +66,7 @@ class VideoGeneratorService: # === Image Parameters === image_width: int = 1024, image_height: int = 1024, - image_preset: Optional[str] = None, + image_workflow: Optional[str] = None, # === Video Parameters === video_width: int = 1080, @@ -121,7 +121,7 @@ class VideoGeneratorService: image_width: Generated image width (default 1024) image_height: Generated image height (default 1024) - image_preset: Image workflow preset (e.g., "flux", "sdxl", None = use default) + image_workflow: Image workflow filename (e.g., "image_flux.json", None = use default) video_width: Final video width (default 1080) video_height: Final video height (default 1920) @@ -215,7 +215,7 @@ class VideoGeneratorService: voice_id=voice_id, image_width=image_width, image_height=image_height, - image_preset=image_preset, + image_workflow=image_workflow, frame_template=frame_template ) diff --git a/reelforge/utils/web_config.py b/reelforge/utils/web_config.py new file mode 100644 index 0000000..8bf5ebb --- /dev/null +++ b/reelforge/utils/web_config.py @@ -0,0 +1,114 @@ +""" +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 9d3b066..46cf7ba 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.config_manager import ConfigManager +from reelforge.utils.web_config import WebConfig from reelforge.models.progress import ProgressEvent # Setup page config (must be first) @@ -48,10 +48,8 @@ def safe_rerun(): # ============================================================================ def get_config_manager(): - """Get ConfigManager instance (no caching - always fresh)""" - manager = ConfigManager() - manager.load_or_create_default() - return manager + """Get WebConfig instance (no caching - always fresh)""" + return WebConfig() def init_i18n(): @@ -129,7 +127,7 @@ def init_session_state(): # System Configuration (Required) # ============================================================================ -def render_advanced_settings(config_manager: ConfigManager): +def render_advanced_settings(config_manager: WebConfig): """Render system configuration (required) with 2-column layout""" # Check if system is configured is_configured = config_manager.validate() @@ -237,8 +235,8 @@ def render_advanced_settings(config_manager: ConfigManager): with st.container(border=True): st.markdown(f"**{tr('settings.image.title')}**") - # Get current configuration (flat structure) - image_config = config_manager.config.get("image", {}) + # Get current configuration + image_config = config_manager.get_image_config() # Local/Self-hosted ComfyUI configuration st.markdown(f"**{tr('settings.image.local_title')}**") @@ -282,16 +280,15 @@ def render_advanced_settings(config_manager: ConfigManager): with col1: if st.button(tr("btn.save_config"), use_container_width=True, key="save_config_btn"): try: - # Save LLM configuration (simple 3-field format) + # Save LLM configuration if llm_api_key and llm_base_url and llm_model: config_manager.set_llm_config(llm_api_key, llm_base_url, llm_model) - # Save Image configuration (flat structure) - config_manager.config["image"]["default"] = "default" - if comfyui_url: - config_manager.config["image"]["comfyui_url"] = comfyui_url - if runninghub_api_key: - config_manager.config["image"]["runninghub_api_key"] = runninghub_api_key + # Save Image configuration + config_manager.set_image_config( + comfyui_url=comfyui_url if comfyui_url else None, + runninghub_api_key=runninghub_api_key if runninghub_api_key else None + ) # Save to file config_manager.save() @@ -303,7 +300,17 @@ def render_advanced_settings(config_manager: ConfigManager): with col2: if st.button(tr("btn.reset_config"), use_container_width=True, key="reset_config_btn"): - config_manager.config = config_manager._create_default_config() + # 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" + } + } config_manager.save() st.success(tr("status.config_reset")) safe_rerun() @@ -539,18 +546,15 @@ def main(): workflow_files if workflow_files else ["image_default.json"], index=default_workflow_index, label_visibility="collapsed", - key="image_preset_select" + key="image_workflow_select" ) - # Extract preset name from filename: "image_default.json" -> "default" - image_preset = workflow_filename.replace("image_", "").replace(".json", "") if workflow_filename else None - # 2. Prompt prefix input st.caption(tr("style.prompt_prefix")) # Get current prompt_prefix from config - image_config = config_manager.config.get("image", {}) + image_config = config_manager.get_image_config() current_prefix = image_config.get("prompt_prefix", "") # Prompt prefix input (temporary, not saved to config) @@ -687,7 +691,7 @@ def main(): title=title if title else None, n_scenes=n_scenes, voice_id=voice_id, - image_preset=image_preset, # Pass image_preset + image_workflow=workflow_filename, # Pass image workflow filename frame_template=frame_template, prompt_prefix=prompt_prefix, # Pass prompt_prefix bgm_path=bgm_path,