Files
call-center/ui/admin/index.html
T

507 lines
28 KiB
HTML

<!doctype html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>KonturCC Администрирование</title>
<link rel="icon" href="/operator/assets/favicon.svg" type="image/svg+xml" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@500;600;700;800&family=IBM+Plex+Sans:wght@400;500;600&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="/operator/assets/styles.css" />
</head>
<body>
<div class="app-shell">
<aside class="sidebar">
<div class="brand">
<div class="brand-mark">K</div>
<div>
<div class="brand-title">KonturCC</div>
<div class="brand-sub">Панель управления</div>
</div>
</div>
<nav class="nav-menu">
<div class="nav-group">
<div class="nav-label">Рабочие зоны</div>
<a class="nav-link" data-shell="operator" data-roles="admin,supervisor,operator,analyst" href="/operator"><span class="nav-bullet"></span>Оператор</a>
<a class="nav-link" data-shell="supervisor" data-roles="admin,supervisor" href="/supervisor"><span class="nav-bullet"></span>Супервизор</a>
<a class="nav-link" data-shell="analyst" data-roles="admin,supervisor,analyst" href="/analyst"><span class="nav-bullet"></span>Аналитика</a>
<a class="nav-link" data-shell="sales" data-roles="admin,supervisor,operator,analyst" href="/sales"><span class="nav-bullet"></span>Продажи</a>
<a class="nav-link active" data-shell="admin" data-roles="admin" href="/admin"><span class="nav-bullet"></span>Администрирование</a>
</div>
<div class="nav-group">
<div class="nav-label">Блоки</div>
<a class="nav-link" href="#users"><span class="nav-bullet"></span>Пользователи</a>
<a class="nav-link" href="#queues"><span class="nav-bullet"></span>Очереди</a>
<a class="nav-link" href="#routes"><span class="nav-bullet"></span>Маршрутизация</a>
<a class="nav-link" href="#ivr"><span class="nav-bullet"></span>IVR</a>
<a class="nav-link" href="#voiceNameCollection"><span class="nav-bullet"></span>Voice AI</a>
<a class="nav-link" href="#voiceTtsConfig"><span class="nav-bullet"></span>Voice TTS</a>
<a class="nav-link" href="#asterisk"><span class="nav-bullet"></span>Asterisk</a>
</div>
</nav>
<div class="sidebar-footer">
<strong>Экран администратора</strong>
<p>Управление пользователями, очередями и правилами маршрутизации в одном экране.</p>
<button id="logoutBtn" class="btn ghost" type="button">Выйти</button>
</div>
</aside>
<section class="workspace">
<header class="workspace-topbar reveal">
<div class="search-box">
<input class="app-search" placeholder="Найти пользователя, очередь или правило..." />
</div>
<div class="topbar-actions">
<div class="status-pill" id="gatewayStatus">Шлюз: проверка...</div>
<button class="icon-button" type="button" aria-label="Уведомления"></button>
<div class="profile-chip">
<div class="avatar-badge">AD</div>
<div class="profile-meta">
<div class="profile-name" id="profileName">Экран администратора</div>
<div class="profile-role" id="profileRole">Панель управления</div>
</div>
</div>
</div>
</header>
<main class="layout">
<section class="page-hero reveal">
<p class="eyebrow">Административный контур</p>
<h1>Панель администратора</h1>
<p class="hint">Управление пользователями, очередями и предпросмотром маршрутизации без технического режима.</p>
</section>
<section class="panel auth reveal" id="authPanel">
<h2>Сессия</h2>
<div class="row">
<label>Пользователь</label>
<input id="sessionUser" value="admin" />
</div>
<div class="row">
<label>Роль</label>
<select id="sessionRole">
<option value="admin" selected>Администратор</option>
<option value="supervisor">Супервизор</option>
<option value="operator">Оператор</option>
<option value="analyst">Аналитик</option>
</select>
</div>
<div class="row">
<label>Пароль</label>
<input id="sessionPassword" type="password" value="admin123" />
</div>
<div class="actions">
<button id="loginBtn" class="btn">Войти</button>
<button id="corporateLoginBtn" class="btn ghost" type="button">Корпоративный вход</button>
<button id="refreshBtn" class="btn ghost">Обновить всё</button>
</div>
<p class="hint" id="sessionInfo">Токен: отсутствует</p>
</section>
<section class="grid">
<article class="panel reveal" id="users">
<h2>Пользователи</h2>
<div class="inline-form split-two">
<input id="newUsername" placeholder="Логин" value="wave2_admin_demo" />
<select id="newUserRole">
<option value="operator">Оператор</option>
<option value="supervisor">Супервизор</option>
<option value="analyst">Аналитик</option>
<option value="admin">Администратор</option>
</select>
</div>
<div class="inline-form split-two">
<input id="newFullName" placeholder="ФИО" value="Wave 2 Demo User" />
<input id="newPassword" type="password" placeholder="Пароль" value="wave2pass" />
</div>
<div class="actions compact-actions">
<button id="loadUsersBtn" class="btn ghost">Показать пользователей</button>
<button id="createUserBtn" class="btn">Создать пользователя</button>
</div>
<div class="inline-form compact">
<input id="userLookupId" placeholder="ID пользователя для карточки" />
<button id="loadUserDetailBtn" class="btn ghost">Открыть карточку</button>
</div>
<div class="inline-form split-two">
<input id="editUserFullName" placeholder="Новое ФИО" value="Wave 2 Demo User Updated" />
<select id="editUserRole">
<option value="operator">Оператор</option>
<option value="supervisor">Супервизор</option>
<option value="analyst">Аналитик</option>
<option value="admin">Администратор</option>
</select>
</div>
<div class="inline-form compact">
<input id="editUserPassword" type="password" placeholder="Новый пароль (необязательно)" />
<button id="updateUserBtn" class="btn ghost">Обновить пользователя</button>
</div>
<div class="inline-form compact">
<button id="deleteUserBtn" class="btn ghost">Удалить пользователя</button>
</div>
<pre id="userDetailOutput" class="output">Карточка пользователя появится здесь.</pre>
<ul id="usersList" class="list"></ul>
</article>
<article class="panel reveal" id="queues">
<h2>Очереди</h2>
<div class="inline-form split-two">
<input id="queueName" placeholder="Название очереди" value="Демонстрационная очередь" />
<select id="queueChannel">
<option value="voice">Голос</option>
<option value="telegram">Telegram</option>
<option value="webchat">Веб-чат</option>
<option value="email">Электронная почта</option>
</select>
</div>
<div class="inline-form split-two">
<input id="queueDescription" placeholder="Описание" value="Очередь для экрана администратора" />
<input id="queueSla" placeholder="SLA (сек)" type="number" value="45" />
</div>
<div class="actions compact-actions">
<button id="loadQueuesBtn" class="btn ghost">Показать очереди</button>
<button id="createQueueBtn" class="btn">Создать очередь</button>
</div>
<div class="inline-form split-two">
<input id="updateQueueId" placeholder="ID очереди для обновления" />
<select id="updateQueueChannel">
<option value="voice">Голос</option>
<option value="telegram">Telegram</option>
<option value="webchat">Веб-чат</option>
<option value="email">Электронная почта</option>
</select>
</div>
<div class="inline-form split-two">
<input id="updateQueueName" placeholder="Новое имя" value="Демонстрационная очередь обновлена" />
<input id="updateQueueDescription" placeholder="Новое описание" value="Обновлено из экрана администратора" />
</div>
<div class="inline-form compact">
<input id="updateQueueSla" placeholder="Новый SLA (сек)" type="number" value="60" />
<button id="updateQueueBtn" class="btn ghost">Обновить правила</button>
</div>
<div class="inline-form compact">
<button id="deleteQueueBtn" class="btn ghost">Удалить очередь</button>
</div>
<pre id="queueUpdateOutput" class="output">Результат обновления очереди появится здесь.</pre>
<ul id="queuesList" class="list"></ul>
</article>
<article class="panel reveal" id="routes">
<h2>Предпросмотр маршрута</h2>
<div class="inline-form split-two">
<input id="routeQueueId" placeholder="ID очереди" />
<select id="routeChannel">
<option value="voice">Голос</option>
<option value="telegram">Telegram</option>
<option value="webchat">Веб-чат</option>
<option value="email">Электронная почта</option>
</select>
</div>
<div class="inline-form compact">
<input id="routePriority" placeholder="Приоритет" type="number" value="3" />
<button id="previewRouteBtn" class="btn">Рассчитать маршрут</button>
</div>
<pre id="routeOutput" class="output">Результат маршрутизации появится здесь.</pre>
</article>
<article class="panel reveal" id="ivr">
<h2>IVR</h2>
<div class="inline-form split-two">
<input id="ivrName" placeholder="Название сценария" value="Демонстрационный IVR-сценарий" />
<input id="ivrQueueId" placeholder="ID очереди" />
</div>
<div class="inline-form split-two">
<input id="ivrDescription" placeholder="Описание" value="DTMF-сценарий для голосовой маршрутизации" />
<input id="ivrEntryNodeId" placeholder="ID входного узла" value="root" />
</div>
<div class="inline-form compact">
<input id="ivrFlowId" placeholder="ID сценария для просмотра или обновления" />
<label class="checkbox-label"><input id="ivrIsActive" type="checkbox" checked /> Активен</label>
</div>
<div class="row">
<label for="ivrFlowJson">JSON сценария</label>
<textarea id="ivrFlowJson" rows="12" class="json-textarea"></textarea>
</div>
<div class="actions compact-actions">
<button id="loadIvrFlowsBtn" class="btn ghost">Показать сценарии</button>
<button id="createIvrFlowBtn" class="btn">Создать сценарий</button>
<button id="loadIvrFlowDetailBtn" class="btn ghost">Открыть сценарий</button>
<button id="updateIvrFlowBtn" class="btn ghost">Обновить сценарий</button>
<button id="activateIvrFlowBtn" class="btn ghost">Активировать сценарий</button>
</div>
<ul id="ivrFlowsList" class="list"></ul>
<pre id="ivrFlowOutput" class="output">Детали IVR-сценария появятся здесь.</pre>
<div class="inline-form split-two">
<input id="ivrTestCallId" placeholder="ID звонка" value="demo_call_ivr" />
<input id="ivrTestInteractionId" placeholder="ID обращения (необязательно)" />
</div>
<div class="inline-form split-two">
<input id="ivrTestQueueId" placeholder="ID очереди для старта" />
<input id="ivrSessionId" placeholder="ID сессии" />
</div>
<div class="inline-form compact">
<input id="ivrDigit" placeholder="Цифра" value="2" />
<button id="startIvrSessionBtn" class="btn">Запустить сессию</button>
<button id="sendIvrDigitBtn" class="btn ghost">Отправить DTMF</button>
<button id="loadIvrSessionBtn" class="btn ghost">Открыть сессию</button>
<button id="previewIvrRouteBtn" class="btn ghost">Показать IVR-маршрут</button>
</div>
<pre id="ivrSessionOutput" class="output">Состояние IVR-сессии появится здесь.</pre>
<pre id="ivrRouteOutput" class="output">Предпросмотр IVR-маршрута появится здесь.</pre>
</article>
<article class="panel reveal" id="voiceNameCollection">
<h2>Voice AI: сбор имени</h2>
<p class="hint">Глобальная админ-настройка сценария определения имени клиента для voice_start и downstream AI.</p>
<pre id="voiceNameSummary" class="output">Сводка по текущим настройкам появится здесь.</pre>
<div class="inline-form split-two">
<label class="checkbox-label"><input id="voiceNameEnabled" type="checkbox" checked /> Сценарий включён</label>
<label class="checkbox-label"><input id="voiceNameAskOnStart" type="checkbox" checked /> Спрашивать имя на старте</label>
</div>
<div class="inline-form split-two">
<label>
Известный клиент
<select id="voiceNameKnownCustomerBehavior">
<option value="trust_and_handoff">Сразу доверять имени и переводить дальше</option>
<option value="confirm_in_downstream">Передавать имя на подтверждение в downstream</option>
<option value="ask_on_start">Переспрашивать имя на старте</option>
</select>
</label>
<label>
Неизвестный клиент
<select id="voiceNameUnknownCustomerBehavior">
<option value="ask_on_start">Спрашивать имя на старте</option>
<option value="skip_to_downstream">Сразу переводить в downstream</option>
</select>
</label>
</div>
<div class="inline-form split-two">
<label>
Если имя не получено
<select id="voiceNameMissingNameBehavior">
<option value="ask_inline_once">Спросить inline один раз</option>
<option value="do_not_ask">Не спрашивать inline</option>
</select>
</label>
<label>
Если имя неуверенное
<select id="voiceNameUncertainNameBehavior">
<option value="confirm_then_finalize">Подтвердить и только потом финализировать</option>
<option value="finalize_immediately">Считать имя финальным сразу</option>
<option value="discard_and_collect">Сбросить кандидат и собирать заново</option>
</select>
</label>
</div>
<div class="inline-form split-two">
<label class="checkbox-label"><input id="voiceNameFinalizeOnExplicitName" type="checkbox" checked /> Финализировать имя при явном ответе</label>
<label class="checkbox-label"><input id="voiceNameFinalizeOnConfirmation" type="checkbox" checked /> Финализировать имя после подтверждения</label>
</div>
<div class="row">
<label for="voiceNameRuStartPrompt">Тексты RU</label>
<textarea id="voiceNameRuStartPrompt" rows="2" class="json-textarea" placeholder="Стартовый prompt"></textarea>
<textarea id="voiceNameRuPersonalizedGreeting" rows="3" class="json-textarea" placeholder="Персонализированное приветствие с {name}"></textarea>
<textarea id="voiceNameRuConfirmationGreeting" rows="3" class="json-textarea" placeholder="Подтверждение имени с {name}"></textarea>
<textarea id="voiceNameRuInlineFollowup" rows="2" class="json-textarea" placeholder="Inline follow-up"></textarea>
</div>
<div class="row">
<label for="voiceNameKzStartPrompt">Тексты KZ</label>
<textarea id="voiceNameKzStartPrompt" rows="2" class="json-textarea" placeholder="Стартовый prompt"></textarea>
<textarea id="voiceNameKzPersonalizedGreeting" rows="3" class="json-textarea" placeholder="Персонализированное приветствие с {name}"></textarea>
<textarea id="voiceNameKzConfirmationGreeting" rows="3" class="json-textarea" placeholder="Подтверждение имени с {name}"></textarea>
<textarea id="voiceNameKzInlineFollowup" rows="2" class="json-textarea" placeholder="Inline follow-up"></textarea>
</div>
<div class="actions compact-actions">
<button id="loadVoiceNameConfigBtn" class="btn ghost">Загрузить настройки</button>
<button id="saveVoiceNameConfigBtn" class="btn">Сохранить настройки</button>
<button id="resetVoiceNameConfigBtn" class="btn ghost">Сбросить форму к текущим</button>
</div>
<pre id="voiceNameConfigOutput" class="output">Raw payload voice name collection появится здесь.</pre>
</article>
<article class="panel reveal" id="voiceTtsConfig">
<h2>Voice AI: TTS</h2>
<p class="hint">Выберите провайдера и голоса для RU и KZ. Для ElevenLabs в поле голоса хранится voice ID, для Brian уже есть готовый preset.</p>
<pre id="voiceTtsSummary" class="output">Сводка по voice TTS появится здесь.</pre>
<div class="inline-form split-two">
<label>
Активный провайдер
<select id="voiceTtsProvider">
<option value="yandex">Yandex SpeechKit</option>
<option value="elevenlabs">ElevenLabs</option>
<option value="openai">OpenAI</option>
</select>
</label>
<div class="hint">RU и KZ можно настроить отдельно, а провайдер переключать из админки без правки env.</div>
</div>
<div class="row">
<label>Yandex</label>
<div class="inline-form split-two">
<label>
RU preset
<select id="voiceTtsYandexRuVoicePreset"></select>
</label>
<label>
RU voice
<input id="voiceTtsYandexRuVoice" placeholder="jane" />
</label>
</div>
<div class="inline-form split-two">
<label>
KZ preset
<select id="voiceTtsYandexKzVoicePreset"></select>
</label>
<label>
KZ voice
<input id="voiceTtsYandexKzVoice" placeholder="amira" />
</label>
</div>
</div>
<div class="row">
<label>ElevenLabs</label>
<div class="inline-form split-two">
<label>
RU preset
<select id="voiceTtsElevenRuVoicePreset"></select>
</label>
<label>
RU voice ID
<input id="voiceTtsElevenRuVoice" placeholder="nPczCjzI2devNBz1zQrb" />
</label>
</div>
<div class="inline-form split-two">
<label>
RU model
<input id="voiceTtsElevenRuModel" placeholder="eleven_v3" />
</label>
<label>
RU language_code
<input id="voiceTtsElevenRuLanguageCode" placeholder="ru" />
</label>
</div>
<div class="inline-form split-two">
<label>
KZ preset
<select id="voiceTtsElevenKzVoicePreset"></select>
</label>
<label>
KZ voice ID
<input id="voiceTtsElevenKzVoice" placeholder="nPczCjzI2devNBz1zQrb" />
</label>
</div>
<div class="inline-form split-two">
<label>
KZ model
<input id="voiceTtsElevenKzModel" placeholder="eleven_v3" />
</label>
<label>
KZ language_code
<input id="voiceTtsElevenKzLanguageCode" placeholder="kk" />
</label>
</div>
</div>
<div class="row">
<label>OpenAI</label>
<div class="inline-form split-two">
<label>
RU preset
<select id="voiceTtsOpenAiRuVoicePreset"></select>
</label>
<label>
RU voice
<input id="voiceTtsOpenAiRuVoice" placeholder="alloy" />
</label>
</div>
<div class="inline-form split-two">
<label>
RU model
<input id="voiceTtsOpenAiRuModel" placeholder="gpt-4o-mini-tts" />
</label>
<label>
RU language_code
<input id="voiceTtsOpenAiRuLanguageCode" placeholder="ru" />
</label>
</div>
<div class="inline-form split-two">
<label>
KZ preset
<select id="voiceTtsOpenAiKzVoicePreset"></select>
</label>
<label>
KZ voice
<input id="voiceTtsOpenAiKzVoice" placeholder="alloy" />
</label>
</div>
<div class="inline-form split-two">
<label>
KZ model
<input id="voiceTtsOpenAiKzModel" placeholder="gpt-4o-mini-tts" />
</label>
<label>
KZ language_code
<input id="voiceTtsOpenAiKzLanguageCode" placeholder="kz" />
</label>
</div>
</div>
<div class="actions compact-actions">
<button id="loadVoiceTtsConfigBtn" class="btn ghost">Загрузить настройки</button>
<button id="saveVoiceTtsConfigBtn" class="btn">Сохранить настройки</button>
<button id="resetVoiceTtsConfigBtn" class="btn ghost">Сбросить форму к текущим</button>
</div>
<pre id="voiceTtsConfigOutput" class="output">Raw payload voice TTS появится здесь.</pre>
</article>
<article class="panel reveal" id="asterisk">
<h2>Мост Asterisk</h2>
<p class="hint">Диагностика AMI-подключения, пересланных bridge-событий и ручного повтора.</p>
<div class="actions compact-actions">
<button id="loadAsteriskStatusBtn" class="btn">Показать статус</button>
<button id="loadAsteriskEventsBtn" class="btn ghost">Показать события</button>
</div>
<div class="inline-form split-two">
<select id="asteriskEventStatus">
<option value="">Все статусы</option>
<option value="received">Получено</option>
<option value="forwarded">Переслано</option>
<option value="failed">Ошибка</option>
</select>
<input id="asteriskBridgeEventId" placeholder="ID bridge-события для просмотра или повтора" />
</div>
<div class="actions compact-actions">
<button id="loadAsteriskEventDetailBtn" class="btn ghost">Открыть событие</button>
<button id="retryAsteriskEventBtn" class="btn ghost">Повторить событие</button>
<button id="reconnectAsteriskBtn" class="btn ghost">Переподключить AMI</button>
</div>
<pre id="asteriskStatusOutput" class="output">Статус моста Asterisk появится здесь.</pre>
<ul id="asteriskEventsList" class="list"></ul>
<pre id="asteriskEventOutput" class="output">Детали bridge-события Asterisk появятся здесь.</pre>
</article>
<article class="panel reveal">
<h2>Лог действий</h2>
<p class="hint">Сверху всегда последнее действие администратора.</p>
<pre id="eventLog" class="output tall"></pre>
</article>
</section>
</main>
</section>
</div>
<script src="/admin/assets/app.js?v=track35-admin-ru-polish"></script>
</body>
</html>