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

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

@@ -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,

117
web/i18n/__init__.py Normal file
View File

@@ -0,0 +1,117 @@
"""
International language support for ReelForge Web UI
"""
import json
from pathlib import Path
from typing import Dict, Optional
from loguru import logger
_locales: Dict[str, dict] = {}
_current_language: str = "zh_CN"
def load_locales() -> Dict[str, dict]:
"""Load all locale files from locales directory"""
global _locales
locales_dir = Path(__file__).parent / "locales"
if not locales_dir.exists():
logger.warning(f"Locales directory not found: {locales_dir}")
return _locales
for json_file in locales_dir.glob("*.json"):
lang_code = json_file.stem
try:
with open(json_file, "r", encoding="utf-8") as f:
_locales[lang_code] = json.load(f)
logger.debug(f"Loaded locale: {lang_code}")
except Exception as e:
logger.error(f"Failed to load locale {lang_code}: {e}")
logger.info(f"Loaded {len(_locales)} locales: {list(_locales.keys())}")
return _locales
def set_language(lang_code: str):
"""Set current language"""
global _current_language
if lang_code in _locales:
_current_language = lang_code
logger.debug(f"Language set to: {lang_code}")
else:
logger.warning(f"Language {lang_code} not found, keeping {_current_language}")
def get_language() -> str:
"""Get current language"""
return _current_language
def tr(key: str, fallback: Optional[str] = None, **kwargs) -> str:
"""
Translate a key to current language
Args:
key: Translation key (e.g., "app.title")
fallback: Fallback text if key not found
**kwargs: Format parameters for string interpolation
Returns:
Translated text
Example:
tr("app.title") # => "ReelForge"
tr("error.missing_field", field="API Key") # => "请填写 API Key"
"""
locale = _locales.get(_current_language, {})
translations = locale.get("t", {})
result = translations.get(key)
if result is None:
# Try fallback parameter
if fallback is not None:
result = fallback
# Try English fallback
elif _current_language != "en_US" and "en_US" in _locales:
en_locale = _locales["en_US"]
result = en_locale.get("t", {}).get(key)
# Last resort: return the key itself
if result is None:
result = key
logger.debug(f"Translation missing: {key}")
# Apply string interpolation if kwargs provided
if kwargs:
try:
result = result.format(**kwargs)
except (KeyError, ValueError) as e:
logger.warning(f"Failed to format translation '{key}': {e}")
return result
def get_language_name(lang_code: Optional[str] = None) -> str:
"""Get display name of a language"""
if lang_code is None:
lang_code = _current_language
locale = _locales.get(lang_code, {})
return locale.get("language_name", lang_code)
def get_available_languages() -> Dict[str, str]:
"""Get all available languages with their display names"""
return {
code: locale.get("language_name", code)
for code, locale in _locales.items()
}
# Auto-load locales on import
load_locales()

197
web/i18n/locales/en_US.json Normal file
View File

