Страница разбора расшифровки в браузере

По одному JSON не понять, чья ошибка: распознавание склеило реплики или
LLM неверно расставила роли. Страница показывает обе стороны и принимает
ответ модели, чтобы диалог собрался на глазах.

GET /v1/jobs/{id}/view - токен заголовком либо ссылкой ?token=ЗНАЧЕНИЕ.
Страница самодостаточна: ни одного внешнего запроса.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Vladimir Bryzgalov
2026-08-17 13:58:08 +05:00
co-authored by Claude Opus 5
parent 020706a69a
commit 0dd531aaa1
3 changed files with 259 additions and 8 deletions
+31 -8
View File
@@ -14,7 +14,7 @@ from urllib.parse import quote
from fastapi import Depends, FastAPI, File, HTTPException, Query, Request, UploadFile
from fastapi.openapi.docs import get_swagger_ui_html
from fastapi.openapi.utils import get_openapi
from fastapi.responses import JSONResponse
from fastapi.responses import HTMLResponse, JSONResponse
from fastapi.security import HTTPBearer
from app.config import ConfigError, Settings, load_settings
@@ -25,6 +25,7 @@ from app.pipeline import ModelsMissing, Pipeline, to_wav16k
from app.security import (check_token, client_address, ip_allowed,
parse_allowlist, token_matches)
from app.store import JobStatus, JobStore
from app.view import render_job
from app.webhook import deliver_async, secret_fingerprint
from app.version import __version__
@@ -227,6 +228,21 @@ def guard(request: Request) -> None:
raise HTTPException(status_code=401, detail="неверный или отсутствующий токен")
def page_guard(request: Request) -> str:
"""Пускает к странице для браузера и возвращает токен для ссылок на ней.
Токен принимается заголовком и параметром ?token=: браузер, открывая
страницу по ссылке, заголовок не подставит.
"""
ip_guard(request)
supplied = request.query_params.get("token")
if check_token(request.headers.get("authorization"), settings.token):
return supplied or ""
if token_matches(supplied, settings.token):
return supplied or ""
raise HTTPException(status_code=401, detail="неверный или отсутствующий токен")
def docs_guard(request: Request) -> str:
"""Пускает к документации и возвращает токен для ссылки на схему.
@@ -238,13 +254,7 @@ def docs_guard(request: Request) -> str:
if not settings.docs:
# 404, а не 403: подтверждать существование страницы незачем.
raise HTTPException(status_code=404, detail="Not Found")
ip_guard(request)
supplied = request.query_params.get("token")
if check_token(request.headers.get("authorization"), settings.token):
return supplied or ""
if token_matches(supplied, settings.token):
return supplied or ""
raise HTTPException(status_code=401, detail="неверный или отсутствующий токен")
return page_guard(request)
@app.get("/docs", include_in_schema=False)
@@ -270,6 +280,19 @@ def openapi_schema(request: Request) -> JSONResponse:
return JSONResponse(schema)
@app.get("/v1/jobs/{job_id}/view", include_in_schema=False)
def job_view(job_id: str, request: Request):
"""Расшифровка в браузере: видно, что отдал сервис и что дала разметка."""
page_guard(request)
job = store.get(job_id)
if job is None or job["status"] != JobStatus.DONE:
raise HTTPException(status_code=404, detail="задача не найдена или не готова")
payload = dict(job.get("result") or {})
payload["job_id"] = job_id
payload["filename"] = job.get("filename", "")
return HTMLResponse(render_job(payload))
@app.get("/v1/logs", dependencies=[Depends(guard)], summary="Журнал сервиса",
description="Последние записи журнала: удобно посмотреть причину сбоя, "
"не заходя на машину. Уровень можно отфильтровать параметром level.")