更新配置文件,重构工作流管理逻辑,调整国际化文件文职,优化图像生成和文本转语音服务的工作流解析,确保默认工作流配置必填,调整前端工作流选择逻辑。

This commit is contained in:
puke
2025-10-29 16:01:14 +08:00
parent f71c1d9487
commit 2c36e5a2af
11 changed files with 300 additions and 372 deletions

View File

@@ -23,7 +23,16 @@ tts:
# ==================== Image Generation Configuration ==================== # ==================== Image Generation Configuration ====================
image: image:
default: image_default.json # Required: Default workflow to use (no fallback)
# Options: runninghub/image_default.json (recommended, no local setup)
# selfhost/image_default.json (requires local ComfyUI)
default_workflow: runninghub/image_default.json
# Local ComfyUI configuration (required if using selfhost workflows)
comfyui_url: http://127.0.0.1:8188 comfyui_url: http://127.0.0.1:8188
# RunningHub cloud configuration (required if using runninghub workflows)
runninghub_api_key: "" runninghub_api_key: ""
# Image prompt prefix (optional)
prompt_prefix: "Pure white background, minimalist illustration, matchstick figure style, black and white line drawing, simple clean lines" prompt_prefix: "Pure white background, minimalist illustration, matchstick figure style, black and white line drawing, simple clean lines"

View File

