Upload app.py
Browse files
app.py
CHANGED
|
@@ -77,7 +77,14 @@ def get_conn():
|
|
| 77 |
|
| 78 |
def init_db():
|
| 79 |
with get_conn() as conn:
|
| 80 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
conn.execute("""
|
| 82 |
CREATE TABLE IF NOT EXISTS users (
|
| 83 |
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
@@ -418,110 +425,141 @@ def reviewer_lang_choices(session):
|
|
| 418 |
return [(l["name"], l["id"]) for l in session["languages"]]
|
| 419 |
|
| 420 |
|
| 421 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 422 |
session = gr.State(None) # logged-in user session dict
|
| 423 |
current_sample = gr.State(None) # sample id currently being rated
|
| 424 |
|
| 425 |
-
gr.Markdown("# π§ Plotweaver AI β TTS MOS Evaluation
|
|
|
|
| 426 |
|
| 427 |
# ----------------------------- AUTH ------------------------------------ #
|
| 428 |
-
with gr.Column(visible=True) as auth_col:
|
| 429 |
-
gr.
|
| 430 |
-
|
| 431 |
-
|
| 432 |
-
|
| 433 |
-
|
| 434 |
-
|
| 435 |
-
|
| 436 |
-
|
| 437 |
-
|
| 438 |
-
|
| 439 |
-
|
| 440 |
-
|
| 441 |
-
|
| 442 |
-
|
| 443 |
-
|
| 444 |
-
|
| 445 |
|
| 446 |
# ----------------------------- APP ------------------------------------- #
|
| 447 |
with gr.Column(visible=False) as app_col:
|
| 448 |
-
with gr.Row():
|
| 449 |
greeting = gr.Markdown()
|
| 450 |
-
logout_btn = gr.Button("Log out", scale=0)
|
| 451 |
|
| 452 |
-
with gr.Tabs()
|
| 453 |
# ---------- Rate tab ----------
|
| 454 |
with gr.Tab("Rate samples"):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 455 |
gr.Markdown(
|
| 456 |
-
"
|
| 457 |
-
|
|
|
|
| 458 |
)
|
| 459 |
-
with gr.Row():
|
| 460 |
-
rate_lang = gr.Dropdown(label="Language", choices=[], interactive=True)
|
| 461 |
-
rate_sample = gr.Dropdown(label="Sample (β = already rated)", choices=[], interactive=True)
|
| 462 |
-
next_btn = gr.Button("Next unrated βΆ", scale=0)
|
| 463 |
-
rate_audio = gr.Audio(label="Audio sample", type="filepath", interactive=False)
|
| 464 |
|
| 465 |
criterion_inputs = {}
|
| 466 |
-
with gr.
|
| 467 |
-
|
| 468 |
-
|
| 469 |
-
|
| 470 |
-
|
| 471 |
-
|
| 472 |
-
|
| 473 |
-
|
| 474 |
-
|
| 475 |
-
|
| 476 |
-
rate_comments = gr.Textbox(
|
| 477 |
-
|
| 478 |
-
|
|
|
|
|
|
|
| 479 |
rate_msg = gr.Markdown()
|
| 480 |
|
| 481 |
# ---------- Progress tab ----------
|
| 482 |
with gr.Tab("My progress"):
|
| 483 |
-
refresh_prog_btn = gr.Button("Refresh")
|
| 484 |
progress_md = gr.Markdown()
|
| 485 |
-
progress_tbl = gr.Dataframe(
|
|
|
|
| 486 |
|
| 487 |
# ---------- Profile tab ----------
|
| 488 |
with gr.Tab("My languages"):
|
| 489 |
-
gr.Markdown("
|
| 490 |
prof_langs = gr.CheckboxGroup(label="Languages", choices=lang_choices())
|
| 491 |
-
prof_save = gr.Button("Save", variant="primary")
|
| 492 |
prof_msg = gr.Markdown()
|
| 493 |
|
| 494 |
# ---------- Admin tab ----------
|
| 495 |
with gr.Tab("Admin", visible=False) as admin_tab:
|
| 496 |
gr.Markdown("### Languages")
|
| 497 |
with gr.Row():
|
| 498 |
-
al_code = gr.Textbox(label="Code", info="e.g. yo, ha, ig, pcm, en-NG")
|
| 499 |
-
al_name = gr.Textbox(label="Name", info="e.g. Yoruba")
|
| 500 |
-
al_btn = gr.Button("Add language", scale=0)
|
| 501 |
al_msg = gr.Markdown()
|
| 502 |
-
langs_tbl = gr.Dataframe(headers=["id", "code", "name"], interactive=False,
|
|
|
|
| 503 |
|
| 504 |
gr.Markdown("### Upload audio samples")
|
| 505 |
with gr.Row():
|
| 506 |
-
up_lang = gr.Dropdown(label="Language", choices=lang_choices()
|
|
|
|
| 507 |
up_model = gr.Textbox(label="Model / system name", value="",
|
| 508 |
-
info="e.g. F5-TTS, XTTS-v2, MMS-TTS, human. Hidden from reviewers."
|
|
|
|
| 509 |
up_files = gr.File(label="Audio files (wav/mp3/flac/ogg)", file_count="multiple",
|
| 510 |
file_types=["audio"])
|
| 511 |
with gr.Row():
|
| 512 |
up_isref = gr.Checkbox(label="These are reference / human anchor samples", value=False)
|
| 513 |
up_transcript = gr.Textbox(label="Transcript (optional, applies to all uploaded)", scale=2)
|
| 514 |
-
up_btn = gr.Button("Upload", variant="primary")
|
| 515 |
up_msg = gr.Markdown()
|
| 516 |
samples_tbl = gr.Dataframe(
|
| 517 |
headers=["id", "language", "sample_name", "model", "reference"],
|
| 518 |
interactive=False, label="Samples")
|
| 519 |
with gr.Row():
|
| 520 |
del_sample_id = gr.Number(label="Delete sample by id", precision=0)
|
| 521 |
-
del_btn = gr.Button("Delete", variant="stop", scale=0)
|
| 522 |
|
| 523 |
gr.Markdown("### Users")
|
| 524 |
-
refresh_users_btn = gr.Button("Refresh users")
|
| 525 |
users_tbl = gr.Dataframe(
|
| 526 |
headers=["id", "username", "role", "active", "languages", "ratings"],
|
| 527 |
interactive=False)
|
|
@@ -529,14 +567,15 @@ with gr.Blocks(title="Plotweaver AI β TTS MOS Evaluation", theme=gr.themes.Sof
|
|
| 529 |
promote_id = gr.Number(label="User id", precision=0)
|
| 530 |
role_choice = gr.Dropdown(label="Set role", choices=["reviewer", "admin"], value="reviewer")
|
| 531 |
active_choice = gr.Dropdown(label="Set active", choices=["yes", "no"], value="yes")
|
| 532 |
-
update_user_btn = gr.Button("Apply", scale=0)
|
| 533 |
user_admin_msg = gr.Markdown()
|
| 534 |
|
| 535 |
gr.Markdown("### Results")
|
| 536 |
with gr.Row():
|
| 537 |
-
res_lang = gr.Dropdown(label="Language", choices=lang_choices()
|
| 538 |
-
|
| 539 |
-
|
|
|
|
| 540 |
res_summary = gr.Markdown()
|
| 541 |
res_model_tbl = gr.Dataframe(label="MOS by model", interactive=False)
|
| 542 |
res_sample_tbl = gr.Dataframe(label="MOS by sample", interactive=False)
|
|
@@ -550,26 +589,36 @@ with gr.Blocks(title="Plotweaver AI β TTS MOS Evaluation", theme=gr.themes.Sof
|
|
| 550 |
def do_login(username, password):
|
| 551 |
user = authenticate(username, password)
|
| 552 |
if not user:
|
|
|
|
| 553 |
return (gr.update(), gr.update(), None, "β Invalid username or password.",
|
| 554 |
-
gr.update(), gr.update(), gr.update(), gr.update()
|
|
|
|
| 555 |
sess = user_session(user["id"])
|
| 556 |
is_admin = sess["role"] == "admin"
|
| 557 |
rl = reviewer_lang_choices(sess)
|
| 558 |
return (
|
| 559 |
gr.update(visible=False), # auth_col
|
| 560 |
gr.update(visible=True), # app_col
|
| 561 |
-
sess, # session
|
| 562 |
"", # li_msg
|
| 563 |
-
gr.update(value=f"Signed in as **{sess['username']}**
|
| 564 |
gr.update(visible=is_admin), # admin_tab
|
| 565 |
-
gr.update(choices=rl, value=
|
| 566 |
gr.update(choices=lang_choices(),
|
| 567 |
-
value=[l["id"] for l in sess["languages"]]),
|
|
|
|
|
|
|
| 568 |
)
|
| 569 |
|
| 570 |
li_btn.click(
|
| 571 |
do_login, [li_user, li_pw],
|
| 572 |
-
[auth_col, app_col, session, li_msg, greeting, admin_tab, rate_lang, prof_langs
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 573 |
)
|
| 574 |
|
| 575 |
def do_signup(username, email, password, lang_ids, code):
|
|
@@ -577,10 +626,14 @@ with gr.Blocks(title="Plotweaver AI β TTS MOS Evaluation", theme=gr.themes.Sof
|
|
| 577 |
try:
|
| 578 |
create_user(username, email, password, role=role, language_ids=lang_ids or [])
|
| 579 |
except ValueError as e:
|
| 580 |
-
|
| 581 |
-
|
| 582 |
-
|
| 583 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 584 |
|
| 585 |
def do_logout():
|
| 586 |
return (gr.update(visible=True), gr.update(visible=False), None, "")
|
|
@@ -596,7 +649,6 @@ with gr.Blocks(title="Plotweaver AI β TTS MOS Evaluation", theme=gr.themes.Sof
|
|
| 596 |
rate_lang.change(load_samples_for_lang, [session, rate_lang], [rate_sample])
|
| 597 |
|
| 598 |
def load_sample(sess, sample_id):
|
| 599 |
-
"""Load audio + any existing rating into the form."""
|
| 600 |
if not sample_id:
|
| 601 |
return (None, None, *[None] * len(CRITERIA_KEYS), "")
|
| 602 |
with get_conn() as conn:
|
|
@@ -622,30 +674,28 @@ with gr.Blocks(title="Plotweaver AI β TTS MOS Evaluation", theme=gr.themes.Sof
|
|
| 622 |
return gr.update(value=nxt)
|
| 623 |
next_btn.click(go_next_unrated, [session, rate_lang], [rate_sample])
|
| 624 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 625 |
def submit_rating(sess, sample_id, comments, *scores):
|
| 626 |
if not sess:
|
| 627 |
-
return "β Not signed in."
|
| 628 |
if not sample_id:
|
| 629 |
-
return "β No sample selected."
|
| 630 |
score_map = dict(zip(CRITERIA_KEYS, scores))
|
| 631 |
missing = [lbl for (k, lbl, _) in CRITERIA if score_map.get(k) in (None, "")]
|
| 632 |
if missing:
|
| 633 |
-
return
|
| 634 |
upsert_rating(sess["id"], sample_id, score_map, comments)
|
| 635 |
-
|
| 636 |
-
items = samples_for_reviewer(sess["id"],
|
| 637 |
rated = sum(1 for s in items if s["rated"])
|
| 638 |
-
return
|
| 639 |
-
|
| 640 |
-
def score_lang(sess, sample_id):
|
| 641 |
-
with get_conn() as conn:
|
| 642 |
-
row = conn.execute("SELECT language_id FROM samples WHERE id=?", (sample_id,)).fetchone()
|
| 643 |
-
return row["language_id"] if row else None
|
| 644 |
|
| 645 |
submit_btn.click(
|
| 646 |
-
submit_rating,
|
| 647 |
-
[session, current_sample, rate_comments, *rate_outputs],
|
| 648 |
-
[rate_msg, rate_sample],
|
| 649 |
).then(
|
| 650 |
load_samples_for_lang, [session, rate_lang], [rate_sample]
|
| 651 |
)
|
|
@@ -667,24 +717,14 @@ with gr.Blocks(title="Plotweaver AI β TTS MOS Evaluation", theme=gr.themes.Sof
|
|
| 667 |
# ---- Profile ----
|
| 668 |
def save_profile(sess, lang_ids):
|
| 669 |
if not sess:
|
| 670 |
-
return "β Not signed in.",
|
| 671 |
set_user_languages(sess["id"], lang_ids or [])
|
| 672 |
new_sess = user_session(sess["id"])
|
| 673 |
rl = reviewer_lang_choices(new_sess)
|
| 674 |
-
return
|
| 675 |
-
gr.update(choices=rl, value=(rl[0][1] if rl else None)))
|
| 676 |
prof_save.click(save_profile, [session, prof_langs], [prof_msg, session, rate_lang])
|
| 677 |
|
| 678 |
# ---- Admin: languages ----
|
| 679 |
-
def admin_add_language(sess, code, name):
|
| 680 |
-
if not sess or sess["role"] != "admin":
|
| 681 |
-
return "β Admin only.", _languages_table(), *_lang_dropdown_updates()
|
| 682 |
-
try:
|
| 683 |
-
add_language(code, name)
|
| 684 |
-
except ValueError as e:
|
| 685 |
-
return f"β {e}", _languages_table(), *_lang_dropdown_updates()
|
| 686 |
-
return f"β
Added {name} ({code}).", _languages_table(), *_lang_dropdown_updates()
|
| 687 |
-
|
| 688 |
def _languages_table():
|
| 689 |
return [[l["id"], l["code"], l["name"]] for l in list_languages()]
|
| 690 |
|
|
@@ -692,12 +732,24 @@ with gr.Blocks(title="Plotweaver AI β TTS MOS Evaluation", theme=gr.themes.Sof
|
|
| 692 |
ch = lang_choices()
|
| 693 |
return (gr.update(choices=ch), gr.update(choices=ch), gr.update(choices=ch), gr.update(choices=ch))
|
| 694 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 695 |
al_btn.click(
|
| 696 |
admin_add_language, [session, al_code, al_name],
|
| 697 |
[al_msg, langs_tbl, up_lang, res_lang, su_langs, prof_langs],
|
| 698 |
)
|
| 699 |
|
| 700 |
# ---- Admin: samples ----
|
|
|
|
|
|
|
|
|
|
|
|
|
| 701 |
def admin_upload(sess, language_id, files, model, is_ref, transcript):
|
| 702 |
if not sess or sess["role"] != "admin":
|
| 703 |
return "β Admin only.", _samples_table()
|
|
@@ -714,11 +766,6 @@ with gr.Blocks(title="Plotweaver AI β TTS MOS Evaluation", theme=gr.themes.Sof
|
|
| 714 |
except Exception as e: # noqa
|
| 715 |
return f"β Error on a file: {e}", _samples_table()
|
| 716 |
return f"β
Uploaded {count} sample(s).", _samples_table()
|
| 717 |
-
|
| 718 |
-
def _samples_table():
|
| 719 |
-
return [[s["id"], f"{s['language_name']} ({s['language_code']})", s["sample_name"],
|
| 720 |
-
s["model_name"], "yes" if s["is_reference"] else "no"] for s in list_samples()]
|
| 721 |
-
|
| 722 |
up_btn.click(admin_upload, [session, up_lang, up_files, up_model, up_isref, up_transcript],
|
| 723 |
[up_msg, samples_tbl])
|
| 724 |
|
|
@@ -739,11 +786,13 @@ with gr.Blocks(title="Plotweaver AI β TTS MOS Evaluation", theme=gr.themes.Sof
|
|
| 739 |
langs = conn.execute(
|
| 740 |
"SELECT l.name FROM user_languages ul JOIN languages l ON l.id=ul.language_id "
|
| 741 |
"WHERE ul.user_id=?", (u["id"],)).fetchall()
|
| 742 |
-
nratings = conn.execute("SELECT COUNT(*) c FROM ratings WHERE user_id=?",
|
|
|
|
| 743 |
rows.append([u["id"], u["username"], u["role"], "yes" if u["is_active"] else "no",
|
| 744 |
", ".join(l["name"] for l in langs), nratings])
|
| 745 |
return rows
|
| 746 |
-
refresh_users_btn.click(lambda s: _users_table() if s and s["role"] == "admin" else [],
|
|
|
|
| 747 |
|
| 748 |
def admin_update_user(sess, uid, role, active):
|
| 749 |
if not sess or sess["role"] != "admin":
|
|
@@ -760,9 +809,9 @@ with gr.Blocks(title="Plotweaver AI β TTS MOS Evaluation", theme=gr.themes.Sof
|
|
| 760 |
# ---- Admin: results ----
|
| 761 |
def admin_results(sess, language_id):
|
| 762 |
if not sess or sess["role"] != "admin":
|
| 763 |
-
return "β Admin only.",
|
| 764 |
if not language_id:
|
| 765 |
-
return "Choose a language.",
|
| 766 |
per_model, per_sample, summary = compute_results(language_id)
|
| 767 |
return summary, per_model, per_sample
|
| 768 |
res_btn.click(admin_results, [session, res_lang], [res_summary, res_model_tbl, res_sample_tbl])
|
|
@@ -773,11 +822,15 @@ with gr.Blocks(title="Plotweaver AI β TTS MOS Evaluation", theme=gr.themes.Sof
|
|
| 773 |
return export_results(language_id)
|
| 774 |
export_btn.click(admin_export, [session, res_lang], [res_file])
|
| 775 |
|
| 776 |
-
# Populate admin tables
|
| 777 |
li_btn.click(lambda s: (_languages_table(), _samples_table(), _users_table())
|
| 778 |
if s and s["role"] == "admin" else ([], [], []),
|
| 779 |
[session], [langs_tbl, samples_tbl, users_tbl])
|
| 780 |
|
| 781 |
|
| 782 |
if __name__ == "__main__":
|
| 783 |
-
demo.queue().launch(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
|
| 78 |
def init_db():
|
| 79 |
with get_conn() as conn:
|
| 80 |
+
# WAL gives better concurrency on a real local disk, but it relies on
|
| 81 |
+
# shared-memory/mmap that does NOT work on FUSE/object-store mounts
|
| 82 |
+
# (e.g. a Hugging Face Storage Bucket). Default to DELETE for safety;
|
| 83 |
+
# set MOS_JOURNAL_MODE=WAL when running on a genuine local disk.
|
| 84 |
+
journal = os.environ.get("MOS_JOURNAL_MODE", "DELETE").upper()
|
| 85 |
+
if journal not in ("DELETE", "WAL", "TRUNCATE", "PERSIST", "MEMORY"):
|
| 86 |
+
journal = "DELETE"
|
| 87 |
+
conn.execute(f"PRAGMA journal_mode = {journal};")
|
| 88 |
conn.execute("""
|
| 89 |
CREATE TABLE IF NOT EXISTS users (
|
| 90 |
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
| 425 |
return [(l["name"], l["id"]) for l in session["languages"]]
|
| 426 |
|
| 427 |
|
| 428 |
+
CSS = """
|
| 429 |
+
.gradio-container { max-width: 1080px !important; margin: auto !important; }
|
| 430 |
+
#app-title { text-align: center; margin-bottom: 0; }
|
| 431 |
+
#app-title h1 { font-size: 1.7rem; margin: 6px 0 0; }
|
| 432 |
+
#app-sub { text-align: center; color: var(--body-text-color-subdued); margin-top: 4px; font-size: 0.95rem; }
|
| 433 |
+
#auth-wrap { max-width: 460px; margin: 18px auto; }
|
| 434 |
+
.soft-card { border: 1px solid var(--block-border-color); border-radius: 16px;
|
| 435 |
+
padding: 18px; background: var(--block-background-fill); }
|
| 436 |
+
#userbar { display: flex; align-items: center; justify-content: space-between;
|
| 437 |
+
padding: 4px 2px 10px; }
|
| 438 |
+
#scale-hint { text-align: center; font-size: 0.9rem; color: var(--body-text-color-subdued);
|
| 439 |
+
margin: 4px 0 12px; }
|
| 440 |
+
#submit-row button { font-size: 1.05rem; padding: 12px; border-radius: 12px; }
|
| 441 |
+
.rating-grid .gr-radio { margin-bottom: 6px; }
|
| 442 |
+
footer { display: none !important; }
|
| 443 |
+
"""
|
| 444 |
+
|
| 445 |
+
with gr.Blocks(title="Plotweaver AI β TTS MOS Evaluation",
|
| 446 |
+
theme=gr.themes.Soft(primary_hue="indigo", secondary_hue="indigo"),
|
| 447 |
+
css=CSS) as demo:
|
| 448 |
session = gr.State(None) # logged-in user session dict
|
| 449 |
current_sample = gr.State(None) # sample id currently being rated
|
| 450 |
|
| 451 |
+
gr.Markdown("# π§ Plotweaver AI β TTS MOS Evaluation", elem_id="app-title")
|
| 452 |
+
gr.Markdown("Rate synthesised speech on 7 quality criteria, by language.", elem_id="app-sub")
|
| 453 |
|
| 454 |
# ----------------------------- AUTH ------------------------------------ #
|
| 455 |
+
with gr.Column(visible=True, elem_id="auth-wrap") as auth_col:
|
| 456 |
+
with gr.Column(elem_classes="soft-card"):
|
| 457 |
+
with gr.Tabs():
|
| 458 |
+
with gr.Tab("Sign in"):
|
| 459 |
+
li_user = gr.Textbox(label="Username", autofocus=True)
|
| 460 |
+
li_pw = gr.Textbox(label="Password", type="password")
|
| 461 |
+
li_btn = gr.Button("Sign in", variant="primary")
|
| 462 |
+
li_msg = gr.Markdown()
|
| 463 |
+
with gr.Tab("Create account"):
|
| 464 |
+
su_user = gr.Textbox(label="Username", info="3β32 chars: letters, numbers, _ . -")
|
| 465 |
+
su_email = gr.Textbox(label="Email (optional)")
|
| 466 |
+
su_pw = gr.Textbox(label="Password", type="password", info="At least 6 characters")
|
| 467 |
+
su_langs = gr.CheckboxGroup(label="Languages you can evaluate", choices=lang_choices())
|
| 468 |
+
su_code = gr.Textbox(label="Admin code (optional)", type="password",
|
| 469 |
+
info="Leave blank for a normal reviewer account.")
|
| 470 |
+
su_btn = gr.Button("Create account", variant="primary")
|
| 471 |
+
su_msg = gr.Markdown()
|
| 472 |
|
| 473 |
# ----------------------------- APP ------------------------------------- #
|
| 474 |
with gr.Column(visible=False) as app_col:
|
| 475 |
+
with gr.Row(elem_id="userbar"):
|
| 476 |
greeting = gr.Markdown()
|
| 477 |
+
logout_btn = gr.Button("Log out", scale=0, size="sm")
|
| 478 |
|
| 479 |
+
with gr.Tabs():
|
| 480 |
# ---------- Rate tab ----------
|
| 481 |
with gr.Tab("Rate samples"):
|
| 482 |
+
with gr.Row():
|
| 483 |
+
rate_lang = gr.Dropdown(label="Language", choices=[], interactive=True,
|
| 484 |
+
allow_custom_value=True, scale=2)
|
| 485 |
+
rate_sample = gr.Dropdown(label="Sample (β = already rated)", choices=[],
|
| 486 |
+
interactive=True, allow_custom_value=True, scale=3)
|
| 487 |
+
next_btn = gr.Button("Next unrated βΆ", scale=1, size="sm")
|
| 488 |
+
|
| 489 |
+
rate_audio = gr.Audio(label="βΆ Listen to the full sample",
|
| 490 |
+
type="filepath", interactive=False)
|
| 491 |
gr.Markdown(
|
| 492 |
+
"Use headphones in a quiet room. "
|
| 493 |
+
"**1** Very Poor Β· **2** Poor Β· **3** Fair Β· **4** Good Β· **5** Excellent",
|
| 494 |
+
elem_id="scale-hint",
|
| 495 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 496 |
|
| 497 |
criterion_inputs = {}
|
| 498 |
+
with gr.Column(elem_classes="rating-grid"):
|
| 499 |
+
with gr.Row():
|
| 500 |
+
for key, label, definition in CRITERIA[:4]:
|
| 501 |
+
criterion_inputs[key] = gr.Radio(
|
| 502 |
+
choices=[1, 2, 3, 4, 5], label=label, info=definition, value=None)
|
| 503 |
+
with gr.Row():
|
| 504 |
+
for key, label, definition in CRITERIA[4:]:
|
| 505 |
+
criterion_inputs[key] = gr.Radio(
|
| 506 |
+
choices=[1, 2, 3, 4, 5], label=label, info=definition, value=None)
|
| 507 |
+
|
| 508 |
+
rate_comments = gr.Textbox(
|
| 509 |
+
label="Comments (optional)",
|
| 510 |
+
placeholder="Pronunciation errors, unnatural prosody, noise, artifactsβ¦", lines=2)
|
| 511 |
+
with gr.Row(elem_id="submit-row"):
|
| 512 |
+
submit_btn = gr.Button("β Submit / update rating", variant="primary")
|
| 513 |
rate_msg = gr.Markdown()
|
| 514 |
|
| 515 |
# ---------- Progress tab ----------
|
| 516 |
with gr.Tab("My progress"):
|
| 517 |
+
refresh_prog_btn = gr.Button("Refresh", size="sm")
|
| 518 |
progress_md = gr.Markdown()
|
| 519 |
+
progress_tbl = gr.Dataframe(
|
| 520 |
+
headers=["Language", "Rated", "Total", "Remaining"], interactive=False)
|
| 521 |
|
| 522 |
# ---------- Profile tab ----------
|
| 523 |
with gr.Tab("My languages"):
|
| 524 |
+
gr.Markdown("Choose which languages you are eligible to evaluate.")
|
| 525 |
prof_langs = gr.CheckboxGroup(label="Languages", choices=lang_choices())
|
| 526 |
+
prof_save = gr.Button("Save", variant="primary", size="sm")
|
| 527 |
prof_msg = gr.Markdown()
|
| 528 |
|
| 529 |
# ---------- Admin tab ----------
|
| 530 |
with gr.Tab("Admin", visible=False) as admin_tab:
|
| 531 |
gr.Markdown("### Languages")
|
| 532 |
with gr.Row():
|
| 533 |
+
al_code = gr.Textbox(label="Code", info="e.g. yo, ha, ig, pcm, en-NG", scale=1)
|
| 534 |
+
al_name = gr.Textbox(label="Name", info="e.g. Yoruba", scale=2)
|
| 535 |
+
al_btn = gr.Button("Add language", scale=0, size="sm")
|
| 536 |
al_msg = gr.Markdown()
|
| 537 |
+
langs_tbl = gr.Dataframe(headers=["id", "code", "name"], interactive=False,
|
| 538 |
+
label="Existing languages")
|
| 539 |
|
| 540 |
gr.Markdown("### Upload audio samples")
|
| 541 |
with gr.Row():
|
| 542 |
+
up_lang = gr.Dropdown(label="Language", choices=lang_choices(),
|
| 543 |
+
allow_custom_value=True, scale=1)
|
| 544 |
up_model = gr.Textbox(label="Model / system name", value="",
|
| 545 |
+
info="e.g. F5-TTS, XTTS-v2, MMS-TTS, human. Hidden from reviewers.",
|
| 546 |
+
scale=2)
|
| 547 |
up_files = gr.File(label="Audio files (wav/mp3/flac/ogg)", file_count="multiple",
|
| 548 |
file_types=["audio"])
|
| 549 |
with gr.Row():
|
| 550 |
up_isref = gr.Checkbox(label="These are reference / human anchor samples", value=False)
|
| 551 |
up_transcript = gr.Textbox(label="Transcript (optional, applies to all uploaded)", scale=2)
|
| 552 |
+
up_btn = gr.Button("Upload", variant="primary", size="sm")
|
| 553 |
up_msg = gr.Markdown()
|
| 554 |
samples_tbl = gr.Dataframe(
|
| 555 |
headers=["id", "language", "sample_name", "model", "reference"],
|
| 556 |
interactive=False, label="Samples")
|
| 557 |
with gr.Row():
|
| 558 |
del_sample_id = gr.Number(label="Delete sample by id", precision=0)
|
| 559 |
+
del_btn = gr.Button("Delete", variant="stop", scale=0, size="sm")
|
| 560 |
|
| 561 |
gr.Markdown("### Users")
|
| 562 |
+
refresh_users_btn = gr.Button("Refresh users", size="sm")
|
| 563 |
users_tbl = gr.Dataframe(
|
| 564 |
headers=["id", "username", "role", "active", "languages", "ratings"],
|
| 565 |
interactive=False)
|
|
|
|
| 567 |
promote_id = gr.Number(label="User id", precision=0)
|
| 568 |
role_choice = gr.Dropdown(label="Set role", choices=["reviewer", "admin"], value="reviewer")
|
| 569 |
active_choice = gr.Dropdown(label="Set active", choices=["yes", "no"], value="yes")
|
| 570 |
+
update_user_btn = gr.Button("Apply", scale=0, size="sm")
|
| 571 |
user_admin_msg = gr.Markdown()
|
| 572 |
|
| 573 |
gr.Markdown("### Results")
|
| 574 |
with gr.Row():
|
| 575 |
+
res_lang = gr.Dropdown(label="Language", choices=lang_choices(),
|
| 576 |
+
allow_custom_value=True, scale=2)
|
| 577 |
+
res_btn = gr.Button("Compute MOS", variant="primary", scale=0, size="sm")
|
| 578 |
+
export_btn = gr.Button("Export XLSX", scale=0, size="sm")
|
| 579 |
res_summary = gr.Markdown()
|
| 580 |
res_model_tbl = gr.Dataframe(label="MOS by model", interactive=False)
|
| 581 |
res_sample_tbl = gr.Dataframe(label="MOS by sample", interactive=False)
|
|
|
|
| 589 |
def do_login(username, password):
|
| 590 |
user = authenticate(username, password)
|
| 591 |
if not user:
|
| 592 |
+
# keep typed credentials so the user can correct them
|
| 593 |
return (gr.update(), gr.update(), None, "β Invalid username or password.",
|
| 594 |
+
gr.update(), gr.update(), gr.update(), gr.update(),
|
| 595 |
+
gr.update(), gr.update())
|
| 596 |
sess = user_session(user["id"])
|
| 597 |
is_admin = sess["role"] == "admin"
|
| 598 |
rl = reviewer_lang_choices(sess)
|
| 599 |
return (
|
| 600 |
gr.update(visible=False), # auth_col
|
| 601 |
gr.update(visible=True), # app_col
|
| 602 |
+
sess, # session
|
| 603 |
"", # li_msg
|
| 604 |
+
gr.update(value=f"Signed in as **{sess['username']}** Β· {sess['role']}"), # greeting
|
| 605 |
gr.update(visible=is_admin), # admin_tab
|
| 606 |
+
gr.update(choices=rl, value=None), # rate_lang
|
| 607 |
gr.update(choices=lang_choices(),
|
| 608 |
+
value=[l["id"] for l in sess["languages"]]), # prof_langs
|
| 609 |
+
gr.update(value=""), # li_user (clear)
|
| 610 |
+
gr.update(value=""), # li_pw (clear)
|
| 611 |
)
|
| 612 |
|
| 613 |
li_btn.click(
|
| 614 |
do_login, [li_user, li_pw],
|
| 615 |
+
[auth_col, app_col, session, li_msg, greeting, admin_tab, rate_lang, prof_langs,
|
| 616 |
+
li_user, li_pw],
|
| 617 |
+
)
|
| 618 |
+
li_pw.submit( # allow Enter-to-login
|
| 619 |
+
do_login, [li_user, li_pw],
|
| 620 |
+
[auth_col, app_col, session, li_msg, greeting, admin_tab, rate_lang, prof_langs,
|
| 621 |
+
li_user, li_pw],
|
| 622 |
)
|
| 623 |
|
| 624 |
def do_signup(username, email, password, lang_ids, code):
|
|
|
|
| 626 |
try:
|
| 627 |
create_user(username, email, password, role=role, language_ids=lang_ids or [])
|
| 628 |
except ValueError as e:
|
| 629 |
+
# keep fields so the user can fix the problem
|
| 630 |
+
return (f"β {e}", gr.update(), gr.update(), gr.update(), gr.update(), gr.update())
|
| 631 |
+
note = " (admin account)" if role == "admin" else ""
|
| 632 |
+
return ("β
Account created" + note + ". Switch to the **Sign in** tab to continue.",
|
| 633 |
+
gr.update(value=""), gr.update(value=""), gr.update(value=""),
|
| 634 |
+
gr.update(value=[]), gr.update(value=""))
|
| 635 |
+
su_btn.click(do_signup, [su_user, su_email, su_pw, su_langs, su_code],
|
| 636 |
+
[su_msg, su_user, su_email, su_pw, su_langs, su_code])
|
| 637 |
|
| 638 |
def do_logout():
|
| 639 |
return (gr.update(visible=True), gr.update(visible=False), None, "")
|
|
|
|
| 649 |
rate_lang.change(load_samples_for_lang, [session, rate_lang], [rate_sample])
|
| 650 |
|
| 651 |
def load_sample(sess, sample_id):
|
|
|
|
| 652 |
if not sample_id:
|
| 653 |
return (None, None, *[None] * len(CRITERIA_KEYS), "")
|
| 654 |
with get_conn() as conn:
|
|
|
|
| 674 |
return gr.update(value=nxt)
|
| 675 |
next_btn.click(go_next_unrated, [session, rate_lang], [rate_sample])
|
| 676 |
|
| 677 |
+
def score_lang(sess, sample_id):
|
| 678 |
+
with get_conn() as conn:
|
| 679 |
+
row = conn.execute("SELECT language_id FROM samples WHERE id=?", (sample_id,)).fetchone()
|
| 680 |
+
return row["language_id"] if row else None
|
| 681 |
+
|
| 682 |
def submit_rating(sess, sample_id, comments, *scores):
|
| 683 |
if not sess:
|
| 684 |
+
return "β Not signed in."
|
| 685 |
if not sample_id:
|
| 686 |
+
return "β No sample selected."
|
| 687 |
score_map = dict(zip(CRITERIA_KEYS, scores))
|
| 688 |
missing = [lbl for (k, lbl, _) in CRITERIA if score_map.get(k) in (None, "")]
|
| 689 |
if missing:
|
| 690 |
+
return "β Please rate every criterion. Missing: " + ", ".join(missing)
|
| 691 |
upsert_rating(sess["id"], sample_id, score_map, comments)
|
| 692 |
+
lid = score_lang(sess, sample_id)
|
| 693 |
+
items = samples_for_reviewer(sess["id"], lid)
|
| 694 |
rated = sum(1 for s in items if s["rated"])
|
| 695 |
+
return f"β
Saved β {rated}/{len(items)} samples rated in this language. Click **Next unrated βΆ** to continue."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 696 |
|
| 697 |
submit_btn.click(
|
| 698 |
+
submit_rating, [session, current_sample, rate_comments, *rate_outputs], [rate_msg],
|
|
|
|
|
|
|
| 699 |
).then(
|
| 700 |
load_samples_for_lang, [session, rate_lang], [rate_sample]
|
| 701 |
)
|
|
|
|
| 717 |
# ---- Profile ----
|
| 718 |
def save_profile(sess, lang_ids):
|
| 719 |
if not sess:
|
| 720 |
+
return "β Not signed in.", sess, gr.update()
|
| 721 |
set_user_languages(sess["id"], lang_ids or [])
|
| 722 |
new_sess = user_session(sess["id"])
|
| 723 |
rl = reviewer_lang_choices(new_sess)
|
| 724 |
+
return "β
Languages updated.", new_sess, gr.update(choices=rl, value=None)
|
|
|
|
| 725 |
prof_save.click(save_profile, [session, prof_langs], [prof_msg, session, rate_lang])
|
| 726 |
|
| 727 |
# ---- Admin: languages ----
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 728 |
def _languages_table():
|
| 729 |
return [[l["id"], l["code"], l["name"]] for l in list_languages()]
|
| 730 |
|
|
|
|
| 732 |
ch = lang_choices()
|
| 733 |
return (gr.update(choices=ch), gr.update(choices=ch), gr.update(choices=ch), gr.update(choices=ch))
|
| 734 |
|
| 735 |
+
def admin_add_language(sess, code, name):
|
| 736 |
+
if not sess or sess["role"] != "admin":
|
| 737 |
+
return ("β Admin only.", _languages_table(), *_lang_dropdown_updates())
|
| 738 |
+
try:
|
| 739 |
+
add_language(code, name)
|
| 740 |
+
except ValueError as e:
|
| 741 |
+
return (f"β {e}", _languages_table(), *_lang_dropdown_updates())
|
| 742 |
+
return (f"β
Added {name} ({code}).", _languages_table(), *_lang_dropdown_updates())
|
| 743 |
al_btn.click(
|
| 744 |
admin_add_language, [session, al_code, al_name],
|
| 745 |
[al_msg, langs_tbl, up_lang, res_lang, su_langs, prof_langs],
|
| 746 |
)
|
| 747 |
|
| 748 |
# ---- Admin: samples ----
|
| 749 |
+
def _samples_table():
|
| 750 |
+
return [[s["id"], f"{s['language_name']} ({s['language_code']})", s["sample_name"],
|
| 751 |
+
s["model_name"], "yes" if s["is_reference"] else "no"] for s in list_samples()]
|
| 752 |
+
|
| 753 |
def admin_upload(sess, language_id, files, model, is_ref, transcript):
|
| 754 |
if not sess or sess["role"] != "admin":
|
| 755 |
return "β Admin only.", _samples_table()
|
|
|
|
| 766 |
except Exception as e: # noqa
|
| 767 |
return f"β Error on a file: {e}", _samples_table()
|
| 768 |
return f"β
Uploaded {count} sample(s).", _samples_table()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 769 |
up_btn.click(admin_upload, [session, up_lang, up_files, up_model, up_isref, up_transcript],
|
| 770 |
[up_msg, samples_tbl])
|
| 771 |
|
|
|
|
| 786 |
langs = conn.execute(
|
| 787 |
"SELECT l.name FROM user_languages ul JOIN languages l ON l.id=ul.language_id "
|
| 788 |
"WHERE ul.user_id=?", (u["id"],)).fetchall()
|
| 789 |
+
nratings = conn.execute("SELECT COUNT(*) c FROM ratings WHERE user_id=?",
|
| 790 |
+
(u["id"],)).fetchone()["c"]
|
| 791 |
rows.append([u["id"], u["username"], u["role"], "yes" if u["is_active"] else "no",
|
| 792 |
", ".join(l["name"] for l in langs), nratings])
|
| 793 |
return rows
|
| 794 |
+
refresh_users_btn.click(lambda s: _users_table() if s and s["role"] == "admin" else [],
|
| 795 |
+
[session], [users_tbl])
|
| 796 |
|
| 797 |
def admin_update_user(sess, uid, role, active):
|
| 798 |
if not sess or sess["role"] != "admin":
|
|
|
|
| 809 |
# ---- Admin: results ----
|
| 810 |
def admin_results(sess, language_id):
|
| 811 |
if not sess or sess["role"] != "admin":
|
| 812 |
+
return "β Admin only.", None, None
|
| 813 |
if not language_id:
|
| 814 |
+
return "Choose a language.", None, None
|
| 815 |
per_model, per_sample, summary = compute_results(language_id)
|
| 816 |
return summary, per_model, per_sample
|
| 817 |
res_btn.click(admin_results, [session, res_lang], [res_summary, res_model_tbl, res_sample_tbl])
|
|
|
|
| 822 |
return export_results(language_id)
|
| 823 |
export_btn.click(admin_export, [session, res_lang], [res_file])
|
| 824 |
|
| 825 |
+
# Populate admin tables right after an admin logs in.
|
| 826 |
li_btn.click(lambda s: (_languages_table(), _samples_table(), _users_table())
|
| 827 |
if s and s["role"] == "admin" else ([], [], []),
|
| 828 |
[session], [langs_tbl, samples_tbl, users_tbl])
|
| 829 |
|
| 830 |
|
| 831 |
if __name__ == "__main__":
|
| 832 |
+
demo.queue().launch(
|
| 833 |
+
server_name="0.0.0.0",
|
| 834 |
+
server_port=int(os.environ.get("PORT", 7860)),
|
| 835 |
+
allowed_paths=[DATA_DIR], # let Gradio serve audio stored under MOS_DATA_DIR (e.g. /data)
|
| 836 |
+
)
|