@@ -0,0 +1,197 @@
{
"language_name": "English",
"t": {
"app.title": "⚡ ReelForge - AI Auto Short Video Engine",
"app.subtitle": "Forge your perfect reel engine",
"section.content_input": "📖 Content Input",
"section.audio_settings": "🔊 Audio Settings",
"section.visual_settings": "🎨 Visual Settings",
"section.video_generation": "🎬 Generate Video",
"input_mode.book": "📚 Book Name",
"input_mode.topic": "💡 Topic",
"input_mode.custom": "✍️ Custom Content",
"mode.generate": "💡 Generate Mode",
"mode.fixed": "📄 Fixed Mode",
"input.book_name": "Book Name",
"input.book_name_placeholder": "e.g., Atomic Habits, How to Win Friends",
"input.book_name_help": "Enter the book name, will fetch book info and generate video",
"input.topic": "Topic",
"input.topic_placeholder": "AI automatically creates specified number of narrations\nExample: How to build passive income, 如何增加被动收入",
"input.topic_help": "Enter a topic, AI will generate content based on it",
"input.text": "Text Input",
"input.text_help_generate": "Enter topic or theme (AI will create narrations)",
"input.text_help_fixed": "Enter complete narration script (used directly without modification, one narration per line)",
"input.content": "Content",
"input.content_placeholder": "Used directly without modification, one narration per line\nExample:\nHello everyone, today I'll share three study tips\nThe first tip is focus training, meditate for 10 minutes daily\nThe second tip is active recall, review immediately after learning",
"input.content_help": "Provide your own content for video generation",
"input.title": "Title (Optional)",
"input.title_placeholder": "Video title (auto-generated if empty)",
"input.title_help": "Optional: Custom title for the video",
"book.search": "🔍 Search Book",
"book.searching": "Searching book...",
"book.found": "✅ Book found!",
"book.not_found": "❌ Failed to fetch book: {error}",
"book.name_required": "Please enter a book name",
"book.title": "Title",
"book.author": "Author",
"book.rating": "Rating",
"book.summary": "📝 Summary",
"voice.title": "🎤 Voice Selection",
"voice.male_professional": "🎤 Male-Professional",
"voice.male_young": "🎙️ Male-Young",
"voice.female_gentle": "🎵 Female-Gentle",
"voice.female_energetic": "🎶 Female-Energetic",
"voice.preview": "▶ Preview Voice",
"voice.previewing": "Generating voice preview...",
"voice.preview_failed": "Preview failed: {error}",
"style.title": "🎨 Image Settings",
"style.workflow": "ComfyUI Workflow",
"style.workflow_help": "💡 Custom: Place image_xxx.json in workflows/ folder",
"style.prompt_prefix": "Style Prompt Prefix",
"style.prompt_prefix_placeholder": "Enter style prefix (leave empty for config default)",
"style.prompt_prefix_help": "This text will be automatically added before all image generation prompts. To permanently change, edit config.yaml",
"style.custom": "Custom",
"style.description": "Style Description",
"style.description_placeholder": "Describe the illustration style you want (any language)...",
"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",
"template.default": "Default",
"template.modern": "Modern",
"template.neon": "Neon",
"template.custom_help": "💡 Custom: Place .html files in templates/ folder",
"template.preview_title": "🔍 Preview Template",
"template.preview_param_title": "Title",
"template.preview_param_text": "Text",
"template.preview_param_image": "Image Path",
"template.preview_param_width": "Width",
"template.preview_param_height": "Height",
"template.preview_default_title": "AI Changes Content Creation",
"template.preview_default_text": "Artificial intelligence is transforming the way we create content, making it easy for everyone to produce professional-grade videos.",
"template.preview_button": "🖼️ Generate Preview",
"template.preview_generating": "Generating template preview...",
"template.preview_success": "✅ Preview generated successfully!",
"template.preview_failed": "❌ Preview failed: {error}",
"template.preview_image_help": "Supports local path or URL",
"template.preview_caption": "Template Preview: {template}",
"video.title": "🎬 Video Settings",
"video.frames": "Scenes",
"video.frames_help": "More scenes = longer video",
"video.frames_label": "Scenes: {n}",
"video.frames_fixed_mode_hint": "💡 Fixed mode: scene count is determined by actual script segments",
"bgm.title": "🎵 Background Music",
"bgm.none": "🔇 No BGM",
"bgm.preview": "▶ Preview Music",
"bgm.preview_failed": "❌ Music file not found: {file}",
"bgm.custom_help": "💡 Custom: Place audio files in bgm/ folder",
"btn.generate": "🎬 Generate Video",
"btn.save_config": "💾 Save Configuration",
"btn.reset_config": "🔄 Reset to Default",
"btn.save_and_start": "Save and Start",
"btn.test_connection": "Test Connection",
"status.initializing": "🔧 Initializing...",
"status.generating": "🚀 Generating video...",
"status.success": "✅ Video generated successfully!",
"status.error": "❌ Generation failed: {error}",
"status.video_generated": "✅ Video generated: {path}",
"status.video_not_found": "Video file not found: {path}",
"status.config_saved": "✅ Configuration saved",
"status.config_reset": "✅ Configuration reset to default",
"status.connection_success": "✅ Connected",
"status.connection_failed": "❌ Connection failed",
"progress.generating_narrations": "Generating narrations...",
"progress.splitting_script": "Splitting script...",
"progress.generating_image_prompts": "Generating image prompts...",
"progress.frame": "Frame {current}/{total}",
"progress.frame_step": "Frame {current}/{total} - Step {step}/4: {action}",
"progress.step_audio": "Generating audio",
"progress.step_image": "Generating image",
"progress.step_compose": "Composing frame",
"progress.step_video": "Creating video segment",
"progress.concatenating": "Concatenating video segments...",
"progress.finalizing": "Finalizing...",
"error.input_required": "❌ Please provide book name, topic, or content",
"error.api_key_required": "❌ Please enter API Key",
"error.missing_field": "Please enter {field}",
"info.duration": "Duration",
"info.file_size": "File Size",
"info.frames": "Scenes",
"info.scenes_unit": " scenes",
"info.resolution": "Resolution",
"info.video_information": "📊 Video Information",
"info.no_video_yet": "Video preview will appear here after generation",
"settings.title": "⚙️ System Configuration (Required)",
"settings.not_configured": "⚠️ Please complete system configuration before generating videos",
"settings.llm.title": "🤖 Large Language Model",
"settings.llm.quick_select": "Quick Select",
"settings.llm.quick_select_help": "Choose a preset LLM or custom configuration",
"settings.llm.get_api_key": "Get API Key",
"settings.llm.api_key": "API Key",
"settings.llm.api_key_help": "Enter your API Key",
"settings.llm.base_url": "Base URL",
"settings.llm.base_url_help": "API service address",
"settings.llm.model": "Model",
"settings.llm.model_help": "Model name",
"settings.tts.title": "🎤 Text-to-Speech",
"settings.tts.provider": "Provider",
"settings.tts.provider_help": "Select TTS service provider",
"settings.tts.edge_info": "💡 Edge TTS is free and requires no configuration",
"settings.image.title": "🎨 Image Generation",
"settings.image.local_title": "Local/Self-hosted ComfyUI",
"settings.image.cloud_title": "RunningHub Cloud",
"settings.image.comfyui_url": "ComfyUI Service URL",
"settings.image.comfyui_url_help": "Local or remote ComfyUI service URL, default: http://127.0.0.1:8188",
"settings.image.runninghub_api_key": "RunningHub API Key",
"settings.image.runninghub_api_key_help": "Visit https://runninghub.ai to register and get API Key",
"settings.book.title": "📚 Book Information",
"settings.book.provider": "Provider",
"settings.book.provider_help": "Select book information source",
"welcome.first_time": "🎉 Welcome to ReelForge! Please complete basic configuration",
"welcome.config_hint": "💡 First-time setup requires API Key configuration, you can modify it in advanced settings later",
"wizard.llm_required": "🤖 Large Language Model Configuration (Required)",
"wizard.image_optional": "🎨 Image Generation Configuration (Optional)",
"wizard.image_hint": "💡 If not configured, default template will be used (no AI image generation)",
"wizard.configure_image": "Configure Image Generation (Recommended)",
"label.required": "(Required)",
"label.optional": "(Optional)",
"language.select": "🌐 Language"
}
}

