Spaces:
Running on Zero
Running on Zero
feat(ui): m6 polish — in-memory history sidebar + _safe_call error wrapper
Browse filesH2 + H3 from the M6 plan, in one commit because the app.py hunks are
interleaved.
- _HISTORY list + _history_render + _history_push helpers, capped at 12
newest-first entries. Each on_*_click handler now returns a third
output (history HTML) wired into the dynamic gr.HTML in the sidebar.
- _safe_call wrapper centralises LoRAValidationError / ValueError /
FileNotFoundError / RuntimeError -> gr.Error translation so each
click handler stays a single call into modes.*. MPS RuntimeErrors get
the PYTORCH_ENABLE_MPS_FALLBACK hint.
- theme.CSS gains compact .ams-history-row styling (mono mode token +
ellipsis sans label) and hides the history block at the 640 px
mobile breakpoint.
app.py
CHANGED
|
@@ -67,6 +67,33 @@ def get_backend() -> be.ACEStepStudioBackend:
|
|
| 67 |
return _BACKEND
|
| 68 |
|
| 69 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
def _sha256(path: str) -> str:
|
| 71 |
"""Stream a file through SHA-256 in 64 KB chunks.
|
| 72 |
|
|
@@ -172,24 +199,23 @@ def on_generate_click(
|
|
| 172 |
progress=gr.Progress(track_tqdm=True), # noqa: B008
|
| 173 |
):
|
| 174 |
loras = [lora_state] if lora_state else []
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
return out_path, meta
|
| 193 |
|
| 194 |
|
| 195 |
def on_cover_click(
|
|
@@ -203,24 +229,24 @@ def on_cover_click(
|
|
| 203 |
):
|
| 204 |
"""Cover-mode click. ref_audio is a filepath from gr.Audio(type='filepath')."""
|
| 205 |
loras = [lora_state] if lora_state else []
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
|
| 225 |
|
| 226 |
def on_extend_click(
|
|
@@ -238,28 +264,28 @@ def on_extend_click(
|
|
| 238 |
):
|
| 239 |
"""Extend-mode click. seed_audio is a filepath from gr.Audio(type='filepath')."""
|
| 240 |
loras = [lora_state] if lora_state else []
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
|
| 264 |
|
| 265 |
def on_draft_lyrics(
|
|
@@ -283,27 +309,27 @@ def on_draft_lyrics(
|
|
| 283 |
``lyrics_lm``; the first click triggers a ~4 GB MLX download (cached
|
| 284 |
afterwards) and ~30 s warm-up before the draft appears.
|
| 285 |
"""
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
|
| 307 |
|
| 308 |
|
| 309 |
def on_separate_stems(audio_path):
|
|
@@ -357,31 +383,31 @@ def on_edit_click(
|
|
| 357 |
):
|
| 358 |
"""Edit-mode click. source_audio is a filepath from gr.Audio(type='filepath')."""
|
| 359 |
loras = [lora_state] if lora_state else []
|
| 360 |
-
|
| 361 |
-
|
| 362 |
-
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
|
| 369 |
-
|
| 370 |
-
|
| 371 |
-
|
| 372 |
-
|
| 373 |
-
|
| 374 |
-
|
| 375 |
-
|
| 376 |
-
|
| 377 |
-
|
| 378 |
-
|
| 379 |
-
|
| 380 |
-
|
| 381 |
-
|
| 382 |
-
|
| 383 |
-
|
| 384 |
-
|
| 385 |
|
| 386 |
|
| 387 |
HEADER_HTML = """
|
|
@@ -425,6 +451,41 @@ HISTORY_HTML = """
|
|
| 425 |
""".strip()
|
| 426 |
|
| 427 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 428 |
MODE_CHOICES = [
|
| 429 |
("🎵 Generate", "generate"),
|
| 430 |
("🎤 Cover", "cover"),
|
|
@@ -460,7 +521,11 @@ def build_app() -> gr.Blocks:
|
|
| 460 |
container=False,
|
| 461 |
elem_classes=["ams-side-radio"],
|
| 462 |
)
|
| 463 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 464 |
|
| 465 |
# --- Content ----------------------------------------------------
|
| 466 |
with gr.Column(scale=10, elem_classes=["ams-content"]):
|
|
@@ -490,7 +555,7 @@ def build_app() -> gr.Blocks:
|
|
| 490 |
g["instrumental"],
|
| 491 |
g["lora_state"],
|
| 492 |
],
|
| 493 |
-
outputs=[g["output_audio"], g["output_meta"]],
|
| 494 |
)
|
| 495 |
# Post-processing actions (M5/G2)
|
| 496 |
g["separate_stems_btn"].click(
|
|
@@ -535,7 +600,7 @@ def build_app() -> gr.Blocks:
|
|
| 535 |
c["audio_cover_strength"],
|
| 536 |
c["lora_state"],
|
| 537 |
],
|
| 538 |
-
outputs=[c["output_audio"], c["output_meta"]],
|
| 539 |
)
|
| 540 |
# Post-processing actions (M5/G2)
|
| 541 |
c["separate_stems_btn"].click(
|
|
@@ -584,7 +649,7 @@ def build_app() -> gr.Blocks:
|
|
| 584 |
x["chunk_mask_mode"],
|
| 585 |
x["lora_state"],
|
| 586 |
],
|
| 587 |
-
outputs=[x["output_audio"], x["output_meta"]],
|
| 588 |
)
|
| 589 |
# Post-processing actions (M5/G2)
|
| 590 |
x["separate_stems_btn"].click(
|
|
@@ -636,7 +701,7 @@ def build_app() -> gr.Blocks:
|
|
| 636 |
e["flow_n_avg"],
|
| 637 |
e["lora_state"],
|
| 638 |
],
|
| 639 |
-
outputs=[e["output_audio"], e["output_meta"]],
|
| 640 |
)
|
| 641 |
# Post-processing actions (M5/G2)
|
| 642 |
e["separate_stems_btn"].click(
|
|
@@ -673,7 +738,7 @@ def build_app() -> gr.Blocks:
|
|
| 673 |
lyr["max_new_tokens"],
|
| 674 |
lyr["seed"],
|
| 675 |
],
|
| 676 |
-
outputs=[lyr["lyrics_output"], lyr["meta_output"]],
|
| 677 |
)
|
| 678 |
# Cross-tab "Use these in Generate" — pipes the drafted
|
| 679 |
# text straight into the Generate tab's lyrics textbox.
|
|
|
|
| 67 |
return _BACKEND
|
| 68 |
|
| 69 |
|
| 70 |
+
def _safe_call(fn, *args, **kwargs):
|
| 71 |
+
"""Wrap a mode handler so all known exceptions become friendly gr.Error toasts.
|
| 72 |
+
|
| 73 |
+
Centralising this here lets every on_*_click handler stay a single-line
|
| 74 |
+
call into modes.* without each one repeating the try/except mosaic. The
|
| 75 |
+
error classes mirror what each mode handler can actually raise:
|
| 76 |
+
|
| 77 |
+
- ``lora_stack.LoRAValidationError`` — uploaded LoRA isn't compatible
|
| 78 |
+
- ``ValueError`` — mode-handler param validation (missing prompt, etc.)
|
| 79 |
+
- ``FileNotFoundError`` — user-supplied ref_audio path doesn't exist
|
| 80 |
+
- ``RuntimeError`` — pipeline crash, including MPS op-fallback failures
|
| 81 |
+
"""
|
| 82 |
+
try:
|
| 83 |
+
return fn(*args, **kwargs)
|
| 84 |
+
except lora_stack.LoRAValidationError as e:
|
| 85 |
+
raise gr.Error(str(e)) from e
|
| 86 |
+
except ValueError as e:
|
| 87 |
+
raise gr.Error(str(e)) from e
|
| 88 |
+
except FileNotFoundError as e:
|
| 89 |
+
raise gr.Error(f"File not found: {e}") from e
|
| 90 |
+
except RuntimeError as e:
|
| 91 |
+
msg = str(e)
|
| 92 |
+
if "MPS" in msg or "mps" in msg:
|
| 93 |
+
raise gr.Error(f"Apple Silicon op issue: {msg}. PYTORCH_ENABLE_MPS_FALLBACK is enabled.") from e
|
| 94 |
+
raise gr.Error(f"Generation failed: {msg}") from e
|
| 95 |
+
|
| 96 |
+
|
| 97 |
def _sha256(path: str) -> str:
|
| 98 |
"""Stream a file through SHA-256 in 64 KB chunks.
|
| 99 |
|
|
|
|
| 199 |
progress=gr.Progress(track_tqdm=True), # noqa: B008
|
| 200 |
):
|
| 201 |
loras = [lora_state] if lora_state else []
|
| 202 |
+
out_path, meta = _safe_call(
|
| 203 |
+
modes.generate,
|
| 204 |
+
get_backend(),
|
| 205 |
+
params={
|
| 206 |
+
"prompt": prompt,
|
| 207 |
+
"lyrics": lyrics,
|
| 208 |
+
"duration_s": int(duration_s),
|
| 209 |
+
"instrumental": instrumental_label == "Instrumental",
|
| 210 |
+
"seed": random.randint(1, 2_147_483_647),
|
| 211 |
+
"loras": loras,
|
| 212 |
+
"advanced": {},
|
| 213 |
+
"lm": {},
|
| 214 |
+
"dcw": {},
|
| 215 |
+
},
|
| 216 |
+
)
|
| 217 |
+
new_history = _history_push("generate", prompt or "(no prompt)")
|
| 218 |
+
return out_path, meta, new_history
|
|
|
|
| 219 |
|
| 220 |
|
| 221 |
def on_cover_click(
|
|
|
|
| 229 |
):
|
| 230 |
"""Cover-mode click. ref_audio is a filepath from gr.Audio(type='filepath')."""
|
| 231 |
loras = [lora_state] if lora_state else []
|
| 232 |
+
out_path, meta = _safe_call(
|
| 233 |
+
modes.cover,
|
| 234 |
+
get_backend(),
|
| 235 |
+
params={
|
| 236 |
+
"ref_audio": ref_audio,
|
| 237 |
+
"prompt": prompt,
|
| 238 |
+
"lyrics": lyrics,
|
| 239 |
+
"duration_s": int(duration_s),
|
| 240 |
+
"audio_cover_strength": float(audio_cover_strength),
|
| 241 |
+
"seed": random.randint(1, 2_147_483_647),
|
| 242 |
+
"loras": loras,
|
| 243 |
+
"advanced": {},
|
| 244 |
+
"lm": {},
|
| 245 |
+
"dcw": {},
|
| 246 |
+
},
|
| 247 |
+
)
|
| 248 |
+
new_history = _history_push("cover", prompt or "(cover)")
|
| 249 |
+
return out_path, meta, new_history
|
| 250 |
|
| 251 |
|
| 252 |
def on_extend_click(
|
|
|
|
| 264 |
):
|
| 265 |
"""Extend-mode click. seed_audio is a filepath from gr.Audio(type='filepath')."""
|
| 266 |
loras = [lora_state] if lora_state else []
|
| 267 |
+
out_path, meta = _safe_call(
|
| 268 |
+
modes.extend,
|
| 269 |
+
get_backend(),
|
| 270 |
+
params={
|
| 271 |
+
"seed_audio": seed_audio,
|
| 272 |
+
"extra_prompt": extra_prompt,
|
| 273 |
+
"extension_lyrics": extension_lyrics,
|
| 274 |
+
"extra_duration_s": int(extra_duration_s),
|
| 275 |
+
"wav_crossfade_s": float(wav_crossfade_s),
|
| 276 |
+
"repaint_mode": repaint_mode,
|
| 277 |
+
"repaint_strength": float(repaint_strength),
|
| 278 |
+
"latent_crossfade_frames": int(latent_crossfade_frames),
|
| 279 |
+
"chunk_mask_mode": chunk_mask_mode,
|
| 280 |
+
"seed": random.randint(1, 2_147_483_647),
|
| 281 |
+
"loras": loras,
|
| 282 |
+
"advanced": {},
|
| 283 |
+
"lm": {},
|
| 284 |
+
"dcw": {},
|
| 285 |
+
},
|
| 286 |
+
)
|
| 287 |
+
new_history = _history_push("extend", extra_prompt or "(extend)")
|
| 288 |
+
return out_path, meta, new_history
|
| 289 |
|
| 290 |
|
| 291 |
def on_draft_lyrics(
|
|
|
|
| 309 |
``lyrics_lm``; the first click triggers a ~4 GB MLX download (cached
|
| 310 |
afterwards) and ~30 s warm-up before the draft appears.
|
| 311 |
"""
|
| 312 |
+
lyrics_text, meta = _safe_call(
|
| 313 |
+
modes.lyrics,
|
| 314 |
+
get_backend(),
|
| 315 |
+
params={
|
| 316 |
+
"brief": brief,
|
| 317 |
+
"structure": structure,
|
| 318 |
+
"language": language,
|
| 319 |
+
"tone": tone,
|
| 320 |
+
"verse_lines": int(verse_lines),
|
| 321 |
+
"chorus_lines": int(chorus_lines),
|
| 322 |
+
"bridge_lines": int(bridge_lines),
|
| 323 |
+
"rhyme": rhyme,
|
| 324 |
+
"temperature": float(temperature),
|
| 325 |
+
"top_p": float(top_p),
|
| 326 |
+
"top_k": int(top_k),
|
| 327 |
+
"max_new_tokens": int(max_new_tokens),
|
| 328 |
+
"seed": int(seed) if seed is not None else None,
|
| 329 |
+
},
|
| 330 |
+
)
|
| 331 |
+
new_history = _history_push("lyrics", brief or "(brief)")
|
| 332 |
+
return lyrics_text, meta, new_history
|
| 333 |
|
| 334 |
|
| 335 |
def on_separate_stems(audio_path):
|
|
|
|
| 383 |
):
|
| 384 |
"""Edit-mode click. source_audio is a filepath from gr.Audio(type='filepath')."""
|
| 385 |
loras = [lora_state] if lora_state else []
|
| 386 |
+
out_path, meta = _safe_call(
|
| 387 |
+
modes.edit,
|
| 388 |
+
get_backend(),
|
| 389 |
+
params={
|
| 390 |
+
"source_audio": source_audio,
|
| 391 |
+
"sub_mode": sub_mode,
|
| 392 |
+
"source_lyrics": source_lyrics,
|
| 393 |
+
"target_lyrics": target_lyrics,
|
| 394 |
+
"segment_start_s": float(segment_start_s),
|
| 395 |
+
"segment_end_s": float(segment_end_s),
|
| 396 |
+
"repaint_strength": float(repaint_strength),
|
| 397 |
+
"repaint_mode": repaint_mode,
|
| 398 |
+
"flow_source_caption": flow_source_caption,
|
| 399 |
+
"flow_n_min": float(flow_n_min),
|
| 400 |
+
"flow_n_max": float(flow_n_max),
|
| 401 |
+
"flow_n_avg": int(flow_n_avg),
|
| 402 |
+
"seed": random.randint(1, 2_147_483_647),
|
| 403 |
+
"loras": loras,
|
| 404 |
+
"advanced": {},
|
| 405 |
+
"lm": {},
|
| 406 |
+
"dcw": {},
|
| 407 |
+
},
|
| 408 |
+
)
|
| 409 |
+
new_history = _history_push("edit", target_lyrics or sub_mode or "(edit)")
|
| 410 |
+
return out_path, meta, new_history
|
| 411 |
|
| 412 |
|
| 413 |
HEADER_HTML = """
|
|
|
|
| 451 |
""".strip()
|
| 452 |
|
| 453 |
|
| 454 |
+
# --- In-memory history (M6/H2) ----------------------------------------------
|
| 455 |
+
# Per spec §13, persistent history is out of scope for v1. The sidebar block
|
| 456 |
+
# is an in-process list that lives for the lifetime of the Gradio process and
|
| 457 |
+
# resets on reload. Newest entries first; capped at _HISTORY_MAX so the
|
| 458 |
+
# bordered sidebar stays compact at the desktop breakpoint.
|
| 459 |
+
_HISTORY: list[dict] = []
|
| 460 |
+
_HISTORY_MAX = 12
|
| 461 |
+
|
| 462 |
+
|
| 463 |
+
def _history_render() -> str:
|
| 464 |
+
"""Render _HISTORY into the sidebar HTML block.
|
| 465 |
+
|
| 466 |
+
Falls back to the empty-state HTML constant when no rows are present so
|
| 467 |
+
the placeholder copy stays exactly aligned with the wireframe.
|
| 468 |
+
"""
|
| 469 |
+
if not _HISTORY:
|
| 470 |
+
return HISTORY_HTML
|
| 471 |
+
rows_html = "\n".join(
|
| 472 |
+
f'<div class="ams-history-row" title="{h["label"]}">'
|
| 473 |
+
f'<span class="ams-history-mode">{h["mode"]}</span>'
|
| 474 |
+
f'<span class="ams-history-label">{h["label"]}</span>'
|
| 475 |
+
f"</div>"
|
| 476 |
+
for h in _HISTORY
|
| 477 |
+
)
|
| 478 |
+
return f'<div class="ams-history"><div class="ams-history-title">History · session</div>{rows_html}</div>'
|
| 479 |
+
|
| 480 |
+
|
| 481 |
+
def _history_push(mode: str, label: str) -> str:
|
| 482 |
+
"""Push a generation onto the history and return the new HTML."""
|
| 483 |
+
_HISTORY.insert(0, {"mode": mode, "label": (label or "").strip()[:30] or "(untitled)"})
|
| 484 |
+
while len(_HISTORY) > _HISTORY_MAX:
|
| 485 |
+
_HISTORY.pop()
|
| 486 |
+
return _history_render()
|
| 487 |
+
|
| 488 |
+
|
| 489 |
MODE_CHOICES = [
|
| 490 |
("🎵 Generate", "generate"),
|
| 491 |
("🎤 Cover", "cover"),
|
|
|
|
| 521 |
container=False,
|
| 522 |
elem_classes=["ams-side-radio"],
|
| 523 |
)
|
| 524 |
+
# Dynamic in-memory history (M6/H2). Initial value renders
|
| 525 |
+
# the same "No generations yet" placeholder the static block
|
| 526 |
+
# used to emit; each click handler refreshes the HTML via
|
| 527 |
+
# _history_push().
|
| 528 |
+
history_html = gr.HTML(HISTORY_HTML, elem_classes=["ams-history-wrapper"])
|
| 529 |
|
| 530 |
# --- Content ----------------------------------------------------
|
| 531 |
with gr.Column(scale=10, elem_classes=["ams-content"]):
|
|
|
|
| 555 |
g["instrumental"],
|
| 556 |
g["lora_state"],
|
| 557 |
],
|
| 558 |
+
outputs=[g["output_audio"], g["output_meta"], history_html],
|
| 559 |
)
|
| 560 |
# Post-processing actions (M5/G2)
|
| 561 |
g["separate_stems_btn"].click(
|
|
|
|
| 600 |
c["audio_cover_strength"],
|
| 601 |
c["lora_state"],
|
| 602 |
],
|
| 603 |
+
outputs=[c["output_audio"], c["output_meta"], history_html],
|
| 604 |
)
|
| 605 |
# Post-processing actions (M5/G2)
|
| 606 |
c["separate_stems_btn"].click(
|
|
|
|
| 649 |
x["chunk_mask_mode"],
|
| 650 |
x["lora_state"],
|
| 651 |
],
|
| 652 |
+
outputs=[x["output_audio"], x["output_meta"], history_html],
|
| 653 |
)
|
| 654 |
# Post-processing actions (M5/G2)
|
| 655 |
x["separate_stems_btn"].click(
|
|
|
|
| 701 |
e["flow_n_avg"],
|
| 702 |
e["lora_state"],
|
| 703 |
],
|
| 704 |
+
outputs=[e["output_audio"], e["output_meta"], history_html],
|
| 705 |
)
|
| 706 |
# Post-processing actions (M5/G2)
|
| 707 |
e["separate_stems_btn"].click(
|
|
|
|
| 738 |
lyr["max_new_tokens"],
|
| 739 |
lyr["seed"],
|
| 740 |
],
|
| 741 |
+
outputs=[lyr["lyrics_output"], lyr["meta_output"], history_html],
|
| 742 |
)
|
| 743 |
# Cross-tab "Use these in Generate" — pipes the drafted
|
| 744 |
# text straight into the Generate tab's lyrics textbox.
|
theme.py
CHANGED
|
@@ -954,6 +954,53 @@ main, .contain {{
|
|
| 954 |
}}
|
| 955 |
}}
|
| 956 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 957 |
/* Hide Gradio footer + the floating "Use via API" / settings panel */
|
| 958 |
footer {{ display:none !important; }}
|
| 959 |
.show-api {{ display:none !important; }}
|
|
|
|
| 954 |
}}
|
| 955 |
}}
|
| 956 |
|
| 957 |
+
/* ============================================================
|
| 958 |
+
* History rows — clickable-looking compact list (M6/H2)
|
| 959 |
+
* Replaces the static "No generations yet" placeholder with a live
|
| 960 |
+
* in-memory feed of mode + label tuples. The mode segment renders in
|
| 961 |
+
* mono uppercase to mirror the small uppercase labels used throughout
|
| 962 |
+
* the sidebar; the label segment uses the sans body face and truncates
|
| 963 |
+
* with ellipsis at the sidebar's compact 188-210 px width.
|
| 964 |
+
* ============================================================ */
|
| 965 |
+
.ams-content .ams-history-wrapper {{
|
| 966 |
+
margin-top: 4px;
|
| 967 |
+
}}
|
| 968 |
+
.ams-history-row {{
|
| 969 |
+
display: flex;
|
| 970 |
+
gap: 6px;
|
| 971 |
+
align-items: baseline;
|
| 972 |
+
font-family: {FONT_MONO};
|
| 973 |
+
font-size: 10px;
|
| 974 |
+
color: {INK_MUTED};
|
| 975 |
+
padding: 4px 6px;
|
| 976 |
+
border-radius: 3px;
|
| 977 |
+
cursor: default;
|
| 978 |
+
}}
|
| 979 |
+
.ams-history-row:hover {{
|
| 980 |
+
background: {HOVER_BG};
|
| 981 |
+
color: {INK};
|
| 982 |
+
}}
|
| 983 |
+
.ams-history-mode {{
|
| 984 |
+
color: {PRIMARY};
|
| 985 |
+
text-transform: lowercase;
|
| 986 |
+
letter-spacing: 0;
|
| 987 |
+
flex-shrink: 0;
|
| 988 |
+
}}
|
| 989 |
+
.ams-history-label {{
|
| 990 |
+
color: {INK_MUTED};
|
| 991 |
+
overflow: hidden;
|
| 992 |
+
text-overflow: ellipsis;
|
| 993 |
+
white-space: nowrap;
|
| 994 |
+
font-family: {FONT_SANS};
|
| 995 |
+
font-size: 11px;
|
| 996 |
+
}}
|
| 997 |
+
@media (max-width: 640px) {{
|
| 998 |
+
.ams-history,
|
| 999 |
+
.ams-history-wrapper {{
|
| 1000 |
+
display: none !important;
|
| 1001 |
+
}}
|
| 1002 |
+
}}
|
| 1003 |
+
|
| 1004 |
/* Hide Gradio footer + the floating "Use via API" / settings panel */
|
| 1005 |
footer {{ display:none !important; }}
|
| 1006 |
.show-api {{ display:none !important; }}
|