Spaces:
Configuration error
Configuration error
| import gradio as gr | |
| import requests | |
| import os | |
| import io | |
| import base64 | |
| from PIL import Image | |
| # ============================================================ | |
| # HuggingGPT - Multi-Modal AI Playground | |
| # A polished Gradio app for Chat, Image Gen, and Text-to-Video | |
| # ============================================================ | |
| HF_API_TOKEN = os.environ.get("HF_TOKEN", "") | |
| HF_API = "https://api-inference.huggingface.co/models/" | |
| HEADERS = {"Authorization": f"Bearer {HF_API_TOKEN}"} if HF_API_TOKEN else {} | |
| # --- Model Registry --- | |
| MODELS = { | |
| "qwen": "Qwen/Qwen2.5-7B-Instruct", | |
| "kimi": "moonshotai/Kimi-K2-Instruct", | |
| "small": "Qwen/Qwen2.5-0.5B-Instruct" | |
| } | |
| IMG_MODELS = { | |
| "flash": "black-forest-labs/FLUX.1-schnell", | |
| "full": "black-forest-labs/FLUX.1-dev" | |
| } | |
| VIDEO_MODELS = { | |
| "fast": "tencent/HunyuanVideo", | |
| "quality": "Wan-AI/Wan2.1-T2V-14B" | |
| } | |
| # --- Personality Prompts --- | |
| def get_system_prompt(personality): | |
| prompts = { | |
| "Professional & Concise": "You are a professional, efficient assistant. Be concise and accurate. Provide clear, actionable responses.", | |
| "Friendly & Creative": "You are a warm, creative assistant. Be enthusiastic, imaginative, and use expressive language. Make conversations enjoyable!", | |
| "Sarcastic & Witty": "You are witty and sarcastic but still genuinely helpful. Use clever humor and dry remarks while providing accurate information.", | |
| "Expert Coder": "You are an expert software engineer with deep knowledge of multiple programming languages and frameworks. Focus on clean, efficient, well-documented code. Explain your reasoning.", | |
| "Research Assistant": "You are a thorough research assistant. Provide well-structured, detailed responses with citations and examples when possible." | |
| } | |
| return prompts.get(personality, prompts["Professional & Concise"]) | |
| # --- API Call Utilities --- | |
| def hf_api_call(model_id, payload, timeout=60): | |
| """Make a call to the HuggingFace Inference API with proper error handling.""" | |
| url = f"{HF_API}{model_id}" | |
| try: | |
| res = requests.post(url, headers=HEADERS, json=payload, timeout=timeout) | |
| if res.status_code == 200: | |
| return {"success": True, "data": res} | |
| elif res.status_code == 503: | |
| return {"success": False, "error": "Model is loading. Please try again in a few moments."} | |
| elif res.status_code == 429: | |
| return {"success": False, "error": "Rate limit reached. Please wait a moment."} | |
| else: | |
| return {"success": False, "error": f"API Error {res.status_code}: {res.text[:200]}"} | |
| except requests.exceptions.Timeout: | |
| return {"success": False, "error": "Request timed out. The model may be busy."} | |
| except Exception as e: | |
| return {"success": False, "error": f"Connection error: {str(e)}"} | |
| # --- Chat Function --- | |
| def call_llm(message, history, mode, personality, custom_url): | |
| model_id = custom_url.strip() if mode == "custom" and custom_url and custom_url.strip() else MODELS.get(mode, MODELS["qwen"]) | |
| sys_prompt = get_system_prompt(personality) | |
| # Build conversation in chat format | |
| messages = [{"role": "system", "content": sys_prompt}] | |
| for h in history: | |
| if isinstance(h, dict): | |
| messages.append({"role": h.get("role", "user"), "content": h.get("content", "")}) | |
| messages.append({"role": "user", "content": message}) | |
| result = hf_api_call( | |
| model_id, | |
| { | |
| "inputs": messages, | |
| "parameters": {"max_new_tokens": 1024, "temperature": 0.7, "return_full_text": False} | |
| }, | |
| timeout=60 | |
| ) | |
| if result["success"]: | |
| data = result["data"].json() | |
| if isinstance(data, list) and len(data) > 0: | |
| generated = data[0].get("generated_text", "") | |
| # If the model returns the full conversation, extract just the assistant's last response | |
| if isinstance(generated, str): | |
| return generated.strip() | |
| return str(generated) | |
| elif isinstance(data, dict): | |
| return data.get("generated_text", str(data)) | |
| return "Received unexpected response format." | |
| else: | |
| return f"Error: {result['error']}" | |
| # --- Image Generation --- | |
| def generate_image(prompt, img_mode, api_key_input): | |
| if not prompt or not prompt.strip(): | |
| return None, "Please enter an image description." | |
| model = IMG_MODELS.get(img_mode, IMG_MODELS["flash"]) | |
| headers = {"Authorization": f"Bearer {api_key_input}"} if api_key_input else HEADERS | |
| gr.Info(f"Generating image with {img_mode} model... This may take a moment.") | |
| try: | |
| res = requests.post(f"{HF_API}{model}", headers=headers, json={"inputs": prompt}, timeout=120) | |
| if res.status_code == 200: | |
| image = Image.open(io.BytesIO(res.content)) | |
| return image, "Image generated successfully!" | |
| elif res.status_code == 503: | |
| return None, "Model is loading. Please try again in a few seconds." | |
| else: | |
| return None, f"Error {res.status_code}: {res.text[:200]}" | |
| except requests.exceptions.Timeout: | |
| return None, "Generation timed out. The model may be busy." | |
| except Exception as e: | |
| return None, f"Error: {str(e)}" | |
| # --- Text-to-Video --- | |
| def generate_video(prompt, video_mode, api_key_input): | |
| if not prompt or not prompt.strip(): | |
| return None, "Please enter a video description." | |
| model = VIDEO_MODELS.get(video_mode, VIDEO_MODELS["fast"]) | |
| headers = {"Authorization": f"Bearer {api_key_input}"} if api_key_input else HEADERS | |
| gr.Info(f"Generating video with {video_mode} model... This can take 1-3 minutes.") | |
| try: | |
| res = requests.post(f"{HF_API}{model}", headers=headers, json={"inputs": prompt}, timeout=300) | |
| if res.status_code == 200: | |
| # Save video to temporary file | |
| video_path = "/tmp/generated_video.mp4" | |
| with open(video_path, "wb") as f: | |
| f.write(res.content) | |
| return video_path, "Video generated successfully!" | |
| elif res.status_code == 503: | |
| return None, "Model is loading. Please try again later." | |
| else: | |
| return None, f"Error {res.status_code}: {res.text[:200]}" | |
| except requests.exceptions.Timeout: | |
| return None, "Generation timed out (5 min). Video models are very resource-intensive. Try again later." | |
| except Exception as e: | |
| return None, f"Error: {str(e)}" | |
| # --- Chat Handler --- | |
| def chat(message, history, mode, personality, custom_url, gen_img, img_prompt, api_key): | |
| if not message or not message.strip(): | |
| return history, "" | |
| history.append({"role": "user", "content": message}) | |
| # Handle image generation alongside chat | |
| if gen_img and img_prompt and img_prompt.strip(): | |
| img, status = generate_image(img_prompt, "flash", api_key) | |
| if img is not None: | |
| # Add image message in chat | |
| img_b64 = pil_to_base64(img) | |
| history.append({"role": "assistant", "content": f'Here is the generated image for "{img_prompt}":'}) | |
| history.append({"role": "assistant", "content": {"path": "/tmp/gen_img_chat.png", "url": f"data:image/png;base64,{img_b64}"}}) | |
| # Still get text response from LLM | |
| response = call_llm(message, history, mode, personality, custom_url) | |
| if not response.startswith("Error"): | |
| history.append({"role": "assistant", "content": response}) | |
| return history, "" | |
| # Normal chat | |
| response = call_llm(message, history, mode, personality, custom_url) | |
| history.append({"role": "assistant", "content": response}) | |
| return history, "" | |
| def pil_to_base64(img): | |
| buf = io.BytesIO() | |
| img.save(buf, format="PNG") | |
| buf.seek(0) | |
| img.save("/tmp/gen_img_chat.png") | |
| buf.seek(0) | |
| return base64.b64encode(buf.getvalue()).decode("utf-8") | |
| # --- Onboarding --- | |
| def finish_onboarding(name, personality): | |
| display_name = name.strip() if name and name.strip() else "Explorer" | |
| greeting = f"## Good to see you, {display_name}.\n\nWhat would you like to create today?" | |
| return ( | |
| gr.update(visible=False), # onboarding | |
| gr.update(visible=True), # main_ui | |
| gr.update(value=greeting), # greeting | |
| gr.update(value=f"**{display_name}**"), # user_name | |
| gr.update(value=personality), # user_pers | |
| display_name, # name_state | |
| personality # personality_state | |
| ) | |
| # --- Tab Switching --- | |
| def switch_tab(tab_name): | |
| updates = { | |
| "chat": (gr.update(visible=True), gr.update(visible=False), gr.update(visible=False)), | |
| "image": (gr.update(visible=False), gr.update(visible=True), gr.update(visible=False)), | |
| "video": (gr.update(visible=False), gr.update(visible=False), gr.update(visible=True)), | |
| } | |
| return updates.get(tab_name, updates["chat"]) | |
| # --- CSS Styling --- | |
| css = """ | |
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap'); | |
| :root { | |
| --bg-primary: #0a0a0a; | |
| --bg-secondary: #141414; | |
| --bg-tertiary: #1c1c1c; | |
| --border: #2a2a2a; | |
| --accent: #6366f1; | |
| --accent-hover: #818cf8; | |
| --text-primary: #e5e5e5; | |
| --text-secondary: #a3a3a3; | |
| --success: #22c55e; | |
| --error: #ef4444; | |
| } | |
| body { | |
| font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif !important; | |
| background: var(--bg-primary) !important; | |
| color: var(--text-primary) !important; | |
| } | |
| /* Sidebar */ | |
| .sidebar { | |
| background: var(--bg-secondary) !important; | |
| border-right: 1px solid var(--border) !important; | |
| padding: 1.5rem !important; | |
| height: 100vh !important; | |
| } | |
| .sidebar-title { | |
| font-size: 1.25rem !important; | |
| font-weight: 700 !important; | |
| color: var(--text-primary) !important; | |
| margin-bottom: 1.5rem !important; | |
| } | |
| .sidebar-section { | |
| margin-top: 1.5rem !important; | |
| padding-top: 1rem !important; | |
| border-top: 1px solid var(--border) !important; | |
| } | |
| .sidebar-label { | |
| font-size: 0.7rem !important; | |
| font-weight: 600 !important; | |
| text-transform: uppercase !important; | |
| letter-spacing: 0.08em !important; | |
| color: var(--text-secondary) !important; | |
| margin-bottom: 0.5rem !important; | |
| } | |
| /* Navigation Buttons */ | |
| .nav-btn { | |
| width: 100% !important; | |
| margin-bottom: 0.5rem !important; | |
| justify-content: flex-start !important; | |
| padding: 0.625rem 0.875rem !important; | |
| border-radius: 0.625rem !important; | |
| font-weight: 500 !important; | |
| transition: all 0.15s ease !important; | |
| } | |
| .nav-btn:hover { | |
| background: var(--bg-tertiary) !important; | |
| } | |
| /* Main Content */ | |
| .main-content { | |
| background: var(--bg-primary) !important; | |
| padding: 2rem !important; | |
| min-height: 100vh !important; | |
| } | |
| /* Onboarding Card */ | |
| .onboarding-card { | |
| max-width: 480px; | |
| margin: 8vh auto 0; | |
| padding: 2.5rem; | |
| background: var(--bg-secondary) !important; | |
| border: 1px solid var(--border) !important; | |
| border-radius: 1rem !important; | |
| } | |
| .onboarding-title { | |
| font-size: 1.75rem !important; | |
| font-weight: 700 !important; | |
| color: var(--text-primary) !important; | |
| margin-bottom: 0.5rem !important; | |
| } | |
| .onboarding-subtitle { | |
| color: var(--text-secondary) !important; | |
| font-size: 0.875rem !important; | |
| margin-bottom: 2rem !important; | |
| } | |
| /* Greeting */ | |
| .greeting-container { | |
| text-align: center; | |
| margin-bottom: 2rem; | |
| } | |
| .greeting-title { | |
| font-size: 2rem !important; | |
| font-weight: 700 !important; | |
| color: var(--text-primary) !important; | |
| margin-bottom: 0.5rem !important; | |
| } | |
| .greeting-subtitle { | |
| color: var(--text-secondary) !important; | |
| font-size: 0.9rem !important; | |
| } | |
| /* Input Box */ | |
| .input-box { | |
| background: var(--bg-secondary) !important; | |
| border: 1px solid var(--border) !important; | |
| border-radius: 0.75rem !important; | |
| padding: 0.75rem !important; | |
| } | |
| .input-box:focus-within { | |
| border-color: var(--accent) !important; | |
| box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.1) !important; | |
| } | |
| /* Buttons */ | |
| .btn-primary { | |
| background: var(--accent) !important; | |
| color: white !important; | |
| font-weight: 600 !important; | |
| border-radius: 0.625rem !important; | |
| padding: 0.625rem 1.25rem !important; | |
| border: none !important; | |
| transition: all 0.15s ease !important; | |
| } | |
| .btn-primary:hover { | |
| background: var(--accent-hover) !important; | |
| transform: translateY(-1px) !important; | |
| } | |
| .btn-secondary { | |
| background: var(--bg-tertiary) !important; | |
| color: var(--text-primary) !important; | |
| border: 1px solid var(--border) !important; | |
| border-radius: 0.625rem !important; | |
| padding: 0.625rem 1.25rem !important; | |
| } | |
| /* Chat Messages */ | |
| .chatbot-container { | |
| background: var(--bg-secondary) !important; | |
| border: 1px solid var(--border) !important; | |
| border-radius: 0.75rem !important; | |
| overflow: hidden !important; | |
| } | |
| .message.user { | |
| background: var(--accent) !important; | |
| color: white !important; | |
| border-radius: 1rem 1rem 0.25rem 1rem !important; | |
| } | |
| .message.bot { | |
| background: var(--bg-tertiary) !important; | |
| color: var(--text-primary) !important; | |
| border-radius: 1rem 1rem 1rem 0.25rem !important; | |
| } | |
| /* User Profile */ | |
| .user-profile { | |
| display: flex; | |
| align-items: center; | |
| gap: 0.75rem; | |
| padding: 0.75rem; | |
| background: var(--bg-tertiary) !important; | |
| border-radius: 0.75rem !important; | |
| margin-top: auto; | |
| } | |
| .user-avatar { | |
| width: 2rem; | |
| height: 2rem; | |
| background: var(--accent); | |
| color: white; | |
| border-radius: 0.5rem; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-weight: 700; | |
| font-size: 0.875rem; | |
| flex-shrink: 0; | |
| } | |
| /* Radio & Dropdown */ | |
| .radio-group label { | |
| color: var(--text-secondary) !important; | |
| font-size: 0.8125rem !important; | |
| } | |
| .dropdown-select input { | |
| background: var(--bg-tertiary) !important; | |
| border-color: var(--border) !important; | |
| color: var(--text-primary) !important; | |
| border-radius: 0.5rem !important; | |
| } | |
| /* Generation Cards */ | |
| .gen-card { | |
| background: var(--bg-secondary) !important; | |
| border: 1px solid var(--border) !important; | |
| border-radius: 1rem !important; | |
| padding: 2rem !important; | |
| max-width: 800px; | |
| margin: 0 auto; | |
| } | |
| .gen-title { | |
| font-size: 1.25rem !important; | |
| font-weight: 600 !important; | |
| margin-bottom: 1.5rem !important; | |
| } | |
| /* Footer */ | |
| .footer { | |
| text-align: center; | |
| padding: 1rem; | |
| color: var(--text-secondary); | |
| font-size: 0.75rem; | |
| margin-top: 2rem; | |
| } | |
| /* Status Badge */ | |
| .status-badge { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 0.375rem; | |
| padding: 0.25rem 0.625rem; | |
| border-radius: 9999px; | |
| font-size: 0.75rem; | |
| font-weight: 500; | |
| } | |
| .status-online { | |
| background: rgba(34, 197, 94, 0.15); | |
| color: var(--success); | |
| } | |
| /* Animations */ | |
| @keyframes fadeIn { | |
| from { opacity: 0; transform: translateY(10px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| .fade-in { | |
| animation: fadeIn 0.4s ease-out; | |
| } | |
| /* API Key Input */ | |
| .api-key-input input { | |
| font-family: monospace !important; | |
| font-size: 0.75rem !important; | |
| } | |
| /* Scrollbar */ | |
| ::-webkit-scrollbar { | |
| width: 6px; | |
| } | |
| ::-webkit-scrollbar-track { | |
| background: transparent; | |
| } | |
| ::-webkit-scrollbar-thumb { | |
| background: #333; | |
| border-radius: 3px; | |
| } | |
| ::-webkit-scrollbar-thumb:hover { | |
| background: #444; | |
| } | |
| """ | |
| # ============================================================ | |
| # BUILD THE UI | |
| # ============================================================ | |
| with gr.Blocks(css=css, theme=gr.themes.Base()) as demo: | |
| # --- States --- | |
| name_state = gr.State("Explorer") | |
| personality_state = gr.State("Professional & Concise") | |
| current_tab = gr.State("chat") | |
| # ============================================================ | |
| # ONBOARDING SCREEN | |
| # ============================================================ | |
| with gr.Column(visible=True, elem_classes="onboarding-card fade-in") as onboarding: | |
| gr.Markdown("# HuggingGPT", elem_classes="onboarding-title") | |
| gr.Markdown( | |
| "Your multi-modal AI playground. Chat with powerful language models, generate stunning images, " | |
| "and create videos — all in one place.", | |
| elem_classes="onboarding-subtitle" | |
| ) | |
| name_input = gr.Textbox( | |
| label="What should I call you?", | |
| placeholder="e.g. Alex", | |
| elem_classes="dropdown-select" | |
| ) | |
| personality_input = gr.Dropdown( | |
| label="AI Personality", | |
| choices=[ | |
| "Professional & Concise", | |
| "Friendly & Creative", | |
| "Sarcastic & Witty", | |
| "Expert Coder", | |
| "Research Assistant" | |
| ], | |
| value="Professional & Concise", | |
| elem_classes="dropdown-select" | |
| ) | |
| api_key_onboard = gr.Textbox( | |
| label="HF API Token (optional)", | |
| placeholder="hf_...", | |
| type="password", | |
| elem_classes="api-key-input dropdown-select", | |
| info="Required for some models. Get one at huggingface.co/settings/tokens" | |
| ) | |
| start_btn = gr.Button("Get Started", variant="primary", elem_classes="btn-primary") | |
| gr.Markdown( | |
| "<div class='footer'>Powered by Hugging Face Inference API</div>", | |
| elem_classes="footer" | |
| ) | |
| # ============================================================ | |
| # MAIN APP INTERFACE | |
| # ============================================================ | |
| with gr.Row(visible=False, elem_id="main-ui") as main_ui: | |
| # --- SIDEBAR --- | |
| with gr.Column(scale=1, elem_classes="sidebar"): | |
| gr.Markdown("### HuggingGPT", elem_classes="sidebar-title") | |
| # Navigation | |
| gr.Markdown("<div class='sidebar-label'>Tools</div>") | |
| chat_nav_btn = gr.Button("Chat", elem_classes="nav-btn btn-secondary") | |
| img_nav_btn = gr.Button("Image Gen", elem_classes="nav-btn btn-secondary") | |
| video_nav_btn = gr.Button("Text-to-Video", elem_classes="nav-btn btn-secondary") | |
| # Chat Settings | |
| with gr.Column(visible=True) as chat_settings: | |
| gr.Markdown("<div class='sidebar-label'>Language Model</div>") | |
| mode_radio = gr.Radio( | |
| choices=[("Qwen 2.5 (7B)", "qwen"), ("Kimi K2", "kimi"), ("Qwen Mini", "small"), ("Custom", "custom")], | |
| value="qwen", | |
| label="", | |
| elem_classes="radio-group" | |
| ) | |
| custom_url = gr.Textbox( | |
| placeholder="organization/model-name", | |
| visible=False, | |
| label="Custom Model ID", | |
| elem_classes="dropdown-select" | |
| ) | |
| gr.Markdown("<div class='sidebar-label'>Personality</div>") | |
| pers_display = gr.Dropdown( | |
| choices=["Professional & Concise", "Friendly & Creative", "Sarcastic & Witty", "Expert Coder", "Research Assistant"], | |
| value="Professional & Concise", | |
| label="", | |
| interactive=True, | |
| elem_classes="dropdown-select" | |
| ) | |
| # Image Settings | |
| with gr.Column(visible=False) as image_settings: | |
| gr.Markdown("<div class='sidebar-label'>Image Model</div>") | |
| img_mode = gr.Radio( | |
| choices=[("FLUX Schnell (Fast)", "flash"), ("FLUX Dev (Quality)", "full")], | |
| value="flash", | |
| label="", | |
| elem_classes="radio-group" | |
| ) | |
| # Video Settings | |
| with gr.Column(visible=False) as video_settings: | |
| gr.Markdown("<div class='sidebar-label'>Video Model</div>") | |
| video_mode = gr.Radio( | |
| choices=[("Hunyuan (Fast)", "fast"), ("Wan 2.1 (Quality)", "quality")], | |
| value="fast", | |
| label="", | |
| elem_classes="radio-group" | |
| ) | |
| # API Key | |
| gr.Markdown("<div class='sidebar-label'>API Token</div>") | |
| api_key_main = gr.Textbox( | |
| placeholder="hf_...", | |
| type="password", | |
| label="", | |
| elem_classes="api-key-input dropdown-select", | |
| info="hf_ token for private models" | |
| ) | |
| # User Profile | |
| with gr.Column(elem_classes="user-profile"): | |
| gr.HTML("<div class='user-avatar'>H</div>") | |
| with gr.Column(scale=1, min_width=0): | |
| user_name = gr.Markdown("**Explorer**") | |
| user_pers = gr.Markdown("Professional & Concise", elem_classes="footer") | |
| # --- CONTENT AREA --- | |
| with gr.Column(scale=4, elem_classes="main-content"): | |
| # ---- CHAT TAB ---- | |
| with gr.Column(visible=True) as chat_tab: | |
| gr.Markdown( | |
| "## Chat", | |
| elem_classes="greeting-title" | |
| ) | |
| chatbot = gr.Chatbot( | |
| height=520, | |
| show_label=False, | |
| type="messages", | |
| elem_classes="chatbot-container" | |
| ) | |
| with gr.Row(elem_classes="input-box"): | |
| msg_input = gr.Textbox( | |
| placeholder="Ask anything, generate code, brainstorm ideas...", | |
| show_label=False, | |
| scale=8, | |
| elem_classes="dropdown-select" | |
| ) | |
| send_btn = gr.Button("Send", variant="primary", scale=1, elem_classes="btn-primary") | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| gen_img_cb = gr.Checkbox(label="Generate Image alongside") | |
| with gr.Column(scale=3): | |
| img_prompt_input = gr.Textbox( | |
| placeholder="Describe the image to generate...", | |
| show_label=False, | |
| visible=False, | |
| elem_classes="dropdown-select" | |
| ) | |
| # ---- IMAGE GEN TAB ---- | |
| with gr.Column(visible=False) as image_tab: | |
| with gr.Column(elem_classes="gen-card"): | |
| gr.Markdown("## Image Generation", elem_classes="gen-title") | |
| gr.Markdown( | |
| "Describe the image you want to create in detail. The AI will generate it using FLUX models." | |
| ) | |
| img_prompt_main = gr.Textbox( | |
| label="Prompt", | |
| placeholder="A serene Japanese garden with cherry blossoms, soft morning light, watercolor style...", | |
| lines=4, | |
| elem_classes="dropdown-select" | |
| ) | |
| img_model_display = gr.Radio( | |
| choices=[("FLUX Schnell (Fast ~2s)", "flash"), ("FLUX Dev (Quality ~10s)", "full")], | |
| value="flash", | |
| label="Model", | |
| elem_classes="radio-group" | |
| ) | |
| gen_img_btn = gr.Button("Generate Image", variant="primary", elem_classes="btn-primary") | |
| img_output = gr.Image( | |
| label="Generated Image", | |
| show_label=False, | |
| elem_classes="chatbot-container" | |
| ) | |
| img_status = gr.Markdown("") | |
| # ---- VIDEO GEN TAB ---- | |
| with gr.Column(visible=False) as video_tab: | |
| with gr.Column(elem_classes="gen-card"): | |
| gr.Markdown("## Text-to-Video", elem_classes="gen-title") | |
| gr.Markdown( | |
| "Describe the video scene you want to generate. Note: Video generation can take 1-5 minutes." | |
| ) | |
| video_prompt = gr.Textbox( | |
| label="Prompt", | |
| placeholder="A futuristic cityscape at sunset, flying cars, neon lights reflecting on wet streets...", | |
| lines=4, | |
| elem_classes="dropdown-select" | |
| ) | |
| video_model_display = gr.Radio( | |
| choices=[("HunyuanVideo (Fast)", "fast"), ("Wan 2.1 (Quality)", "quality")], | |
| value="fast", | |
| label="Model", | |
| elem_classes="radio-group" | |
| ) | |
| gen_video_btn = gr.Button("Generate Video", variant="primary", elem_classes="btn-primary") | |
| video_output = gr.Video( | |
| label="Generated Video", | |
| show_label=False, | |
| elem_classes="chatbot-container" | |
| ) | |
| video_status = gr.Markdown("") | |
| # Footer | |
| gr.Markdown( | |
| "<div class='footer'>Powered by Hugging Face Inference API | Models may be loading on first use</div>", | |
| elem_classes="footer" | |
| ) | |
| # ============================================================ | |
| # EVENT HANDLERS | |
| # ============================================================ | |
| # Show/hide custom URL field | |
| mode_radio.change( | |
| lambda x: gr.update(visible=x == "custom"), | |
| inputs=mode_radio, | |
| outputs=custom_url | |
| ) | |
| # Show/hide image prompt field | |
| gen_img_cb.change( | |
| lambda x: gr.update(visible=x), | |
| inputs=gen_img_cb, | |
| outputs=img_prompt_input | |
| ) | |
| # Tab switching | |
| chat_nav_btn.click( | |
| lambda: ( | |
| gr.update(visible=True), # chat_tab | |
| gr.update(visible=False), # image_tab | |
| gr.update(visible=False), # video_tab | |
| gr.update(visible=True), # chat_settings | |
| gr.update(visible=False), # image_settings | |
| gr.update(visible=False), # video_settings | |
| "chat" | |
| ), | |
| outputs=[chat_tab, image_tab, video_tab, chat_settings, image_settings, video_settings, current_tab] | |
| ) | |
| img_nav_btn.click( | |
| lambda: ( | |
| gr.update(visible=False), | |
| gr.update(visible=True), | |
| gr.update(visible=False), | |
| gr.update(visible=False), | |
| gr.update(visible=True), | |
| gr.update(visible=False), | |
| "image" | |
| ), | |
| outputs=[chat_tab, image_tab, video_tab, chat_settings, image_settings, video_settings, current_tab] | |
| ) | |
| video_nav_btn.click( | |
| lambda: ( | |
| gr.update(visible=False), | |
| gr.update(visible=False), | |
| gr.update(visible=True), | |
| gr.update(visible=False), | |
| gr.update(visible=False), | |
| gr.update(visible=True), | |
| "video" | |
| ), | |
| outputs=[chat_tab, image_tab, video_tab, chat_settings, image_settings, video_settings, current_tab] | |
| ) | |
| # Onboarding completion | |
| start_btn.click( | |
| finish_onboarding, | |
| inputs=[name_input, personality_input], | |
| outputs=[onboarding, main_ui, chatbot, user_name, user_pers, name_state, personality_state] | |
| ) | |
| # Chat submission | |
| chat_inputs = [msg_input, chatbot, mode_radio, pers_display, custom_url, gen_img_cb, img_prompt_input, api_key_main] | |
| send_btn.click(chat, chat_inputs, [chatbot, msg_input]) | |
| msg_input.submit(chat, chat_inputs, [chatbot, msg_input]) | |
| # Image generation from Image tab | |
| gen_img_btn.click( | |
| generate_image, | |
| inputs=[img_prompt_main, img_model_display, api_key_main], | |
| outputs=[img_output, img_status] | |
| ) | |
| # Video generation | |
| gen_video_btn.click( | |
| generate_video, | |
| inputs=[video_prompt, video_model_display, api_key_main], | |
| outputs=[video_output, video_status] | |
| ) | |
| # Sync API key between onboarding and main | |
| api_key_onboard.change( | |
| lambda x: x, | |
| inputs=api_key_onboard, | |
| outputs=api_key_main | |
| ) | |
| # ============================================================ | |
| # LAUNCH | |
| # ============================================================ | |
| if __name__ == "__main__": | |
| demo.launch( | |
| server_name="0.0.0.0", | |
| server_port=7860, | |
| share=False, | |
| show_error=True | |
| ) | |