diff --git a/CLAUDE.md b/CLAUDE.md index 3ddc8e9..5d19708 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -37,6 +37,16 @@ Default login: admin / admin. Guest demo: demo / demo (read-only). **Deploy gotcha**: Vite deletes and recreates `admin/dist/` (new inode), breaking Docker bind mounts. Always `docker compose restart` after `npm run build`. +### Mobile App + +```bash +cd mobile && npm install # First-time setup +cd mobile && npm run build # Production build (vue-tsc type-check + vite build) +cd mobile && npm run dev # Dev server +cd mobile && npx cap sync android # Sync web assets to Android project +cd mobile && npx cap open android # Open in Android Studio → Build APK +``` + ### User Management ```bash @@ -115,7 +125,7 @@ Always run lint locally before pushing. Protected branches require PR workflow ``` ┌──────────────────────────────────────────────────────────────┐ │ Orchestrator (port 8002) │ -│ orchestrator.py + app/routers/ (21 routers, ~371 endpoints) │ +│ orchestrator.py + modules/*/router*.py (~28 routers) │ │ ┌────────────────────────────────────────────────────────┐ │ │ │ Vue 3 Admin Panel (24 views, PWA) │ │ │ │ admin/dist/ │ │ @@ -270,6 +280,8 @@ New routers import domain services directly (`from modules.monitoring.service im **Build**: `cd mobile && npm run build && npx cap sync android`. APK via Android Studio: `npx cap open android` → Build → Build APK. +**No lint/format/test** — mobile app has only `dev`, `build`, `preview` scripts. Type checking happens during `npm run build` via `vue-tsc -b`. + ## Code Patterns **Adding a new API endpoint:** diff --git a/mobile/src/components/MessageBubble.vue b/mobile/src/components/MessageBubble.vue index 8f9685d..0ee581f 100644 --- a/mobile/src/components/MessageBubble.vue +++ b/mobile/src/components/MessageBubble.vue @@ -90,11 +90,11 @@ function cancelEdit() { Cancel + >Отмена Save + >Сохранить @@ -125,7 +125,7 @@ function cancelEdit() { @@ -142,7 +142,7 @@ function cancelEdit() { @@ -157,7 +157,7 @@ function cancelEdit() { @@ -168,7 +168,7 @@ function cancelEdit() { @@ -179,7 +179,7 @@ function cancelEdit() { @@ -190,7 +190,7 @@ function cancelEdit() { @@ -207,7 +207,7 @@ function cancelEdit() { @@ -222,7 +222,7 @@ function cancelEdit() { @@ -237,7 +237,7 @@ function cancelEdit() { @@ -248,7 +248,7 @@ function cancelEdit() { @@ -259,7 +259,7 @@ function cancelEdit() { @@ -270,7 +270,7 @@ function cancelEdit() { diff --git a/mobile/src/views/ChatListView.vue b/mobile/src/views/ChatListView.vue index 1a70cf9..509cd70 100644 --- a/mobile/src/views/ChatListView.vue +++ b/mobile/src/views/ChatListView.vue @@ -35,7 +35,7 @@ async function loadSessions() { await autoOpenChat(); } } catch (e) { - error.value = e instanceof Error ? e.message : "Failed to load"; + error.value = e instanceof Error ? e.message : "Не удалось загрузить"; } finally { isLoading.value = false; } @@ -77,7 +77,7 @@ async function createNewChat() { const data = await chatApi.createSession(); router.push(`/chat/${data.session.id}`); } catch (e) { - error.value = e instanceof Error ? e.message : "Failed to create"; + error.value = e instanceof Error ? e.message : "Не удалось создать"; } finally { isCreating.value = false; } @@ -85,12 +85,12 @@ async function createNewChat() { async function deleteSession(id: string, event: Event) { event.stopPropagation(); - if (!confirm("Delete this chat?")) return; + if (!confirm("Удалить этот чат?")) return; try { await chatApi.deleteSession(id); sessions.value = sessions.value.filter((s) => s.id !== id); } catch (e) { - error.value = e instanceof Error ? e.message : "Failed to delete"; + error.value = e instanceof Error ? e.message : "Не удалось удалить"; } } @@ -135,7 +135,7 @@ async function sendFromWelcome() { router.push(`/chat/${data.session.id}?msg=${encodeURIComponent(text)}`); } } catch (e) { - error.value = e instanceof Error ? e.message : "Failed to start chat"; + error.value = e instanceof Error ? e.message : "Не удалось начать чат"; } finally { isSending.value = false; } @@ -173,7 +173,7 @@ onMounted(loadSessions); {{ error }} - Retry + Повторить @@ -195,11 +195,11 @@ onMounted(loadSessions); {{ formatDate(session.updated) }} {{ truncate(session.last_message || "", 80) }} - {{ session.message_count }} messages + {{ session.message_count }} сообщ. @@ -262,7 +262,7 @@ onMounted(loadSessions); {{ error }} - Retry + Повторить diff --git a/mobile/src/views/ChatView.vue b/mobile/src/views/ChatView.vue index a0206dd..3a5cb05 100644 --- a/mobile/src/views/ChatView.vue +++ b/mobile/src/views/ChatView.vue @@ -143,7 +143,7 @@ async function loadSession() { customPrompt.value = data.session.system_prompt || ""; await scrollToBottom(); } catch (e) { - error.value = e instanceof Error ? e.message : "Failed to load"; + error.value = e instanceof Error ? e.message : "Не удалось загрузить"; } finally { isLoading.value = false; } @@ -200,7 +200,7 @@ async function sendMessage(content: string) { } break; case "tool_start": - streamingContent.value += "\n_Searching..._\n"; + streamingContent.value += "\n_Поиск..._\n"; break; case "done": isStreaming.value = false; @@ -283,7 +283,7 @@ async function switchBranch(messageId: string) { await loadBranches(); await scrollToBottom(); } catch (e) { - error.value = e instanceof Error ? e.message : "Failed to switch"; + error.value = e instanceof Error ? e.message : "Не удалось переключить"; } } @@ -298,17 +298,17 @@ async function createNewBranch() { if (showBranches.value) await loadBranches(); await scrollToBottom(); } catch (e) { - error.value = e instanceof Error ? e.message : "Failed to create branch"; + error.value = e instanceof Error ? e.message : "Не удалось создать ветку"; } } async function deleteBranch() { const activeMessages = messages.value; if (!activeMessages.length) { - error.value = "No messages to delete"; + error.value = "Нет сообщений для удаления"; return; } - if (!confirm("Delete current branch? All messages will be removed from the server.")) return; + if (!confirm("Удалить текущую ветку? Все сообщения будут удалены с сервера.")) return; try { for (let i = activeMessages.length - 1; i >= 0; i--) { const m = activeMessages[i]!; @@ -319,7 +319,7 @@ async function deleteBranch() { messages.value = []; if (showBranches.value) await loadBranches(); } catch (e) { - error.value = e instanceof Error ? e.message : "Failed to delete"; + error.value = e instanceof Error ? e.message : "Не удалось удалить"; await loadSession(); } } @@ -375,7 +375,7 @@ async function saveContextFiles() { context_files: contextFiles.value, }); } catch (e) { - error.value = e instanceof Error ? e.message : "Failed to save files"; + error.value = e instanceof Error ? e.message : "Не удалось сохранить файлы"; } } @@ -393,7 +393,7 @@ async function saveSystemPrompt() { system_prompt: customPrompt.value, }); } catch (e) { - error.value = e instanceof Error ? e.message : "Failed to save prompt"; + error.value = e instanceof Error ? e.message : "Не удалось сохранить промпт"; } } @@ -462,7 +462,7 @@ async function handleEditMessage(messageId: string, content: string) { await chatApi.editMessage(sessionId.value, messageId, content); await loadSession(); } catch (e) { - error.value = e instanceof Error ? e.message : "Failed to edit"; + error.value = e instanceof Error ? e.message : "Не удалось отредактировать"; } } @@ -485,7 +485,7 @@ async function handleSummarizeBranch(messageId: string) { showBranches.value = false; showSettings.value = false; } catch (e) { - error.value = e instanceof Error ? e.message : "Failed to summarize"; + error.value = e instanceof Error ? e.message : "Не удалось суммаризировать"; } } @@ -494,12 +494,12 @@ async function handleRegenerateResponse(messageId: string) { await chatApi.regenerateResponse(sessionId.value, messageId); await loadSession(); } catch (e) { - error.value = e instanceof Error ? e.message : "Failed to regenerate"; + error.value = e instanceof Error ? e.message : "Не удалось перегенерировать"; } } async function handleDeleteFromMessage(messageId: string) { - if (!confirm("Delete this message and everything after it?")) return; + if (!confirm("Удалить это сообщение и все последующие?")) return; const idx = messages.value.findIndex((m) => m.id === messageId); if (idx < 0) return; try { @@ -512,16 +512,16 @@ async function handleDeleteFromMessage(messageId: string) { messages.value = messages.value.slice(0, idx); if (showBranches.value) await loadBranches(); } catch (e) { - error.value = e instanceof Error ? e.message : "Failed to delete"; + error.value = e instanceof Error ? e.message : "Не удалось удалить"; await loadSession(); } } const anyPanelOpen = computed(() => showBranches.value || showContextFiles.value || showSettings.value); const activePanelName = computed(() => { - if (showBranches.value) return "Branch Tree"; - if (showContextFiles.value) return "Context Files"; - if (showSettings.value) return "Settings"; + if (showBranches.value) return "Дерево веток"; + if (showContextFiles.value) return "Файлы контекста"; + if (showSettings.value) return "Настройки"; return ""; }); @@ -636,7 +636,7 @@ onUnmounted(() => { @@ -671,7 +671,7 @@ onUnmounted(() => { @@ -684,7 +684,7 @@ onUnmounted(() => { @@ -697,19 +697,30 @@ onUnmounted(() => { class="absolute right-0 top-full mt-1 bg-stone-900 border border-stone-700 rounded-xl shadow-xl z-50 min-w-[160px] py-1" > - Copy to clipboard + Копировать в буфер - Export Markdown + Экспорт Markdown - Export JSON + Экспорт JSON + + + + + + + { @@ -736,20 +747,9 @@ onUnmounted(() => { - - - - - - - @@ -780,13 +780,13 @@ onUnmounted(() => { - Branch Tree + Дерево веток - No branches yet + Пока нет истории диалогов @@ -807,7 +807,7 @@ onUnmounted(() => { {{ node.content_preview || '...' }} - active + текущая @@ -817,14 +817,14 @@ onUnmounted(() => { - Context Files ({{ contextFiles.length }}) + Файлы контекста ({{ contextFiles.length }}) - + Add file + + Добавить - No files attached + Нет прикреплённых файлов @@ -931,7 +931,7 @@ onUnmounted(() => { {{ error }} - dismiss + закрыть @@ -1033,7 +1033,7 @@ onUnmounted(() => { - No branches yet + Пока нет истории диалогов { {{ node.content_preview || '...' }} - active + текущая @@ -1063,11 +1063,11 @@ onUnmounted(() => { - + Add file + + Добавить - No files attached + Нет прикреплённых файлов diff --git a/wiki-pages/Home.md b/wiki-pages/Home.md index be86fd7..6605982 100644 --- a/wiki-pages/Home.md +++ b/wiki-pages/Home.md @@ -19,7 +19,9 @@ - **Telegram боты** — мультиинстанс с воронкой продаж и платежами - **WhatsApp боты** — мультиинстанс через WhatsApp Cloud API с воронкой продаж - **Веб-виджеты** — чат для сайтов (мультиинстанс) +- **Мобильное приложение** — нативный Android-клиент (Capacitor), ролевой чат с ИИ - **amoCRM** — OAuth2 интеграция, контакты, сделки, воронки +- **WooCommerce** — синхронизация товаров и заказов в базу знаний для RAG - **GSM телефония** — SIM7600E-H, голосовые звонки, SMS - **Fine-tuning** — LoRA для LLM + TTS fine-tuning (Qwen3-TTS) - **Wiki RAG** — база знаний с BM25 поиском и стеммингом для контекста LLM @@ -49,13 +51,22 @@ | [[Widget]] | Настройка веб-виджетов (мультиинстанс) | | [[Telegram]] | Управление Telegram ботами (мультиинстанс) | | [[WhatsApp]] | Управление WhatsApp ботами (Cloud API, мультиинстанс) | +| [[Mobile-App]] | Мобильное Android-приложение (чат с ИИ, ролевой UX) | | [[GSM]] | GSM телефония (звонки, SMS) | | [[Kanban]] | Канбан-доска задач с Gantt-диаграммой и чеклистами | | [[Sales]] | Воронка продаж Telegram ботов (квиз, сегменты, follow-up) | | [[CRM]] | Интеграция с amoCRM (контакты, сделки, воронки) | +| [[WooCommerce]] | Интеграция с WooCommerce (товары, заказы, RAG-синхронизация) | | [[Usage]] | Статистика использования, лимиты, аналитика | | [[Audit]] | Аудит действий пользователей | | [[Settings]] | Профиль, настройки, бэкапы, язык, тема | +| [[Users]] | Управление пользователями, ролями, приглашениями | + +### Архитектура и справка + +| Страница | Описание | +|----------|----------| +| [[Architecture]] | Архитектура системы, модули, схемы | ### Интеграции и справка @@ -107,5 +118,5 @@ docker compose -f docker-compose.yml -f docker-compose.cpu.yml up -d --- -**Версия документации:** 2.0 -**Последнее обновление:** Февраль 2026 +**Версия документации:** 2.1 +**Последнее обновление:** Март 2026 diff --git a/wiki-pages/Mobile-App.md b/wiki-pages/Mobile-App.md new file mode 100644 index 0000000..73ada92 --- /dev/null +++ b/wiki-pages/Mobile-App.md @@ -0,0 +1,245 @@ +# Mobile App (Мобильное приложение) + +Нативное Android-приложение для чата с ИИ-ассистентом. Подключается к серверу `ai-sekretar24.ru`, предоставляет ролевой UX: админы получают полный контроль, обычные пользователи — предварительно настроенный опыт. + +## Скриншот + + + + +## Концепция + +Мобильное приложение — это **отдельный Android-клиент** (Vue 3 + Capacitor), не PWA-обёртка админ-панели: + +- **Hardcoded сервер** — `https://ai-sekretar24.ru`, без настройки URL +- **JWT-аутентификация** — токен хранится в Capacitor Preferences (не localStorage) +- **Ролевой интерфейс** — админ видит все чаты и контролы, обычный пользователь — только расшаренные чаты +- **Mobile Instances** — админ создаёт предварительно настроенные экземпляры (LLM, голос, промпт, RAG), назначает пользователям через ResourceShare +- **Стриминг** — SSE-потоковая генерация ответов +- **TTS** — озвучка сообщений для всех ролей +- **Тема** — Night-eyes (тёплый коричневый/янтарный/золотой), без переключения + +## Экраны приложения + +### Login (Вход) + +- Поля: имя пользователя + пароль +- Нет настройки URL сервера +- При входе автоматически загружается конфиг назначенного Mobile Instance (`GET /admin/mobile/my-config`) +- При успехе — редирект на `/chats` + +### Chat List (Список чатов) + +**Для админа:** + +| Элемент | Описание | +|---------|----------| +| **Список сессий** | Карточки: название, последнее сообщение, количество, дата | +| **FAB (Floating Action Button)** | Создание нового чата | +| **Удаление** | Кнопка удаления на каждой карточке (с подтверждением) | +| **Настройки** | Кнопка перехода в Settings | + +**Для обычного пользователя:** + +| Элемент | Описание | +|---------|----------| +| **Приветствие** | Центрированное «Привет, {username}» в стиле Claude | +| **Поле ввода** | Отправка первого сообщения сразу с экрана | +| **Расшаренные чаты** | Карточки только расшаренных сессий (`is_shared_with_me`) | +| **Авто-маршрутизация** | 1) Дефолтная мобильная сессия → 2) Первый расшаренный чат → 3) Создание нового | + +### Chat (Чат) + +Основной экран стриминговой беседы с ИИ-ассистентом. + +#### Тулбар + +| Кнопка | Доступ | Описание | +|--------|--------|----------| +| **Назад** | Админ | Возврат к списку чатов | +| **LLM-провайдер** | Админ | Выпадающий список: Default / vLLM (Local) / ☁️ Cloud providers | +| **RAG-коллекции** | Админ | Мультиселект коллекций базы знаний | +| **Экспорт** | Админ | Копировать / Скачать .md / Скачать .json | +| **Новая ветка** | Все | Создание новой ветки разговора | +| **Настройки чата** | Все | Панель с контекстными файлами и системным промптом | +| **Ветки** | Все | Дерево веток разговора | + +#### Действия с сообщениями + +| Действие | Админ | Обычный | +|----------|-------|---------| +| **TTS** (озвучка) | ✓ | ✓ | +| **Копировать** | ✓ | ✓ | +| **Редактировать** | ✓ | — | +| **Регенерировать** (user msg) | ✓ | — | +| **Сохранить в контекст** (assistant msg) | ✓ | — | +| **Суммаризация ветки** | ✓ | — | +| **Удалить от этого сообщения** | ✓ | — | + +#### Панели (боковые/нижние) + +| Панель | Описание | +|--------|----------| +| **Ветки** | Дерево сообщений с навигацией, переключение между ветками | +| **Контекстные файлы** | Загрузка файлов (.txt, .md, .json, .csv, .py, .js, .ts и др.), прикрепление к контексту | +| **Промпт** | Текстовое поле для кастомного системного промпта | + +- В портретном режиме панели появляются внизу (горизонтальный ресайз) +- В ландшафтном — сбоку (вертикальный ресайз) + +#### Ввод сообщений + +- Автоматически растягивающийся textarea (макс. 120px) +- Enter = отправить, Shift+Enter = перенос строки +- Вставка кода — определяется автоматически, показывается как chip с языком и количеством строк +- Кнопка Stop при стриминге + +### Settings (Настройки) + +- Имя пользователя и роль (read-only) +- Кнопка выхода +- Информация о версии (v1.0.0) + +## Mobile Instances (Управление в админ-панели) + +Администратор создаёт и настраивает экземпляры мобильного приложения через страницу **Mobile App** в админ-панели (`MobileAppView`). + +### Список инстансов + +| Элемент | Описание | +|---------|----------| +| **Индикатор** | Зелёный = включён, серый = отключён | +| **Название** | Имя инстанса | +| **Описание** | Краткое описание | +| **Шаринг** | Иконка Users + количество назначенных пользователей | + +### Создание / Редактирование инстанса + +| Параметр | Описание | По умолчанию | +|----------|----------|--------------| +| **Название*** | Имя инстанса | — | +| **Описание** | Описание | — | +| **LLM Backend** | Выпадающий список: Local vLLM / ☁️ Cloud providers | `vllm` | +| **Персона** | Имя персоны (anna, marina и др.) | `anna` | +| **Системный промпт** | Кастомный промпт для ассистента | — | +| **TTS Engine** | Движок синтеза речи (xtts, piper, openvoice) | `xtts` | +| **TTS Voice** | Голос для озвучки | `anna` | +| **RAG Mode** | Режим RAG: all / selected / off | `all` | +| **Коллекции знаний** | Мультиселект коллекций (при mode=selected) | — | +| **Rate Limit** | Ограничение запросов (кол-во / часов) | Без ограничений | + +### Вкладки деталей + +| Вкладка | Содержимое | +|---------|-----------| +| **Settings** | ID, название, описание, TTS, rate limit | +| **AI** | LLM backend, персона, системный промпт | +| **RAG** | Режим RAG, список коллекций (бейджи) | + +### Назначение пользователей (ResourceShare) + +Кнопка **"Поделиться"** открывает диалог ResourceShareDialog: +- Выбор пользователя из списка +- Уровень доступа: `view` (только чтение) или `edit` (редактирование) +- Назначенный пользователь видит инстанс через `GET /admin/mobile/my-config` +- Один пользователь может быть назначен только одному инстансу + +## Потоки данных + +### Авторизация + +``` +LoginView → POST /admin/auth/login → JWT + → Capacitor Preferences.set("token", jwt) + → Decode JWT → {user_id, role, workspace_id} + → GET /admin/mobile/my-config → MobileInstanceConfig + → router.push("/chats") +``` + +### Отправка сообщения (админ с LLM-оверрайдом) + +``` +ChatInput → sendMessage(content) + → overrides: {llm_backend: "cloud:123", rag_mode: "selected", knowledge_collection_ids: [1,2]} + → POST /admin/chat/sessions/{id}/stream (SSE) + body: {content, mobile_instance_id, ...overrides} + → SSE chunks: "chunk" → streamingContent accumulates + → SSE "assistant_message" → commit to messages array +``` + +### Назначение пользователю + +``` +Admin: POST /admin/mobile/instances → создание инстанса +Admin: POST /admin/mobile/instances/{id}/shares → {user_id, permission: "edit"} +User: GET /admin/mobile/my-config → {id, name, llm_backend, system_prompt, ...} +User: Defaults applied to all new chat sessions (source="mobile", source_id=instanceId) +``` + +## API (Mobile Module) + +### Инстансы + +| Метод | Путь | Описание | Доступ | +|-------|------|----------|--------| +| `GET` | `/admin/mobile/instances` | Список инстансов | channels:view | +| `POST` | `/admin/mobile/instances` | Создание инстанса | channels:edit | +| `GET` | `/admin/mobile/instances/{id}` | Получить инстанс | channels:view | +| `PUT` | `/admin/mobile/instances/{id}` | Обновить инстанс | channels:edit | +| `DELETE` | `/admin/mobile/instances/{id}` | Удалить инстанс (каскадно удаляет shares) | channels:edit | + +### Конфигурация пользователя + +| Метод | Путь | Описание | Доступ | +|-------|------|----------|--------| +| `GET` | `/admin/mobile/my-config` | Конфиг назначенного инстанса для текущего юзера | chat:view | + +### Шаринг (ResourceShare) + +| Метод | Путь | Описание | Доступ | +|-------|------|----------|--------| +| `GET` | `/admin/mobile/instances/{id}/shares` | Список назначений | channels:view | +| `POST` | `/admin/mobile/instances/{id}/shares` | Назначить пользователя | channels:edit | +| `PUT` | `/admin/mobile/instances/{id}/shares/{user_id}` | Изменить уровень доступа | channels:edit | +| `DELETE` | `/admin/mobile/instances/{id}/shares/{user_id}` | Снять назначение | channels:edit | + +## Стек технологий + +| Компонент | Технология | +|-----------|-----------| +| **Framework** | Vue 3 + Composition API + TypeScript | +| **Build** | Vite 7 | +| **Native** | Capacitor 8 (Android) | +| **CSS** | TailwindCSS 4 | +| **State** | Pinia | +| **Router** | Vue Router (hash history) | +| **Markdown** | marked + DOMPurify | + +## Сборка и деплой + +```bash +cd mobile && npm install # Установка зависимостей +cd mobile && npm run build # Сборка (vue-tsc + vite) +cd mobile && npx cap sync android # Синхронизация с Android-проектом +cd mobile && npx cap open android # Открыть в Android Studio → Build APK +``` + +**App ID**: `com.shaerware.aisecretary` +**Размер**: ~77KB gzipped (vs ~2MB админ-панель) + +## Отличия от админ-панели + +| Характеристика | Админ-панель | Мобильное приложение | +|----------------|-------------|---------------------| +| **Платформа** | PWA (браузер) | Нативный Android (Capacitor) | +| **URL сервера** | Настраивается | Hardcoded (`ai-sekretar24.ru`) | +| **Хранение JWT** | localStorage | Capacitor Preferences | +| **Функции** | Полное управление системой | Только чат | +| **Демо-режим** | Есть (VITE_DEMO_MODE) | Нет | +| **Темы** | Светлая/тёмная | Только Night-eyes | +| **i18n** | ru/en/kk | Только ru | +| **Размер** | ~2MB gzipped | ~77KB gzipped | + +--- + +**См. также:** [[Chat]], [[Chat-Sharing]], [[RBAC]], [[Cloud-LLM-Providers]], [[Wiki-RAG]] diff --git a/wiki-pages/Users.md b/wiki-pages/Users.md new file mode 100644 index 0000000..77ba40f --- /dev/null +++ b/wiki-pages/Users.md @@ -0,0 +1,220 @@ +# Users (Управление пользователями) + +Управление пользователями, ролями, приглашениями и сессиями через страницу Users в админ-панели. + +## Скриншот + + + + +## Концепция + +Страница Users (`UsersView.vue`) — единый центр управления командой: + +- **Участники воркспейса** — таблица с ролями, статусами, датами +- **Смена ролей** — прямо из таблицы через dropdown +- **Система приглашений** — создание invite-ссылок с настройками +- **Защита** — нельзя изменить собственную роль, удалить себя или владельца воркспейса + +## Страница Users + +### Информация о воркспейсе + +Карточка вверху страницы с 3 метриками: + +| Метрика | Описание | +|---------|----------| +| **Название** | Имя воркспейса | +| **Участников** | Количество членов | +| **Создан** | Дата создания | + +### Таблица участников + +| Колонка | Описание | +|---------|----------| +| **Имя** | Display name (иконка 👑 у владельца воркспейса) | +| **Username** | Логин пользователя | +| **Роль** | Dropdown-селектор (интерактивный для manage-уровня) | +| **Присоединился** | Дата добавления | +| **Последний вход** | Дата и время последнего логина | +| **Статус** | Active (зелёный) / Inactive (серый) | +| **Действия** | Кнопка удаления (только для manage-уровня) | + +**Цвета бейджей ролей:** + +| Роль | Цвет | +|------|------| +| owner | Янтарный | +| admin | Красный | +| operator | Синий | +| viewer | Серый | + +**Защита от ошибок:** +- Нельзя изменить свою роль +- Нельзя изменить роль владельца воркспейса +- Нельзя удалить себя +- Нельзя удалить владельца + +### Приглашения (Invites) + +> Секция видна только пользователям с уровнем `manage` для модуля `users`. + +#### Таблица приглашений + +| Колонка | Описание | +|---------|----------| +| **Роль** | Роль для нового пользователя (бейдж) | +| **Email** | Email получателя (опционально) | +| **Использование** | Счётчик: текущее / максимум | +| **Истекает** | Дата истечения (или «never») | +| **Статус** | Active (зелёный) / Expired (серый) | +| **Действия** | Копировать ссылку / Удалить | + +#### Создание приглашения (InviteDialog) + +| Параметр | Описание | По умолчанию | +|----------|----------|--------------| +| **Роль** | Выпадающий список (кроме owner) | — | +| **Email** | Email получателя (опционально) | — | +| **Макс. использований** | Лимит регистраций по ссылке | Без ограничений | +| **Срок действия** | 24ч / 3 дня / 7 дней / 30 дней / Бессрочно | — | + +Сгенерированная ссылка: `{origin}#/invite/{invite_code}` + +#### Регистрация по приглашению (InviteView) + +Получатель открывает ссылку и видит форму: + +1. Информация о приглашении (воркспейс, роль, срок действия) +2. Display Name (опционально) +3. Username (обязательно, допустимые символы: a-z, 0-9, `-._`) +4. Пароль (мин. 6 символов) +5. Подтверждение пароля + +При регистрации — автоматический вход и редирект в `/chat`. + +## Роли и разрешения + +### Системные роли + +| Роль | Описание | Удаление | +|------|----------|----------| +| **admin** | Полный доступ ко всем модулям | Нельзя | +| **operator** | Чтение + запись ресурсов | Нельзя | +| **viewer** | Только чтение | Нельзя | + +Кастомные роли можно создавать с произвольным набором разрешений через API. + +### Структура разрешений + +Разрешения — это пары `{модуль: уровень}`: + +**Уровни** (иерархия): +- `view` (1) — только чтение +- `edit` (2) — чтение + запись +- `manage` (3) — полный контроль (включает view + edit) + +**Модули:** +`chat`, `channels`, `knowledge`, `llm`, `tts`, `sales`, `system`, `users`, `speech`, `gsm` и др. + +> В cloud-режиме (`DEPLOYMENT_MODE=cloud`) модули `speech` и `gsm` скрываются. + +### Связь с legacy-ролями + +CLI-инструмент `manage_users.py` использует legacy-роли. Маппинг: + +| Legacy (users.role) | Workspace (roles.name) | +|---------------------|----------------------| +| admin | admin | +| user | operator | +| web | operator | +| guest | viewer | + +## Сессии и аутентификация + +### JWT-токены + +| Параметр | Значение | +|----------|----------| +| **Алгоритм** | HS256 | +| **Время жизни** | 24 часа (по умолчанию) | +| **Содержимое** | username, user_id, role, workspace_id, exp, iat, jti | +| **Хранение** | `localStorage['admin_token']` (админ-панель), Capacitor Preferences (мобильное) | + +### Управление сессиями + +Каждый вход создаёт запись в таблице `user_sessions` с JTI (JWT ID). Это позволяет: + +- **Просмотр активных сессий** — `GET /admin/auth/sessions` +- **Отзыв конкретной сессии** — `DELETE /admin/auth/sessions/{jti}` +- **Массовый отзыв** — при смене пароля или деактивации все сессии аннулируются +- **Автоочистка** — фоновая задача `session-cleanup` удаляет истёкшие сессии старше 7 дней + +### Кеширование + +Три in-memory кеша для производительности: +- **SessionCache** — JTI → user_id (быстрая проверка отзыва) +- **PermissionsCache** — role_name → {module: level} +- **MemberRoleCache** — (user_id, workspace_id) → role_name + +## CLI-инструмент + +```bash +python scripts/manage_users.py list # Список пользователей +python scripts/manage_users.py create alice pass123 --role admin # Создать пользователя +python scripts/manage_users.py set-password alice newpass # Сменить пароль +python scripts/manage_users.py set-role alice operator # Сменить роль +python scripts/manage_users.py disable alice # Деактивировать +python scripts/manage_users.py enable alice # Активировать +python scripts/manage_users.py delete alice # Удалить +``` + +Вывод `list`: ID, Username, Role, WS Role, Active, Display Name, Last Login. + +## API + +### Аутентификация + +| Метод | Путь | Описание | Доступ | +|-------|------|----------|--------| +| `POST` | `/admin/auth/login` | Вход (username + password) | Rate-limited | +| `GET` | `/admin/auth/me` | Текущий пользователь | Авторизация | +| `GET` | `/admin/auth/profile` | Полный профиль | Авторизация | +| `PUT` | `/admin/auth/profile` | Обновить display name | settings:edit | +| `POST` | `/admin/auth/change-password` | Сменить пароль (отзывает все сессии) | settings:edit | +| `GET` | `/admin/auth/sessions` | Активные сессии | Авторизация | +| `DELETE` | `/admin/auth/sessions/{jti}` | Отозвать сессию | Авторизация | +| `GET` | `/admin/auth/permissions` | Разрешения текущего пользователя | Авторизация | + +### Воркспейс и участники + +| Метод | Путь | Описание | Доступ | +|-------|------|----------|--------| +| `GET` | `/admin/workspace` | Информация о воркспейсе | users:view | +| `GET` | `/admin/workspace/members` | Список участников | users:view | +| `PUT` | `/admin/workspace/members/{user_id}/role` | Сменить роль участника | users:manage | +| `DELETE` | `/admin/workspace/members/{user_id}` | Удалить участника | users:manage | + +### Приглашения + +| Метод | Путь | Описание | Доступ | +|-------|------|----------|--------| +| `POST` | `/admin/workspace/invites` | Создать приглашение | users:manage | +| `GET` | `/admin/workspace/invites` | Список приглашений | users:manage | +| `DELETE` | `/admin/workspace/invites/{id}` | Удалить приглашение | users:manage | +| `GET` | `/admin/workspace/invites/{code}/info` | Информация о приглашении | Публичный | +| `POST` | `/admin/workspace/invites/accept` | Регистрация по приглашению | Rate-limited | + +### Роли + +| Метод | Путь | Описание | Доступ | +|-------|------|----------|--------| +| `GET` | `/admin/roles` | Список ролей с разрешениями | users:manage | +| `POST` | `/admin/roles` | Создать кастомную роль | users:manage | +| `GET` | `/admin/roles/{id}` | Получить роль | users:manage | +| `PUT` | `/admin/roles/{id}` | Обновить роль | users:manage | +| `DELETE` | `/admin/roles/{id}` | Удалить кастомную роль | users:manage | + +--- + +**См. также:** [[RBAC]], [[Settings]], [[Data-Privacy]] diff --git a/wiki-pages/WooCommerce.md b/wiki-pages/WooCommerce.md new file mode 100644 index 0000000..35c7066 --- /dev/null +++ b/wiki-pages/WooCommerce.md @@ -0,0 +1,195 @@ +# WooCommerce (Интеграция) + +Интеграция с WooCommerce REST API v3: синхронизация товаров, категорий и заказов в базу знаний для RAG-поиска. + +## Скриншот + + + + +## Концепция + +Интеграция с **WooCommerce REST API v3** для автоматического импорта каталога интернет-магазина в систему знаний: + +- **Синхронизация товаров** — названия, цены, описания, атрибуты, категории, наличие +- **Синхронизация заказов** — статистика, суммы, статусы, последние заказы +- **Markdown-документы** — данные конвертируются в markdown для индексации RAG +- **Автоматический ежедневный sync** — фоновая задача в 23:00 UTC (02:00 МСК) +- **Знание для ассистента** — ИИ использует каталог для ответов на вопросы о товарах и ценах + +## Страница WooCommerce + +Управление интеграцией через `WooCommerceView.vue`. Две секции: **подключение** и **синхронизация датасета**. + +--- + +### Секция: Подключение + +| Параметр | Описание | +|----------|----------| +| **Store URL** | Адрес магазина (например, `https://shop.example.com`) | +| **Consumer Key** | API-ключ WooCommerce (маскируется: `***1234`) | +| **Consumer Secret** | API-секрет WooCommerce (маскируется: `***5678`) | + +**Кнопки:** + +| Кнопка | Описание | +|--------|----------| +| **Сохранить** | Сохраняет URL и ключи. Ключи очищаются из формы после сохранения (безопасность) | +| **Проверить подключение** | Тестирует соединение, показывает название магазина и версию WC | +| **Отключить** | Очищает ключи, сбрасывает статус подключения | + +При успешной проверке отображается зелёный индикатор «Подключено» и информация о магазине: +- Название магазина +- Версия WooCommerce +- URL + +--- + +### Секция: Синхронизация датасета + +> Появляется только после успешного подключения (`is_connected=true`). + +#### Статистика (сетка 2×3) + +| Метрика | Описание | +|---------|----------| +| **Товаров** | Количество синхронизированных товаров | +| **Категорий** | Количество категорий | +| **Заказов** | Количество заказов | +| **Документов** | Количество markdown-документов в базе знаний | +| **Секций** | Суммарное количество секций (для RAG-индексации) | +| **Последняя синхронизация** | Дата и время последнего sync | + +#### Кнопки + +| Кнопка | Описание | Доступ | +|--------|----------|--------| +| **Синхронизировать** | Запускает полную синхронизацию (товары + категории + заказы → markdown → RAG) | sales:edit | +| **Очистить** | Удаляет все markdown-документы и очищает RAG-индексы | sales:manage | + +## Процесс синхронизации + +При нажатии «Синхронизировать» или по расписанию: + +1. **Загрузка данных** из WooCommerce: + - `GET /wp-json/wc/v3/products` — все товары (автопагинация по 100) + - `GET /wp-json/wc/v3/products/categories` — все категории + - `GET /wp-json/wc/v3/orders` — все заказы (опционально, ошибка не фатальна) + +2. **Группировка товаров** по категориям + +3. **Генерация markdown-документов** в `data/woocommerce-dataset/`: + + | Файл | Содержимое | + |------|-----------| + | `wc-category-{id}.md` | Товары категории: название, SKU, цена (₸), наличие, описание, атрибуты | + | `wc-uncategorized.md` | Товары без категории | + | `wc-orders.md` | Статистика заказов: общее количество, выручка, статусы, последние 50 заказов | + | `wc-summary.md` | Общая статистика: количество товаров, диапазон цен, категории | + +4. **Индексация в RAG**: + - Создаётся/обновляется коллекция знаний с slug `"woocommerce"` + - Удаляются старые документы коллекции + - Загружаются новые документы с подсчётом секций + - Перезагружаются RAG-индексы (BM25 + эмбеддинги) + +5. **Обновление статистики** в конфиге (счётчики + timestamp) + +## Фоновая задача + +| Параметр | Значение | +|----------|----------| +| **Имя** | `woocommerce-sync` | +| **Расписание** | Ежедневно в 23:00 UTC (02:00 МСК) | +| **Задержка запуска** | 120 секунд (прогрев) | +| **Условие** | Выполняется только если `sync_enabled=true` | +| **При ошибке** | Повтор через 1 час | + +Задача регистрируется в `TaskRegistry` при старте системы. + +## API + +| Метод | Путь | Описание | Доступ | +|-------|------|----------|--------| +| `GET` | `/admin/woocommerce/config` | Получить конфиг (ключи замаскированы) | sales:view | +| `POST` | `/admin/woocommerce/config` | Сохранить/обновить конфиг | sales:edit | +| `POST` | `/admin/woocommerce/test` | Проверить подключение | sales:edit | +| `POST` | `/admin/woocommerce/disconnect` | Отключить (очистить ключи) | sales:edit | +| `POST` | `/admin/woocommerce/dataset-sync` | Запустить синхронизацию | sales:edit | +| `GET` | `/admin/woocommerce/dataset-status` | Статус датасета (документы, секции, файлы) | sales:view | +| `DELETE` | `/admin/woocommerce/dataset` | Очистить датасет | sales:manage | + +### Ответ dataset-sync + +```json +{ + "status": "ok", + "products": 150, + "categories": 8, + "orders": 42, + "files_written": 5, + "files_removed": 3, + "collection_id": 7, + "synced_at": "20.03.2026 14:30" +} +``` + +### Ответ dataset-status + +```json +{ + "synced": true, + "collection_id": 7, + "collection_name": "WooCommerce", + "documents": 5, + "total_sections": 127, + "last_sync": "20.03.2026 14:30", + "files": ["wc-category-1.md", "wc-orders.md", "wc-summary.md"] +} +``` + +## Модель данных + +**Таблица `woocommerce_config`** (синглтон, id=1): + +| Поле | Тип | Описание | +|------|-----|----------| +| `store_url` | string | URL магазина | +| `consumer_key` | string | API-ключ (маскируется в API-ответах) | +| `consumer_secret` | string | API-секрет (маскируется) | +| `is_connected` | boolean | Статус подключения | +| `sync_enabled` | boolean | Включить автосинхронизацию | +| `last_sync_at` | string | Время последней синхронизации | +| `products_count` | integer | Количество товаров | +| `categories_count` | integer | Количество категорий | +| `orders_count` | integer | Количество заказов | +| `workspace_id` | integer | FK на workspace | + +## Архитектура модуля + +``` +modules/ecommerce/ + ├── models.py → WooCommerceConfig (SQLAlchemy) + ├── router.py → 7 API-эндпоинтов + ├── service.py → WooCommerceService (CRUD конфига) + ├── sync.py → run_woocommerce_sync() — основная логика + └── tasks.py → woocommerce_daily_sync() — фоновая задача + +app/services/ + ├── woocommerce_service.py → HTTP-клиент WC REST API v3 + └── woocommerce_dataset_service.py → Генерация markdown из данных WC +``` + +## Особенности + +- **Аутентификация** — через query-параметры (`consumer_key` + `consumer_secret`), не HTTP Basic Auth (совместимость с хостингами) +- **Маскирование секретов** — API всегда возвращает `***{последние 4 символа}` +- **Устойчивость к ошибкам** — сбой загрузки категорий/заказов не прерывает синхронизацию (товары обязательны) +- **Автопагинация** — REST-клиент автоматически обходит все страницы (по 100 записей) +- **Цены в тенге** — форматирование с разделителем тысяч и знаком ₸ +- **Очистка HTML** — описания товаров очищаются от тегов, нормализуются пробелы + +--- + +**См. также:** [[Wiki-RAG]], [[Sales]], [[FAQ]] diff --git a/wiki-pages/_Sidebar.md b/wiki-pages/_Sidebar.md index 313e477..879132f 100644 --- a/wiki-pages/_Sidebar.md +++ b/wiki-pages/_Sidebar.md @@ -2,6 +2,7 @@ ### Главная * [[Home]] +* [[Architecture]] ### Мониторинг * [[Dashboard]] @@ -24,16 +25,19 @@ * [[Telegram]] * [[WhatsApp]] * [[Widget]] +* [[Mobile-App]] * [[GSM]] ### Бизнес * [[FAQ]] * [[Sales]] * [[CRM]] +* [[WooCommerce]] * [[Payments]] * [[Kanban]] ### Система +* [[Users]] * [[Settings]] * [[Backup]] * [[Wiki-RAG]]
{{ error }}
{{ truncate(session.last_message || "", 80) }}