Update admin/panel.py
Browse files- admin/panel.py +162 -37
admin/panel.py
CHANGED
|
@@ -18,22 +18,17 @@ def _gate(supplied: str) -> bool:
|
|
| 18 |
|
| 19 |
|
| 20 |
def build_admin_tabs(admin_password: str = None):
|
| 21 |
-
"""
|
| 22 |
-
Build the admin UI *inside* the current Gradio Blocks context.
|
| 23 |
-
No second Gradio app, no mount, no SSR proxy.
|
| 24 |
-
"""
|
| 25 |
global ADMIN_PASSWORD
|
| 26 |
if admin_password:
|
| 27 |
ADMIN_PASSWORD = admin_password
|
| 28 |
|
| 29 |
-
# --- Login gate
|
| 30 |
with gr.Row():
|
| 31 |
pw = gr.Textbox(label="Admin password", type="password", scale=3)
|
| 32 |
login_btn = gr.Button("Login", scale=1, variant="primary")
|
| 33 |
|
| 34 |
status_md = gr.Markdown("🔒 Locked. Enter password above.")
|
| 35 |
|
| 36 |
-
# --- Admin content (hidden until login) ---
|
| 37 |
with gr.Column(visible=False) as admin_area:
|
| 38 |
with gr.Tabs():
|
| 39 |
|
|
@@ -121,8 +116,12 @@ def build_admin_tabs(admin_password: str = None):
|
|
| 121 |
del_btn.click(_del, inputs=[del_uid], outputs=del_out,
|
| 122 |
queue=False, show_progress=False)
|
| 123 |
|
| 124 |
-
# ============ Conversations ============
|
| 125 |
with gr.Tab("💬 Conversations"):
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
with gr.Row():
|
| 127 |
conv_uid = gr.Number(label="Filter user_id (blank = all)",
|
| 128 |
precision=0, scale=2)
|
|
@@ -151,7 +150,7 @@ def build_admin_tabs(admin_password: str = None):
|
|
| 151 |
outputs=conv_table,
|
| 152 |
queue=False, show_progress=False)
|
| 153 |
|
| 154 |
-
gr.Markdown("### Export")
|
| 155 |
with gr.Row():
|
| 156 |
exp_json_btn = gr.Button("📦 JSON")
|
| 157 |
exp_csv_btn = gr.Button("📊 CSV")
|
|
@@ -184,6 +183,124 @@ def build_admin_tabs(admin_password: str = None):
|
|
| 184 |
exp_md_btn.click(_write_md, outputs=exp_file,
|
| 185 |
queue=False, show_progress=False)
|
| 186 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 187 |
# ============ Tokens ============
|
| 188 |
with gr.Tab("🎟️ Tokens"):
|
| 189 |
tok_table = gr.Dataframe(
|
|
@@ -236,30 +353,35 @@ def build_admin_tabs(admin_password: str = None):
|
|
| 236 |
queue=False, show_progress=False)
|
| 237 |
|
| 238 |
# ============ Settings ============
|
| 239 |
-
with gr.Tab("⚙️ Settings"):
|
| 240 |
-
gr.Markdown("Changes apply immediately.")
|
| 241 |
|
| 242 |
window = gr.Number(label="conversation_window (turns)",
|
| 243 |
-
value=
|
| 244 |
-
precision=0)
|
| 245 |
max_new = gr.Number(label="max_new_tokens",
|
| 246 |
-
value=
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
topp = gr.Number(label="top_p",
|
| 251 |
-
value=float(dbm.get_setting("top_p", "0.9")))
|
| 252 |
-
sys_prompt = gr.Textbox(label="system_prompt",
|
| 253 |
-
value=dbm.get_setting("system_prompt",
|
| 254 |
-
"You are a helpful assistant."),
|
| 255 |
-
lines=4)
|
| 256 |
daily_limit = gr.Number(label="daily_turn_limit",
|
| 257 |
-
value=
|
| 258 |
-
|
|
|
|
|
|
|
|
|
|
| 259 |
|
| 260 |
-
save_settings = gr.Button("💾 Save settings", variant="primary")
|
| 261 |
settings_out = gr.Markdown()
|
| 262 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 263 |
def _save(w, mn, t, tp, sp, dl):
|
| 264 |
dbm.set_setting("conversation_window", int(w))
|
| 265 |
dbm.set_setting("max_new_tokens", int(mn))
|
|
@@ -267,7 +389,9 @@ def build_admin_tabs(admin_password: str = None):
|
|
| 267 |
dbm.set_setting("top_p", float(tp))
|
| 268 |
dbm.set_setting("system_prompt", sp)
|
| 269 |
dbm.set_setting("daily_turn_limit", int(dl))
|
| 270 |
-
|
|
|
|
|
|
|
| 271 |
|
| 272 |
save_settings.click(
|
| 273 |
_save,
|
|
@@ -276,6 +400,18 @@ def build_admin_tabs(admin_password: str = None):
|
|
| 276 |
queue=False, show_progress=False,
|
| 277 |
)
|
| 278 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 279 |
# ============ Health ============
|
| 280 |
with gr.Tab("🩺 Health"):
|
| 281 |
health_md = gr.Markdown()
|
|
@@ -301,12 +437,6 @@ def build_admin_tabs(admin_password: str = None):
|
|
| 301 |
refresh_health.click(_recent, outputs=recent_gpu,
|
| 302 |
queue=False, show_progress=False)
|
| 303 |
|
| 304 |
-
# ----------------------------------------------------------
|
| 305 |
-
# Login wiring — must be OUTSIDE the `with gr.Column` block
|
| 306 |
-
# so it can see both `admin_area` and `_dash`.
|
| 307 |
-
# We chain .then(_dash) so the dashboard populates immediately
|
| 308 |
-
# after successful login (replaces the invalid .change() call).
|
| 309 |
-
# ----------------------------------------------------------
|
| 310 |
def _login(p):
|
| 311 |
if _gate(p):
|
| 312 |
return gr.update(visible=True), "✅ Logged in as admin."
|
|
@@ -318,9 +448,4 @@ def build_admin_tabs(admin_password: str = None):
|
|
| 318 |
outputs=[admin_area, status_md],
|
| 319 |
queue=False,
|
| 320 |
show_progress=False,
|
| 321 |
-
).then(
|
| 322 |
-
_dash,
|
| 323 |
-
outputs=dash_md,
|
| 324 |
-
queue=False,
|
| 325 |
-
show_progress=False,
|
| 326 |
)
|
|
|
|
| 18 |
|
| 19 |
|
| 20 |
def build_admin_tabs(admin_password: str = None):
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
global ADMIN_PASSWORD
|
| 22 |
if admin_password:
|
| 23 |
ADMIN_PASSWORD = admin_password
|
| 24 |
|
| 25 |
+
# --- Login gate ---
|
| 26 |
with gr.Row():
|
| 27 |
pw = gr.Textbox(label="Admin password", type="password", scale=3)
|
| 28 |
login_btn = gr.Button("Login", scale=1, variant="primary")
|
| 29 |
|
| 30 |
status_md = gr.Markdown("🔒 Locked. Enter password above.")
|
| 31 |
|
|
|
|
| 32 |
with gr.Column(visible=False) as admin_area:
|
| 33 |
with gr.Tabs():
|
| 34 |
|
|
|
|
| 116 |
del_btn.click(_del, inputs=[del_uid], outputs=del_out,
|
| 117 |
queue=False, show_progress=False)
|
| 118 |
|
| 119 |
+
# ============ Conversations (live context) ============
|
| 120 |
with gr.Tab("💬 Conversations"):
|
| 121 |
+
gr.Markdown(
|
| 122 |
+
"_Live context only — cleared by user `/reset`._ "
|
| 123 |
+
"See **📁 Sessions** tab for the permanent archive."
|
| 124 |
+
)
|
| 125 |
with gr.Row():
|
| 126 |
conv_uid = gr.Number(label="Filter user_id (blank = all)",
|
| 127 |
precision=0, scale=2)
|
|
|
|
| 150 |
outputs=conv_table,
|
| 151 |
queue=False, show_progress=False)
|
| 152 |
|
| 153 |
+
gr.Markdown("### Export (live + full archive)")
|
| 154 |
with gr.Row():
|
| 155 |
exp_json_btn = gr.Button("📦 JSON")
|
| 156 |
exp_csv_btn = gr.Button("📊 CSV")
|
|
|
|
| 183 |
exp_md_btn.click(_write_md, outputs=exp_file,
|
| 184 |
queue=False, show_progress=False)
|
| 185 |
|
| 186 |
+
# ============ Sessions (per-session markdown archive) ============
|
| 187 |
+
with gr.Tab("📁 Sessions"):
|
| 188 |
+
gr.Markdown(
|
| 189 |
+
"**Permanent archive.** One markdown file per session, "
|
| 190 |
+
"stored at `/data/chat_logs/{user_id}/{timestamp}.md`. "
|
| 191 |
+
"User `/reset` closes a session and starts a new file — "
|
| 192 |
+
"**nothing is ever deleted** except by admin action here."
|
| 193 |
+
)
|
| 194 |
+
|
| 195 |
+
with gr.Row():
|
| 196 |
+
sess_uid = gr.Number(label="Filter by user_id (blank = all)",
|
| 197 |
+
precision=0, scale=2)
|
| 198 |
+
sess_refresh = gr.Button("🔄 Refresh sessions",
|
| 199 |
+
variant="primary", scale=1)
|
| 200 |
+
|
| 201 |
+
sess_table = gr.Dataframe(
|
| 202 |
+
headers=["user_id", "filename", "size_bytes", "modified", "path"],
|
| 203 |
+
interactive=False,
|
| 204 |
+
wrap=True,
|
| 205 |
+
)
|
| 206 |
+
|
| 207 |
+
def _sess_rows(uid):
|
| 208 |
+
uid_int = int(uid) if uid else None
|
| 209 |
+
rows = dbm.archive_list(uid_int)
|
| 210 |
+
return [[r["user_id"], r["filename"], r["size"],
|
| 211 |
+
r["modified"], r["path"]] for r in rows]
|
| 212 |
+
|
| 213 |
+
sess_refresh.click(_sess_rows, inputs=[sess_uid],
|
| 214 |
+
outputs=sess_table,
|
| 215 |
+
queue=False, show_progress=False)
|
| 216 |
+
|
| 217 |
+
gr.Markdown("### View a session")
|
| 218 |
+
with gr.Row():
|
| 219 |
+
view_uid = gr.Number(label="User ID", precision=0, scale=1)
|
| 220 |
+
view_file = gr.Textbox(label="Filename (e.g. 20261016T123045.md)",
|
| 221 |
+
scale=3)
|
| 222 |
+
view_btn = gr.Button("📖 Load", variant="primary", scale=1)
|
| 223 |
+
|
| 224 |
+
view_code = gr.Code(label="Session transcript",
|
| 225 |
+
language="markdown",
|
| 226 |
+
lines=25,
|
| 227 |
+
interactive=False)
|
| 228 |
+
|
| 229 |
+
def _view(uid, fn):
|
| 230 |
+
if not uid or not fn:
|
| 231 |
+
return ""
|
| 232 |
+
return dbm.archive_read(int(uid), fn.strip())
|
| 233 |
+
|
| 234 |
+
view_btn.click(_view, inputs=[view_uid, view_file],
|
| 235 |
+
outputs=view_code,
|
| 236 |
+
queue=False, show_progress=False)
|
| 237 |
+
|
| 238 |
+
gr.Markdown("### Download full transcript for a user")
|
| 239 |
+
with gr.Row():
|
| 240 |
+
dl_uid = gr.Number(label="User ID", precision=0, scale=2)
|
| 241 |
+
dl_btn = gr.Button("📦 Build combined .md", scale=1)
|
| 242 |
+
dl_file = gr.File(label="Combined transcript", interactive=False)
|
| 243 |
+
|
| 244 |
+
def _build_combined(uid):
|
| 245 |
+
if not uid:
|
| 246 |
+
return None
|
| 247 |
+
p = f"/tmp/transcript_{int(uid)}.md"
|
| 248 |
+
with open(p, "w", encoding="utf-8") as f:
|
| 249 |
+
f.write(dbm.archive_export_user(int(uid)))
|
| 250 |
+
return p
|
| 251 |
+
|
| 252 |
+
dl_btn.click(_build_combined, inputs=[dl_uid],
|
| 253 |
+
outputs=dl_file,
|
| 254 |
+
queue=False, show_progress=False)
|
| 255 |
+
|
| 256 |
+
gr.Markdown("### Manage sessions")
|
| 257 |
+
with gr.Row():
|
| 258 |
+
del_uid = gr.Number(label="Delete one file — user_id",
|
| 259 |
+
precision=0, scale=1)
|
| 260 |
+
del_file = gr.Textbox(label="Filename", scale=3)
|
| 261 |
+
del_btn = gr.Button("🗑️ Delete file", variant="stop", scale=1)
|
| 262 |
+
del_out = gr.Markdown()
|
| 263 |
+
|
| 264 |
+
def _del_file(uid, fn):
|
| 265 |
+
if not uid or not fn:
|
| 266 |
+
return "Enter user_id and filename."
|
| 267 |
+
ok = dbm.archive_delete(int(uid), fn.strip())
|
| 268 |
+
return ("✅ Deleted." if ok else "⚠️ File not found.")
|
| 269 |
+
|
| 270 |
+
del_btn.click(_del_file, inputs=[del_uid, del_file],
|
| 271 |
+
outputs=del_out,
|
| 272 |
+
queue=False, show_progress=False)
|
| 273 |
+
|
| 274 |
+
with gr.Row():
|
| 275 |
+
del_user_uid = gr.Number(label="Delete ALL files for user_id",
|
| 276 |
+
precision=0, scale=2)
|
| 277 |
+
del_user_btn = gr.Button("🗑️ Delete user's sessions",
|
| 278 |
+
variant="stop", scale=1)
|
| 279 |
+
del_user_out = gr.Markdown()
|
| 280 |
+
|
| 281 |
+
def _del_user(uid):
|
| 282 |
+
if not uid:
|
| 283 |
+
return "Enter a user_id."
|
| 284 |
+
n = dbm.archive_delete_user(int(uid))
|
| 285 |
+
return f"🗑️ Deleted {n} session file(s) for {int(uid)}."
|
| 286 |
+
|
| 287 |
+
del_user_btn.click(_del_user, inputs=[del_user_uid],
|
| 288 |
+
outputs=del_user_out,
|
| 289 |
+
queue=False, show_progress=False)
|
| 290 |
+
|
| 291 |
+
gr.Markdown("---")
|
| 292 |
+
gr.Markdown("### ⚠️ Danger zone")
|
| 293 |
+
clear_all_btn = gr.Button("🗑️ Delete ALL sessions for ALL users",
|
| 294 |
+
variant="stop")
|
| 295 |
+
clear_all_out = gr.Markdown()
|
| 296 |
+
|
| 297 |
+
def _clear_all():
|
| 298 |
+
n = dbm.archive_delete_all()
|
| 299 |
+
return f"🗑️ Deleted {n} session file(s) total."
|
| 300 |
+
|
| 301 |
+
clear_all_btn.click(_clear_all, outputs=clear_all_out,
|
| 302 |
+
queue=False, show_progress=False)
|
| 303 |
+
|
| 304 |
# ============ Tokens ============
|
| 305 |
with gr.Tab("🎟️ Tokens"):
|
| 306 |
tok_table = gr.Dataframe(
|
|
|
|
| 353 |
queue=False, show_progress=False)
|
| 354 |
|
| 355 |
# ============ Settings ============
|
| 356 |
+
with gr.Tab("⚙️ Settings") as settings_tab:
|
| 357 |
+
gr.Markdown("Changes apply immediately. Use **🔄 Reload from DB** if values look stale.")
|
| 358 |
|
| 359 |
window = gr.Number(label="conversation_window (turns)",
|
| 360 |
+
value=6, precision=0)
|
|
|
|
| 361 |
max_new = gr.Number(label="max_new_tokens",
|
| 362 |
+
value=1024, precision=0)
|
| 363 |
+
temp = gr.Number(label="temperature", value=0.7)
|
| 364 |
+
topp = gr.Number(label="top_p", value=0.9)
|
| 365 |
+
sys_prompt = gr.Textbox(label="system_prompt", value="", lines=10)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 366 |
daily_limit = gr.Number(label="daily_turn_limit",
|
| 367 |
+
value=100, precision=0)
|
| 368 |
+
|
| 369 |
+
with gr.Row():
|
| 370 |
+
save_settings = gr.Button("💾 Save settings", variant="primary")
|
| 371 |
+
reload_settings = gr.Button("🔄 Reload from DB")
|
| 372 |
|
|
|
|
| 373 |
settings_out = gr.Markdown()
|
| 374 |
|
| 375 |
+
def _load_settings():
|
| 376 |
+
return (
|
| 377 |
+
int(dbm.get_setting("conversation_window", "6")),
|
| 378 |
+
int(dbm.get_setting("max_new_tokens", "1024")),
|
| 379 |
+
float(dbm.get_setting("temperature", "0.7")),
|
| 380 |
+
float(dbm.get_setting("top_p", "0.9")),
|
| 381 |
+
dbm.get_setting("system_prompt", "You are a helpful assistant."),
|
| 382 |
+
int(dbm.get_setting("daily_turn_limit", "100")),
|
| 383 |
+
)
|
| 384 |
+
|
| 385 |
def _save(w, mn, t, tp, sp, dl):
|
| 386 |
dbm.set_setting("conversation_window", int(w))
|
| 387 |
dbm.set_setting("max_new_tokens", int(mn))
|
|
|
|
| 389 |
dbm.set_setting("top_p", float(tp))
|
| 390 |
dbm.set_setting("system_prompt", sp)
|
| 391 |
dbm.set_setting("daily_turn_limit", int(dl))
|
| 392 |
+
import datetime as _d
|
| 393 |
+
return (f"✅ Saved at {_d.datetime.now().strftime('%H:%M:%S')}. "
|
| 394 |
+
f"Prompt length: {len(sp)} chars.")
|
| 395 |
|
| 396 |
save_settings.click(
|
| 397 |
_save,
|
|
|
|
| 400 |
queue=False, show_progress=False,
|
| 401 |
)
|
| 402 |
|
| 403 |
+
reload_settings.click(
|
| 404 |
+
_load_settings,
|
| 405 |
+
outputs=[window, max_new, temp, topp, sys_prompt, daily_limit],
|
| 406 |
+
queue=False, show_progress=False,
|
| 407 |
+
)
|
| 408 |
+
|
| 409 |
+
settings_tab.select(
|
| 410 |
+
_load_settings,
|
| 411 |
+
outputs=[window, max_new, temp, topp, sys_prompt, daily_limit],
|
| 412 |
+
queue=False, show_progress=False,
|
| 413 |
+
)
|
| 414 |
+
|
| 415 |
# ============ Health ============
|
| 416 |
with gr.Tab("🩺 Health"):
|
| 417 |
health_md = gr.Markdown()
|
|
|
|
| 437 |
refresh_health.click(_recent, outputs=recent_gpu,
|
| 438 |
queue=False, show_progress=False)
|
| 439 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 440 |
def _login(p):
|
| 441 |
if _gate(p):
|
| 442 |
return gr.update(visible=True), "✅ Logged in as admin."
|
|
|
|
| 448 |
outputs=[admin_area, status_md],
|
| 449 |
queue=False,
|
| 450 |
show_progress=False,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 451 |
)
|