import os import requests import gradio as gr BASE_URL = "https://router.huggingface.co/v1" CSS = """ :root{ --bg:#0b0f19; --panel:rgba(255,255,255,.06); --panel2:rgba(255,255,255,.08); --text:rgba(255,255,255,.92); --muted:rgba(255,255,255,.65); --border:rgba(255,255,255,.10); --shadow: 0 20px 60px rgba(0,0,0,.45); } body.light{ --bg:#f6f7fb; --panel:rgba(0,0,0,.04); --panel2:rgba(0,0,0,.06); --text:rgba(0,0,0,.88); --muted:rgba(0,0,0,.55); --border:rgba(0,0,0,.10); --shadow: 0 20px 60px rgba(0,0,0,.12); } .gradio-container{ background: radial-gradient(1200px 700px at 20% 10%, rgba(98, 123, 255, .22), transparent 60%), radial-gradient(1000px 700px at 80% 30%, rgba(255, 90, 150, .18), transparent 60%), var(--bg) !important; color: var(--text) !important; } #wrap{ max-width: 980px; margin: 22px auto; padding: 0 14px; } .header{ display:flex; justify-content:space-between; align-items:center; padding: 18px 14px; border: 1px solid var(--border); border-radius: 18px; background: var(--panel); box-shadow: var(--shadow); } .block{ border: 1px solid var(--border); border-radius: 18px; background: var(--panel); box-shadow: var(--shadow); padding: 14px; margin-top: 14px; } .subtle{ color: var(--muted); } .chat_shell{ border: 1px solid var(--border); border-radius: 18px; background: var(--panel); box-shadow: var(--shadow); padding: 10px; margin-top: 14px; } .composer{ margin-top: 10px; } .primary button, .secondary button{ border-radius: 14px !important; border: 1px solid var(--border) !important; } .primary button{ background: rgba(98, 123, 255, .85) !important; color: white !important; } .secondary button{ background: var(--panel2) !important; color: var(--text) !important; } footer{ display:none !important; } """ TOGGLE = """
Theme
""" # ---------------------------- # HF Router helpers # ---------------------------- def get_headers(): key = os.environ.get("HF_API_KEY") if not key: return None, "HF_API_KEY missing (Space Settings → Variables)" return {"Authorization": f"Bearer {key}", "Content-Type": "application/json"}, None def list_models(headers): r = requests.get(f"{BASE_URL}/models", headers=headers, timeout=20) if r.status_code != 200: return None, f"HTTP {r.status_code}: {r.text}" data = r.json() models = [m.get("id") for m in data.get("data", []) if m.get("id")] if not models: return None, "No models returned" return models, None def chat_call(headers, model, messages, temperature, max_tokens): payload = { "model": model, "messages": messages, "temperature": float(temperature), "max_tokens": int(max_tokens), } r = requests.post( f"{BASE_URL}/chat/completions", headers=headers, json=payload, timeout=60, ) if r.status_code != 200: return f"HTTP {r.status_code}: {r.text}" data = r.json() try: return data["choices"][0]["message"]["content"] except Exception: return f"Unexpected response shape: {data}" # ---------------------------- # Message utilities # ---------------------------- MAX_TURNS = 14 # last N user+assistant pairs def trimmed_messages(msgs): return (msgs or [])[-MAX_TURNS * 2 :] def build_messages(system_prompt, msgs, user_msg): return ( [{"role": "system", "content": system_prompt}] + trimmed_messages(msgs) + [{"role": "user", "content": user_msg}] ) def ui_messages_from_state(msgs): # Chatbot (messages format) expects list of dicts with role/content. # Hide system messages from UI. out = [] for m in msgs or []: role = m.get("role") if role in ("user", "assistant"): out.append({"role": role, "content": m.get("content", "")}) return out # ---------------------------- # Gradio app # ---------------------------- with gr.Blocks(title="Chat") as demo: with gr.Column(elem_id="wrap"): with gr.Row(elem_classes=["header"]): gr.Markdown("## My AI Chatbot") gr.HTML(TOGGLE) status = gr.Markdown("Starting…", elem_classes=["subtle"]) with gr.Column(elem_classes=["block"]): with gr.Row(): model_dd = gr.Dropdown( label="Model (from /v1/models)", choices=[], value=None, interactive=True, ) refresh_btn = gr.Button("Refresh models", elem_classes=["secondary"]) model_override = gr.Textbox( label="Model override (optional)", placeholder="Paste a chat-capable model id here (takes precedence).", ) system_prompt = gr.Textbox(label="System prompt", value="You are a helpful assistant.") with gr.Row(): temperature = gr.Slider(0.0, 1.5, value=0.7, step=0.05, label="Temperature") max_tokens = gr.Slider(64, 2048, value=800, step=32, label="Max tokens") with gr.Column(elem_classes=["chat_shell"]): # IMPORTANT: no type=... (your Gradio build doesn't support it) chatbot = gr.Chatbot(height=560) msg_state = gr.State([]) # OpenAI-style list[dict] with gr.Column(elem_classes=["composer"]): msg = gr.Textbox(placeholder="Message…", show_label=False, lines=1) with gr.Row(): send = gr.Button("Send", elem_classes=["primary"]) clear = gr.Button("Clear", elem_classes=["secondary"]) def do_refresh_models(): headers, err = get_headers() if err: return f"❌ {err}", gr.update(choices=[], value=None) models, err = list_models(headers) if err: return f"❌ {err}", gr.update(choices=[], value=None) default = models[0] if models else None return f"✅ Models loaded ({len(models)}).", gr.update(choices=models, value=default) def init(): return do_refresh_models() def respond(user_msg, msgs, model_dd_value, model_text, sys_prompt, temp, mx): user_msg = (user_msg or "").strip() msgs = msgs or [] if not user_msg: return "", ui_messages_from_state(msgs), msgs headers, err = get_headers() if err: msgs = msgs + [ {"role": "user", "content": user_msg}, {"role": "assistant", "content": f"Setup error: {err}"}, ] return "", ui_messages_from_state(msgs), msgs model = (model_text or "").strip() or (model_dd_value or "").strip() if not model: msgs = msgs + [ {"role": "user", "content": user_msg}, {"role": "assistant", "content": "No model selected. Choose one or set Model override."}, ] return "", ui_messages_from_state(msgs), msgs req_messages = build_messages(sys_prompt or "You are a helpful assistant.", msgs, user_msg) bot = chat_call(headers, model, req_messages, temp, mx) msgs = msgs + [ {"role": "user", "content": user_msg}, {"role": "assistant", "content": bot}, ] return "", ui_messages_from_state(msgs), msgs def do_clear(): return [], [] demo.load(init, outputs=[status, model_dd]) refresh_btn.click(do_refresh_models, outputs=[status, model_dd]) send.click( respond, inputs=[msg, msg_state, model_dd, model_override, system_prompt, temperature, max_tokens], outputs=[msg, chatbot, msg_state], ) msg.submit( respond, inputs=[msg, msg_state, model_dd, model_override, system_prompt, temperature, max_tokens], outputs=[msg, chatbot, msg_state], ) clear.click(do_clear, outputs=[chatbot, msg_state]) # Keep this if your Gradio build supports css= on launch() demo.launch(css=CSS)