Голый 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>
7.4 KiB
MODIFIED Requirements
Requirement: Вывод имени через LLM со структурированным выводом
Система SHALL строить отображаемое имя с помощью LLM (структурированный JSON-вывод), извлекая из контекста тип (movie/series), название, год, режиссёра и (для сериала) номер сезона. Год и режиссёр — опциональные поля.
Если и контекст, и подсказка из полей источника (dn magnet / имя .torrent)
пусты, система SHALL считать имя не выведенным и SHALL NOT вызывать LLM (выводить
имя не из чего — вызов на пустом входе способен лишь галлюцинировать). В этом
случае загрузка добавляется без rename, а download.display_name остаётся
пустым.
Название SHALL быть на русском языке для российского контента и на английском (оригинальном) — для остального.
Система SHALL предпринять ограниченное число попыток получить от LLM валидный
результат (корректный JSON с непустым названием); бюджет попыток —
[llm].max_retries (по умолчанию 3). Транспортные ретраи провайдера LLM
(сетевые сбои, 429, 5xx) в этот счёт не входят.
Недоступность или ошибка LLM SHALL NOT прерывать приём загрузки: система переходит к алгоритмическому фолбеку.
Scenario: LLM возвращает структурированное имя
- WHEN LLM по контексту возвращает валидный JSON с непустым названием
- THEN система формирует отображаемое имя из его полей (название, год, для сериала — сезон)
Scenario: Российский контент — название на русском
- WHEN контент распознан как российский
- THEN в отображаемом имени используется русское название
Scenario: Исчерпан бюджет попыток LLM
- WHEN LLM за отведённые попытки (
[llm].max_retries) не вернул валидный результат либо недоступен - THEN система не прерывает приём и переходит к алгоритмическому фолбеку
Scenario: Пустой вход — LLM не вызывается
- GIVEN пойманная загрузка без пользовательского контекста и без подсказки
из полей источника (голый magnet без
dn) - WHEN система выводит отображаемое имя на шаге добавления
- THEN LLM не вызывается, имя считается не выведенным
- AND загрузка добавляется без
rename, аdownload.display_nameпуст (заголовок в веб-UI берётся из фолбека)
ADDED Requirements
Requirement: Обновление отображаемого имени по распознаванию
Система SHALL уметь обновлять отображаемое имя загрузки после того, как
распознавание дало каноническое название, — переливая уже вычисленное имя (без
нового вызова LLM) в download.display_name и в имя раздачи qBittorrent.
Источником имени SHALL быть эффективное распознанное название: пины
title/year (если матч подтверждён), иначе recognition.title/year — то
же название, что использует раскладка. Формат SHALL быть коротким детерминированным
ярлыком Title (Year) (год опционален), с той же очисткой от управляющих
символов и обрезкой по длине, что и вывод имени на шаге добавления. Пустой
источник (нет распознавания или пустое название) SHALL приводить к отсутствию
изменений (no-op).
Переименование раздачи в qBittorrent SHALL адресоваться по infohash своей
раздачи и SHALL быть best-effort: сбой (раздача удалена, qBittorrent недоступен)
SHALL NOT проваливать обновление — download.display_name обновляется в любом
случае, ошибка внешнего вызова логируется. Как и на шаге добавления,
отображаемое имя SHALL влиять только на отображение и SHALL NOT влиять на пути
файлов, распознавание или раскладку.
Обновление имени SHALL иметь две точки входа: авто — при подтверждённом
матче (см. capability review); ручную — по явному действию пользователя.
Ручное действие SHALL перезаписывать текущее имя всегда; авто SHALL перезаписывать,
когда выведенное имя непусто.
Scenario: Перелив имени в загрузку и раздачу
- GIVEN загрузка с распознанным непустым каноническим названием
- WHEN запускается обновление отображаемого имени
- THEN
download.display_nameустанавливается вTitle (Year) - AND раздача в qBittorrent переименовывается в то же имя (по infohash своей раздачи)
Scenario: qBittorrent недоступен — имя у загрузки всё равно обновлено
- GIVEN обновление отображаемого имени с выведенным непустым именем
- WHEN переименование раздачи в qBittorrent завершается ошибкой (недоступен или раздача удалена)
- THEN
download.display_nameвсё равно обновлён - AND ошибка внешнего вызова qBittorrent логируется, операция не проваливается
Scenario: Нет распознавания — обновление ничего не делает
- GIVEN загрузка без распознанного названия (пустой источник имени)
- WHEN запускается обновление отображаемого имени
- THEN ни
download.display_name, ни имя раздачи не меняются (no-op)