feat(photo-ai): Stage 5 — фронтенд фото-ИИ (режимы, статус, метаданные)

Только фронтенд: server.js, worker.js, photo-ai/, compose и схема БД не тронуты.
Контракт /enhance-ai, /api/photo-ai/health и service в /api/photo-jobs/status
взят из Stage 4 как есть (I6 — новых колонок нет, метаданные из photo_jobs.params
и audit_log.target).

journal.html + journal.js:
- селект режима рядом с «🤖 ИИ»: Универсально (x2) / Быстро (x4) / Лица /
  Лица + фон; PHOTO_AI_MODES — единственная таблица режим→{model, face}
- значение уходит в POST .../enhance-ai телом {model, face, face_model};
  face_model подставляется только когда face !== 'off'
- дефолт селекта из photo_ai_face_mode (face=all → facesbg, face=face → faces)
- подсказка про медленный CPU: loadPhotoAiDevice() читает
  GET /api/photo-ai/health (только для админа)
- текст подтверждения и надпись «ИИ обрабатывает…» различают апскейл и face-режим
- кнопка и селект скрываются вместе при photo_ai_enabled === 'false' (I3)

settings.html + settings.js:
- новый раздел «Фото-ИИ»: режим по умолчанию, модель лиц, желаемое устройство
- read-only статус из /api/photo-jobs/status: фактическое устройство,
  device_name, VRAM, модели/модели лиц/загруженные; жёлтым — расхождение с
  желаемым устройством, красным — недоступность и незаданный PHOTO_AI_URL
- renderStackInfo() рисует карточку «Фото-ИИ» из stack.photo_ai
- новые id в DIRTY_FIELDS и в payload PUT /api/settings
- photo_ai_device_pref документирующий: фактическое устройство задаёт
  PHOTO_AI_DEVICE в контейнере

worker.html + worker.js:
- PHOTO_ACTION_LABELS: ai_face «ИИ + лица», ai_upscale «ИИ-апскейл»
- в модалке сравнения — чипы с моделью, режимом лиц, найденными лицами,
  устройством, временем обработки и предупреждениями
- строка состояния учитывает service.reachable === false («задания ждут»)
  и дописывает «· расчёт на <device>»

audit.js: 10 меток для кодов, которые раньше показывались сырыми (профиль и
фото ученика, главное фото, фото модуля, удаление/очистка уведомлений, логотип).

Проверено: node --check для всех четырёх js. Приёмка на живом стеке в этом
изменении не гонялась — чекбокс приёмки Stage 5 в TODO_PHOTO_FACE_AI.md
оставлен пустым, там же журнал раздела 5 с замечаниями для Stage 6.
This commit is contained in:
dev
2026-09-30 11:15:54 +03:00
parent 884188e9ec
commit 45033bffa8
8 changed files with 311 additions and 15 deletions
+39
View File
@@ -26,6 +26,7 @@
<a href="#sec-share" data-nav="sec-share"><i data-lucide="link"></i> Публичные ссылки</a>
<a href="#sec-form" data-nav="sec-form"><i data-lucide="file-text"></i> Форма ответа</a>
<a href="#sec-photo" data-nav="sec-photo"><i data-lucide="camera"></i> Фото</a>
<a href="#sec-photo-ai" data-nav="sec-photo-ai"><i data-lucide="sparkles"></i> Фото-ИИ</a>
<a href="#sec-notify" data-nav="sec-notify"><i data-lucide="bell"></i> Уведомления</a>
<a href="#sec-ai" data-nav="sec-ai"><i data-lucide="bot"></i> ИИ</a>
<a href="#sec-backup" data-nav="sec-backup"><i data-lucide="database"></i> Бэкапы</a>
@@ -259,6 +260,44 @@
</div>
</div>
<div class="settings-card" id="sec-photo-ai">
<div class="card-head">
<div class="ic"><i data-lucide="sparkles"></i></div>
<h3>Фото-ИИ</h3>
<button class="icon-btn" id="photoAiRefreshBtn" type="button" title="Обновить статус фото-ИИ" aria-label="Обновить статус фото-ИИ"><i data-lucide="refresh-cw"></i></button>
</div>
<p class="hint">Нейросетевая обработка фотографий: Real-ESRGAN увеличивает разрешение, GFPGAN восстанавливает лица. Лицевой режим включается только явно — модель дорисовывает лицо по приору и может изменить черты. Без `PHOTO_AI_URL` кнопка «🤖 ИИ» в журнале скрыта.</p>
<div class="settings-row">
<div class="settings-field">
<label for="photoAiFaceMode">Режим по умолчанию</label>
<select id="photoAiFaceMode" class="settings-input">
<option value="off">Без лиц — только увеличение</option>
<option value="face">Лица — восстановить лица, фон увеличить</option>
<option value="all">Лица + фон — восстановить лица и снять шум фона</option>
</select>
<span class="hint">Предлагается в окне улучшения фото, режим можно переключить там же</span>
</div>
<div class="settings-field">
<label for="photoAiFaceModel">Модель лиц</label>
<select id="photoAiFaceModel" class="settings-input">
<option value="gfpgan">GFPGAN v1.4</option>
<option value="codeformer">CodeFormer</option>
</select>
<span class="hint">CodeFormer работает, только если модуль установлен в образ photo-ai</span>
</div>
<div class="settings-field">
<label for="photoAiDevicePref">Желаемое устройство</label>
<select id="photoAiDevicePref" class="settings-input">
<option value="auto">Авто (GPU, если доступен)</option>
<option value="cuda">GPU (CUDA)</option>
<option value="cpu">CPU</option>
</select>
<span class="hint">Документирующее значение: фактическое устройство задаёт переменная PHOTO_AI_DEVICE в контейнере</span>
</div>
</div>
<div class="data-stats" id="photoAiStatus"><span class="hint">Загрузка статуса фото-ИИ…</span></div>
</div>
<div class="settings-card" id="sec-trash">
<div class="card-head">
<div class="ic"><i data-lucide="trash-2"></i></div>