@@ -96,7 +96,7 @@ class ConfigManager:
def get_image_config(self) -> dict: def get_image_config(self) -> dict:
"""Get image configuration as dict""" """Get image configuration as dict"""
return { return {
"default": self.config.image.default, "default_workflow": self.config.image.default_workflow,
"comfyui_url": self.config.image.comfyui_url, "comfyui_url": self.config.image.comfyui_url,
"runninghub_api_key": self.config.image.runninghub_api_key, "runninghub_api_key": self.config.image.runninghub_api_key,
"prompt_prefix": self.config.image.prompt_prefix, "prompt_prefix": self.config.image.prompt_prefix,

View File

@@ -15,12 +15,16 @@ class LLMConfig(BaseModel):
class TTSConfig(BaseModel): class TTSConfig(BaseModel):
"""TTS configuration""" """TTS configuration"""
default: str = Field(default="edge", description="Default TTS workflow") model_config = {"populate_by_name": True} # Allow both field name and alias
default_workflow: str = Field(default="edge", description="Default TTS workflow", alias="default")
class ImageConfig(BaseModel): class ImageConfig(BaseModel):
"""Image generation configuration""" """Image generation configuration"""
default: str = Field(default="image_default.json", description="Default image workflow") model_config = {"populate_by_name": True} # Allow both field name and alias
default_workflow: str = Field(default=None, description="Default image workflow (required, no fallback)", alias="default")
comfyui_url: str = Field(default="http://127.0.0.1:8188", description="ComfyUI Server URL") 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)") runninghub_api_key: str = Field(default="", description="RunningHub API Key (optional)")
prompt_prefix: str = Field( prompt_prefix: str = Field(

View File

@@ -2,6 +2,7 @@
ComfyUI Base Service - Common logic for ComfyUI-based services ComfyUI Base Service - Common logic for ComfyUI-based services
""" """
import json
import os import os
from pathlib import Path from pathlib import Path
from typing import Optional, List, Dict, Any from typing import Optional, List, Dict, Any
@@ -38,13 +39,29 @@ class ComfyBaseService:
self.service_name = service_name self.service_name = service_name
self._workflows_cache: Optional[List[str]] = None self._workflows_cache: Optional[List[str]] = None
def _scan_workflows(self) -> List[str]: def _scan_workflows(self) -> List[Dict[str, Any]]:
""" """
Scan workflows/{prefix}*.json files Scan workflows/source/*.json files from all source directories
Returns: Returns:
List of workflow filenames List of workflow info dicts
Example: ["image_default.json", "image_flux.json"] Example: [
{
"name": "image_default.json",
"display_name": "image_default.json - Selfhost",
"source": "selfhost",
"path": "workflows/selfhost/image_default.json",
"key": "selfhost/image_default.json"
},
{
"name": "image_default.json",
"display_name": "image_default.json - Runninghub",
"source": "runninghub",
"path": "workflows/runninghub/image_default.json",
"key": "runninghub/image_default.json",
"workflow_id": "123456"
}
]
""" """
workflows = [] workflows = []
workflows_dir = Path(self.WORKFLOWS_DIR) workflows_dir = Path(self.WORKFLOWS_DIR)
@@ -53,67 +70,130 @@ class ComfyBaseService:
logger.warning(f"Workflows directory not found: {workflows_dir}") logger.warning(f"Workflows directory not found: {workflows_dir}")
return workflows return workflows
# Scan for {prefix}_*.json files # Scan subdirectories (selfhost, runninghub, etc.)
for file in workflows_dir.glob(f"{self.WORKFLOW_PREFIX}*.json"): for source_dir in workflows_dir.iterdir():
workflows.append(file.name) if not source_dir.is_dir():
logger.debug(f"Found {self.service_name} workflow: {file.name}") logger.debug(f"Skipping non-directory: {source_dir}")
continue
return sorted(workflows) source_name = source_dir.name
# Scan workflow files in this source directory
for file_path in source_dir.glob(f"{self.WORKFLOW_PREFIX}*.json"):
try:
workflow_info = self._parse_workflow_file(file_path, source_name)
workflows.append(workflow_info)
logger.debug(f"Found workflow: {workflow_info['key']}")
except Exception as e:
logger.error(f"Failed to parse workflow {file_path}: {e}")
# Sort by key (source/name)
return sorted(workflows, key=lambda w: w["key"])
def _parse_workflow_file(self, file_path: Path, source: str) -> Dict[str, Any]:
"""
Parse workflow file and extract metadata
Args:
file_path: Path to workflow JSON file
source: Source directory name (e.g., "selfhost", "runninghub")
Returns:
Workflow info dict with structure:
{
"name": "image_default.json",
"display_name": "image_default.json - Runninghub",
"source": "runninghub",
"path": "workflows/runninghub/image_default.json",
"key": "runninghub/image_default.json",
"workflow_id": "123456" # Only for RunningHub
}
"""
with open(file_path, 'r', encoding='utf-8') as f:
content = json.load(f)
# Build base info
workflow_info = {
"name": file_path.name,
"display_name": f"{file_path.name} - {source.title()}",
"source": source,
"path": str(file_path),
"key": f"{source}/{file_path.name}"
}
# Check if it's a wrapper format (RunningHub, etc.)
if "source" in content:
# Wrapper format: {"source": "runninghub", "workflow_id": "xxx", ...}
if "workflow_id" in content:
workflow_info["workflow_id"] = content["workflow_id"]
if "metadata" in content:
workflow_info["metadata"] = content["metadata"]
return workflow_info
def _get_default_workflow(self) -> str: def _get_default_workflow(self) -> str:
""" """
Get default workflow name from config or use DEFAULT_WORKFLOW Get default workflow from config (required, no fallback)
Returns: Returns:
Default workflow filename Default workflow key (e.g., "runninghub/image_default.json")
"""
return self.config.get("default_workflow", self.DEFAULT_WORKFLOW)
def _resolve_workflow(self, workflow: Optional[str] = None) -> str: Raises:
ValueError: If default_workflow not configured
""" """
Resolve workflow to actual workflow path default_workflow = self.config.get("default_workflow")
if not default_workflow:
raise ValueError(
f"No default workflow configured for {self.service_name}. "
f"Please set 'default_workflow' in config.yaml under '{self.service_name}' section. "
f"Available workflows: {', '.join(self.available)}"
)
return default_workflow
def _resolve_workflow(self, workflow: Optional[str] = None) -> Dict[str, Any]:
"""
Resolve workflow key to workflow info
Args: Args:
workflow: Workflow filename (e.g., "image_default.json") workflow: Workflow key (e.g., "runninghub/image_default.json")
Can also be: If None, uses default from config
- Absolute path: "/path/to/workflow.json"
- Relative path: "custom/workflow.json"
- URL: "http://..."
- RunningHub ID: "12345"
Returns: Returns:
Workflow file path or identifier Workflow info dict with structure:
{
"name": "image_default.json",
"display_name": "image_default.json - Runninghub",
"source": "runninghub",
"path": "workflows/runninghub/image_default.json",
"key": "runninghub/image_default.json",
"workflow_id": "123456" # Only for RunningHub
}
Raises: Raises:
ValueError: If workflow not found ValueError: If workflow not found
""" """
# 1. If not specified, use default # 1. If not specified, use default from config
if workflow is None: if workflow is None:
workflow = self._get_default_workflow() workflow = self._get_default_workflow()
# 2. If it's an absolute path, URL, or looks like RunningHub ID, use as-is # 2. Scan available workflows
if (workflow.startswith("/") or available_workflows = self._scan_workflows()
workflow.startswith("http://") or
workflow.startswith("https://") or
workflow.isdigit()):
logger.debug(f"Using workflow identifier: {workflow}")
return workflow
# 3. If it's just a filename, look in workflows/ directory
workflow_path = Path(self.WORKFLOWS_DIR) / workflow
if not workflow_path.exists():
# List available workflows for error message
available = self._scan_workflows()
available_str = ", ".join(available) if available else "none"
raise ValueError(
f"Workflow '{workflow}' not found at {workflow_path}. "
f"Available workflows: {available_str}\n"
f"Please create: {workflow_path}"
)
# 3. Find matching workflow by key
for wf_info in available_workflows:
if wf_info["key"] == workflow:
logger.info(f"🎬 Using {self.service_name} workflow: {workflow}") logger.info(f"🎬 Using {self.service_name} workflow: {workflow}")
return str(workflow_path) return wf_info
# 4. Not found - generate error message
available_keys = [wf["key"] for wf in available_workflows]
available_str = ", ".join(available_keys) if available_keys else "none"
raise ValueError(
f"Workflow '{workflow}' not found. "
f"Available workflows: {available_str}"
)
def _prepare_comfykit_config( def _prepare_comfykit_config(
self, self,
@@ -153,31 +233,42 @@ class ComfyBaseService:
logger.debug(f"ComfyKit config: {kit_config}") logger.debug(f"ComfyKit config: {kit_config}")
return kit_config return kit_config
def list_workflows(self) -> List[str]: def list_workflows(self) -> List[Dict[str, Any]]:
""" """
List all available workflows List all available workflows with full metadata
Returns: Returns:
List of workflow filenames (sorted alphabetically) List of workflow info dicts (sorted by key)
Example: Example:
workflows = service.list_workflows() workflows = service.list_workflows()
# ['image_default.json', 'image_flux.json'] # [
# {
# "name": "image_default.json",
# "display_name": "image_default.json - Runninghub",
# "source": "runninghub",
# "path": "workflows/runninghub/image_default.json",
# "key": "runninghub/image_default.json",
# "workflow_id": "123456"
# },
# ...
# ]
""" """
return self._scan_workflows() return self._scan_workflows()
@property @property
def available(self) -> List[str]: def available(self) -> List[str]:
""" """
List available workflows List available workflow keys
Returns: Returns:
List of available workflow filenames List of available workflow keys (e.g., ["runninghub/image_default.json", ...])
Example: Example:
print(f"Available workflows: {service.available}") print(f"Available workflows: {service.available}")
""" """
return self.list_workflows() workflows = self.list_workflows()
return [wf["key"] for wf in workflows]
def __repr__(self) -> str: def __repr__(self) -> str:
"""String representation""" """String representation"""

View File

@@ -31,7 +31,7 @@ class ImageService(ComfyBaseService):
""" """
WORKFLOW_PREFIX = "image_" WORKFLOW_PREFIX = "image_"
DEFAULT_WORKFLOW = "image_default.json" DEFAULT_WORKFLOW = None # No hardcoded default, must be configured
WORKFLOWS_DIR = "workflows" WORKFLOWS_DIR = "workflows"
def __init__(self, config: dict): def __init__(self, config: dict):
@@ -112,10 +112,10 @@ class ImageService(ComfyBaseService):
comfyui_url="http://192.168.1.100:8188" comfyui_url="http://192.168.1.100:8188"
) )
""" """
# 1. Resolve workflow path # 1. Resolve workflow (returns structured info)
workflow_path = self._resolve_workflow(workflow=workflow) workflow_info = self._resolve_workflow(workflow=workflow)
# 2. Prepare ComfyKit config # 2. Prepare ComfyKit config (supports both selfhost and runninghub)
kit_config = self._prepare_comfykit_config( kit_config = self._prepare_comfykit_config(
comfyui_url=comfyui_url, comfyui_url=comfyui_url,
runninghub_api_key=runninghub_api_key runninghub_api_key=runninghub_api_key
@@ -145,12 +145,21 @@ class ImageService(ComfyBaseService):
logger.debug(f"Workflow parameters: {workflow_params}") logger.debug(f"Workflow parameters: {workflow_params}")
# 4. Execute workflow # 4. Execute workflow (ComfyKit auto-detects based on input type)
try: try:
kit = ComfyKit(**kit_config) kit = ComfyKit(**kit_config)
logger.info(f"Executing workflow: {workflow_path}") # Determine what to pass to ComfyKit based on source
result = await kit.execute(workflow_path, workflow_params) if workflow_info["source"] == "runninghub" and "workflow_id" in workflow_info:
# RunningHub: pass workflow_id (ComfyKit will use runninghub backend)
workflow_input = workflow_info["workflow_id"]
logger.info(f"Executing RunningHub workflow: {workflow_input}")
else:
# Selfhost: pass file path (ComfyKit will use local ComfyUI)
workflow_input = workflow_info["path"]
logger.info(f"Executing selfhost workflow: {workflow_input}")
result = await kit.execute(workflow_input, workflow_params)
# 5. Handle result # 5. Handle result
if result.status != "completed": if result.status != "completed":

View File

@@ -139,11 +139,8 @@ class TTSService(ComfyBaseService):
workflow="/path/to/custom_tts.json" workflow="/path/to/custom_tts.json"
) )
""" """
# 1. Resolve workflow path or provider # 1. Check if it's a builtin provider (edge-tts)
workflow_or_provider = self._resolve_workflow(workflow=workflow) if workflow in self.BUILTIN_PROVIDERS or workflow is None and self._get_default_workflow() in self.BUILTIN_PROVIDERS:
# 2. Determine execution path
if workflow_or_provider in self.BUILTIN_PROVIDERS:
# Use edge-tts # Use edge-tts
return await self._call_edge_tts( return await self._call_edge_tts(
text=text, text=text,
@@ -154,10 +151,12 @@ class TTSService(ComfyBaseService):
output_path=output_path, output_path=output_path,
**params **params
) )
else:
# Use ComfyUI workflow # 2. Use ComfyUI workflow - resolve to structured info
workflow_info = self._resolve_workflow(workflow=workflow)
return await self._call_comfyui_workflow( return await self._call_comfyui_workflow(
workflow_path=workflow_or_provider, workflow_info=workflow_info,
text=text, text=text,
comfyui_url=comfyui_url, comfyui_url=comfyui_url,
runninghub_api_key=runninghub_api_key, runninghub_api_key=runninghub_api_key,
@@ -227,7 +226,7 @@ class TTSService(ComfyBaseService):
async def _call_comfyui_workflow( async def _call_comfyui_workflow(
self, self,
workflow_path: str, workflow_info: dict,
text: str, text: str,
comfyui_url: Optional[str] = None, comfyui_url: Optional[str] = None,
runninghub_api_key: Optional[str] = None, runninghub_api_key: Optional[str] = None,
@@ -242,7 +241,7 @@ class TTSService(ComfyBaseService):
Generate speech using ComfyUI workflow Generate speech using ComfyUI workflow
Args: Args:
workflow_path: Path to workflow file workflow_info: Workflow info dict from _resolve_workflow()
text: Text to convert to speech text: Text to convert to speech
comfyui_url: ComfyUI URL comfyui_url: ComfyUI URL
runninghub_api_key: RunningHub API key runninghub_api_key: RunningHub API key
@@ -256,9 +255,9 @@ class TTSService(ComfyBaseService):
Returns: Returns:
Generated audio file path (local if output_path provided, otherwise URL) Generated audio file path (local if output_path provided, otherwise URL)
""" """
logger.info(f"🎙️ Using ComfyUI workflow: {workflow_path}") logger.info(f"🎙️ Using workflow: {workflow_info['key']}")
# 1. Prepare ComfyKit config # 1. Prepare ComfyKit config (supports both selfhost and runninghub)
kit_config = self._prepare_comfykit_config( kit_config = self._prepare_comfykit_config(
comfyui_url=comfyui_url, comfyui_url=comfyui_url,
runninghub_api_key=runninghub_api_key runninghub_api_key=runninghub_api_key
@@ -282,12 +281,21 @@ class TTSService(ComfyBaseService):
logger.debug(f"Workflow parameters: {workflow_params}") logger.debug(f"Workflow parameters: {workflow_params}")
# 3. Execute workflow # 3. Execute workflow (ComfyKit auto-detects based on input type)
try: try:
kit = ComfyKit(**kit_config) kit = ComfyKit(**kit_config)
logger.info(f"Executing TTS workflow: {workflow_path}") # Determine what to pass to ComfyKit based on source
result = await kit.execute(workflow_path, workflow_params) if workflow_info["source"] == "runninghub" and "workflow_id" in workflow_info:
# RunningHub: pass workflow_id
workflow_input = workflow_info["workflow_id"]
logger.info(f"Executing RunningHub TTS workflow: {workflow_input}")
else:
# Selfhost: pass file path
workflow_input = workflow_info["path"]
logger.info(f"Executing selfhost TTS workflow: {workflow_input}")
result = await kit.execute(workflow_input, workflow_params)
# 4. Handle result # 4. Handle result
if result.status != "completed": if result.status != "completed":

View File

@@ -13,7 +13,7 @@ import streamlit as st
from loguru import logger from loguru import logger
# Import i18n and config manager # Import i18n and config manager
from reelforge.i18n import load_locales, set_language, tr, get_available_languages from web.i18n import load_locales, set_language, tr, get_available_languages
from reelforge.config import config_manager from reelforge.config import config_manager
from reelforge.models.progress import ProgressEvent from reelforge.models.progress import ProgressEvent
@@ -63,38 +63,6 @@ def init_i18n():
set_language(st.session_state.language) set_language(st.session_state.language)
# ============================================================================
# Preview Cache Functions
# ============================================================================
def generate_style_preview_cached(prompt_prefix: str):
"""
Generate and cache visual style preview
Args:
prompt_prefix: Prompt prefix to test
Returns:
Generated image path
"""
from reelforge.utils.prompt_helper import build_image_prompt
reelforge = get_reelforge()
# Build final prompt with prefix
test_prompt = "A peaceful mountain landscape"
final_prompt = build_image_prompt(test_prompt, prompt_prefix)
# Generate preview image (small size for speed)
preview_image_path = run_async(reelforge.image(
prompt=final_prompt,
width=512,
height=512
))
return preview_image_path
# ============================================================================ # ============================================================================
# Initialize ReelForge # Initialize ReelForge
# ============================================================================ # ============================================================================
@@ -512,25 +480,39 @@ def main():
st.caption(tr("style.workflow")) st.caption(tr("style.workflow"))
st.caption(tr("style.workflow_help")) st.caption(tr("style.workflow_help"))
# Dynamically scan workflows folder for image_*.json files # Get available workflows from reelforge (with source info)
workflows_folder = Path("workflows") workflows = reelforge.image.list_workflows()
workflow_files = []
if workflows_folder.exists():
workflow_files = sorted([f.name for f in workflows_folder.glob("image_*.json")])
# Default to "image_default.json" if exists, otherwise first option # Build options for selectbox
# Display: "image_default.json - Runninghub"
# Value: "runninghub/image_default.json"
workflow_options = [wf["display_name"] for wf in workflows]
workflow_keys = [wf["key"] for wf in workflows]
# Default to first option (should be runninghub by sorting)
default_workflow_index = 0 default_workflow_index = 0
if "image_default.json" in workflow_files:
default_workflow_index = workflow_files.index("image_default.json")
workflow_filename = st.selectbox( # If user has a saved preference in config, try to match it
image_config = config_manager.get_image_config()
saved_workflow = image_config.get("default_workflow")
if saved_workflow and saved_workflow in workflow_keys:
default_workflow_index = workflow_keys.index(saved_workflow)
workflow_display = st.selectbox(
"Workflow", "Workflow",
workflow_files if workflow_files else ["image_default.json"], workflow_options if workflow_options else ["No workflows found"],
index=default_workflow_index, index=default_workflow_index,
label_visibility="collapsed", label_visibility="collapsed",
key="image_workflow_select" key="image_workflow_select"
) )
# Get the actual workflow key (e.g., "runninghub/image_default.json")
if workflow_options:
workflow_selected_index = workflow_options.index(workflow_display)
workflow_key = workflow_keys[workflow_selected_index]
else:
workflow_key = "runninghub/image_default.json" # fallback
# 2. Prompt prefix input # 2. Prompt prefix input
st.caption(tr("style.prompt_prefix")) st.caption(tr("style.prompt_prefix"))
@@ -549,15 +531,37 @@ def main():
help=tr("style.prompt_prefix_help") help=tr("style.prompt_prefix_help")
) )
# Visual style preview button # Style preview expander (similar to template preview)
with st.expander(tr("style.preview_title"), expanded=False):
# Test prompt input
test_prompt = st.text_input(
tr("style.test_prompt"),
value="a dog",
help=tr("style.test_prompt_help"),
key="style_test_prompt"
)
# Preview button
if st.button(tr("style.preview"), key="preview_style", use_container_width=True): if st.button(tr("style.preview"), key="preview_style", use_container_width=True):
with st.spinner(tr("style.previewing")): with st.spinner(tr("style.previewing")):
try: try:
# Generate preview using cached function from reelforge.utils.prompt_helper import build_image_prompt
preview_image_path = generate_style_preview_cached(prompt_prefix)
# Build final prompt with prefix
final_prompt = build_image_prompt(test_prompt, prompt_prefix)
# Generate preview image (small size for speed)
preview_image_path = run_async(reelforge.image(
prompt=final_prompt,
workflow=workflow_key,
width=512,
height=512
))
# Display preview (support both URL and local path) # Display preview (support both URL and local path)
if preview_image_path: if preview_image_path:
st.success(tr("style.preview_success"))
# Read and encode image # Read and encode image
if preview_image_path.startswith('http'): if preview_image_path.startswith('http'):
# URL - use directly # URL - use directly
@@ -569,15 +573,14 @@ def main():
img_html = f'<div class="preview-image"><img src="data:image/png;base64,{img_data}" alt="Style Preview"/></div>' img_html = f'<div class="preview-image"><img src="data:image/png;base64,{img_data}" alt="Style Preview"/></div>'
st.markdown(img_html, unsafe_allow_html=True) st.markdown(img_html, unsafe_allow_html=True)
st.caption("Preview with test prompt: 'A peaceful mountain landscape'")
# Show the final prompt used # Show the final prompt used
from reelforge.utils.prompt_helper import build_image_prompt st.info(f"**{tr('style.final_prompt_label')}**\n{final_prompt}")
test_prompt = "A peaceful mountain landscape"
final_prompt = build_image_prompt(test_prompt, prompt_prefix) # Show file path
st.info(f"Final prompt used: {final_prompt}") st.caption(f"📁 {preview_image_path}")
else: else:
st.error("Failed to generate preview image") st.error(tr("style.preview_failed_general"))
except Exception as e: except Exception as e:
st.error(tr("style.preview_failed", error=str(e))) st.error(tr("style.preview_failed", error=str(e)))
logger.exception(e) logger.exception(e)
@@ -755,7 +758,7 @@ def main():
title=title if title else None, title=title if title else None,
n_scenes=n_scenes, n_scenes=n_scenes,
voice_id=voice_id, voice_id=voice_id,
image_workflow=workflow_filename, # Pass image workflow filename image_workflow=workflow_key, # Pass workflow key (e.g., "runninghub/image_default.json")
frame_template=frame_template, frame_template=frame_template,
prompt_prefix=prompt_prefix, # Pass prompt_prefix prompt_prefix=prompt_prefix, # Pass prompt_prefix
bgm_path=bgm_path, bgm_path=bgm_path,

View File

@@ -1,5 +1,5 @@
""" """
International language support for ReelForge International language support for ReelForge Web UI
""" """
import json import json

View File

@@ -65,10 +65,16 @@
"style.custom": "Custom", "style.custom": "Custom",
"style.description": "Style Description", "style.description": "Style Description",
"style.description_placeholder": "Describe the illustration style you want (any language)...", "style.description_placeholder": "Describe the illustration style you want (any language)...",
"style.preview": "🖼️ Preview Style", "style.preview_title": "🔍 Preview Style",
"style.test_prompt": "Test Prompt",
"style.test_prompt_help": "Enter test prompt to preview style effect",
"style.preview": "🖼️ Generate Preview",
"style.previewing": "Generating style preview...", "style.previewing": "Generating style preview...",
"style.preview_success": "✅ Preview generated successfully!",
"style.preview_caption": "Style Preview", "style.preview_caption": "Style Preview",
"style.preview_failed": "Preview failed: {error}", "style.preview_failed": "Preview failed: {error}",
"style.preview_failed_general": "Failed to generate preview image",
"style.final_prompt_label": "Final Prompt",
"style.generated_prompt": "Generated prompt: {prompt}", "style.generated_prompt": "Generated prompt: {prompt}",
"template.title": "📐 Storyboard Template", "template.title": "📐 Storyboard Template",

View File

@@ -65,10 +65,16 @@
"style.custom": "自定义", "style.custom": "自定义",
"style.description": "风格描述", "style.description": "风格描述",
"style.description_placeholder": "描述您想要的插图风格(任何语言)...", "style.description_placeholder": "描述您想要的插图风格(任何语言)...",
"style.preview": "🖼️ 预览风格", "style.preview_title": "🔍 预览风格",
"style.test_prompt": "测试提示词",
"style.test_prompt_help": "输入测试提示词来预览风格效果",
"style.preview": "🖼️ 生成预览",
"style.previewing": "正在生成风格预览...", "style.previewing": "正在生成风格预览...",
"style.preview_success": "✅ 预览生成成功!",
"style.preview_caption": "风格预览", "style.preview_caption": "风格预览",
"style.preview_failed": "预览失败:{error}", "style.preview_failed": "预览失败:{error}",
"style.preview_failed_general": "预览图片生成失败",
"style.final_prompt_label": "最终提示词",
"style.generated_prompt": "生成的提示词:{prompt}", "style.generated_prompt": "生成的提示词:{prompt}",
"template.title": "📐 分镜模板", "template.title": "📐 分镜模板",

View File

@@ -1,208 +0,0 @@
{
"2": {
"inputs": {
"noise": [
"5",
0
],
"guider": [
"7",
0
],
"sampler": [
"4",
0
],
"sigmas": [
"3",
0
],
"latent_image": [
"10",
0
]
},
"class_type": "SamplerCustomAdvanced",
"_meta": {
"title": "自定义采样器(高级)"
}
},
"3": {
"inputs": {
"scheduler": "simple",
"steps": 20,
"denoise": 1,
"model": [
"8",
0
]
},
"class_type": "BasicScheduler",
"_meta": {
"title": "基本调度器"
}
},
"4": {
"inputs": {
"sampler_name": "euler"
},
"class_type": "KSamplerSelect",
"_meta": {
"title": "K采样器选择"
}
},
"5": {
"inputs": {
"noise_seed": 1091846418466568
},
"class_type": "RandomNoise",
"_meta": {
"title": "随机噪波"
}
},
"6": {
"inputs": {
"clip_name1": "t5xxl_fp16.safetensors",
"clip_name2": "clip_l.safetensors",
"type": "flux",
"device": "default"
},
"class_type": "DualCLIPLoader",
"_meta": {
"title": "双CLIP加载器"
}
},
"7": {
"inputs": {
"model": [
"8",
0
],
"conditioning": [
"11",
0
]
},
"class_type": "BasicGuider",
"_meta": {
"title": "基本引导器"
}
},
"8": {
"inputs": {
"unet_name": "flux1-dev.safetensors",
"weight_dtype": "fp8_e4m3fn"
},
"class_type": "UNETLoader",
"_meta": {
"title": "UNet加载器"
}
},
"9": {
"inputs": {
"vae_name": "ae.safetensors"
},
"class_type": "VAELoader",
"_meta": {
"title": "加载VAE"
}
},
"10": {
"inputs": {
"width": [
"27",
0
],
"height": [
"28",
0
],
"batch_size": 1
},
"class_type": "EmptyLatentImage",
"_meta": {
"title": "空Latent图像"
}
},
"11": {
"inputs": {
"text": [
"21",
0
],
"clip": [
"6",
0
]
},
"class_type": "CLIPTextEncode",
"_meta": {
"title": "CLIP文本编码"
}
},
"14": {
"inputs": {
"samples": [
"2",
0
],
"vae": [
"9",
0
]
},
"class_type": "VAEDecode",
"_meta": {
"title": "VAE解码"
}
},
"20": {
"inputs": {
"filename_prefix": "ComfyUI",
"images": [
"14",
0
]
},
"class_type": "SaveImage",
"_meta": {
"title": "保存图像"
}
},
"21": {
"inputs": {
"text": "A serene and peaceful scene of a European/American girl playing guitar by the seaside. The background features a calm ocean under soft, natural lighting, with tranquil sky and subtle waves. The overall atmosphere is quiet and harmonious, evoking a sense of relaxation and gentle breeze. Horizontal composition, photorealistic style,"
},
"class_type": "Text _O",
"_meta": {
"title": "$prompt.text!:image prompt"
}
},
"24": {
"inputs": {
"text": "Generate high-quality images from text prompts using local FLUX 1.1 model.\n \n Creates completely new images with exceptional detail, style diversity, and prompt adherence.\n The model excels at photorealistic scenes, artistic styles, and complex compositions.\n \n Prompt writing tips for best results:\n - Be detailed and specific: include colors, mood, lighting, and composition\n - Use natural language and full sentences rather than keywords\n - For photos: mention camera type, settings (e.g., \"shot on DSLR, f/2.8, golden hour\")\n - For portraits: describe features, expressions, clothing, and background\n - For scenes: specify foreground, middle ground, and background elements\n \n Excellent prompt examples:\n - \"birthday, girl selfie with bright smile and colorful balloons\"\n - \"Close-up portrait of a child with bright vivid emotions, sparkling eyes full of joy, sunlight dancing on rosy cheeks\"\n - \"Serene lake reflecting dense forest during golden hour, shot on DSLR with wide-angle lens, f/8, perfect symmetry\"\n - \"Vintage travel poster for Paris, Eiffel Tower silhouette in warm sunset colors, 'PARIS' in golden Art Deco font\"\n - \"Ethereal dragon with neon lightning crystal on head, glowing blue eyes, majestic wings spread wide\""
},
"class_type": "Text _O",
"_meta": {
"title": "MCP"
}
},
"27": {
"inputs": {
"value": 1024
},
"class_type": "easy int",
"_meta": {
"title": "$width.value"
}
},
"28": {
"inputs": {
"value": 1024
},
"class_type": "easy int",
"_meta": {
"title": "$height.value"
}
}
}