197
web/i18n/locales/zh_CN.json Normal file
View File

@@ -0,0 +1,197 @@
{
"language_name": "简体中文",
"t": {
"app.title": "⚡ ReelForge - AI 全自动短视频引擎",
"app.subtitle": "打造专属你的视频创作引擎",
"section.content_input": "📖 内容输入",
"section.audio_settings": "🔊 声音设置",
"section.visual_settings": "🎨 画面设置",
"section.video_generation": "🎬 生成视频",
"input_mode.book": "📚 书名",
"input_mode.topic": "💡 主题",
"input_mode.custom": "✍️ 自定义内容",
"mode.generate": "💡 生成模式",
"mode.fixed": "📄 固定模式",
"input.book_name": "书名",
"input.book_name_placeholder": "例如原子习惯、人性的弱点、Atomic Habits",
"input.book_name_help": "输入书名,将自动获取书籍信息并生成视频",
"input.topic": "主题",
"input.topic_placeholder": "AI 自动创作指定数量的旁白\n例如如何增加被动收入、How to build passive income",
"input.topic_help": "输入一个主题AI 将根据主题生成内容",
"input.text": "文本输入",
"input.text_help_generate": "输入主题或话题AI 将创作旁白)",
"input.text_help_fixed": "输入完整的旁白脚本(直接使用,不做改写,每行一个旁白)",
"input.content": "内容",
"input.content_placeholder": "直接使用,不做改写,每行一个旁白\n例如\n大家好今天跟你分享三个学习技巧\n第一个技巧是专注力训练每天冥想10分钟\n第二个技巧是主动回忆学完立即复述",
"input.content_help": "提供您自己的内容用于视频生成",
"input.title": "标题(可选)",
"input.title_placeholder": "视频标题(留空则自动生成)",
"input.title_help": "可选:自定义视频标题",
"book.search": "🔍 搜索书籍",
"book.searching": "正在搜索书籍...",
"book.found": "✅ 找到书籍!",
"book.not_found": "❌ 获取书籍失败:{error}",
"book.name_required": "请输入书名",
"book.title": "书名",
"book.author": "作者",
"book.rating": "评分",
"book.summary": "📝 简介",
"voice.title": "🎤 语音选择",
"voice.male_professional": "🎤 男声-专业",
"voice.male_young": "🎙️ 男声-年轻",
"voice.female_gentle": "🎵 女声-温柔",
"voice.female_energetic": "🎶 女声-活力",
"voice.preview": "▶ 试听语音",
"voice.previewing": "正在生成语音预览...",
"voice.preview_failed": "预览失败:{error}",
"style.title": "🎨 插图设置",
"style.workflow": "生图工作流",
"style.workflow_help": "💡 自定义:将 image_xxx.json 放入 workflows/ 文件夹",
"style.prompt_prefix": "风格提示词前缀",
"style.prompt_prefix_placeholder": "输入风格前缀(留空则使用配置文件默认值)",
"style.prompt_prefix_help": "此文本将自动添加到所有图像生成提示词之前。要永久修改,请编辑 config.yaml",
"style.custom": "自定义",
"style.description": "风格描述",
"style.description_placeholder": "描述您想要的插图风格(任何语言)...",
"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": "📐 分镜模板",
"template.default": "默认",
"template.modern": "现代",
"template.neon": "霓虹",
"template.custom_help": "💡 自定义:将 .html 文件放入 templates/ 文件夹",
"template.preview_title": "🔍 预览模板",
"template.preview_param_title": "标题",
"template.preview_param_text": "文本",
"template.preview_param_image": "图片路径",
"template.preview_param_width": "宽度",
"template.preview_param_height": "高度",
"template.preview_default_title": "AI 改变内容创作",
"template.preview_default_text": "人工智能正在改变内容创作的方式,让每个人都能轻松制作专业级视频。",
"template.preview_button": "🖼️ 生成预览",
"template.preview_generating": "正在生成模板预览...",
"template.preview_success": "✅ 预览生成成功!",
"template.preview_failed": "❌ 预览失败:{error}",
"template.preview_image_help": "支持本地路径或 URL",
"template.preview_caption": "模板预览:{template}",
"video.title": "🎬 视频设置",
"video.frames": "分镜数",
"video.frames_help": "更多分镜 = 更长视频",
"video.frames_label": "分镜数:{n}",
"video.frames_fixed_mode_hint": "💡 固定模式:分镜数由脚本实际段落数决定",
"bgm.title": "🎵 背景音乐",
"bgm.none": "🔇 无背景音乐",
"bgm.preview": "▶ 试听音乐",
"bgm.preview_failed": "❌ 音乐文件未找到:{file}",
"bgm.custom_help": "💡 自定义:将音频文件放入 bgm/ 文件夹",
"btn.generate": "🎬 生成视频",
"btn.save_config": "💾 保存配置",
"btn.reset_config": "🔄 重置默认",
"btn.save_and_start": "保存并开始",
"btn.test_connection": "测试连接",
"status.initializing": "🔧 正在初始化...",
"status.generating": "🚀 正在生成视频...",
"status.success": "✅ 视频生成成功!",
"status.error": "❌ 生成失败:{error}",
"status.video_generated": "✅ 视频已生成:{path}",
"status.video_not_found": "视频文件未找到:{path}",
"status.config_saved": "✅ 配置已保存",
"status.config_reset": "✅ 配置已重置为默认值",
"status.connection_success": "✅ 连接成功",
"status.connection_failed": "❌ 连接失败",
"progress.generating_narrations": "生成旁白...",
"progress.splitting_script": "切分脚本...",
"progress.generating_image_prompts": "生成图片提示词...",
"progress.frame": "分镜 {current}/{total}",
"progress.frame_step": "分镜 {current}/{total} - 步骤 {step}/4: {action}",
"progress.step_audio": "生成语音",
"progress.step_image": "生成插图",
"progress.step_compose": "合成画面",
"progress.step_video": "创建视频片段",
"progress.concatenating": "拼接视频片段...",
"progress.finalizing": "完成中...",
"error.input_required": "❌ 请提供书名、主题或内容",
"error.api_key_required": "❌ 请填写 API Key",
"error.missing_field": "请填写 {field}",
"info.duration": "时长",
"info.file_size": "文件大小",
"info.frames": "分镜数",
"info.scenes_unit": "分镜",
"info.resolution": "分辨率",
"info.video_information": "📊 视频信息",
"info.no_video_yet": "生成视频后,预览将显示在这里",
"settings.title": "⚙️ 系统配置(必需)",
"settings.not_configured": "⚠️ 请先完成系统配置才能生成视频",
"settings.llm.title": "🤖 大语言模型",
"settings.llm.quick_select": "快速选择",
"settings.llm.quick_select_help": "选择预置的 LLM 或自定义配置",
"settings.llm.get_api_key": "获取 API Key",
"settings.llm.api_key": "API Key",
"settings.llm.api_key_help": "填入您的 API Key",
"settings.llm.base_url": "Base URL",
"settings.llm.base_url_help": "API 服务地址",
"settings.llm.model": "Model",
"settings.llm.model_help": "模型名称",
"settings.tts.title": "🎤 语音合成",
"settings.tts.provider": "服务商",
"settings.tts.provider_help": "选择 TTS 服务提供商",
"settings.tts.edge_info": "💡 Edge TTS 是免费的,无需配置",
"settings.image.title": "🎨 图像生成",
"settings.image.local_title": "本地/自建 ComfyUI",
"settings.image.cloud_title": "RunningHub 云端",
"settings.image.comfyui_url": "ComfyUI 服务地址",
"settings.image.comfyui_url_help": "本地或远程 ComfyUI 服务地址,默认: http://127.0.0.1:8188",
"settings.image.runninghub_api_key": "RunningHub API Key",
"settings.image.runninghub_api_key_help": "访问 https://runninghub.ai 注册并获取 API Key",
"settings.book.title": "📚 书籍信息",
"settings.book.provider": "服务商",
"settings.book.provider_help": "选择书籍信息来源",
"welcome.first_time": "🎉 欢迎使用 ReelForge请先完成基础配置",
"welcome.config_hint": "💡 首次使用需要配置 API Key后续可以在高级设置中修改",
"wizard.llm_required": "🤖 大语言模型配置(必需)",
"wizard.image_optional": "🎨 图像生成配置(可选)",
"wizard.image_hint": "💡 如果不配置图像生成,将使用默认模板(无 AI 生图)",
"wizard.configure_image": "配置图像生成(推荐)",
"label.required": "(必需)",
"label.optional": "(可选)",
"language.select": "🌐 语言"
}
}