AAYMA
RU
Войти

EKT x Kochshi

Встраиваемый React-виджет AI-консультанта для ekt.kz.

Доведённый продукт
Команда «Strikers Club» 7💬 0 комментариевс 23 сентября 2026 г.

О проекте

Команда: Strikers Club
Кейс HackAlem.ai: Торговля — Электрокомплект · ekt.kz
Стадия: Доведённый продукт

Задача кейса: AI-консультант по каталогу ekt.kz: товары, аналоги, цены, остатки, корзина

Суть

Встраиваемый React-виджет AI-консультанта для ekt.kz. Ищет товары в живом каталоге EKT, подбирает аналоги, разбирает фото и спецификации (PDF/DOCX/XLSX) и после явного подтверждения добавляет товар в демо-корзину.

Как устроено

React-виджет общается с Node BFF, который хранит API-ключ на сервере и пропускает только разрешённые маршруты. BFF проксирует запросы в FastAPI. Бэкенд вызывает OpenAI Responses API с инструментами чата (chat_tools), обращается к EKT API за карточками, ценами и остатками, а гостевые сессии держит в Redis с TTL. Вложения проверяются через ClamAV и разбираются в список распознанных позиций с уверенностью. Корзина меняется только после подтверждения серверного предложения по уникальному operation_id, повтор идемпотентен.

Ключевые решения

  • OpenAI Responses API function calling
  • подбор аналогов с совпадениями и отличиями
  • извлечение позиций из фото и документов
  • идемпотентное подтверждение корзины по operation_id
  • BFF-прокси
  • ClamAV INSTREAM

Особенности

Продуманная безопасность: ключи не попадают в браузер, BFF ограничивает маршруты, размеры тел и origin, вложения сканирует антивирус. Есть двухшаговое подтверждение корзины с защитой от повторов и явный demo-режим без backend. В README заявлено 147 фронтенд-тестов, в репозитории лежат тестовые спецификации и накладные.

Стек

React 19 · TypeScript · Vite · Material UI · Redux Toolkit · TanStack Query · Node.js BFF · Python · FastAPI · Pydantic · Redis · ClamAV · Docker Compose · Vitest · Pytest

Модели

OpenAI (Responses API)


Проект создан за 5 часов на финале хакатона HackAlem.ai (23 сентября 2026, Астана). Код — BAITC-Hacks/hack-e3d8b0d1-strikers-club.

README

README.md

EKT x Kochshi

ИИ-ассистент по электротехнике

React FastAPI TypeScript Tests

Полноценный AI-консультант для сайта ekt.kz. Ассистент понимает запрос обычным языком, ищет электротехнические товары, сравнивает аналоги, разбирает фотографии и спецификации и безопасно добавляет выбранную позицию в демонстрационную корзину.

Репозиторий содержит весь проект:

  • React-виджет и презентационную страницу;
  • same-origin BFF на Node.js;
  • backend на FastAPI;
  • Redis для временных сессий;
  • профиль ClamAV для проверки вложений;
  • frontend- и backend-тесты.

Локальная папка backend/ игнорируется Git: это может быть дополнительный checkout для разработки. Backend проекта уже находится в корневых папках app/, tests/ и файлах Docker.


Проект за 30 секунд

Проблема

В каталоге электротехники тысячи похожих товаров и технических параметров. Покупателю приходится знать точный артикул, вручную сравнивать характеристики и проверять остатки. Ошибка в номинале или совместимости приводит к потере времени.

Решение

Strikers Club превращает поиск в диалог:

  1. Пользователь описывает задачу или прикладывает документ.
  2. AI находит товары в реальном каталоге EKT.
  3. Интерфейс показывает цену, остаток, параметры и отличия аналогов.
  4. Перед изменением корзины пользователь видит точное предложение.
  5. Товар добавляется только после явного подтверждения.

Польза

ПокупателюБизнесуКоманде
Поиск без знания каталогаМеньше потерянных обращенийВстраиваемый React-виджет
Понятное сравнение аналоговБыстрее путь от вопроса к товаруСтрогий API-контракт
Работа с фото и документамиКонсультация 24/7Ключи остаются на сервере
Контроль перед корзинойБезопасный UX без случайной покупкиПолный набор автотестов

