From ea4ed73327248f94f0a0dfe18641ef6be8b76c0d Mon Sep 17 00:00:00 2001 From: stepan Date: Tue, 15 Sep 2026 21:59:43 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8=D1=82?= =?UTF-8?q?=D1=8C=20=D0=B4=D0=BE=D0=BA=D1=83=D0=BC=D0=B5=D0=BD=D1=82=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D1=8E=20=D0=BF=D0=BE=D0=B4=20=D0=B3=D0=BB=D0=BE?= =?UTF-8?q?=D0=B1=D0=B0=D0=BB=D1=8C=D0=BD=D1=83=D1=8E=20=D0=BA=D0=BE=D0=BD?= =?UTF-8?q?=D1=84=D0=B8=D0=B3=D1=83=D1=80=D0=B0=D1=86=D0=B8=D1=8E=20=D0=98?= =?UTF-8?q?=D0=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ТЗ, api-map, техническая документация, инструкция пользователя и контракт операторских настроек: настройки ИИ-провайдера перенесены в консоль оператора, пользовательский POST /api/ai/check удалён. --- docs/api/api-map.md | 19 +++-- .../2026-09-10-operator-analytics-contract.md | 70 +++++++++++++++++++ docs/spec/ТЗ-дейл-новая-архитектура.md | 8 ++- .../Техническая-документация-Дейл.md | 36 ++++++---- .../Инструкция-пользователя-Дейл.md | 14 ++-- 5 files changed, 114 insertions(+), 33 deletions(-) diff --git a/docs/api/api-map.md b/docs/api/api-map.md index 82753d3..4f55b44 100644 --- a/docs/api/api-map.md +++ b/docs/api/api-map.md @@ -136,17 +136,19 @@ | `POST /dialogs/{dialog_id}/backfill` | Догнать сообщения одного диалога | — | `{ok: true, processed: int}` *(фронт не вызывает — только сервер)* | | `POST /dialogs/preview` | Последние сообщения диалога (свежие из TG, старые из БД) | `{dialogId, limit?=24 (clamp 1..50)}` | `{items: [§4.11 сообщение]}` | -### 3.4 Settings / rates / meta (settings_routes.py) — 6 +### 3.4 Settings / rates / meta (settings_routes.py) — 5 | METHOD /api/… | Назначение | Request body | Response | |---|---|---|---| | `GET /settings` | Публичные настройки (секреты замаскированы) | — | §4.6 (полный settings) | -| `PATCH /settings` | Частичное обновление (см. §4.6 список ключей). Инварианты: `archiveAfterDays` 1..30, `minLen` 10..500, `discJoinLimit` 1..200, `discJoinDelayMin/Max` 5..600 (min≤max), `discEvalSample` 3..30, `discEvalThreshold` 1..100; `aiConfigs` apiKey ≥8 → шифруется; `myPrompts` ≤100. ⚠ `tgKeys` удалён из настроек тенанта — ключи Telegram задаёт оператор глобально. ⚠ Ответ — **весь** public settings (фронт затирает локальное состояние ответом) | произвольный dict из публичных ключей | §4.6 | -| `POST /ai/check` | Проверка подключения AI-провайдера | — | `{ok: bool, message: str}` + поля статуса провайдера | +| `PATCH /settings` | Частичное обновление (см. §4.6 список ключей). Инварианты: `archiveAfterDays` 1..30, `minLen` 10..500, `discJoinLimit` 1..200, `discJoinDelayMin/Max` 5..600 (min≤max), `discEvalSample` 3..30, `discEvalThreshold` 1..100; `myPrompts` ≤100. ⚠ `tgKeys`, `aiProvider` и `aiConfigs` удалены из настроек тенанта — ключи Telegram и конфигурацию ИИ задаёт оператор глобально. ⚠ Ответ — **весь** public settings (фронт затирает локальное состояние ответом) | произвольный dict из публичных ключей | §4.6 | | `GET /rates` | Курсы валют | — | `{base: "RUB", rates: {CODE: num}, source: "cbr"\|"mock", updatedAt: ms\|null}` | | `POST /rates/refresh` | Принудительно обновить курсы (ЦБ/мок) | — | `{ok: bool, rates: {base, rates, source, updatedAt}}` — ⚠ фронт передаёт `r.rates` в `applyRates` | | `GET /meta/constants` | Валюты/стадии/палитра | — | `{currencies: [{code,name,symbol}], stages: [§4.4], palette: ["#…"]}` *(фронт не вызывает — зашиты в data.js)* | +⚠ `POST /ai/check` удалён: проверка связи с провайдером ИИ теперь операторская — +`POST /api/operator/settings/ai-config/check` (см. `2026-09-10-operator-analytics-contract.md`). + ### 3.5 Детальные операции карточки (бывший Projects, projects_routes.py) — 15 Все операции — над ресурсом `/api/cards/{cardId}` (см. §4.1); отдельного `/api/projects` больше нет. @@ -347,14 +349,12 @@ links/files/history/tzText/reminder` — модули; `isNew/prevCol/isVacancy/ "discJoinLimit": 50, "discJoinDelayMin": 50, "discJoinDelayMax": 70, "discEvalSample": 10, "discEvalThreshold": 40, // (не используется фронтом) "discPaused": false, "colState": {}, // colState — то же, что GET /columns/state - "aiProvider": "deepseek", - "aiConfigs": { "deepseek": {"baseUrl": "https://api.deepseek.com", "model": "…", "keySet": true, "keyMasked": "sk-12…3456"} }, - "providers": [{"id":"deepseek","name":"DeepSeek","base":"…","local":false,"models":[…]}, …] } ``` -Ключи, которые фронт шлёт в PATCH (по одному/группами): `aiProvider`, `aiConfigs{:{baseUrl,model,apiKey?}}`, `aiPrompt`, `cardPrompt`, `aiFilterPrompt`, `stopPhrases`, `domainDescription`, `domainKeywords`, `hireMarkers`, `levelTerms`, `resumeMarkers`, `blockResumes`, `myPrompts`, `autoArchive`, `archiveAfterDays`, `aiEnabled`, `aiFilterEnabled`, `minLen`, `conversionOn`, `targetCurrency`, `rateSource`, `remindersEnabled`, `mlEnabled`, `wantedType`, `budgetRequiredHire`, `budgetRequiredOrder`, `hireLabel`, `orderLabel`, `autoMonitorNew`, `discJoinLimit`, `discJoinDelayMin`, `discJoinDelayMax`, `discPaused`. -⚠ Ответ PATCH — **полный** settings: `schedulePersist`/`saveAiSettings`/`saveDiscQuota` применяют его целиком к локальному state (источник истины после клампов). +Ключи, которые фронт шлёт в PATCH (по одному/группами): `aiPrompt`, `cardPrompt`, `aiFilterPrompt`, `stopPhrases`, `domainDescription`, `domainKeywords`, `hireMarkers`, `levelTerms`, `resumeMarkers`, `blockResumes`, `myPrompts`, `autoArchive`, `archiveAfterDays`, `aiEnabled`, `aiFilterEnabled`, `minLen`, `conversionOn`, `targetCurrency`, `rateSource`, `remindersEnabled`, `mlEnabled`, `wantedType`, `budgetRequiredHire`, `budgetRequiredOrder`, `hireLabel`, `orderLabel`, `autoMonitorNew`, `discJoinLimit`, `discJoinDelayMin`, `discJoinDelayMax`, `discPaused`. +⚠ Ответ PATCH — **полный** settings: `schedulePersist`/`saveDiscQuota` применяют его целиком к локальному state (источник истины после клампов). ⚠ **Изменение (решение владельца, вариант A):** ключей Telegram (`api_id`/`api_hash`) в настройках тенанта больше нет — они задаются оператором глобально (ТЗ §4.1/§8.1), см. `docs/architecture/2026-09-10-operator-analytics-contract.md` (раздел «Операторские настройки»). Вкладка Telegram у тенанта остаётся (подключение аккаунта, `GET /api/tg/status`). +⚠ **Изменение (2026-09-14):** настройки ИИ-провайдера (`aiProvider`, `aiConfigs`, `providers`) из настроек тенанта убраны — провайдера, модель, адрес и ключ задаёт оператор в консоли (раздел «ИИ», `GET/PUT /api/operator/settings/ai-config`); все ИИ-вызовы всех пользователей идут на эту конфигурацию. ### 4.7 Промпты - `aiPrompt`, `aiFilterPrompt`, `cardPrompt` — plain string, редактируются на вкладке ИИ; содержат плейсхолдеры `{domain}`/`{keywords}`. @@ -381,7 +381,6 @@ links/files/history/tzText/reminder` — модули; `isNew/prevCol/isVacancy/ `{passed, wouldCreateCard, targetContainer, matchHits, parsed, stages:[{stage, pass, skipped, reason, kw, label}]}`. Коды `stage`: `length|stop|resume|type|exclude|ml|ai|spam_ai|budget`; `skipped=true` — этап выключен настройкой. `parsed` — разбор текста (поля карточки) либо null. Запись в систему не производится. -- **`POST /api/ai/check`**: `{ok: bool, message: string, local?, keySet?}`. - Комментарии карточки: `{id, by: string, text, time: string}` — `by` всегда «Вы», `time` «только что». --- @@ -400,7 +399,7 @@ links/files/history/tzText/reminder` — модули; `isNew/prevCol/isVacancy/ |---|---:| | Auth `/api/auth` | 4 | | Telegram `/api/tg` | 14 | -| Settings/rates/meta (`/api/settings`, `/api/ai/check`, `/api/rates`) | 5 | +| Settings/rates/meta (`/api/settings`, `/api/rates`) | 4 | | Processing `/api/pipeline` (+ `/api/admin/check-message`) | 7 | | ML `/api/ml` | 5 | | Discovery `/api/discovery` | 13 | diff --git a/docs/architecture/2026-09-10-operator-analytics-contract.md b/docs/architecture/2026-09-10-operator-analytics-contract.md index 3a29a5a..06d3ce1 100644 --- a/docs/architecture/2026-09-10-operator-analytics-contract.md +++ b/docs/architecture/2026-09-10-operator-analytics-contract.md @@ -268,3 +268,73 @@ > Примечание для вкладки Telegram у тенанта: `GET /api/tg/status` остаётся (подключение аккаунта), > поле `keysSet` отражает глобальные ключи; команды `start-phone`/`start-qr` без ключей отвечают > `400 { "detail": "Ключи Telegram не заданы оператором" }`. + +--- + +## Операторские настройки: глобальная конфигурация ИИ + +Провайдер, модель, адрес API и ключ ИИ задаёт оператор **глобально**, едины для всех тенантов +(в настройках тенанта ключей `aiProvider`/`aiConfigs` больше нет). Хранилище — системная таблица +`public.global_settings` (ключ `aiConfig`), `apiKey` хранится зашифрованным (`enc:`) и наружу +не отдаётся. На эту конфигурацию работают все ИИ-вызовы всех тенантов: классификация, фильтр, +ключи поиска, карточки (`AiProviderConfigBuilder`). + +### GET /api/operator/settings/ai-config + +Маскированный снимок конфигурации вместе с каталогом провайдеров для выбора. + +**200** + +```json +{ + "providerId": "deepseek", + "baseUrl": "https://api.deepseek.com", + "model": "deepseek-v4-flash", + "keySet": true, + "keyMasked": "sk-o…-123", + "providers": [ + { "id": "deepseek", "name": "DeepSeek", "base": "https://api.deepseek.com", "local": false, "models": ["…"] } + ] +} +``` + +- `providerId` — пусто, если конфигурацию ещё не задавали. +- `baseUrl`/`model` — эффективные значения (адрес каталога, первая модель каталога), если не переопределены. +- `keyMasked` — **маска** (пусто / `x…` / `1234…5678`); открытый ключ не возвращается никогда. +- `providers` — каталог `AiProviders` (`id`/`name`/`base`/`local`/`models`); адрес каталогных облачных + провайдеров фиксирован (SSRF-гейт), свой `baseUrl` задаётся только локальным и `custom`. + +**Коды**: `200`, `401`. + +### PUT /api/operator/settings/ai-config + +Сохранение/смена. Поля можно передавать **по отдельности** (частичное обновление): непереданное поле +(`null` или отсутствие в JSON) сохраняет текущее значение. Если конфигурации ещё нет, `providerId` обязателен. +При смене провайдера `baseUrl`/`model`/`apiKey` не переносятся от старого (дефолты каталога); ключ +меняется только при явной передаче (маска не принимается). + +**Тело** + +```json +{ "providerId": "deepseek", "model": "deepseek-v4-pro", "apiKey": "sk-…" } +``` + +**200** — маскированный снимок (форма как у GET). + +**Ошибки** + +- `400 { "detail": "Укажите хотя бы одно поле (providerId, baseUrl, model, apiKey)" }` +- `400 { "detail": "Провайдер не из списка разрешённых" }` +- `400 { "detail": "Конфигурация ИИ ещё не задана — укажите providerId" }` +- `400 { "detail": "Укажите model — у выбранного провайдера нет моделей по умолчанию" }` +- `400 { "detail": "API-ключ должен быть не короче 8 символов, без маски" }` +- `401 { "detail": "Требуется вход оператора" }` + +### POST /api/operator/settings/ai-config/check + +Проверка связи с сохранённым провайдером (`200` — результат `AiCheckResultDto`). Локальный провайдер +отвечает `ok: true` без HTTP; облачный — запрос к списку моделей (приватные адреса запрещены, SSRF-гейт). +`400 { "detail": "Сначала сохраните конфигурацию ИИ" }` — конфигурации ещё нет. + +**Аудит**: событие `ai_config_changed` (актор `operator`, `tenantId: null`, детали +`{providerId, baseUrl, model, keySet}` — без ключа). diff --git a/docs/spec/ТЗ-дейл-новая-архитектура.md b/docs/spec/ТЗ-дейл-новая-архитектура.md index 8f0ae37..64487a0 100644 --- a/docs/spec/ТЗ-дейл-новая-архитектура.md +++ b/docs/spec/ТЗ-дейл-новая-архитектура.md @@ -189,9 +189,9 @@ Telegram-аккаунт, выбирает каналы/группы для мо ## 8. Настройки тенанта - Telegram: ключи приложения (оператор), подключение аккаунта, авто-мониторинг новых. -- ИИ: провайдер (один; включая локальные), модель, ключ (хранится зашифрованно), - промпты (базовый + свой), библиотека готовых промптов по сферам + «мои промпты», - вкл/выкл ИИ, вкл/выкл ИИ-фильтр. +- ИИ: провайдер (один; включая локальные), модель и ключ задаёт **оператор** глобально (едины для всех + тенантов; в консоли оператора раздел «ИИ», ключ хранится зашифрованно); пользователю — промпты + (базовый + свой), библиотека готовых промптов по сферам + «мои промпты», вкл/выкл ИИ, вкл/выкл ИИ-фильтр. - ML: вкл/выкл, обучение на действиях, проверка на сообщении/канале, сброс, самооценка («ML справляется с последними N сообщениями — ИИ можно отключить»). - Обработка: стоп-фразы, длина, резюме, тип заявки, домен/ключи, маркеры найма/заказа. @@ -219,6 +219,8 @@ Telegram-аккаунт, выбирает каналы/группы для мо ## 10. Админка оператора - Тенанты: создание, инвайты, статус, лимиты/бюджеты, приостановка. +- Глобальные настройки сервиса: ключи приложения Telegram и конфигурация ИИ-провайдера + (провайдер/модель/baseUrl/ключ; ключ зашифрован, наружу — маска) с проверкой связи. - Health всех сервисов и очередей. - Аудит: входы/выходы, инвайты, impersonation, действия оператора и пользователей тенанта (создание/перенос/удаление карточек, комментарии, контейнеры, настройки, каналы). diff --git a/docs/technical/Техническая-документация-Дейл.md b/docs/technical/Техническая-документация-Дейл.md index 76dce27..c258342 100644 --- a/docs/technical/Техническая-документация-Дейл.md +++ b/docs/technical/Техническая-документация-Дейл.md @@ -119,8 +119,10 @@ global_settings(key, value, updated_at) > `token_usage_events` — история расхода токенов (этап 10, T2; подробнее — §13.10). > `global_settings` — глобальные настройки уровня сервиса; сейчас хранит ключи приложения Telegram -> (`telegramKeys`: `api_id`/`api_hash`, hash — в `enc:`), которые задаёт **оператор** глобально -> (ручки `GET/PUT /api/operator/settings/telegram-keys`); тенант ключи не видит/не задаёт. +> (`telegramKeys`: `api_id`/`api_hash`, hash — в `enc:`) и конфигурацию ИИ-провайдера (`aiConfig`: +> `providerId`/`baseUrl`/`model`/`apiKey` — в `enc:`), которые задаёт **оператор** глобально +> (ручки `GET/PUT /api/operator/settings/telegram-keys` и `/ai-config`, проверка связи — +> `POST /api/operator/settings/ai-config/check`); тенант эти настройки не видит и не задаёт. > Операторские таблицы этапа 7 (`operators`, `operator_sessions`, `tenant_limits`, `audit_log`) и их > контур описаны в §13.8. С этапа 12 счётчики распределённого rate-limit и попыток входа — > `public.rate_limit_counters` (см. §10). @@ -496,7 +498,8 @@ DEAL_MTLS_ENABLED=0|1 DEAL_MTLS_CERT_PASSWORD=... DEAL_DEFAULT_AI_BU ответ — полный снимок; секреты наружу только масками `keyMasked`/`apiId`; внутренние ключи `ratesCache`/`mlDecisions`/`aiDecisions` не публикуются); - шифрование секретов AI/Telegram: AES-256-GCM, в БД — `enc:` + Base64 (ключ — env/file, см. §13.4a); -- проверка подключения ИИ: `POST /api/ai/check` (локальный провайдер / HTTP-проверка облачного); +- проверка подключения ИИ: `POST /api/operator/settings/ai-config/check` (операторская; локальный провайдер / + HTTP-проверка облачного); - курсы валют: `GET /api/rates`, `POST /api/rates/refresh` (кэш `ratesCache` в settings; `mock`/ЦБ); - ML-панель на детерминированной заглушке: `GET /api/ml/status`, `POST /api/ml/reset|predict` (candidates → `{items:[]}`, apply → 404 — нет telegram-данных до этапа 6); @@ -774,28 +777,30 @@ health — `GET /api/health` → `{"ok":true,"service":"deal"}`. ### 4a. Шифрование секретов настроек (ключи AI/Telegram) -Секреты (`aiConfigs[].apiKey`) хранятся в `settings.ValueJson` шифротекстом: -`enc:` + Base64(nonce‖ct‖tag), AES-256-GCM (nonce 12 Б, tag 16 Б). Ключ шифрования — env -`DEAL_ENCRYPTION_KEY` (32 байта в urlsafe-Base64); при отсутствии в dev берётся/создаётся файл -`/data/encryption.key` (путь переопределяется env `DEAL_ENCRYPTION_KEY_FILE`) — -при генерации лог-warning. Невалидный env-ключ — ошибка при старте. Наружу секреты не отдаются: -в GET/PATCH `/api/settings` только маски `keyMasked` (первые 4 + «…» + последние 4, len≤8 — как есть) +Секреты хранятся шифротекстом `enc:` + Base64(nonce‖ct‖tag), AES-256-GCM (nonce 12 Б, tag 16 Б). +Ключ шифрования — env `DEAL_ENCRYPTION_KEY` (32 байта в urlsafe-Base64); при отсутствии в dev +берётся/создаётся файл `/data/encryption.key` (путь переопределяется env +`DEAL_ENCRYPTION_KEY_FILE`) — при генерации лог-warning. Невалидный env-ключ — ошибка при старте. +Наружу секреты не отдаются: только маски `keyMasked` (первые 4 + «…» + последние 4, len≤8 — как есть) и `keySet`. > Исторический раздел (этап 2). С этапа 12 ключей Telegram (`tgKeys`/`apiId`/`apiHash`) в настройках > тенанта нет — они задаются **оператором** глобально (таблица `public.global_settings`, > `GET/PUT /api/operator/settings/telegram-keys`; hash шифруется тем же AES-256-GCM). +> +> С 2026-09-14 там же живёт и конфигурация ИИ-провайдера (ключ `aiConfig` в `public.global_settings`, +> `GET/PUT /api/operator/settings/ai-config`): провайдер, модель, baseUrl и API-ключ задаёт оператор, +> все ИИ-вызовы всех тенантов идут на эту конфигурацию; в настройках тенанта ключей `aiProvider`/ +> `aiConfigs` больше нет. ### 4b. Эндпоинты этапа 2 (настройки тенанта; сессия `deal_session` обязательна, иначе 401) - `GET /api/settings` — публичный снимок дерева настроек: дефолты модуля, перекрытые - переопределениями из `settings` тенанта; включает списки `providers`/`aiConfigs`/`tgKeys`/`myPrompts`. + переопределениями из `settings` тенанта; включает `myPrompts` и колонки, но не настройки + ИИ-провайдера (они операторские). `PATCH /api/settings` — частичное обновление (невалидное поле мягко пропускается, ответ — полный снимок). Побочные эффекты: при `rateSource` — фоновый refresh курсов. Внутренние ключи (`ratesCache`, `mlDecisions`, `aiDecisions`) в GET/PATCH не участвуют. -- `POST /api/ai/check` — проверка подключения активного провайдера (`aiProvider` + `aiConfigs`, ключ - расшифровывается): локальный провайдер → `ok:true` «Локальный сервер…»; облачный — HTTP `GET - {base}/models`; без ключа → «Не задан API-ключ». - `GET /api/rates` / `POST /api/rates/refresh` — курсы к RUB (`base` = `RUB`); источник по `rateSource` (`mock` — константа, `cbr` — ЦБ РФ, ≤4 запроса/сутки, интервал 6 ч; `USDT`=`USD`); кэш — внутренняя настройка `ratesCache` `{rates, source, updatedAtMs}`. @@ -1149,8 +1154,9 @@ docker compose -f deploy/compose.dev.yml down # погасить ст (`PUT /api/operator/settings/telegram-keys`, hash шифруется) → `POST /api/tg/start-qr` → QR-скан → фаза `ready` («Telegram подключён, сессия сохранена»), затем реальные диалоги/мониторинг/«Перечитать»/ discovery-поиск и вступления. В настройках тенанта ключей нет (решение владельца, вариант A). -- LLM: `PATCH /api/settings` `aiConfigs`/`aiProvider` (напр. DeepSeek или локальный OpenAI-совместимый) → - `POST /api/ai/check`; реальная классификация/фильтр/генерация ключей при `Services__Ai__UseLocal=false`. +- LLM: оператор задаёт провайдера и модель в консоли (`PUT /api/operator/settings/ai-config`, напр. DeepSeek + или локальный OpenAI-совместимый) → `POST /api/operator/settings/ai-config/check`; реальная + классификация/фильтр/генерация ключей при `Services__Ai__UseLocal=false`. - Сквозной smoke стека — `scripts/dev-smoke.sh` (одна команда; Docker Desktop должен быть поднят). ### 8. Этап 7 — SaaS-контур (Tasks 1–14; бэкапы — §13.9; финальные доки — Task 16): оператор/инвайты/лимиты/аудит/rate-limit/mTLS/логи/compose-prod diff --git a/docs/user-guide/Инструкция-пользователя-Дейл.md b/docs/user-guide/Инструкция-пользователя-Дейл.md index c5df84d..78b943e 100644 --- a/docs/user-guide/Инструкция-пользователя-Дейл.md +++ b/docs/user-guide/Инструкция-пользователя-Дейл.md @@ -184,12 +184,13 @@ **Настройки → Telegram:** подключение аккаунта, авто-мониторинг новых чатов. **Настройки → ИИ:** -- провайдер и модель (можно выбрать один, включая локальные OpenAI-совместимые); -- ключ API (хранится зашифрованно); -- **промпты**: базовый (не меняется) + свой промпт; библиотека готовых промптов по сферам - с поиском и категориями; сохранённые свои промпты («Мои промпты»); - вкл/выкл ИИ и ИИ-фильтр. Если ML уже уверенно обрабатывает поток — система подскажет, - что ИИ можно отключить. + что ИИ можно отключить; +- **промпты**: базовый (не меняется) + свой промпт; библиотека готовых промптов по сферам + с поиском и категориями; сохранённые свои промпты («Мои промпты»). + +Провайдера, модель и API-ключ задаёт оператор сервиса — они едины для всех пользователей +и в кабинете не настраиваются. **Настройки → ML:** включение, обучение на ваших действиях, проверка модели на сообщении/канале, сброс обучения, показатели самооценки. @@ -257,6 +258,9 @@ пространствам и лимитам) с фильтрами по типу события, актору, пространству и периоду; есть пагинация. - **Аналитика** — обзор за период (число пространств, расход токенов, входы/выходы/неудачные входы), расход токенов с группировкой по дням/пространствам/провайдерам/моделям и лента действий. +- **ИИ** — глобальный провайдер ИИ: выбор провайдера из каталога, модель, адрес API (для локальных + и «Другого») и API-ключ, а также проверка связи. Конфигурация единая для всех пользователей; + ключ хранится зашифрованным и показывается только маской. - **Состояние системы** — доступность ядра, базы данных и сервисов (Telegram, ИИ, ML). > **Dev-окружение:** вход в консоль — `operator`/`operator`. В обычной (прод) сборке учётные