Список задач в браузере и сравнение с эталоном
GET /v1/jobs/view - список последних задач со ссылками на разбор. Токен
подставляется в ссылки, иначе по каждой пришлось бы дописывать его руками.
Отдельный тест следит, чтобы маршрут не перехватывался /v1/jobs/{job_id}.
build/compare_reference.py считает WER относительно эталона, выписанного
на слух. Косвенные метрики ловят крупные сдвиги и слепы к систематическим
мелким потерям - так и пропустили обрыв концов фраз.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
7c88480ac9
commit
f3640f2941
+70
-19
@@ -7,8 +7,9 @@
|
||||
"""
|
||||
import html
|
||||
import json
|
||||
from urllib.parse import quote
|
||||
|
||||
__all__ = ["render_job"]
|
||||
__all__ = ["render_job", "render_index"]
|
||||
|
||||
GAP_NOTICE_SEC = 3.0
|
||||
|
||||
@@ -38,6 +39,73 @@ def _stats_row(job: dict) -> str:
|
||||
f'<span class="v">{html.escape(v)}</span></div>' for k, v in items)
|
||||
|
||||
|
||||
_CSS = """ :root { --bg:#fbfaf7; --fg:#22201d; --dim:#8a8580; --line:#e6e2db;
|
||||
--a:#2f6f4f; --b:#7a4b8a; --warn:#b06a2c; }
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root:not([data-theme="light"]) { --bg:#1a1917; --fg:#e8e4dd; --dim:#8a8580;
|
||||
--line:#33302c; --a:#7fc0a0; --b:#c79ad6; --warn:#d99a5c; } }
|
||||
* { box-sizing:border-box; }
|
||||
body { margin:0; padding:24px; background:var(--bg); color:var(--fg);
|
||||
font:15px/1.55 -apple-system,Segoe UI,Roboto,sans-serif; }
|
||||
.wrap { max-width:900px; margin:0 auto; }
|
||||
h1 { font-size:19px; margin:0 0 14px; font-weight:600; }
|
||||
h2 { font-size:15px; margin:26px 0 8px; font-weight:600; }
|
||||
.hint { color:var(--dim); font-size:13px; margin:6px 0 14px; }
|
||||
.bad { color:var(--warn); }"""
|
||||
|
||||
STATUS_RU = {"done": "готово", "failed": "ошибка",
|
||||
"running": "в работе", "queued": "в очереди"}
|
||||
|
||||
|
||||
def render_index(jobs: list[dict], token: str) -> str:
|
||||
"""Список задач со ссылками на разбор.
|
||||
|
||||
Токен подставляется в ссылки: иначе по каждой пришлось бы дописывать его
|
||||
руками, а ради этого список и заводится.
|
||||
"""
|
||||
import time as _time
|
||||
|
||||
suffix = f"?token={quote(token)}" if token else ""
|
||||
rows = []
|
||||
for job in jobs:
|
||||
status = job.get("status", "")
|
||||
when = _time.strftime("%d.%m %H:%M", _time.localtime(job.get("created_at", 0)))
|
||||
title = html.escape(str(job.get("filename") or job["id"][:8]))
|
||||
cell = (f'<a href="/v1/jobs/{job["id"]}/view{suffix}">{title}</a>'
|
||||
if status == "done" else title)
|
||||
note = html.escape(str(job.get("error") or "")[:80])
|
||||
rows.append(
|
||||
f'<tr class="st-{html.escape(status)}">'
|
||||
f'<td class="dim">{when}</td>'
|
||||
f'<td>{cell}</td>'
|
||||
f'<td class="dim">{timecode(job.get("duration_sec") or 0)}</td>'
|
||||
f'<td class="status">{STATUS_RU.get(status, status)}</td>'
|
||||
f'<td class="dim err">{note}</td></tr>')
|
||||
|
||||
body = ("".join(rows) or
|
||||
'<tr><td colspan="5" class="dim">задач пока нет</td></tr>')
|
||||
return f"""<!doctype html>
|
||||
<html lang="ru"><head><meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Задачи talkscore-asr</title>
|
||||
<style>{_CSS}
|
||||
table {{ width:100%; border-collapse:collapse; }}
|
||||
td {{ padding:6px 10px 6px 0; border-bottom:1px solid var(--line);
|
||||
vertical-align:top; }}
|
||||
td.dim {{ color:var(--dim); font-size:13px; font-variant-numeric:tabular-nums;
|
||||
white-space:nowrap; }}
|
||||
td.err {{ white-space:normal; }}
|
||||
.status {{ font-size:13px; }}
|
||||
.st-failed .status {{ color:var(--warn); }}
|
||||
.st-done .status {{ color:var(--a); }}
|
||||
a {{ color:inherit; }}
|
||||
</style></head><body><div class="wrap">
|
||||
<h1>Задачи</h1>
|
||||
<div class="hint">Готовые открываются по клику. Показаны последние {len(jobs)}.</div>
|
||||
<table>{body}</table>
|
||||
</div></body></html>"""
|
||||
|
||||
|
||||
def render_job(job: dict) -> str:
|
||||
"""Собирает самодостаточную страницу: без внешних файлов и запросов."""
|
||||
result = job.get("result") or job
|
||||
@@ -90,21 +158,7 @@ def render_job(job: dict) -> str:
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Расшифровка {html.escape(str(job.get("job_id", "")))[:8]}</title>
|
||||
<style>
|
||||
:root {{ --bg:#fbfaf7; --fg:#22201d; --dim:#8a8580; --line:#e6e2db;
|
||||
--a:#2f6f4f; --b:#7a4b8a; --warn:#b06a2c; }}
|
||||
@media (prefers-color-scheme: dark) {{
|
||||
:root:not([data-theme="light"]) {{ --bg:#1a1917; --fg:#e8e4dd; --dim:#8a8580;
|
||||
--line:#33302c; --a:#7fc0a0; --b:#c79ad6; --warn:#d99a5c; }} }}
|
||||
* {{ box-sizing:border-box; }}
|
||||
body {{ margin:0; padding:24px; background:var(--bg); color:var(--fg);
|
||||
font:15px/1.55 -apple-system,Segoe UI,Roboto,sans-serif; }}
|
||||
.wrap {{ max-width:900px; margin:0 auto; }}
|
||||
h1 {{ font-size:19px; margin:0 0 14px; font-weight:600; }}
|
||||
.stats {{ display:flex; flex-wrap:wrap; gap:6px 22px; padding:12px 0;
|
||||
border-top:1px solid var(--line); border-bottom:1px solid var(--line);
|
||||
margin-bottom:18px; }}
|
||||
.stat .k {{ color:var(--dim); font-size:13px; margin-right:6px; }}
|
||||
.stat .v {{ font-variant-numeric:tabular-nums; }}
|
||||
{_CSS}
|
||||
.turn {{ display:grid; grid-template-columns:52px 34px 58px 1fr; gap:8px;
|
||||
padding:5px 0; border-bottom:1px solid var(--line); }}
|
||||
.tc, .db {{ color:var(--dim); font-size:12px; font-variant-numeric:tabular-nums; }}
|
||||
@@ -120,9 +174,6 @@ def render_job(job: dict) -> str:
|
||||
.roles .line {{ display:grid; grid-template-columns:76px 1fr; gap:10px;
|
||||
padding:4px 0; border-bottom:1px solid var(--line); }}
|
||||
.who2 {{ font-weight:600; font-size:13px; }}
|
||||
.hint {{ color:var(--dim); font-size:13px; margin:6px 0 14px; }}
|
||||
.bad {{ color:var(--warn); }}
|
||||
h2 {{ font-size:15px; margin:26px 0 8px; font-weight:600; }}
|
||||
</style></head><body><div class="wrap">
|
||||
<h1>Расшифровка {html.escape(str(job.get("job_id", ""))[:8])} - {html.escape(str(job.get("filename", "")))}</h1>
|
||||
<div class="stats">{_stats_row(job)}</div>
|
||||
|
||||
Reference in New Issue
Block a user