Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -77,7 +77,7 @@ body.light{
|
|
| 77 |
overflow:hidden;
|
| 78 |
}
|
| 79 |
|
| 80 |
-
/*
|
| 81 |
.chat_shell .message{
|
| 82 |
border-radius:16px !important;
|
| 83 |
border:1px solid var(--border) !important;
|
|
@@ -152,10 +152,7 @@ def get_headers():
|
|
| 152 |
key = os.environ.get("HF_API_KEY")
|
| 153 |
if not key:
|
| 154 |
return None, "HF_API_KEY missing (Space Settings → Variables)"
|
| 155 |
-
return {
|
| 156 |
-
"Authorization": f"Bearer {key}",
|
| 157 |
-
"Content-Type": "application/json",
|
| 158 |
-
}, None
|
| 159 |
|
| 160 |
def list_models(headers):
|
| 161 |
r = requests.get(f"{BASE_URL}/models", headers=headers, timeout=20)
|
|
@@ -163,7 +160,9 @@ def list_models(headers):
|
|
| 163 |
return None, f"HTTP {r.status_code}: {r.text}"
|
| 164 |
data = r.json()
|
| 165 |
models = [m.get("id") for m in data.get("data", []) if m.get("id")]
|
| 166 |
-
|
|
|
|
|
|
|
| 167 |
|
| 168 |
def chat_call(headers, model, messages, temperature, max_tokens):
|
| 169 |
payload = {
|
|
@@ -188,30 +187,47 @@ def chat_call(headers, model, messages, temperature, max_tokens):
|
|
| 188 |
return f"Unexpected response shape: {data}"
|
| 189 |
|
| 190 |
# ----------------------------
|
| 191 |
-
#
|
| 192 |
# ----------------------------
|
| 193 |
-
MAX_TURNS = 14 #
|
| 194 |
-
|
| 195 |
-
def
|
| 196 |
-
"""
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 201 |
return []
|
| 202 |
-
return
|
| 203 |
|
| 204 |
-
def build_messages(system_prompt,
|
| 205 |
return (
|
| 206 |
[{"role": "system", "content": system_prompt}]
|
| 207 |
-
+
|
| 208 |
+ [{"role": "user", "content": user_msg}]
|
| 209 |
)
|
| 210 |
|
| 211 |
# ----------------------------
|
| 212 |
# Gradio app
|
| 213 |
# ----------------------------
|
| 214 |
-
with gr.Blocks(title="Chat"
|
| 215 |
with gr.Column(elem_id="wrap"):
|
| 216 |
with gr.Row(elem_classes=["header"]):
|
| 217 |
gr.Markdown("## My AI Chatbot")
|
|
@@ -223,21 +239,23 @@ with gr.Blocks(title="Chat", css=CSS) as demo:
|
|
| 223 |
with gr.Row():
|
| 224 |
model_dd = gr.Dropdown(label="Model (from /v1/models)", choices=[], value=None, interactive=True)
|
| 225 |
refresh_btn = gr.Button("Refresh models", elem_classes=["secondary"])
|
|
|
|
| 226 |
model_override = gr.Textbox(
|
| 227 |
label="Model override (optional)",
|
| 228 |
-
placeholder="
|
| 229 |
-
)
|
| 230 |
-
system_prompt = gr.Textbox(
|
| 231 |
-
label="System prompt",
|
| 232 |
-
value="You are a helpful assistant.",
|
| 233 |
)
|
|
|
|
|
|
|
| 234 |
with gr.Row():
|
| 235 |
temperature = gr.Slider(0.0, 1.5, value=0.7, step=0.05, label="Temperature")
|
| 236 |
max_tokens = gr.Slider(64, 2048, value=800, step=32, label="Max tokens")
|
| 237 |
|
| 238 |
with gr.Column(elem_classes=["chat_shell"]):
|
| 239 |
-
#
|
| 240 |
-
chatbot = gr.Chatbot(
|
|
|
|
|
|
|
|
|
|
| 241 |
|
| 242 |
with gr.Column(elem_classes=["composer"]):
|
| 243 |
msg = gr.Textbox(placeholder="Message…", show_label=False, lines=1)
|
|
@@ -249,62 +267,64 @@ with gr.Blocks(title="Chat", css=CSS) as demo:
|
|
| 249 |
headers, err = get_headers()
|
| 250 |
if err:
|
| 251 |
return f"❌ {err}", gr.update(choices=[], value=None)
|
| 252 |
-
|
| 253 |
models, err = list_models(headers)
|
| 254 |
-
if err
|
| 255 |
-
return f"❌ {err
|
| 256 |
-
|
| 257 |
-
# do NOT assume first model is chat-capable; just pick first as a convenience
|
| 258 |
return f"✅ Models loaded ({len(models)}).", gr.update(choices=models, value=models[0])
|
| 259 |
|
| 260 |
def init():
|
| 261 |
return do_refresh_models()
|
| 262 |
|
| 263 |
-
def respond(user_msg,
|
| 264 |
-
|
| 265 |
user_msg = (user_msg or "").strip()
|
| 266 |
if not user_msg:
|
| 267 |
-
return "",
|
| 268 |
|
| 269 |
headers, err = get_headers()
|
| 270 |
if err:
|
| 271 |
-
|
| 272 |
{"role": "user", "content": user_msg},
|
| 273 |
{"role": "assistant", "content": f"Setup error: {err}"},
|
| 274 |
]
|
| 275 |
-
return "",
|
| 276 |
|
| 277 |
-
# Prefer override, otherwise dropdown
|
| 278 |
model = (model_text or "").strip() or (model_dd_value or "").strip()
|
| 279 |
if not model:
|
| 280 |
-
|
| 281 |
{"role": "user", "content": user_msg},
|
| 282 |
-
{"role": "assistant", "content": "No model selected. Choose one
|
| 283 |
]
|
| 284 |
-
return "",
|
| 285 |
|
| 286 |
-
|
| 287 |
-
|
|
|
|
| 288 |
|
| 289 |
-
|
| 290 |
{"role": "user", "content": user_msg},
|
| 291 |
{"role": "assistant", "content": bot},
|
| 292 |
]
|
| 293 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 294 |
|
| 295 |
demo.load(init, outputs=[status, model_dd])
|
| 296 |
refresh_btn.click(do_refresh_models, outputs=[status, model_dd])
|
| 297 |
|
| 298 |
send.click(
|
| 299 |
respond,
|
| 300 |
-
inputs=[msg,
|
| 301 |
-
outputs=[msg, chatbot],
|
| 302 |
)
|
| 303 |
msg.submit(
|
| 304 |
respond,
|
| 305 |
-
inputs=[msg,
|
| 306 |
-
outputs=[msg, chatbot],
|
| 307 |
)
|
| 308 |
-
clear.click(
|
| 309 |
|
| 310 |
-
|
|
|
|
|
|
| 77 |
overflow:hidden;
|
| 78 |
}
|
| 79 |
|
| 80 |
+
/* Best-effort bubble styling (DOM varies by Gradio version) */
|
| 81 |
.chat_shell .message{
|
| 82 |
border-radius:16px !important;
|
| 83 |
border:1px solid var(--border) !important;
|
|
|
|
| 152 |
key = os.environ.get("HF_API_KEY")
|
| 153 |
if not key:
|
| 154 |
return None, "HF_API_KEY missing (Space Settings → Variables)"
|
| 155 |
+
return {"Authorization": f"Bearer {key}", "Content-Type": "application/json"}, None
|
|
|
|
|
|
|
|
|
|
| 156 |
|
| 157 |
def list_models(headers):
|
| 158 |
r = requests.get(f"{BASE_URL}/models", headers=headers, timeout=20)
|
|
|
|
| 160 |
return None, f"HTTP {r.status_code}: {r.text}"
|
| 161 |
data = r.json()
|
| 162 |
models = [m.get("id") for m in data.get("data", []) if m.get("id")]
|
| 163 |
+
if not models:
|
| 164 |
+
return None, "No models returned"
|
| 165 |
+
return models, None
|
| 166 |
|
| 167 |
def chat_call(headers, model, messages, temperature, max_tokens):
|
| 168 |
payload = {
|
|
|
|
| 187 |
return f"Unexpected response shape: {data}"
|
| 188 |
|
| 189 |
# ----------------------------
|
| 190 |
+
# History conversion utilities
|
| 191 |
# ----------------------------
|
| 192 |
+
MAX_TURNS = 14 # keep last N user+assistant pairs
|
| 193 |
+
|
| 194 |
+
def ui_pairs_from_messages(msgs):
|
| 195 |
+
"""Convert OpenAI messages -> Chatbot tuple list [(user, assistant), ...]"""
|
| 196 |
+
pairs = []
|
| 197 |
+
pending_user = None
|
| 198 |
+
for m in msgs or []:
|
| 199 |
+
role = m.get("role")
|
| 200 |
+
content = m.get("content", "")
|
| 201 |
+
if role == "user":
|
| 202 |
+
pending_user = content
|
| 203 |
+
elif role == "assistant":
|
| 204 |
+
if pending_user is None:
|
| 205 |
+
# assistant without user, show it anyway
|
| 206 |
+
pairs.append(("", content))
|
| 207 |
+
else:
|
| 208 |
+
pairs.append((pending_user, content))
|
| 209 |
+
pending_user = None
|
| 210 |
+
if pending_user is not None:
|
| 211 |
+
pairs.append((pending_user, "")) # user message without assistant yet
|
| 212 |
+
return pairs
|
| 213 |
+
|
| 214 |
+
def trimmed_messages(msgs):
|
| 215 |
+
"""Trim to last MAX_TURNS pairs (2 messages each)."""
|
| 216 |
+
if not msgs:
|
| 217 |
return []
|
| 218 |
+
return msgs[-MAX_TURNS * 2 :]
|
| 219 |
|
| 220 |
+
def build_messages(system_prompt, msgs, user_msg):
|
| 221 |
return (
|
| 222 |
[{"role": "system", "content": system_prompt}]
|
| 223 |
+
+ trimmed_messages(msgs)
|
| 224 |
+ [{"role": "user", "content": user_msg}]
|
| 225 |
)
|
| 226 |
|
| 227 |
# ----------------------------
|
| 228 |
# Gradio app
|
| 229 |
# ----------------------------
|
| 230 |
+
with gr.Blocks(title="Chat") as demo:
|
| 231 |
with gr.Column(elem_id="wrap"):
|
| 232 |
with gr.Row(elem_classes=["header"]):
|
| 233 |
gr.Markdown("## My AI Chatbot")
|
|
|
|
| 239 |
with gr.Row():
|
| 240 |
model_dd = gr.Dropdown(label="Model (from /v1/models)", choices=[], value=None, interactive=True)
|
| 241 |
refresh_btn = gr.Button("Refresh models", elem_classes=["secondary"])
|
| 242 |
+
|
| 243 |
model_override = gr.Textbox(
|
| 244 |
label="Model override (optional)",
|
| 245 |
+
placeholder="Paste a chat-capable model id here (takes precedence over dropdown).",
|
|
|
|
|
|
|
|
|
|
|
|
|
| 246 |
)
|
| 247 |
+
|
| 248 |
+
system_prompt = gr.Textbox(label="System prompt", value="You are a helpful assistant.")
|
| 249 |
with gr.Row():
|
| 250 |
temperature = gr.Slider(0.0, 1.5, value=0.7, step=0.05, label="Temperature")
|
| 251 |
max_tokens = gr.Slider(64, 2048, value=800, step=32, label="Max tokens")
|
| 252 |
|
| 253 |
with gr.Column(elem_classes=["chat_shell"]):
|
| 254 |
+
# IMPORTANT: no `type=` here (your Gradio doesn't support it)
|
| 255 |
+
chatbot = gr.Chatbot(height=560)
|
| 256 |
+
|
| 257 |
+
# Internal message state (OpenAI-style dict list)
|
| 258 |
+
msg_state = gr.State([])
|
| 259 |
|
| 260 |
with gr.Column(elem_classes=["composer"]):
|
| 261 |
msg = gr.Textbox(placeholder="Message…", show_label=False, lines=1)
|
|
|
|
| 267 |
headers, err = get_headers()
|
| 268 |
if err:
|
| 269 |
return f"❌ {err}", gr.update(choices=[], value=None)
|
|
|
|
| 270 |
models, err = list_models(headers)
|
| 271 |
+
if err:
|
| 272 |
+
return f"❌ {err}", gr.update(choices=[], value=None)
|
|
|
|
|
|
|
| 273 |
return f"✅ Models loaded ({len(models)}).", gr.update(choices=models, value=models[0])
|
| 274 |
|
| 275 |
def init():
|
| 276 |
return do_refresh_models()
|
| 277 |
|
| 278 |
+
def respond(user_msg, msgs, model_dd_value, model_text, sys_prompt, temp, mx):
|
| 279 |
+
msgs = msgs or []
|
| 280 |
user_msg = (user_msg or "").strip()
|
| 281 |
if not user_msg:
|
| 282 |
+
return "", ui_pairs_from_messages(msgs), msgs
|
| 283 |
|
| 284 |
headers, err = get_headers()
|
| 285 |
if err:
|
| 286 |
+
msgs = msgs + [
|
| 287 |
{"role": "user", "content": user_msg},
|
| 288 |
{"role": "assistant", "content": f"Setup error: {err}"},
|
| 289 |
]
|
| 290 |
+
return "", ui_pairs_from_messages(msgs), msgs
|
| 291 |
|
|
|
|
| 292 |
model = (model_text or "").strip() or (model_dd_value or "").strip()
|
| 293 |
if not model:
|
| 294 |
+
msgs = msgs + [
|
| 295 |
{"role": "user", "content": user_msg},
|
| 296 |
+
{"role": "assistant", "content": "No model selected. Choose one or set Model override."},
|
| 297 |
]
|
| 298 |
+
return "", ui_pairs_from_messages(msgs), msgs
|
| 299 |
|
| 300 |
+
# Build request messages safely
|
| 301 |
+
req_messages = build_messages(sys_prompt or "You are a helpful assistant.", msgs, user_msg)
|
| 302 |
+
bot = chat_call(headers, model, req_messages, temp, mx)
|
| 303 |
|
| 304 |
+
msgs = msgs + [
|
| 305 |
{"role": "user", "content": user_msg},
|
| 306 |
{"role": "assistant", "content": bot},
|
| 307 |
]
|
| 308 |
+
|
| 309 |
+
return "", ui_pairs_from_messages(msgs), msgs
|
| 310 |
+
|
| 311 |
+
def do_clear():
|
| 312 |
+
return [], []
|
| 313 |
|
| 314 |
demo.load(init, outputs=[status, model_dd])
|
| 315 |
refresh_btn.click(do_refresh_models, outputs=[status, model_dd])
|
| 316 |
|
| 317 |
send.click(
|
| 318 |
respond,
|
| 319 |
+
inputs=[msg, msg_state, model_dd, model_override, system_prompt, temperature, max_tokens],
|
| 320 |
+
outputs=[msg, chatbot, msg_state],
|
| 321 |
)
|
| 322 |
msg.submit(
|
| 323 |
respond,
|
| 324 |
+
inputs=[msg, msg_state, model_dd, model_override, system_prompt, temperature, max_tokens],
|
| 325 |
+
outputs=[msg, chatbot, msg_state],
|
| 326 |
)
|
| 327 |
+
clear.click(do_clear, outputs=[chatbot, msg_state])
|
| 328 |
|
| 329 |
+
# Gradio 6.x: pass css to launch(), not Blocks()
|
| 330 |
+
demo.launch(css=CSS)
|