Дата: 2026-07-26
Создать первую рабочую версию корпоративного интерфейса для общения с ИИ-моделями и будущими агентами. По структуре и поведению интерфейс должен быть знаком пользователям ChatGPT, использовать компоненты shadcn и не зависеть от особенностей конкретного провайдера моделей.
Первая версия включает:
Authorization;В первую версию не входят корпоративный каталог пользователей, регистрация, восстановление пароля, досрочный отзыв JWT, серверное хранение истории, загрузка файлов, поиск по документам, голосовой режим, управление агентами и административная панель.
До открытия чата приложение проверяет сохраненный JWT. Во время проверки показывается нейтральный полноэкранный индикатор загрузки, поэтому форма входа не появляется даже на мгновение у уже авторизованного пользователя.
Если валидного JWT нет, приложение показывает отдельный экран входа с названием продукта, полями электронной почты и пароля, кнопкой входа и областью для общей ошибки. Карточка входа использует ту же типографику, палитру и shadcn-компоненты, что и основной интерфейс.
Для локальной первой версии используется тестовый пользователь:
demo@ai-control.local;Demo1234!.Значения тестовых учетных данных задаются серверными переменными окружения. Пароль не включается в клиентскую сборку и не выводится автоматически на экран входа.
Экран состоит из боковой панели и основной области чата.
Боковая панель содержит название продукта, кнопку нового чата, список недавних диалогов и компактный блок профиля-заглушки. На узких экранах панель открывается как выезжающее меню.
В верхней части основной области расположен селектор модели. Центральная область показывает пустое приветственное состояние или ленту сообщений. В нижней части закреплен композер с многострочным вводом, кнопкой отправки и кнопкой остановки во время генерации.
Визуальный стиль — спокойный корпоративный минимализм: светлая нейтральная палитра, мягкие границы, умеренные скругления, типографика Geist, shadcn-компоненты и иконки Lucide. Интерфейс повторяет знакомую информационную архитектуру ChatGPT, но не копирует брендинг OpenAI.
Enter отправляет сообщение, Shift+Enter добавляет новую строку.localStorage только на текущем устройстве и изолируются ключом идентификатора пользователя.Модель представляется конфигурацией интерфейса:
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 и добавляет его ко всем защищенным запросам:
Authorization: Bearer <JWT>
При загрузке приложения клиент читает токен и вызывает 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 принимает нормализованный запрос:
type ChatRequest = {
model: "chatgpt" | "deepseek" | "qwen";
messages: Array<{
role: "user" | "assistant";
content: string;
}>;
};
Маршрут проверяет структуру запроса, вызывает единственный GatewayAdapter и возвращает клиенту поток текста. Клиентский контракт не меняется при добавлении новых провайдеров внутрь будущего Gateway.
Адаптер имеет один интерфейс: принимает нормализованный запрос и возвращает ReadableStream<Uint8Array>. Реализация выбирает транспорт по серверной конфигурации:
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;ChatRequest;Authorization: Bearer <key> добавляется только при наличии ключа;AbortSignal.localStorage./api/auth/me с Bearer-заголовком./api/chat с Bearer-заголовком.model и массив messages.GatewayAdapter выбирает HTTP- или демонстрационный транспорт.401; клиент удаляет такой токен и показывает вход.400.503 с безопасным пользовательским сообщением.localStorage по прямому требованию к header-based аутентификации, поэтому приложение не выполняет сырой HTML, не подключает сторонние скрипты и задает ограничивающую Content Security Policy.JWT_SECRET, тестовый пароль и AI_GATEWAY_API_KEY существуют только в серверной конфигурации. В репозиторий добавляются только названия переменных и безопасные примеры без рабочего секрета.401 и выход.