Экран ревью теперь показывает единый список источников совпадения: строка «распознано нейронкой» наравне с кандидатами баз; выбор/переключение/снятие в пользу нейронки; ручное добавление по id или URL (TMDB/IMDb — по URL, TVDB — по числовому id); предпросмотр полей и целевых путей каждого источника до применения (место под режиссёра зарезервировано). «Раскладка» осталась отдельной секцией для активного источника, инлайн-превью неактивных — по клику. Ядро: единая деривация «источник → overrides» (sourcePins), общая для превью и коммита → preview == apply; заодно чинит латентный залипший override title/year при переключении источника. Превью считается эфемерно, без записи в БД; пользовательский URL только парсится (SSRF нет). Ревью дизайна и кода пройдены; правки ревьюеров учтены (сообщение об ошибке ручного ввода доходит до пользователя, URL без схемы принимается). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
164 lines
8.2 KiB
HTML
164 lines
8.2 KiB
HTML
<!doctype html>
|
|
<html lang="ru">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>jellybit — ревью #{{.ID}}</title>
|
|
<link rel="stylesheet" href="{{asset "css/jellybit.css"}}">
|
|
<script src="{{asset "vendor/htmx.min.js"}}" defer></script>
|
|
<script src="{{asset "js/app.js"}}" defer></script>
|
|
</head>
|
|
<body>
|
|
|
|
{{template "header" ""}}
|
|
|
|
<main class="main">
|
|
<a class="back-link" href="/">← ко всем загрузкам</a>
|
|
|
|
{{if .Error}}<div class="result-banner" style="background:var(--st-err-bg);border-color:color-mix(in srgb,var(--st-err) 35%,transparent)"><h2 style="color:var(--st-err)">{{.Error}}</h2></div>{{end}}
|
|
{{if .StateError}}<div class="result-banner" style="background:var(--st-err-bg);border-color:color-mix(in srgb,var(--st-err) 35%,transparent)"><h2 style="color:var(--st-err)">{{.StateError}}</h2></div>{{end}}
|
|
|
|
<!-- Шапка ревью -->
|
|
<div class="review-head">
|
|
<div>
|
|
<div class="review-title">{{.Source}}</div>
|
|
<div class="reason-line">
|
|
{{template "status_badge" .State}}
|
|
{{if .Reasons}}<span>Причины: {{range $i, $r := .Reasons}}{{if $i}} · {{end}}<b>{{$r}}</b>{{end}}</span>{{end}}
|
|
{{if .Confidence}}<span class="conf">уверенность {{.Confidence}}</span>{{end}}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{if eq .State "recognizing"}}
|
|
<div class="section">
|
|
<p style="margin:0">⏳ Идёт распознавание — обновите страницу через несколько секунд.</p>
|
|
<p style="margin:var(--sp-2) 0 0"><a href="/review/{{.ID}}">Обновить</a></p>
|
|
</div>
|
|
{{end}}
|
|
|
|
<!-- Контекст -->
|
|
{{if .Context}}
|
|
<div class="section">
|
|
<div class="section-head"><h2>Контекст</h2><span class="hint">исходное сообщение при добавлении</span></div>
|
|
<p style="margin:0;color:var(--text-dim)">«{{.Context}}»</p>
|
|
</div>
|
|
{{end}}
|
|
|
|
{{if .HasPlan}}
|
|
<!-- Догадка -->
|
|
<div class="section">
|
|
<div class="section-head"><h2>Догадка</h2></div>
|
|
<div style="margin-bottom:var(--sp-4)">
|
|
<span class="label">Тип</span>
|
|
<form class="row" method="post" action="/ui/downloads/{{.ID}}/type" style="display:flex;gap:var(--sp-2);align-items:center">
|
|
<button class="btn btn-sm{{if not .IsSeries}} btn-primary{{end}}" name="type" value="movie"{{if not .IsSeries}} disabled{{end}}>фильм</button>
|
|
<button class="btn btn-sm{{if .IsSeries}} btn-primary{{end}}" name="type" value="series"{{if .IsSeries}} disabled{{end}}>сериал</button>
|
|
<span class="hint">переключение пересоберёт план</span>
|
|
</form>
|
|
</div>
|
|
<div class="guess-grid">
|
|
<div class="field"><span class="label">Название</span><div>{{.Title}}</div></div>
|
|
{{if .OriginalTitle}}<div class="field"><span class="label">Ориг. название</span><div class="mono">{{.OriginalTitle}}</div></div>{{end}}
|
|
{{if .Year}}<div class="field"><span class="label">Год</span><div>{{.Year}}</div></div>{{end}}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Источник совпадения -->
|
|
<div class="section">
|
|
<div class="section-head"><h2>Источник совпадения</h2><span class="hint">выбери источник — распознавание нейронкой или запись базы</span></div>
|
|
|
|
<div class="cand-list">
|
|
{{range .Sources}}
|
|
<div class="cand{{if .Active}} selected{{end}}">
|
|
<span class="radio"></span>
|
|
<div class="body">
|
|
{{if eq .Kind "neural"}}
|
|
<div class="name"><span class="prov none">нейронка</span> распознано нейронкой</div>
|
|
<div class="sub">без базы — тег папки не ставится</div>
|
|
{{else}}
|
|
<div class="name"><span class="prov {{.Provider}}">{{.Provider}}</span> {{if .Title}}{{.Title}}{{else}}<span class="faint">id {{.ProviderID}}</span>{{end}}{{if .Year}} <span class="muted">· {{.Year}}</span>{{end}}</div>
|
|
<div class="sub">id {{.ProviderID}}{{if .MatchURL}} · <a class="ext-link" href="{{.MatchURL}}" target="_blank" rel="noopener">запись ↗</a>{{end}}</div>
|
|
{{end}}
|
|
{{if not .Active}}
|
|
<details class="src-preview">
|
|
<summary>предпросмотр</summary>
|
|
<div class="src-fields">
|
|
<span class="label">Тип</span> {{if .IsSeries}}сериал{{else}}фильм{{end}} ·
|
|
<span class="label">Название</span> {{.Title}}{{if .Year}} ({{.Year}}){{end}} ·
|
|
<span class="label">Режиссёр</span> <span class="faint">—</span>
|
|
</div>
|
|
{{template "layout_widget" .Files}}
|
|
</details>
|
|
{{end}}
|
|
</div>
|
|
{{if .Active}}
|
|
<button class="btn btn-sm" type="button" disabled>активен</button>
|
|
{{else}}
|
|
<form method="post" action="/ui/downloads/{{$.ID}}/{{if eq .Kind "neural"}}nobase{{else}}candidate{{end}}">
|
|
{{if ne .Kind "neural"}}<input type="hidden" name="candidate_id" value="{{.CandidateID}}">{{end}}
|
|
<button class="btn btn-sm btn-primary" type="submit">выбрать</button>
|
|
</form>
|
|
{{end}}
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
|
|
<form class="cand-extra" method="post" action="/ui/downloads/{{.ID}}/source">
|
|
<select class="select" name="provider" style="max-width:120px">
|
|
<option value="tmdb">TMDB</option>
|
|
<option value="tvdb">TVDB</option>
|
|
<option value="imdb">IMDb</option>
|
|
</select>
|
|
<input class="input mono" name="provider_id" placeholder="id или URL записи (для TVDB — числовой id)" required>
|
|
<button class="btn btn-sm" type="submit">Добавить</button>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Раскладка -->
|
|
<div class="section">
|
|
<div class="section-head">
|
|
<h2>Раскладка</h2>
|
|
<span class="hint">файл источника → целевой хардлинк</span>
|
|
</div>
|
|
{{template "layout_widget" .Files}}
|
|
<p class="faint" style="font-size:var(--fs-xs);margin:var(--sp-3) 0 0">
|
|
Буквальные целевые пути. Применяются как хардлинки — исходник остаётся на раздаче.
|
|
</p>
|
|
</div>
|
|
{{end}}
|
|
|
|
<!-- Уточнить и перераспознать -->
|
|
<div class="section">
|
|
<div class="section-head"><h2>Уточнить и перераспознать</h2></div>
|
|
{{if .Hints}}
|
|
<div class="hint-list">
|
|
{{range $i, $h := .Hints}}
|
|
<div class="hint-item"><span class="idx">#{{add $i 1}}</span><span class="txt">{{$h}}</span></div>
|
|
{{end}}
|
|
</div>
|
|
{{end}}
|
|
<form method="post" action="/ui/downloads/{{.ID}}/refine">
|
|
<textarea class="textarea" name="hint" placeholder="Например: «это Fargo, год сериала 2014, не 2015» или «sample.mkv не считать эпизодом»"></textarea>
|
|
<div class="refine-row">
|
|
<button class="btn btn-primary btn-sm" type="submit">🔁 Уточнить</button>
|
|
</div>
|
|
</form>
|
|
<form method="post" action="/ui/downloads/{{.ID}}/rerecognize" style="margin-top:var(--sp-2)">
|
|
<button class="btn btn-sm" type="submit">🔄 Распознать заново</button>
|
|
<span class="hint">без новой подсказки — по уже накопленному контексту</span>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Панель действий -->
|
|
<div class="action-bar">
|
|
<span class="grow">{{if .HasLinks}}Превью готово — можно применять.{{else}}Подтверди источник, чтобы получить превью раскладки.{{end}}</span>
|
|
<form method="post" action="/ui/downloads/{{.ID}}/defer"><button class="btn btn-lg" type="submit">🕗 Позже</button></form>
|
|
<form method="post" action="/ui/downloads/{{.ID}}/cancel"><button class="btn btn-lg btn-danger" type="submit">❌ Отклонить</button></form>
|
|
{{if .HasLinks}}<form method="post" action="/ui/downloads/{{.ID}}/apply"><button class="btn btn-lg btn-primary" type="submit">✅ Применить</button></form>{{end}}
|
|
</div>
|
|
|
|
</main>
|
|
</body>
|
|
</html>
|