Перейти к основному содержимому

Техническая справка: AI-генерация меты

Документ для разработчиков. Описывает устройство фичи генерации меты и её применения к товарам (apply-meta).

Идея

Генерация — это сохранённый переиспользуемый payload тех же четырёх групп, что и копирование между товарами: описание, SEO, теги, характеристики. Поэтому генерация применяется к товарам через тот же пайплайн apply-meta, что и «копирование из другого товара». Внешний AI изолирован интерфейсом и пока подменён заглушкой.

Модель данных

Таблица meta_generations — миграция back/.../db/changelog/changes/084-create-meta-generations.xml (зарегистрирована в db.changelog-master.xml).

Сущность entity/MetaGenerationEntity.java:

ПолеТипНазначение
titlevarcharЗаголовок для поиска/переиспользования
statusvarchar(20)PENDING|DONE|ERROR; заглушка всегда DONE (задел под async)
modelvarchar(100)Выбранная модель (значение с фронта; логики маршрутизации нет)
commenttextДоп. инструкция для AI
sourceProductIdbigint FKТовар-основа; FK ON DELETE SET NULL
genDescription/genSeo/genTags/genAttributesbooleanЧто было запрошено
inputSnapshotjsonb (Map<String,String>)Резолвленные значения товара, переданные AI (метка → значение)
outputjsonb (GenerationOutput)Результат

model/generation/GenerationOutput.java (JSONB): descriptionDoc (DescriptionDoc), metaTitle, metaDescription, tagNames: List<String> (теги — имена, не id), attributes: List<GenerationAttribute(name,value)>.

JSONB маппится через @JdbcTypeCode(SqlTypes.JSON) (как descriptionDoc у товара).

Граница внешнего AI

  • generation/MetaGenerationClient.java — интерфейс: GenerationOutput generate(GenerationCommand).
  • generation/GenerationCommand.java — вход: model, comment, description, seo, tags, attributes, inputs: Map<String,String>.
  • generation/StubMetaGenerationClient.java@Component, синхронная заглушка: детерминированный плейсхолдер, заполняет только запрошенные группы, вплетает комментарий.

Точка подмены: реальный HTTP-клиент реализует MetaGenerationClient и заменяет StubMetaGenerationClient — сущность, сервис и UI не меняются. Под асинхронный контракт переключить status на PENDING + опрос/вебхук.

Сервис и эндпоинты

service/MetaGenerationService + service/impl/MetaGenerationServiceImpl:

  • preview(GeneratePreviewRequest) — грузит товар, buildInputs() собирает выбранные поля в Map<метка,значение>, вызывает клиент, возвращает output + inputSnapshot (без сохранения).
  • save / list / get / update / delete / regenerate. regenerate перезапускает клиент по сохранённому inputSnapshot.
  • buildInputs() — маппинг ключей полей (name, category, brand, country, price, description, attributes, tags) в русские метки (Название, Категория, Бренд, Страна, Цена, Текущее описание, Характеристики, Теги); эти метки читает заглушка.

Контроллер controller/MetaGenerationController/api/v1/generations:

МетодПутьНазначение
POST/previewПрогон без сохранения
POST/Сохранить (201)
GET/Список (по createdAt desc)
GET/{id}Одна генерация
PUT/{id}Правка title + output
DELETE/{id}Удалить (204)
POST/{id}/regenerateПерепрогон заглушки

Доступ: /api/v1/generations/**ADMIN, SUPER_ADMINconfig/SecurityConfig). Исключение GenerationNotFoundException зарегистрировано в GlobalExceptionHandler.

Применение: apply-meta

POST /api/v1/products/{id}/apply-meta (controller/ProductController), DTO dto/product/ApplyMetaRequest:

applyDescription + descriptionDoc
applySeo + metaTitle + metaDescription
applyTags + tagIds
applyAttributes + attributes

Каждый флаг applyX включает свою группу (false = не трогать, true = применить, в т.ч. пусто). ProductServiceImpl.applyMeta(...) — один @Transactional: описание/SEO выставляются, теги и MANUAL-характеристики полностью замещаются. Общая логика вынесена в приватные replaceTags(...) / replaceManualAttributes(...), которые переиспользуют setTags и setManualAttributes (валидация неизвестного тега откатывает транзакцию).

apply-meta не знает о генерациях — фронт резолвит генерацию в тот же черновик.

Фронтенд

  • admin/products/[id]/generate/page.tsx — экран создания (входы/выходы/модель/ комментарий → preview → редактируемое превью → save / «применить сейчас»).
  • admin/generations/page.tsx — список + «Применить к товару» (пикер цели → /copy-from?generationId=).
  • admin/products/[id]/copy-from/page.tsx — мастер. Источник (товар или генерация) приводится к общему ResolvedSource; вкладки источника; резолв tagNames → tagIds (несовпавшие → блок «создать/пропустить» через POST /api/v1/tags); поддержка ?generationId= (обёрнут в Suspense из-за useSearchParams в Next 16).
  • components/Admin/DescriptionEditor.tsx — добавлены необязательные onChange (живой поток значения, onChange держится в ref во избежание цикла ре-рендера) и hideSave.
  • Типы: types/generation.ts, types/product.ts (ApplyMetaRequest, ProductAttribute).

Тесты

  • controller/ProductApplyMetaIT — apply-meta (все группы, applyX=false, замена MANUAL с сохранением AUTO, неизвестный тег → 404, 401).
  • controller/MetaGenerationControllerIT — preview/save/get/list/update/delete/regenerate, 401, 404.
  • generation/StubMetaGenerationClientTest — unit на заглушку.

Запуск: cd back && ./gradlew test --tests "*MetaGeneration*" --tests "*ApplyMeta*" (нужен Docker для Testcontainers).