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()