aegida-console
README.md

AI Control Center

Корпоративный Next.js-интерфейс для Aegida Gate. Приложение делегирует вход в user-service, хранит историю диалогов в PostgreSQL и оставляет браузер на same-origin /api/...: адрес Gate и инфраструктурные настройки доступны только серверным Route Handlers.

Локальный запуск

Нужны два внешних сервиса:

  • user-service на http://localhost:8080;
  • aegida-gate на http://localhost:8082 (порт 8082 выбран, чтобы не конфликтовать с user-service).

В Gate настройте AEGIDA_GATE_PORT=8082, JWT_ISSUER=corp-ui, JWT_AUDIENCE=aegida-gate и JWT_HS256_SECRET, равный JWT_SECRET из corp-ui. Затем:

docker compose up -d db minio
cp .env.example .env.local
npm install
npm run db:migrate
npm run dev

Интерфейс доступен на http://localhost:3000, PostgreSQL — на localhost:5433. MinIO нужен только для чтения legacy-вложений, созданных до перехода на Gate Files API; новые файлы туда не записываются.

Для контейнерного запуска при уже работающих host-сервисах используйте docker compose up --build -d. Контейнер обращается к user-service на host.docker.internal:8080, а к Gate — на host.docker.internal:8082.

Переменные окружения

DATABASE_URL=postgresql://ai-control-chat-ui:ai-control-chat-ui@localhost:5433/ai_control_chat_ui
JWT_SECRET=replace-with-at-least-32-random-characters
JWT_ISSUER=corp-ui
JWT_AUDIENCE=aegida-gate
AEGIDA_TENANT_ID=tenant-1
AEGIDA_GATE_URL=http://localhost:8082
AUTH_SEED_ENABLED=false
AUTH_USER_ID=00000000-0000-4000-8000-000000000001
AUTH_EMAIL=demo@ai-control.local
AUTH_PASSWORD=Demo1234!
NEXT_PUBLIC_APP_URL=http://localhost:3000
USER_SERVICE_URL=http://localhost:8080
S3_ENDPOINT=http://localhost:9000
S3_REGION=us-east-1
S3_BUCKET=ai-control-chat-attachments
S3_ACCESS_KEY_ID=ai-control-chat-ui
S3_SECRET_ACCESS_KEY=ai-control-chat-ui
S3_FORCE_PATH_STYLE=true

AEGIDA_GATE_URL — строго server-only переменная: не добавляйте к ней префикс NEXT_PUBLIC_. В браузер не попадают ни base URL Gate, ни общий service key. Corp-ui пересылает Gate исходный JWT вошедшего пользователя.

JWT должен быть согласован с Gate:

  • JWT_SECRET в corp-ui равен JWT_HS256_SECRET в Gate;
  • JWT_ISSUER и JWT_AUDIENCE имеют одинаковые значения в обоих сервисах;
  • AEGIDA_TENANT_ID выпускается как claim tenant_id;
  • external_user_id берётся из профиля user-service и передаётся канонической десятичной строкой.

AUTH_* используются только при явно включённом legacy seed и не заменяют интерактивную аутентификацию через USER_SERVICE_URL. Все демонстрационные секреты необходимо заменить за пределами локальной среды. Никогда не коммитьте .env.local.

Интеграция с Aegida Gate

Модели

GET /api/models получает OpenAI-совместимый каталог /v1/models от имени текущего пользователя. Идентификаторы моделей динамические и валидируются как стабильные slugs; безопасный default/fallback — auto. Выбор сохраняется только после повторной серверной проверки каталога.

Chat и streaming

Клиент отправляет в POST /api/chat только локальный контракт:

{
  "conversationId": "optional-existing-conversation-id",
  "model": "auto",
  "content": "Привет",
  "attachmentIds": ["optional-local-attachment-id"]
}

Route Handler собирает историю из PostgreSQL и вызывает POST /v1/chat/completions с stream:true. OpenAI SSE разбирается инкрементально; браузер по-прежнему получает plain UTF-8 поток и заголовки X-Conversation-Id, X-User-Message-Id, X-Assistant-Message-Id. Upstream payload и диагностические тела не раскрываются клиенту.

Для повторения последнего прерванного/ошибочного ответа используется { "conversationId": "...", "model": "auto", "retry": true }.

Files API

Загрузка двухшаговая:

  1. corp-ui передаёт исходный multipart file и purpose=user_data в POST /v1/files с JWT пользователя;
  2. в PostgreSQL сохраняются локальные метаданные и opaque gate_file_id, а Chat получает native part { "type":"file", "file": { "file_id":"file-..." } }.

Ни S3 key, ни presigned URL не отправляются на inference. Миграция сохраняет старые object_key: такие legacy-строки продолжают скачиваться через приватный MinIO до окончания retention. После удаления/переноса всех legacy-строк можно отдельной миграцией убрать S3 runtime wiring и bucket bootstrap.

Эффективный баланс

GET /api/me/quota?model=... проксирует /v1/aegida/quota. Это информационный authoritative snapshot, а не резервирование токенов: UI не уменьшает значение оптимистически и обновляет его после model switch и каждого turn. Отправка блокируется только при явном exhausted:true; сбой quota dependency показывает Баланс недоступен, но не блокирует Chat.

Проверка

npm test
npm run lint
npm run build
./scripts/smoke-db.sh
git diff --check

Smoke-скрипт печатает SKIP и завершается успешно, если отсутствуют Docker, user-service или готовый Gate. Для полного smoke в user-service должна существовать учётная запись из SMOKE_EMAIL/SMOKE_PASSWORD (по умолчанию demo-значения из Compose), а Gate должен доверять тому же JWT contract.

Kubernetes (minikube)

Манифесты разворачивают corp-ui, его PostgreSQL и временный legacy MinIO в namespace aegida-services. Они ожидают уже существующие Service:

  • user-service.aegida-services.svc.cluster.local:8080;
  • aegida-gate.aegida-services.svc.cluster.local:8080.
minikube image build -t ai-control-chat-ui:minikube .
kubectl apply -f k8s/namespace.yaml
kubectl apply -f k8s/config.yaml
kubectl apply -f k8s/postgres.yaml
kubectl apply -f k8s/minio.yaml
kubectl apply -f k8s/app.yaml
kubectl -n aegida-services port-forward service/ai-control-chat-ui 3000:3000

Ingress намеренно отсутствует. Development Secret в k8s/config.yaml необходимо заменить; его JWT_SECRET должен совпадать с JWT_HS256_SECRET деплоймента Gate. Дополнительные проверки описаны в k8s/README.md.