"""Z-Image-Turbo v2.3 - Multilingual Support"""
import os
import logging
import torch
import spaces
import gradio as gr
import requests
import io
import base64
import tempfile
from typing import Tuple, Optional, Dict
from PIL import Image
from diffusers import DiffusionPipeline, ZImageImg2ImgPipeline
from openai import OpenAI
# Configure logging (replaces debug print statements)
logging.basicConfig(level=logging.INFO, format='[%(levelname)s] %(message)s')
logger = logging.getLogger(__name__)
# =============================================================================
# MULTILINGUAL SUPPORT
# =============================================================================
LANGUAGES = ["English", "Español", "Português (BR)", "العربية", "हिंदी"]
TRANSLATIONS: Dict[str, Dict[str, str]] = {
"English": {
# Header
"title": "Z Image Turbo + GLM-4.6V",
"subtitle": "AI Image Generation & Transformation powered by DeepSeek Reasoning",
"like_msg": "If you liked it, please ❤️ like it. Thank you!",
# Tabs
"tab_generate": "Generate",
"tab_assistant": "AI Assistant",
"tab_transform": "Transform",
# Generate tab
"prompt": "Prompt",
"prompt_placeholder": "Describe your image in detail...",
"polish_checkbox": "Prompt+ by deepseek-reasoner",
"style": "Style",
"aspect_ratio": "Aspect Ratio",
"advanced_settings": "Advanced Settings",
"steps": "Steps",
"seed": "Seed",
"random_seed": "Random Seed",
"generate_btn": "Generate",
"generated_image": "Generated Image",
"enhanced_prompt": "Enhanced Prompt",
"seed_used": "Seed Used",
"share": "Share",
# AI Assistant tab
"ai_description": "**AI-Powered Prompt Generator** - Upload an image, analyze it with GLM-4.6V, then generate optimized prompts.",
"upload_image": "Upload Image",
"analyze_btn": "Analyze Image",
"image_description": "Image Description",
"changes_request": "What changes do you want?",
"changes_placeholder": "e.g., 'watercolor style' or 'dramatic sunset lighting'",
"target_style": "Target Style",
"generate_prompt_btn": "Generate Prompt",
"generated_prompt": "Generated Prompt",
"send_to_transform": "Send to Transform Tab",
"how_to_use": "How to Use",
"how_to_use_content": """1. **Upload** an image and click "Analyze Image"
2. **Describe** the changes you want
3. **Generate** an optimized prompt
4. **Send** to Transform tab to apply changes""",
# Transform tab
"transform_description": "**Transform your image** - Upload and describe the transformation. Lower strength = subtle, higher = dramatic.",
"transformation_prompt": "Transformation Prompt",
"transform_placeholder": "e.g., 'oil painting style, vibrant colors'",
"strength": "Strength",
"transform_btn": "Transform",
"transformed_image": "Transformed Image",
"example_prompts": "Example Prompts",
# Footer
"models": "Models",
"by": "by",
},
"Español": {
"title": "Z Image Turbo + GLM-4.6V",
"subtitle": "Generación y Transformación de Imágenes con IA impulsado por DeepSeek Reasoning",
"like_msg": "Si te gustó, por favor dale me gusta. ¡Gracias!",
"tab_generate": "Generar",
"tab_assistant": "Asistente IA",
"tab_transform": "Transformar",
"prompt": "Prompt",
"prompt_placeholder": "Describe tu imagen en detalle...",
"polish_checkbox": "Prompt+ por deepseek-reasoner",
"style": "Estilo",
"aspect_ratio": "Relación de Aspecto",
"advanced_settings": "Configuración Avanzada",
"steps": "Pasos",
"seed": "Semilla",
"random_seed": "Semilla Aleatoria",
"generate_btn": "Generar",
"generated_image": "Imagen Generada",
"enhanced_prompt": "Prompt Mejorado",
"seed_used": "Semilla Usada",
"share": "Compartir",
"ai_description": "**Generador de Prompts con IA** - Sube una imagen, analízala con GLM-4.6V, y genera prompts optimizados.",
"upload_image": "Subir Imagen",
"analyze_btn": "Analizar Imagen",
"image_description": "Descripción de la Imagen",
"changes_request": "¿Qué cambios quieres?",
"changes_placeholder": "ej., 'estilo acuarela' o 'iluminación de atardecer dramático'",
"target_style": "Estilo Objetivo",
"generate_prompt_btn": "Generar Prompt",
"generated_prompt": "Prompt Generado",
"send_to_transform": "Enviar a Transformar",
"how_to_use": "Cómo Usar",
"how_to_use_content": """1. **Sube** una imagen y haz clic en "Analizar Imagen"
2. **Describe** los cambios que quieres
3. **Genera** un prompt optimizado
4. **Envía** a la pestaña Transformar para aplicar cambios""",
"transform_description": "**Transforma tu imagen** - Sube y describe la transformación. Menor fuerza = sutil, mayor = dramático.",
"transformation_prompt": "Prompt de Transformación",
"transform_placeholder": "ej., 'estilo pintura al óleo, colores vibrantes'",
"strength": "Fuerza",
"transform_btn": "Transformar",
"transformed_image": "Imagen Transformada",
"example_prompts": "Prompts de Ejemplo",
"models": "Modelos",
"by": "por",
},
"Português (BR)": {
"title": "Z Image Turbo + GLM-4.6V",
"subtitle": "Geração e Transformação de Imagens com IA alimentado por DeepSeek Reasoning",
"like_msg": "Se você gostou, por favor curta. Obrigado!",
"tab_generate": "Gerar",
"tab_assistant": "Assistente IA",
"tab_transform": "Transformar",
"prompt": "Prompt",
"prompt_placeholder": "Descreva sua imagem em detalhes...",
"polish_checkbox": "Prompt+ por deepseek-reasoner",
"style": "Estilo",
"aspect_ratio": "Proporção",
"advanced_settings": "Configurações Avançadas",
"steps": "Passos",
"seed": "Semente",
"random_seed": "Semente Aleatória",
"generate_btn": "Gerar",
"generated_image": "Imagem Gerada",
"enhanced_prompt": "Prompt Aprimorado",
"seed_used": "Semente Usada",
"share": "Compartilhar",
"ai_description": "**Gerador de Prompts com IA** - Envie uma imagem, analise com GLM-4.6V, e gere prompts otimizados.",
"upload_image": "Enviar Imagem",
"analyze_btn": "Analisar Imagem",
"image_description": "Descrição da Imagem",
"changes_request": "Quais mudanças você quer?",
"changes_placeholder": "ex., 'estilo aquarela' ou 'iluminação dramática de pôr do sol'",
"target_style": "Estilo Alvo",
"generate_prompt_btn": "Gerar Prompt",
"generated_prompt": "Prompt Gerado",
"send_to_transform": "Enviar para Transformar",
"how_to_use": "Como Usar",
"how_to_use_content": """1. **Envie** uma imagem e clique em "Analisar Imagem"
2. **Descreva** as mudanças que você quer
3. **Gere** um prompt otimizado
4. **Envie** para a aba Transformar para aplicar mudanças""",
"transform_description": "**Transforme sua imagem** - Envie e descreva a transformação. Menor força = sutil, maior = dramático.",
"transformation_prompt": "Prompt de Transformação",
"transform_placeholder": "ex., 'estilo pintura a óleo, cores vibrantes'",
"strength": "Força",
"transform_btn": "Transformar",
"transformed_image": "Imagem Transformada",
"example_prompts": "Prompts de Exemplo",
"models": "Modelos",
"by": "por",
},
"العربية": {
"title": "Z Image Turbo + GLM-4.6V",
"subtitle": "توليد وتحويل الصور بالذكاء الاصطناعي مدعوم من DeepSeek Reasoning",
"like_msg": "إذا أعجبك، يرجى الإعجاب. شكراً لك!",
"tab_generate": "توليد",
"tab_assistant": "مساعد الذكاء الاصطناعي",
"tab_transform": "تحويل",
"prompt": "الوصف",
"prompt_placeholder": "صف صورتك بالتفصيل...",
"polish_checkbox": "تحسين+ بواسطة deepseek-reasoner",
"style": "النمط",
"aspect_ratio": "نسبة العرض",
"advanced_settings": "إعدادات متقدمة",
"steps": "الخطوات",
"seed": "البذرة",
"random_seed": "بذرة عشوائية",
"generate_btn": "توليد",
"generated_image": "الصورة المولدة",
"enhanced_prompt": "الوصف المحسن",
"seed_used": "البذرة المستخدمة",
"share": "مشاركة",
"ai_description": "**مولد الأوصاف بالذكاء الاصطناعي** - ارفع صورة، حللها باستخدام GLM-4.6V، ثم أنشئ أوصافاً محسنة.",
"upload_image": "رفع صورة",
"analyze_btn": "تحليل الصورة",
"image_description": "وصف الصورة",
"changes_request": "ما التغييرات التي تريدها؟",
"changes_placeholder": "مثال: 'نمط ألوان مائية' أو 'إضاءة غروب درامية'",
"target_style": "النمط المستهدف",
"generate_prompt_btn": "توليد الوصف",
"generated_prompt": "الوصف المولد",
"send_to_transform": "إرسال إلى التحويل",
"how_to_use": "كيفية الاستخدام",
"how_to_use_content": """1. **ارفع** صورة وانقر على "تحليل الصورة"
2. **صف** التغييرات التي تريدها
3. **أنشئ** وصفاً محسناً
4. **أرسل** إلى تبويب التحويل لتطبيق التغييرات""",
"transform_description": "**حوّل صورتك** - ارفع وصف التحويل. قوة أقل = تغيير طفيف، قوة أكبر = تغيير جذري.",
"transformation_prompt": "وصف التحويل",
"transform_placeholder": "مثال: 'نمط لوحة زيتية، ألوان نابضة'",
"strength": "القوة",
"transform_btn": "تحويل",
"transformed_image": "الصورة المحولة",
"example_prompts": "أمثلة الأوصاف",
"models": "النماذج",
"by": "بواسطة",
},
"हिंदी": {
"title": "Z Image Turbo + GLM-4.6V",
"subtitle": "DeepSeek Reasoning द्वारा संचालित AI छवि निर्माण और रूपांतरण",
"like_msg": "अगर आपको पसंद आया, तो कृपया लाइक करें। धन्यवाद!",
"tab_generate": "बनाएं",
"tab_assistant": "AI सहायक",
"tab_transform": "रूपांतरित करें",
"prompt": "प्रॉम्प्ट",
"prompt_placeholder": "अपनी छवि का विस्तार से वर्णन करें...",
"polish_checkbox": "Prompt+ by deepseek-reasoner",
"style": "शैली",
"aspect_ratio": "पक्षानुपात",
"advanced_settings": "उन्नत सेटिंग्स",
"steps": "चरण",
"seed": "बीज",
"random_seed": "यादृच्छिक बीज",
"generate_btn": "बनाएं",
"generated_image": "बनाई गई छवि",
"enhanced_prompt": "उन्नत प्रॉम्प्ट",
"seed_used": "प्रयुक्त बीज",
"share": "साझा करें",
"ai_description": "**AI-संचालित प्रॉम्प्ट जनरेटर** - एक छवि अपलोड करें, GLM-4.6V से विश्लेषण करें, फिर अनुकूलित प्रॉम्प्ट बनाएं।",
"upload_image": "छवि अपलोड करें",
"analyze_btn": "छवि विश्लेषण करें",
"image_description": "छवि विवरण",
"changes_request": "आप क्या बदलाव चाहते हैं?",
"changes_placeholder": "उदा., 'वॉटरकलर शैली' या 'नाटकीय सूर्यास्त प्रकाश'",
"target_style": "लक्ष्य शैली",
"generate_prompt_btn": "प्रॉम्प्ट बनाएं",
"generated_prompt": "बनाया गया प्रॉम्प्ट",
"send_to_transform": "रूपांतरण टैब पर भेजें",
"how_to_use": "कैसे उपयोग करें",
"how_to_use_content": """1. **अपलोड** करें एक छवि और "छवि विश्लेषण करें" पर क्लिक करें
2. **वर्णन** करें जो बदलाव आप चाहते हैं
3. **बनाएं** एक अनुकूलित प्रॉम्प्ट
4. **भेजें** रूपांतरण टैब पर बदलाव लागू करने के लिए""",
"transform_description": "**अपनी छवि रूपांतरित करें** - अपलोड करें और रूपांतरण का वर्णन करें। कम शक्ति = सूक्ष्म, अधिक = नाटकीय।",
"transformation_prompt": "रूपांतरण प्रॉम्प्ट",
"transform_placeholder": "उदा., 'तेल चित्रकला शैली, जीवंत रंग'",
"strength": "शक्ति",
"transform_btn": "रूपांतरित करें",
"transformed_image": "रूपांतरित छवि",
"example_prompts": "उदाहरण प्रॉम्प्ट",
"models": "मॉडल",
"by": "द्वारा",
},
}
def get_text(lang: str, key: str) -> str:
"""Get translated text for a key."""
return TRANSLATIONS.get(lang, TRANSLATIONS["English"]).get(key, key)
def change_language(lang_name: str):
"""Update all component labels when language changes."""
t = TRANSLATIONS.get(lang_name, TRANSLATIONS["English"])
return [
# Generate tab
gr.update(label=t["prompt"], placeholder=t["prompt_placeholder"]),
gr.update(label=t["polish_checkbox"]),
gr.update(label=t["style"]),
gr.update(label=t["aspect_ratio"]),
gr.update(label=t["steps"]),
gr.update(label=t["seed"]),
gr.update(label=t["random_seed"]),
gr.update(value=t["generate_btn"]),
gr.update(label=t["generated_image"]),
gr.update(label=t["enhanced_prompt"]),
gr.update(label=t["seed_used"]),
gr.update(value=t["share"]),
# AI Assistant tab
gr.update(value=t["ai_description"]),
gr.update(label=t["upload_image"]),
gr.update(value=t["analyze_btn"]),
gr.update(label=t["image_description"]),
gr.update(label=t["changes_request"], placeholder=t["changes_placeholder"]),
gr.update(label=t["target_style"]),
gr.update(value=t["generate_prompt_btn"]),
gr.update(label=t["generated_prompt"]),
gr.update(value=t["send_to_transform"]),
gr.update(value=t["how_to_use_content"]),
# Transform tab
gr.update(value=t["transform_description"]),
gr.update(label=t["upload_image"]),
gr.update(label=t["transformation_prompt"], placeholder=t["transform_placeholder"]),
gr.update(label=t["polish_checkbox"]),
gr.update(label=t["style"]),
gr.update(label=t["strength"]),
gr.update(label=t["steps"]),
gr.update(label=t["seed"]),
gr.update(label=t["random_seed"]),
gr.update(value=t["transform_btn"]),
gr.update(label=t["transformed_image"]),
gr.update(label=t["enhanced_prompt"]),
gr.update(label=t["seed_used"]),
gr.update(value=t["share"]),
]
# =============================================================================
# Constants (replaces magic numbers)
MIN_IMAGE_DIM = 512
MAX_IMAGE_DIM = 2048
IMAGE_ALIGNMENT = 16
API_TIMEOUT = 90.0
API_MAX_RETRIES = 2
MAX_DESCRIPTION_LENGTH = 1200 # For GLM prompt generation
# Enable optimized backends (SDPA uses FlashAttention when available)
torch.backends.cuda.enable_flash_sdp(True)
torch.backends.cuda.enable_mem_efficient_sdp(True)
torch.backends.cudnn.benchmark = True
# Enable TF32 for better performance on Ampere+ GPUs
torch.backends.cuda.matmul.allow_tf32 = True
torch.backends.cudnn.allow_tf32 = True
# Singleton clients with timeout and retry
_deepseek_client: Optional[OpenAI] = None
_glm_client: Optional[OpenAI] = None
def get_deepseek_client() -> Optional[OpenAI]:
"""Get DeepSeek API client (singleton with timeout)."""
global _deepseek_client
if _deepseek_client is None:
api_key = os.environ.get("DEEPSEEK_API_KEY")
if not api_key:
logger.warning("DEEPSEEK_API_KEY not configured")
return None
_deepseek_client = OpenAI(
base_url="https://api.deepseek.com",
api_key=api_key,
timeout=API_TIMEOUT,
max_retries=API_MAX_RETRIES,
)
return _deepseek_client
def polish_prompt(original_prompt: str, mode: str = "generate") -> str:
"""Expand short prompts into detailed, high-quality prompts using deepseek-reasoner."""
logger.info(f"polish_prompt called: mode={mode}, prompt_len={len(original_prompt) if original_prompt else 0}")
if not original_prompt or not original_prompt.strip():
logger.info("polish_prompt: empty input, using default")
if mode == "transform":
return "high quality, enhanced details, professional finish"
return "Ultra HD, 4K, cinematic composition, highly detailed"
client = get_deepseek_client()
if not client:
logger.warning("polish_prompt: DeepSeek client not available, returning original")
return original_prompt
if mode == "transform":
system_prompt = """ROLE: Expert prompt engineer for AI image-to-image transformation.
TASK: Rewrite the user's input into a precise, technical prompt describing the target visual result.
STRICT RULES:
- MAXIMUM 600 TOKENS (strict limit). You MUST write the new prompt in maximum of 600 tokens.
- Focus on: artistic style, color palette, lighting, texture, rendering technique, mood
- Describe HOW the image should look, not what to change
- No action words like "transform", "convert", "change"
- Present tense, as if describing the final image
You MUST respect the maximum of 600 TOKENS in your response.
OUTPUT FORMAT: Only the final prompt text. No thinking, no explanation, no preamble, no word count."""
else:
system_prompt = """ROLE: Expert prompt engineer for AI image generation.
TASK: Expand the user's input into a detailed, expressive prompt for stunning image generation.
STRICT RULES:
- MAXIMUM 600 TOKENS (strict limit). You MUST write the new prompt in maximum of 600 tokens.
- Be descriptive about: subject, lighting, atmosphere, style, composition, details
- Use vivid, specific language
- Include artistic style references when appropriate
You MUST respect the maximum of 600 TOKENS in your response.
OUTPUT FORMAT: Only the final prompt text. No thinking, no explanation, no preamble, no word count."""
try:
response = client.chat.completions.create(
model="deepseek-reasoner",
max_tokens=600,
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": original_prompt}
],
)
msg = response.choices[0].message
content = msg.content if msg.content else ""
logger.info(f"polish_prompt API response: content_len={len(content)}, has_reasoning={hasattr(msg, 'reasoning_content') and bool(msg.reasoning_content)}")
# If content is empty, try to extract final answer from reasoning_content
if not content and hasattr(msg, 'reasoning_content') and msg.reasoning_content:
text = msg.reasoning_content.strip()
paragraphs = [p.strip() for p in text.split('\n\n') if p.strip()]
if paragraphs:
content = paragraphs[-1]
logger.info(f"polish_prompt: extracted from reasoning_content, len={len(content)}")
if content:
content = content.strip().replace("\n", " ")
if "" in content:
content = content.split("")[-1].strip()
if content.startswith('"') and content.endswith('"'):
content = content[1:-1]
max_words = 600 # 600 tokens limit for all modes
words = content.split()
if len(words) > max_words:
content = " ".join(words[:max_words])
logger.info(f"polish_prompt SUCCESS: enhanced from {len(original_prompt)} to {len(content)} chars")
return content
logger.warning(f"polish_prompt: no content extracted, returning original prompt")
return original_prompt
except Exception as e:
logger.error(f"polish_prompt FAILED: {type(e).__name__}: {str(e)}")
return original_prompt
# GLM-4V Vision AI functions (runs on CPU - API calls)
def get_glm_client() -> Optional[OpenAI]:
"""Get GLM API client (singleton with timeout)."""
global _glm_client
if _glm_client is None:
api_key = os.environ.get("GLM_API_KEY")
if not api_key:
return None
_glm_client = OpenAI(
base_url="https://api.z.ai/api/paas/v4",
api_key=api_key,
timeout=API_TIMEOUT,
max_retries=API_MAX_RETRIES,
)
return _glm_client
def encode_image_base64(image: Optional[Image.Image]) -> Optional[str]:
"""Convert PIL image to base64 with proper memory cleanup."""
if image is None:
return None
buf = io.BytesIO()
try:
image.save(buf, format='JPEG', quality=90) # JPEG is faster for API calls
buf.seek(0)
return base64.b64encode(buf.getvalue()).decode('utf-8')
finally:
buf.close()
def clean_glm_response(text: str) -> str:
"""Remove GLM special tokens and clean up text."""
if not text:
return ""
text = text.replace('<|begin_of_box|>', '').replace('<|end_of_box|>', '')
text = text.strip()
return text
def is_thinking_text(text: str) -> bool:
"""Check if text looks like GLM thinking/reasoning rather than actual content."""
if not text:
return True
text_lower = text.lower().strip()
# Reject if starts with planning/markdown headers
planning_starts = (
'**plan', '## plan', '# plan', 'plan:',
'**step', '## step', '# step',
'**analysis', '**approach', '**strategy',
'here is my', 'here\'s my',
)
if any(text_lower.startswith(pat) for pat in planning_starts):
return True
# Reject if starts with clear meta-language
thinking_starts = (
'let me ', 'i need to', 'i should ', 'i will ', "i'll ",
'got it', 'okay, ', 'okay ', 'alright, ', 'alright ',
'the user ', 'the request ', 'based on ', 'following the ',
'now i ', 'my prompt ', 'for this task', 'considering ',
'understood', 'i understand', 'sure, ', 'sure ',
'1. ', '1) ', # Numbered lists = planning
)
if any(text_lower.startswith(pat) for pat in thinking_starts):
return True
# Check for planning phrases ANYWHERE in text (these are NEVER in good prompts)
planning_phrases = (
'i need to describe', 'i should ', 'i\'ll describe', 'i\'ll keep',
'i will describe', 'i will keep', 'this includes',
'the key change', 'key part of the scene', 'is a defining feature',
'is crucial', 'is important', 'should remain', 'should be',
'**main subject:**', '**weapon:**', '**setting:**', '**mood:**',
'**colors', '**lighting', '**plan:**',
)
if any(phrase in text_lower for phrase in planning_phrases):
return True
return False
def analyze_image_with_glm(image: Optional[Image.Image]) -> str:
"""Analyze image using GLM-4V and return description.
FIXED: Removed double filtering, lowered thresholds, added debug logging.
"""
if image is None:
return "Please upload an image first."
client = get_glm_client()
if not client:
return "GLM API key not configured. Please add GLM_API_KEY to space secrets."
try:
base64_image = encode_image_base64(image)
response = client.chat.completions.create(
model="glm-4.6v-flash",
messages=[
{
"role": "user",
"content": [
{
"type": "image_url",
"image_url": {"url": f"data:image/jpeg;base64,{base64_image}"}
},
{
"type": "text",
"text": """Write a DETAILED image description. LENGTH: 400-500 TOKENS. This is important - DO NOT stop early, write a FULL detailed description until you reach 500 tokens.
START DIRECTLY with the main subject. NO meta-language, NO preamble.
WRONG starts: "This image shows...", "I can see...", "The image depicts...", "Here is..."
CORRECT starts: "A woman in red dress...", "Golden sunset over mountains...", "Vintage car parked..."
DESCRIBE IN DETAIL (use ALL 250-350 tokens):
- Main subject: appearance, clothing, pose, expression, features
- Setting: environment, location, architecture, objects nearby
- Colors: specific hues, color palette, dominant colors
- Lighting: source, quality, shadows, highlights, time of day
- Textures: materials (silk, metal, wood, fabric, skin)
- Atmosphere: mood, emotion, feeling, energy
- Background: secondary elements, depth, perspective
- Small details: accessories, decorations, patterns
OUTPUT FORMAT: One continuous paragraph, 400-500 tokens. No bullet points, no sections. Keep writing until you reach 500 tokens.
Write the complete detailed description now:"""
}
]
}
],
max_tokens=1000,
)
msg = response.choices[0].message
raw_content = msg.content if msg.content else ""
# Debug logging
logger.debug(f"GLM Analyze: raw content length={len(raw_content)}")
if raw_content:
logger.debug(f"GLM Analyze preview: {raw_content[:200]}...")
# For image descriptions, use the FULL content (don't split by paragraphs)
# Only apply minimal cleaning
result = clean_glm_response(raw_content)
# Remove common bad starts but keep the rest
bad_starts = ('here is', 'here\'s', 'the image shows', 'this image', 'i can see')
result_lower = result.lower()
for bad in bad_starts:
if result_lower.startswith(bad):
# Find the first period or comma and start after it
for i, c in enumerate(result):
if c in '.,:' and i < 50:
result = result[i+1:].strip()
break
break
# Strip quotes
result = result.strip('"\'""')
# If content is too short, try reasoning_content
if len(result) < 100:
if hasattr(msg, 'reasoning_content') and msg.reasoning_content:
reasoning = clean_glm_response(msg.reasoning_content)
# Take the longest paragraph from reasoning as fallback
paragraphs = [p.strip() for p in reasoning.split('\n\n') if len(p.strip()) > 50]
if paragraphs:
longest = max(paragraphs, key=len)
if len(longest) > len(result):
result = longest.strip('"\'""')
logger.debug(f"GLM Analyze: using reasoning content ({len(result)} chars)")
if result and len(result) >= 50:
logger.info(f"GLM Analyze: success ({len(result)} chars)")
return result
error_details = f"content_len={len(raw_content)}"
logger.warning(f"GLM Analyze: result too short ({error_details})")
return f"Description too short ({error_details}). Please try again."
except Exception as e:
logger.error(f"GLM Analyze exception: {type(e).__name__}: {str(e)}")
return f"Error analyzing image: {str(e)}"
def generate_prompt_with_glm(image_description: str, user_request: str, style: str) -> str:
"""Generate transformation prompt using GLM based on image description and user request.
FIXED: Removed double filtering, lowered thresholds, added debug logging.
"""
if not image_description or image_description.startswith("Please") or image_description.startswith("Error") or image_description.startswith("GLM API") or image_description.startswith("Could not"):
return "Please analyze the image first."
if not user_request or not user_request.strip():
return "Please describe what changes you want."
client = get_glm_client()
if not client:
return "GLM API key not configured. Please add GLM_API_KEY to space secrets."
style_hint = f" Style: {style}." if style and style != "None" else ""
desc = image_description[:MAX_DESCRIPTION_LENGTH] if len(image_description) > MAX_DESCRIPTION_LENGTH else image_description
try:
response = client.chat.completions.create(
model="glm-4.6v-flash",
messages=[
{
"role": "user",
"content": f"""TASK: Write an image prompt describing the FINAL transformed scene.
ORIGINAL: {desc}
CHANGE: {user_request}{style_hint}
CRITICAL OUTPUT RULES:
- Output ONLY the final prompt text (80-120 words)
- Start directly with the main subject (e.g., "A cyberpunk samurai...")
- NO planning, NO thinking, NO explanations, NO numbered lists
- NO phrases like "I will", "I should", "The key change is"
- ONE paragraph describing the final image as if it already exists
OUTPUT THE PROMPT NOW (nothing else):"""
}
],
max_tokens=1000,
)
msg = response.choices[0].message
raw_content = msg.content if msg.content else ""
# Debug logging
logger.debug(f"GLM Prompt: raw content length={len(raw_content)}")
if raw_content:
logger.debug(f"GLM Prompt preview: {raw_content[:200]}...")
# Use FULL content (don't split by paragraphs)
result = clean_glm_response(raw_content)
# Remove thinking starts but keep the rest
result_lower = result.lower()
bad_starts = ('here is', 'here\'s', 'sure,', 'sure ', 'okay,', 'okay ')
for bad in bad_starts:
if result_lower.startswith(bad):
for i, c in enumerate(result):
if c in '.,:' and i < 30:
result = result[i+1:].strip()
break
break
# Check if it's thinking text
if is_thinking_text(result):
# Try reasoning_content
if hasattr(msg, 'reasoning_content') and msg.reasoning_content:
reasoning = clean_glm_response(msg.reasoning_content)
paragraphs = [p.strip() for p in reasoning.split('\n\n') if len(p.strip()) > 50 and not is_thinking_text(p)]
if paragraphs:
result = max(paragraphs, key=len)
logger.debug(f"GLM Prompt: using reasoning ({len(result)} chars)")
result = result.strip('"\'""')
if result and len(result) >= 50:
logger.info(f"GLM Prompt: success ({len(result)} chars)")
return result
error_details = f"content_len={len(raw_content)}"
logger.warning(f"GLM Prompt: failed ({error_details})")
return f"Could not generate prompt ({error_details}). Please try again."
except Exception as e:
logger.error(f"GLM Prompt exception: {type(e).__name__}: {str(e)}")
return f"Error: {str(e)}"
logger.info("Loading Z-Image-Turbo pipeline...")
pipe_t2i = DiffusionPipeline.from_pretrained(
"Tongyi-MAI/Z-Image-Turbo",
torch_dtype=torch.bfloat16, # Set dtype at load time for efficiency
)
pipe_t2i.to("cuda")
# Enable FlashAttention-3 via kernels library (H100/H200 Hopper GPUs)
try:
pipe_t2i.transformer.set_attention_backend("_flash_3_hub")
logger.info("FlashAttention-3 enabled via kernels library")
except Exception as e:
logger.warning(f"FA3 not available, using default SDPA attention: {e}")
# Enable AoTI for VAE decoder (transformer has incompatible dynamic device access)
try:
pipe_t2i.vae.decode = torch.compile(
pipe_t2i.vae.decode,
mode="reduce-overhead",
)
logger.info("torch.compile (AoTI) enabled for VAE decoder")
except Exception as e:
logger.warning(f"VAE torch.compile failed: {e}")
# Note: ZImagePipeline custom pipeline doesn't support VAE slicing/tiling optimization
pipe_i2i = ZImageImg2ImgPipeline(
transformer=pipe_t2i.transformer,
vae=pipe_t2i.vae,
text_encoder=pipe_t2i.text_encoder,
tokenizer=pipe_t2i.tokenizer,
scheduler=pipe_t2i.scheduler,
)
logger.info("Pipelines ready! (TF32 + FA3 + VAE AoTI)")
STYLES = ["None", "Photorealistic", "Cinematic", "Anime", "Digital Art",
"Oil Painting", "Watercolor", "3D Render", "Fantasy", "Sci-Fi"]
STYLE_SUFFIXES = {
"None": "",
"Photorealistic": ", photorealistic, ultra detailed, 8k, professional photography",
"Cinematic": ", cinematic lighting, movie scene, dramatic atmosphere, film grain",
"Anime": ", anime style, vibrant colors, cel shaded, studio ghibli inspired",
"Digital Art": ", digital art, artstation trending, concept art, highly detailed",
"Oil Painting": ", oil painting style, classical art, brush strokes visible",
"Watercolor": ", watercolor painting, soft edges, artistic, delicate colors",
"3D Render": ", 3D render, octane render, unreal engine 5, ray tracing",
"Fantasy": ", fantasy art, magical, ethereal glow, mystical atmosphere",
"Sci-Fi": ", science fiction, futuristic, advanced technology, neon accents",
}
RATIOS = [
"1:1 Square (1024x1024)", "16:9 Landscape (1344x768)", "9:16 Portrait (768x1344)",
"4:3 Standard (1152x896)", "3:4 Vertical (896x1152)", "21:9 Cinematic (1536x640)",
"3:2 Photo (1216x832)", "2:3 Photo Portrait (832x1216)", "1:1 XL (1536x1536)",
"16:9 XL (1920x1088)", "9:16 XL (1088x1920)", "4:3 XL (1536x1152)",
"3:4 XL (1152x1536)", "1:1 MAX (2048x2048)", "16:9 MAX (2048x1152)",
"9:16 MAX (1152x2048)", "4:3 MAX (2048x1536)", "3:4 MAX (1536x2048)",
]
RATIO_DIMS = {
"1:1 Square (1024x1024)": (1024, 1024), "16:9 Landscape (1344x768)": (1344, 768),
"9:16 Portrait (768x1344)": (768, 1344), "4:3 Standard (1152x896)": (1152, 896),
"3:4 Vertical (896x1152)": (896, 1152), "21:9 Cinematic (1536x640)": (1536, 640),
"3:2 Photo (1216x832)": (1216, 832), "2:3 Photo Portrait (832x1216)": (832, 1216),
"1:1 XL (1536x1536)": (1536, 1536), "16:9 XL (1920x1088)": (1920, 1088),
"9:16 XL (1088x1920)": (1088, 1920), "4:3 XL (1536x1152)": (1536, 1152),
"3:4 XL (1152x1536)": (1152, 1536), "1:1 MAX (2048x2048)": (2048, 2048),
"16:9 MAX (2048x1152)": (2048, 1152), "9:16 MAX (1152x2048)": (1152, 2048),
"4:3 MAX (2048x1536)": (2048, 1536), "3:4 MAX (1536x2048)": (1536, 2048),
}
EXAMPLES_GENERATE = [
["Majestic phoenix rising from volcanic flames at midnight, ember particles swirling against a star-filled sky, wings of liquid gold and crimson fire", "Fantasy", "1:1 Square (1024x1024)", 9, 42, True],
["Underwater steampunk city with brass submarines and coral-covered clockwork towers, schools of glowing fish swimming through glass tunnels", "Digital Art", "9:16 Portrait (768x1344)", 9, 42, True],
["Street food vendor in a bustling night market, steam rising from sizzling woks, colorful paper lanterns illuminating weathered hands preparing dumplings", "Photorealistic", "4:3 Standard (1152x896)", 9, 42, True],
["Android geisha performing tea ceremony in a neon-lit zen garden, holographic cherry blossoms falling around chrome kimono", "Sci-Fi", "3:4 Vertical (896x1152)", 9, 42, True],
["Venetian masquerade ball at twilight, masked dancers in elaborate baroque costumes twirling beneath frescoed ceilings, candlelight reflecting off gilded mirrors and velvet drapes", "Oil Painting", "4:3 XL (1536x1152)", 9, 42, True],
["Colossal ancient tree growing through the ruins of a forgotten temple, roots wrapped around crumbling stone pillars, golden light filtering through the dense canopy as fireflies dance in the mist", "Cinematic", "16:9 XL (1920x1088)", 9, 42, True],
["Crystal ice palace floating above frozen tundra, aurora borealis casting ethereal green and purple ribbons across the polar sky, snow wolves howling on distant glaciers below", "Fantasy", "16:9 MAX (2048x1152)", 9, 42, True],
["Alchemist laboratory in a medieval tower, bubbling potions in glass vessels connected by copper tubes, scattered grimoires and astronomical instruments, moonlight streaming through a rose window casting prismatic shadows", "Digital Art", "1:1 MAX (2048x2048)", 9, 42, True],
]
EXAMPLES_TRANSFORM = [
["Transform into ultra realistic photograph with sharp details and natural lighting", "Photorealistic", 0.7, 9, 42, True],
["Dramatic movie scene with cinematic lighting and film grain texture", "Cinematic", 0.65, 9, 42, True],
["Japanese anime style with vibrant colors and cel shading", "Anime", 0.75, 9, 42, True],
["Digital concept art style, trending on artstation", "Digital Art", 0.6, 9, 42, True],
["Classical oil painting with visible brush strokes and rich colors", "Oil Painting", 0.7, 9, 42, True],
["Soft watercolor painting with delicate washes and gentle edges", "Watercolor", 0.65, 9, 42, True],
["High quality 3D render with ray tracing and realistic materials", "3D Render", 0.7, 9, 42, True],
["Magical fantasy art with ethereal glow and mystical atmosphere", "Fantasy", 0.65, 9, 42, True],
["Futuristic sci-fi style with neon accents and advanced technology", "Sci-Fi", 0.7, 9, 42, True],
["Enhanced version with improved details and quality", "None", 0.4, 9, 42, True],
]
def upload_to_hf_cdn(image: Optional[Image.Image]) -> str:
"""Upload image to HuggingFace CDN with proper memory cleanup."""
if image is None:
return "No image to share"
buf = io.BytesIO()
try:
image.save(buf, format='PNG')
buf.seek(0)
response = requests.post(
"https://huggingface.co/uploads",
headers={"Content-Type": "image/png"},
data=buf.getvalue(),
timeout=30,
)
if response.status_code == 200:
return response.text.strip()
return f"Upload failed: {response.status_code}"
except requests.Timeout:
return "Upload timed out. Please try again."
except Exception as e:
logger.error(f"upload_to_hf_cdn failed: {type(e).__name__}: {str(e)}")
return "Upload error. Please try again."
finally:
buf.close()
def do_polish_prompt(prompt: str, style: str, do_polish: bool, mode: str = "generate") -> Tuple[str, str]:
"""Polish prompt before generation (runs on CPU, before GPU allocation)."""
if not prompt or not prompt.strip():
return "", ""
base_prompt = prompt.strip()
if do_polish:
polished = polish_prompt(base_prompt, mode=mode)
else:
polished = base_prompt
final_prompt = polished + STYLE_SUFFIXES.get(style, "")
return final_prompt, polished
def do_polish_transform_prompt(prompt: str, style: str, do_polish: bool) -> Tuple[str, str]:
"""Polish prompt for transformation (style-focused)."""
if not do_polish:
base = prompt.strip() if prompt else "high quality image"
final = base + STYLE_SUFFIXES.get(style, "")
return final, ""
return do_polish_prompt(prompt, style, True, mode="transform")
# =============================================================================
# UNIFIED WRAPPER FUNCTIONS (Fix for race condition with gr.State)
# These combine polish + generate/transform into single atomic operations
# =============================================================================
def generate_with_polish(prompt: str, style: str, do_polish: bool, ratio: str, steps: int, seed: int, randomize: bool):
"""Unified generate with progress feedback using generator.
Yields intermediate status updates so user knows what's happening.
"""
logger.info(f"generate_with_polish: do_polish={do_polish}, style={style}, prompt_len={len(prompt) if prompt else 0}")
# Always yield initial status
if do_polish:
yield None, "✨ Enhancing prompt with DeepSeek Reasoner...", seed
else:
yield None, "🎨 Preparing generation...", seed
full_prompt, polished_display = do_polish_prompt(prompt, style, do_polish, mode="generate")
# Show whether enhancement was applied
if do_polish and polished_display and polished_display != prompt:
logger.info(f"generate_with_polish: Prompt+ applied successfully")
elif do_polish:
logger.warning(f"generate_with_polish: Prompt+ was enabled but enhancement unchanged")
if not full_prompt.strip():
yield None, "❌ Empty prompt - please enter a description", seed
return
# Show status before GPU generation with the prompt that will be used
status_prompt = polished_display if polished_display else full_prompt
yield None, f"🎨 Generating image...\n\n{status_prompt}", seed
# GPU generation
image, used_seed = generate(full_prompt, polished_display, ratio, steps, seed, randomize)
# Final result
final_display = polished_display if polished_display else full_prompt
yield image, final_display, used_seed
def transform_with_polish(input_image: Optional[Image.Image], prompt: str, style: str, do_polish: bool, strength: float, steps: int, seed: int, randomize: bool):
"""Unified transform with progress feedback using generator.
Yields intermediate status updates so user knows what's happening.
"""
logger.info(f"transform_with_polish: do_polish={do_polish}, style={style}, prompt_len={len(prompt) if prompt else 0}")
if input_image is None:
yield None, "❌ Please upload an image first", 0
return
# Always yield initial status
if do_polish:
yield None, "✨ Enhancing prompt with DeepSeek Reasoner...", 0
else:
yield None, "🎨 Preparing transformation...", 0
full_prompt, polished_display = do_polish_transform_prompt(prompt, style, do_polish)
# Show whether enhancement was applied
if do_polish and polished_display and polished_display != prompt:
logger.info(f"transform_with_polish: Prompt+ applied successfully")
elif do_polish:
logger.warning(f"transform_with_polish: Prompt+ was enabled but enhancement unchanged")
# Show status before GPU transform with the prompt that will be used
status_prompt = polished_display if polished_display else full_prompt
yield None, f"🎨 Transforming image...\n\n{status_prompt}", 0
# GPU transform
image, used_seed = transform(input_image, full_prompt, polished_display, strength, steps, seed, randomize)
# Final result
final_display = polished_display if polished_display else full_prompt
yield image, final_display, used_seed
@spaces.GPU(duration=60)
def generate(full_prompt: str, polished_display: str, ratio: str, steps: int, seed: int, randomize: bool, progress=gr.Progress(track_tqdm=True)) -> Tuple[Optional[Image.Image], int]:
"""Generate image from text prompt."""
if randomize:
seed = torch.randint(0, 2**32 - 1, (1,)).item()
seed = int(seed)
if not full_prompt.strip():
return None, seed
try:
w, h = RATIO_DIMS.get(ratio, (1024, 1024))
generator = torch.Generator("cuda").manual_seed(seed)
image = pipe_t2i(
prompt=full_prompt,
height=h,
width=w,
num_inference_steps=int(steps),
guidance_scale=0.0,
generator=generator,
).images[0]
# Force PNG format for MCP server output
png_path = os.path.join(tempfile.gettempdir(), f"z_gen_{seed}.png")
image.save(png_path, format="PNG")
return Image.open(png_path), seed
except Exception as e:
logger.error(f"Generation failed: {type(e).__name__}: {str(e)}")
return None, seed
@spaces.GPU(duration=45)
def transform(input_image: Optional[Image.Image], full_prompt: str, polished_display: str, strength: float, steps: int, seed: int, randomize: bool, progress=gr.Progress(track_tqdm=True)) -> Tuple[Optional[Image.Image], int]:
"""Transform image using prompt guidance."""
if input_image is None:
return None, 0
if randomize:
seed = torch.randint(0, 2**32 - 1, (1,)).item()
seed = int(seed)
if not full_prompt.strip():
full_prompt = "high quality image, enhanced details"
try:
input_image = input_image.convert("RGB")
w, h = input_image.size
w = (w // IMAGE_ALIGNMENT) * IMAGE_ALIGNMENT
h = (h // IMAGE_ALIGNMENT) * IMAGE_ALIGNMENT
w = max(MIN_IMAGE_DIM, min(MAX_IMAGE_DIM, w))
h = max(MIN_IMAGE_DIM, min(MAX_IMAGE_DIM, h))
input_image = input_image.resize((w, h), Image.Resampling.BILINEAR)
strength = float(strength)
effective_steps = max(4, int(steps / strength)) if strength > 0 else int(steps)
generator = torch.Generator("cuda").manual_seed(seed)
image = pipe_i2i(
prompt=full_prompt,
image=input_image,
strength=strength,
num_inference_steps=effective_steps,
guidance_scale=0.0,
generator=generator,
).images[0]
# Force PNG format for MCP server output
png_path = os.path.join(tempfile.gettempdir(), f"z_trans_{seed}.png")
image.save(png_path, format="PNG")
return Image.open(png_path), seed
except Exception as e:
logger.error(f"Transform failed: {type(e).__name__}: {str(e)}")
return None, seed
# =============================================================================
# MCP-FRIENDLY WRAPPER FUNCTIONS
# These functions expose all parameters directly for MCP server compatibility
# =============================================================================
@spaces.GPU(duration=60)
def mcp_generate(prompt: str, style: str = "None", ratio: str = "1:1 Square (1024x1024)",
steps: int = 9, seed: int = 42, randomize: bool = True) -> Tuple[Optional[Image.Image], int]:
"""MCP-friendly image generation. Takes prompt directly and handles polish internally."""
if randomize:
seed = torch.randint(0, 2**32 - 1, (1,)).item()
seed = int(seed)
if not prompt or not prompt.strip():
return None, seed
# Apply style suffix
full_prompt = prompt.strip() + STYLE_SUFFIXES.get(style, "")
try:
w, h = RATIO_DIMS.get(ratio, (1024, 1024))
generator = torch.Generator("cuda").manual_seed(seed)
image = pipe_t2i(
prompt=full_prompt,
height=h,
width=w,
num_inference_steps=int(steps),
guidance_scale=0.0,
generator=generator,
).images[0]
# Force PNG format for MCP server output
png_path = os.path.join(tempfile.gettempdir(), f"z_mcp_gen_{seed}.png")
image.save(png_path, format="PNG")
return Image.open(png_path), seed
except Exception as e:
logger.error(f"MCP Generate failed: {type(e).__name__}: {str(e)}")
return None, seed
@spaces.GPU(duration=45)
def mcp_transform(image: Optional[Image.Image], prompt: str, style: str = "None",
strength: float = 0.6, steps: int = 9, seed: int = 42,
randomize: bool = True) -> Tuple[Optional[Image.Image], int]:
"""MCP-friendly image transformation. Takes all parameters directly."""
if image is None:
return None, 0
if randomize:
seed = torch.randint(0, 2**32 - 1, (1,)).item()
seed = int(seed)
# Apply style suffix
full_prompt = (prompt.strip() if prompt else "high quality image") + STYLE_SUFFIXES.get(style, "")
try:
image = image.convert("RGB")
w, h = image.size
w = (w // IMAGE_ALIGNMENT) * IMAGE_ALIGNMENT
h = (h // IMAGE_ALIGNMENT) * IMAGE_ALIGNMENT
w = max(MIN_IMAGE_DIM, min(MAX_IMAGE_DIM, w))
h = max(MIN_IMAGE_DIM, min(MAX_IMAGE_DIM, h))
image = image.resize((w, h), Image.Resampling.BILINEAR)
strength = float(strength)
effective_steps = max(4, int(steps / strength)) if strength > 0 else int(steps)
generator = torch.Generator("cuda").manual_seed(seed)
result = pipe_i2i(
prompt=full_prompt,
image=image,
strength=strength,
num_inference_steps=effective_steps,
guidance_scale=0.0,
generator=generator,
).images[0]
# Force PNG format for MCP server output
png_path = os.path.join(tempfile.gettempdir(), f"z_mcp_trans_{seed}.png")
result.save(png_path, format="PNG")
return Image.open(png_path), seed
except Exception as e:
logger.error(f"MCP Transform failed: {type(e).__name__}: {str(e)}")
return None, seed
css = r"""
/* Google Fonts for multilingual support */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Noto+Sans+Arabic:wght@400;500;600;700&family=Noto+Sans+Devanagari:wght@400;500;600;700&display=swap');
:root {
--bg-primary: #0c0c0e;
--bg-secondary: #141416;
--bg-tertiary: #1c1c20;
--surface: #232328;
--surface-hover: #2a2a30;
--accent-primary: #818cf8;
--accent-secondary: #a78bfa;
--accent-hover: #6366f1;
--accent-gradient: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
--accent-glow: rgba(99, 102, 241, 0.4);
--text-primary: #f4f4f5;
--text-secondary: #a1a1aa;
--text-muted: #71717a;
--border-subtle: rgba(255, 255, 255, 0.08);
--border-default: rgba(255, 255, 255, 0.12);
--success: #10b981;
--warning: #f59e0b;
--error: #ef4444;
--shadow-sm: 0 1px 2px rgba(0,0,0,0.3);
--shadow-md: 0 4px 6px -1px rgba(0,0,0,0.4);
--shadow-lg: 0 10px 15px -3px rgba(0,0,0,0.5);
--shadow-glow: 0 0 20px var(--accent-glow);
--radius-sm: 8px;
--radius-md: 12px;
--radius-lg: 16px;
--transition: 0.2s ease;
/* Font stacks */
--font-latin: 'Inter', -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
--font-arabic: 'Noto Sans Arabic', 'Tahoma', sans-serif;
--font-hindi: 'Noto Sans Devanagari', 'Mangal', sans-serif;
}
/* Arabic font */
.lang-ar, .lang-ar * { font-family: var(--font-arabic) !important; }
/* Hindi font */
.lang-hi, .lang-hi * { font-family: var(--font-hindi) !important; }
/* RTL Support for Arabic */
[dir="rtl"], .rtl { direction: rtl; text-align: right; }
[dir="rtl"] .tab-nav { flex-direction: row-reverse; }
[dir="rtl"] .gr-row, [dir="rtl"] [class*="row"] { flex-direction: row-reverse; }
[dir="rtl"] input, [dir="rtl"] textarea { text-align: right; direction: rtl; }
[dir="rtl"] input[type="number"] { direction: ltr; text-align: left; }
[dir="rtl"] label, [dir="rtl"] .gr-label { text-align: right; }
[dir="rtl"] .gr-checkbox { flex-direction: row-reverse; }
[dir="rtl"] .gr-slider { direction: ltr; }
[dir="rtl"] .gr-markdown ul, [dir="rtl"] .gr-markdown ol { padding-left: 0; padding-right: 1.5em; }
/* Language selector in header */
.lang-selector-row { display: flex; justify-content: flex-end; margin-bottom: 8px; }
[dir="rtl"] .lang-selector-row { justify-content: flex-start; }
.gradio-container {
background: var(--bg-primary) !important;
min-height: 100vh;
color: var(--text-primary);
}
.tabs { background: transparent !important; padding: 8px 0; }
.tab-nav {
background: var(--bg-secondary) !important;
border: 1px solid var(--border-subtle) !important;
border-radius: var(--radius-lg);
padding: 6px;
gap: 6px;
margin-bottom: 20px;
display: flex;
justify-content: center;
flex-wrap: wrap;
}
.tab-nav > button {
background: transparent !important;
color: var(--text-secondary) !important;
border: none !important;
border-radius: var(--radius-md);
padding: 12px 24px;
font-weight: 500;
font-size: 0.95rem;
cursor: pointer;
transition: all var(--transition);
}
.tab-nav > button:hover {
background: var(--bg-tertiary) !important;
color: var(--text-primary) !important;
}
.tab-nav > button.selected,
.tab-nav > button[aria-selected="true"],
[role="tab"][aria-selected="true"] {
background: var(--accent-gradient) !important;
color: white !important;
font-weight: 600;
box-shadow: var(--shadow-glow);
}
button.primary, .primary {
background: var(--accent-gradient) !important;
border: none !important;
border-radius: var(--radius-md);
font-weight: 600;
padding: 12px 24px;
color: white !important;
cursor: pointer;
transition: all var(--transition);
box-shadow: var(--shadow-md);
}
button.primary:hover, .primary:hover {
box-shadow: var(--shadow-glow), var(--shadow-lg);
filter: brightness(1.1);
}
button.secondary, .secondary {
background: var(--surface) !important;
color: var(--text-primary) !important;
border: 1px solid var(--border-default) !important;
border-radius: var(--radius-sm);
cursor: pointer;
transition: all var(--transition);
}
button.secondary:hover, .secondary:hover {
background: var(--surface-hover) !important;
border-color: var(--accent-primary) !important;
}
.block {
background: var(--bg-secondary) !important;
border: 1px solid var(--border-subtle) !important;
border-radius: var(--radius-lg) !important;
box-shadow: var(--shadow-sm);
padding: 20px;
margin: 8px 0;
transition: all var(--transition);
}
.tabitem { background: transparent !important; padding: 16px 0; }
input, textarea, .gr-input, .gr-textbox textarea {
background: var(--bg-tertiary) !important;
border: 1px solid var(--border-default) !important;
border-radius: var(--radius-sm) !important;
color: var(--text-primary) !important;
transition: all var(--transition);
}
input:focus, textarea:focus {
border-color: var(--accent-primary) !important;
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.2) !important;
outline: none !important;
}
.gr-dropdown, select {
background: var(--bg-tertiary) !important;
border: 1px solid var(--border-default) !important;
border-radius: var(--radius-sm) !important;
color: var(--text-primary) !important;
}
.gr-slider input[type="range"] { accent-color: var(--accent-primary); }
.gr-checkbox input[type="checkbox"] { accent-color: var(--accent-primary); }
label, .gr-label { color: var(--text-secondary) !important; font-weight: 500; }
.gr-image, .image-container {
background: var(--bg-tertiary) !important;
border: 2px dashed var(--border-default) !important;
border-radius: var(--radius-lg) !important;
transition: all var(--transition);
}
.gr-image:hover { border-color: var(--accent-primary) !important; }
.gr-image img { border-radius: var(--radius-md); }
/* Examples table - Dark theme (stable selectors only) */
.examples, .gr-examples, [class*="example"], [class*="Example"],
div[class*="example"], div[class*="sample"], .sample-table,
[data-testid="examples"], [data-testid*="example"] {
background: var(--bg-secondary) !important;
border-radius: var(--radius-lg) !important;
}
/* Table itself */
.examples table, .gr-examples table, [class*="example"] table,
[data-testid="examples"] table {
background: var(--bg-secondary) !important;
border-collapse: collapse !important;
width: 100% !important;
}
/* All rows */
.examples tr, .gr-examples tr, [class*="example"] tr,
[data-testid="examples"] tr {
background: var(--bg-secondary) !important;
border-bottom: 1px solid var(--border-default) !important;
}
/* Row hover */
.examples tr:hover, .gr-examples tr:hover, [class*="example"] tr:hover,
[data-testid="examples"] tr:hover {
background: var(--surface) !important;
}
/* Table cells */
.examples td, .gr-examples td, [class*="example"] td,
[data-testid="examples"] td {
color: var(--text-secondary) !important;
background: transparent !important;
}
/* First column (prompts) - emphasized */
.examples td:first-child, [class*="example"] td:first-child,
[data-testid="examples"] td:first-child {
color: var(--text-primary) !important;
font-weight: 500 !important;
}
/* Headers */
.examples th, .gr-examples th, [class*="example"] th,
[data-testid="examples"] th {
background: var(--surface) !important;
color: var(--text-primary) !important;
font-weight: 600 !important;
border-bottom: 1px solid var(--border-default) !important;
}
/* Wrapper divs */
.examples > div, [class*="example"] > div {
background: var(--bg-secondary) !important;
}
h1, h2, h3, h4 { color: var(--text-primary) !important; }
h1 { font-size: clamp(1.5rem, 4vw, 2.2rem); font-weight: 700; }
.markdown-text, .gr-markdown { color: var(--text-secondary) !important; }
.gr-markdown a { color: var(--accent-primary) !important; }
.gr-group {
background: var(--surface) !important;
border: 1px solid var(--border-subtle) !important;
border-radius: var(--radius-lg) !important;
padding: 16px !important;
}
.gr-accordion {
background: var(--bg-secondary) !important;
border: 1px solid var(--border-subtle) !important;
border-radius: var(--radius-md) !important;
}
.footer-no-box { background: transparent !important; border: none !important; box-shadow: none !important; padding: 0; }
.gradio-container > footer {
background: var(--bg-secondary) !important;
border-top: 1px solid var(--border-subtle) !important;
padding: 12px 20px;
}
.gradio-container > footer span, .gradio-container > footer p { color: var(--text-muted) !important; }
.gradio-container > footer a { color: var(--accent-primary) !important; }
.progress-bar { background: var(--bg-tertiary) !important; border-radius: 4px; }
.progress-bar > div { background: var(--accent-gradient) !important; border-radius: 4px; }
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}
@media (max-width: 768px) {
.tab-nav { padding: 4px; gap: 4px; }
.tab-nav > button { padding: 10px 16px; font-size: 0.85rem; }
.block { padding: 12px; margin: 6px 0; }
button.primary { padding: 10px 16px; width: 100%; }
h1 { font-size: 1.4rem !important; }
}
/* Accessibility - keyboard focus indicators */
button:focus-visible, input:focus-visible, textarea:focus-visible,
select:focus-visible, [role="button"]:focus-visible {
outline: 2px solid var(--accent-primary) !important;
outline-offset: 2px !important;
}
.gr-image:focus-visible, [role="tab"]:focus-visible {
outline: 2px solid var(--accent-primary) !important;
outline-offset: 2px !important;
}
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: var(--bg-secondary); }
::-webkit-scrollbar-thumb { background: var(--bg-tertiary); border-radius: 4px; }
::-webkit-scrollbar-thumb:hover { background: var(--surface); }
/* Tab navigation text */
.tab-nav button, .tab-nav > button, button[role="tab"], .tabs button { color: var(--text-primary) !important; }
/* Labels and spans */
label, .gr-label, .label-wrap, .label-wrap span, .gr-box label, .gr-form label, .gr-group label { color: var(--text-secondary) !important; }
.gr-block span, .gr-box span, .gr-form span, .gr-group span, .block span { color: var(--text-secondary) !important; }
/* Table overrides */
table thead, table thead tr, table thead th, [class*="examples"] thead th { background: var(--surface) !important; color: var(--text-primary) !important; }
table tbody td, [class*="examples"] td { color: var(--text-secondary) !important; }
/* Accordion and markdown */
.gr-accordion summary, .gr-accordion button, details summary, summary span { color: var(--text-primary) !important; }
.gr-markdown, .gr-markdown p, .gr-markdown li, .markdown-text, .prose { color: var(--text-secondary) !important; }
/* Input placeholders and buttons */
input::placeholder, textarea::placeholder { color: var(--text-muted) !important; }
button.secondary, .secondary { color: var(--text-primary) !important; }
/* Dropdown menus - dark theme */
.gr-dropdown ul, .gr-dropdown li, [data-testid="dropdown"] ul,
.svelte-select-list, .dropdown-menu, select option,
[role="listbox"], [role="listbox"] [role="option"] {
background: var(--bg-tertiary) !important;
color: var(--text-primary) !important;
}
/* Dropdown hover/selected states */
.gr-dropdown li:hover, select option:hover,
[role="option"]:hover, [role="option"][aria-selected="true"] {
background: var(--surface) !important;
}
/* Portal dropdowns (rendered outside .gradio-container) */
[data-testid="dropdown-list"],
[role="listbox"]:not(.gradio-container [role="listbox"]) {
background-color: var(--bg-tertiary) !important;
color: var(--text-primary) !important;
border: 1px solid var(--border-default) !important;
border-radius: var(--radius-sm) !important;
}
/* Slider and checkbox labels */
.gr-slider span, .gr-slider output, .range-wrap span,
input[type="range"] + span { color: var(--text-primary) !important; }
.gr-checkbox label, .gr-checkbox span,
input[type="checkbox"] + span { color: var(--text-secondary) !important; }
/* Image upload text */
.gr-image span, .gr-image p, .upload-text,
[data-testid="image"] span { color: var(--text-secondary) !important; }
.gr-image svg, .upload-icon { fill: var(--text-muted) !important; }
/* Error/warning states */
.gr-error, [class*="error"] {
background: rgba(239,68,68,0.15) !important;
color: var(--error) !important;
border-color: var(--error) !important;
}
.gr-info, [class*="info-msg"] {
background: rgba(129,140,248,0.15) !important;
color: var(--accent-primary) !important;
}
/* Copy buttons and icons */
.gr-textbox button, button svg, .copy-button {
color: var(--text-secondary) !important;
fill: var(--text-secondary) !important;
}
.gr-textbox button:hover { color: var(--text-primary) !important; }
/* Tooltips */
[role="tooltip"], .gr-tooltip, .tooltip {
background: var(--surface) !important;
color: var(--text-primary) !important;
border: 1px solid var(--border-default) !important;
}
/* Progress/loading text */
.progress-text, .loading-text, [class*="loading"] span,
[class*="progress"] span { color: var(--text-secondary) !important; }
/* Number input spinners */
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button { filter: invert(0.8); }
"""
# Create custom dark theme
dark_theme = gr.themes.Base(
primary_hue=gr.themes.colors.indigo,
secondary_hue=gr.themes.colors.purple,
neutral_hue=gr.themes.colors.zinc,
).set(
# Backgrounds
body_background_fill="#0c0c0e",
body_background_fill_dark="#0c0c0e",
background_fill_primary="#141416",
background_fill_primary_dark="#141416",
background_fill_secondary="#1c1c20",
background_fill_secondary_dark="#1c1c20",
# Borders
border_color_primary="rgba(255,255,255,0.12)",
border_color_primary_dark="rgba(255,255,255,0.12)",
# Text
body_text_color="#e5e5e5",
body_text_color_dark="#e5e5e5",
body_text_color_subdued="#a1a1aa",
body_text_color_subdued_dark="#a1a1aa",
# Blocks
block_background_fill="#141416",
block_background_fill_dark="#141416",
block_border_color="rgba(255,255,255,0.08)",
block_border_color_dark="rgba(255,255,255,0.08)",
block_label_background_fill="#1c1c20",
block_label_background_fill_dark="#1c1c20",
block_label_text_color="#a1a1aa",
block_label_text_color_dark="#a1a1aa",
# Inputs
input_background_fill="#1c1c20",
input_background_fill_dark="#1c1c20",
input_border_color="rgba(255,255,255,0.12)",
input_border_color_dark="rgba(255,255,255,0.12)",
# Buttons
button_primary_background_fill="linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)",
button_primary_background_fill_dark="linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)",
button_primary_text_color="white",
button_primary_text_color_dark="white",
button_secondary_background_fill="#232328",
button_secondary_background_fill_dark="#232328",
button_secondary_text_color="#e5e5e5",
button_secondary_text_color_dark="#e5e5e5",
# Table/Examples - CRITICAL for fixing white background
table_even_background_fill="#1a1a1e",
table_even_background_fill_dark="#1a1a1e",
table_odd_background_fill="#1a1a1e",
table_odd_background_fill_dark="#1a1a1e",
table_row_focus="#252528",
table_row_focus_dark="#252528",
)
with gr.Blocks(title="Z Image Turbo", css=css, theme=dark_theme) as demo:
# Language selector at top
with gr.Row(elem_classes="lang-selector-row"):
lang_selector = gr.Dropdown(
choices=LANGUAGES,
value="English",
label="🌐 Language",
scale=0,
min_width=160,
interactive=True
)
gr.HTML("""
Z-Image Turbo + GLM-4.6V / DeepSeek-3.2 Thinking
Image Gen & Edit with GLM-4.6V + DeepSeek-3.2
If you liked it, please ❤️ like it. Thank you!
""")
with gr.Tabs():
# TAB 1: Generate Image
with gr.Tab("Generate"):
with gr.Row():
with gr.Column(scale=2):
gen_prompt = gr.Textbox(label="Prompt", placeholder="Describe your image in detail...", lines=4)
gen_polish = gr.Checkbox(label="Prompt+ by deepseek-reasoner", value=False)
with gr.Row():
gen_style = gr.Dropdown(choices=STYLES, value="None", label="Style")
gen_ratio = gr.Dropdown(choices=RATIOS, value="1:1 Square (1024x1024)", label="Aspect Ratio")
with gr.Accordion("Advanced Settings", open=False):
gen_steps = gr.Slider(minimum=4, maximum=16, value=9, step=1, label="Steps")
with gr.Row():
gen_seed = gr.Number(label="Seed", value=42, precision=0)
gen_randomize = gr.Checkbox(label="Random Seed", value=True)
gen_btn = gr.Button("Generate", variant="primary", size="lg")
with gr.Column(scale=3):
gen_output = gr.Image(label="Generated Image", type="pil", interactive=False, height=512)
gen_polished_prompt = gr.Textbox(label="Enhanced Prompt", interactive=False, visible=True, lines=4)
gen_seed_out = gr.Number(label="Seed Used", interactive=False)
with gr.Row():
gen_share_btn = gr.Button("Share", variant="secondary")
gen_share_link = gr.Textbox(label="", interactive=False, show_copy_button=True, show_label=False)
gr.Examples(examples=EXAMPLES_GENERATE, inputs=[gen_prompt, gen_style, gen_ratio, gen_steps, gen_seed, gen_randomize])
gen_btn.click(
fn=generate_with_polish,
inputs=[gen_prompt, gen_style, gen_polish, gen_ratio, gen_steps, gen_seed, gen_randomize],
outputs=[gen_output, gen_polished_prompt, gen_seed_out]
)
gen_prompt.submit(
fn=generate_with_polish,
inputs=[gen_prompt, gen_style, gen_polish, gen_ratio, gen_steps, gen_seed, gen_randomize],
outputs=[gen_output, gen_polished_prompt, gen_seed_out]
)
gen_share_btn.click(fn=upload_to_hf_cdn, inputs=[gen_output], outputs=[gen_share_link])
# TAB 2: AI Vision Assistant
with gr.Tab("AI Assistant"):
ai_desc_md = gr.Markdown("**AI-Powered Prompt Generator** - Upload an image, analyze it with GLM-4.6V, then generate optimized prompts.")
with gr.Row():
with gr.Column(scale=1):
ai_image = gr.Image(label="Upload Image", type="pil", height=300)
ai_analyze_btn = gr.Button("Analyze Image", variant="primary")
ai_description = gr.Textbox(label="Image Description", lines=5, interactive=False)
with gr.Column(scale=1):
ai_request = gr.Textbox(label="What changes do you want?", placeholder="e.g., 'watercolor style' or 'dramatic sunset lighting'", lines=2)
ai_style = gr.Dropdown(choices=STYLES, value="None", label="Target Style")
ai_generate_btn = gr.Button("Generate Prompt", variant="primary")
ai_generated_prompt = gr.Textbox(label="Generated Prompt", lines=6, interactive=False)
ai_send_btn = gr.Button("Send to Transform Tab", variant="primary")
with gr.Accordion("How to Use", open=False):
ai_howto_md = gr.Markdown("""
1. **Upload** an image and click "Analyze Image"
2. **Describe** the changes you want
3. **Generate** an optimized prompt
4. **Send** to Transform tab to apply changes
""")
ai_analyze_btn.click(
fn=analyze_image_with_glm,
inputs=[ai_image],
outputs=[ai_description]
)
ai_generate_btn.click(
fn=generate_prompt_with_glm,
inputs=[ai_description, ai_request, ai_style],
outputs=[ai_generated_prompt]
)
# TAB 3: Transform Image
with gr.Tab("Transform"):
trans_desc_md = gr.Markdown("**Transform your image** - Upload and describe the transformation. Lower strength = subtle, higher = dramatic.")
with gr.Row():
with gr.Column(scale=2):
trans_input = gr.Image(label="Upload Image", type="pil", height=300)
trans_prompt = gr.Textbox(label="Transformation Prompt", placeholder="e.g., 'oil painting style, vibrant colors'", lines=3)
trans_polish = gr.Checkbox(label="Prompt+ by deepseek-reasoner", value=False)
with gr.Row():
trans_style = gr.Dropdown(choices=STYLES, value="None", label="Style")
trans_strength = gr.Slider(minimum=0.1, maximum=1.0, value=0.6, step=0.05, label="Strength")
with gr.Accordion("Advanced Settings", open=False):
trans_steps = gr.Slider(minimum=4, maximum=16, value=9, step=1, label="Steps")
with gr.Row():
trans_seed = gr.Number(label="Seed", value=42, precision=0)
trans_randomize = gr.Checkbox(label="Random Seed", value=True)
trans_btn = gr.Button("Transform", variant="primary", size="lg")
with gr.Column(scale=3):
trans_output = gr.Image(label="Transformed Image", type="pil", interactive=False, height=512)
trans_polished_prompt = gr.Textbox(label="Enhanced Prompt", interactive=False, visible=True, lines=4)
trans_seed_out = gr.Number(label="Seed Used", interactive=False)
with gr.Row():
trans_share_btn = gr.Button("Share", variant="secondary")
trans_share_link = gr.Textbox(label="", interactive=False, show_copy_button=True, show_label=False)
with gr.Accordion("Example Prompts", open=False):
gr.Examples(examples=EXAMPLES_TRANSFORM, inputs=[trans_prompt, trans_style, trans_strength, trans_steps, trans_seed, trans_randomize])
trans_btn.click(
fn=transform_with_polish,
inputs=[trans_input, trans_prompt, trans_style, trans_polish, trans_strength, trans_steps, trans_seed, trans_randomize],
outputs=[trans_output, trans_polished_prompt, trans_seed_out]
)
trans_prompt.submit(
fn=transform_with_polish,
inputs=[trans_input, trans_prompt, trans_style, trans_polish, trans_strength, trans_steps, trans_seed, trans_randomize],
outputs=[trans_output, trans_polished_prompt, trans_seed_out]
)
trans_share_btn.click(fn=upload_to_hf_cdn, inputs=[trans_output], outputs=[trans_share_link])
# Cross-tab handler
ai_send_btn.click(
fn=lambda prompt, img: (prompt, img),
inputs=[ai_generated_prompt, ai_image],
outputs=[trans_prompt, trans_input]
)
# Language selector - update all UI labels when language changes
lang_selector.change(
fn=change_language,
inputs=[lang_selector],
outputs=[
# Generate tab (12 components)
gen_prompt, gen_polish, gen_style, gen_ratio, gen_steps, gen_seed,
gen_randomize, gen_btn, gen_output, gen_polished_prompt, gen_seed_out, gen_share_btn,
# AI Assistant tab (10 components)
ai_desc_md, ai_image, ai_analyze_btn, ai_description, ai_request, ai_style,
ai_generate_btn, ai_generated_prompt, ai_send_btn, ai_howto_md,
# Transform tab (14 components)
trans_desc_md, trans_input, trans_prompt, trans_polish, trans_style, trans_strength,
trans_steps, trans_seed, trans_randomize, trans_btn, trans_output, trans_polished_prompt,
trans_seed_out, trans_share_btn,
]
)
gr.HTML(
"""
""",
elem_classes="footer-no-box"
)
# MCP API Endpoints - Hidden components for direct API access
with gr.Row(visible=False):
mcp_prompt_in = gr.Textbox()
mcp_style_in = gr.Dropdown(choices=STYLES, value="None")
mcp_ratio_in = gr.Dropdown(choices=RATIOS, value="1:1 Square (1024x1024)")
mcp_steps_in = gr.Slider(minimum=4, maximum=16, value=9)
mcp_seed_in = gr.Number(value=42)
mcp_random_in = gr.Checkbox(value=True)
mcp_image_out = gr.Image(type="pil", format="png")
mcp_seed_out = gr.Number()
mcp_gen_btn = gr.Button()
mcp_gen_btn.click(
fn=mcp_generate,
inputs=[mcp_prompt_in, mcp_style_in, mcp_ratio_in, mcp_steps_in, mcp_seed_in, mcp_random_in],
outputs=[mcp_image_out, mcp_seed_out],
api_name="mcp_generate"
)
demo.launch(mcp_server=True)