# Фотографии и публикации: примеры для AI

Три готовых примера с фотографиями: статья, работа портфолио и услуга. [Посмотрите результат](https://eval.su/docs/ai/#recipe-media) или [скачайте комплект с изображениями](https://eval.su/docs/examples/media.zip). В архиве уже есть два JPEG, подписи, alt, обложка, галерея и отдельный JSON каждого материала. Это демо Аишки, не заказ реального клиента.

[JSON для AI](https://eval.su/docs/examples/media-publications.json) генерируется из тех же материалов. `sourceFiles` содержит адрес каждого изображения, имя, размер, SHA-256, подпись и alt; это описание исходных файлов, не аргументы команды. `arguments.data` — тело черновика. UUID 00000000-... являются явными заполнителями: сначала получите собственные ready UUID загрузкой. Инструкция доступна по HTTP и как MCP resource.

[API](https://eval.su/docs/api.md) · [Все операции](https://eval.su/api/v1/capabilities) · [OpenAPI](https://eval.su/openapi.json) · [Инструкция для AI](https://eval.su/docs/ai.md)

## Попробовать сразу с фотографиями

Распакуйте ZIP и перейдите в `examples/integrations/media`. Ничего не нужно искать или добавлять, чтобы посмотреть состав примера:

```sh
node publication-draft.mjs article.example.json article.state.json
node publication-draft.mjs portfolio.example.json portfolio.state.json
node publication-draft.mjs service.example.json service.state.json
```

Без `--save-draft` программа только проверяет локальные файлы и выводит будущий документ. Чтобы сохранить выбранный материал, задайте ключ с нужными правами через защищённое окружение `AISHKA_ACCESS_TOKEN` и повторите соответствующую команду с `--save-draft`. Для каждого материала свой журнал. Ничего автоматически не публикуется.

- `photos/studio.jpg`: светлая керамическая ваза на нейтральном фоне. В примере это предметный кадр для обложки и карточки.
- `photos/interior.jpg`: ваза с веткой на деревянной тумбе в освещённом интерьере. Это второй кадр галереи, а не «результат после обработки» или фотография сотрудника.
- Статья: обложка, разделы, форматированный текст, список, иллюстрации и блок кода.
- Портфолио: задача, процесс, изображения и демонстрационный результат без выдуманных показателей.
- Услуга: состав, этапы, ожидаемый результат, ограничения и визуальные примеры.

Для своего материала скопируйте JSON вместе с папкой `photos`, замените файлы и подписи. Сохраните расширения и реальные MIME; пути считаются от JSON-файла. Обложка и галерея используют ссылки на один реестр файлов и не требуют повторной загрузки одной фотографии.

## Что попросить у AI

- «Собери черновик статьи из моего текста и двух фотографий. Сохрани смысл, добавь заголовки, подписи и alt. Первую фотографию используй как обложку. Код оформи отдельным блоком JavaScript. Покажи результат; на проверку пока не отправляй».
- «Оформи работу портфолио: задача, моя роль, процесс и подтверждённый результат. Снимки экрана собери в галерею. Не придумывай дату, срок, цену и показатели; неизвестные поля оставь пустыми».
- «Подготовь услугу с ожидаемым результатом, этапами и ограничениями. Используй приложенный пример как превью. Существующие связи и материалы сохрани».

Файлы из переписки с моделью сами по себе не становятся файлами Аишки. Приложение должно иметь разрешённый доступ к байтам выбранного файла. OpenAI file ID, локальный путь, Base64, blob URL и внешняя ссылка не являются UUID готового вложения.

## 1. Выбрать профиль и права

Прочитайте `GET /api/v1/me/capabilities` или MCP `getMyCapabilities`. Сверьте `profileUuid`: личный и корпоративный автор — разные профили. Токен привязан к профилю; передать чужой `profileUuid` в публикации нельзя. Все файлы должны принадлежать тому же профилю.

Для статьи нужны `profiles:read`, `files:write`, `files:read`, `articles:write`, `articles:read`. Для портфолио замените `articles:*` на `portfolio:*`, для услуги — на `services:*`. Это конкретные разрешения, не буквальный wildcard. Отправка на проверку требует отдельного `<раздел>:submit` и подтверждения владельца. Ключ хранится в приложении, а не в тексте запроса к модели. HTTP требует разрешённую surface `api`; MCP — `mcp`. Если MCP-клиент также отправляет байты по HTTP, ему нужен разрешённый HTTP транспорт.

## 2. Загрузить фотографию

Пример ниже — шаблон HTTP-запросов. Вычислите размер в байтах и SHA-256 исходного файла; сгенерируйте UUID каждой команды один раз и сохраните их перед запросом. При повторе используйте те же UUID и те же аргументы.

### Подготовка

```http
POST /api/v1/files/uploads
Authorization: Bearer <токен приложения>
Content-Type: application/json

{
  "requestUuid": "<сохранённый UUID команды подготовки>",
  "name": "studio.jpg",
  "mimeType": "image/jpeg",
  "size": 184632,
  "sha256": "<64 символа SHA-256 реального файла>",
  "purpose": "article"
}
```

`184632` — только пример размера. Используйте фактические байты своего файла. Для работы укажите `purpose: "portfolio"`, для услуги — `"service"`. Назначение должно совпасть с видом публикации; повторно использовать файл другого назначения нельзя.

Ответ содержит `uuid`, `status: "pending_upload"`, `uploadUrl`, `expiresAt`. Сохраните ответ. Срок загрузки берите из ответа, не задавайте самостоятельно.

### Передача байтов

```http
PUT <uploadUrl из ответа>
Authorization: Bearer <тот же токен>
Content-Type: application/octet-stream

<исходные байты файла, без JSON, multipart и Base64>
```

Проверьте origin и путь `uploadUrl` перед отправкой токена; не следуйте внешним перенаправлениям. Успешная передача возвращает `processing`. Это ещё не `ready`. Сервер проверяет размер, SHA-256, сигнатуру и допустимый формат. Для SVG дополнительно отклоняются опасные элементы и внешние ссылки.

### Завершение и проверка

```http
POST /api/v1/files/uploads/<uuid файла>/complete
Authorization: Bearer <тот же токен>
Content-Type: application/json

{"requestUuid":"<сохранённый новый UUID команды завершения>"}
```

Проверьте `status: "ready"` в ответе. После неизвестного исхода повторите ту же команду или прочитайте `GET /api/v1/files/<uuid>`. Не прикрепляйте `pending_upload`, `processing`, `expired` и отклонённые файлы. Повторите последовательность для каждой фотографии.

Ограничения: большинство файлов — до 25 МиБ; видео для портфолио — до 100 МиБ; файл заказа — до 1 МиБ. Публикация — до 40 уникальных файлов и 300 МиБ; заказ — до 5 файлов и 2 МиБ. Хранилище профиля — 2 ГиБ. Серверный ответ имеет приоритет. Фото: JPEG, PNG, WebP, GIF, AVIF, безопасный SVG; видео: MP4/WebM; аудио: WebM/MP3/OGG/WAV. Поддерживаются также PDF, TXT/MD/LOG/VTT/SRT, CSV, JSON, ZIP, DOCX, XLSX с соответствующим MIME. Произвольные внешние URL сервер не скачивает.

## 3. Привязать готовые фотографии

В `data.content.assets` используйте UUID из шага 2. Не копируйте байты, внешние ссылки или сведения о хранилище в публикацию. `name`, `mimeType`, `size`, `url` сервер заполнит по записи файла.

```json
{
  "version": 1,
  "assets": [
    {
      "id": "<ready UUID первой фотографии>",
      "kind": "image",
      "caption": "Предметный кадр: светлая керамическая ваза на нейтральном фоне.",
      "alt": "Светлая керамическая ваза на бежевом фоне.",
      "showInGallery": true
    }
  ],
  "blocks": []
}
```

`caption` видна под фотографией и объясняет её смысл. `alt` описывает изображение для доступности. Название файла не заменяет подпись. Не подписывайте фото сотрудников как результаты проекта. При отправке на проверку изображения требуют подписи.

`mediaIds` в блоках, `preview.assetIds`, `article.coverId`, `posterId` и другие ссылки должны указывать на элементы `content.assets`. Один файл можно использовать в нескольких блоках без повторной загрузки и дубля в assets. Не удаляйте файл из assets, пока на него ссылаются блоки или обложка.

## 4. Форматировать текст блоками

`data.content.version` равен `1`. `blocks` — упорядоченный массив, у каждого уникальный стабильный `id`. Для совместимости с редактором ограничьте документ 100 блоками; серверный предел — 200. В JSON-шаблонах заполнены стандартные поля всех блоков; при редактировании сохраняйте их и существующие `id`. Пропущенные title/lead/text/author/role/language сервер заполнит пустой строкой, mediaIds/items — пустым массивом, layout — wide, tone — plain. Явный null или значение неверного типа отклоняются.

Поддерживается ограниченное текстовое форматирование, а не произвольный HTML или весь CommonMark:

- `**важное**` — полужирное; `*уточнение*` — курсив; обратные кавычки — код в строке.
- `[название](https://example.org/page)` — ссылка. Используйте безопасный HTTP(S) адрес.
- Пустая строка разделяет абзацы. `#`, `##`, `###` в начале отдельного абзаца создают вложенные заголовки; основное название публикации уже выводится как H1.
- Строки `- пункт` создают маркированный список, если весь абзац состоит из таких строк.
- Для нумерации используйте `type: "steps", items: [...]`; для списка — `type: "list"`.
- Для кода используйте `type: "code", language: "javascript", text: "..."`; HTML-пример допустим как текст кода. Не вставляйте `<img>`, `<script>` или HTML-разметку в `richtext`.
- Таблицы задавайте через `type: "table", table: {headers: [...], rows: [[...]]}`; Markdown-таблицы не разбираются.
- Цитата: `type: "quote"`, `text`, `author`, `role`; цитата не создаёт отзыв или рейтинг.
- Акцент: `type: "callout"`, `text`. Изображение: `type: "image", mediaIds: [...]`. Галерея: `type: "gallery", mediaIds: [...]` в нужном порядке.

Остальные существующие типы: `text`, `richtext`, `video`, `audio`, `comparison` (два mediaIds: до/после), `checklist` (items и checked), `buttons` (buttons с id/label/url/style), `prompt`, `storyboard`, `linkcard`, `download`. Их необязательные вложенные поля сверяйте с сохранённой записью и интерфейсом; не придумывайте новые типы.

Смотрите [полные JSON-примеры](https://eval.su/docs/examples/media-publications.json): статья содержит форматированный текст, галерею, этапы, код и таблицу; портфолио — задачу, галерею и результат; услуга — список состава, этапы, результат и ограничения.

## 5. Обложка статьи и карточка в списке

Обложка подробной статьи и превью карточки настраиваются отдельно:

```json
{
  "article": {
    "lead": "",
    "coverId": "<ready UUID обложки>",
    "readingMinutes": "",
    "tags": ["Интерфейсы"],
    "sources": []
  },
  "preview": {
    "kind": "gallery",
    "assetIds": ["<ready UUID первого фото>", "<ready UUID второго фото>"],
    "title": "",
    "summary": "",
    "fit": "contain",
    "focalPoint": {"x": 50, "y": 50}
  }
}
```

Это поля внутри `data.content`. `fit: "contain"` показывает макет целиком; `cover` заполняет рамку с кадрированием. Координаты фокуса — проценты. Для одной картинки используйте `preview.kind: "image"`; для видео — `"video"` с видео в assetIds и изображением posterId. Превью галереи показывает до пяти кадров, полный материал хранит все.

`articlePublishedAt` и `caseReleasedAt` находятся в `data`, формат `YYYY-MM-DD`. Неизвестная дата — пустая строка или отсутствие поля. `authors` содержит реальные name, необязательные role/profileId; не подставляйте текущую дату или имя модели как историческое авторство. Дата кейса не заменяет служебные createdAt/updatedAt. Цену, срок и достижения не выдумывайте.

## 6. Сохранить и прочитать черновик по HTTP

Возьмите один `examples[].arguments` из JSON-шаблонов. Замените демонстрационный текст и UUID реальными; добавьте один новый сохранённый `requestUuid`. Отправьте JSON на путь, соответствующий `operationId`:

- статья: `POST /api/v1/commands/articles.save`;
- работа: `POST /api/v1/commands/portfolio.save`;
- услуга: `POST /api/v1/commands/services.save`.

Тело: `{ "requestUuid": "...", "data": { ... } }`. `kind` в тело этих команд не добавляйте: его задаёт операция. Ответ — сохранённая запись с `uuid`, `revision`, `status`, `draft`, `validationErrors`, `webUrl`. Проверьте её через `GET /api/v1/commands/articles.get?publicationUuid=<uuid>` (или portfolio.get/services.get), затем откройте полученный webUrl под правами владельца и посмотрите обложку, подписи, галерею, код и мобильную версию. Публичный GET не показывает приватный черновик.

Для редактирования сначала прочитайте свою актуальную запись и объедините изменения с `draft`. `data` заменяет весь черновик, это не PATCH. Сохраните неизвестные вам блоки, файлы, авторство и связи. Передайте `publicationUuid` и `expectedRevision` из чтения, новый requestUuid для новой команды. При `409` перечитайте и согласуйте конфликт; не перезаписывайте чужие изменения слепым повтором с новой revision.

Сохранение даёт `draft`, а не публикацию. Не вызывайте submit автоматически. Для отправки на проверку подготовьте `articles.submit` / `portfolio.submit` / `services.submit` через `POST /api/v1/integration-commands/prepare` с publicationUuid и expectedRevision в arguments, дождитесь подтверждения владельца по approvalUrl и выполните commit. `requestUuid` и `profileUuid` внутри arguments подготовленной команды не передаются. `pending` означает проверку, не опубликованный материал.

## 7. Та же последовательность в MCP

MCP-мутатор сначала только готовит команду. Полные имена tool берите из tools/list; для этого сценария это `files_prepare`, `files_complete`, `articles_save`, `portfolio_save`, `services_save`.

1. Вызовите `files_prepare` без requestUuid. Сохраните `result.commandUuid`.
2. Вызовите `aishka_commit_command` с этим commandUuid. Успех: `result.state: "succeeded"`; запись файла находится в `result.result`. Получите uploadUrl/uuid.
3. HTTP-компонент приложения отправляет реальные байты по PUT. MCP tool для бинарной передачи не существует; не кладите Base64 в аргументы инструмента.
4. `files_complete` с fileUuid → `aishka_commit_command` → проверьте ready в сохранённом результате.
5. `articles_save` с `data` из шаблона → `aishka_commit_command` → проверьте сохранённую запись. Для работы/услуги используйте соответствующий tool.
6. При таймауте прочитайте `aishka_get_command` с тем же commandUuid. Повторяйте commit этого UUID; не готовьте новую команду, пока исход предыдущей неизвестен.

Вызов prepare, текст модели и одобрение OpenAI не заменяют выполнение команды и подтверждение в Аишке. Реальная передача файлов конкретным ChatGPT-клиентом зависит от его доступных средств; если он не может передать байты, пользователь загружает файл в интерфейсе, а AI продолжает по готовому UUID. Это не автоматическая поддержка библиотеки файлов ChatGPT.

## Исполняемый пример и проверка результата

В [готовом ZIP](https://eval.su/docs/examples/media.zip) есть `publication-draft.mjs`, три файла `article.example.json`, `portfolio.example.json`, `service.example.json`, оба JPEG в `photos/` и инструкция. Примеры запускаются без доступа к репозиторию. По умолчанию пример проверяет манифест без сетевых записей; явный `--save-draft` включает загрузку и сохранение черновика. Журнал хранит UUID повторов, а секрет берётся из окружения. Этот пример не отправляет материал на модерацию и не вызывает OpenAI.

Перед завершением подтвердите фактические ответы: каждый файл ready; сохранены UUID и revision; все mediaIds разрешаются; галерея имеет подписи; код и таблица представлены блоками; сохранены авторство и существующие связи. Сообщайте отдельно «сохранён черновик», «отправлено на проверку» и «опубликовано» по серверному состоянию.
