Files
vidconf/backend/core/plugins/config.py
Max Ronzhin 7ee68b17ee feat(auth): проверка устройств на входе — запрос доступа и превью камеры
Отключаемый модуль (instance_settings.device_check, дефолт выключен):
запрос доступа к камере/микрофону на LoginPage и в карточке "Как вас
зовут?" (JoinPage), живое зеркальное превью и кнопки вкл/выкл камеры и
микрофона там же. На JoinPage кнопки определяют, с чем гость войдёт в
конференцию (RoomPage.LiveKitRoom audio/video вместо жёстких false) —
на LoginPage только пре-авторизуют разрешение, без UI (карточка ведёт
в лобби, применить выбор некуда). Вход в комнату по умолчанию, как и
раньше, с выключенными микрофоном/камерой.

Публичный GET /api/v1/public/settings отдаёт флаг модуля обеим
страницам до аутентификации.
2026-08-09 18:09:10 +03:00

141 lines
8.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Модели Pydantic для описания `config/plugins.yaml` и его загрузчика."""
from pathlib import Path
from typing import Any, Literal
import yaml
from pydantic import BaseModel, Field
class TranscriberConfig(BaseModel):
"""Конфигурация активного плагина transcriber."""
enabled: bool = True
provider: str = Field(default="null", min_length=1)
model: str | None = None
language: str = "ru"
options: dict[str, Any] = Field(default_factory=dict)
class SummarizerConfig(BaseModel):
"""Конфигурация активного плагина summarizer."""
enabled: bool = True
provider: str = Field(default="null", min_length=1)
model: str | None = None
chunk_minutes: int = 20
options: dict[str, Any] = Field(default_factory=dict)
class ChatConfig(BaseModel):
"""Конфигурация переключателя функции чата."""
enabled: bool = True
class HandQueueConfig(BaseModel):
"""Конфигурация переключателя модуля «поднятие руки» (кнопка + очередь целиком)."""
enabled: bool = True
class PluginsConfig(BaseModel):
"""Корневая модель конфигурации для `config/plugins.yaml`."""
transcriber: TranscriberConfig = Field(default_factory=TranscriberConfig)
summarizer: SummarizerConfig = Field(default_factory=SummarizerConfig)
chat: ChatConfig = Field(default_factory=ChatConfig)
hand_queue: HandQueueConfig = Field(default_factory=HandQueueConfig)
def load_plugins_config(path: str | Path) -> PluginsConfig:
"""Загрузить и валидировать `PluginsConfig` из YAML файла."""
raw = yaml.safe_load(Path(path).read_text()) or {}
return PluginsConfig.model_validate(raw)
# --- Настройки инстанса: БД поверх дефолтов `plugins.yaml`. ---
# Ключи `instance_settings` зеркалят секции ниже (`transcriber`, `summarizer`,
# `chat`, `ai_level`, `summary_recipients`, `display_timezone`) — см.
# `services/instance_settings.py`.
AiLevel = Literal["min", "medium", "max"]
"""Уровень AI-модуля инстанса (модели/требования — ADR-004,
`docs/architecture/adr/004-ai-tier-matrix.md`, `services/ai_tiers.TIERS`).
Доступность каждого уровня на конкретном инстансе зависит от обнаруженного
железа и скачанных моделей — см. `services/ai_levels.py::detect_ai_levels`."""
SummaryRecipientsMode = Literal["all", "owner"]
"""Режим рассылки саммари по умолчанию: всем участникам либо только
владельцу конференции (переопределяется на уровне `conferences.summary_recipients`)."""
PublishQualityCap = Literal["off", "180p", "360p", "720p"]
"""Потолок качества исходящего видео участника (задача «рычаги качества
медиа»). `off` — без ограничения (дефолт, поведение как до появления
настройки). Остальные значения режут `publishDefaults.videoEncoding` и
`videoSimulcastLayers` на клиенте (`frontend/src/lib/publishQualityCap.ts`) —
именно битрейт верхнего слоя симулкаста, а не жёсткое разрешение захвата
камеры; фактическое разрешение WebRTC подстраивает под битрейт сам."""
StageMaxTiles = Literal[4, 9, 16, 25]
"""Потолок числа одновременно видимых плиток на сцене (`StageGrid`) —
режет набор доступных раскладок сетки, что бросает лишних участников на
следующую страницу пагинации вместо подписки на их треки. `25` — дефолт,
совпадает с текущим максимумом сетки (5×5), то есть без ограничения."""
class MediaLimitsConfig(BaseModel):
"""Рычаги нагрузки медиа для администратора инстанса (не логика состояния
конференции — статичные потолки, применяются на клиенте при входе)."""
publish_quality_cap: PublishQualityCap = "off"
stage_max_tiles: StageMaxTiles = 25
class InstanceConfig(BaseModel):
"""Эффективная конфигурация инстанса (значения `instance_settings` поверх дефолтов
`plugins.yaml`, см. `services/instance_settings.py::load_effective_config`)."""
transcriber: TranscriberConfig
summarizer: SummarizerConfig
chat: ChatConfig
hand_queue: HandQueueConfig = Field(default_factory=HandQueueConfig)
ai_level: AiLevel = "min"
summary_recipients: SummaryRecipientsMode = "all"
display_timezone: str = "Europe/Moscow"
# Разрешить выбор команды на форме регистрации (справочник `teams`)
# — см. `services/instance_settings.py`.
registration_team_choice: bool = False
# Верификация регистрирующихся по домену email: при включении
# `POST /auth/register` принимает только email с доменом из
# `registration_email_domains` (любым из списка) — см.
# `services/instance_settings.py`.
registration_email_domain_enabled: bool = False
registration_email_domains: list[str] = Field(default_factory=list)
# Контактный адрес инстанса — подставляется в `Reply-To` исходящих писем
# (сами письма уходят от `no-reply@`, отвечать на них некуда без этого
# адреса) — см. `services/instance_settings.py`, `services/email.py`.
contact_email_enabled: bool = False
contact_email: str | None = None
# Потолок качества публикации + максимум плиток сцены — см.
# `services/instance_settings.py`. Отдаётся участнику ДО входа в
# LiveKit-комнату (в ответе join, `schemas/conferences.py::JoinOut`), а
# не только в админке — настройка должна быть на руках у клиента до
# публикации трека.
media_limits: MediaLimitsConfig = Field(default_factory=MediaLimitsConfig)
# Согласие на обработку персональных данных при регистрации: галочка
# обязательна только при `consent_required=True`, текст/версия — редактируемая
# администратором настройка (дефолт — типовой шаблон, не юридический документ) —
# см. `services/instance_settings.py`. `consent_policy_text`/`_version`
# отдаются публично (`GET /auth/registration-options`) независимо от
# `consent_required`, чтобы страница регламента была осмысленной и при
# выключенном модуле.
consent_required: bool = False
consent_policy_text: str = ""
consent_policy_version: int = 1
# Проверка устройств на входе (сессия 33): запрос доступа к микрофону/камере
# + превью камеры на странице логина и в карточке «Как вас зовут?» (JoinPage).
# Дефолт False сохраняет поведение существующих инсталляций — см.
# `services/instance_settings.py`.
device_check_enabled: bool = False