Возможности

  • поиск по названию, артикулу и техническому описанию;
  • свежая карточка товара с ценой, остатками и характеристиками;
  • подбор аналогов с совпадениями, отличиями и неизвестными параметрами;
  • внешние варианты с обязательной пометкой «требует проверки»;
  • JPEG, PNG, PDF, DOCX и XLSX до 20 МБ;
  • отображение распознанных позиций и уверенности извлечения;
  • серверное предложение на добавление товара;
  • подтверждение по уникальному operation_id;
  • идемпотентный повтор после потери сетевого ответа;
  • временная гостевая сессия с TTL;
  • понятное восстановление после истечения сессии;
  • автономный demo-режим для презентации без backend.

Демонстрация для хакатона

Откройте http://localhost:5173 и нажмите круглую кнопку чата в правом нижнем углу.

Сценарий на 2–3 минуты:

  1. Спросите: «Найди кабель ВВГнг 3×2,5».
  2. Покажите карточку и откройте «Подробнее».
  3. Попросите подобрать аналог и покажите явные отличия.
  4. Напишите: «Добавь 1 единицу товара с ID 515291 в корзину».
  5. Покажите, что до подтверждения корзина не изменилась.
  6. Подтвердите предложение и откройте внутреннюю демо-корзину.
  7. Приложите фото или спецификацию при запущенном ClamAV.

Корзина демонстрационная: заказ и резерв на ekt.kz не создаются.


Архитектура

flowchart LR
    U[Пользователь] --> UI[React-виджет]
    UI --> BFF[Node BFF<br/>/backend/api]
    BFF -->|X-API-Key только на сервере| API[FastAPI]
    API --> AI[OpenAI]
    API --> EKT[Каталог EKT]
    API --> R[(Redis TTL)]
    API --> AV[ClamAV]

Зачем нужен BFF

