AI-агент — Чат (floating)
Роуты: нет своего — chat-bubble накладывается поверх любой страницы админки.
API: POST /api/v1/admin/agent/chat (SSE), GET /api/v1/admin/agent/conversations, DELETE /api/v1/admin/agent/conversations/{id}
Статус реализации
- Backend: не начат —
erp-openclaw-agentещё не создан как репозиторий (самописerp-llm-gatewayотказан 2026-05-13, агент ходит к LLM-провайдеру напрямую — TBD)- Admin BFF: не начат — нет proxy-роута
/api/v1/admin/agent/*- Admin Web: не начат — нет компонента
AgentChatBubble- Permission
agent.useещё не в каталоге User Service (миграция требуется)
Источники
- Бизнес: AI-агент (бизнес-спека)
- BR: BR 6.3
- API: OpenClaw Agent API
- LLM: ADR-023
Размещение в UI
Floating button в правом нижнем углу страницы (position: fixed; bottom: 24px; right: 24px; z-index: 1000):
- Иконка:
✨(sparkle, как в большинстве AI-инструментов) - Размер: 56×56 px, круглая, красный (
colors.red), shadowshadow.md - Hover: scale 1.05,
colors.redHover - Tooltip: «AI-помощник (бета)»
- Permission-guard: скрыта если у юзера нет
agent.use(через UI-гейтинг)
При клике — открывается чат-окно.
Чат-окно
Положение: anchored к floating button, поднимается вверх. Десктоп: 380×600 px, fixed bottom-right.
Структура:
┌────────────────────────────────────────┐
│ AI-помощник [⋯] [✕] │ ← header
├────────────────────────────────────────┤
│ │
│ Привет! Я могу: │
│ 🔍 искать товары │
│ 📝 создавать заказы │
│ 🚫 управлять стоп-листом │
│ 📊 показывать сводку продаж │
│ │
│ Что нужно сделать? │
│ │
│ ┌─────────────────────┐ │
│ │ Создай заказ на │ │ ← user message
│ │ маргариту и колу │ │
│ │ на вынос для Арбата │ │
│ └─────────────────────┘ │
│ │
│ 🔍 ищу товары "маргарита" │ ← tool_call_started
│ ✓ нашёл: Пицца Маргарита 590 ₽ │ ← tool_call_completed
│ 🔍 ищу товары "кола" │
│ ✓ нашёл: Кока-кола 260 ₽ │
│ 📝 создаю заказ... │
│ ✓ заказ #1234, сумма 850 ₽ │
│ │
│ Заказ #1234 создан: Пицца Маргарита, │ ← assistant final content
│ Кока-кола. Сумма 850 ₽. │
│ │
├────────────────────────────────────────┤
│ ┌──────────────────────────────────┐ │
│ │ Введи команду… [→] │ │ ← input
│ └──────────────────────────────────┘ │
└────────────────────────────────────────┘
Header:
- Заголовок «AI-помощник» (h3)
- Бэйдж «бета» рядом
- Кнопка «⋯» (меню): «История сессий», «Очистить чат», «Начать новый»
- Кнопка «✕» — закрыть окно (chat-bubble остаётся на странице)
Сообщения (history):
- User: справа, белый бэкграунд, border 1px
colors.border, padding 12px, border-radius 12px (с уголком снизу-справа) - Assistant final content: слева, без бордера, основной текст (
type.body) - Tool call started / completed: inline-блоки между assistant content. Иконка слева (🔍 / ✓ / ✗), текст
colors.textMuted, размер 12px (type.small). Анимация спиннера на active
Input:
- Textarea, авторазмер по содержимому (1-4 строки)
- Кнопка отправки справа (стрелка)
- Enter → отправить, Shift+Enter → перенос строки
- Лимит 2000 символов (счётчик появляется при ≥ 1800)
Состояния
| Состояние | Что видит юзер |
|---|---|
| Empty (новая сессия) | Приветствие + 4 примера команд (по одному tool каждой) |
| Sending | Input заблокирован, плейсхолдер «Отправляю…» |
| Streaming | Видны input заблокирован, пузырь ассистента наполняется, tool-блоки появляются по мере выполнения |
| Idle (есть история) | Скролл к низу, новый input готов |
| Conversation limit (20 ходов) | Input блокирован, баннер «В этой сессии 20 ходов — начни новый чат» с кнопкой «Новый чат» |
| Rate limit (429) | Toast «Слишком много запросов, подожди 38 сек» |
| LLM offline (503) | Внутри чата плейсхолдер: «AI временно недоступен. Алексей в курсе.» + кнопка «Повторить» |
No permission (agent.use нет) | Floating button скрыт |
Поток (state machine)
stateDiagram-v2 [*] --> ButtonHidden : нет agent.use [*] --> ButtonClosed : есть agent.use ButtonClosed --> ChatOpen : клик ChatOpen --> ChatOpen_Idle : OK (есть/нет история) ChatOpen --> ChatOpen_Closed : клик ✕ ChatOpen_Closed --> ButtonClosed ChatOpen_Idle --> Sending : submit message Sending --> Streaming : 200 + SSE 'conversation' event Sending --> Error_LLMOff : 503 Sending --> Error_Rate : 429 Sending --> Error_Limit : 409 conversation limit Streaming --> Streaming : SSE chunk (delta / tool_call / etc.) Streaming --> ChatOpen_Idle : SSE 'done' Streaming --> Error_Stream : SSE 'error' event Error_LLMOff --> ChatOpen_Idle : клик "Повторить" Error_Rate --> ChatOpen_Idle : через 38 сек Error_Stream --> ChatOpen_Idle : визит на UI, оставляем чат Error_Limit --> NewConversation : клик "Новый чат" NewConversation --> ChatOpen_Idle
История сессий (опционально)
Через меню «⋯ → История» → выпадающий список последних 10 сессий:
История
─────────────────────
• Создай заказ на… (5 мин)
• Что в стоп-листе? (1 ч)
• Сколько продали… (вчера)
• ...
Клик → загружается история в текущее окно (GET /conversations/{id}).
API: GET /api/v1/admin/agent/conversations?limit=10.
В MVP — можно сделать только «Новый чат» / текущая сессия, без сайдбара истории (упрощение).
Технические детали
Хранение текущей сессии:
conversation_idвlocalStorageключеagent_current_conversation— переживает F5- При закрытии вкладки сессия остаётся
activeв БД — можно вернуться
SSE-клиент:
- Использовать
EventSourceилиfetchс reader (для headers — нуженfetch, неEventSource) - Парсить каждый
event:+data:блок - На
done→ закрыть стрим, разблокировать input - При обрыве сети → toast «Связь прервалась, отправь ещё раз»
BFF-роут:
POST /api/v1/admin/agent/chatв admin-bff проксирует напрямую вerp-openclaw-agentсaccept: text/event-stream- Стрим пробрасывается без преобразований
Permission-guard:
- В
Layout.tsx(Admin Web) рендер<AgentChatBubble />только еслиpermissions.includes('agent.use') - См. UI-гейтинг
Что НЕ делаем в MVP
- ❌ Голосовой ввод
- ❌ Превью результатов tool с картинками / графиками (пока только текст-summary)
- ❌ Возможность отменить активный tool_call (cancel)
- ❌ Markdown-рендеринг в ответах (только текст)
- ❌ Подсказки / autocomplete команд
- ❌ Drag-resize окна чата