File size: 9,467 Bytes
3b7d44a 20b4d4f 3b7d44a 9db6d22 20b4d4f 3b7d44a 9db6d22 5cbe7bc 3b7d44a 20b4d4f 3b7d44a fa68614 9db6d22 3b7d44a 20b4d4f 9db6d22 00417bf 20b4d4f fa68614 20b4d4f fa68614 3b7d44a 5cbe7bc 20b4d4f 3b7d44a fa68614 20b4d4f fa68614 20b4d4f fa68614 20b4d4f fa68614 20b4d4f fa68614 20b4d4f fa68614 20b4d4f fa68614 20b4d4f fa68614 20b4d4f fa68614 20b4d4f fa68614 20b4d4f fa68614 20b4d4f fa68614 9db6d22 20b4d4f fa68614 20b4d4f 3b7d44a 20b4d4f fa68614 3b7d44a |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 |
import gradio as gr
import json
from constants import INTRODUCTION_TEXT, DATASETS
from utils import (
init_repo,
load_data,
process_submit,
get_datasets_description,
get_metrics_html,
compute_wer_cer,
get_submit_html,
)
from styles import LEADERBOARD_CSS
ASSETS_HTML = """
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.4/css/jquery.dataTables.min.css">
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js"></script>
<script>
window.initLeaderboardDT = function(){
try{
if (!window.jQuery || !$.fn.DataTable) return;
document.querySelectorAll('.leaderboard-table table:not(.dt-ready)').forEach(function(tbl){
$(tbl).addClass('dt-ready').DataTable({
pageLength: 25,
order: [[3, 'asc']],
language: { url: 'https://cdn.datatables.net/plug-ins/1.13.4/i18n/ru.json' }
});
});
}catch(e){}
};
const tryInit = () => window.initLeaderboardDT && window.initLeaderboardDT();
document.addEventListener('DOMContentLoaded', tryInit);
const mo = new MutationObserver(tryInit);
mo.observe(document.documentElement, { childList: true, subtree: true });
setTimeout(tryInit, 300);
</script>
"""
dataset_info_html = """
<div class="dataset-info-card">
<h3>📦 Тестовый набор</h3>
<p>Все результаты рассчитываются на
<a href="https://huggingface.co/datasets/Vikhrmodels/RuASRBenchmark" target="_blank">RuASRBenchmark</a>.
В нём собраны разные сеты с русской речью, чтобы проверять модели на разных типах аудио.</p>
</div>
"""
init_repo()
gr.set_static_paths(paths=["."])
with gr.Blocks(css=LEADERBOARD_CSS, theme=gr.themes.Soft()) as demo:
gr.HTML('<img src="/gradio_api/file=Logo.png" style="display:block; margin:0 auto; width:34%; height:auto;">')
gr.HTML(ASSETS_HTML)
gr.Markdown(INTRODUCTION_TEXT, elem_classes="markdown-text")
with gr.Tabs():
with gr.Tab("🏅 Лидерборд"):
leaderboard_html = gr.HTML(value=load_data(), every=60)
with gr.Tab("📈 Метрики"):
gr.HTML(get_metrics_html())
with gr.Group():
gr.Markdown("### Песочница: посчитайте WER/CER на своих строках")
with gr.Row():
ref = gr.Textbox(label="Референсный текст", lines=2)
hyp = gr.Textbox(label="Распознанный текст", lines=2)
with gr.Row():
normalize = gr.Checkbox(value=True, label="Нормализовать (нижний регистр, без пунктуации)")
btn_calc = gr.Button("Посчитать")
with gr.Row():
out_wer = gr.Number(label="WER, %", precision=2)
out_cer = gr.Number(label="CER, %", precision=2)
def _ui_compute(ref_text, hyp_text, norm):
wer, cer = compute_wer_cer(ref_text or "", hyp_text or "", norm)
return wer, cer
btn_calc.click(_ui_compute, inputs=[ref, hyp, normalize], outputs=[out_wer, out_cer])
with gr.Tab("📊 Датасеты"):
gr.HTML(dataset_info_html)
gr.HTML(get_datasets_description())
with gr.Tab("✉️ Отправить результат"):
# 1) Кнопка входа через HF
login = gr.LoginButton(size="md")
auth_note = gr.HTML()
# 2) Панель формы, скрыта до входа
submit_panel = gr.Group(visible=False)
with submit_panel:
gr.HTML(get_submit_html())
with gr.Row():
with gr.Column():
model_name = gr.Textbox(label="Название модели *", placeholder="MyAwesomeASRModel")
link = gr.Textbox(label="Ссылка на модель *", placeholder="https://huggingface.co/username/model")
license_field = gr.Textbox(label="Лицензия *", placeholder="MIT / Apache-2.0 / Closed")
with gr.Column():
metrics_json = gr.TextArea(
label="Метрики JSON *",
placeholder='{"Russian_LibriSpeech": {"wer": 0.1234, "cer": 0.0567}, ...}',
lines=16,
)
submit_btn = gr.Button("🚀 Отправить")
output_msg = gr.HTML()
def _alert(kind, text):
return f'<div class="alert {kind}">{text}</div>'
# 3) Гейт: показать/скрыть форму в зависимости от профиля
def gate_visibility(profile: gr.OAuthProfile | None):
if profile is None:
return gr.update(visible=False), _alert("error", "Чтобы отправить результат, войдите через Hugging Face выше.")
return gr.update(visible=True), ""
# Проверяем на загрузке страницы и после нажатия на LoginButton
demo.load(gate_visibility, inputs=None, outputs=[submit_panel, auth_note])
login.click(lambda: None, outputs=None) # сам клик перенаправит на OAuth; по возврату сработает demo.load
# 4) Авторизованный сабмит: профиль инжектится автоматически
def build_json_and_submit(name, link_, lic, metrics_str, profile: gr.OAuthProfile | None):
if profile is None:
return gr.update(), _alert("error", "Нужен вход через Hugging Face."), name, link_, lic, metrics_str
name = (name or "").strip()
link_ = (link_ or "").strip()
lic = (lic or "").strip()
if not name:
return gr.update(), _alert("error", "Укажите название модели."), name, link_, lic, metrics_str
if not link_ or not (link_.startswith("http://") or link_.startswith("https://")):
return gr.update(), _alert("error", "Ссылка должна начинаться с http:// или https://"), name, link_, lic, metrics_str
if not lic:
return gr.update(), _alert("error", "Укажите лицензию модели."), name, link_, lic, metrics_str
try:
metrics = json.loads(metrics_str)
except Exception as e:
return gr.update(), _alert("error", f"Невалидный JSON метрик: {e}"), name, link_, lic, metrics_str
if not isinstance(metrics, dict):
return gr.update(), _alert("error", "Метрики должны быть объектом JSON с датасетами верхнего уровня."), name, link_, lic, metrics_str
missing = [ds for ds in DATASETS if ds not in metrics]
extra = [k for k in metrics.keys() if k not in DATASETS]
if missing:
return gr.update(), _alert("error", f"Отсутствуют датасеты: {', '.join(missing)}"), name, link_, lic, metrics_str
if extra:
return gr.update(), _alert("error", f"Лишние ключи в метриках: {', '.join(extra)}"), name, link_, lic, metrics_str
for ds in DATASETS:
entry = metrics.get(ds)
if not isinstance(entry, dict):
return gr.update(), _alert("error", f"{ds}: значение должно быть объектом с полями wer и cer"), name, link_, lic, metrics_str
for k in ("wer", "cer"):
v = entry.get(k)
if not isinstance(v, (int, float)):
return gr.update(), _alert("error", f"{ds}: поле {k} должно быть числом"), name, link_, lic, metrics_str
if not (0 <= float(v) <= 1):
return gr.update(), _alert("error", f"{ds}: поле {k} должно быть в диапазоне [0, 1]"), name, link_, lic, metrics_str
payload = json.dumps({"model_name": name, "link": link_, "license": lic, "metrics": metrics}, ensure_ascii=False)
updated_html, status_msg, _ = process_submit(payload)
if updated_html is None:
msg = status_msg.replace("Ошибка:", "").strip()
return gr.update(), _alert("error", f"Не удалось добавить: {msg}"), name, link_, lic, metrics_str
return updated_html, _alert("success", "✅ Результат добавлен в лидерборд."), "", "", "", ""
submit_btn.click(
build_json_and_submit,
inputs=[model_name, link, license_field, metrics_json],
outputs=[leaderboard_html, output_msg, model_name, link, license_field, metrics_json],
)
demo.launch()
|