Spaces:
Sleeping
Sleeping
| 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 = """ | |
| <div style="display:flex;align-items:center;gap:10px;user-select:none;"> | |
| <span class="subtle">Theme</span> | |
| <label style="display:flex;align-items:center;gap:10px;"> | |
| <span class="subtle">Light</span> | |
| <input id="themeToggle" type="checkbox" style="transform:scale(1.1);"/> | |
| <span class="subtle">Dark</span> | |
| </label> | |
| </div> | |
| <script> | |
| const saved = localStorage.getItem("theme") || "dark"; | |
| if(saved==="light") document.body.classList.add("light"); | |
| const t = document.getElementById("themeToggle"); | |
| t.checked = (saved !== "light"); | |
| t.addEventListener("change", ()=>{ | |
| if(t.checked){ | |
| document.body.classList.remove("light"); | |
| localStorage.setItem("theme","dark"); | |
| } else { | |
| document.body.classList.add("light"); | |
| localStorage.setItem("theme","light"); | |
| } | |
| }); | |
| </script> | |
| """ | |
| # ---------------------------- | |
| # 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) | |