Браузер не получает серверный API_KEY. Запросы идут на тот же origin через /backend/api/*, а BFF:

  • добавляет backend-ключ;
  • передаёт токен гостевой сессии;
  • разрешает только известные маршруты и методы;
  • проверяет origin и Content-Type;
  • ограничивает JSON до 16 КиБ и файл до 20 МиБ;
  • устанавливает timeout 120 секунд;
  • возвращает request_id для диагностики;
  • запрещает кеширование API.

Session token хранится только в памяти клиента. Он не записывается в Redux, localStorage, URL или логи.


Технологии

СлойТехнологии
FrontendReact 19, TypeScript, Vite, Material UI, Emotion
StateRedux Toolkit, TanStack Query
BFFNode.js HTTP server, Vite middleware
BackendPython 3.11+, FastAPI, Pydantic
AI и каталогOpenAI Responses API, EKT API
Временные данныеRedis с TTL
ВложенияClamAV INSTREAM
ТестыVitest, Testing Library, Pytest
КачествоESLint, Prettier, Ruff, MyPy

Быстрый запуск

Требования

  • Node.js 22.12+;
  • npm;
  • Docker и Docker Compose;
  • ключ OpenAI;
  • доступ к EKT API.

1. Настройте окружение

Linux/macOS:

cp .env.example .env

Windows PowerShell:

Copy-Item .env.example .env

Заполните в .env:

API_KEY=случайный_секрет_длиной_не_менее_32_символов
OPENAI_API_KEY=ваш_openai_key
EKT_API_USER=логин_ekt
EKT_API_PASSWORD=пароль_ekt

VITE_API_MODE=live
VITE_API_BASE_URL=/backend/api
BACKEND_URL=http://127.0.0.1:8000
BACKEND_API_PREFIX=/api

Для генерации API_KEY:

python -c "import secrets; print(secrets.token_urlsafe(48))"

Не добавляйте секреты в переменные с префиксом VITE_: они доступны браузеру. .env и .env.local исключены из Git.

2. Запустите backend

С поддержкой файлов:

docker compose --profile uploads up --build -d

Только текстовый чат:

docker compose up --build -d

Проверьте readiness:

curl http://127.0.0.1:8000/api/health/ready

Ожидаемый ответ:

{ "status": "ok", "redis": "ok" }

3. Запустите frontend

npm install
npm run dev

Откройте http://localhost:5173.

Vite читает корневой .env, но передаёт в браузер только VITE_*. Значение API_KEY использует серверная часть BFF.


Demo без backend

Если для презентации нет внешних ключей, создайте .env.local:

VITE_API_MODE=demo

Затем:

npm install
npm run dev

Demo использует только синтетические данные. При сбое live-backend приложение не переключается в demo автоматически, поэтому проблемы интеграции остаются видимыми.


Адреса

СервисАдрес
Frontendhttp://localhost:5173
BFF readinesshttp://localhost:5173/backend/api/health/ready
Backendhttp://127.0.0.1:8000
Backend readinesshttp://127.0.0.1:8000/api/health/ready
Swagger в developmenthttp://localhost:8000/docs

Swagger использует backend API_KEY. В production Swagger и OpenAPI отключены.


Основные переменные окружения

ПеременнаяНазначение
API_KEYАвторизация BFF в FastAPI
OPENAI_API_KEYДоступ к OpenAI
EKT_API_USER, EKT_API_PASSWORDДоступ к каталогу
REDIS_URLХранилище временных сессий
UPLOAD_SCAN_HOSTHost ClamAV; для Compose — clamav
VITE_API_MODElive или явный demo
VITE_API_BASE_URLПуть браузера к BFF
BACKEND_URLOrigin FastAPI для BFF
BACKEND_API_PREFIXОбычно /api
BACKEND_API_KEYОтдельный BFF-ключ; пустое значение использует API_KEY
PUBLIC_ORIGINТочный origin за reverse proxy

Полный список и безопасные значения по умолчанию находятся в .env.example.


HTTP API

МетодПутьНазначение
POST/api/chat/sessionsСоздать гостевую сессию
POST/api/chatОтправить текст
POST/api/chat/upload?session_id=…Отправить файл сырым телом
GET/api/products/{product_id}Получить свежую карточку
GET/api/cart?session_id=…Получить демо-корзину
POST/api/cart/itemsПодтвердить предложение
GET/api/health/liveПроверить процесс
GET/api/health/readyПроверить FastAPI и Redis

Подробные контракты:


Безопасное подтверждение корзины

sequenceDiagram
    participant U as Пользователь
    participant UI as Виджет
    participant API as Backend

    U->>UI: «Добавь 2 единицы»
    UI->>API: POST /chat
    API-->>UI: pending_cart_action + operation_id
    UI-->>U: Цена, остаток и срок
    U->>UI: Явное подтверждение
    UI->>API: POST /cart/items
    API-->>UI: added или reconfirmation_required
  • Новое предложение заменяет старое.
  • Отмена отправляется backend сообщением «отмена».
  • При изменении цены или остатка нужен новый клик.
  • Повтор одного operation_id не добавляет товар второй раз.
  • cart_url — защищённый API-путь, а не checkout-ссылка.
  • Корзина всегда отмечена как демонстрационная.

Структура

.
├── app/                # FastAPI backend
├── tests/              # backend-тесты
├── docs/               # API и техническая документация
├── src/
│   ├── api/            # TypeScript-клиент и runtime-валидация
│   ├── components/     # чат, карточки и диалоги
│   ├── hooks/          # сессия, сообщения, файлы и корзина
│   ├── state/          # Redux state
│   └── styles/         # стили виджета и preview
├── server/             # Node BFF и static server
├── docker-compose.yml
├── Dockerfile
├── package.json
└── .env.example

Локальная дублирующая папка backend/ намеренно исключена из Git.


Команды frontend

КомандаНазначение
npm run devVite development server с BFF
npm testВсе frontend- и BFF-тесты
npm run typecheckTypeScript
npm run lintESLint
npm run format:checkPrettier
npm run buildPreview + widget + Node server
npm startProduction Node BFF/static server

Команды backend

docker compose --profile uploads up --build -d
docker compose logs -f api
docker compose down
pytest
ruff check app tests
mypy app

Production-сборка frontend

npm ci
npm run build
npm start

Сборка создаёт:

  • dist/preview — презентационную страницу;
  • dist/widget — встраиваемый виджет и CSS;
  • dist/server — Node BFF и static server.

За HTTPS reverse proxy направьте /backend/api/* на Node-сервер, разрешите тела до 20 МиБ и установите upstream timeout не меньше 120 секунд.


Встраивание виджета

import AssistantWidget from './dist/widget/ekt-assistant.js';
import './dist/widget/ekt-ai-assistant.css';

export function App() {
  return <AssistantWidget mode="live" apiBaseUrl="/backend/api" initiallyOpen={false} />;
}

Хост-приложение должно предоставить BFF по указанному same-origin пути.


Качество и проверки

Frontend проверяет критические интеграционные сценарии:

  • сессия создаётся один раз;
  • upload и текст выполняются последовательно;
  • успешный upload не повторяется после частичного сбоя;
  • старое предложение нельзя подтвердить после замены;
  • двойной клик не создаёт две операции;
  • потерянное подтверждение повторяется тем же payload;
  • cart: null не стирает корзину;
  • decimal-строки отображаются без потери точности;
  • BFF проверяет ключ, token, origin, маршрут, размер и timeout.

Последняя frontend-проверка:

19 test files passed
147 tests passed
TypeScript passed
ESLint passed
Prettier passed
Production build passed

Полная проверка frontend:

npm run typecheck
npm run lint
npm run format:check
npm test
npm run build

Backend имеет отдельный набор Pytest-тестов с mock внешних интеграций.


Ограничения прототипа

  • Корзина не создаёт заказ и резерв на ekt.kz.
  • Сессия, история и корзина исчезают после TTL или перезапуска Redis.
  • Вложения не сохраняются backend на диск.
  • Для файлов требуется готовый ClamAV.
  • Healthcheck не гарантирует доступность OpenAI, EKT и сканера.
  • Внешние варианты нельзя подтверждать как каталожный товар.

Ограничения явно показаны в интерфейсе. Live-ошибки не маскируются автоматическим переключением на demo.


Статус

Полный сценарий проверен с реальным backend: создание сессии, получение товара, предложение, подтверждение демонстрационной корзины и загрузка файла через ClamAV.

Звёзды
⭐ 0
Форки
0
Язык
TypeScript
Последний push
в прошлом месяце
Коммиты
9
Авторы
2
Файлы
169
Ветки
2

Авторы коммитов

Galm 6aldick 4

История коммитов

  • 2026-09-23 17:58finalaldick
  • 2026-09-23 17:23Add EKT AI assistant frontend and BFFGalm
  • 2026-09-23 17:19Initial commit with all filesGalm
  • 2026-09-23 17:19testGalm
  • 2026-09-23 16:44final version of the backendaldick
  • 2026-09-23 15:20backend technical documentation 2.0 createdaldick
  • 2026-09-23 15:06backend technical documentation createdaldick
  • 2026-09-23 13:03testGalm
Все коммиты (9)
  • 2026-09-23 12:42Initial commit with all filesGalm
Структура репозитория (169 файлов)
.dockerignore
.env.example
.github/workflows/ci.yml
.gitignore
.prettierignore
.prettierrc.json
Dockerfile
README.md
app/__init__.py
app/api/__init__.py
app/api/errors.py
app/api/middleware.py
app/api/routes/__init__.py
app/api/routes/cart.py
app/api/routes/chat.py
app/api/routes/health.py
app/api/routes/products.py
app/api/uploads.py
app/clients/__init__.py
app/clients/catalog.py
app/clients/openai.py
app/core/__init__.py
app/core/config.py
app/core/exceptions.py
app/core/logging.py
app/core/redis.py
app/core/security.py
app/data/category_profiles.json
app/data/purchase_conditions.json
app/main.py
app/models/__init__.py
app/models/chat_session.py
app/models/products.py
app/repositories/__init__.py
app/repositories/chat_sessions.py
app/schemas/__init__.py
app/schemas/chat.py
app/schemas/errors.py
app/services/__init__.py
app/services/analogs.py
app/services/cart.py
app/services/catalog.py
app/services/chat.py
app/services/chat_tools.py
app/services/uploads.py
docker-compose.yml
docs/BACKEND_TECHNICAL_SPECIFICATION.md
docs/CHAT_API.md
docs/FRONTEND_INTEGRATION.md
eslint.config.js
index.html
package-lock.json
package.json
pyproject.toml
requirements-dev.lock
requirements.lock
server/body.ts
server/config.ts
server/errors.ts
server/headers.ts
server/index.ts
server/proxy.test.ts
server/proxy.ts
server/routes.ts
server/security.test.ts
server/security.ts
server/static.ts
server/vitePlugin.ts
src/AssistantWidget.tsx
src/api/catalog.ts
src/api/client.test.ts
src/api/client.ts
src/api/demo.test.ts
src/api/demo.ts
src/api/demoCart.ts
src/api/errors.ts
src/api/fixtures.ts
src/api/http.test.ts
src/api/http.ts
src/api/normalize.test.ts
src/api/normalize.ts
src/api/normalizeCart.ts
src/api/normalizeProducts.ts
src/api/queryKeys.ts
src/api/requests.test.ts
src/api/requests.ts
src/api/types.ts
src/api/validation.ts
src/components/Chat.attachments.test.tsx
src/components/Chat.test.tsx
src/components/Chat.tsx
src/components/ProductArt.tsx
src/components/ProductCard.test.tsx
src/components/ProductCard.tsx
src/components/QuantityControl.tsx
src/components/chat/ChatHeader.tsx
src/components/chat/ChatLauncher.tsx
src/components/chat/Composer.tsx
src/components/chat/MessageList.tsx
src/components/chat/RecognizedProducts.tsx
src/components/chat/SessionNotice.tsx
src/components/chat/StructuredResults.test.tsx
src/components/chat/StructuredResults.tsx
src/components/chat/Welcome.tsx
src/components/dialogs/CartDialog.tsx
src/components/dialogs/CartDialogs.test.tsx
src/components/dialogs/ConfirmDialog.tsx
src/components/dialogs/ProductDialog.test.tsx
src/components/dialogs/ProductDialog.tsx
src/components/dialogs/ResetDialog.tsx
src/hooks/cartActions.test-utils.tsx
src/hooks/useCartActions.races.test.tsx
src/hooks/useCartActions.test.tsx
src/hooks/useCartActions.ts
src/hooks/useChatWindow.ts
src/hooks/useMessaging.test.tsx
src/hooks/useMessaging.ts
src/hooks/useResponses.ts
src/hooks/useSession.ts
src/lib/attachments.test.ts
src/lib/attachments.ts
src/lib/errorMessage.ts
src/lib/format.test.ts
src/lib/format.ts
src/main.tsx
src/preview.css
src/preview/circuit.css
src/preview/page.css
src/state/hooks.ts
src/state/store.test.ts
src/state/store.ts
src/styles.css
src/styles/compact.css
src/styles/composer.css
src/styles/frame.css
src/styles/messages.css
src/styles/overlays.css
src/styles/responsive.css
src/styles/welcome.css
src/test/chatHarness.tsx
src/test/uiFixtures.ts
src/theme.ts
src/widget.tsx
tests/01_specification.xlsx
tests/02_mixed_request.xlsx
tests/03_purchase_request.docx
tests/04_delivery_note.docx
tests/05_specification.pdf
tests/06_scanned_delivery_note.pdf
tests/07_breaker_photo.jpeg
tests/08_relay_photo.jpeg
tests/__init__.py
tests/conftest.py
tests/ekt-test-scenarios.pdf
tests/test_analogs.py
tests/test_api.py
tests/test_cart.py
tests/test_catalog.py
tests/test_chat_api.py
tests/test_chat_upload.py
tests/test_core_redis.py
tests/test_openai_client.py
tests/test_sessions.py
tests/test_uploads.py
tests_results.md
tsconfig.json
tsconfig.server.json
vite.config.ts
vite.widget.config.ts
Ветки (2)

Обсуждение 0 комментариев

Войдите, чтобы комментировать

Пока нет комментариев — будьте первым.