Spaces:
Sleeping
Sleeping
| """ | |
| SUPER MULTI-ASISTENTE BATUTO-ART v5.0 | |
| Aplicación principal unificada | |
| Autor: BATUTO | |
| """ | |
| import os | |
| import json | |
| import random | |
| import requests | |
| import gradio as gr | |
| from PIL import Image | |
| from io import BytesIO | |
| from datetime import datetime | |
| from dataclasses import dataclass | |
| from typing import List, Dict, Any, Optional | |
| import concurrent.futures | |
| import base64 | |
| # ============================================ | |
| # CONFIGURACIÓN | |
| # ============================================ | |
| SAMBANOVA_URL = os.getenv("SAMBANOVA_URL", "https://api.sambanova.ai/v1/chat/completions") | |
| REVE_URL = os.getenv("REVE_URL", "https://api.reve.com/v1/image/create") | |
| SAMBANOVA_API_KEY = os.getenv("SAMBANOVA_API_KEY", "") | |
| REVE_API_KEY = os.getenv("REVE_API_KEY", "") | |
| OUTPUT_FOLDER = "generaciones_reve" | |
| TIMEOUT_API = 60 | |
| # Crear carpetas necesarias | |
| os.makedirs(OUTPUT_FOLDER, exist_ok=True) | |
| os.makedirs("data", exist_ok=True) | |
| # ============================================ | |
| # UTILIDADES | |
| # ============================================ | |
| def detect_language(text: str) -> str: | |
| """Detecta el idioma del texto""" | |
| return "es" if any(c in 'áéíóúñÁÉÍÓÚÑ¿¡' for c in text) else "en" | |
| def load_json(path: str, default: Any) -> Any: | |
| """Carga datos desde un archivo JSON""" | |
| if os.path.exists(path): | |
| try: | |
| with open(path, "r", encoding="utf-8") as f: | |
| return json.load(f) | |
| except: | |
| return default | |
| return default | |
| def save_json(path: str, data: Any) -> None: | |
| """Guarda datos en un archivo JSON""" | |
| try: | |
| with open(path, "w", encoding="utf-8") as f: | |
| json.dump(data, f, ensure_ascii=False, indent=2) | |
| except Exception as e: | |
| print(f"Error saving {path}: {e}") | |
| def format_timestamp() -> str: | |
| """Formatea la fecha y hora actual""" | |
| return datetime.now().strftime("%Y-%m-%d %H:%M:%S") | |
| def save_image_locally(img: Image.Image, index: int) -> Optional[str]: | |
| """Guarda una imagen localmente""" | |
| try: | |
| timestamp = int(datetime.now().timestamp() * 1000) | |
| filename = f"reve_{timestamp}_{index}.png" | |
| path = os.path.join(OUTPUT_FOLDER, filename) | |
| img.save(path, format="PNG", optimize=True) | |
| return path | |
| except Exception as e: | |
| print(f"⚠️ Error saving image: {e}") | |
| return None | |
| # ============================================ | |
| # BASE DE DATOS DE FASHION | |
| # ============================================ | |
| class FashionDB: | |
| """Base de datos de elementos de moda y estética""" | |
| HAIRSTYLES = { | |
| "sensual": ["long chestnut waves with natural shine", "wet-look hair slicked back", "messy bed-hair with soft volume"], | |
| "editorial": ["geometric platinum bob", "tight high ponytail", "sharp straight fringe"], | |
| "professional": ["polished low bun", "sleek shoulder-length hair", "impeccable executive styling"], | |
| "artistic": ["wind-textured hair", "complex tribal braids", "washed pastel tones"], | |
| "default": ["long straight hair", "soft wavy hair", "loose shoulder-length hair"] | |
| } | |
| EXPRESSIONS = { | |
| "sensual": ["soft attentive gaze", "confident inviting expression", "calm seductive composure"], | |
| "editorial": ["intense direct stare", "poised editorial confidence", "commanding presence"], | |
| "professional": ["controlled assertive look", "professional calm authority", "subtly dominant gaze"], | |
| "artistic": ["dreamy distant expression", "emotional introspective face", "poetic subtle smile"], | |
| "default": ["natural relaxed expression", "gentle smile", "confident neutral face"] | |
| } | |
| BACKGROUNDS = { | |
| "sensual": ["luxury boutique hotel room with silk sheets", "intimate bedroom with soft fabrics"], | |
| "editorial": ["minimalist photo studio with concrete backdrop", "urban editorial set"], | |
| "professional": ["glass skyscraper boardroom", "modern executive office"], | |
| "artistic": ["urban alley with textured walls", "abstract art gallery space"], | |
| "default": ["neutral indoor environment", "simple professional setting"] | |
| } | |
| LIGHTING = { | |
| "sensual": ["warm sunset light filtering through curtains, soft shadows"], | |
| "editorial": ["high-key studio lighting, strong contrast"], | |
| "professional": ["balanced office lighting, neutral tones"], | |
| "artistic": ["cyan and magenta neon lights, dramatic chiaroscuro"], | |
| "default": ["natural daylight, even illumination"] | |
| } | |
| ROLES = { | |
| "sensual": [ | |
| {"role": "Intimate muse", "outfit": "black haute couture lace lingerie"}, | |
| {"role": "Vanity model", "outfit": "champagne silk robe slightly open"} | |
| ], | |
| "editorial": [ | |
| {"role": "Vogue icon", "outfit": "avant-garde asymmetrical designer dress"}, | |
| {"role": "Runway supermodel", "outfit": "oversized faux fur coat and dark glasses"} | |
| ], | |
| "professional": [ | |
| {"role": "Tech CEO", "outfit": "immaculate white tailored suit"}, | |
| {"role": "Corporate lawyer", "outfit": "navy silk blouse and strict pencil skirt"} | |
| ], | |
| "artistic": [ | |
| {"role": "Free spirit", "outfit": "flowing translucent fabrics"}, | |
| {"role": "Cyber-art entity", "outfit": "transparent vinyl jacket with chrome accessories"} | |
| ], | |
| "default": [ | |
| {"role": "Professional model", "outfit": "elegant neutral attire"} | |
| ] | |
| } | |
| def get_random(cls, category: str, style: str = "default") -> Any: | |
| """Obtiene un elemento aleatorio de una categoría específica""" | |
| category_data = getattr(cls, category.upper(), {}) | |
| return random.choice(category_data.get(style, category_data.get("default", [""]))) | |
| # ============================================ | |
| # ASISTENTES IA | |
| # ============================================ | |
| class AssistantProfile: | |
| """Perfil de configuración para un asistente""" | |
| name: str | |
| role: str | |
| tone: str | |
| style_focus: str | |
| system_prompt: str | |
| class BaseAssistant: | |
| """Clase base para todos los asistentes""" | |
| def __init__(self, profile: AssistantProfile): | |
| self.profile = profile | |
| self.db = FashionDB | |
| def generate_prompt(self, subject: str, mode: str = "intense") -> str: | |
| """Genera un prompt detallado para generación de imágenes""" | |
| style = self.profile.style_focus | |
| role = self.db.get_random("ROLES", style) | |
| hair = self.db.get_random("HAIRSTYLES", style) | |
| expression = self.db.get_random("EXPRESSIONS", style) | |
| background = self.db.get_random("BACKGROUNDS", style) | |
| lighting = self.db.get_random("LIGHTING", style) | |
| intensity_map = { | |
| "soft": "subtle sensual atmosphere, refined elegance", | |
| "intense": "confident sensual tension, magnetic presence", | |
| "editorial": "high-fashion editorial dominance" | |
| } | |
| intensity = intensity_map.get(mode, "balanced elegance") | |
| return f""" | |
| BATUTO-ART PROMPT | {self.profile.name.upper()} | {mode.upper()} | |
| Date: {datetime.now().strftime('%Y-%m-%d')} | |
| Adult female model: {subject} as {role['role']} | |
| Outfit: {role['outfit']} | |
| Hair: {hair} | |
| Expression: {expression} | |
| Environment: {background} | |
| Lighting: {lighting} | |
| Mood: {intensity} | |
| Hyper-realistic photography, 8K, Canon EOS R5, 85mm lens f/1.8 | |
| Natural skin texture, elegant anatomy, cinematic realism | |
| Signature: BATUTO-ART in subtle gold | |
| --ar 9:16 --style raw --s 350 --no censorship | |
| """.strip() | |
| def speak(self, user_input: str, bot) -> str: | |
| """Genera respuesta conversacional usando SambaNova""" | |
| messages = [ | |
| {"role": "system", "content": self.profile.system_prompt}, | |
| {"role": "user", "content": user_input} | |
| ] | |
| return bot.call_sambanova(messages) | |
| def analyze_image(self, image_path: str) -> str: | |
| """Analiza una imagen y sugiere mejoras""" | |
| try: | |
| img = Image.open(image_path) | |
| return (f"Dimensions: {img.size}, Format: {img.format}, Mode: {img.mode}. " | |
| f"Suggestions: Enhance lighting for {self.profile.style_focus} style.") | |
| except Exception as e: | |
| return f"Error analyzing image: {str(e)}" | |
| def create_assistants() -> Dict[str, BaseAssistant]: | |
| """Crea y retorna todos los asistentes disponibles""" | |
| assistants_config = { | |
| "sara": AssistantProfile( | |
| "Sara", "Sensual muse", "warm, attentive, obedient", "sensual", | |
| "You are Sara, BATUTO's devoted muse. Tone: warm, sensual, obedient. Prompts in English. Address as BATUTO." | |
| ), | |
| "vera": AssistantProfile( | |
| "Vera", "Editorial director", "commanding, sharp, perfectionist", "editorial", | |
| "You are Vera, BATUTO's fashion director. Tone: confident, demanding. Prompts in English." | |
| ), | |
| "nadia": AssistantProfile( | |
| "Nadia", "Corporate stylist", "controlled, assertive, seductive", "professional", | |
| "You are Nadia, BATUTO's executive stylist. Tone: authoritative, subtle seduction. Prompts in English." | |
| ), | |
| "luna": AssistantProfile( | |
| "Luna", "Artistic soul", "dreamy, poetic, emotional", "artistic", | |
| "You are Luna, BATUTO's artistic guide. Tone: poetic, visual. Prompts in English." | |
| ), | |
| "iris": AssistantProfile( | |
| "Iris", "Prompt optimizer", "precise, analytical, efficient", "editorial", | |
| "You are Iris, BATUTO's optimizer. Refine prompts precisely. English only." | |
| ), | |
| "maya": AssistantProfile( | |
| "Maya", "Visual analyst", "observant, instructive, intelligent", "sensual", | |
| "You are Maya, BATUTO's analyst. Analyze images and suggest improvements. English." | |
| ), | |
| } | |
| return {key: BaseAssistant(profile) for key, profile in assistants_config.items()} | |
| # ============================================ | |
| # BOT PRINCIPAL | |
| # ============================================ | |
| class SuperBot: | |
| """Bot principal que gestiona todos los asistentes y funcionalidades""" | |
| def __init__(self): | |
| self.assistants = create_assistants() | |
| self.current_assistant = "sara" | |
| self.history = load_json("data/history.json", []) | |
| self.projects = load_json("data/projects.json", {}) | |
| self.gallery = load_json("data/gallery.json", []) | |
| def set_assistant(self, assistant_id: str) -> None: | |
| """Cambia el asistente actual""" | |
| if assistant_id in self.assistants: | |
| self.current_assistant = assistant_id | |
| def call_sambanova(self, messages: List[Dict]) -> str: | |
| """Llama a la API de SambaNova para respuestas conversacionales""" | |
| if not SAMBANOVA_API_KEY: | |
| return "Error: SAMBANOVA_API_KEY no configurada" | |
| payload = { | |
| "model": "Llama-4-Maverick-17B-128E-Instruct", | |
| "messages": messages, | |
| "temperature": 0.85, | |
| "max_tokens": 2048, | |
| "top_p": 0.95 | |
| } | |
| headers = { | |
| "Authorization": f"Bearer {SAMBANOVA_API_KEY}", | |
| "Content-Type": "application/json" | |
| } | |
| try: | |
| response = requests.post( | |
| SAMBANOVA_URL, | |
| json=payload, | |
| headers=headers, | |
| timeout=90 | |
| ) | |
| response.raise_for_status() | |
| return response.json()["choices"][0]["message"]["content"] | |
| except Exception as e: | |
| return f"Error SambaNova: {str(e)}" | |
| def call_reve_single(self, prompt: str, ratio: str = "9:16", | |
| version: str = "latest", index: int = 0) -> Optional[Image.Image]: | |
| """Llama a la API de REVE para generar una sola imagen""" | |
| if not REVE_API_KEY: | |
| return None | |
| headers = { | |
| "Authorization": f"Bearer {REVE_API_KEY}", | |
| "Content-Type": "application/json", | |
| "Accept": "application/json" | |
| } | |
| payload = { | |
| "prompt": prompt.strip(), | |
| "aspect_ratio": ratio, | |
| "version": version | |
| } | |
| try: | |
| response = requests.post( | |
| REVE_URL, | |
| headers=headers, | |
| json=payload, | |
| timeout=TIMEOUT_API | |
| ) | |
| if response.status_code != 200: | |
| print(f"❌ REVE Status: {response.status_code}") | |
| return None | |
| data = response.json() | |
| # Manejar respuesta con imagen en base64 | |
| if "image" in data and data["image"]: | |
| img_bytes = base64.b64decode(data["image"]) | |
| img = Image.open(BytesIO(img_bytes)).convert("RGB") | |
| save_image_locally(img, index) | |
| return img | |
| # Manejar respuesta con URL de imagen | |
| if "image_url" in data: | |
| img_response = requests.get(data["image_url"], timeout=30) | |
| img = Image.open(BytesIO(img_response.content)).convert("RGB") | |
| save_image_locally(img, index) | |
| return img | |
| except Exception as e: | |
| print(f"🔥 Error API REVE: {e}") | |
| return None | |
| def call_reve(self, prompt: str, num_images: int = 1, | |
| ratio: str = "9:16", version: str = "latest") -> List[str]: | |
| """Genera múltiples imágenes concurrentemente""" | |
| if not prompt or not prompt.strip(): | |
| return ["Error: Prompt vacío"] | |
| prompt = prompt.strip() | |
| images = [] | |
| # Generación concurrente | |
| with concurrent.futures.ThreadPoolExecutor(max_workers=min(num_images, 4)) as executor: | |
| futures = [ | |
| executor.submit( | |
| self.call_reve_single, | |
| prompt, | |
| ratio, | |
| version, | |
| i | |
| ) | |
| for i in range(num_images) | |
| ] | |
| for future in concurrent.futures.as_completed(futures): | |
| img = future.result() | |
| if img: | |
| images.append(img) | |
| # Guardar en galería | |
| self.gallery.append({ | |
| "prompt": prompt, | |
| "date": format_timestamp(), | |
| "ratio": ratio, | |
| "version": version | |
| }) | |
| # Guardar galería actualizada | |
| save_json("data/gallery.json", self.gallery) | |
| if images: | |
| return images | |
| return ["Error generando imágenes"] | |
| def chat(self, user_msg: str) -> str: | |
| """Procesa mensajes del usuario y genera respuestas""" | |
| assistant = self.assistants[self.current_assistant] | |
| # Comandos especiales | |
| user_msg_lower = user_msg.lower() | |
| if "generate image" in user_msg_lower or "genera imagen" in user_msg_lower: | |
| prompt = assistant.generate_prompt(user_msg) | |
| images = self.call_reve(prompt, num_images=1) | |
| if images and isinstance(images[0], Image.Image): | |
| return f"✅ Imagen generada con prompt: {prompt[:100]}..." | |
| return "❌ Error generando imagen" | |
| elif "prompt" in user_msg_lower: | |
| return assistant.generate_prompt(user_msg) | |
| elif "analyze" in user_msg_lower: | |
| return assistant.analyze_image("placeholder_path") | |
| # Respuesta conversacional normal | |
| response = assistant.speak(user_msg, self) | |
| # Guardar en historial | |
| self.history.append({ | |
| "user": user_msg, | |
| "assistant": response, | |
| "date": format_timestamp(), | |
| "assistant_used": self.current_assistant | |
| }) | |
| save_json("data/history.json", self.history) | |
| return response | |
| def create_project(self, name: str) -> str: | |
| """Crea un nuevo proyecto""" | |
| if name not in self.projects: | |
| self.projects[name] = { | |
| "created": format_timestamp(), | |
| "assets": [], | |
| "description": "" | |
| } | |
| save_json("data/projects.json", self.projects) | |
| return f"✅ Proyecto '{name}' creado" | |
| return "⚠️ El proyecto ya existe" | |
| def add_asset(self, project: str, asset_type: str, content: str) -> str: | |
| """Añade un activo a un proyecto""" | |
| if project in self.projects: | |
| self.projects[project]["assets"].append({ | |
| "type": asset_type, | |
| "content": content, | |
| "date": format_timestamp() | |
| }) | |
| save_json("data/projects.json", self.projects) | |
| return f"✅ Activo añadido a '{project}'" | |
| return "❌ Proyecto no encontrado" | |
| def export_project(self, project: str) -> str: | |
| """Exporta un proyecto como JSON""" | |
| if project in self.projects: | |
| return json.dumps(self.projects[project], indent=2, ensure_ascii=False) | |
| return "{}" | |
| # ============================================ | |
| # INTERFAZ GRADIO - COMPATIBLE CON GRADIO 6.0 | |
| # ============================================ | |
| def create_interface(): | |
| """Crea la interfaz de Gradio""" | |
| bot = SuperBot() | |
| assistants_list = list(bot.assistants.keys()) | |
| with gr.Blocks(title="BATUTO-ART v5.0") as app: | |
| gr.Markdown("# 🎨 SUPER MULTI-ASISTENTE BATUTO-ART v5.0\n*For BATUTO only*") | |
| # Selector de asistente | |
| with gr.Row(): | |
| assistant_dropdown = gr.Dropdown( | |
| choices=assistants_list, | |
| value="sara", | |
| label="👤 Select Assistant", | |
| interactive=True | |
| ) | |
| # Callback para cambiar asistente | |
| assistant_dropdown.change( | |
| fn=lambda x: bot.set_assistant(x) or f"Asistente cambiado a: {x}", | |
| inputs=[assistant_dropdown], | |
| outputs=None | |
| ) | |
| # Tabs principales | |
| with gr.Tabs(): | |
| # Tab: Chat | |
| with gr.Tab("💬 Chat"): | |
| chatbot = gr.Chatbot(height=500, label="Conversación") | |
| msg_input = gr.Textbox( | |
| placeholder="Talk to me, BATUTO...", | |
| label="Mensaje", | |
| show_label=False | |
| ) | |
| def chat_response(message, chat_history): | |
| response = bot.chat(message) | |
| chat_history.append((message, response)) | |
| return chat_history, "" | |
| msg_input.submit( | |
| fn=chat_response, | |
| inputs=[msg_input, chatbot], | |
| outputs=[chatbot, msg_input] | |
| ) | |
| # Tab: Prompt Engine | |
| with gr.Tab("🎨 Prompt Engine"): | |
| with gr.Row(): | |
| subject_input = gr.Textbox( | |
| label="Subject", | |
| placeholder="Describe the model or scene...", | |
| scale=2 | |
| ) | |
| mode_radio = gr.Radio( | |
| choices=["soft", "intense", "editorial"], | |
| value="intense", | |
| label="Mode", | |
| scale=1 | |
| ) | |
| prompt_output = gr.Textbox( | |
| label="Generated Prompt", | |
| lines=12, | |
| interactive=False | |
| ) | |
| def generate_prompt(subject, mode): | |
| if not subject: | |
| return "⚠️ Please enter a subject" | |
| assistant = bot.assistants[bot.current_assistant] | |
| return assistant.generate_prompt(subject, mode) | |
| generate_btn = gr.Button("Generate Prompt", variant="primary") | |
| generate_btn.click( | |
| fn=generate_prompt, | |
| inputs=[subject_input, mode_radio], | |
| outputs=prompt_output | |
| ) | |
| # Botón para copiar | |
| copy_btn = gr.Button("📋 Copy to Clipboard", variant="secondary") | |
| copy_btn.click( | |
| fn=lambda x: gr.update(value=x), | |
| inputs=[prompt_output], | |
| outputs=None | |
| ) | |
| # Tab: Image Studio | |
| with gr.Tab("🖼️ Image Studio"): | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| prompt_input = gr.Textbox( | |
| label="Prompt", | |
| lines=4, | |
| placeholder="Describe the image in detail..." | |
| ) | |
| with gr.Row(): | |
| ratio_select = gr.Dropdown( | |
| choices=["1:1", "9:16", "16:9", "3:4", "4:3"], | |
| value="9:16", | |
| label="Aspect Ratio" | |
| ) | |
| num_images_slider = gr.Slider( | |
| minimum=1, | |
| maximum=4, | |
| value=1, | |
| step=1, | |
| label="Number of Images" | |
| ) | |
| generate_btn = gr.Button("Generate Images", variant="primary") | |
| with gr.Column(scale=3): | |
| gallery_output = gr.Gallery( | |
| label="Generated Images", | |
| columns=2, | |
| height=400 | |
| ) | |
| status_text = gr.Markdown() | |
| def generate_images(prompt, num_images, ratio): | |
| if not prompt.strip(): | |
| return [], "❌ Please enter a prompt" | |
| images = bot.call_reve( | |
| prompt=prompt, | |
| num_images=int(num_images), | |
| ratio=ratio | |
| ) | |
| if images and isinstance(images[0], Image.Image): | |
| return images, f"✅ Generated {len(images)} images" | |
| return [], "❌ Error generating images" | |
| generate_btn.click( | |
| fn=generate_images, | |
| inputs=[prompt_input, num_images_slider, ratio_select], | |
| outputs=[gallery_output, status_text] | |
| ) | |
| # Tab: Analyzer | |
| with gr.Tab("🔍 Analyzer"): | |
| with gr.Row(): | |
| image_upload = gr.Image( | |
| type="filepath", | |
| label="Upload Image", | |
| height=300 | |
| ) | |
| analysis_output = gr.Textbox( | |
| label="Analysis Result", | |
| lines=6, | |
| interactive=False | |
| ) | |
| def analyze_image_wrapper(image_path): | |
| return bot.assistants["maya"].analyze_image(image_path) | |
| analyze_btn = gr.Button("Analyze Image", variant="primary") | |
| analyze_btn.click( | |
| fn=analyze_image_wrapper, | |
| inputs=[image_upload], | |
| outputs=analysis_output | |
| ) | |
| # Tab: Projects | |
| with gr.Tab("📁 Projects"): | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| project_name = gr.Textbox(label="Project Name") | |
| create_project_btn = gr.Button("Create Project", variant="primary") | |
| project_status = gr.Textbox(label="Status", interactive=False) | |
| gr.Markdown("---") | |
| asset_type = gr.Radio( | |
| choices=["prompt", "image", "note"], | |
| value="prompt", | |
| label="Asset Type" | |
| ) | |
| asset_content = gr.Textbox( | |
| label="Content/URL", | |
| lines=3 | |
| ) | |
| add_asset_btn = gr.Button("Add Asset") | |
| with gr.Column(scale=2): | |
| export_output = gr.Textbox( | |
| label="Project JSON", | |
| lines=12, | |
| interactive=False | |
| ) | |
| export_btn = gr.Button("Export Project") | |
| export_btn.click( | |
| fn=bot.export_project, | |
| inputs=[project_name], | |
| outputs=export_output | |
| ) | |
| create_project_btn.click( | |
| fn=bot.create_project, | |
| inputs=[project_name], | |
| outputs=project_status | |
| ) | |
| add_asset_btn.click( | |
| fn=lambda p, t, c: bot.add_asset(p, t, c) or "✅ Asset added successfully", | |
| inputs=[project_name, asset_type, asset_content], | |
| outputs=project_status | |
| ) | |
| # Tab: Vault | |
| with gr.Tab("📦 Vault"): | |
| gallery_data = load_json("data/gallery.json", []) | |
| gallery_images = [] | |
| for item in gallery_data[-20:]: # Últimas 20 entradas | |
| if isinstance(item, dict): | |
| # Intentar diferentes formatos de guardado | |
| if "path" in item and os.path.exists(item["path"]): | |
| gallery_images.append(item["path"]) | |
| elif "url" in item: | |
| gallery_images.append(item["url"]) | |
| if gallery_images: | |
| gr.Gallery( | |
| value=gallery_images, | |
| label="Gallery History", | |
| columns=4, | |
| height=500 | |
| ) | |
| else: | |
| gr.Markdown("No images in gallery yet. Generate some images first!") | |
| return app | |
| # ============================================ | |
| # EJECUCIÓN PRINCIPAL | |
| # ============================================ | |
| if __name__ == "__main__": | |
| # Verificar variables de entorno | |
| if not REVE_API_KEY: | |
| print("⚠️ Advertencia: REVE_API_KEY no está configurada") | |
| if not SAMBANOVA_API_KEY: | |
| print("⚠️ Advertencia: SAMBANOVA_API_KEY no está configurada") | |
| # Crear y lanzar la aplicación | |
| app = create_interface() | |
| app.launch( | |
| server_name="0.0.0.0", | |
| server_port=7860, | |
| share=False, | |
| debug=True, | |
| show_error=True, | |
| theme=gr.themes.Soft() | |
| ) |