import os import logging import gradio as gr from config import config from processor import process_batch_sync, expand_tags_sync logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) os.makedirs(config.OUTPUT_DIR, exist_ok=True) CUSTOM_CSS = """ .gradio-container { max-width: 1200px !important; font-family: 'Inter', sans-serif !important; } .title-block { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 16px; padding: 28px 32px; margin-bottom: 20px; color: white; text-align: center; box-shadow: 0 8px 32px rgba(102, 126, 234, 0.25); } .title-block h1 { font-size: 28px; font-weight: 700; margin: 0 0 8px 0; letter-spacing: -0.5px; } .title-block p { font-size: 14px; opacity: 0.9; margin: 0; } .primary-btn { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important; border: none !important; border-radius: 12px !important; color: white !important; font-weight: 600 !important; font-size: 16px !important; padding: 14px 28px !important; box-shadow: 0 4px 15px rgba(102, 126, 234, 0.35) !important; transition: all 0.3s ease !important; } .primary-btn:hover { transform: translateY(-2px) !important; box-shadow: 0 6px 20px rgba(102, 126, 234, 0.5) !important; } .nsfw-toggle { border: 1px solid rgba(255, 107, 107, 0.3) !important; border-radius: 10px !important; padding: 12px !important; background: rgba(255, 107, 107, 0.05) !important; } .mode-toggle-portrait { border: 1px solid rgba(102, 126, 234, 0.3) !important; border-radius: 10px !important; padding: 12px !important; background: rgba(102, 126, 234, 0.05) !important; margin-bottom: 10px; } .tip-text { font-size: 13px; color: rgba(156, 163, 175, 0.9); background: rgba(102, 126, 234, 0.08); border-radius: 10px; padding: 12px 16px; border-left: 3px solid rgba(102, 126, 234, 0.5); margin-bottom: 15px; } .warning-text { font-size: 12px; color: #ef4444; margin-top: 4px; font-weight: bold; } .summary-box textarea { font-family: 'JetBrains Mono', Consolas, monospace !important; font-size: 13px !important; line-height: 1.6 !important; border-radius: 10px !important; } .upload-area { border: 2px dashed rgba(102, 126, 234, 0.3) !important; border-radius: 14px !important; transition: all 0.3s ease !important; } .upload-area:hover { border-color: rgba(102, 126, 234, 0.6) !important; background: rgba(102, 126, 234, 0.05) !important; } .fetch-btn { background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%) !important; border: none !important; color: #1a1a2e !important; font-weight: 600 !important; } """ def fetch_models_from_env(): models = config.AVAILABLE_MODELS if not models: return gr.update(choices=[], value=None), "⚠️ HF 环境变量 AVAILABLE_MODELS 未配置或为空" return gr.update(choices=models, value=models[0]), f"✅ 已读取到 {len(models)} 个可用模型" def _prepare_params(api_key, base_url, model_dropdown, model_text, portrait_suffix, dim_appearance, dim_body, dim_clothing, dim_pose, dim_nsfw_detail, dim_composition, dim_background, dim_style): safe_api_key = (api_key or "").strip() or None safe_base_url = (base_url or "").strip() or None safe_model = (model_text or "").strip() or (model_dropdown or "").strip() or None safe_portrait_suffix = (portrait_suffix or "").strip() enabled_dims = [] if dim_appearance: enabled_dims.append("appearance") if dim_body: enabled_dims.append("body") if dim_clothing: enabled_dims.append("clothing") if dim_pose: enabled_dims.append("pose") if dim_nsfw_detail: enabled_dims.append("nsfw_detail") if dim_composition: enabled_dims.append("composition") if dim_background: enabled_dims.append("background") if dim_style: enabled_dims.append("style") if not enabled_dims: enabled_dims = ["appearance"] return safe_api_key, safe_base_url, safe_model, safe_portrait_suffix, enabled_dims def analyze_images(files, provider, api_key, base_url, model_dropdown, model_text, images_per_request, nsfw_mode, nsfw_max_rolls, portrait_mode, portrait_suffix, custom_prompt, dim_appearance, dim_body, dim_clothing, dim_pose, dim_nsfw_detail, dim_composition, dim_background, dim_style): if not files: return None, "❌ 请上传至少一张图片或一个 ZIP 压缩包", [] input_paths = [f if isinstance(f, str) else f.name for f in files] safe_api_key, safe_base_url, safe_model, safe_portrait_suffix, enabled_dims = _prepare_params( api_key, base_url, model_dropdown, model_text, portrait_suffix, dim_appearance, dim_body, dim_clothing, dim_pose, dim_nsfw_detail, dim_composition, dim_background, dim_style ) try: zip_paths, summary, gallery_data = process_batch_sync( input_paths=input_paths, provider=provider, api_key=safe_api_key, base_url=safe_base_url, model=safe_model, custom_prompt=(custom_prompt or "").strip() or None, images_per_request=int(images_per_request), nsfw=nsfw_mode, nsfw_max_rolls=int(nsfw_max_rolls), enabled_dims=enabled_dims, portrait=portrait_mode, portrait_suffix=safe_portrait_suffix ) return zip_paths, summary, gallery_data except Exception as e: logger.error(f"反推失败: {e}") return None, f"❌ 处理失败: {str(e)}", [] def expand_text_tags(tags, provider, api_key, base_url, model_dropdown, model_text, nsfw_mode, nsfw_max_rolls, portrait_mode, portrait_suffix, dim_appearance, dim_body, dim_clothing, dim_pose, dim_nsfw_detail, dim_composition, dim_background, dim_style): safe_api_key, safe_base_url, safe_model, safe_portrait_suffix, enabled_dims = _prepare_params( api_key, base_url, model_dropdown, model_text, portrait_suffix, dim_appearance, dim_body, dim_clothing, dim_pose, dim_nsfw_detail, dim_composition, dim_background, dim_style ) try: result = expand_tags_sync( tags=tags, provider=provider, api_key=safe_api_key, base_url=safe_base_url, model=safe_model, nsfw=nsfw_mode, nsfw_max_rolls=int(nsfw_max_rolls), enabled_dims=enabled_dims, portrait=portrait_mode, portrait_suffix=safe_portrait_suffix ) return result except Exception as e: logger.error(f"扩写失败: {e}") return f"❌ 扩写失败: {str(e)}" def on_nsfw_toggle(nsfw_on): if nsfw_on: return gr.update(value=True), gr.update(visible=True) else: return gr.update(value=False), gr.update(visible=False) def create_gradio_app(): with gr.Blocks(title="🖼️ Image & Tag to Prompt") as app: gr.HTML(f"") gr.HTML("""
动态提取变量接口 · 标签扩写 · NSFW 防截断 Roll 系统 · 肖像数据集标注