"""Модели 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