# Корпоративный интерфейс чата с ИИ Дата: 2026-07-26 ## Цель Создать первую рабочую версию корпоративного интерфейса для общения с ИИ-моделями и будущими агентами. По структуре и поведению интерфейс должен быть знаком пользователям ChatGPT, использовать компоненты shadcn и не зависеть от особенностей конкретного провайдера моделей. ## Объём первой версии Первая версия включает: - экран входа с одним тестовым пользователем; - JWT-аутентификацию через заголовок `Authorization`; - адаптивный интерфейс чата для настольных и мобильных экранов; - создание нового чата и локальную историю диалогов; - выбор маршрута ChatGPT, DeepSeek или Qwen; - отправку сообщений, потоковое отображение ответа и остановку генерации; - копирование ответа и повторную отправку последнего запроса; - единый серверный адаптер для будущего AI Gateway; - демонстрационный потоковый транспорт для работы без развернутого Gateway; - понятные состояния загрузки, пустого чата, отсутствующей конфигурации и сетевой ошибки. В первую версию не входят корпоративный каталог пользователей, регистрация, восстановление пароля, досрочный отзыв JWT, серверное хранение истории, загрузка файлов, поиск по документам, голосовой режим, управление агентами и административная панель. ## Пользовательский интерфейс ### Экран входа До открытия чата приложение проверяет сохраненный JWT. Во время проверки показывается нейтральный полноэкранный индикатор загрузки, поэтому форма входа не появляется даже на мгновение у уже авторизованного пользователя. Если валидного JWT нет, приложение показывает отдельный экран входа с названием продукта, полями электронной почты и пароля, кнопкой входа и областью для общей ошибки. Карточка входа использует ту же типографику, палитру и shadcn-компоненты, что и основной интерфейс. Для локальной первой версии используется тестовый пользователь: - электронная почта: `demo@ai-control.local`; - пароль: `Demo1234!`. Значения тестовых учетных данных задаются серверными переменными окружения. Пароль не включается в клиентскую сборку и не выводится автоматически на экран входа. ### Общая компоновка Экран состоит из боковой панели и основной области чата. Боковая панель содержит название продукта, кнопку нового чата, список недавних диалогов и компактный блок профиля-заглушки. На узких экранах панель открывается как выезжающее меню. В верхней части основной области расположен селектор модели. Центральная область показывает пустое приветственное состояние или ленту сообщений. В нижней части закреплен композер с многострочным вводом, кнопкой отправки и кнопкой остановки во время генерации. Визуальный стиль — спокойный корпоративный минимализм: светлая нейтральная палитра, мягкие границы, умеренные скругления, типографика Geist, shadcn-компоненты и иконки Lucide. Интерфейс повторяет знакомую информационную архитектуру ChatGPT, но не копирует брендинг OpenAI. ### Поведение - `Enter` отправляет сообщение, `Shift+Enter` добавляет новую строку. - Пустое сообщение отправить нельзя. - После отправки текст пользователя сразу появляется в ленте. - Ответ ассистента добавляется частями по мере поступления потока. - Во время генерации пользователь может остановить запрос. - При смене модели новый выбор применяется к следующим сообщениям текущего чата. - Новый чат начинается с выбранной в данный момент моделью. - Название диалога формируется из первого пользовательского сообщения. - История и выбранная модель сохраняются в `localStorage` только на текущем устройстве и изолируются ключом идентификатора пользователя. - Выход удаляет JWT и пользовательское состояние из памяти приложения, после чего показывает экран входа. ## Модель данных Модель представляется конфигурацией интерфейса: ```ts type ModelOption = { id: "chatgpt" | "deepseek" | "qwen"; name: string; description: string; }; ``` Диалог содержит идентификатор, название, выбранный `modelId`, время обновления и массив сообщений. Сообщение содержит идентификатор, роль `user` или `assistant`, текст, время создания и необязательное состояние ошибки. Отображаемые модели являются маршрутами AI Gateway. В приложении нет сведений о базовых URL, API-форматах или ключах отдельных провайдеров. ## Архитектура ### Аутентификация `POST /api/auth/login` принимает электронную почту и пароль. Сервер сравнивает их с `AUTH_EMAIL` и `AUTH_PASSWORD`, не раскрывая клиенту, какое именно поле неверно. При успехе сервер подписывает JWT секретом `JWT_SECRET` и возвращает его в JSON. JWT подписывается алгоритмом HS256 и содержит только `sub`, `email`, `iat` и `exp`. Срок между `iat` и `exp` равен 24 часам. Токен не обновляется автоматически: он остается неизменным до выхода или истечения срока и переживает перезапуск браузера благодаря `localStorage`. Клиент хранит JWT под ключом `ai-control-center:auth-token` и добавляет его ко всем защищенным запросам: ```http Authorization: Bearer ``` При загрузке приложения клиент читает токен и вызывает `GET /api/auth/me`. Пока проверка не завершена, ни вход, ни чат не отображаются. Валидный ответ открывает чат; ответ `401` удаляет токен и открывает вход. `GET /api/auth/me` и `POST /api/chat` проверяют наличие Bearer-заголовка, подпись, алгоритм, срок действия и обязательные поля JWT. Защита API всегда выполняется на сервере и не полагается на состояние интерфейса. Поскольку обычная загрузка страницы браузером не позволяет приложению добавить произвольный заголовок, HTML-оболочка остается доступной без авторизации. Пользовательские данные и возможности чата становятся доступны только после проверки JWT через защищенный API. ### Клиент Клиентская страница отвечает за состояние чатов, модель, ввод, потоковый ответ и локальное сохранение истории. UI-компоненты разделяются по назначению: боковая панель, селектор модели, лента сообщений, сообщение, пустое состояние и композер. Клиент обращается только к внутреннему маршруту `POST /api/chat`, добавляя пользовательский JWT в `Authorization`, и не получает адрес или ключ AI Gateway. ### Серверный маршрут `POST /api/chat` принимает нормализованный запрос: ```ts type ChatRequest = { model: "chatgpt" | "deepseek" | "qwen"; messages: Array<{ role: "user" | "assistant"; content: string; }>; }; ``` Маршрут проверяет структуру запроса, вызывает единственный `GatewayAdapter` и возвращает клиенту поток текста. Клиентский контракт не меняется при добавлении новых провайдеров внутрь будущего Gateway. ### GatewayAdapter Адаптер имеет один интерфейс: принимает нормализованный запрос и возвращает `ReadableStream`. Реализация выбирает транспорт по серверной конфигурации: - если задан `AI_GATEWAY_URL`, запрос передается по HTTP в будущий Gateway; - если задано `AI_GATEWAY_MOCK=true`, используется детерминированный потоковый ответ без внешнего вызова; - если не настроен ни один транспорт, маршрут возвращает контролируемую ошибку конфигурации. Для HTTP-транспорта используются `AI_GATEWAY_URL` и необязательный `AI_GATEWAY_API_KEY`. Ключ никогда не сериализуется в клиентский код. Gateway получает `model` как непрозрачный идентификатор маршрута. Пользовательский JWT не передается в AI Gateway. Исходящий запрос использует отдельный `AI_GATEWAY_API_KEY`, если он настроен. Контракт Gateway для первой версии: - `POST` на адрес из `AI_GATEWAY_URL`; - JSON-тело соответствует `ChatRequest`; - заголовок `Authorization: Bearer ` добавляется только при наличии ключа; - успешный ответ возвращается как поток UTF-8 текста; - отмена клиентского запроса прерывает исходящий запрос через `AbortSignal`. ## Поток данных ### Вход и восстановление сессии 1. При старте клиент читает JWT из `localStorage`. 2. При отсутствии токена сразу показывается экран входа. 3. При наличии токена клиент запрашивает `/api/auth/me` с Bearer-заголовком. 4. Сервер проверяет подпись и срок действия JWT. 5. При успехе клиент открывает чат без промежуточного показа входа. 6. При ошибке клиент удаляет токен и показывает вход. 7. После успешной отправки формы входа клиент сохраняет выданный JWT и открывает чат. ### Общение с моделью 1. Пользователь выбирает модель и отправляет сообщение. 2. Клиент добавляет сообщение в локальное состояние и вызывает `/api/chat` с Bearer-заголовком. 3. Сервер проверяет JWT, затем `model` и массив `messages`. 4. `GatewayAdapter` выбирает HTTP- или демонстрационный транспорт. 5. Сервер прозрачно передает поток клиенту. 6. Клиент постепенно обновляет сообщение ассистента и сохраняет завершенный диалог локально. 7. При остановке генерации клиент отменяет запрос, а сервер передает отмену Gateway. ## Ошибки и безопасность - Отсутствующий, некорректный или истекший JWT возвращает `401`; клиент удаляет такой токен и показывает вход. - Неверная пара логина и пароля возвращает одинаковое общее сообщение без уточнения причины. - Неизвестная модель и некорректные сообщения возвращают `400`. - Отсутствующая серверная конфигурация возвращает `503` с безопасным пользовательским сообщением. - Ошибки Gateway нормализуются; внутренние URL, ключи и необработанные ответы провайдера клиенту не раскрываются. - Один запрос может содержать не более 100 сообщений, не более 32 000 символов в одном сообщении и не более 128 000 символов суммарно. - При сетевой ошибке пользовательское сообщение остается в диалоге, сообщение ассистента получает состояние ошибки, а интерфейс предлагает повторить запрос. - Markdown первой версии отображается как безопасный текст с сохранением переносов строк; выполнение HTML исключено. - Пользовательский JWT хранится в `localStorage` по прямому требованию к header-based аутентификации, поэтому приложение не выполняет сырой HTML, не подключает сторонние скрипты и задает ограничивающую Content Security Policy. - `JWT_SECRET`, тестовый пароль и `AI_GATEWAY_API_KEY` существуют только в серверной конфигурации. В репозиторий добавляются только названия переменных и безопасные примеры без рабочего секрета. ## Проверка качества - Модульные тесты проверяют успешный и неуспешный вход, срок JWT 24 часа, проверку подписи и отклонение истекшего токена. - Тесты интерфейса проверяют отсутствие вспышки формы входа при валидном сохраненном JWT, показ входа при `401` и выход. - Модульные тесты проверяют валидацию запроса, выбор транспорта, заголовок авторизации, потоковую передачу и нормализацию ошибок. - Тесты компонентов проверяют выбор модели, отправку по Enter, запрет пустого сообщения, остановку генерации и восстановление локальной истории. - Ручная проверка охватывает настольный и мобильный макет, длинные сообщения, пустое состояние и ошибки Gateway. - Перед завершением должны успешно пройти линтер, тесты и production-сборка. ## Критерии готовности - Тестовый пользователь может войти, перезапустить браузер и не увидеть форму входа, пока JWT действителен. - Без валидного Bearer JWT получить данные пользователя или вызвать чат невозможно. - JWT имеет срок действия ровно 24 часа, не обновляется автоматически и удаляется при выходе или серверном отклонении. - Пользователь может создать чат, выбрать один из трех маршрутов и получить потоковый ответ. - Интерфейс визуально и поведенчески напоминает ChatGPT, оставаясь самостоятельным корпоративным продуктом. - Без Gateway приложение можно продемонстрировать в явно включенном локальном режиме. - Подключение будущего AI Gateway требует только настройки серверных переменных и соблюдения описанного контракта, без изменений клиентских компонентов. - Адрес и ключ Gateway не попадают в браузерную сборку или локальную историю.