# Диалоги

Покажите покупателю список его диалогов, дайте продолжить выбранный разговор или убрать его из истории. Сообщения выбранного диалога загружаются отдельным методом [истории](history.md).

Список и скрытие требуют `Authorization: Bearer gst_...`. Сервер определяет секцию и владельца истории по [гостевому токену](guest_sessions.md). Во внешнем `resourceId` сохраняйте идентификатор из основной интеграции Gravity Field: `user.uid` либо применяемый `user.custom`. Один токен подходит для нескольких диалогов своей сессии. Статический API-ключ не заменяет гостевой токен.

Список содержит диалоги только текущей гостевой сессии. Повторная регистрация с тем же `resourceId` создаёт новую сессию с пустой историей и памятью ассистента. Прежние диалоги доступны по прежнему действующему токену; совпадение UID не объединяет сессии.

## Список диалогов

### `GET /shopping/dialogs`

Переменная окружения `GUEST_ACCESS_TOKEN` в примерах содержит полный токен сохранённой гостевой сессии.

```bash
curl --request GET \
  --url 'https://shopping-assistant-api.gravityfield.ai/shopping/dialogs?limit=20' \
  --header "Authorization: Bearer $GUEST_ACCESS_TOKEN"
```

| Query-параметр | Обязательное | Назначение |
| --- | --- | --- |
| `limit` | Нет | Количество диалогов: целое число от `1` до `100`, по умолчанию `20`. |
| `cursor` | Нет | Значение `pagination.nextCursor` из предыдущего ответа. Для первой страницы не передаётся. |

Тело запроса не требуется. Не передавайте `tenant_id`, `resourceId` или другие query-параметры вне таблицы — API вернёт `400`.

### Ответ списка

API возвращает `200 OK`:

```json
{
  "items": [
    {
      "threadId": "thread-8f2b1c",
      "title": "Помоги выбрать корм для котёнка",
      "createdAt": "2026-10-16T10:00:00.000000Z",
      "lastMessageAt": "2026-10-16T10:01:00.000000Z"
    }
  ],
  "pagination": {
    "hasMore": false,
    "nextCursor": null
  }
}
```

| Поле | Назначение |
| --- | --- |
| `items[].threadId` | ID диалога для загрузки сообщений, продолжения и скрытия. |
| `items[].title` | Заголовок из начала первого сообщения; если он пустой — `Диалог`. |
| `items[].createdAt` | Время создания диалога в ISO 8601. |
| `items[].lastMessageAt` | Время последнего сохранённого сообщения покупателя или ассистента в ISO 8601. |
| `pagination.hasMore` | Есть ли следующая страница. |
| `pagination.nextCursor` | Курсор следующей страницы или `null`, если её нет. |

В список попадают только доступные пользователю диалоги с сохранёнными сообщениями. Пустые и скрытые диалоги не возвращаются. При отсутствии диалогов ответ содержит `items: []`, `hasMore: false` и `nextCursor: null`.

Последние активные диалоги идут первыми. При одинаковом времени последнего сообщения используется порядок `threadId` по убыванию. Служебные сообщения не меняют порядок списка.

Если `hasMore` равно `true`, передайте `nextCursor` в query-параметре `cursor` следующего запроса. Курсор привязан к гостевой сессии и типу списка: не разбирайте его, не изменяйте и кодируйте как значение URL-параметра. Он не заменяет токен. Неверный или чужой курсор возвращает `400 INVALID_CURSOR`.

Список обновляется по мере общения. После продолжения диалога заново загрузите первую страницу и объедините записи по `threadId`, чтобы обновить порядок и избежать дублей.

## Открытие и продолжение

1. Возьмите `threadId` из списка и загрузите сообщения через [`POST /shopping/history` с пагинацией с конца](history.md#загрузка-с-конца).
2. Отобразите полученные сообщения. Историю не нужно повторно отправлять в генерацию.
3. Чтобы продолжить разговор, вызовите [`POST /shopping/generate`](generate.md) с тем же `threadId`, `resourceId` и гостевым токеном, передав только новое сообщение.
4. Для действия «Новый чат» создайте новый `threadId`, сохранив прежний идентификатор пользователя и действующий токен.

Отдельного метода создания пустого диалога нет: используйте первый запрос обычной генерации. При смене страницы сохраняйте текущий `threadId` вместе с `sessionId`, а в новом запросе генерации передавайте актуальный контекст страницы. Токен другой сессии не откроет этот диалог, даже если UID совпадает: API вернёт `404`.

## Убрать из истории

### `DELETE /shopping/dialogs/{threadId}`

Передайте ID выбранного диалога в пути и гостевой токен в заголовке. Тело запроса не требуется:

```bash
curl --request DELETE \
  --url 'https://shopping-assistant-api.gravityfield.ai/shopping/dialogs/thread-8f2b1c' \
  --header "Authorization: Bearer $GUEST_ACCESS_TOKEN"
```

После успешного скрытия API возвращает `204 No Content`. Повторное скрытие своего диалога также возвращает `204`. Для чужого или отсутствующего диалога ответ — `404 DIALOG_NOT_FOUND`.

После скрытия:

- диалог исчезает из списка;
- чтение сообщений, продолжение, оценка и создание публичных ссылок возвращают `404`, включая прежний API истории, debug-генерацию и повтор генерации;
- ранее созданные публичные ссылки перестают открываться;
- сообщения остаются для аналитики магазина, а сохранённые предпочтения — для персонализации. Восстановление диалога через API не предусмотрено.

Называйте действие в интерфейсе **«Убрать из истории»**: оно не удаляет персональные данные. После `204` уберите диалог из интерфейса. Уже выполняющийся запрос может завершиться после скрытия — не возвращайте его поздний ответ в список и не вставляйте в другой открытый чат.

## Ошибки и ограничения

| HTTP | Причина | Что делать клиенту |
| --- | --- | --- |
| `400` | Некорректные параметры списка или ID диалога; `INVALID_CURSOR` — неверный курсор. | Проверить параметры. При ошибке курсора загрузить список с первой страницы. |
| `401` | Гостевой токен отсутствует, некорректен, истёк или отозван. | Проверить заголовок и состояние сохранённой сессии по [правилам работы с токеном](guest_sessions.md#ошибки-и-ограничения). |
| `403` | IP не разрешён для секции. | Проверить условия подключения. |
| `404` | При скрытии диалог не найден или не принадлежит владельцу токена. | Убрать недоступный диалог из интерфейса и обновить список. |
| `503` | `AUTH_STORAGE_UNAVAILABLE` — сервис не смог проверить доступ или выполнить операцию с диалогами. | Сохранить текущую сессию, показать ошибку и предложить повторить позже. |

Ответы используют `Cache-Control: no-store`. Не помещайте список в общий или публичный кеш. CORS разрешает `Authorization` и методы `GET`/`DELETE`; запросы `OPTIONS` не требуют токена.

Список и скрытие не объединяют историю сайта и приложения через аккаунт магазина. Доступ определяется текущей гостевой сессией.
