Spaces:
Running
on
Zero
Running
on
Zero
| import gradio as gr | |
| import torch | |
| from diffusers import DiffusionPipeline | |
| import random | |
| import os | |
| import sys | |
| import time | |
| # Set PyTorch MPS fallback for Apple Silicon compatibility | |
| os.environ['PYTORCH_ENABLE_MPS_FALLBACK'] = '1' | |
| # Check for dev mode | |
| DEV_MODE = "--dev" in sys.argv | |
| # Import spaces for HuggingFace deployment | |
| try: | |
| import spaces | |
| HF_SPACES = True | |
| print("🚀 Running on HuggingFace Spaces with ZeroGPU") | |
| # Optimize for ZeroGPU performance | |
| torch.set_float32_matmul_precision('high') # Enable TensorFloat32 for better performance | |
| torch.backends.cudnn.allow_tf32 = True # Enable TF32 on cuDNN | |
| except ImportError: | |
| HF_SPACES = False | |
| print("🏠 Running locally - spaces module not available") | |
| # MCP is always enabled | |
| print("🔌 MCP protocol enabled - tools available for external access") | |
| MAX_SEED = 2**32 - 1 | |
| # Liste des catégories pour le chatbot (100+ catégories variées) | |
| CHAT_CATEGORIES = [ | |
| # Animaux et créatures | |
| "animal", "bird", "sea creature", "insect", "mythical creature", "prehistoric creature", | |
| # Couleurs et teintes | |
| "color", "shade", "metallic color", "gemstone color", | |
| # Objets et artefacts | |
| "weapon", "tool", "musical instrument", "piece of furniture", "ancient artifact", "modern gadget", | |
| # Émotions et traits | |
| "emotion", "personality trait", "mood", "mental state", "virtue", "flaw", | |
| # Nature et éléments | |
| "natural element", "weather phenomenon", "season", "time of day", "celestial body", "landscape", | |
| # Plantes et végétaux | |
| "flower", "tree", "herb", "fruit", "vegetable", "mushroom", | |
| # Arts et culture | |
| "art style", "musical genre", "dance style", "literary genre", "architectural style", "fashion style", | |
| # Matériaux et textures | |
| "fabric", "metal", "stone", "wood type", "crystal", "texture", | |
| # Géographie et lieux | |
| "country", "city type", "building", "room", "natural landmark", "climate zone", | |
| # Histoire et époques | |
| "historical period", "ancient civilization", "mythology", "legend", "cultural tradition", | |
| # Sciences et cosmos | |
| "planet", "star type", "galaxy", "chemical element", "geometric shape", "mathematical concept", | |
| # Sens et perceptions | |
| "scent", "taste", "sound", "touch sensation", "visual effect", "temperature", | |
| # Énergies et forces | |
| "type of energy", "natural force", "magical power", "spiritual element", "life force", | |
| # Professions et rôles | |
| "profession", "fantasy role", "mythical being", "guardian spirit", "mentor figure", | |
| # Activités et actions | |
| "hobby", "sport", "art form", "ritual", "celebration", "journey type", | |
| # Abstractions et concepts | |
| "philosophical concept", "virtue", "sin", "dream", "fear", "hope", "memory type", | |
| # Objets magiques et fantastiques | |
| "magical item", "enchanted object", "potion ingredient", "spell component", "rune", | |
| # Environnements spéciaux | |
| "mystical place", "hidden realm", "sacred space", "forbidden zone", "lost city" | |
| ] | |
| # Variable globale pour tracking des catégories utilisées dans la conversation | |
| used_categories = [] | |
| def get_next_category(): | |
| """Retourne une catégorie aléatoire non utilisée""" | |
| available_categories = [cat for cat in CHAT_CATEGORIES if cat not in used_categories] | |
| if not available_categories: | |
| # Si toutes les catégories ont été utilisées, reset | |
| used_categories.clear() | |
| available_categories = CHAT_CATEGORIES.copy() | |
| category = random.choice(available_categories) | |
| used_categories.append(category) | |
| return category | |
| def simple_chat_response(user_message, history): | |
| """Logique simple de chat sans LLM - pose juste la question suivante""" | |
| if not user_message.strip(): | |
| return "Please type your answer." | |
| # Si c'est le début de la conversation | |
| if len(history) == 0 or user_message.lower().strip() in ["ready", "start", "begin"]: | |
| used_categories.clear() # Reset les catégories | |
| category = get_next_category() | |
| return f"If you were {category}, what would you be?" | |
| # Sinon, poser la question suivante | |
| category = get_next_category() | |
| return f"If you were {category}, what would you be?" | |
| def load_flux_model(): | |
| dtype = torch.bfloat16 | |
| # For HuggingFace Spaces, prioritize CUDA | |
| if HF_SPACES and torch.cuda.is_available(): | |
| device = "cuda" | |
| # For local development, prioritize MPS for Apple Silicon | |
| elif torch.backends.mps.is_available(): | |
| device = "mps" | |
| elif torch.cuda.is_available(): | |
| device = "cuda" | |
| else: | |
| device = "cpu" | |
| print(f"Using device for FLUX: {device}") | |
| pipe = DiffusionPipeline.from_pretrained( | |
| "black-forest-labs/FLUX.1-schnell", | |
| torch_dtype=dtype | |
| ).to(device) | |
| return pipe | |
| flux_pipe = load_flux_model() | |
| def generate_simple_flux_prompt(user_responses): | |
| """Generate simple FLUX prompt by concatenating user responses""" | |
| # Extraire seulement les réponses utilisateur (pas les "si j'étais") | |
| responses = [response.strip() for response in user_responses if response.strip()] | |
| # Concatener avec des virgules | |
| if responses: | |
| concatenated = ", ".join(responses) | |
| return f"digital portrait with the following criteria: {concatenated}" | |
| else: | |
| return "digital portrait with the following criteria: artistic avatar" | |
| # Multilingual support | |
| def get_translations(): | |
| return { | |
| "en": { | |
| "title": "🎭 Avatar Generator - Chinese Portrait", | |
| "subtitle": "Complete at least the first 3 groups to generate your personalized avatar.", | |
| "portrait_title": "📝 Chinese Portrait (first 3 groups required)", | |
| "group": "Group", | |
| "required": "Required", | |
| "optional": "Optional", | |
| "if_i_was": "If I was", | |
| "i_would_be": "I would be", | |
| "generate_btn": "🎨 Generate Avatar", | |
| "avatar_title": "🖼️ Generated Avatar", | |
| "your_avatar": "Your Avatar", | |
| "information": "Information", | |
| "error_required": "Error: The first 3 groups of fields are required.", | |
| "success": "Avatar generated successfully!", | |
| "prompt_used": "Prompt used:", | |
| "error_generation": "Error during generation:", | |
| "footer": "Avatar generated with FLUX.1-schnell", | |
| "quality_normal": "Normal Quality (4 steps, 512x512)", | |
| "quality_high": "High Quality (8 steps, 512x512)", | |
| "quality_label": "Quality:", | |
| "tab_form": "📝 Form Mode", | |
| "tab_chat": "💬 Chat Mode", | |
| "chat_title": "🤖 AI Assistant - Avatar Creator", | |
| "chat_subtitle": "Let me guide you through creating your Chinese portrait!", | |
| "thinking": "Thinking...", | |
| "placeholders": { | |
| "animal": "an animal...", | |
| "animal_answer": "a lion...", | |
| "color": "a color...", | |
| "color_answer": "red...", | |
| "object": "an object...", | |
| "object_answer": "a sword...", | |
| "feeling": "a feeling...", | |
| "feeling_answer": "joy...", | |
| "element": "an element...", | |
| "element_answer": "fire..." | |
| } | |
| }, | |
| "fr": { | |
| "title": "🎭 Générateur d'Avatar - Portrait Chinois", | |
| "subtitle": "Complétez au minimum les 3 premiers groupes pour générer votre avatar personnalisé.", | |
| "portrait_title": "📝 Portrait Chinois (3 premiers groupes obligatoires)", | |
| "group": "Groupe", | |
| "required": "Obligatoire", | |
| "optional": "Optionnel", | |
| "if_i_was": "Si j'étais", | |
| "i_would_be": "Je serais", | |
| "generate_btn": "🎨 Générer l'Avatar", | |
| "avatar_title": "🖼️ Avatar Généré", | |
| "your_avatar": "Votre Avatar", | |
| "information": "Informations", | |
| "error_required": "Erreur: Les 3 premiers groupes de champs sont obligatoires.", | |
| "success": "Avatar généré avec succès!", | |
| "prompt_used": "Prompt utilisé:", | |
| "error_generation": "Erreur lors de la génération:", | |
| "footer": "Avatar généré avec FLUX.1-schnell", | |
| "quality_normal": "Qualité Normale (4 étapes, 512x512)", | |
| "quality_high": "Haute Qualité (8 étapes, 512x512)", | |
| "quality_label": "Qualité:", | |
| "tab_form": "📝 Mode Formulaire", | |
| "tab_chat": "💬 Mode Chat", | |
| "chat_title": "🤖 Assistant IA - Créateur d'Avatar", | |
| "chat_subtitle": "Laissez-moi vous guider pour créer votre portrait chinois!", | |
| "thinking": "Réflexion...", | |
| "placeholders": { | |
| "animal": "un animal...", | |
| "animal_answer": "un lion...", | |
| "color": "une couleur...", | |
| "color_answer": "rouge...", | |
| "object": "un objet...", | |
| "object_answer": "une épée...", | |
| "feeling": "un sentiment...", | |
| "feeling_answer": "la joie...", | |
| "element": "un élément...", | |
| "element_answer": "le feu..." | |
| } | |
| } | |
| } | |
| # Dev mode default values | |
| def get_dev_defaults(): | |
| return { | |
| "if1": "an animal", "would1": "a majestic wolf", | |
| "if2": "a color", "would2": "deep purple", | |
| "if3": "an object", "would3": "an ancient sword", | |
| "if4": "a feeling", "would4": "fierce determination", | |
| "if5": "an element", "would5": "lightning" | |
| } | |
| # Apply ZeroGPU decorator if available | |
| if HF_SPACES: | |
| def generate_avatar(if1: str, would1: str, if2: str, would2: str, if3: str, would3: str, if4: str = "", would4: str = "", if5: str = "", would5: str = "", language: str = "en", quality: str = "normal"): | |
| return _generate_avatar_impl(if1, would1, if2, would2, if3, would3, if4, would4, if5, would5, language, quality) | |
| else: | |
| def generate_avatar(if1: str, would1: str, if2: str, would2: str, if3: str, would3: str, if4: str = "", would4: str = "", if5: str = "", would5: str = "", language: str = "en", quality: str = "normal"): | |
| return _generate_avatar_impl(if1, would1, if2, would2, if3, would3, if4, would4, if5, would5, language, quality) | |
| def _generate_avatar_impl(if1, would1, if2, would2, if3, would3, if4, would4, if5, would5, language, quality): | |
| translations = get_translations() | |
| t = translations.get(language, translations["en"]) | |
| # Validation des champs obligatoires | |
| if not if1 or not would1 or not if2 or not would2 or not if3 or not would3: | |
| return None, t["error_required"] | |
| # Collecter toutes les réponses utilisateur | |
| user_responses = [would1, would2, would3] | |
| if would4: | |
| user_responses.append(would4) | |
| if would5: | |
| user_responses.append(would5) | |
| # Générer le prompt simple | |
| prompt = generate_simple_flux_prompt(user_responses) | |
| try: | |
| # Configuration selon la qualité | |
| if quality == "high": | |
| width, height, steps = 512, 512, 8 | |
| else: | |
| width, height, steps = 512, 512, 4 | |
| # Génération avec seed aléatoire | |
| seed = random.randint(0, MAX_SEED) | |
| generator = torch.Generator(device=flux_pipe.device).manual_seed(seed) | |
| image = flux_pipe( | |
| prompt=prompt, | |
| width=width, | |
| height=height, | |
| num_inference_steps=steps, | |
| guidance_scale=0.0, | |
| generator=generator | |
| ).images[0] | |
| return image, f"{t['success']}\n{t['prompt_used']} {prompt}\nSeed: {seed}\nQuality: {quality} ({steps} steps, {width}x{height})" | |
| except Exception as e: | |
| return None, f"{t['error_generation']} {str(e)}" | |
| def generate_avatar_from_chat(history: list, language: str = "en", quality: str = "normal"): | |
| """ | |
| Generate avatar from conversation history with AI assistant. | |
| """ | |
| # Extraire directement les réponses utilisateur de la conversation | |
| user_responses = [] | |
| for user_msg, assistant_msg in history: | |
| if user_msg and user_msg.strip() and not user_msg.lower().strip() in ["ready", "start", "begin", "let's start the chinese portrait game!"]: | |
| # Ajouter la réponse de l'utilisateur | |
| user_responses.append(user_msg.strip()) | |
| # Générer le prompt simple | |
| prompt = generate_simple_flux_prompt(user_responses) | |
| try: | |
| # Configuration selon la qualité | |
| if quality == "high": | |
| width, height, steps = 512, 512, 8 | |
| else: | |
| width, height, steps = 512, 512, 4 | |
| # Génération avec seed aléatoire | |
| seed = random.randint(0, MAX_SEED) | |
| generator = torch.Generator(device=flux_pipe.device).manual_seed(seed) | |
| image = flux_pipe( | |
| prompt=prompt, | |
| width=width, | |
| height=height, | |
| num_inference_steps=steps, | |
| guidance_scale=0.0, | |
| generator=generator | |
| ).images[0] | |
| responses_text = "\n".join([f"- {response}" for response in user_responses]) | |
| return image, f"Avatar generated from conversation!\n\nUser responses:\n{responses_text}\n\nPrompt: {prompt}\nSeed: {seed}\nQuality: {quality} ({steps} steps, {width}x{height})" | |
| except Exception as e: | |
| return None, f"Error during generation: {str(e)}" | |
| def create_form_interface(language="en"): | |
| translations = get_translations() | |
| t = translations.get(language, translations["en"]) | |
| dev_defaults = get_dev_defaults() if DEV_MODE else {} | |
| with gr.Column() as form_interface: | |
| gr.Markdown(f"### {t['portrait_title']}") | |
| # Commutateur de qualité | |
| quality_radio = gr.Radio( | |
| choices=["normal", "high"], | |
| value="normal", | |
| label=t["quality_label"] | |
| ) | |
| # Groupe 1 (obligatoire) | |
| gr.Markdown(f"**{t['group']} 1** ⭐ *{t['required']}*") | |
| with gr.Row(): | |
| if1 = gr.Textbox(label=t["if_i_was"], placeholder=t["placeholders"]["animal"], | |
| value=dev_defaults.get("if1", ""), scale=1) | |
| would1 = gr.Textbox(label=t["i_would_be"], placeholder=t["placeholders"]["animal_answer"], | |
| value=dev_defaults.get("would1", ""), scale=1) | |
| # Groupe 2 (obligatoire) | |
| gr.Markdown(f"**{t['group']} 2** ⭐ *{t['required']}*") | |
| with gr.Row(): | |
| if2 = gr.Textbox(label=t["if_i_was"], placeholder=t["placeholders"]["color"], | |
| value=dev_defaults.get("if2", ""), scale=1) | |
| would2 = gr.Textbox(label=t["i_would_be"], placeholder=t["placeholders"]["color_answer"], | |
| value=dev_defaults.get("would2", ""), scale=1) | |
| # Groupe 3 (obligatoire) | |
| gr.Markdown(f"**{t['group']} 3** ⭐ *{t['required']}*") | |
| with gr.Row(): | |
| if3 = gr.Textbox(label=t["if_i_was"], placeholder=t["placeholders"]["object"], | |
| value=dev_defaults.get("if3", ""), scale=1) | |
| would3 = gr.Textbox(label=t["i_would_be"], placeholder=t["placeholders"]["object_answer"], | |
| value=dev_defaults.get("would3", ""), scale=1) | |
| # Groupe 4 (optionnel) | |
| gr.Markdown(f"**{t['group']} 4** ✨ *{t['optional']}*") | |
| with gr.Row(): | |
| if4 = gr.Textbox(label=t["if_i_was"], placeholder=t["placeholders"]["feeling"], | |
| value=dev_defaults.get("if4", ""), scale=1) | |
| would4 = gr.Textbox(label=t["i_would_be"], placeholder=t["placeholders"]["feeling_answer"], | |
| value=dev_defaults.get("would4", ""), scale=1) | |
| # Groupe 5 (optionnel) | |
| gr.Markdown(f"**{t['group']} 5** ✨ *{t['optional']}*") | |
| with gr.Row(): | |
| if5 = gr.Textbox(label=t["if_i_was"], placeholder=t["placeholders"]["element"], | |
| value=dev_defaults.get("if5", ""), scale=1) | |
| would5 = gr.Textbox(label=t["i_would_be"], placeholder=t["placeholders"]["element_answer"], | |
| value=dev_defaults.get("would5", ""), scale=1) | |
| generate_btn = gr.Button(t["generate_btn"], variant="primary", size="lg") | |
| gr.Markdown(f"### {t['avatar_title']}") | |
| output_image = gr.Image(label=t["your_avatar"], height=400) | |
| output_text = gr.Textbox(label=t["information"], lines=4, interactive=False) | |
| # Hidden state for language | |
| lang_state = gr.State(value=language) | |
| generate_btn.click( | |
| fn=generate_avatar, | |
| inputs=[if1, would1, if2, would2, if3, would3, if4, would4, if5, would5, lang_state, quality_radio], | |
| outputs=[output_image, output_text] | |
| ) | |
| return form_interface | |
| def create_chat_interface(language="en"): | |
| translations = get_translations() | |
| t = translations.get(language, translations["en"]) | |
| with gr.Column() as chat_interface: | |
| gr.Markdown(f"### {t['chat_title']}") | |
| gr.Markdown(t["chat_subtitle"]) | |
| chatbot = gr.Chatbot(height=400, show_copy_button=True) | |
| # Zone de message avec bouton d'envoi | |
| with gr.Row(): | |
| msg = gr.Textbox(label="Message", placeholder="Type your response here...", visible=False, scale=4) | |
| send_btn = gr.Button("📤", visible=False, scale=1, min_width=50) | |
| # Boutons de contrôle - en dessous du chat | |
| with gr.Row(): | |
| start_btn = gr.Button("🚀 Start New Conversation", variant="primary", scale=1) | |
| avatar_btn = gr.Button("🎨 Get My Avatar", variant="secondary", scale=1) | |
| quality_chat = gr.Radio(choices=["normal", "high"], value="normal", label="Quality", scale=1) | |
| # Résultats de génération d'avatar | |
| avatar_output = gr.Image(label="Generated Avatar", visible=False) | |
| avatar_info = gr.Textbox(label="Avatar Info", lines=4, interactive=False, visible=False) | |
| # Hidden state for language | |
| lang_state = gr.State(value=language) | |
| def respond(message: str, history: list, language: str = "en"): | |
| """ | |
| Process user message and generate simple response using get_next_category(). | |
| """ | |
| # Convert history format if needed | |
| if history is None: | |
| history = [] | |
| # Use simple chat logic instead of Gemma | |
| response = simple_chat_response(message, history) | |
| # Update history with user message and bot response | |
| updated_history = history + [[message, response]] | |
| # Yield the updated history (no streaming needed for simple logic) | |
| yield "", updated_history | |
| def start_conversation(language): | |
| """Démarre la conversation avec une question simple sans LLM""" | |
| used_categories.clear() # Reset les catégories | |
| # Générer la première question directement | |
| first_category = get_next_category() | |
| first_question = f"If you were {first_category}, what would you be?" | |
| # Créer l'historique initial | |
| initial_history = [["Let's start the Chinese Portrait game!", first_question]] | |
| return initial_history, gr.update(visible=True), gr.update(visible=True), gr.update(visible=False), gr.update(visible=False) | |
| def show_avatar_interface(): | |
| """Affiche immédiatement l'interface avatar pour montrer que ça calcule""" | |
| return gr.update(visible=True), gr.update(visible=True, value="Generating your avatar...") | |
| def generate_avatar_from_conversation(history, language, quality): | |
| if not history: | |
| return None, "No conversation found. Please start a conversation first." | |
| image, info = generate_avatar_from_chat(history, language, quality) | |
| return image, info | |
| # Événements | |
| start_btn.click( | |
| fn=start_conversation, | |
| inputs=[lang_state], | |
| outputs=[chatbot, msg, send_btn, avatar_output, avatar_info] | |
| ) | |
| # Envoi via Enter ou bouton | |
| msg.submit( | |
| respond, | |
| [msg, chatbot, lang_state], | |
| [msg, chatbot], | |
| queue=True | |
| ) | |
| send_btn.click( | |
| respond, | |
| [msg, chatbot, lang_state], | |
| [msg, chatbot], | |
| queue=True | |
| ) | |
| # Affichage immédiat de l'interface puis génération | |
| avatar_btn.click( | |
| show_avatar_interface, | |
| outputs=[avatar_output, avatar_info] | |
| ).then( | |
| generate_avatar_from_conversation, | |
| inputs=[chatbot, lang_state, quality_chat], | |
| outputs=[avatar_output, avatar_info] | |
| ) | |
| gr.Markdown("*Click 'Start New Conversation' to begin, then 'Get My Avatar' when you've completed your portrait!*") | |
| return chat_interface | |
| # Create the main web interface with MCP tools integrated | |
| with gr.Blocks(title="🎭 Avatar Generator") as demo: | |
| gr.Markdown("# 🎭 Avatar Generator - Chinese Portrait") | |
| gr.Markdown("Generate personalized avatars from Chinese portrait descriptions using FLUX.1-schnell") | |
| with gr.Tabs(): | |
| # Main application tabs | |
| with gr.Tab("📝 Form Mode"): | |
| create_form_interface("en") | |
| with gr.Tab("💬 Chat Mode"): | |
| create_chat_interface("en") | |
| gr.Markdown("---") | |
| gr.Markdown("🔌 **MCP Integration**: This app exposes tools via MCP protocol at `/gradio_api/mcp/sse`") | |
| gr.Markdown("*Avatar generated with FLUX.1-schnell*") | |
| if __name__ == "__main__": | |
| if DEV_MODE: | |
| print("🚀 Running in DEV MODE with pre-filled values") | |
| print("🔌 Starting server with MCP support...") | |
| print("📡 MCP endpoint available at: http://localhost:7860/gradio_api/mcp/sse") | |
| print("🌐 Web interface available at: http://localhost:7860") | |
| demo.launch(mcp_server=True, show_api=True) |