Имя: восстановление display_name после распознавания + гард пустого входа

Голый magnet без dn/контекста заставлял namer звать LLM на пустом входе,
и модель галлюцинировала мусорное имя («Unknown»), которое писалось и в
display_name, и в rename qBittorrent, а заодно ломало UI-фолбэк на
распознанное название. Верное каноническое имя, вычисляемое позже при
распознавании, никуда не переливалось.

- naming: гард пустого входа в DeriveName (нет контекста и подсказки → ""
  без вызова LLM) + детерминированный форматтер FormatTitleYear.
- qbt: операция RenameTorrent (переименование существующей раздачи).
- store: SetDisplayName — обновление имени постфактум без гарда состояния.
- worker: refreshDisplayNameLocked/RefreshDisplayName — перелив канонического
  имени (эффективный план) в display_name + best-effort rename раздачи по
  реальному t.Hash; авто-триггер при подтверждении матча (choose/manual add).
- web-ui: кнопка «Обновить имя» на странице загрузки (htmx-своп заголовка,
  деградация без JS), видимая при наличии распознавания (вкл. done/orphaned).

Спека: дельты ingest/review/web-ui влиты в openspec/specs; change
refresh-display-name заархивирован.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
av
2026-07-10 16:51:04 +03:00
co-authored by Claude Opus 4.8
parent e2ea1840c9
commit 0c9421f4c1
26 changed files with 1179 additions and 21 deletions
+24
View File
@@ -28,6 +28,7 @@ type Reviewer interface {
SetProviderID(ctx context.Context, id string, provider, providerID string) error
AddManualSource(ctx context.Context, id, provider, providerID string) error
ClearProvider(ctx context.Context, id string) error
RefreshDisplayName(ctx context.Context, id string) error
}
// --- Представление страницы ревью ---
@@ -229,6 +230,29 @@ func (s *server) handleAddSource(w http.ResponseWriter, r *http.Request) {
})
}
// handleRefreshName переливает распознанное каноническое имя в display_name и в
// ярлык раздачи (ручная кнопка на странице загрузки). Свопит главную область
// (#download-main) с обновлённым заголовком; без htmx — PRG на страницу загрузки
// (в отличие от surfaceAction, уводящего на список: смысл действия — увидеть
// новое имя здесь же). Ошибку на htmx-пути показываем 200 + фрагментом.
func (s *server) handleRefreshName(w http.ResponseWriter, r *http.Request) {
id, err := pathID(r)
if err != nil {
redirectErr(w, r, "некорректный id")
return
}
actionErr := s.deps.Reviewer.RefreshDisplayName(r.Context(), id)
if !isHTMX(r) {
if actionErr != nil {
redirectErr(w, r, userErr(r, actionErr, id))
return
}
http.Redirect(w, r, "/download/"+id, http.StatusSeeOther)
return
}
s.renderDownloadFragment(w, r, id, actionErr)
}
var errInvalidCandidate = errors.New("некорректный id кандидата")
var errManualSource = errors.New("не удалось разобрать id или URL записи (для TVDB — числовой id)")