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

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:
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
# RunningHub cloud configuration (required if using runninghub workflows)
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"

View File

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

View File

@@ -15,12 +15,16 @@ class LLMConfig(BaseModel):
class TTSConfig(BaseModel):
"""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):
"""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")
runninghub_api_key: str = Field(default="", description="RunningHub API Key (optional)")
prompt_prefix: str = Field(

View File

@@ -2,6 +2,7 @@
ComfyUI Base Service - Common logic for ComfyUI-based services
"""
import json
import os
from pathlib import Path
from typing import Optional, List, Dict, Any
@@ -38,13 +39,29 @@ class ComfyBaseService:
self.service_name = service_name
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:
List of workflow filenames
Example: ["image_default.json", "image_flux.json"]
List of workflow info dicts
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_dir = Path(self.WORKFLOWS_DIR)
@@ -53,67 +70,130 @@ class ComfyBaseService:
logger.warning(f"Workflows directory not found: {workflows_dir}")
return workflows
# Scan for {prefix}_*.json files
for file in workflows_dir.glob(f"{self.WORKFLOW_PREFIX}*.json"):
workflows.append(file.name)
logger.debug(f"Found {self.service_name} workflow: {file.name}")
# Scan subdirectories (selfhost, runninghub, etc.)
for source_dir in workflows_dir.iterdir():
if not source_dir.is_dir():
logger.debug(f"Skipping non-directory: {source_dir}")
continue
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}")
return sorted(workflows)
# 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:
"""
Get default workflow name from config or use DEFAULT_WORKFLOW
Get default workflow from config (required, no fallback)
Returns:
Default workflow filename
Default workflow key (e.g., "runninghub/image_default.json")
Raises:
ValueError: If default_workflow not configured
"""
return self.config.get("default_workflow", self.DEFAULT_WORKFLOW)
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) -> str:
def _resolve_workflow(self, workflow: Optional[str] = None) -> Dict[str, Any]:
"""
Resolve workflow to actual workflow path
Resolve workflow key to workflow info
Args:
workflow: Workflow filename (e.g., "image_default.json")
Can also be:
- Absolute path: "/path/to/workflow.json"
- Relative path: "custom/workflow.json"
- URL: "http://..."
- RunningHub ID: "12345"
workflow: Workflow key (e.g., "runninghub/image_default.json")
If None, uses default from config
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:
ValueError: If workflow not found
"""
# 1. If not specified, use default
# 1. If not specified, use default from config
if workflow is None:
workflow = self._get_default_workflow()
# 2. If it's an absolute path, URL, or looks like RunningHub ID, use as-is
if (workflow.startswith("/") or
workflow.startswith("http://") or
workflow.startswith("https://") or
workflow.isdigit()):
logger.debug(f"Using workflow identifier: {workflow}")
return workflow
# 2. Scan available workflows
available_workflows = self._scan_workflows()
# 3. If it's just a filename, look in workflows/ directory
workflow_path = Path(self.WORKFLOWS_DIR) / workflow
# 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}")
return wf_info
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}"
)
logger.info(f"🎬 Using {self.service_name} workflow: {workflow}")
return str(workflow_path)
# 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(
self,
@@ -153,31 +233,42 @@ class ComfyBaseService:
logger.debug(f"ComfyKit config: {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:
List of workflow filenames (sorted alphabetically)
List of workflow info dicts (sorted by key)
Example:
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()
@property
def available(self) -> List[str]:
"""
List available workflows
List available workflow keys
Returns:
List of available workflow filenames
List of available workflow keys (e.g., ["runninghub/image_default.json", ...])
Example:
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:
"""String representation"""

View File

@@ -31,7 +31,7 @@ class ImageService(ComfyBaseService):
"""
WORKFLOW_PREFIX = "image_"
DEFAULT_WORKFLOW = "image_default.json"
DEFAULT_WORKFLOW = None # No hardcoded default, must be configured
WORKFLOWS_DIR = "workflows"
def __init__(self, config: dict):
@@ -112,10 +112,10 @@ class ImageService(ComfyBaseService):
comfyui_url="http://192.168.1.100:8188"
)
"""
# 1. Resolve workflow path
workflow_path = self._resolve_workflow(workflow=workflow)
# 1. Resolve workflow (returns structured info)
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(
comfyui_url=comfyui_url,
runninghub_api_key=runninghub_api_key
@@ -145,12 +145,21 @@ class ImageService(ComfyBaseService):
logger.debug(f"Workflow parameters: {workflow_params}")
# 4. Execute workflow
# 4. Execute workflow (ComfyKit auto-detects based on input type)
try:
kit = ComfyKit(**kit_config)
logger.info(f"Executing workflow: {workflow_path}")
result = await kit.execute(workflow_path, workflow_params)
# Determine what to pass to ComfyKit based on source
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
if result.status != "completed":

View File

@@ -139,11 +139,8 @@ class TTSService(ComfyBaseService):
workflow="/path/to/custom_tts.json"
)
"""
# 1. Resolve workflow path or provider
workflow_or_provider = self._resolve_workflow(workflow=workflow)
# 2. Determine execution path
if workflow_or_provider in self.BUILTIN_PROVIDERS:
# 1. Check if it's a builtin provider (edge-tts)
if workflow in self.BUILTIN_PROVIDERS or workflow is None and self._get_default_workflow() in self.BUILTIN_PROVIDERS:
# Use edge-tts
return await self._call_edge_tts(
text=text,
@@ -154,20 +151,22 @@ class TTSService(ComfyBaseService):
output_path=output_path,
**params
)
else:
# Use ComfyUI workflow
return await self._call_comfyui_workflow(
workflow_path=workflow_or_provider,
text=text,
comfyui_url=comfyui_url,
runninghub_api_key=runninghub_api_key,
voice=voice,
rate=rate,
volume=volume,
pitch=pitch,
output_path=output_path,
**params
)
# 2. Use ComfyUI workflow - resolve to structured info
workflow_info = self._resolve_workflow(workflow=workflow)
return await self._call_comfyui_workflow(
workflow_info=workflow_info,
text=text,
comfyui_url=comfyui_url,
runninghub_api_key=runninghub_api_key,
voice=voice,
rate=rate,
volume=volume,
pitch=pitch,
output_path=output_path,
**params
)
async def _call_edge_tts(
self,
@@ -227,7 +226,7 @@ class TTSService(ComfyBaseService):
async def _call_comfyui_workflow(
self,
workflow_path: str,
workflow_info: dict,
text: str,
comfyui_url: Optional[str] = None,
runninghub_api_key: Optional[str] = None,
@@ -242,7 +241,7 @@ class TTSService(ComfyBaseService):
Generate speech using ComfyUI workflow
Args:
workflow_path: Path to workflow file
workflow_info: Workflow info dict from _resolve_workflow()
text: Text to convert to speech
comfyui_url: ComfyUI URL
runninghub_api_key: RunningHub API key
@@ -256,9 +255,9 @@ class TTSService(ComfyBaseService):
Returns:
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(
comfyui_url=comfyui_url,
runninghub_api_key=runninghub_api_key
@@ -282,12 +281,21 @@ class TTSService(ComfyBaseService):
logger.debug(f"Workflow parameters: {workflow_params}")
# 3. Execute workflow
# 3. Execute workflow (ComfyKit auto-detects based on input type)
try:
kit = ComfyKit(**kit_config)
logger.info(f"Executing TTS workflow: {workflow_path}")
result = await kit.execute(workflow_path, workflow_params)
# Determine what to pass to ComfyKit based on source
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
if result.status != "completed":

View File

@@ -13,7 +13,7 @@ import streamlit as st
from loguru import logger
# 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.models.progress import ProgressEvent
@@ -63,38 +63,6 @@ def init_i18n():
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
# ============================================================================
@@ -512,25 +480,39 @@ def main():
st.caption(tr("style.workflow"))
st.caption(tr("style.workflow_help"))
# Dynamically scan workflows folder for image_*.json files
workflows_folder = Path("workflows")
workflow_files = []
if workflows_folder.exists():
workflow_files = sorted([f.name for f in workflows_folder.glob("image_*.json")])
# Get available workflows from reelforge (with source info)
workflows = reelforge.image.list_workflows()
# 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
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_files if workflow_files else ["image_default.json"],
workflow_options if workflow_options else ["No workflows found"],
index=default_workflow_index,
label_visibility="collapsed",
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
st.caption(tr("style.prompt_prefix"))
@@ -549,38 +531,59 @@ def main():
help=tr("style.prompt_prefix_help")
)
# Visual style preview button
if st.button(tr("style.preview"), key="preview_style", use_container_width=True):
with st.spinner(tr("style.previewing")):
try:
# Generate preview using cached function
preview_image_path = generate_style_preview_cached(prompt_prefix)
# Display preview (support both URL and local path)
if preview_image_path:
# Read and encode image
if preview_image_path.startswith('http'):
# URL - use directly
img_html = f'<div class="preview-image"><img src="{preview_image_path}" alt="Style Preview"/></div>'
else:
# Local file - encode as base64
with open(preview_image_path, 'rb') as f:
img_data = base64.b64encode(f.read()).decode()
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.caption("Preview with test prompt: 'A peaceful mountain landscape'")
# Show the final prompt used
# 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):
with st.spinner(tr("style.previewing")):
try:
from reelforge.utils.prompt_helper import build_image_prompt
test_prompt = "A peaceful mountain landscape"
# Build final prompt with prefix
final_prompt = build_image_prompt(test_prompt, prompt_prefix)
st.info(f"Final prompt used: {final_prompt}")
else:
st.error("Failed to generate preview image")
except Exception as e:
st.error(tr("style.preview_failed", error=str(e)))
logger.exception(e)
# 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)
if preview_image_path:
st.success(tr("style.preview_success"))
# Read and encode image
if preview_image_path.startswith('http'):
# URL - use directly
img_html = f'<div class="preview-image"><img src="{preview_image_path}" alt="Style Preview"/></div>'
else:
# Local file - encode as base64
with open(preview_image_path, 'rb') as f:
img_data = base64.b64encode(f.read()).decode()
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)
# Show the final prompt used
st.info(f"**{tr('style.final_prompt_label')}**\n{final_prompt}")
# Show file path
st.caption(f"📁 {preview_image_path}")
else:
st.error(tr("style.preview_failed_general"))
except Exception as e:
st.error(tr("style.preview_failed", error=str(e)))
logger.exception(e)
# Frame template (moved from right column)
@@ -755,7 +758,7 @@ def main():
title=title if title else None,
n_scenes=n_scenes,
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,
prompt_prefix=prompt_prefix, # Pass prompt_prefix
bgm_path=bgm_path,

View File

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

View File

@@ -65,10 +65,16 @@
"style.custom": "Custom",
"style.description": "Style Description",
"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.preview_success": "✅ Preview generated successfully!",
"style.preview_caption": "Style Preview",
"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}",
"template.title": "📐 Storyboard Template",

View File

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