Spaces:
Runtime error
Runtime error
import gradio as gr | |
import logging | |
import sys | |
import random | |
import time | |
import os | |
import json | |
import uuid | |
import base64 | |
from datetime import datetime | |
from huggingface_hub import InferenceClient | |
from PIL import Image, ImageOps, ImageEnhance, ImageFilter | |
import io | |
import math | |
import hashlib | |
import tempfile | |
import re | |
import numpy as np | |
from pathlib import Path | |
# =============== SETUP LOGGING =============== | |
logging.basicConfig( | |
level=logging.INFO, | |
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', | |
handlers=[logging.StreamHandler(sys.stdout)] | |
) | |
logger = logging.getLogger("imaginova") | |
# =============== APP CONFIGURATION =============== | |
APP_CONFIG = { | |
"name": "Imaginova", | |
"tagline": "Where Imagination Becomes Reality", | |
"version": "3.0.0", | |
"description": "Create magnificent AI-generated artwork from your imagination", | |
"max_history": 50, # Maximum number of images to store in history | |
"cache_enabled": True, # Enable caching to improve performance | |
"enable_analytics": False, # Anonymous usage statistics | |
"default_theme": "auto", # Default theme (light, dark, or auto) | |
"huggingface_space": True, # Running in HF Spaces environment | |
"enable_guided_creation": True, # Enable guided creative mode | |
"max_batch_size": 4, # Maximum images in batch generation | |
"enable_image_editing": True, # Enable image editing features | |
"data_dir": "imaginova_data", # Directory to store persistent data | |
} | |
# Create data directory if it doesn't exist | |
os.makedirs(APP_CONFIG["data_dir"], exist_ok=True) | |
# =============== MODEL CLIENTS SETUP =============== | |
def setup_client(api_key=None, provider=None): | |
"""Initialize and return API client""" | |
try: | |
if provider: | |
client = InferenceClient(provider=provider, api_key=api_key) | |
logger.info(f"{provider} client initialized successfully") | |
else: | |
client = InferenceClient(api_key=api_key) | |
logger.info("Hugging Face client initialized successfully") | |
return client | |
except Exception as e: | |
logger.error(f"Error initializing client: {str(e)}") | |
return None | |
# Initialize clients | |
try: | |
# For Hugging Face Spaces, we can use the internal API | |
if APP_CONFIG["huggingface_space"]: | |
# In Hugging Face Spaces, no API key is needed | |
hf_client = InferenceClient() | |
logger.info("Hugging Face client created successfully in Spaces environment") | |
else: | |
# Replace with your actual HF API key for local development | |
hf_api_key = os.getenv("HF_API_KEY", None) | |
hf_client = setup_client(hf_api_key) | |
logger.info("Hugging Face client created successfully") | |
# Set up prompt enhancer client if API key is provided | |
enhancer_api_key = os.getenv("ENHANCER_API_KEY", None) | |
try: | |
enhancer_client = setup_client(enhancer_api_key, "text-generation") | |
use_ai_enhancer = True | |
logger.info("Prompt enhancer client created successfully") | |
except Exception as e: | |
logger.warning(f"Prompt enhancer not available: {str(e)}. Will use fallback enhancement.") | |
enhancer_client = None | |
use_ai_enhancer = False | |
except Exception as e: | |
logger.error(f"Failed to create Hugging Face client: {str(e)}") | |
hf_client = None | |
enhancer_client = None | |
use_ai_enhancer = False | |
# =============== DATA MODELS =============== | |
# Image Models with friendly names, descriptions and icons | |
IMAGE_MODELS = { | |
"stabilityai/stable-diffusion-xl-base-1.0": { | |
"display_name": "Nova XL", | |
"description": "Premium quality with exceptional detail and composition", | |
"icon": "⭐", | |
"speed": "slow", | |
"quality": "excellent", | |
"strengths": "Composition, detail, realism", | |
"weaknesses": "Generation time, resource intensive", | |
"category": "premium", | |
"recommended_for": ["portraits", "landscapes", "detailed scenes", "professional work"] | |
}, | |
"runwayml/stable-diffusion-v1-5": { | |
"display_name": "Nova Fast", | |
"description": "Quick generation with good all-around results", | |
"icon": "🚀", | |
"speed": "fast", | |
"quality": "good", | |
"strengths": "Speed, versatility, community support", | |
"weaknesses": "Less sophisticated than Nova XL", | |
"category": "standard", | |
"recommended_for": ["quick sketches", "iterations", "simpler scenes"] | |
}, | |
"stabilityai/stable-diffusion-2-1": { | |
"display_name": "Nova Balance", | |
"description": "Balanced approach between speed and quality", | |
"icon": "✨", | |
"speed": "medium", | |
"quality": "very good", | |
"strengths": "Human faces, consistency, balanced", | |
"weaknesses": "Less stylistic range than Nova Fast", | |
"category": "standard", | |
"recommended_for": ["portraits", "consistent style", "balanced workflow"] | |
}, | |
"prompthero/openjourney": { | |
"display_name": "Nova Art", | |
"description": "Stylized artistic results with vibrant imagination", | |
"icon": "🎨", | |
"speed": "medium", | |
"quality": "stylized", | |
"strengths": "Artistic style, vibrant colors", | |
"weaknesses": "Less photorealistic, inconsistent faces", | |
"category": "specialized", | |
"recommended_for": ["creative artworks", "stylized illustrations", "fantasy concepts"] | |
}, | |
"dreamlike-art/dreamlike-diffusion-1.0": { | |
"display_name": "Nova Dream", | |
"description": "Dreamy, ethereal aesthetics with artistic flair", | |
"icon": "💫", | |
"speed": "medium", | |
"quality": "artistic", | |
"strengths": "Dreamy atmospheres, artistic flair", | |
"weaknesses": "Less precise control, abstract results", | |
"category": "specialized", | |
"recommended_for": ["dreamy scenes", "abstract concepts", "atmospheric images"] | |
}, | |
"cjwbw/anything-v3-better-vae": { | |
"display_name": "Nova Anime", | |
"description": "Specialized for anime and illustration styles", | |
"icon": "🍙", | |
"speed": "fast", | |
"quality": "specialized", | |
"strengths": "Anime style, illustrations, characters", | |
"weaknesses": "Not suitable for photorealism", | |
"category": "specialized", | |
"recommended_for": ["anime characters", "manga style", "cartoon illustrations"] | |
}, | |
"eimiss/EimisAnimeDiffusion_1.0v": { | |
"display_name": "Nova Toon", | |
"description": "Enhanced anime and cartoon renderer with vibrant colors", | |
"icon": "📺", | |
"speed": "medium", | |
"quality": "specialized", | |
"strengths": "Vibrant anime, consistent character design", | |
"weaknesses": "Limited to cartoon styles", | |
"category": "specialized", | |
"recommended_for": ["vivid anime", "cartoon characters", "stylized portraits"] | |
}, | |
"thibaud/sdxl-lightning": { | |
"display_name": "Nova Lightning", | |
"description": "Ultra-fast version of Nova XL with impressive speed", | |
"icon": "⚡", | |
"speed": "very fast", | |
"quality": "good", | |
"strengths": "Extreme speed, good quality", | |
"weaknesses": "Less detail than Nova XL", | |
"category": "premium", | |
"recommended_for": ["quick iterations", "rapid prototyping", "multiple variations"] | |
}, | |
"stabilityai/sdxl-turbo": { | |
"display_name": "Nova Turbo", | |
"description": "Accelerated high-quality generation with fewer steps", | |
"icon": "🔱", | |
"speed": "fast", | |
"quality": "premium", | |
"strengths": "Speed with premium quality", | |
"weaknesses": "Slightly less detailed than Nova XL", | |
"category": "premium", | |
"recommended_for": ["production quality with speed", "professional work", "detailed concepts"] | |
}, | |
"SG161222/Realistic_Vision_V5.1": { | |
"display_name": "Nova Reality", | |
"description": "Specialized in photorealistic people and scenes", | |
"icon": "👁️", | |
"speed": "medium", | |
"quality": "specialized", | |
"strengths": "Realistic humans, lifelike scenes", | |
"weaknesses": "Less versatile for other styles", | |
"category": "specialized", | |
"recommended_for": ["realistic portraits", "photorealistic scenes", "human figures"] | |
} | |
} | |
# Creation types with icons and detailed descriptions | |
CREATION_TYPES = { | |
"Realistic Photo": { | |
"description": "Create a photorealistic image with natural details and lighting", | |
"icon": "📷", | |
"prompt_hint": "Try to include details about lighting, time of day, and environment", | |
"examples": ["portrait photography", "landscape photo", "product photography"], | |
"recommended_models": ["Nova XL", "Nova Reality", "Nova Balance"] | |
}, | |
"Digital Art": { | |
"description": "Create colorful digital artwork with clean lines and vibrant colors", | |
"icon": "🖌️", | |
"prompt_hint": "Consider specifying color palette and mood for better results", | |
"examples": ["digital landscape art", "character concept", "futuristic city"], | |
"recommended_models": ["Nova Fast", "Nova Art", "Nova XL"] | |
}, | |
"Fantasy Illustration": { | |
"description": "Create magical and fantastical scenes with otherworldly elements", | |
"icon": "🧙", | |
"prompt_hint": "Describe magical elements, creatures, and environments in detail", | |
"examples": ["dragon's lair", "fairy forest", "wizard tower"], | |
"recommended_models": ["Nova Art", "Nova Dream", "Nova XL"] | |
}, | |
"Concept Art": { | |
"description": "Create professional concept art for characters, environments or objects", | |
"icon": "🎮", | |
"prompt_hint": "Include details about perspective, purpose, and design influences", | |
"examples": ["sci-fi vehicle", "fantasy character design", "alien landscape"], | |
"recommended_models": ["Nova XL", "Nova Art", "Nova Turbo"] | |
}, | |
"Anime/Manga": { | |
"description": "Create Japanese anime or manga style illustration", | |
"icon": "🍙", | |
"prompt_hint": "Specify anime aesthetics like shading style and character features", | |
"examples": ["anime portrait", "manga action scene", "chibi character"], | |
"recommended_models": ["Nova Anime", "Nova Toon", "Nova Art"] | |
}, | |
"Oil Painting": { | |
"description": "Create an image with oil painting textures and artistic brushstrokes", | |
"icon": "🖼️", | |
"prompt_hint": "Consider describing texture, brushwork style, and canvas feel", | |
"examples": ["oil portrait", "impressionist landscape", "still life painting"], | |
"recommended_models": ["Nova XL", "Nova Dream", "Nova Art"] | |
}, | |
"Watercolor": { | |
"description": "Create a soft watercolor illustration with subtle color blending", | |
"icon": "💧", | |
"prompt_hint": "Mention color blending, paper texture, and watercolor-specific effects", | |
"examples": ["watercolor landscape", "botanical illustration", "watercolor portrait"], | |
"recommended_models": ["Nova Dream", "Nova XL", "Nova Art"] | |
}, | |
"Sketch": { | |
"description": "Create a detailed sketch or drawing with line art focus", | |
"icon": "✏️", | |
"prompt_hint": "Describe line weight, hatching style, and sketch medium (pencil, charcoal, etc.)", | |
"examples": ["pencil portrait", "architectural sketch", "nature study drawing"], | |
"recommended_models": ["Nova Balance", "Nova Fast", "Nova XL"] | |
}, | |
"3D Rendering": { | |
"description": "Create an image that looks like a 3D rendered scene with realistic lighting", | |
"icon": "💻", | |
"prompt_hint": "Include details about lighting setup, materials, and camera perspective", | |
"examples": ["3D product render", "architectural visualization", "game environment"], | |
"recommended_models": ["Nova XL", "Nova Reality", "Nova Balance"] | |
}, | |
"Pixel Art": { | |
"description": "Create retro-style pixel art with limited color palette", | |
"icon": "👾", | |
"prompt_hint": "Specify resolution, color limitations, and pixel art style (e.g., 16-bit, 8-bit)", | |
"examples": ["pixel game character", "isometric pixel scene", "pixel landscape"], | |
"recommended_models": ["Nova Fast", "Nova Art", "Nova Balance"] | |
}, | |
"Isometric Art": { | |
"description": "Create isometric style illustrations with geometric perspective", | |
"icon": "📐", | |
"prompt_hint": "Describe the scene elements, color scheme, and isometric perspective", | |
"examples": ["isometric room", "isometric city", "isometric landscape"], | |
"recommended_models": ["Nova Fast", "Nova Balance", "Nova Art"] | |
}, | |
"Cinematic Scene": { | |
"description": "Create a dramatic scene with cinematic lighting and composition", | |
"icon": "🎬", | |
"prompt_hint": "Specify camera angle, lighting setup, and film style references", | |
"examples": ["sci-fi movie scene", "dramatic character moment", "epic landscape shot"], | |
"recommended_models": ["Nova XL", "Nova Turbo", "Nova Reality"] | |
}, | |
"Character Portrait": { | |
"description": "Create a focused portrait of a character with detailed features", | |
"icon": "👤", | |
"prompt_hint": "Describe facial features, expression, clothing, and character background", | |
"examples": ["fantasy character", "sci-fi hero", "historical figure"], | |
"recommended_models": ["Nova XL", "Nova Reality", "Nova Anime"] | |
}, | |
"Landscape": { | |
"description": "Create a stunning natural or urban landscape with atmospheric elements", | |
"icon": "🏞️", | |
"prompt_hint": "Include details about terrain, weather, time of day, and focal points", | |
"examples": ["mountain vista", "coastal sunset", "futuristic cityscape"], | |
"recommended_models": ["Nova XL", "Nova Turbo", "Nova Dream"] | |
}, | |
"Abstract Composition": { | |
"description": "Create non-representational art with focus on shapes, colors and textures", | |
"icon": "🔳", | |
"prompt_hint": "Describe mood, color relationships, textures, and compositional elements", | |
"examples": ["geometric abstraction", "fluid color composition", "textural abstract"], | |
"recommended_models": ["Nova Dream", "Nova Art", "Nova XL"] | |
}, | |
"Product Visualization": { | |
"description": "Create professional product imagery with studio-quality lighting", | |
"icon": "📦", | |
"prompt_hint": "Describe the product details, materials, lighting setup, and background", | |
"examples": ["gadget render", "fashion accessory", "food photography"], | |
"recommended_models": ["Nova XL", "Nova Reality", "Nova Turbo"] | |
} | |
} | |
# Art styles with icons and detailed descriptions | |
ART_STYLES = { | |
"Photorealistic": { | |
"description": "Detailed realistic style that resembles a photograph with accurate lighting and textures", | |
"icon": "📸", | |
"examples": "Works by Chuck Close, Richard Estes, or modern 3D renderings", | |
"technical_terms": ["hyperdetailed", "photographic", "lifelike", "realistic lighting"] | |
}, | |
"Impressionist": { | |
"description": "Soft brushstrokes that capture light and atmosphere over precise details, like Monet", | |
"icon": "🌈", | |
"examples": "Claude Monet, Pierre-Auguste Renoir, Camille Pissarro", | |
"technical_terms": ["impressionism", "plein air", "visible brushstrokes", "light emphasis"] | |
}, | |
"Surrealist": { | |
"description": "Dreamlike quality with impossible or irrational scenes, like Salvador Dali", | |
"icon": "🌀", | |
"examples": "Salvador Dali, René Magritte, Frida Kahlo", | |
"technical_terms": ["surrealism", "dreamlike", "symbolic", "juxtaposition"] | |
}, | |
"Pop Art": { | |
"description": "Bold colors, sharp lines and popular culture references, like Andy Warhol", | |
"icon": "🎭", | |
"examples": "Andy Warhol, Roy Lichtenstein, Keith Haring", | |
"technical_terms": ["pop art", "halftone dots", "bold outlines", "bright flat colors"] | |
}, | |
"Minimalist": { | |
"description": "Simplified forms, limited color palette, and clean composition with minimal elements", | |
"icon": "⬜", | |
"examples": "Piet Mondrian, Kazimir Malevich, Agnes Martin", | |
"technical_terms": ["minimalism", "geometric", "simplicity", "negative space"] | |
}, | |
"Abstract": { | |
"description": "Non-representational style using shapes, colors, and forms to express ideas", | |
"icon": "🔶", | |
"examples": "Wassily Kandinsky, Jackson Pollock, Mark Rothko", | |
"technical_terms": ["abstract", "non-figurative", "expressive", "color field"] | |
}, | |
"Cubist": { | |
"description": "Geometric shapes and multiple perspectives shown simultaneously, like Picasso", | |
"icon": "📐", | |
"examples": "Pablo Picasso, Georges Braque, Juan Gris", | |
"technical_terms": ["cubism", "geometric", "multiple perspectives", "fragmented"] | |
}, | |
"Art Nouveau": { | |
"description": "Ornate, flowing lines inspired by natural forms with decorative elegance", | |
"icon": "🌿", | |
"examples": "Alphonse Mucha, Gustav Klimt, Antoni Gaudí", | |
"technical_terms": ["art nouveau", "organic curves", "decorative", "floral motifs"] | |
}, | |
"Gothic": { | |
"description": "Dark, medieval-inspired aesthetic with dramatic lighting and architectural elements", | |
"icon": "🏰", | |
"examples": "Zdzisław Beksiński, H.R. Giger, medieval architecture", | |
"technical_terms": ["gothic", "medieval", "dark fantasy", "ornate details"] | |
}, | |
"Cyberpunk": { | |
"description": "Futuristic dystopian style with neon colors, technology, and urban decay", | |
"icon": "🤖", | |
"examples": "Blade Runner, Ghost in the Shell, Akira", | |
"technical_terms": ["cyberpunk", "neon", "high-tech low-life", "dystopian"] | |
}, | |
"Steampunk": { | |
"description": "Victorian-era aesthetic combined with steam-powered technology and brass elements", | |
"icon": "⚙️", | |
"examples": "Works by James Ng, Keith Thompson, retrofuturistic Jules Verne adaptations", | |
"technical_terms": ["steampunk", "brass", "victorian", "mechanical"] | |
}, | |
"Retro/Vintage": { | |
"description": "Nostalgic style reminiscent of past decades with period-appropriate elements", | |
"icon": "📺", | |
"examples": "1950s advertisements, vintage travel posters, pulp magazine covers", | |
"technical_terms": ["retro", "vintage", "nostalgic", "aged texture"] | |
}, | |
"Art Deco": { | |
"description": "Geometric patterns, bold colors, and luxurious materials in a symmetrical style", | |
"icon": "🏢", | |
"examples": "Works from the 1920s-30s, Chrysler Building, Tamara de Lempicka paintings", | |
"technical_terms": ["art deco", "geometric", "luxurious", "symmetrical"] | |
}, | |
"Baroque": { | |
"description": "Dramatic, ornate style with rich details, contrast, and dynamic composition", | |
"icon": "👑", | |
"examples": "Caravaggio, Rembrandt, Peter Paul Rubens", | |
"technical_terms": ["baroque", "chiaroscuro", "ornate", "dramatic lighting"] | |
}, | |
"Ukiyo-e": { | |
"description": "Traditional Japanese woodblock print style with flat areas of color and strong outlines", | |
"icon": "🌊", | |
"examples": "Hokusai's Great Wave, Hiroshige's landscapes, traditional Japanese prints", | |
"technical_terms": ["ukiyo-e", "woodblock print", "japanese", "flat color"] | |
}, | |
"Comic Book": { | |
"description": "Bold outlines, bright colors, and action-oriented composition like classic comics", | |
"icon": "💥", | |
"examples": "Jack Kirby, Steve Ditko, modern Marvel/DC art styles", | |
"technical_terms": ["comic art", "bold outlines", "dynamic", "halftone"] | |
}, | |
"Psychedelic": { | |
"description": "Vibrant, swirling colors with abstract patterns inspired by 1960s art", | |
"icon": "🔄", | |
"examples": "1960s concert posters, Peter Max, Alex Grey", | |
"technical_terms": ["psychedelic", "vibrant", "swirling", "fractal patterns"] | |
}, | |
"Vaporwave": { | |
"description": "Glitch aesthetics with pastel colors, 80s/90s nostalgia and digital elements", | |
"icon": "📼", | |
"examples": "Retrowave aesthetics, 80s digital graphics, glitch art", | |
"technical_terms": ["vaporwave", "retrowave", "glitch", "pastel"] | |
}, | |
"Studio Ghibli": { | |
"description": "Whimsical, detailed animation style inspired by Japanese animated films", | |
"icon": "🐉", | |
"examples": "Spirited Away, My Neighbor Totoro, Howl's Moving Castle", | |
"technical_terms": ["anime", "ghibli", "miyazaki", "hand-drawn animation"] | |
}, | |
"Hyperrealism": { | |
"description": "Extremely detailed realism that exceeds photograph-like precision", | |
"icon": "🔍", | |
"examples": "Works by Roberto Bernardi, Denis Peterson, Gottfried Helnwein", | |
"technical_terms": ["hyperrealism", "photorealistic", "extreme detail", "precision"] | |
}, | |
"Stained Glass": { | |
"description": "Design resembling stained glass with defined segments and vivid colors", | |
"icon": "🪟", | |
"examples": "Medieval cathedral windows, Tiffany lamps, modern stained glass art", | |
"technical_terms": ["stained glass", "leaded", "translucent", "sectioned"] | |
}, | |
"Synthwave": { | |
"description": "80s-inspired futuristic style with neon grids, sunset gradients and retro elements", | |
"icon": "🌆", | |
"examples": "Retrowave artwork, 80s sci-fi aesthetics, digital sunset landscapes", | |
"technical_terms": ["synthwave", "retrowave", "neon", "grid", "80s futurism"] | |
}, | |
"Low Poly": { | |
"description": "3D style with visible polygonal faces and geometric simplification", | |
"icon": "📊", | |
"examples": "Low polygon 3D models, indie game art, geometric illustrations", | |
"technical_terms": ["low poly", "polygonal", "faceted", "geometric"] | |
}, | |
"Watercolor Sketch": { | |
"description": "Combination of line drawing with soft watercolor washes and bleeding colors", | |
"icon": "🎨", | |
"examples": "Urban sketchers, travel journals, architectural sketches with color", | |
"technical_terms": ["urban sketch", "line and wash", "watercolor sketch", "loose style"] | |
}, | |
"Digital Painting": { | |
"description": "Digital art that imitates traditional painting techniques with modern tools", | |
"icon": "🖱️", | |
"examples": "Contemporary digital artists, concept art, digital illustrations", | |
"technical_terms": ["digital painting", "brushwork", "textured", "painterly digital"] | |
}, | |
"Cel Shaded": { | |
"description": "Comic or animation style with flat colors and defined outlines", | |
"icon": "🎭", | |
"examples": "Western animation, comic books, video game cel-shading", | |
"technical_terms": ["cel shading", "toon shading", "flat color", "outlined"] | |
}, | |
"Cinematic": { | |
"description": "Movie-like composition with dramatic lighting and film color grading", | |
"icon": "🎞️", | |
"examples": "Film stills, cinematography, movie posters", | |
"technical_terms": ["cinematic", "film grain", "color grading", "composition"] | |
}, | |
"Renaissance": { | |
"description": "Classical style with realistic proportions, perspective and religious themes", | |
"icon": "🏛️", | |
"examples": "Leonardo da Vinci, Michelangelo, Raphael", | |
"technical_terms": ["renaissance", "classical", "sfumato", "perspective"] | |
}, | |
"Folk Art": { | |
"description": "Traditional decorative style with cultural motifs and simplified forms", | |
"icon": "🧶", | |
"examples": "Various cultural folk art traditions, primitive art", | |
"technical_terms": ["folk art", "naive art", "traditional", "decorative"] | |
}, | |
"Pointillism": { | |
"description": "Style using small distinct dots of color to form images", | |
"icon": "👆", | |
"examples": "Georges Seurat, Paul Signac", | |
"technical_terms": ["pointillism", "stippling", "dot technique", "optical mixing"] | |
}, | |
"Bauhaus": { | |
"description": "Modernist style with geometric shapes, primary colors and functional design", | |
"icon": "🔷", | |
"examples": "Wassily Kandinsky's Bauhaus works, Josef Albers, László Moholy-Nagy", | |
"technical_terms": ["bauhaus", "modernist", "functional", "geometric"] | |
} | |
} | |
# Moods with icons and descriptions | |
MOODS = { | |
"Happy": { | |
"description": "Bright, cheerful atmosphere with warm colors", | |
"icon": "😊", | |
"color_palette": "Warm and vibrant colors: yellows, bright oranges, light blues", | |
"lighting": "Bright, open lighting with minimal shadows" | |
}, | |
"Sad": { | |
"description": "Melancholic atmosphere with muted colors", | |
"icon": "😢", | |
"color_palette": "Muted blues, grays, desaturated colors, cool tones", | |
"lighting": "Soft, subdued lighting with long shadows" | |
}, | |
"Mysterious": { | |
"description": "Enigmatic atmosphere with shadows and hidden elements", | |
"icon": "🔮", | |
"color_palette": "Deep purples, dark blues, hints of teal, selective lighting", | |
"lighting": "Dramatic, directional lighting with strong shadows" | |
}, | |
"Peaceful": { | |
"description": "Serene, calm atmosphere with gentle lighting", | |
"icon": "🕊️", | |
"color_palette": "Soft blues, gentle greens, pastel colors, balanced light", | |
"lighting": "Soft, diffused lighting with minimal contrast" | |
}, | |
"Tense": { | |
"description": "Suspenseful atmosphere with dramatic lighting", | |
"icon": "😰", | |
"color_palette": "High contrast, dark shadows, selective reds, strong highlights", | |
"lighting": "Harsh, directional lighting with strong shadows" | |
}, | |
"Whimsical": { | |
"description": "Playful, imaginative atmosphere with fanciful elements", | |
"icon": "🦄", | |
"color_palette": "Pastels, candy colors, unexpected color combinations", | |
"lighting": "Magical, glowing lighting with soft sparkles" | |
}, | |
"Dark": { | |
"description": "Gloomy atmosphere with deep shadows and low lighting", | |
"icon": "🌑", | |
"color_palette": "Dark blues, blacks, deep greens, minimal highlights", | |
"lighting": "Low-key lighting with strong shadows and minimal highlights" | |
}, | |
"Energetic": { | |
"description": "Dynamic, vibrant atmosphere with strong colors and movement", | |
"icon": "⚡", | |
"color_palette": "Saturated primary colors, bold contrasts, vibrant hues", | |
"lighting": "Bold, dynamic lighting with sharp contrasts" | |
}, | |
"Romantic": { | |
"description": "Soft, dreamy atmosphere with warm, gentle lighting", | |
"icon": "❤️", | |
"color_palette": "Soft pinks, gentle reds, golden highlights, warm tones", | |
"lighting": "Warm, soft lighting with a gentle glow" | |
}, | |
"Epic": { | |
"description": "Grand, impressive atmosphere with dramatic scale and lighting", | |
"icon": "🏔️", | |
"color_palette": "Bold colors, dramatic contrast, atmospheric lighting, expansive scale", | |
"lighting": "Dramatic, cinematic lighting with god rays and atmosphere" | |
}, | |
"Ethereal": { | |
"description": "Delicate, otherworldly atmosphere with a sense of transcendence", | |
"icon": "✨", | |
"color_palette": "Soft whites, pale blues, gentle pastels, luminous tones", | |
"lighting": "Glowing, diffused lighting with light particles" | |
}, | |
"Nostalgic": { | |
"description": "Warm, reminiscent atmosphere evoking memories and sentiment", | |
"icon": "🕰️", | |
"color_palette": "Warm ambers, faded colors, slightly desaturated tones", | |
"lighting": "Golden hour lighting or vintage film effects" | |
}, | |
"Dramatic": { | |
"description": "Bold, emotionally charged atmosphere with strong contrasts", | |
"icon": "🎭", | |
"color_palette": "Strong contrasts, selective color highlights, bold tones", | |
"lighting": "Strong directional lighting with pronounced shadows" | |
}, | |
"Serene": { | |
"description": "Tranquil, meditative atmosphere with balanced elements", | |
"icon": "🧘", | |
"color_palette": "Balanced harmonious colors, natural tones, soft transitions", | |
"lighting": "Even, gentle lighting with subtle gradients" | |
}, | |
"Chaotic": { | |
"description": "Disorderly, energetic atmosphere with clashing elements", | |
"icon": "💥", | |
"color_palette": "Clashing colors, high contrast, unpredictable combinations", | |
"lighting": "Uneven, conflicting lighting from multiple sources" | |
}, | |
"Futuristic": { | |
"description": "Advanced technological atmosphere with sleek, modern elements", | |
"icon": "🚀", | |
"color_palette": "Blues, cyans, whites, metallics, with accent neons", | |
"lighting": "High-tech lighting, glows, volumetric beams" | |
}, | |
"Organic": { | |
"description": "Natural, living atmosphere with biological elements and growth", | |
"icon": "🌱", | |
"color_palette": "Natural greens, browns, living colors, earthy tones", | |
"lighting": "Soft, natural lighting with dappled shadows and highlights" | |
}, | |
"Magical": { | |
"description": "Enchanted atmosphere with mystical elements and wonder", | |
"icon": "✨", | |
"color_palette": "Glowing purples, blues, with golden accents and sparkles", | |
"lighting": "Magical glow, light particles, soft radiance from magical elements" | |
}, | |
"Cozy": { | |
"description": "Warm, comfortable atmosphere that evokes feelings of safety and comfort", | |
"icon": "🏡", | |
"color_palette": "Warm oranges, browns, reds, soft yellows, comforting tones", | |
"lighting": "Warm, soft lighting like firelight or sunset glow" | |
}, | |
"Elegant": { | |
"description": "Refined, sophisticated atmosphere with tasteful elements", | |
"icon": "👑", | |
"color_palette": "Subdued luxurious colors, golds, deep blues, soft whites", | |
"lighting": "Carefully balanced, flattering lighting with subtle highlights" | |
} | |
} | |
# New aspect ratios with descriptions and use cases | |
ASPECT_RATIOS = { | |
"1:1 (Square)": { | |
"dimensions": (512, 512), | |
"description": "Perfect square format", | |
"use_cases": "Social media posts, profile pictures, album covers", | |
"icon": "⬛" | |
}, | |
"4:3 (Classic)": { | |
"dimensions": (576, 432), | |
"description": "Traditional TV and monitor ratio", | |
"use_cases": "Desktop wallpapers, presentations, classic photo prints", | |
"icon": "📺" | |
}, | |
"3:2 (Photo)": { | |
"dimensions": (576, 384), | |
"description": "Traditional photography ratio", | |
"use_cases": "Professional photography, prints, postcards", | |
"icon": "📷" | |
}, | |
"16:9 (Widescreen)": { | |
"dimensions": (576, 324), | |
"description": "Modern video and display format", | |
"use_cases": "Widescreen wallpapers, video thumbnails, presentations", | |
"icon": "🖥️" | |
}, | |
"2:1 (Panoramic)": { | |
"dimensions": (640, 320), | |
"description": "Wide panoramic format", | |
"use_cases": "Landscape photography, banner images, wide displays", | |
"icon": "🌄" | |
}, | |
"9:16 (Portrait)": { | |
"dimensions": (384, 680), | |
"description": "Vertical mobile format", | |
"use_cases": "Mobile wallpapers, stories, vertical videos", | |
"icon": "📱" | |
}, | |
"3:4 (Portrait)": { | |
"dimensions": (384, 512), | |
"description": "Traditional portrait ratio", | |
"use_cases": "Portraits, book covers, magazine covers", | |
"icon": "📔" | |
}, | |
"2:3 (Portrait Photo)": { | |
"dimensions": (384, 576), | |
"description": "Portrait photography ratio", | |
"use_cases": "Portrait prints, phone wallpapers, book covers", | |
"icon": "📱" | |
}, | |
"21:9 (Ultrawide)": { | |
"dimensions": (640, 272), | |
"description": "Ultra-widescreen cinematic format", | |
"use_cases": "Cinematic scenes, movie stills, ultrawide displays", | |
"icon": "🎬" | |
} | |
} | |
# Color palettes with descriptions and sample colors | |
COLOR_PALETTES = { | |
"Vibrant": { | |
"description": "Bold, energetic colors with high saturation", | |
"colors": ["#FF4500", "#FFD700", "#00FF7F", "#1E90FF", "#FF1493"], | |
"icon": "🌈", | |
"mood_association": ["Happy", "Energetic", "Whimsical"] | |
}, | |
"Pastel": { | |
"description": "Soft, light colors with low saturation", | |
"colors": ["#FFB6C1", "#E6E6FA", "#B0E0E6", "#FFDAB9", "#98FB98"], | |
"icon": "🍭", | |
"mood_association": ["Peaceful", "Whimsical", "Romantic"] | |
}, | |
"Monochrome": { | |
"description": "Various shades and tones of a single color", | |
"colors": ["#000000", "#333333", "#666666", "#999999", "#CCCCCC"], | |
"icon": "⚫", | |
"mood_association": ["Elegant", "Mysterious", "Dramatic"] | |
}, | |
"Earthy": { | |
"description": "Natural tones inspired by nature and earth", | |
"colors": ["#8B4513", "#A0522D", "#556B2F", "#BDB76B", "#F5DEB3"], | |
"icon": "🍂", | |
"mood_association": ["Peaceful", "Organic", "Serene"] | |
}, | |
"Neon": { | |
"description": "Bright, glowing colors with high intensity", | |
"colors": ["#FF00FF", "#00FFFF", "#FF9900", "#39FF14", "#FE01B1"], | |
"icon": "💫", | |
"mood_association": ["Energetic", "Futuristic", "Chaotic"] | |
}, | |
"Vintage": { | |
"description": "Faded, nostalgic colors with a retro feel", | |
"colors": ["#D3A588", "#C3B091", "#8F9E8B", "#535657", "#A87C6D"], | |
"icon": "🕰️", | |
"mood_association": ["Nostalgic", "Romantic", "Cozy"] | |
}, | |
"Cool": { | |
"description": "Blues, greens, and purples with a calm feel", | |
"colors": ["#4682B4", "#5F9EA0", "#6495ED", "#483D8B", "#008080"], | |
"icon": "❄️", | |
"mood_association": ["Peaceful", "Mysterious", "Serene"] | |
}, | |
"Warm": { | |
"description": "Reds, oranges, and yellows with a warm feel", | |
"colors": ["#CD5C5C", "#F08080", "#FA8072", "#FFA07A", "#FFDAB9"], | |
"icon": "🔥", | |
"mood_association": ["Happy", "Energetic", "Romantic"] | |
}, | |
"Cyberpunk": { | |
"description": "Neon colors against dark backgrounds with high contrast", | |
"colors": ["#FF00FF", "#00FFFF", "#101010", "#FE9A00", "#121634"], | |
"icon": "👾", | |
"mood_association": ["Futuristic", "Mysterious", "Dramatic"] | |
}, | |
"Minimal": { | |
"description": "Limited color palette with blacks, whites, and accents", | |
"colors": ["#FFFFFF", "#000000", "#DDDDDD", "#333333", "#FF3366"], | |
"icon": "🔲", | |
"mood_association": ["Elegant", "Dramatic", "Serene"] | |
} | |
} | |
# Special effects with descriptions and parameters | |
SPECIAL_EFFECTS = { | |
"None": { | |
"description": "No special effects applied", | |
"icon": "⭕", | |
"intensity_range": (0, 0), | |
"default_intensity": 0 | |
}, | |
"Glow": { | |
"description": "Adds a subtle luminous glow to bright areas", | |
"icon": "✨", | |
"intensity_range": (0.1, 1.0), | |
"default_intensity": 0.5 | |
}, | |
"Film Grain": { | |
"description": "Adds a textured grain similar to analog film", | |
"icon": "🎞️", | |
"intensity_range": (0.1, 1.0), | |
"default_intensity": 0.3 | |
}, | |
"Vignette": { | |
"description": "Darkens the edges of the image for focus", | |
"icon": "⚫", | |
"intensity_range": (0.1, 0.8), | |
"default_intensity": 0.4 | |
}, | |
"Chromatic Aberration": { | |
"description": "Creates subtle color fringing on edges", | |
"icon": "🌈", | |
"intensity_range": (0.1, 0.5), | |
"default_intensity": 0.2 | |
}, | |
"Retro Wave": { | |
"description": "80s style grid and neon effects", | |
"icon": "📺", | |
"intensity_range": (0.2, 1.0), | |
"default_intensity": 0.7 | |
}, | |
"Dream Blur": { | |
"description": "Soft gaussian blur with glow for dreamy effect", | |
"icon": "💭", | |
"intensity_range": (0.1, 0.6), | |
"default_intensity": 0.3 | |
}, | |
"Duotone": { | |
"description": "Two-color overlay effect", | |
"icon": "🔄", | |
"intensity_range": (0.3, 1.0), | |
"default_intensity": 0.7 | |
}, | |
"Halftone": { | |
"description": "Comic-style dots pattern effect", | |
"icon": "👾", | |
"intensity_range": (0.2, 0.8), | |
"default_intensity": 0.5 | |
}, | |
"Noir": { | |
"description": "High contrast black and white with film noir look", | |
"icon": "🕵️", | |
"intensity_range": (0.4, 1.0), | |
"default_intensity": 0.7 | |
} | |
} | |
# Example prompts with rich metadata and additional parameters | |
EXAMPLE_PROMPTS = [ | |
{ | |
"text": "A serene lake at sunset with mountains in the background and a small wooden boat floating nearby", | |
"thumbnail_desc": "Peaceful lake scene at sunset", | |
"creation_type": "Realistic Photo", | |
"art_style": "Photorealistic", | |
"mood": "Peaceful", | |
"aspect_ratio": "16:9 (Widescreen)", | |
"color_palette": "Warm", | |
"special_effect": "None", | |
"tags": ["nature", "landscape", "water", "sunset"] | |
}, | |
{ | |
"text": "A futuristic cityscape with flying cars, neon lights, and tall skyscrapers under a night sky with two moons", | |
"thumbnail_desc": "Futuristic city with flying cars", | |
"creation_type": "Concept Art", | |
"art_style": "Cyberpunk", | |
"mood": "Mysterious", | |
"aspect_ratio": "16:9 (Widescreen)", | |
"color_palette": "Cyberpunk", | |
"special_effect": "Glow", | |
"tags": ["scifi", "future", "urban", "night"] | |
}, | |
{ | |
"text": "A close-up portrait of an elderly craftsman with weathered hands working on an intricate wooden carving in his workshop", | |
"thumbnail_desc": "Elderly craftsman working with wood", | |
"creation_type": "Oil Painting", | |
"art_style": "Hyperrealism", | |
"mood": "Peaceful", | |
"aspect_ratio": "3:4 (Portrait)", | |
"color_palette": "Earthy", | |
"special_effect": "Vignette", | |
"tags": ["portrait", "craftsmanship", "elderly", "detail"] | |
}, | |
{ | |
"text": "A magical forest with glowing mushrooms, fairy lights, and a small cottage with smoke coming from the chimney", | |
"thumbnail_desc": "Magical forest with glowing elements", | |
"creation_type": "Fantasy Illustration", | |
"art_style": "Studio Ghibli", | |
"mood": "Whimsical", | |
"aspect_ratio": "3:2 (Photo)", | |
"color_palette": "Vibrant", | |
"special_effect": "Glow", | |
"tags": ["fantasy", "forest", "magic", "cottage"] | |
}, | |
{ | |
"text": "A cybernetic samurai with glowing blue circuits standing in a rainy Tokyo street at night", | |
"thumbnail_desc": "Cybernetic samurai in rainy Tokyo", | |
"creation_type": "Digital Art", | |
"art_style": "Cyberpunk", | |
"mood": "Dark", | |
"aspect_ratio": "2:3 (Portrait Photo)", | |
"color_palette": "Cyberpunk", | |
"special_effect": "Chromatic Aberration", | |
"tags": ["character", "cyberpunk", "samurai", "rain"] | |
}, | |
{ | |
"text": "A cute cat with dragon wings and tiny horns sleeping on a pile of gold coins", | |
"thumbnail_desc": "Cat with dragon features on gold", | |
"creation_type": "Fantasy Illustration", | |
"art_style": "Comic Book", | |
"mood": "Whimsical", | |
"aspect_ratio": "1:1 (Square)", | |
"color_palette": "Vibrant", | |
"special_effect": "None", | |
"tags": ["creature", "fantasy", "cute", "treasure"] | |
}, | |
{ | |
"text": "An ancient temple covered in vines and moss, partially sunken into a crystal-clear cenote in the jungle", | |
"thumbnail_desc": "Ancient temple in jungle cenote", | |
"creation_type": "Concept Art", | |
"art_style": "Photorealistic", | |
"mood": "Mysterious", | |
"aspect_ratio": "16:9 (Widescreen)", | |
"color_palette": "Earthy", | |
"special_effect": "Vignette", | |
"tags": ["architecture", "ruins", "jungle", "water"] | |
}, | |
{ | |
"text": "A cozy coffee shop interior with rain falling outside the windows, soft lighting, and a few people reading books", | |
"thumbnail_desc": "Cozy rainy day coffee shop", | |
"creation_type": "Digital Art", | |
"art_style": "Impressionist", | |
"mood": "Cozy", | |
"aspect_ratio": "3:2 (Photo)", | |
"color_palette": "Warm", | |
"special_effect": "Dream Blur", | |
"tags": ["interior", "rainy", "cozy", "urban"] | |
}, | |
{ | |
"text": "An astronaut standing on a distant planet with multiple ringed moons in the sky and alien vegetation around", | |
"thumbnail_desc": "Astronaut on alien planet", | |
"creation_type": "Cinematic Scene", | |
"art_style": "Photorealistic", | |
"mood": "Epic", | |
"aspect_ratio": "21:9 (Ultrawide)", | |
"color_palette": "Cool", | |
"special_effect": "Film Grain", | |
"tags": ["scifi", "space", "exploration", "alien"] | |
}, | |
{ | |
"text": "A stylish cyberpunk street fashion model with neon accessories and holographic clothing in a futuristic marketplace", | |
"thumbnail_desc": "Cyberpunk street fashion", | |
"creation_type": "Character Portrait", | |
"art_style": "Cyberpunk", | |
"mood": "Futuristic", | |
"aspect_ratio": "2:3 (Portrait Photo)", | |
"color_palette": "Neon", | |
"special_effect": "Chromatic Aberration", | |
"tags": ["fashion", "character", "cyberpunk", "future"] | |
}, | |
{ | |
"text": "An underwater city with bioluminescent buildings and mermaid-like inhabitants swimming between coral structures", | |
"thumbnail_desc": "Bioluminescent underwater city", | |
"creation_type": "Fantasy Illustration", | |
"art_style": "Digital Painting", | |
"mood": "Mysterious", | |
"aspect_ratio": "16:9 (Widescreen)", | |
"color_palette": "Cool", | |
"special_effect": "Glow", | |
"tags": ["underwater", "fantasy", "city", "merfolk"] | |
}, | |
{ | |
"text": "A tranquil Japanese zen garden with carefully raked sand, moss-covered stones, and maple trees in autumn colors", | |
"thumbnail_desc": "Japanese zen garden in autumn", | |
"creation_type": "Realistic Photo", | |
"art_style": "Photorealistic", | |
"mood": "Peaceful", | |
"aspect_ratio": "3:2 (Photo)", | |
"color_palette": "Earthy", | |
"special_effect": "None", | |
"tags": ["garden", "japanese", "zen", "autumn"] | |
}, | |
{ | |
"text": "A steampunk airship battle among the clouds with brass and copper vessels exchanging cannon fire", | |
"thumbnail_desc": "Steampunk airship battle", | |
"creation_type": "Concept Art", | |
"art_style": "Steampunk", | |
"mood": "Dramatic", | |
"aspect_ratio": "16:9 (Widescreen)", | |
"color_palette": "Vintage", | |
"special_effect": "Film Grain", | |
"tags": ["steampunk", "battle", "airship", "clouds"] | |
} | |
] | |
# =============== HELPER FUNCTIONS =============== | |
# Format dropdown choices with icons for better UI | |
def format_dropdown_choices(options_dict): | |
"""Format dropdown choices with icons for better UI""" | |
return [f"{options_dict[key].get('icon', '•')} {key}" for key in options_dict.keys()] | |
# Extract the key from a formatted choice with icon | |
def extract_key(formatted_choice): | |
"""Extract the key from a formatted choice with icon""" | |
if not formatted_choice: | |
return None | |
# Skip the icon and space at the beginning | |
parts = formatted_choice.split(' ', 1) | |
if len(parts) > 1: | |
return parts[1] | |
return formatted_choice | |
# Function to load example prompt | |
def load_example(example_index): | |
"""Load a pre-defined example prompt""" | |
if example_index < 0 or example_index >= len(EXAMPLE_PROMPTS): | |
return "", "", "", "", "", "", "" | |
example = EXAMPLE_PROMPTS[example_index] | |
creation = f"{CREATION_TYPES[example['creation_type']]['icon']} {example['creation_type']}" | |
art = f"{ART_STYLES[example['art_style']]['icon']} {example['art_style']}" | |
mood = f"{MOODS[example['mood']]['icon']} {example['mood']}" | |
aspect = f"{ASPECT_RATIOS[example['aspect_ratio']]['icon']} {example['aspect_ratio']}" | |
color_palette = f"{COLOR_PALETTES[example['color_palette']]['icon']} {example['color_palette']}" | |
special_effect = f"{SPECIAL_EFFECTS[example['special_effect']]['icon']} {example['special_effect']}" | |
return example["text"], creation, art, mood, aspect, color_palette, special_effect | |
# Get model key from formatted display name | |
def get_model_key_from_display_name(formatted_name): | |
"""Get the model ID from its formatted display name""" | |
if not formatted_name: | |
return list(IMAGE_MODELS.keys())[0] # Default to first model | |
# Extract display name without the icon | |
display_name = extract_key(formatted_name) | |
# Find the corresponding key by display name | |
for key, info in IMAGE_MODELS.items(): | |
if info['display_name'] == display_name: | |
return key | |
# Default to first model if not found | |
return list(IMAGE_MODELS.keys())[0] | |
# Helper function to update character count with color coding | |
def update_char_count(text): | |
"""Update character count with color coding based on length""" | |
count = len(text) if text else 0 | |
if count == 0: | |
color_class = "" | |
advice = "Start typing to create your prompt" | |
elif count < 20: | |
color_class = "warning" | |
advice = "Your prompt is too short. Add more details for better results" | |
elif count > 300: | |
color_class = "info" | |
advice = "Great level of detail! You can still add more if needed" | |
elif count > 500: | |
color_class = "warning" | |
advice = "Your prompt is getting very long. Consider focusing on key details" | |
elif count > 700: | |
color_class = "error" | |
advice = "Very long prompts may be truncated. Try to be more concise" | |
else: | |
color_class = "success" | |
advice = "Good prompt length with useful details" | |
return f"""<div class='character-counter {color_class}'> | |
<span class='count'>{count} characters</span> | |
<span class='advice'>{advice}</span> | |
</div>""" | |
# Helper function to update creation type info | |
def update_creation_info(choice): | |
"""Update creation type info display based on selection""" | |
key = extract_key(choice) | |
if not key or key not in CREATION_TYPES: | |
return "" | |
info = CREATION_TYPES[key] | |
examples = ", ".join([f"<span class='example-tag'>{ex}</span>" for ex in info.get('examples', [])]) | |
recommended_models = "" | |
if 'recommended_models' in info: | |
model_list = [f"<span class='recommended-model'>{model}</span>" for model in info['recommended_models']] | |
recommended_models = f""" | |
<div class="recommended-models"> | |
<p>✓ Recommended models: {", ".join(model_list)}</p> | |
</div> | |
""" | |
return f"""<div class="info-card"> | |
<h3>{info['icon']} {key}</h3> | |
<p>{info['description']}</p> | |
<p class="prompt-hint">💡 {info['prompt_hint']}</p> | |
<div class="examples-section"> | |
<p>Examples: {examples}</p> | |
</div> | |
{recommended_models} | |
</div>""" | |
# Helper function to update art style info | |
def update_art_style_info(choice): | |
"""Update art style info display based on selection""" | |
key = extract_key(choice) | |
if not key or key not in ART_STYLES: | |
return "" | |
info = ART_STYLES[key] | |
technical_terms = "" | |
if 'technical_terms' in info: | |
terms = ", ".join([f"<span class='technical-term'>{term}</span>" for term in info['technical_terms']]) | |
technical_terms = f""" | |
<div class="technical-terms"> | |
<p>Keywords: {terms}</p> | |
</div> | |
""" | |
return f"""<div class="info-card"> | |
<h3>{info['icon']} {key}</h3> | |
<p>{info['description']}</p> | |
<p class="examples-text">Inspired by: {info['examples']}</p> | |
{technical_terms} | |
</div>""" | |
# Helper function to update model info | |
def update_model_info(formatted_choice): | |
"""Update model info display based on selection""" | |
if not formatted_choice: | |
return "" | |
# Extract display name without the icon | |
display_name = extract_key(formatted_choice) | |
# Find the corresponding key and info | |
for key, info in IMAGE_MODELS.items(): | |
if info['display_name'] == display_name: | |
# Create speed badge | |
speed_badge = "" | |
if info.get('speed') == 'very fast': | |
speed_badge = '<span class="badge badge-success">Ultra Fast</span>' | |
elif info.get('speed') == 'fast': | |
speed_badge = '<span class="badge badge-success">Fast</span>' | |
elif info.get('speed') == 'medium': | |
speed_badge = '<span class="badge badge-warning">Medium</span>' | |
elif info.get('speed') == 'slow': | |
speed_badge = '<span class="badge badge-error">Slower</span>' | |
# Create quality badge | |
quality_badge = "" | |
if info.get('quality') == 'premium': | |
quality_badge = '<span class="badge badge-success">Premium Quality</span>' | |
elif info.get('quality') == 'excellent': | |
quality_badge = '<span class="badge badge-success">Excellent Quality</span>' | |
elif info.get('quality') == 'very good': | |
quality_badge = '<span class="badge badge-success">Very Good Quality</span>' | |
elif info.get('quality') == 'good': | |
quality_badge = '<span class="badge badge-info">Good Quality</span>' | |
elif info.get('quality') == 'specialized': | |
quality_badge = '<span class="badge badge-info">Specialized</span>' | |
elif info.get('quality') == 'artistic': | |
quality_badge = '<span class="badge badge-info">Artistic</span>' | |
# Create recommended for badges | |
recommended_badges = "" | |
if 'recommended_for' in info: | |
badges = " ".join([f'<span class="badge badge-primary">{item}</span>' for item in info['recommended_for'][:3]]) | |
recommended_badges = f"""<div class="recommended-for"> | |
<p>Ideal for: {badges}</p> | |
</div>""" | |
# Create strengths and weaknesses section | |
strengths_weaknesses = "" | |
if 'strengths' in info and 'weaknesses' in info: | |
strengths_weaknesses = f"""<div class="strengths-weaknesses"> | |
<p><span class="highlight positive">✓ Strengths:</span> {info['strengths']}</p> | |
<p><span class="highlight negative">✗ Limitations:</span> {info['weaknesses']}</p> | |
</div>""" | |
return f"""<div class="model-info"> | |
<h3>{info['icon']} {info['display_name']} {speed_badge} {quality_badge}</h3> | |
<p>{info['description']}</p> | |
{strengths_weaknesses} | |
{recommended_badges} | |
<div class="model-id">{key}</div> | |
</div>""" | |
return "" | |
# Helper function to update mood info | |
def update_mood_info(choice): | |
"""Update mood info display based on selection""" | |
key = extract_key(choice) | |
if not key or key not in MOODS: | |
return "" | |
info = MOODS[key] | |
return f"""<div class="info-card mood-card"> | |
<h3>{info['icon']} {key}</h3> | |
<p>{info['description']}</p> | |
<p class="color-info">🎨 {info['color_palette']}</p> | |
<p class="lighting-info">💡 {info['lighting']}</p> | |
</div>""" | |
# Helper function to update status message with styling | |
def update_status(message, is_error=False, is_warning=False, is_info=False): | |
"""Update status message with appropriate styling""" | |
if is_error: | |
status_class = "status-error" | |
icon = "❌" | |
elif is_warning: | |
status_class = "status-warning" | |
icon = "⚠️" | |
elif is_info: | |
status_class = "status-info" | |
icon = "ℹ️" | |
else: | |
status_class = "status-success" | |
icon = "✅" | |
return f"""<div class="status-message {status_class}"> | |
<span class="icon">{icon}</span> | |
<span>{message}</span> | |
</div>""" | |
# Helper function to format parameters display as pills | |
def format_parameters(creation_type_val, art_style_val, mood_val, aspect_ratio_val, color_palette_val, special_effect_val, model_name): | |
"""Format generation parameters as readable badges/pills""" | |
creation_key = extract_key(creation_type_val) | |
art_key = extract_key(art_style_val) | |
mood_key = extract_key(mood_val) | |
aspect_key = extract_key(aspect_ratio_val) | |
palette_key = extract_key(color_palette_val) | |
effect_key = extract_key(special_effect_val) | |
# Get model info | |
model_display_name = "Unknown Model" | |
model_id = "" | |
model_icon = "🤖" | |
for key, info in IMAGE_MODELS.items(): | |
if info['display_name'] == extract_key(model_name): | |
model_display_name = info['display_name'] | |
model_id = key | |
model_icon = info['icon'] | |
break | |
html = """<div class="parameters-container"> | |
<div class="parameters-title">Generated with these parameters:</div> | |
<div class="parameters-grid">""" | |
# Add creation type pill | |
if creation_key and creation_key in CREATION_TYPES: | |
html += f"""<div class="parameter-pill"> | |
<span class="icon">{CREATION_TYPES[creation_key]['icon']}</span> | |
<span class="label">{creation_key}</span> | |
</div>""" | |
# Add art style pill | |
if art_key and art_key in ART_STYLES: | |
html += f"""<div class="parameter-pill"> | |
<span class="icon">{ART_STYLES[art_key]['icon']}</span> | |
<span class="label">{art_key}</span> | |
</div>""" | |
# Add mood pill | |
if mood_key and mood_key in MOODS: | |
html += f"""<div class="parameter-pill"> | |
<span class="icon">{MOODS[mood_key]['icon']}</span> | |
<span class="label">{mood_key}</span> | |
</div>""" | |
# Add aspect ratio pill | |
if aspect_key and aspect_key in ASPECT_RATIOS: | |
html += f"""<div class="parameter-pill"> | |
<span class="icon">{ASPECT_RATIOS[aspect_key]['icon']}</span> | |
<span class="label">{aspect_key}</span> | |
</div>""" | |
# Add color palette pill | |
if palette_key and palette_key in COLOR_PALETTES: | |
html += f"""<div class="parameter-pill"> | |
<span class="icon">{COLOR_PALETTES[palette_key]['icon']}</span> | |
<span class="label">{palette_key}</span> | |
</div>""" | |
# Add special effect pill | |
if effect_key and effect_key in SPECIAL_EFFECTS and effect_key != "None": | |
html += f"""<div class="parameter-pill"> | |
<span class="icon">{SPECIAL_EFFECTS[effect_key]['icon']}</span> | |
<span class="label">{effect_key}</span> | |
</div>""" | |
# Add model pill | |
html += f"""<div class="parameter-pill model-pill"> | |
<span class="icon">{model_icon}</span> | |
<span class="label">{model_display_name}</span> | |
</div>""" | |
# Close container | |
html += """</div> | |
<div class="generation-time"> | |
<span class="timestamp">Generated on {timestamp}</span> | |
</div> | |
</div>""".replace("{timestamp}", datetime.now().strftime("%Y-%m-%d at %H:%M:%S")) | |
return html | |
# Helper function to cache generation results | |
# Helper function to cache generation results | |
def get_cache_key(description, model_key, creation_type, art_style, mood, aspect_ratio, color_palette, special_effect): | |
"""Generate a unique cache key for generation parameters""" | |
# Create a string representation of all parameters | |
param_string = f"{description}|{model_key}|{creation_type}|{art_style}|{mood}|{aspect_ratio}|{color_palette}|{special_effect}" | |
# Create a hash of the parameters for a shorter key | |
return hashlib.md5(param_string.encode('utf-8')).hexdigest() | |
# Create a simple cache for generated images | |
IMAGE_CACHE = {} | |
def cache_image(key, image, enhanced_prompt, parameters_html, seed=None): | |
"""Cache an image with its metadata""" | |
if not APP_CONFIG["cache_enabled"]: | |
return | |
# Limit cache size | |
if len(IMAGE_CACHE) > 50: # Limit to last 50 images | |
# Remove oldest entry | |
oldest_key = next(iter(IMAGE_CACHE)) | |
IMAGE_CACHE.pop(oldest_key) | |
# Save to cache | |
IMAGE_CACHE[key] = { | |
"image": image, | |
"enhanced_prompt": enhanced_prompt, | |
"parameters_html": parameters_html, | |
"timestamp": datetime.now().isoformat(), | |
"seed": seed | |
} | |
def get_cached_image(key): | |
"""Get an image from cache if it exists""" | |
if not APP_CONFIG["cache_enabled"] or key not in IMAGE_CACHE: | |
return None | |
return IMAGE_CACHE[key] | |
# =============== PROMPT ENHANCEMENT LOGIC =============== | |
# Function to enhance prompt with AI model | |
def enhance_prompt_with_ai(user_input, creation_type, art_style, mood, color_palette=None, special_effect=None): | |
""" | |
Enhance user input with AI model to create detailed image generation prompts | |
Args: | |
user_input (str): User's original description | |
creation_type (str): Selected creation type (e.g., "Digital Art") | |
art_style (str): Selected art style (e.g., "Photorealistic") | |
mood (str): Selected mood (e.g., "Peaceful") | |
color_palette (str): Selected color palette (e.g., "Vibrant") | |
special_effect (str): Selected special effect (e.g., "Glow") | |
Returns: | |
str: Enhanced prompt optimized for image generation | |
""" | |
try: | |
if not use_ai_enhancer or enhancer_client is None: | |
logger.warning("AI enhancer not available, using fallback") | |
return enhance_prompt_fallback(user_input, creation_type, art_style, mood, color_palette, special_effect) | |
logger.info(f"Enhancing prompt with AI for: {creation_type}, {art_style}, {mood}") | |
# Enhanced system prompt with detailed instructions | |
system_prompt = """You are a world-class prompt engineer who specializes in creating detailed, effective prompts for text-to-image AI models. | |
Your task is to transform a user's description into a comprehensive, detailed image generation prompt that will create stunning visuals. Consider all the provided elements and combine them into a cohesive, detailed prompt. | |
MOST IMPORTANTLY - ADD LOGICAL DETAILS: | |
- Analyze what the user wants and add logical details that would make the scene realistic or coherent | |
- Think about environment, lighting, perspective, time of day, weather, and other contextual elements | |
- Create a vivid, imaginable scene with spatial relationships clearly defined | |
PROMPT STRUCTURE GUIDELINES: | |
1. Start with the core subject and its primary characteristics | |
2. Add environment and setting details | |
3. Describe lighting, atmosphere, and mood | |
4. Include specific visual style and artistic technique references | |
5. Add technical quality terms (8K, detailed, masterful, etc.) | |
FORMAT YOUR RESPONSE AS A SINGLE PARAGRAPH with no additional comments, explanations, or bullet points. Use natural language without awkward comma separations. Aim for 75-175 words. | |
AVOID: | |
- Do not include quotation marks in your response | |
- Do not preface with "here's a prompt" or similar text | |
- Do not use placeholders | |
- Do not add negative prompts | |
- Do not write in list format or use bullet points | |
Respond only with the enhanced prompt and nothing else.""" | |
# Get creation type description | |
creation_info = CREATION_TYPES.get(creation_type, {"description": "Create a detailed image", "icon": "🎨"}) | |
creation_description = creation_info["description"] | |
# Get art style description | |
style_info = ART_STYLES.get(art_style, {"description": "with detailed and professional quality", "icon": "🖌️"}) | |
style_description = style_info["description"] | |
style_technical_terms = ", ".join(style_info.get("technical_terms", [])) | |
# Get mood description | |
mood_info = MOODS.get(mood, {"description": "atmospheric", "icon": "✨"}) | |
mood_description = mood_info["description"] | |
mood_lighting = mood_info.get("lighting", "") | |
# Get color palette description if provided | |
color_palette_description = "" | |
if color_palette and color_palette in COLOR_PALETTES: | |
palette_info = COLOR_PALETTES[color_palette] | |
color_palette_description = f"Color Palette: {color_palette} - {palette_info['description']}" | |
# Get special effect description if provided | |
special_effect_description = "" | |
if special_effect and special_effect in SPECIAL_EFFECTS and special_effect != "None": | |
effect_info = SPECIAL_EFFECTS[special_effect] | |
special_effect_description = f"Special Effect: {special_effect} - {effect_info['description']}" | |
# Prepare the user prompt for AI enhancer | |
user_prompt = f"""Description: {user_input} | |
Creation Type: {creation_type} - {creation_description} | |
Art Style: {art_style} - {style_description} | |
Technical Style Terms: {style_technical_terms} | |
Mood: {mood} - {mood_description} | |
Lighting: {mood_lighting} | |
{color_palette_description} | |
{special_effect_description} | |
Please create a comprehensive, detailed image generation prompt that combines all these elements.""" | |
try: | |
# Request enhancement from AI model | |
response = enhancer_client.text_generation( | |
prompt=f"<system>\n{system_prompt}\n</system>\n\n<user>\n{user_prompt}\n</user>\n\n<assistant>", | |
max_new_tokens=500, | |
temperature=0.7, # Slight creativity while maintaining coherence | |
top_p=0.95, | |
repetition_penalty=1.1 | |
) | |
# Extract enhanced prompt | |
enhanced = response.strip() | |
if enhanced.startswith("<assistant>"): | |
enhanced = enhanced[len("<assistant>"):].strip() | |
if enhanced.endswith("</assistant>"): | |
enhanced = enhanced[:-len("</assistant>")].strip() | |
logger.info(f"AI enhanced prompt: {enhanced[:100]}...") | |
return enhanced if enhanced else user_input | |
except Exception as e: | |
logger.error(f"Error during AI enhancement: {str(e)}") | |
return enhance_prompt_fallback(user_input, creation_type, art_style, mood, color_palette, special_effect) | |
except Exception as e: | |
logger.error(f"Error in AI enhancement: {str(e)}") | |
return enhance_prompt_fallback(user_input, creation_type, art_style, mood, color_palette, special_effect) | |
# Fallback prompt enhancement without AI | |
def enhance_prompt_fallback(user_input, creation_type, art_style, mood, color_palette=None, special_effect=None): | |
""" | |
Enhance user input without requiring AI using rule-based enhancement | |
Args: | |
user_input (str): User's original description | |
creation_type (str): Selected creation type (e.g., "Digital Art") | |
art_style (str): Selected art style (e.g., "Photorealistic") | |
mood (str): Selected mood (e.g., "Peaceful") | |
color_palette (str): Selected color palette (e.g., "Vibrant") | |
special_effect (str): Selected special effect (e.g., "Glow") | |
Returns: | |
str: Enhanced prompt using predefined rules and templates | |
""" | |
logger.info(f"Using fallback enhancement for: {user_input[:50]}...") | |
# Quality terms by creation type | |
quality_terms = { | |
"Realistic Photo": [ | |
"photorealistic", "high resolution", "detailed", | |
"natural lighting", "sharp focus", "professional photography", | |
"crisp details", "realistic textures", "DSLR photo", "high-definition" | |
], | |
"Digital Art": [ | |
"vibrant colors", "clean lines", "digital illustration", | |
"polished", "professional digital art", "detailed rendering", | |
"digital painting", "colorful", "vector-like precision", "crisp" | |
], | |
"Fantasy Illustration": [ | |
"magical atmosphere", "fantasy art", "detailed illustration", | |
"epic", "otherworldly", "imaginative scene", | |
"fantasy environment", "magical lighting", "mythical qualities" | |
], | |
"Concept Art": [ | |
"professional concept art", "detailed design", "conceptual illustration", | |
"industry standard", "visual development", "production artwork", | |
"concept design", "detailed environment", "character design" | |
], | |
"Anime/Manga": [ | |
"anime style", "manga illustration", "cel shaded", | |
"Japanese animation", "2D character art", "anime aesthetic", | |
"clean linework", "anime proportions", "stylized features" | |
], | |
"Oil Painting": [ | |
"oil on canvas", "textured brushwork", "rich colors", | |
"traditional painting", "artistic brushstrokes", "gallery quality", | |
"glazed layers", "impasto technique", "classical painting style" | |
], | |
"Watercolor": [ | |
"watercolor painting", "soft color bleeding", "delicate washes", | |
"transparent layers", "loose brushwork", "gentle transitions", | |
"watercolor paper texture", "wet-on-wet technique", "fluid color blending" | |
], | |
"Sketch": [ | |
"detailed sketch", "pencil drawing", "line art", | |
"hand-drawn", "fine details", "shading techniques", | |
"graphite", "charcoal texture", "gestural lines" | |
], | |
"3D Rendering": [ | |
"3D render", "volumetric lighting", "ray tracing", | |
"3D modeling", "realistic textures", "computer graphics", | |
"physically based rendering", "global illumination", "ambient occlusion" | |
], | |
"Pixel Art": [ | |
"pixel art", "8-bit style", "retro game aesthetic", | |
"limited color palette", "pixelated", "nostalgic game art", | |
"16-bit look", "pixel perfect", "dithering effects" | |
], | |
"Character Portrait": [ | |
"character portrait", "detailed features", "expressive pose", | |
"personality captured", "dynamic lighting", "professional portrait", | |
"character design", "emotive", "high-quality portrait" | |
], | |
"Landscape": [ | |
"scenic vista", "atmospheric lighting", "panoramic view", | |
"environmental details", "natural beauty", "grand landscape", | |
"scenic composition", "depth of field", "natural environment" | |
], | |
"Abstract Composition": [ | |
"abstract composition", "non-representational", "artistic expression", | |
"color theory", "visual rhythm", "compositional balance", | |
"abstract forms", "artistic creativity", "non-figurative" | |
], | |
"Product Visualization": [ | |
"product showcase", "studio lighting", "professional product shot", | |
"commercial quality", "advertising aesthetic", "product details", | |
"pristine rendering", "marketing visualization", "product photography" | |
] | |
} | |
# Get art style technical terms | |
style_terms = [] | |
if art_style in ART_STYLES and "technical_terms" in ART_STYLES[art_style]: | |
style_terms = ART_STYLES[art_style]["technical_terms"] | |
# Get mood lighting and color palette | |
mood_lighting = "" | |
if mood in MOODS: | |
mood_lighting = MOODS[mood].get("lighting", "") | |
# Get color palette terms | |
palette_terms = [] | |
if color_palette and color_palette in COLOR_PALETTES: | |
palette_info = COLOR_PALETTES[color_palette] | |
palette_terms = [color_palette.lower(), palette_info["description"].lower()] | |
# Special effect terms | |
effect_terms = [] | |
if special_effect and special_effect in SPECIAL_EFFECTS and special_effect != "None": | |
effect_info = SPECIAL_EFFECTS[special_effect] | |
effect_terms = [special_effect.lower(), effect_info["description"].lower()] | |
# Get terms for the specific creation type, or use generic terms | |
type_terms = quality_terms.get(creation_type, [ | |
"high quality", "detailed", "professional", "masterful", "high resolution", "sharp details" | |
]) | |
# Common quality terms enhanced with trending and technical terms | |
common_terms = [ | |
"8K resolution", "highly detailed", "professional", "masterpiece", | |
"trending on artstation", "award winning", "stunning", "intricate details", | |
"perfect composition", "cinematic lighting", "ultra realistic" | |
] | |
# Get style modifier | |
style_modifier = f"{art_style} style, {', '.join(style_terms[:3])}" if style_terms else f"{art_style} style" | |
# Get mood modifier | |
mood_modifier = f"{mood} mood, {mood_lighting}" if mood_lighting else f"{mood} mood" | |
# Color palette modifier | |
palette_modifier = f"{', '.join(palette_terms[:2])}" if palette_terms else "" | |
# Effect modifier | |
effect_modifier = f"{', '.join(effect_terms[:1])}" if effect_terms else "" | |
# Basic prompt structure - core subject and style elements | |
prompt_parts = [ | |
user_input, | |
style_modifier, | |
mood_modifier | |
] | |
if palette_modifier: | |
prompt_parts.append(palette_modifier) | |
if effect_modifier: | |
prompt_parts.append(effect_modifier) | |
# Add randomly selected quality terms for variety | |
selected_type_terms = random.sample(type_terms, min(3, len(type_terms))) | |
selected_common_terms = random.sample(common_terms, min(3, len(common_terms))) | |
# Combine terms | |
quality_description = ", ".join(selected_type_terms + selected_common_terms) | |
# Final enhanced prompt | |
enhanced_prompt = f"{', '.join(prompt_parts)}, {quality_description}" | |
logger.info(f"Fallback enhanced prompt: {enhanced_prompt[:100]}...") | |
return enhanced_prompt | |
# =============== IMAGE GENERATION FUNCTIONS =============== | |
# Apply special effects to an image | |
def apply_special_effects(image, special_effect, intensity=None): | |
"""Apply special visual effects to an image""" | |
if not special_effect or special_effect == "None" or image is None: | |
return image | |
if special_effect not in SPECIAL_EFFECTS: | |
return image | |
effect_info = SPECIAL_EFFECTS[special_effect] | |
# Use default intensity if not specified | |
if intensity is None: | |
intensity = effect_info["default_intensity"] | |
# Ensure intensity is within range | |
min_intensity, max_intensity = effect_info["intensity_range"] | |
intensity = max(min_intensity, min(max_intensity, intensity)) | |
try: | |
# Apply the selected effect | |
if special_effect == "Glow": | |
# Create a blurred version for the glow effect | |
glow_image = image.copy() | |
glow_image = glow_image.filter(ImageFilter.GaussianBlur(radius=10 * intensity)) | |
# Blend the original with the blurred version | |
return Image.blend(image, glow_image, intensity * 0.5) | |
elif special_effect == "Film Grain": | |
# Create a noise layer | |
width, height = image.size | |
noise = Image.new('L', (width, height)) | |
noise_data = np.random.randint(0, 255, (height, width), dtype=np.uint8) | |
# Adjust noise intensity | |
noise_data = np.clip(noise_data * intensity, 0, 255).astype(np.uint8) | |
noise.putdata([x for x in noise_data.flatten()]) | |
# Convert image to RGBA if it isn't already | |
if image.mode != 'RGBA': | |
image = image.convert('RGBA') | |
# Create an alpha mask for the noise | |
noise_mask = Image.new('L', (width, height), 255) | |
# Apply noise with screen blending mode | |
noise_overlay = Image.new('RGBA', (width, height)) | |
for x in range(width): | |
for y in range(height): | |
r, g, b, a = image.getpixel((x, y)) | |
noise_val = noise_data[y, x] | |
# Screen blend mode | |
nr = min(255, r + (noise_val * intensity * 0.1)) | |
ng = min(255, g + (noise_val * intensity * 0.1)) | |
nb = min(255, b + (noise_val * intensity * 0.1)) | |
noise_overlay.putpixel((x, y), (int(nr), int(ng), int(nb), a)) | |
# Blend the original with the noise overlay | |
result = Image.alpha_composite(image, noise_overlay) | |
return result.convert('RGB') | |
elif special_effect == "Vignette": | |
# Create a radial gradient for vignette | |
width, height = image.size | |
vignette = Image.new('L', (width, height), 255) | |
# Calculate the dimensions of the ellipse | |
ellipse_w = width * 1.5 | |
ellipse_h = height * 1.5 | |
ellipse_x = (width - ellipse_w) / 2 | |
ellipse_y = (height - ellipse_h) / 2 | |
# Create a draw object | |
from PIL import ImageDraw | |
draw = ImageDraw.Draw(vignette) | |
# Draw the gradient | |
for i in range(min(width, height) // 2): | |
# Scale the ellipse size | |
factor = 1 - (i / (min(width, height) / 2)) | |
e_w = ellipse_w * factor | |
e_h = ellipse_h * factor | |
e_x = (width - e_w) / 2 | |
e_y = (height - e_h) / 2 | |
# Calculate the brightness (darker toward edges) | |
brightness = int(255 * (1 - i / (min(width, height) / 2) * intensity)) | |
# Draw an ellipse with the current brightness | |
draw.ellipse((e_x, e_y, e_x + e_w, e_y + e_h), fill=brightness) | |
# Apply the vignette | |
result = image.copy() | |
result.putalpha(vignette) | |
# Convert back to RGB | |
result = result.convert('RGB') | |
return result | |
elif special_effect == "Chromatic Aberration": | |
# Split into RGB channels | |
r, g, b = image.split() | |
# Shift red channel to the right and blue channel to the left | |
shift_amount = int(5 * intensity) | |
r = ImageChops.offset(r, shift_amount, 0) | |
b = ImageChops.offset(b, -shift_amount, 0) | |
# Merge channels back | |
return Image.merge("RGB", (r, g, b)) | |
elif special_effect == "Dream Blur": | |
# Apply a soft Gaussian blur | |
blurred = image.filter(ImageFilter.GaussianBlur(radius=intensity * 3)) | |
# Increase brightness slightly | |
enhancer = ImageEnhance.Brightness(blurred) | |
brightened = enhancer.enhance(1 + intensity * 0.2) | |
# Increase contrast slightly | |
enhancer = ImageEnhance.Contrast(brightened) | |
contrasted = enhancer.enhance(1 + intensity * 0.1) | |
return contrasted | |
elif special_effect == "Duotone": | |
# Convert to grayscale | |
grayscale = image.convert('L') | |
# Define two colors for duotone effect | |
highlight_color = (52, 143, 235) # Blue | |
shadow_color = (145, 39, 143) # Purple | |
# Create duotone image | |
duotone = Image.new('RGB', image.size) | |
# For each pixel, blend between the shadow and highlight colors | |
# based on the grayscale value | |
for x in range(image.width): | |
for y in range(image.height): | |
gray_value = grayscale.getpixel((x, y)) / 255.0 | |
r = int(shadow_color[0] * (1 - gray_value) + highlight_color[0] * gray_value) | |
g = int(shadow_color[1] * (1 - gray_value) + highlight_color[1] * gray_value) | |
b = int(shadow_color[2] * (1 - gray_value) + highlight_color[2] * gray_value) | |
duotone.putpixel((x, y), (r, g, b)) | |
# Blend with original based on intensity | |
return Image.blend(image, duotone, intensity) | |
elif special_effect == "Halftone": | |
# This is a simplified halftone effect | |
# Convert to grayscale | |
grayscale = image.convert('L') | |
# Resize down and then back up to create the dot pattern | |
dot_size = max(1, int(10 * intensity)) | |
small = grayscale.resize((image.width // dot_size, image.height // dot_size), Image.NEAREST) | |
halftone = small.resize(image.size, Image.NEAREST) | |
# Convert back to RGB | |
halftone = halftone.convert('RGB') | |
# Blend with original based on intensity | |
return Image.blend(image, halftone, intensity * 0.7) | |
elif special_effect == "Noir": | |
# Convert to high contrast black and white | |
grayscale = image.convert('L') | |
# Increase contrast | |
enhancer = ImageEnhance.Contrast(grayscale) | |
noir = enhancer.enhance(1.5 + intensity) | |
# Convert back to RGB | |
noir = noir.convert('RGB') | |
return noir | |
elif special_effect == "Retro Wave": | |
# This is a simplified retro wave effect with purple/pink gradient | |
width, height = image.size | |
# Create base gradient | |
gradient = Image.new('RGB', (width, height)) | |
for y in range(height): | |
# Calculate color gradient from pink to purple to blue | |
r = int(255 - (y/height) * 150) | |
g = int(50 + (y/height) * 50) | |
b = int(150 + (y/height) * 105) | |
for x in range(width): | |
gradient.putpixel((x, y), (r, g, b)) | |
# Blend gradient with original image | |
result = Image.blend(image, gradient, intensity * 0.4) | |
# Add scanlines for retro effect | |
scanlines = Image.new('L', (width, height), 255) | |
draw = ImageDraw.Draw(scanlines) | |
# Draw horizontal lines | |
line_spacing = max(2, int(4 * (1/intensity))) | |
for y in range(0, height, line_spacing): | |
draw.line([(0, y), (width, y)], fill=150) | |
# Apply scanlines with opacity based on intensity | |
result.putalpha(scanlines) | |
result = result.convert('RGB') | |
return result | |
# Default case - return original image | |
return image | |
except Exception as e: | |
logger.error(f"Error applying special effect {special_effect}: {str(e)}") | |
return image | |
# Generate image function with loading state handling and retry mechanism | |
def generate_image(description, model_key, creation_type=None, art_style=None, mood=None, | |
aspect_ratio=None, color_palette=None, special_effect=None, seed=None, retries=1): | |
""" | |
Generate image based on user inputs by enhancing prompt and calling image model API | |
Args: | |
description (str): User's original description | |
model_key (str): Model identifier | |
creation_type (str, optional): Creation type | |
art_style (str, optional): Art style | |
mood (str, optional): Mood | |
aspect_ratio (str, optional): Aspect ratio | |
color_palette (str, optional): Color palette | |
special_effect (str, optional): Special effect | |
seed (int, optional): Random seed for reproducibility | |
retries (int): Number of retries if generation fails | |
Returns: | |
tuple: (image, status_message, enhanced_prompt, parameters_html, seed) | |
""" | |
try: | |
# Validate input | |
if not description or not description.strip(): | |
return None, "Please enter a description for your image", "", "", None | |
logger.info(f"Generating image with model: {model_key}") | |
# Check if we have a cached result | |
cache_key = get_cache_key(description, model_key, creation_type, art_style, mood, aspect_ratio, color_palette, special_effect) | |
cached = get_cached_image(cache_key) | |
if cached: | |
logger.info(f"Using cached image for: {description[:50]}...") | |
return cached["image"], "Image retrieved from cache", cached["enhanced_prompt"], cached["parameters_html"], cached["seed"] | |
# Extract aspect ratio dimensions | |
width, height = (512, 512) # Default | |
if aspect_ratio in ASPECT_RATIOS: | |
width, height = ASPECT_RATIOS[aspect_ratio]["dimensions"] | |
# Enhance prompt with AI or fallback | |
enhanced_prompt = enhance_prompt_with_ai( | |
description, | |
creation_type, | |
art_style, | |
mood, | |
color_palette, | |
special_effect | |
) | |
# Validate client availability | |
if hf_client is None: | |
logger.error("Hugging Face client not available") | |
return None, "Error: Unable to connect to image generation service. Please try again later.", enhanced_prompt, "", None | |
# Add negative prompt to avoid common issues | |
negative_prompt = "low quality, blurry, distorted, deformed, disfigured, bad anatomy, watermark, signature, text, poorly drawn, amateur, ugly" | |
try: | |
# Generate image with progress tracking | |
logger.info(f"Sending request to model {model_key} with prompt: {enhanced_prompt[:100]}...") | |
# Log start time for performance tracking | |
start_time = time.time() | |
# Set generation parameters | |
params = { | |
"negative_prompt": negative_prompt, | |
"width": width, | |
"height": height | |
} | |
# Add seed if provided | |
if seed is not None: | |
params["seed"] = seed | |
else: | |
# Generate random seed | |
seed = random.randint(0, 2147483647) | |
params["seed"] = seed | |
# Generate the image | |
image = hf_client.text_to_image( | |
prompt=enhanced_prompt, | |
model=model_key, | |
**params | |
) | |
# Apply special effects if requested | |
if special_effect and special_effect != "None": | |
image = apply_special_effects(image, special_effect) | |
# Calculate generation time | |
generation_time = time.time() - start_time | |
logger.info(f"Image generated successfully in {generation_time:.2f} seconds") | |
# Format parameters for display | |
model_info = "" | |
for key, info in IMAGE_MODELS.items(): | |
if key == model_key: | |
model_info = f"{info['icon']} {info['display_name']}" | |
break | |
# Create parameters display | |
parameters_html = format_parameters( | |
f"{CREATION_TYPES.get(creation_type, {}).get('icon', '•')} {creation_type}" if creation_type else "", | |
f"{ART_STYLES.get(art_style, {}).get('icon', '•')} {art_style}" if art_style else "", | |
f"{MOODS.get(mood, {}).get('icon', '•')} {mood}" if mood else "", | |
f"{ASPECT_RATIOS.get(aspect_ratio, {}).get('icon', '•')} {aspect_ratio}" if aspect_ratio else "", | |
f"{COLOR_PALETTES.get(color_palette, {}).get('icon', '•')} {color_palette}" if color_palette else "", | |
f"{SPECIAL_EFFECTS.get(special_effect, {}).get('icon', '•')} {special_effect}" if special_effect else "", | |
model_info | |
) | |
# Cache the result | |
cache_image(cache_key, image, enhanced_prompt, parameters_html, seed) | |
# Success message with generation details | |
if use_ai_enhancer: | |
enhancement_method = "AI-enhanced prompt" | |
else: | |
enhancement_method = "rule-based prompt enhancement" | |
model_display_name = "Unknown Model" | |
for key, info in IMAGE_MODELS.items(): | |
if key == model_key: | |
model_display_name = info['display_name'] | |
break | |
success_message = f"Image created successfully in {generation_time:.1f}s using {model_display_name} with {enhancement_method} (seed: {seed})" | |
return image, success_message, enhanced_prompt, parameters_html, seed | |
except Exception as e: | |
error_message = str(e) | |
logger.error(f"Error during image generation: {error_message}") | |
# Retry logic for transient errors | |
if retries > 0: | |
logger.info(f"Retrying image generation, {retries} attempts remaining") | |
time.sleep(1) # Small delay before retry | |
return generate_image(description, model_key, creation_type, art_style, mood, | |
aspect_ratio, color_palette, special_effect, seed, retries - 1) | |
# Format user-friendly error message | |
if "429" in error_message: | |
friendly_error = "Server is currently busy. Please try again in a few moments." | |
elif "401" in error_message or "403" in error_message: | |
friendly_error = "Authentication error with the image service. Please check API settings." | |
elif "timeout" in error_message.lower(): | |
friendly_error = "Request timed out. The server might be under heavy load." | |
else: | |
friendly_error = f"Error generating image: {error_message}" | |
return None, friendly_error, enhanced_prompt, "", None | |
except Exception as e: | |
logger.error(f"Unexpected error in generate_image: {str(e)}") | |
return None, f"Unexpected error: {str(e)}", "", "", None | |
# Wrapper function for generate_image with status updates | |
# Wrapper function for generate_image with status updates (continued) | |
def generate_with_status(description, creation_type_val, art_style_val, mood_val, | |
aspect_ratio_val, color_palette_val, special_effect_val, model_name, seed=None): | |
""" | |
Wrapper for generate_image that handles UI status updates and parameter formatting | |
Returns: | |
tuple: (image, status_html, enhanced_prompt, parameters_html, seed) | |
""" | |
# Check if description is empty | |
if not description or not description.strip(): | |
return None, update_status("Please enter a description", is_error=True), "", "", None | |
# Extract keys from formatted values | |
creation_key = extract_key(creation_type_val) | |
art_key = extract_key(art_style_val) | |
mood_key = extract_key(mood_val) | |
aspect_key = extract_key(aspect_ratio_val) | |
palette_key = extract_key(color_palette_val) | |
effect_key = extract_key(special_effect_val) | |
# Get model key from formatted name | |
model_key = get_model_key_from_display_name(model_name) | |
if not model_key: | |
return None, update_status("Invalid model selection", is_error=True), "", "", None | |
try: | |
# Generate the image | |
image, message, enhanced_prompt, parameters_html, used_seed = generate_image( | |
description, model_key, creation_key, art_key, mood_key, | |
aspect_key, palette_key, effect_key, seed | |
) | |
if image is None: | |
return None, update_status(message, is_error=True), "", "", None | |
# Success message | |
success_message = update_status(message) | |
return image, success_message, enhanced_prompt, parameters_html, used_seed | |
except Exception as e: | |
error_message = str(e) | |
logger.error(f"Error in generate_with_status: {error_message}") | |
return None, update_status(f"Error: {error_message}", is_error=True), "", "", None | |
# Batch generation function | |
def generate_variations(description, model_key, creation_type=None, art_style=None, mood=None, | |
aspect_ratio=None, color_palette=None, special_effect=None, num_images=4): | |
""" | |
Generate multiple variations of an image with different seeds | |
Args: | |
description (str): User's original description | |
model_key (str): Model identifier | |
creation_type (str, optional): Creation type | |
art_style (str, optional): Art style | |
mood (str, optional): Mood | |
aspect_ratio (str, optional): Aspect ratio | |
color_palette (str, optional): Color palette | |
special_effect (str, optional): Special effect | |
num_images (int): Number of variations to generate | |
Returns: | |
list: List of tuples (image, seed) for each variation | |
""" | |
results = [] | |
num_images = min(num_images, APP_CONFIG["max_batch_size"]) # Limit to max batch size | |
# Generate varied seeds | |
import time | |
base_seed = int(time.time()) % 10000 | |
seeds = [base_seed + i * 1000 for i in range(num_images)] | |
# Generate images in sequence | |
for i, seed in enumerate(seeds): | |
try: | |
image, _, _, _, used_seed = generate_image( | |
description, model_key, creation_type, art_style, mood, | |
aspect_ratio, color_palette, special_effect, seed, retries=0 | |
) | |
if image: | |
results.append((image, used_seed)) | |
except Exception as e: | |
logger.error(f"Error generating variation {i+1}: {str(e)}") | |
continue | |
return results | |
# =============== USER PROFILE AND HISTORY MANAGEMENT =============== | |
# User profile management | |
class UserProfile: | |
"""User profile management with preferences and history""" | |
def __init__(self, user_id=None): | |
"""Initialize user profile""" | |
self.user_id = user_id or str(uuid.uuid4()) | |
self.preferences = { | |
"theme": APP_CONFIG["default_theme"], | |
"default_model": "stabilityai/stable-diffusion-xl-base-1.0", | |
"default_creation_type": "Digital Art", | |
"default_aspect_ratio": "1:1 (Square)", | |
"show_tips": True, | |
"advanced_mode": False | |
} | |
self.history = [] | |
self.favorites = [] | |
self.load() | |
def save(self): | |
"""Save user profile to disk""" | |
try: | |
data_dir = os.path.join(APP_CONFIG["data_dir"], "users") | |
os.makedirs(data_dir, exist_ok=True) | |
filename = os.path.join(data_dir, f"{self.user_id}.json") | |
with open(filename, 'w') as f: | |
# Create serializable history (convert PIL images to base64) | |
serializable_history = [] | |
for entry in self.history: | |
if 'image' in entry and entry['image'] is not None: | |
# Convert PIL image to base64 | |
img_buffer = io.BytesIO() | |
entry['image'].save(img_buffer, format="PNG") | |
img_base64 = base64.b64encode(img_buffer.getvalue()).decode('utf-8') | |
# Create copy of entry with base64 image | |
entry_copy = entry.copy() | |
entry_copy['image_data'] = img_base64 | |
entry_copy.pop('image', None) # Remove PIL image | |
serializable_history.append(entry_copy) | |
else: | |
entry_copy = entry.copy() | |
entry_copy.pop('image', None) # Remove PIL image if any | |
serializable_history.append(entry_copy) | |
# Create serializable favorites | |
serializable_favorites = [] | |
for entry in self.favorites: | |
if 'image' in entry and entry['image'] is not None: | |
# Convert PIL image to base64 | |
img_buffer = io.BytesIO() | |
entry['image'].save(img_buffer, format="PNG") | |
img_base64 = base64.b64encode(img_buffer.getvalue()).decode('utf-8') | |
# Create copy of entry with base64 image | |
entry_copy = entry.copy() | |
entry_copy['image_data'] = img_base64 | |
entry_copy.pop('image', None) # Remove PIL image | |
serializable_favorites.append(entry_copy) | |
else: | |
entry_copy = entry.copy() | |
entry_copy.pop('image', None) # Remove PIL image if any | |
serializable_favorites.append(entry_copy) | |
# Create serializable profile | |
profile_data = { | |
'user_id': self.user_id, | |
'preferences': self.preferences, | |
'history': serializable_history, | |
'favorites': serializable_favorites | |
} | |
json.dump(profile_data, f, indent=2) | |
logger.info(f"Saved user profile: {self.user_id}") | |
return True | |
except Exception as e: | |
logger.error(f"Error saving user profile: {str(e)}") | |
return False | |
def load(self): | |
"""Load user profile from disk""" | |
try: | |
filename = os.path.join(APP_CONFIG["data_dir"], "users", f"{self.user_id}.json") | |
if not os.path.exists(filename): | |
logger.info(f"User profile doesn't exist: {self.user_id}") | |
return False | |
with open(filename, 'r') as f: | |
data = json.load(f) | |
# Load profile data | |
self.preferences = data.get('preferences', self.preferences) | |
# Load history and convert base64 back to PIL images | |
self.history = [] | |
for entry in data.get('history', []): | |
if 'image_data' in entry: | |
# Convert base64 back to PIL image | |
img_data = base64.b64decode(entry['image_data']) | |
img = Image.open(io.BytesIO(img_data)) | |
# Create entry with PIL image | |
entry_copy = entry.copy() | |
entry_copy['image'] = img | |
entry_copy.pop('image_data', None) # Remove base64 data | |
self.history.append(entry_copy) | |
else: | |
self.history.append(entry) | |
# Load favorites | |
self.favorites = [] | |
for entry in data.get('favorites', []): | |
if 'image_data' in entry: | |
# Convert base64 back to PIL image | |
img_data = base64.b64decode(entry['image_data']) | |
img = Image.open(io.BytesIO(img_data)) | |
# Create entry with PIL image | |
entry_copy = entry.copy() | |
entry_copy['image'] = img | |
entry_copy.pop('image_data', None) # Remove base64 data | |
self.favorites.append(entry_copy) | |
else: | |
self.favorites.append(entry) | |
logger.info(f"Loaded user profile: {self.user_id}") | |
return True | |
except Exception as e: | |
logger.error(f"Error loading user profile: {str(e)}") | |
return False | |
def add_to_history(self, image, description, enhanced_prompt, parameters, seed=None): | |
"""Add an image to user history""" | |
if image is None: | |
return False | |
# Create history entry | |
entry = { | |
'id': str(uuid.uuid4()), | |
'timestamp': datetime.now().isoformat(), | |
'image': image, | |
'description': description, | |
'enhanced_prompt': enhanced_prompt, | |
'parameters': parameters, | |
'seed': seed | |
} | |
# Add to history | |
self.history.insert(0, entry) | |
# Limit history size | |
if len(self.history) > APP_CONFIG["max_history"]: | |
self.history = self.history[:APP_CONFIG["max_history"]] | |
# Save profile | |
self.save() | |
return True | |
def add_to_favorites(self, image_id): | |
"""Add an image from history to favorites""" | |
# Find image in history | |
for entry in self.history: | |
if entry.get('id') == image_id: | |
# Check if already in favorites | |
if any(fav.get('id') == image_id for fav in self.favorites): | |
return False | |
# Add to favorites | |
self.favorites.insert(0, entry) | |
# Save profile | |
self.save() | |
return True | |
return False | |
def remove_from_favorites(self, image_id): | |
"""Remove an image from favorites""" | |
for i, entry in enumerate(self.favorites): | |
if entry.get('id') == image_id: | |
self.favorites.pop(i) | |
self.save() | |
return True | |
return False | |
def get_history(self): | |
"""Get user history""" | |
return self.history | |
def get_favorites(self): | |
"""Get user favorites""" | |
return self.favorites | |
def clear_history(self): | |
"""Clear user history""" | |
self.history = [] | |
self.save() | |
return True | |
def update_preferences(self, preferences): | |
"""Update user preferences""" | |
self.preferences.update(preferences) | |
self.save() | |
return True | |
# Initialize user profile | |
current_user = UserProfile() | |
# =============== UI CREATION FUNCTIONS =============== | |
# Create modern UI with improved UX | |
def create_ui(): | |
"""Create the application UI""" | |
# Set page title and favicon | |
gr.Blocks(analytics_enabled=APP_CONFIG["enable_analytics"]) | |
# Define CSS for enhanced UI | |
css = """ | |
/* Variables for light/dark theme customization */ | |
:root { | |
/* Light Theme (Default) */ | |
--primary-color: #6A24FE; | |
--primary-hover: #5615ED; | |
--secondary-color: #FF4593; | |
--secondary-hover: #E32D7B; | |
--tertiary-color: #2ECADE; | |
--tertiary-hover: #22A8BC; | |
--accent-color: #FFA03C; | |
--accent-hover: #E98321; | |
--background-color: #F9FAFF; | |
--card-color: #FFFFFF; | |
--card-hover: #F5F7FF; | |
--text-color: #1A1A2E; | |
--text-muted: #64748B; | |
--text-light: #94A3B8; | |
--border-color: #E2E8F0; | |
--border-hover: #CBD5E1; | |
--error-color: #E11D48; | |
--warning-color: #F59E0B; | |
--success-color: #10B981; | |
--info-color: #3B82F6; | |
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05); | |
--shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); | |
--shadow-md: 0 6px 10px -2px rgba(0, 0, 0, 0.12), 0 3px 6px -2px rgba(0, 0, 0, 0.08); | |
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); | |
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); | |
--radius-sm: 0.375rem; | |
--radius: 0.5rem; | |
--radius-md: 0.625rem; | |
--radius-lg: 0.75rem; | |
--radius-xl: 1rem; | |
--radius-2xl: 1.5rem; | |
--radius-3xl: 2rem; | |
--radius-full: 9999px; | |
--font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; | |
--font-display: 'Plus Jakarta Sans', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; | |
--font-mono: 'JetBrains Mono', 'SF Mono', 'Roboto Mono', Menlo, Consolas, monospace; | |
--header-height: 4rem; | |
/* Theme transition */ | |
--transition-normal: 0.2s ease; | |
--transition-slow: 0.35s ease; | |
} | |
/* Dark Theme */ | |
.dark-theme { | |
--primary-color: #8B5CF6; | |
--primary-hover: #7C3AED; | |
--secondary-color: #EC4899; | |
--secondary-hover: #DB2777; | |
--tertiary-color: #06B6D4; | |
--tertiary-hover: #0891B2; | |
--accent-color: #F97316; | |
--accent-hover: #EA580C; | |
--background-color: #0F172A; | |
--card-color: #1E293B; | |
--card-hover: #263449; | |
--text-color: #F8FAFC; | |
--text-muted: #94A3B8; | |
--text-light: #CBD5E1; | |
--border-color: #334155; | |
--border-hover: #475569; | |
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.2); | |
--shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3), 0 2px 4px -1px rgba(0, 0, 0, 0.2); | |
--shadow-md: 0 6px 10px -2px rgba(0, 0, 0, 0.4), 0 3px 6px -2px rgba(0, 0, 0, 0.25); | |
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.4), 0 4px 6px -2px rgba(0, 0, 0, 0.2); | |
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.4), 0 10px 10px -5px rgba(0, 0, 0, 0.2); | |
} | |
/* Global styles and resets */ | |
body, html { | |
font-family: var(--font-sans); | |
color: var(--text-color); | |
background-color: var(--background-color); | |
line-height: 1.5; | |
margin: 0; | |
padding: 0; | |
transition: background-color var(--transition-normal), color var(--transition-normal); | |
} | |
/* Container with responsive padding */ | |
.container { | |
max-width: 1500px; | |
margin: 0 auto; | |
padding: 1rem; | |
} | |
@media (max-width: 768px) { | |
.container { | |
padding: 0.75rem; | |
} | |
} | |
@media (max-width: 640px) { | |
.container { | |
padding: 0.5rem; | |
} | |
} | |
/* Add custom fonts */ | |
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Plus+Jakarta+Sans:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap'); | |
/* Card styling with elevation and hover effects */ | |
.gr-panel, div.gradio-box { | |
border-radius: var(--radius-lg) !important; | |
border: 1px solid var(--border-color) !important; | |
box-shadow: var(--shadow) !important; | |
overflow: hidden; | |
transition: transform 0.2s, box-shadow 0.2s, background-color var(--transition-normal), border-color var(--transition-normal); | |
background-color: var(--card-color) !important; | |
} | |
.gr-panel:hover, div.gradio-box:hover { | |
transform: translateY(-2px); | |
box-shadow: var(--shadow-lg) !important; | |
border-color: var(--border-hover) !important; | |
} | |
/* Primary Button styling with gradient and hover states */ | |
button.primary { | |
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)) !important; | |
color: white !important; | |
border: none !important; | |
border-radius: var(--radius) !important; | |
font-weight: 600 !important; | |
letter-spacing: 0.025em !important; | |
padding: 0.75rem 1.5rem !important; | |
transition: all 0.3s ease !important; | |
box-shadow: var(--shadow-sm) !important; | |
outline: none !important; | |
text-transform: none !important; | |
position: relative; | |
overflow: hidden; | |
z-index: 1; | |
} | |
button.primary::before { | |
content: ''; | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
background: linear-gradient(135deg, var(--secondary-color), var(--primary-color)); | |
z-index: -1; | |
opacity: 0; | |
transition: opacity 0.3s ease; | |
} | |
button.primary:hover { | |
transform: translateY(-1px); | |
box-shadow: var(--shadow) !important; | |
} | |
button.primary:hover::before { | |
opacity: 1; | |
} | |
button.primary:active { | |
transform: translateY(0); | |
} | |
button.primary[disabled], button.primary[disabled]:hover { | |
opacity: 0.5; | |
cursor: not-allowed; | |
transform: none; | |
} | |
/* Secondary button styling */ | |
button.secondary { | |
background-color: transparent !important; | |
color: var(--primary-color) !important; | |
border: 1px solid var(--primary-color) !important; | |
border-radius: var(--radius) !important; | |
font-weight: 500 !important; | |
padding: 0.625rem 1.25rem !important; | |
transition: all 0.2s ease !important; | |
text-transform: none !important; | |
} | |
button.secondary:hover { | |
background-color: rgba(106, 36, 254, 0.08) !important; | |
border-color: var(--primary-hover) !important; | |
transform: translateY(-1px); | |
} | |
/* Ghost button styling */ | |
button.ghost { | |
background-color: transparent !important; | |
color: var(--text-color) !important; | |
border: 1px solid var(--border-color) !important; | |
border-radius: var(--radius) !important; | |
font-weight: 500 !important; | |
padding: 0.625rem 1.25rem !important; | |
transition: all 0.2s ease !important; | |
text-transform: none !important; | |
} | |
button.ghost:hover { | |
background-color: rgba(0, 0, 0, 0.04) !important; | |
border-color: var(--border-hover) !important; | |
transform: translateY(-1px); | |
} | |
.dark-theme button.ghost:hover { | |
background-color: rgba(255, 255, 255, 0.04) !important; | |
} | |
/* Style for the example buttons */ | |
.example-button { | |
font-size: 0.875rem !important; | |
padding: 0.75rem 1rem !important; | |
background-color: var(--card-color) !important; | |
border: 1px solid var(--border-color) !important; | |
border-radius: var(--radius-lg) !important; | |
transition: all 0.2s !important; | |
text-align: left !important; | |
justify-content: flex-start !important; | |
height: auto !important; | |
text-overflow: ellipsis; | |
overflow: hidden; | |
white-space: nowrap; | |
width: 100%; | |
color: var(--text-color) !important; | |
box-shadow: var(--shadow-sm) !important; | |
} | |
.example-button:hover { | |
background-color: var(--card-hover) !important; | |
border-color: var(--primary-color) !important; | |
transform: translateY(-2px); | |
box-shadow: var(--shadow) !important; | |
} | |
/* Form controls styling */ | |
.gr-input, .gr-textarea, .gr-dropdown { | |
border-radius: var(--radius) !important; | |
border: 1px solid var(--border-color) !important; | |
transition: border-color 0.2s, box-shadow 0.2s, background-color var(--transition-normal), color var(--transition-normal) !important; | |
font-family: var(--font-sans) !important; | |
color: var(--text-color) !important; | |
background-color: var(--card-color) !important; | |
} | |
.gr-input:focus, .gr-textarea:focus, .gr-dropdown:focus-within { | |
border-color: var(--primary-color) !important; | |
box-shadow: 0 0 0 3px rgba(106, 36, 254, 0.2) !important; | |
outline: none !important; | |
} | |
.dark-theme .gr-input:focus, .dark-theme .gr-textarea:focus, .dark-theme .gr-dropdown:focus-within { | |
box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.3) !important; | |
} | |
.gr-form { | |
gap: 1rem !important; | |
} | |
.gr-input-label, .gr-dropdown-label, .gr-textarea-label, .gr-checkbox-label, .gr-radio-label { | |
font-size: 0.875rem !important; | |
font-weight: 500 !important; | |
color: var(--text-color) !important; | |
margin-bottom: 0.25rem !important; | |
transition: color var(--transition-normal); | |
} | |
/* Input placeholder styling */ | |
.gr-input::placeholder, .gr-textarea::placeholder { | |
color: var(--text-muted) !important; | |
opacity: 0.7; | |
} | |
/* Input and textarea styling */ | |
textarea, input[type="text"], input[type="number"], input[type="email"], input[type="password"] { | |
border-radius: var(--radius) !important; | |
border: 1px solid var(--border-color) !important; | |
padding: 0.75rem 1rem !important; | |
transition: border-color 0.2s, box-shadow 0.2s, background-color var(--transition-normal), color var(--transition-normal) !important; | |
font-family: var(--font-sans) !important; | |
background-color: var(--card-color) !important; | |
color: var(--text-color) !important; | |
} | |
textarea:focus, input[type="text"]:focus, input[type="number"]:focus, input[type="email"]:focus, input[type="password"]:focus { | |
border-color: var(--primary-color) !important; | |
box-shadow: 0 0 0 3px rgba(106, 36, 254, 0.2) !important; | |
outline: none !important; | |
} | |
.dark-theme textarea:focus, .dark-theme input[type="text"]:focus, .dark-theme input[type="number"]:focus, | |
.dark-theme input[type="email"]:focus, .dark-theme input[type="password"]:focus { | |
box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.3) !important; | |
} | |
/* Dropdown styling */ | |
.gr-dropdown { | |
border-radius: var(--radius) !important; | |
border: 1px solid var(--border-color) !important; | |
background-color: var(--card-color) !important; | |
color: var(--text-color) !important; | |
transition: border-color 0.2s, box-shadow 0.2s, background-color var(--transition-normal), color var(--transition-normal) !important; | |
} | |
.gr-dropdown > div { | |
border-radius: var(--radius) !important; | |
min-height: 38px !important; | |
} | |
.gr-dropdown > div > span { | |
font-size: 0.9375rem !important; | |
color: var(--text-color) !important; | |
} | |
/* Dropdown menu styling */ | |
.gr-dropdown ul { | |
background-color: var(--card-color) !important; | |
border: 1px solid var(--border-color) !important; | |
border-radius: var(--radius) !important; | |
box-shadow: var(--shadow) !important; | |
transition: background-color var(--transition-normal), border-color var(--transition-normal) !important; | |
} | |
.gr-dropdown ul li { | |
padding: 0.5rem 0.75rem !important; | |
color: var(--text-color) !important; | |
transition: background-color var(--transition-normal), color var(--transition-normal) !important; | |
} | |
.gr-dropdown ul li:hover { | |
background-color: rgba(106, 36, 254, 0.08) !important; | |
} | |
.dark-theme .gr-dropdown ul li:hover { | |
background-color: rgba(139, 92, 246, 0.15) !important; | |
} | |
/* App header with animation and brand styling */ | |
.app-header { | |
text-align: center; | |
padding: 2.5rem 1.5rem 2rem; | |
margin-bottom: 2rem; | |
background: linear-gradient(135deg, rgba(106, 36, 254, 0.08), rgba(255, 69, 147, 0.08)); | |
border-radius: var(--radius-xl); | |
position: relative; | |
overflow: hidden; | |
transition: background var(--transition-normal); | |
} | |
.dark-theme .app-header { | |
background: linear-gradient(135deg, rgba(139, 92, 246, 0.12), rgba(236, 72, 153, 0.12)); | |
} | |
.app-header::before { | |
content: ''; | |
position: absolute; | |
top: -50px; | |
left: -50px; | |
right: -50px; | |
height: 100px; | |
background: linear-gradient(135deg, rgba(106, 36, 254, 0.2), rgba(255, 69, 147, 0.2)); | |
transform: rotate(-5deg); | |
z-index: 0; | |
transition: background var(--transition-normal); | |
} | |
.dark-theme .app-header::before { | |
background: linear-gradient(135deg, rgba(139, 92, 246, 0.25), rgba(236, 72, 153, 0.25)); | |
} | |
.app-header::after { | |
content: ''; | |
position: absolute; | |
bottom: -50px; | |
left: -50px; | |
right: -50px; | |
height: 70px; | |
background: linear-gradient(135deg, rgba(255, 69, 147, 0.1), rgba(106, 36, 254, 0.1)); | |
transform: rotate(3deg); | |
z-index: 0; | |
transition: background var(--transition-normal); | |
} | |
.dark-theme .app-header::after { | |
background: linear-gradient(135deg, rgba(236, 72, 153, 0.15), rgba(139, 92, 246, 0.15)); | |
} | |
.app-header h1 { | |
font-family: var(--font-display) !important; | |
font-size: 3.5rem !important; | |
font-weight: 800 !important; | |
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); | |
-webkit-background-clip: text; | |
-webkit-text-fill-color: transparent; | |
margin-bottom: 0.5rem !important; | |
position: relative; | |
z-index: 1; | |
letter-spacing: -0.03em; | |
transition: background var(--transition-normal); | |
} | |
.dark-theme .app-header h1 { | |
background: linear-gradient(135deg, #A78BFA, #F472B6); | |
-webkit-background-clip: text; | |
-webkit-text-fill-color: transparent; | |
} | |
.app-header p { | |
font-size: 1.25rem !important; | |
color: var(--text-color); | |
opacity: 0.9; | |
max-width: 42rem; | |
margin: 0 auto; | |
position: relative; | |
z-index: 1; | |
transition: color var(--transition-normal); | |
} | |
.app-tagline { | |
font-family: var(--font-display) !important; | |
font-weight: 600; | |
font-size: 1.5rem !important; | |
background: linear-gradient(135deg, var(--secondary-color), var(--primary-color)); | |
-webkit-background-clip: text; | |
-webkit-text-fill-color: transparent; | |
margin-bottom: 1rem !important; | |
transition: background var(--transition-normal); | |
} | |
.dark-theme .app-tagline { | |
background: linear-gradient(135deg, #F472B6, #A78BFA); | |
-webkit-background-clip: text; | |
-webkit-text-fill-color: transparent; | |
} | |
/* Responsive header */ | |
@media (max-width: 768px) { | |
.app-header h1 { | |
font-size: 2.5rem !important; | |
} | |
.app-tagline { | |
font-size: 1.25rem !important; | |
} | |
.app-header p { | |
font-size: 1rem !important; | |
} | |
} | |
@media (max-width: 640px) { | |
.app-header h1 { | |
font-size: 2rem !important; | |
} | |
.app-header p { | |
font-size: 0.875rem !important; | |
} | |
} | |
/* Gallery with grid layout and responsive design */ | |
.gallery { | |
display: grid; | |
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); | |
gap: 1rem; | |
margin: 1.5rem 0; | |
} | |
@media (max-width: 768px) { | |
.gallery { | |
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); | |
gap: 0.75rem; | |
} | |
} | |
@media (max-width: 480px) { | |
.gallery { | |
grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); | |
gap: 0.5rem; | |
} | |
} | |
.gallery-item { | |
border-radius: var(--radius-lg); | |
overflow: hidden; | |
cursor: pointer; | |
border: 2px solid transparent; | |
transition: all 0.3s; | |
display: flex; | |
flex-direction: column; | |
background-color: var(--card-color); | |
box-shadow: var(--shadow-sm); | |
position: relative; | |
} | |
.gallery-item:hover { | |
transform: translateY(-3px) scale(1.02); | |
border-color: var(--primary-color); | |
box-shadow: var(--shadow-md); | |
} | |
.dark-theme .gallery-item:hover { | |
border-color: var(--primary-color); | |
} | |
.gallery-item:active { | |
transform: translateY(-1px) scale(1.01); | |
} | |
.gallery-item-image { | |
width: 100%; | |
aspect-ratio: 1; | |
background-color: rgba(0, 0, 0, 0.04); | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
color: var(--text-muted); | |
font-size: 1.5rem; | |
border-bottom: 1px solid var(--border-color); | |
transition: background-color var(--transition-normal), border-color var(--transition-normal); | |
} | |
.dark-theme .gallery-item-image { | |
background-color: rgba(255, 255, 255, 0.04); | |
} | |
.gallery-item-caption { | |
padding: 0.75rem; | |
font-size: 0.75rem; | |
line-height: 1.25; | |
color: var(--text-color); | |
overflow: hidden; | |
display: -webkit-box; | |
-webkit-line-clamp: 2; | |
-webkit-box-orient: vertical; | |
transition: color var(--transition-normal); | |
} | |
.gallery-item-labels { | |
display: flex; | |
gap: 0.25rem; | |
padding: 0 0.5rem 0.5rem; | |
} | |
.gallery-item-label { | |
font-size: 0.625rem; | |
padding: 0.125rem 0.375rem; | |
border-radius: var(--radius-full); | |
background-color: rgba(106, 36, 254, 0.1); | |
color: var(--primary-color); | |
white-space: nowrap; | |
transition: background-color var(--transition-normal), color var(--transition-normal); | |
} | |
.dark-theme .gallery-item-label { | |
background-color: rgba(139, 92, 246, 0.2); | |
color: #A78BFA; | |
} | |
/* Loading indicator with animation */ | |
.loading-indicator { | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
height: 100%; | |
width: 100%; | |
position: absolute; | |
top: 0; | |
left: 0; | |
background-color: rgba(255, 255, 255, 0.85); | |
z-index: 1000; | |
backdrop-filter: blur(3px); | |
border-radius: var(--radius-lg); | |
transition: background-color var(--transition-normal); | |
} | |
.dark-theme .loading-indicator { | |
background-color: rgba(15, 23, 42, 0.85); | |
} | |
.spinner { | |
width: 40px; | |
height: 40px; | |
border: 3px solid rgba(106, 36, 254, 0.2); | |
border-radius: 50%; | |
border-top-color: var(--primary-color); | |
animation: spin 1s linear infinite; | |
transition: border-color var(--transition-normal); | |
} | |
.dark-theme .spinner { | |
border: 3px solid rgba(139, 92, 246, 0.3); | |
border-top-color: var(--primary-color); | |
} | |
@keyframes spin { | |
to { | |
transform: rotate(360deg); | |
} | |
} | |
/* Progress bars with animations */ | |
.progress-container { | |
width: 100%; | |
height: 8px; | |
background-color: rgba(106, 36, 254, 0.1); | |
border-radius: var(--radius-full); | |
overflow: hidden; | |
margin: 0.75rem 0; | |
transition: background-color var(--transition-normal); | |
} | |
.dark-theme .progress-container { | |
background-color: rgba(139, 92, 246, 0.15); | |
} | |
.progress-bar { | |
height: 100%; | |
background: linear-gradient(to right, var(--primary-color), var(--secondary-color)); | |
width: 0%; | |
transition: width 0.3s ease, background var(--transition-normal); | |
border-radius: var(--radius-full); | |
position: relative; | |
overflow: hidden; | |
} | |
.dark-theme .progress-bar { | |
background: linear-gradient(to right, #8B5CF6, #EC4899); | |
} | |
.progress-bar::after { | |
content: ""; | |
position: absolute; | |
top: 0; | |
left: 0; | |
bottom: 0; | |
right: 0; | |
background-image: linear-gradient( | |
-45deg, | |
rgba(255, 255, 255, 0.2) 25%, | |
transparent 25%, | |
transparent 50%, | |
rgba(255, 255, 255, 0.2) 50%, | |
rgba(255, 255, 255, 0.2) 75%, | |
transparent 75%, | |
transparent | |
); | |
background-size: 50px 50px; | |
animation: move 2s linear infinite; | |
border-radius: var(--radius-full); | |
overflow: hidden; | |
z-index: 1; | |
} | |
@keyframes move { | |
0% { | |
background-position: 0 0; | |
} | |
100% { | |
background-position: 50px 50px; | |
} | |
} | |
/* Status message with icons */ | |
.status-message { | |
padding: 1rem 1.25rem; | |
border-radius: var(--radius-lg); | |
margin: 1rem 0; | |
font-size: 0.875rem; | |
display: flex; | |
align-items: center; | |
position: relative; | |
transition: background-color var(--transition-normal), color var(--transition-normal); | |
} | |
.status-message .icon { | |
margin-right: 0.75rem; | |
font-size: 1.25rem; | |
flex-shrink: 0; | |
} | |
.status-success { | |
background-color: rgba(16, 185, 129, 0.1); | |
color: var(--success-color); | |
border-left: 3px solid var(--success-color); | |
} | |
.dark-theme .status-success { | |
background-color: rgba(16, 185, 129, 0.15); | |
} | |
.status-error { | |
background-color: rgba(225, 29, 72, 0.1); | |
color: var(--error-color); | |
border-left: 3px solid var(--error-color); | |
} | |
.dark-theme .status-error { | |
background-color: rgba(225, 29, 72, 0.15); | |
} | |
.status-warning { | |
background-color: rgba(245, 158, 11, 0.1); | |
color: var(--warning-color); | |
border-left: 3px solid var(--warning-color); | |
} | |
.dark-theme .status-warning { | |
background-color: rgba(245, 158, 11, 0.15); | |
} | |
.status-info { | |
background-color: rgba(59, 130, 246, 0.1); | |
color: var(--info-color); | |
border-left: 3px solid var(--info-color); | |
} | |
.dark-theme .status-info { | |
background-color: rgba(59, 130, 246, 0.15); | |
} | |
/* Tabs styling with active indicators */ | |
.tabs { | |
display: flex; | |
border-bottom: 2px solid var(--border-color); | |
margin-bottom: 1.5rem; | |
transition: border-color var(--transition-normal); | |
} | |
.tab { | |
padding: 0.875rem 1.25rem; | |
cursor: pointer; | |
border-bottom: 2px solid transparent; | |
font-weight: 600; | |
font-size: 0.9375rem; | |
color: var(--text-muted); | |
transition: all 0.2s; | |
margin-bottom: -2px; | |
position: relative; | |
z-index: 1; | |
} | |
.tab:hover { | |
color: var(--primary-color); | |
} | |
.tab.active { | |
color: var(--primary-color); | |
border-bottom-color: var(--primary-color); | |
} | |
/* Badges for status indicators */ | |
.badge { | |
display: inline-flex; | |
align-items: center; | |
justify-content: center; | |
border-radius: 9999px; | |
padding: 0.25rem 0.625rem; | |
font-size: 0.75rem; | |
font-weight: 600; | |
text-transform: uppercase; | |
letter-spacing: 0.025em; | |
} | |
.badge-success { | |
background-color: rgba(16, 185, 129, 0.1); | |
color: var(--success-color); | |
} | |
.dark-theme .badge-success { | |
background-color: rgba(16, 185, 129, 0.2); | |
} | |
.badge-warning { | |
background-color: rgba(245, 158, 11, 0.1); | |
color: var(--warning-color); | |
} | |
.dark-theme .badge-warning { | |
background-color: rgba(245, 158, 11, 0.2); | |
} | |
.badge-error { | |
background-color: rgba(225, 29, 72, 0.1); | |
color: var(--error-color); | |
} | |
.dark-theme .badge-error { | |
background-color: rgba(225, 29, 72, 0.2); | |
} | |
.badge-info { | |
background-color: rgba(59, 130, 246, 0.1); | |
color: var(--info-color); | |
} | |
.dark-theme .badge-info { | |
background-color: rgba(59, 130, 246, 0.2); | |
} | |
.badge-primary { | |
background-color: rgba(106, 36, 254, 0.1); | |
color: var(--primary-color); | |
} | |
.dark-theme .badge-primary { | |
background-color: rgba(139, 92, 246, 0.2); | |
color: #A78BFA; | |
} | |
/* Character counter with advice */ | |
.character-counter { | |
display: flex; | |
flex-direction: column; | |
font-size: 0.75rem; | |
color: var(--text-muted); | |
margin-top: 0.25rem; | |
transition: color 0.2s; | |
} | |
.character-counter .count { | |
font-weight: 500; | |
} | |
.character-counter .advice { | |
font-size: 0.7rem; | |
opacity: 0.9; | |
margin-top: 0.25rem; | |
} | |
.character-counter.warning { | |
color: var(--warning-color); | |
} | |
.character-counter.error { | |
color: var(--error-color); | |
} | |
.character-counter.success { | |
color: var(--success-color); | |
} | |
.character-counter.info { | |
color: var(--info-color); | |
} | |
/* Creativity slider */ | |
.creativity-slider-container { | |
padding: 1rem; | |
background: linear-gradient(to right, rgba(16, 185, 129, 0.1), rgba(59, 130, 246, 0.1), rgba(139, 92, 246, 0.1)); | |
border-radius: var(--radius-lg); | |
margin: 1rem 0; | |
} | |
.creativity-slider-container .label { | |
display: flex; | |
justify-content: space-between; | |
font-size: 0.875rem; | |
font-weight: 500; | |
margin-bottom: 0.5rem; | |
} | |
.creativity-slider-container .label .value { | |
font-weight: 600; | |
color: var(--primary-color); | |
} | |
/* Model info card */ | |
.model-info { | |
background-color: rgba(106, 36, 254, 0.05); | |
border-left: 3px solid var(--primary-color); | |
padding: 1rem 1.25rem; | |
border-radius: 0 var(--radius) var(--radius) 0; | |
margin: 1rem 0; | |
transition: background-color var(--transition-normal); | |
} | |
.dark-theme .model-info { | |
background-color: rgba(139, 92, 246, 0.1); | |
} | |
.model-info h3 { | |
display: flex; | |
align-items: center; | |
gap: 0.5rem; | |
margin-top: 0; | |
margin-bottom: 0.5rem; | |
font-size: 1rem; | |
font-weight: 600; | |
color: var(--primary-color); | |
} | |
.model-info p { | |
margin: 0 0 0.75rem 0; | |
font-size: 0.875rem; | |
color: var(--text-color); | |
} | |
.model-info .model-id { | |
font-size: 0.75rem; | |
color: var(--text-muted); | |
font-family: var(--font-mono); | |
background-color: rgba(0, 0, 0, 0.03); | |
padding: 0.25rem 0.5rem; | |
border-radius: 4px; | |
margin-top: 0.5rem; | |
word-break: break-all; | |
} | |
.dark-theme .model-info .model-id { | |
background-color: rgba(255, 255, 255, 0.05); | |
} | |
.model-info .strengths-weaknesses { | |
margin-top: 0.5rem; | |
font-size: 0.8125rem; | |
} | |
.model-info .highlight { | |
font-weight: 600; | |
} | |
.model-info .highlight.positive { | |
color: var(--success-color); | |
} | |
.model-info .highlight.negative { | |
color: var(--warning-color); | |
} | |
.model-info .recommended-for { | |
margin-top: 0.75rem; | |
display: flex; | |
flex-wrap: wrap; | |
gap: 0.5rem; | |
align-items: center; | |
} | |
.model-info .recommended-for p { | |
margin: 0; | |
font-size: 0.8125rem; | |
display: flex; | |
flex-wrap: wrap; | |
gap: 0.5rem; | |
align-items: center; | |
} | |
/* Parameter pills */ | |
.parameters-container { | |
background-color: var(--card-color); | |
border-radius: var(--radius-lg); | |
padding: 1rem; | |
margin: 1rem 0; | |
border: 1px solid var(--border-color); | |
transition: background-color var(--transition-normal), border-color var(--transition-normal); | |
} | |
.parameters-title { | |
font-weight: 600; | |
margin-bottom: 0.75rem; | |
font-size: 0.9375rem; | |
color: var(--text-color); | |
} | |
.parameters-grid { | |
display: flex; | |
flex-wrap: wrap; | |
gap: 0.5rem; | |
margin-bottom: 0.75rem; | |
} | |
.parameter-pill { | |
display: inline-flex; | |
align-items: center; | |
background-color: rgba(106, 36, 254, 0.08); | |
color: var(--primary-color); | |
border-radius: var(--radius-full); | |
padding: 0.25rem 0.75rem; | |
font-size: 0.75rem; | |
font-weight: 500; | |
user-select: none; | |
transition: background-color var(--transition-normal), color var(--transition-normal); | |
} | |
.dark-theme .parameter-pill { | |
background-color: rgba(139, 92, 246, 0.12); | |
color: #A78BFA; | |
} | |
.parameter-pill .icon { | |
margin-right: 0.25rem; | |
} | |
.parameter-pill.model-pill { | |
background-color: rgba(59, 130, 246, 0.08); | |
color: var(--info-color); | |
} | |
.dark-theme .parameter-pill.model-pill { | |
background-color: rgba(59, 130, 246, 0.12); | |
color: #60A5FA; | |
} | |
.generation-time { | |
font-size: 0.75rem; | |
color: var(--text-muted); | |
margin-top: 0.5rem; | |
} | |
/* Tips card */ | |
.tips-card { | |
background-color: rgba(59, 130, 246, 0.05); | |
border-radius: var(--radius-lg); | |
padding: 1rem 1.25rem; | |
margin: 1rem 0; | |
border-left: 3px solid var(--info-color); | |
transition: background-color var(--transition-normal); | |
} | |
.dark-theme .tips-card { | |
background-color: rgba(59, 130, 246, 0.1); | |
} | |
.tips-card h4 { | |
display: flex; | |
align-items: center; | |
gap: 0.5rem; | |
margin-top: 0; | |
margin-bottom: 0.75rem; | |
font-size: 1rem; | |
color: var(--info-color); | |
} | |
.tips-card ul { | |
margin: 0; | |
padding-left: 1.5rem; | |
} | |
.tips-card li { | |
margin-bottom: 0.5rem; | |
font-size: 0.875rem; | |
} | |
.tips-card li:last-child { | |
margin-bottom: 0; | |
} | |
/* Theme toggle switch */ | |
.theme-toggle { | |
position: fixed; | |
bottom: 1.5rem; | |
right: 1.5rem; | |
background-color: var(--card-color); | |
border-radius: var(--radius-full); | |
box-shadow: var(--shadow-lg); | |
padding: 0.625rem; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
z-index: 1000; | |
cursor: pointer; | |
border: 1px solid var(--border-color); | |
transition: background-color var(--transition-normal), border-color var(--transition-normal); | |
} | |
.theme-toggle:hover { | |
transform: translateY(-2px); | |
box-shadow: var(--shadow-xl); | |
} | |
.theme-toggle-icon { | |
font-size: 1.25rem; | |
color: var(--text-color); | |
transition: color var(--transition-normal); | |
} | |
/* Tooltip */ | |
.tooltip { | |
position: relative; | |
display: inline-block; | |
} | |
.tooltip .tooltip-text { | |
visibility: hidden; | |
background-color: var(--text-color); | |
color: white; | |
text-align: center; | |
border-radius: var(--radius); | |
padding: 0.5rem 0.75rem; | |
position: absolute; | |
z-index: 1; | |
bottom: 125%; | |
left: 50%; | |
margin-left: -60px; | |
opacity: 0; | |
transition: opacity 0.3s; | |
font-size: 0.75rem; | |
width: 120px; | |
pointer-events: none; | |
} | |
.tooltip .tooltip-text::after { | |
content: ""; | |
position: absolute; | |
top: 100%; | |
left: 50%; | |
margin-left: -5px; | |
border-width: 5px; | |
border-style: solid; | |
border-color: var(--text-color) transparent transparent transparent; | |
} | |
.tooltip:hover .tooltip-text { | |
visibility: visible; | |
opacity: 1; | |
} | |
/* Image grid for gallery view */ | |
.image-grid { | |
display: grid; | |
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); | |
gap: 1rem; | |
margin: 1.5rem 0; | |
} | |
@media (max-width: 768px) { | |
.image-grid { | |
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); | |
gap: 0.75rem; | |
} | |
} | |
@media (max-width: 480px) { | |
.image-grid { | |
grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); | |
gap: 0.5rem; | |
} | |
} | |
/* Empty state illustrations */ | |
.empty-state { | |
display: flex; | |
flex-direction: column; | |
align-items: center; | |
justify-content: center; | |
padding: 3rem 1.5rem; | |
text-align: center; | |
background-color: var(--card-color); | |
border-radius: var(--radius-lg); | |
border: 1px dashed var(--border-color); | |
margin: 1.5rem 0; | |
transition: background-color var(--transition-normal), border-color var(--transition-normal); | |
} | |
.empty-state .icon { | |
font-size: 3rem; | |
margin-bottom: 1.5rem; | |
color: var(--text-muted); | |
} | |
.empty-state h3 { | |
margin: 0 0 0.75rem 0; | |
font-size: 1.25rem; | |
color: var(--text-color); | |
} | |
.empty-state p { | |
margin: 0 0 1.5rem 0; | |
font-size: 0.9375rem; | |
color: var(--text-muted); | |
max-width: 500px; | |
} | |
/* Feature cards */ | |
.feature-grid { | |
display: grid; | |
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); | |
gap: 1.5rem; | |
margin: 2rem 0; | |
} | |
@media (max-width: 768px) { | |
.feature-grid { | |
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); | |
gap: 1rem; | |
} | |
} | |
.feature-card { | |
background-color: var(--card-color); | |
border-radius: var(--radius-lg); | |
padding: 1.5rem; | |
box-shadow: var(--shadow); | |
transition: transform 0.2s, box-shadow 0.2s, background-color var(--transition-normal); | |
display: flex; | |
flex-direction: column; | |
border: 1px solid var(--border-color); | |
} | |
.feature-card:hover { | |
transform: translateY(-5px); | |
box-shadow: var(--shadow-lg); | |
} | |
.feature-icon { | |
font-size: 2.5rem; | |
margin-bottom: 1rem; | |
color: var(--primary-color); | |
} | |
.feature-title { | |
font-size: 1.125rem; | |
font-weight: 700; | |
margin: 0 0 0.75rem 0; | |
color: var(--text-color); | |
} | |
.feature-description { | |
font-size: 0.875rem; | |
color: var(--text-muted); | |
margin: 0; | |
flex-grow: 1; | |
} | |
/* Responsive layout adjustments */ | |
@media (max-width: 1200px) { | |
h1 { | |
font-size: 2.25rem !important; | |
} | |
h2 { | |
font-size: 1.75rem !important; | |
} | |
h3 { | |
font-size: 1.375rem !important; | |
} | |
} | |
@media (max-width: 768px) { | |
button.primary, button.secondary { | |
padding: 0.625rem 1.25rem !important; | |
font-size: 0.9375rem !important; | |
} | |
.app-header { | |
padding: 2rem 1rem 1.5rem; | |
} | |
.gallery-item-image { | |
aspect-ratio: 1; | |
} | |
} | |
@media (max-width: 640px) { | |
.tabs { | |
overflow-x: auto; | |
-webkit-overflow-scrolling: touch; | |
white-space: nowrap; | |
padding-bottom: 0.25rem; | |
} | |
.tab { | |
padding: 0.75rem 1rem; | |
} | |
} | |
""" | |
# Create new interface | |
with gr.Blocks(title=APP_CONFIG["name"], css=css) as interface: | |
# Header with branding | |
with gr.Row(elem_classes="app-header"): | |
with gr.Column(): | |
gr.HTML(f""" | |
<h1>{APP_CONFIG["name"]}</h1> | |
<div class="app-tagline">{APP_CONFIG["tagline"]}</div> | |
<p>{APP_CONFIG["description"]}</p> | |
""") | |
# Main content area - tabs for different sections | |
with gr.Tabs() as tabs: | |
# Create tab | |
with gr.TabItem("Create", id="create-tab") as create_tab: | |
with gr.Row(equal_height=False): | |
# Left column - Input controls | |
with gr.Column(scale=1): | |
# Description input with character counter | |
with gr.Group(): | |
description_input = gr.Textbox( | |
label="Describe what you want to see", | |
placeholder="Be detailed and specific about colors, composition, lighting, and subject...", | |
lines=4, | |
max_lines=8, | |
elem_id="description-input" | |
) | |
# Character counter with dynamic updates | |
char_counter = gr.HTML( | |
value=update_char_count(""), | |
elem_classes="char-counter-container" | |
) | |
# Settings accordion | |
with gr.Accordion("Image Settings", open=True): | |
# Creation Type and Art Style in one row | |
with gr.Row(): | |
# Creation type dropdown with icons | |
creation_type = gr.Dropdown( | |
choices=format_dropdown_choices(CREATION_TYPES), | |
value=f"{CREATION_TYPES['Digital Art']['icon']} Digital Art", | |
label="Creation Type", | |
elem_classes="enhanced-dropdown" | |
) | |
# Art style dropdown with icons | |
art_style = gr.Dropdown( | |
choices=format_dropdown_choices(ART_STYLES), | |
value=f"{ART_STYLES['Photorealistic']['icon']} Photorealistic", | |
label="Art Style", | |
elem_classes="enhanced-dropdown" | |
) | |
# Mood and Model in one row | |
with gr.Row(): | |
# Mood dropdown with icons | |
mood_dropdown = gr.Dropdown( | |
choices=format_dropdown_choices(MOODS), | |
value=f"{MOODS['Peaceful']['icon']} Peaceful", | |
label="Mood", | |
elem_classes="enhanced-dropdown" | |
) | |
# Model selector with display names | |
formatted_models = [f"{info['icon']} {info['display_name']}" for model_key, info in IMAGE_MODELS.items()] | |
model_selector = gr.Dropdown( | |
choices=formatted_models, | |
value=f"{IMAGE_MODELS['stabilityai/stable-diffusion-xl-base-1.0']['icon']} {IMAGE_MODELS['stabilityai/stable-diffusion-xl-base-1.0']['display_name']}", | |
label="Model", | |
elem_classes="enhanced-dropdown" | |
) | |
# Advanced options | |
with gr.Accordion("Advanced Options", open=False): | |
with gr.Row(): | |
# Aspect ratio dropdown | |
aspect_ratio = gr.Dropdown( | |
choices=format_dropdown_choices(ASPECT_RATIOS), | |
value=f"{ASPECT_RATIOS['1:1 (Square)']['icon']} 1:1 (Square)", | |
label="Aspect Ratio", | |
elem_classes="enhanced-dropdown" | |
) | |
# Seed input | |
seed_input = gr.Number( | |
label="Seed (optional)", | |
value=None, | |
precision=0, | |
elem_id="seed-input" | |
) | |
with gr.Row(): | |
# Color palette dropdown | |
color_palette = gr.Dropdown( | |
choices=format_dropdown_choices(COLOR_PALETTES), | |
value=None, | |
label="Color Palette (optional)", | |
elem_classes="enhanced-dropdown" | |
) | |
# Special effects dropdown | |
special_effect = gr.Dropdown( | |
choices=format_dropdown_choices(SPECIAL_EFFECTS), | |
value=f"{SPECIAL_EFFECTS['None']['icon']} None", | |
label="Special Effect (optional)", | |
elem_classes="enhanced-dropdown" | |
) | |
# Examples gallery with clear visual structure | |
with gr.Accordion("Try an Example", open=True): | |
# Gallery of examples | |
with gr.Row(elem_classes="gallery"): | |
for i, example in enumerate(EXAMPLE_PROMPTS[:8]): # Show first 8 examples | |
with gr.Column(elem_classes="gallery-item"): | |
# Example card with visual element and caption | |
example_btn = gr.Button( | |
example["thumbnail_desc"], | |
elem_classes="example-button" | |
) | |
# Event handler for example selection | |
example_btn.click( | |
fn=lambda idx=i: load_example(idx), | |
outputs=[description_input, creation_type, art_style, mood_dropdown, | |
aspect_ratio, color_palette, special_effect] | |
) | |
# Generate buttons with batch option | |
with gr.Row(): | |
# Single image generation | |
generate_btn = gr.Button("✨ Generate Image", variant="primary", elem_classes="primary", elem_id="generate-btn") | |
# Generate multiple variations | |
variations_btn = gr.Button("🔄 Generate 4 Variations", elem_classes="secondary", elem_id="variations-btn") | |
# Generation status message | |
generation_status = gr.HTML(value="", elem_classes="generation-status") | |
# Right column - Output display | |
with gr.Column(scale=1): | |
# Tabs for single image and variations | |
with gr.Tabs() as image_tabs: | |
# Single image tab | |
with gr.TabItem("Image", id="single-tab"): | |
# Image display area with placeholder | |
with gr.Group(elem_classes="output-container"): | |
image_output = gr.Image( | |
label="Generated Image", | |
elem_id="image-output", | |
type="pil", | |
height=512, | |
interactive=False | |
) | |
# Action buttons for generated image | |
with gr.Row(): | |
# Download button | |
download_btn = gr.Button("💾 Save Image", elem_classes="secondary") | |
# Add to favorites | |
favorite_btn = gr.Button("❤️ Add to Favorites", elem_classes="secondary") | |
# Generate variations of this image | |
more_variations_btn = gr.Button("🔄 More Like This", elem_classes="secondary") | |
# Image generation details | |
with gr.Accordion("Image Details", open=True): | |
parameters_display = gr.HTML(value="") | |
# Enhanced prompt display | |
with gr.Accordion("AI-Enhanced Prompt", open=False): | |
prompt_output = gr.Textbox( | |
label="AI-Enhanced Prompt Used", | |
lines=5, | |
elem_id="prompt-output", | |
elem_classes="prompt-display" | |
) | |
# Variations tab | |
with gr.TabItem("Variations", id="variations-tab"): | |
# Variations gallery | |
with gr.Group(elem_classes="variations-gallery"): | |
variations_gallery = gr.Gallery( | |
label="Image Variations", | |
elem_id="variations-gallery", | |
columns=2, | |
rows=2, | |
height=512, | |
object_fit="contain" | |
) | |
# Variations info | |
variations_info = gr.HTML(value="") | |
# Information panels for selected options | |
with gr.Group(elem_classes="info-panels"): | |
# Creation type info | |
creation_info = gr.HTML(value="", elem_classes="creation-info") | |
# Art style info | |
art_info = gr.HTML(value="", elem_classes="art-info") | |
# Model info panel | |
model_info = gr.HTML(value="", elem_classes="model-info") | |
# Mood info | |
mood_info = gr.HTML(value="", elem_classes="mood-info") | |
# Gallery tab with history and favorites | |
with gr.TabItem("My Creations", id="gallery-tab") as gallery_tab: | |
with gr.Tabs() as gallery_tabs: | |
# History tab | |
with gr.TabItem("History", id="history-tab"): | |
# History toolbar | |
with gr.Row(): | |
refresh_history_btn = gr.Button("🔄 Refresh", elem_classes="secondary") | |
clear_history_btn = gr.Button("🗑️ Clear History", elem_classes="secondary") | |
# History gallery | |
history_gallery = gr.Gallery( | |
label="Your Generated Images", | |
elem_id="history-gallery", | |
columns=4, | |
rows=3, | |
height=600, | |
object_fit="contain" | |
) | |
# History info | |
history_info = gr.HTML(value="") | |
# Favorites tab | |
with gr.TabItem("Favorites", id="favorites-tab"): | |
# Favorites toolbar | |
with gr.Row(): | |
refresh_favorites_btn = gr.Button("🔄 Refresh", elem_classes="secondary") | |
# Favorites gallery | |
favorites_gallery = gr.Gallery( | |
label="Your Favorite Images", | |
elem_id="favorites-gallery", | |
columns=4, | |
rows=3, | |
height=600, | |
object_fit="contain" | |
) | |
# Favorites info | |
favorites_info = gr.HTML(value="") | |
# Explore tab with creative examples and guided mode | |
with gr.TabItem("Explore", id="explore-tab") as explore_tab: | |
with gr.Tabs() as explore_tabs: | |
# Creative prompts tab | |
with gr.TabItem("Inspiration", id="inspiration-tab"): | |
gr.HTML(""" | |
<div class="feature-grid"> | |
<div class="feature-card"> | |
<div class="feature-icon">🧙</div> | |
<h3 class="feature-title">Fantasy Worlds</h3> | |
<p class="feature-description">Explore magical realms, mythical creatures, and enchanted landscapes from your imagination.</p> | |
</div> | |
<div class="feature-card"> | |
<div class="feature-icon">🚀</div> | |
<h3 class="feature-title">Sci-Fi Visions</h3> | |
<p class="feature-description">Create futuristic technology, alien worlds, and space adventures in stunning detail.</p> | |
</div> | |
<div class="feature-card"> | |
<div class="feature-icon">🌌</div> | |
<h3 class="feature-title">Cosmic Dreams</h3> | |
<p class="feature-description">Visualize galaxies, nebulae, and celestial phenomena beyond human imagination.</p> | |
</div> | |
<div class="feature-card"> | |
<div class="feature-icon">👤</div> | |
<h3 class="feature-title">Character Design</h3> | |
<p class="feature-description">Bring unique characters to life for stories, games, or personal projects.</p> | |
</div> | |
<div class="feature-card"> | |
<div class="feature-icon">🏙️</div> | |
<h3 class="feature-title">Cityscape Concepts</h3> | |
<p class="feature-description">Design futuristic cities, cyberpunk streets, or nostalgic urban environments.</p> | |
</div> | |
<div class="feature-card"> | |
<div class="feature-icon">🎨</div> | |
<h3 class="feature-title">Artistic Styles</h3> | |
<p class="feature-description">Experiment with different art styles from renaissance to modern abstract expressions.</p> | |
</div> | |
</div> | |
""") | |
# Creative prompts gallery | |
creative_prompts = gr.Gallery( | |
label="Trending Creations", | |
elem_id="creative-prompts", | |
columns=3, | |
rows=2, | |
height=500, | |
object_fit="contain" | |
) | |
# Guided creation tab | |
with gr.TabItem("Guided Creation", id="guided-tab"): | |
gr.HTML(""" | |
<div class="tips-card"> | |
<h4>✨ Guided Creation Mode</h4> | |
<p>Answer the questions below to create a detailed prompt. Our AI will help you transform your ideas into amazing images.</p> | |
</div> | |
""") | |
# Step-by-step guided prompt creation | |
with gr.Group(): | |
# Subject | |
guided_subject = gr.Textbox( | |
label="What is the main subject?", | |
placeholder="Examples: a majestic dragon, a cybernetic samurai, a cozy cottage...", | |
elem_id="guided-subject" | |
) | |
# Setting | |
guided_setting = gr.Textbox( | |
label="Where is it located? (Setting/Environment)", | |
placeholder="Examples: in a misty forest, on a distant planet, in a futuristic city...", | |
elem_id="guided-setting" | |
) | |
# Lighting and atmosphere | |
guided_lighting = gr.Textbox( | |
label="Describe the lighting and atmosphere", | |
placeholder="Examples: sunset with golden rays, blue moonlight, neon lights...", | |
elem_id="guided-lighting" | |
) | |
# Additional details | |
guided_details = gr.Textbox( | |
label="Additional details (optional)", | |
placeholder="Examples: wearing a red cloak, with glowing eyes, surrounded by flowers...", | |
elem_id="guided-details" | |
) | |
# Style preferences | |
with gr.Row(): | |
guided_art_style = gr.Dropdown( | |
choices=format_dropdown_choices(ART_STYLES), | |
value=f"{ART_STYLES['Photorealistic']['icon']} Photorealistic", | |
label="Art Style", | |
elem_classes="enhanced-dropdown" | |
) | |
guided_mood = gr.Dropdown( | |
choices=format_dropdown_choices(MOODS), | |
value=f"{MOODS['Peaceful']['icon']} Peaceful", | |
label="Mood", | |
elem_classes="enhanced-dropdown" | |
) | |
# Build prompt button | |
build_prompt_btn = gr.Button("🪄 Build My Prompt", variant="primary", elem_classes="primary") | |
# Generated prompt preview | |
guided_preview = gr.Textbox( | |
label="Your Generated Prompt", | |
lines=4, | |
elem_id="guided-preview" | |
) | |
# Use this prompt button | |
use_prompt_btn = gr.Button("✅ Use This Prompt", elem_classes="secondary") | |
# Settings tab | |
with gr.TabItem("Settings", id="settings-tab") as settings_tab: | |
with gr.Tabs() as settings_tabs: | |
# Appearance settings | |
with gr.TabItem("Appearance", id="appearance-tab"): | |
# Theme selector | |
theme_selector = gr.Radio( | |
choices=["Light", "Dark", "Auto"], | |
value=current_user.preferences["theme"], | |
label="Theme", | |
elem_id="theme-selector" | |
) | |
# Apply button | |
apply_theme_btn = gr.Button("Apply Theme", elem_classes="secondary") | |
# Defaults settings | |
with gr.TabItem("Defaults", id="defaults-tab"): | |
# Default model | |
default_model = gr.Dropdown( | |
choices=formatted_models, | |
value=f"{IMAGE_MODELS[current_user.preferences['default_model']]['icon']} {IMAGE_MODELS[current_user.preferences['default_model']]['display_name']}", | |
label="Default Model", | |
elem_classes="enhanced-dropdown" | |
) | |
# Default creation type | |
default_creation_type = gr.Dropdown( | |
choices=format_dropdown_choices(CREATION_TYPES), | |
value=f"{CREATION_TYPES[current_user.preferences['default_creation_type']]['icon']} {current_user.preferences['default_creation_type']}", | |
label="Default Creation Type", | |
elem_classes="enhanced-dropdown" | |
) | |
# Default aspect ratio | |
default_aspect_ratio = gr.Dropdown( | |
choices=format_dropdown_choices(ASPECT_RATIOS), | |
value=f"{ASPECT_RATIOS[current_user.preferences['default_aspect_ratio']]['icon']} {current_user.preferences['default_aspect_ratio']}", | |
label="Default Aspect Ratio", | |
elem_classes="enhanced-dropdown" | |
) | |
# Show tips | |
show_tips = gr.Checkbox( | |
value=current_user.preferences["show_tips"], | |
label="Show Tips and Suggestions", | |
elem_id="show-tips" | |
) | |
# Advanced mode | |
advanced_mode = gr.Checkbox( | |
value=current_user.preferences["advanced_mode"], | |
label="Advanced Mode (Show all options by default)", | |
elem_id="advanced-mode" | |
) | |
# Save defaults button | |
save_defaults_btn = gr.Button("Save Defaults", elem_classes="secondary") | |
# About tab | |
with gr.TabItem("About", id="about-tab"): | |
gr.HTML(f""" | |
<div style="text-align: center; padding: 1rem 0;"> | |
<h2 style="margin-bottom: 0.5rem;">{APP_CONFIG["name"]} v{APP_CONFIG["version"]}</h2> | |
<p style="margin-bottom: 2rem;">{APP_CONFIG["tagline"]}</p> | |
<div style="max-width: 800px; margin: 0 auto; text-align: left;"> | |
<h3>About This Application</h3> | |
<p>Imaginova is a powerful AI image generation platform that helps you bring your creative visions to life. | |
Built with advanced diffusion models, Imaginova transforms your text descriptions into stunning, | |
high-quality images across various styles and artistic mediums.</p> | |
<h3>Features</h3> | |
<ul> | |
<li><strong>Multiple AI Models:</strong> Access to several state-of-the-art image generation models</li> | |
<li><strong>Creative Control:</strong> Customize your images with different styles, moods, and special effects</li> | |
<li><strong>Prompt Enhancement:</strong> AI-powered prompt improvement for better results</li> | |
<li><strong>Guided Creation:</strong> Step-by-step assistance to build detailed prompts</li> | |
<li><strong>Image History:</strong> Save and organize your creations</li> | |
<li><strong>Batch Generation:</strong> Create multiple variations of your ideas</li> | |
</ul> | |
<h3>Credits</h3> | |
<p>Powered by Hugging Face's Diffusion Models and built with Gradio.</p> | |
<p>Created with ❤️ for the creative community.</p> | |
</div> | |
</div> | |
""") | |
# Hidden elements for state management | |
current_seed = gr.Number(value=None, visible=False) | |
current_image_id = gr.Textbox(value="", visible=False) | |
# Event handlers | |
# Character counter update | |
description_input.change( | |
fn=update_char_count, | |
inputs=description_input, | |
outputs=char_counter | |
) | |
# Info panels updates | |
creation_type.change( | |
fn=update_creation_info, | |
inputs=creation_type, | |
outputs=creation_info | |
) | |
art_style.change( | |
fn=update_art_style_info, | |
inputs=art_style, | |
outputs=art_info | |
) | |
model_selector.change( | |
fn=update_model_info, | |
inputs=model_selector, | |
outputs=model_info | |
) | |
mood_dropdown.change( | |
fn=update_mood_info, | |
inputs=mood_dropdown, | |
outputs=mood_info | |
) | |
# Generate image button | |
generate_btn.click( | |
fn=generate_with_status, | |
inputs=[ | |
description_input, | |
creation_type, | |
art_style, | |
mood_dropdown, | |
aspect_ratio, | |
color_palette, | |
special_effect, | |
model_selector, | |
seed_input | |
], | |
outputs=[ | |
image_output, | |
generation_status, | |
prompt_output, | |
parameters_display, | |
current_seed | |
] | |
) | |
# Add generated image to history | |
def add_to_history_handler(image, description, prompt, parameters, seed): | |
"""Add the generated image to user history""" | |
if image is None: | |
return gr.HTML.update(value="No image to save") | |
# Add to history | |
image_id = str(uuid.uuid4()) | |
success = current_user.add_to_history(image, description, prompt, parameters, seed) | |
if success: | |
return gr.Textbox.update(value=image_id) | |
else: | |
return gr.Textbox.update(value="") | |
# After successful generation, add to history | |
image_output.change( | |
fn=add_to_history_handler, | |
inputs=[ | |
image_output, | |
description_input, | |
prompt_output, | |
parameters_display, | |
current_seed | |
], | |
outputs=[current_image_id] | |
) | |
# Generate variations | |
def generate_variations_handler(description, creation_type_val, art_style_val, mood_val, | |
aspect_ratio_val, color_palette_val, special_effect_val, model_name): | |
"""Generate multiple variations of an image""" | |
try: | |
# Extract keys from formatted values | |
creation_key = extract_key(creation_type_val) | |
art_key = extract_key(art_style_val) | |
mood_key = extract_key(mood_val) | |
aspect_key = extract_key(aspect_ratio_val) | |
palette_key = extract_key(color_palette_val) | |
effect_key = extract_key(special_effect_val) | |
# Get model key | |
model_key = get_model_key_from_display_name(model_name) | |
# Generate variations (4 by default) | |
results = generate_variations( | |
description, model_key, creation_key, art_key, mood_key, | |
aspect_key, palette_key, effect_key, num_images=4 | |
) | |
# Extract images and seeds | |
images = [img for img, _ in results] | |
seeds = [seed for _, seed in results] | |
# Create info text | |
if images: | |
seeds_text = ", ".join([str(s) for s in seeds]) | |
info_html = f"""<div class="status-message status-success"> | |
<span class="icon">✅</span> | |
<span>Generated {len(images)} variations successfully. Seeds: {seeds_text}</span> | |
</div>""" | |
else: | |
info_html = """<div class="status-message status-error"> | |
<span class="icon">❌</span> | |
<span>Failed to generate variations. Please try again.</span> | |
</div>""" | |
# Switch to variations tab | |
image_tabs.select("variations-tab") | |
return images, info_html | |
except Exception as e: | |
error_html = f"""<div class="status-message status-error"> | |
<span class="icon">❌</span> | |
<span>Error generating variations: {str(e)}</span> | |
</div>""" | |
return [], error_html | |
# Variations button click | |
variations_btn.click( | |
fn=generate_variations_handler, | |
inputs=[ | |
description_input, | |
creation_type, | |
art_style, | |
mood_dropdown, | |
aspect_ratio, | |
color_palette, | |
special_effect, | |
model_selector | |
], | |
outputs=[ | |
variations_gallery, | |
variations_info | |
] | |
) | |
# Guided creation handlers | |
def build_guided_prompt(subject, setting, lighting, details, art_style_val, mood_val): | |
"""Build a detailed prompt from guided inputs""" | |
if not subject: | |
return "Please describe the main subject first" | |
# Extract keys | |
art_key = extract_key(art_style_val) | |
mood_key = extract_key(mood_val) | |
# Get art style info | |
art_style_desc = "" | |
if art_key and art_key in ART_STYLES: | |
art_info = ART_STYLES[art_key] | |
style_terms = ", ".join(art_info.get("technical_terms", [])[:2]) | |
art_style_desc = f"in {art_key} style, {style_terms}" | |
# Get mood info | |
mood_desc = "" | |
if mood_key and mood_key in MOODS: | |
mood_info = MOODS[mood_key] | |
mood_desc = f"with {mood_key.lower()} mood, {mood_info.get('lighting', '')}" | |
# Build prompt parts | |
prompt_parts = [] | |
# Main subject | |
prompt_parts.append(subject.strip()) | |
# Setting/environment | |
if setting: | |
prompt_parts.append(setting.strip()) | |
# Lighting and atmosphere | |
if lighting: | |
prompt_parts.append(lighting.strip()) | |
# Additional details | |
if details: | |
prompt_parts.append(details.strip()) | |
# Style and mood | |
if art_style_desc: | |
prompt_parts.append(art_style_desc) | |
if mood_desc: | |
prompt_parts.append(mood_desc) | |
# Quality terms | |
quality_terms = "highly detailed, masterful, 8K resolution, professional, sharp focus, high quality" | |
prompt_parts.append(quality_terms) | |
# Build final prompt | |
prompt = ", ".join(prompt_parts) | |
return prompt | |
# Build prompt button | |
build_prompt_btn.click( | |
fn=build_guided_prompt, | |
inputs=[ | |
guided_subject, | |
guided_setting, | |
guided_lighting, | |
guided_details, | |
guided_art_style, | |
guided_mood | |
], | |
outputs=[guided_preview] | |
) | |
# Use guided prompt | |
def use_guided_prompt(prompt): | |
"""Use the guided prompt in the main create tab""" | |
# Switch to create tab | |
tabs.select("create-tab") | |
return prompt | |
# Use prompt button | |
use_prompt_btn.click( | |
fn=use_guided_prompt, | |
inputs=[guided_preview], | |
outputs=[description_input] | |
) | |
# History and favorites handlers | |
def load_history(): | |
"""Load user history for gallery display""" | |
history = current_user.get_history() | |
if not history: | |
info_html = """<div class="empty-state"> | |
<div class="icon">🖼️</div> | |
<h3>No Images Yet</h3> | |
<p>Your created images will appear here. Start generating to build your collection!</p> | |
</div>""" | |
return [], info_html | |
# Extract images | |
images = [entry.get("image") for entry in history if "image" in entry] | |
info_html = f"""<div class="status-info"> | |
<p>Showing {len(images)} images from your history</p> | |
</div>""" | |
return images, info_html | |
# Load history on tab click | |
gallery_tab.select( | |
fn=load_history, | |
inputs=[], | |
outputs=[history_gallery, history_info] | |
) | |
# Refresh history button | |
refresh_history_btn.click( | |
fn=load_history, | |
inputs=[], | |
outputs=[history_gallery, history_info] | |
) | |
# Clear history button | |
def clear_history(): | |
"""Clear user history""" | |
success = current_user.clear_history() | |
if success: | |
info_html = """<div class="status-success"> | |
<span class="icon">✅</span> | |
<span>History cleared successfully</span> | |
</div>""" | |
else: | |
info_html = """<div class="status-error"> | |
<span class="icon">❌</span> | |
<span>Failed to clear history</span> | |
</div>""" | |
return [], info_html | |
# Clear history button | |
clear_history_btn.click( | |
fn=clear_history, | |
inputs=[], | |
outputs=[history_gallery, history_info] | |
) | |
# Load favorites | |
def load_favorites(): | |
"""Load user favorites for gallery display""" | |
favorites = current_user.get_favorites() | |
if not favorites: | |
info_html = """<div class="empty-state"> | |
<div class="icon">❤️</div> | |
<h3>No Favorites Yet</h3> | |
<p>Your favorite images will appear here. Add images to favorites to build your collection!</p> | |
</div>""" | |
return [], info_html | |
# Extract images | |
images = [entry.get("image") for entry in favorites if "image" in entry] | |
info_html = f"""<div class="status-info"> | |
<p>Showing {len(images)} favorite images</p> | |
</div>""" | |
return images, info_html | |
# Refresh favorites button | |
refresh_favorites_btn.click( | |
fn=load_favorites, | |
inputs=[], | |
outputs=[favorites_gallery, favorites_info] | |
) | |
# Add to favorites | |
def add_to_favorites(image_id): | |
"""Add an image to favorites""" | |
if not image_id: | |
return """<div class="status-error"> | |
<span class="icon">❌</span> | |
<span>No image to add to favorites</span> | |
</div>""" | |
success = current_user.add_to_favorites(image_id) | |
if success: | |
return """<div class="status-success"> | |
<span class="icon">✅</span> | |
<span>Image added to favorites</span> | |
</div>""" | |
else: | |
return """<div class="status-warning"> | |
<span class="icon">⚠️</span> | |
<span>Image already in favorites or not found</span> | |
</div>""" | |
# Add to favorites button | |
favorite_btn.click( | |
fn=add_to_favorites, | |
inputs=[current_image_id], | |
outputs=[generation_status] | |
) | |
# Update theme | |
def update_theme(theme): | |
"""Update user theme preference""" | |
if theme not in ["Light", "Dark", "Auto"]: | |
theme = "Light" | |
current_user.update_preferences({"theme": theme}) | |
# Return HTML with embedded JavaScript | |
if theme == "Dark": | |
return gr.HTML(""" | |
<script> | |
document.body.classList.add('dark-theme'); | |
document.body.classList.remove('light-theme'); | |
</script> | |
""") | |
elif theme == "Light": | |
return gr.HTML(""" | |
<script> | |
document.body.classList.remove('dark-theme'); | |
document.body.classList.add('light-theme'); | |
</script> | |
""") | |
else: # Auto | |
return gr.HTML(""" | |
<script> | |
// Check system preference | |
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { | |
document.body.classList.add('dark-theme'); | |
document.body.classList.remove('light-theme'); | |
} else { | |
document.body.classList.remove('dark-theme'); | |
document.body.classList.add('light-theme'); | |
} | |
// Listen for system changes | |
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => { | |
if (e.matches) { | |
document.body.classList.add('dark-theme'); | |
document.body.classList.remove('light-theme'); | |
} else { | |
document.body.classList.remove('dark-theme'); | |
document.body.classList.add('light-theme'); | |
} | |
}); | |
</script> | |
""") | |
# Update default settings | |
def update_defaults(model, creation_type, aspect_ratio, show_tips, advanced_mode): | |
"""Update user default settings""" | |
# Extract values | |
model_key = get_model_key_from_display_name(model) | |
creation_key = extract_key(creation_type) | |
aspect_key = extract_key(aspect_ratio) | |
# Update preferences | |
preferences = { | |
"default_model": model_key, | |
"default_creation_type": creation_key, | |
"default_aspect_ratio": aspect_key, | |
"show_tips": show_tips, | |
"advanced_mode": advanced_mode | |
} | |
success = current_user.update_preferences(preferences) | |
if success: | |
return """<div class="status-success"> | |
<span class="icon">✅</span> | |
<span>Default settings saved successfully</span> | |
</div>""" | |
else: | |
return """<div class="status-error"> | |
<span class="icon">❌</span> | |
<span>Failed to save default settings</span> | |
</div>""" | |
# Save defaults button | |
save_defaults_btn.click( | |
fn=update_defaults, | |
inputs=[ | |
default_model, | |
default_creation_type, | |
default_aspect_ratio, | |
show_tips, | |
advanced_mode | |
], | |
outputs=[gr.HTML()] | |
) | |
# Load default values on page load | |
# Load default values on page load | |
interface.load( | |
fn=lambda: ( | |
update_creation_info(f"{CREATION_TYPES['Digital Art']['icon']} Digital Art"), | |
update_art_style_info(f"{ART_STYLES['Photorealistic']['icon']} Photorealistic"), | |
update_model_info(f"{IMAGE_MODELS['stabilityai/stable-diffusion-xl-base-1.0']['icon']} {IMAGE_MODELS['stabilityai/stable-diffusion-xl-base-1.0']['display_name']}"), | |
update_mood_info(f"{MOODS['Peaceful']['icon']} Peaceful"), | |
# Initial theme setting based on user preference | |
update_theme(current_user.preferences["theme"]) | |
), | |
outputs=[creation_info, art_info, model_info, mood_info, gr.Javascript()] | |
) | |
return interface | |
# =============== MAIN APPLICATION FLOW =============== | |
def main(): | |
"""Main application entry point - creates UI and sets up event handlers""" | |
logger.info(f"Starting {APP_CONFIG['name']} v{APP_CONFIG['version']}") | |
# Create the UI components | |
interface = create_ui() | |
# Launch the interface | |
launch_kwargs = {} | |
# If running in Hugging Face Spaces | |
if APP_CONFIG["huggingface_space"]: | |
launch_kwargs.update({ | |
"share": False, | |
"server_name": "0.0.0.0", | |
"server_port": 7860 | |
}) | |
else: | |
# For local development | |
launch_kwargs.update({ | |
"share": False, | |
"server_name": "127.0.0.1", | |
"server_port": 7860 | |
}) | |
try: | |
interface.launch(**launch_kwargs) | |
except Exception as e: | |
logger.error(f"Error launching interface: {str(e)}") | |
interface.launch() # Fallback to default launch | |
# Entry point | |
if __name__ == "__main__": | |
main() |