Spaces:
Running
Running
| import gradio as gr | |
| from huggingface_hub import InferenceClient | |
| from openai import OpenAI | |
| import os | |
| # ============================================ | |
| # MiniMax-M2.1 Streaming Chat | |
| # Dual Provider: Novita (HF) + MiniMax Official API | |
| # Gradio 6.0 Compatible - Full Width Layout | |
| # ============================================ | |
| # Provider Management | |
| class ProviderManager: | |
| def __init__(self): | |
| self.current_provider = "novita" | |
| self.novita_available = True | |
| # Novita Client (via HuggingFace) | |
| self.novita_client = InferenceClient( | |
| provider="novita", | |
| api_key=os.environ.get("HF_TOKEN"), | |
| ) | |
| # MiniMax Official API Client | |
| self.minimax_client = OpenAI( | |
| api_key=os.environ.get("MINIMAX_API_KEY", ""), | |
| base_url="https://api.minimax.chat/v1" | |
| ) | |
| def get_status(self): | |
| if self.current_provider == "novita": | |
| return "[Active] Novita (HuggingFace)" | |
| else: | |
| return "[Active] MiniMax Official API" | |
| def switch_to_minimax(self): | |
| self.current_provider = "minimax" | |
| self.novita_available = False | |
| print("Switched to MiniMax Official API") | |
| def reset_to_novita(self): | |
| self.current_provider = "novita" | |
| self.novita_available = True | |
| print("Reset to Novita provider") | |
| # Global provider manager | |
| provider = ProviderManager() | |
| def chat_with_novita(messages): | |
| """Streaming via Novita provider""" | |
| stream = provider.novita_client.chat.completions.create( | |
| model="MiniMaxAI/MiniMax-M2.1", | |
| messages=messages, | |
| max_tokens=4096, | |
| temperature=1.0, | |
| top_p=0.95, | |
| stream=True | |
| ) | |
| for chunk in stream: | |
| if chunk.choices[0].delta.content: | |
| yield chunk.choices[0].delta.content | |
| def chat_with_minimax(messages): | |
| """Streaming via MiniMax Official API""" | |
| stream = provider.minimax_client.chat.completions.create( | |
| model="MiniMax-M2.1", | |
| messages=messages, | |
| max_tokens=4096, | |
| temperature=1.0, | |
| top_p=0.95, | |
| stream=True | |
| ) | |
| for chunk in stream: | |
| if chunk.choices and chunk.choices[0].delta.content: | |
| yield chunk.choices[0].delta.content | |
| def chat_respond(message, history): | |
| """ | |
| Streaming chat with automatic fallback | |
| Gradio 6.0 messages format: [{"role": "user/assistant", "content": "..."}] | |
| """ | |
| if not message.strip(): | |
| return history, provider.get_status() | |
| # Build API messages | |
| api_messages = [{ | |
| "role": "system", | |
| "content": "You are MiniMax-M2.1, a helpful AI assistant built by MiniMax. You excel at coding, tool use, and complex reasoning tasks. Respond in the same language as the user." | |
| }] | |
| # Add history (Gradio 6.0 messages format) | |
| for h in history: | |
| if isinstance(h, dict): | |
| api_messages.append({"role": h.get("role", "user"), "content": h.get("content", "")}) | |
| api_messages.append({"role": "user", "content": message}) | |
| response_text = "" | |
| # 1st: Try Novita | |
| if provider.novita_available and provider.current_provider == "novita": | |
| try: | |
| for chunk in chat_with_novita(api_messages): | |
| response_text += chunk | |
| new_history = history + [ | |
| {"role": "user", "content": message}, | |
| {"role": "assistant", "content": response_text} | |
| ] | |
| yield new_history, "[Active] Novita (HuggingFace)" | |
| return | |
| except Exception as e: | |
| error_msg = str(e).lower() | |
| if any(kw in error_msg for kw in ["rate limit", "quota", "exceeded", "insufficient", "credit", "balance", "limit", "429", "402", "payment"]): | |
| print(f"Novita error: {e}") | |
| provider.switch_to_minimax() | |
| else: | |
| print(f"Novita error: {e}") | |
| # 2nd: Fallback to MiniMax | |
| try: | |
| if not os.environ.get("MINIMAX_API_KEY"): | |
| new_history = history + [ | |
| {"role": "user", "content": message}, | |
| {"role": "assistant", "content": "Error: MINIMAX_API_KEY not configured."} | |
| ] | |
| yield new_history, "[Error] No API Key" | |
| return | |
| for chunk in chat_with_minimax(api_messages): | |
| response_text += chunk | |
| new_history = history + [ | |
| {"role": "user", "content": message}, | |
| {"role": "assistant", "content": response_text} | |
| ] | |
| yield new_history, "[Active] MiniMax Official API" | |
| return | |
| except Exception as e: | |
| if "rate limit" not in str(e).lower(): | |
| provider.reset_to_novita() | |
| new_history = history + [ | |
| {"role": "user", "content": message}, | |
| {"role": "assistant", "content": f"Error: {str(e)}"} | |
| ] | |
| yield new_history, "[Error]" | |
| def reset_provider_fn(): | |
| """Manually reset to Novita""" | |
| provider.reset_to_novita() | |
| return "Reset to Novita!" | |
| def clear_chat_fn(): | |
| """Clear chat history""" | |
| return [], "", provider.get_status() | |
| # ============================================ | |
| # Comic Classic Theme CSS | |
| # ============================================ | |
| css = """ | |
| @import url('https://fonts.googleapis.com/css2?family=Bangers&family=Comic+Neue:wght@400;700&display=swap'); | |
| .gradio-container { | |
| background-color: #FEF9C3 !important; | |
| background-image: radial-gradient(#1F2937 1px, transparent 1px) !important; | |
| background-size: 20px 20px !important; | |
| min-height: 100vh !important; | |
| font-family: 'Comic Neue', cursive, sans-serif !important; | |
| } | |
| .huggingface-space-header, #space-header, .space-header, [class*="space-header"] { | |
| display: none !important; | |
| } | |
| footer, .footer, .gradio-footer, .built-with-gradio { | |
| display: none !important; | |
| } | |
| .header-text h1 { | |
| font-family: 'Bangers', cursive !important; | |
| color: #1F2937 !important; | |
| font-size: 3.5rem !important; | |
| text-align: center !important; | |
| text-shadow: 4px 4px 0px #FACC15, 6px 6px 0px #1F2937 !important; | |
| letter-spacing: 3px !important; | |
| -webkit-text-stroke: 2px #1F2937 !important; | |
| } | |
| .subtitle { | |
| text-align: center !important; | |
| font-family: 'Comic Neue', cursive !important; | |
| font-size: 1.2rem !important; | |
| color: #1F2937 !important; | |
| font-weight: 700 !important; | |
| } | |
| .gr-panel, .gr-box, .block, .gr-group { | |
| background: #FFFFFF !important; | |
| border: 3px solid #1F2937 !important; | |
| border-radius: 8px !important; | |
| box-shadow: 6px 6px 0px #1F2937 !important; | |
| } | |
| textarea, input[type="text"] { | |
| background: #FFFFFF !important; | |
| border: 3px solid #1F2937 !important; | |
| border-radius: 8px !important; | |
| color: #1F2937 !important; | |
| font-family: 'Comic Neue', cursive !important; | |
| font-size: 1rem !important; | |
| font-weight: 700 !important; | |
| } | |
| textarea:focus, input[type="text"]:focus { | |
| border-color: #3B82F6 !important; | |
| box-shadow: 4px 4px 0px #3B82F6 !important; | |
| outline: none !important; | |
| } | |
| .gr-button-primary, button.primary { | |
| background: #3B82F6 !important; | |
| border: 3px solid #1F2937 !important; | |
| border-radius: 8px !important; | |
| color: #FFFFFF !important; | |
| font-family: 'Bangers', cursive !important; | |
| font-size: 1.3rem !important; | |
| letter-spacing: 2px !important; | |
| padding: 14px 28px !important; | |
| box-shadow: 5px 5px 0px #1F2937 !important; | |
| text-shadow: 1px 1px 0px #1F2937 !important; | |
| } | |
| .gr-button-primary:hover, button.primary:hover { | |
| background: #2563EB !important; | |
| transform: translate(-2px, -2px) !important; | |
| box-shadow: 7px 7px 0px #1F2937 !important; | |
| } | |
| .gr-button-secondary, button.secondary { | |
| background: #EF4444 !important; | |
| border: 3px solid #1F2937 !important; | |
| border-radius: 8px !important; | |
| color: #FFFFFF !important; | |
| font-family: 'Bangers', cursive !important; | |
| font-size: 1.1rem !important; | |
| box-shadow: 4px 4px 0px #1F2937 !important; | |
| } | |
| .gr-button-secondary:hover, button.secondary:hover { | |
| background: #DC2626 !important; | |
| transform: translate(-2px, -2px) !important; | |
| } | |
| .status-box textarea { | |
| background: #1F2937 !important; | |
| color: #10B981 !important; | |
| font-family: 'Courier New', monospace !important; | |
| border: 3px solid #10B981 !important; | |
| box-shadow: 4px 4px 0px #10B981 !important; | |
| } | |
| .chatbot { | |
| border: 3px solid #1F2937 !important; | |
| border-radius: 12px !important; | |
| box-shadow: 6px 6px 0px #1F2937 !important; | |
| background: #FFFFFF !important; | |
| } | |
| pre, code { | |
| background: #1F2937 !important; | |
| color: #10B981 !important; | |
| border: 2px solid #10B981 !important; | |
| border-radius: 6px !important; | |
| font-family: 'Courier New', monospace !important; | |
| } | |
| ::-webkit-scrollbar { width: 12px; } | |
| ::-webkit-scrollbar-track { background: #FEF9C3; border: 2px solid #1F2937; } | |
| ::-webkit-scrollbar-thumb { background: #3B82F6; border: 2px solid #1F2937; } | |
| ::-webkit-scrollbar-thumb:hover { background: #EF4444; } | |
| @media (max-width: 768px) { | |
| .header-text h1 { font-size: 2.2rem !important; } | |
| } | |
| """ | |
| # ============================================ | |
| # Gradio Interface - Full Width Layout (English, No Emoji) | |
| # ============================================ | |
| with gr.Blocks(fill_height=True) as demo: | |
| # Inject CSS via HTML | |
| gr.HTML(f"<style>{css}</style>") | |
| # HOME Badge | |
| gr.HTML(""" | |
| <div style="text-align: center; margin: 20px 0 10px 0;"> | |
| <a href="https://www.humangen.ai" target="_blank" style="text-decoration: none;"> | |
| <img src="https://img.shields.io/static/v1?label=HOME&message=HUMANGEN.AI&color=0000ff&labelColor=ffcc00&style=for-the-badge" alt="HOME"> | |
| </a> | |
| </div> | |
| """) | |
| # Header Title | |
| gr.Markdown("""# MINIMAX-M2.1 CHAT""", elem_classes="header-text") | |
| gr.Markdown("""<p class="subtitle">Claude Sonnet 4.5-level Coding & Agent Performance! 230B Parameter Open Source Model</p>""") | |
| # Model Info Box | |
| gr.HTML(""" | |
| <div style="background: linear-gradient(135deg, #3B82F6 0%, #8B5CF6 100%); border: 3px solid #1F2937; border-radius: 12px; padding: 15px; color: white; box-shadow: 5px 5px 0px #1F2937; margin: 0 auto 20px auto; max-width: 1200px;"> | |
| <div style="display: flex; justify-content: space-around; flex-wrap: wrap; text-align: center;"> | |
| <div><strong style="font-size: 1.5rem;">230B</strong><br><span style="font-size: 0.9rem;">Total Params</span></div> | |
| <div><strong style="font-size: 1.5rem;">10B</strong><br><span style="font-size: 0.9rem;">Active Params</span></div> | |
| <div><strong style="font-size: 1.5rem;">88.6</strong><br><span style="font-size: 0.9rem;">VIBE Score</span></div> | |
| <div><strong style="font-size: 1.5rem;">#1</strong><br><span style="font-size: 0.9rem;">Open Source</span></div> | |
| </div> | |
| </div> | |
| """) | |
| # Provider Status Row | |
| with gr.Row(): | |
| provider_status = gr.Textbox( | |
| value="[Active] Novita (HuggingFace)", | |
| label="Current Provider", | |
| interactive=False, | |
| elem_classes="status-box", | |
| scale=4 | |
| ) | |
| reset_btn = gr.Button("Reset Provider", variant="secondary", scale=1) | |
| # Chatbot - Full Width | |
| chatbot = gr.Chatbot( | |
| label="Chat", | |
| height=500, | |
| show_label=False, | |
| elem_classes="chatbot" | |
| ) | |
| # Input Row | |
| with gr.Row(): | |
| msg = gr.Textbox( | |
| label="", | |
| placeholder="Enter your message... (coding, analysis, creative writing, anything!)", | |
| scale=8, | |
| container=False | |
| ) | |
| submit_btn = gr.Button("SEND", variant="primary", scale=2) | |
| # Clear Button | |
| clear_btn = gr.Button("CLEAR CHAT", variant="secondary") | |
| # Event Handlers | |
| def respond(message, history): | |
| if not message.strip(): | |
| yield history, provider.get_status() | |
| return | |
| for result in chat_respond(message, history): | |
| yield result | |
| # Connect events | |
| msg.submit(respond, [msg, chatbot], [chatbot, provider_status]).then( | |
| lambda: "", outputs=[msg] | |
| ) | |
| submit_btn.click(respond, [msg, chatbot], [chatbot, provider_status]).then( | |
| lambda: "", outputs=[msg] | |
| ) | |
| clear_btn.click(clear_chat_fn, outputs=[chatbot, msg, provider_status]) | |
| reset_btn.click(reset_provider_fn, outputs=[provider_status]) | |
| if __name__ == "__main__": | |
| demo.launch(ssr_mode=False) |