import { useState } from 'react' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { AlertTriangle } from 'lucide-react' import { getAdminSettings, updateAdminSettings, type AiLevel, type SettingsOut, type SettingsUpdateIn } from '@/api/admin' import type { SummaryRecipientsMode } from '@/api/conferences' import { ApiError, errorDetail } from '@/api/client' import { Select } from '@/components/ui/Select' import { useToast } from '@/components/ui/ToastProvider' const SUMMARY_RECIPIENTS_OPTIONS = [ { value: 'all', label: 'Всем участникам' }, { value: 'owner', label: 'Только организатору' }, ] const AI_LEVEL_LABEL: Record = { min: 'Минимальный (CPU, faster-whisper small + Qwen2.5-3B)', medium: 'Средний', max: 'Максимальный', } /** * Вкладка «Настройки» админки — раздела нет в макете `admin.html`, построена * по паттерну того же макета (`section-tab`/`table-card` → карточки-секции * с `toggle-row`/radio). * Тумблер «Транскрибация и суммаризация» — единый переключатель AI-модуля * (см. `SettingsOut.transcription_enabled`). * * Форма (`AdminSettingsForm`) вынесена отдельно и монтируется только после * загрузки `data` — локальное состояние инициализируется прямо из пропсов * при монтировании, без `useEffect`-синхронизации (react-hooks/set-state-in-effect). */ export function AdminSettingsTab() { const { data, isLoading } = useQuery({ queryKey: ['admin', 'settings'], queryFn: getAdminSettings }) if (isLoading || !data) { return

Загрузка настроек…

} return } function AdminSettingsForm({ data }: { data: SettingsOut }) { const queryClient = useQueryClient() const toast = useToast() const [chatEnabled, setChatEnabled] = useState(data.chat_enabled) const [aiEnabled, setAiEnabled] = useState(data.transcription_enabled) const [aiLevel, setAiLevel] = useState(data.ai_level) const [recipients, setRecipients] = useState(data.summary_recipients) const [timezone, setTimezone] = useState(data.display_timezone) const [teamChoiceEnabled, setTeamChoiceEnabled] = useState(data.registration_team_choice) const [domainVerificationEnabled, setDomainVerificationEnabled] = useState(data.registration_email_domain_enabled) const [emailDomain, setEmailDomain] = useState(data.registration_email_domain ?? '') const mutation = useMutation({ mutationFn: (payload: SettingsUpdateIn) => updateAdminSettings(payload), onSuccess: (result) => { queryClient.setQueryData(['admin', 'settings'], result) toast.show('Настройки сохранены', 'success') }, onError: (err: unknown) => { if (err instanceof ApiError && err.status === 400) { toast.show(errorDetail(err) ?? 'Недоступное значение — проверьте уровень AI, таймзону и домен почты', 'error') } else { toast.show('Не удалось сохранить настройки', 'error') } }, }) function handleSave() { mutation.mutate({ chat_enabled: chatEnabled, transcription_enabled: aiEnabled, ai_level: aiLevel, summary_recipients: recipients, display_timezone: timezone.trim(), registration_team_choice: teamChoiceEnabled, registration_email_domain_enabled: domainVerificationEnabled, registration_email_domain: emailDomain.trim() || null, }) } return (

Модули

Включение и отключение возможностей инстанса — переключатели плагинов из поставки.

Чат конференции Текстовый чат внутри комнаты для участников
Транскрибация и суммаризация (AI) Батч-обработка записи после конференции: расшифровка речи и саммари {aiEnabled && !data.transcription_queue_served && ( )}
Разрешить выбор команды при регистрации На экране регистрации появится поле «Команда» со списком команд инстанса
Верификация по домену почты Регистрация только с email эталонного домена (см. плитку «Эталон mail-домена»)
{/* Анонс записи конференций. Чекбокс не имеет состояния и не участвует в сохранении формы — функция появится в одной из следующих версий, здесь только заглушка-анонс в общем стиле «Модулей». */}
Возможность записи конференций Функция появится в ближайших версиях

Уровень AI

Качество распознавания и суммаризации — определяет размер моделей и требования к железу.

{data.ai_levels.map((levelStatus) => ( ))}

Эталон mail-домена

Домен, с которым сверяется email при регистрации, если включена верификация в «Модулях».

setEmailDomain(e.target.value)} />

Рассылка саммари

Кому по умолчанию отправляется письмо с саммари после конференции (переопределяется в карточке конференции).

setTimezone(e.target.value)} />
) }