<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SaaS Панель Управления ИИ-Катей</title>
<style>
body { font-family: Arial, sans-serif; background: #fff5f8; margin: 0; padding: 0; color: #333; }
.auth-container { max-width: 400px; margin: 80px auto; background: white; padding: 30px; border-radius: 16px; box-shadow: 0 4px 25px rgba(0,0,0,0.08); border: 1px solid #ffd1dc; text-align: center; display: block; }
.panel-container { display: none; max-width: 1100px; margin: 30px auto; background: white; padding: 25px; border-radius: 16px; box-shadow: 0 4px 25px rgba(0,0,0,0.08); border: 1px solid #ffd1dc; }
h2 { color: #a91e63; margin-bottom: 20px; font-size: 24px; }
.logo { font-size: 40px; margin-bottom: 10px; display: block; }
.input-group { text-align: left; margin-bottom: 15px; }
.checkbox-group { display: flex; gap: 20px; margin-bottom: 15px; background: #fff5f8; padding: 12px; border-radius: 8px; border: 1px solid #ffe5ec; }
.checkbox-item { display: flex; align-items: center; gap: 8px; font-size: 13px; font-weight: bold; color: #555; cursor: pointer; }
label { font-size: 13px; font-weight: bold; color: #555; display: block; margin-bottom: 5px; }
input[type="text"], input[type="email"], input[type="password"], select, textarea { width: 100%; padding: 12px; border: 1px solid #ddd; border-radius: 8px; font-size: 14px; box-sizing: border-box; outline: none; }
input:focus, textarea:focus { border-color: #a91e63; }
.btn { width: 100%; background: #ff69b4; color: white; border: none; padding: 14px; border-radius: 8px; font-size: 15px; font-weight: bold; cursor: pointer; transition: background 0.2s; margin-top: 10px; }
.btn:hover { background: #a91e63; }
.error-msg { background: #fff0f3; color: #d63031; border: 1px solid #ffd1dc; padding: 10px; border-radius: 8px; font-size: 13px; margin-bottom: 15px; display: none; text-align: left; }
/* СТИЛИ ОФИЦИАЛЬНЫХ ВКЛАДОК */
.tabs { display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 20px; border-bottom: 2px solid #ffe5ec; padding-bottom: 10px; }
.tab-btn { background: none; border: none; padding: 10px 15px; font-size: 13px; font-weight: bold; color: #aaa; cursor: pointer; border-radius: 6px; }
.tab-btn:hover { background: #fff0f3; color: #a91e63; }
.tab-btn.active { color: white; background: #a91e63; }
.tab-content { display: none; }
.tab-content.active { display: block; }
/* ТАБЛИЦЫ */
.price-table { width: 100%; border-collapse: collapse; margin-bottom: 20px; }
.price-table th, .price-table td { padding: 12px; border: 1px solid #ffe5ec; text-align: left; font-size: 14px; }
.price-table th { background: #fff5f8; color: #a91e63; }
.logout-link { float: right; color: #aaa; font-size: 13px; text-decoration: none; margin-top: 5px; }
.logout-link:hover { color: red; }
.status-text { font-size: 13px; font-weight: bold; color: #a91e63; margin-top: 10px; display: inline-block; }
/* БЛОК ДИАЛОГОВЫХ ОКНО ОКРЕМЫХ ЧАТОВ (МЕНЕДЖЕР-ЧЕЛОВЕК) */
.chat-layout { display: flex; gap: 20px; height: 500px; border: 1px solid #ffe5ec; border-radius: 12px; overflow: hidden; background: #fafafa; }
.chat-sessions-list { width: 30%; background: white; border-right: 1px solid #ffe5ec; overflow-y: auto; padding: 10px; }
.session-item { padding: 12px; border-radius: 8px; margin-bottom: 8px; background: #fff5f8; cursor: pointer; font-size: 13px; border: 1px solid #ffe5ec; transition: background 0.2s; }
.session-item:hover, .session-item.active { background: #ffe5ec; color: #a91e63; font-weight: bold; }
.chat-window-box { width: 70%; display: flex; flex-direction: column; background: white; }
.chat-messages-area { flex-grow: 1; padding: 15px; overflow-y: auto; background: #fffbfd; display: flex; flex-direction: column; gap: 10px; }
.msg-bubble { max-width: 70%; padding: 10px 14px; border-radius: 12px; font-size: 13px; line-height: 1.4; }
.msg-user { background: #ffe5ec; align-self: flex-start; color: #333; }
.msg-bot { background: #fff0f3; border: 1px solid #ffd1dc; align-self: flex-end; color: #a91e63; }
.msg-manager { background: #2ecc71; color: white; align-self: flex-end; }
.chat-input-row { padding: 10px; background: white; border-top: 1px solid #ffe5ec; display: flex; gap: 10px; }
/* ВИДЖЕТЫ ПРОФИЛЯ И КОДА */
.profile-card { background: #fffbfd; border: 1px solid #ffd1dc; padding: 20px; border-radius: 12px; margin-bottom: 20px; }
.code-box { background: #2d3436; color: #2ecc71; padding: 15px; border-radius: 8px; font-family: monospace; font-size: 12px; overflow-x: auto; white-space: pre-wrap; user-select: all; }
</style>
</head>
<body>
<!-- БЛОК АВТОРИЗАЦИИ (РЕЗЕРВНЫЙ) -->
<div class="auth-container" id="authBlock">
<span class="logo">🎈</span>
<h2>Вход в SaaS Панель</h2>
<div class="error-msg" id="login-error"></div>
<div class="input-group"><label>Email</label><input type="email" id="login-email" value="sktvl84@gmail.com"></div>
<div class="input-group"><label>Пароль</label><input type="password" id="login-password" value="123456"></div>
<button class="btn" onclick="loginUser()">Войти</button>
</div>
<!-- ГЛАВНАЯ SaaS ПАНЕЛЬ -->
<div class="panel-container" id="panelBlock">
<a href="#" class="logout-link" onclick="logoutUser()">Выйти из системы ✕</a>
<h2>Панель Управления Платформой | ИИ-Катя</h2>
<!-- ВСЕ ОФИЦИАЛЬНЫЕ ВКЛАДОК КЛИЕНТА -->
<div class="tabs">
<button class="tab-btn active" onclick="switchTab('price-tab', this)">Поштучный Прайс CRM</button>
<button class="tab-btn" onclick="switchTab('yml-tab', this)">Выгрузка YML Товаров</button>
<button class="tab-btn" onclick="switchTab('settings-tab', this)">Настройки Бота</button>
<button class="tab-btn" onclick="switchTab('widget-code-tab', this)">Код для Tilda</button>
<button class="tab-btn" onclick="switchTab('training-tab', this)">Обучение Кати (База Знаний)</button>
<button class="tab-btn" onclick="switchTab('logs-tab', this)">Диалоговые Окна (Живой Чат)</button>
<button class="tab-btn" onclick="switchTab('profile-tab', this)">Профиль и Подписка</button>
</div>
<!-- ВКЛАДКА 1: ПОШТУЧНЫЙ ПРАЙС ИЗ ТАБЛИЦЫ ПРАЙС_CRM -->
<div class="tab-content active" id="price-tab">
<p style="font-size: 13px; color: #666; margin-bottom: 15px;">Данные подгружаются в реальном времени из вкладки <strong>Прайс_CRM</strong> вашей таблицы клиента. Вы можете изменить цены прямо здесь.</p>
<div id="price-status" class="status-text"></div>
<table class="price-table">
<thead>
<tr>
<th>Наименование товара / категории</th>
<th style="width: 180px;">Цена студии (руб.)</th>
</tr>
</thead>
<tbody id="priceTableBody">
<tr><td colspan="2" style="text-align:center; color:#aaa;">⏳ Загрузка прайс-листа из вашей CRM таблицы...</td></tr>
</tbody>
</table>
<button class="btn" onclick="savePriceToCloud()" style="background: #2ecc71;">Сохранить изменения в Прайс_CRM</button>
</div>
<!-- ВКЛАДКА 2: ССЫЛКА И ВЫГРУЗКА YML КАТАЛОГА -->
<div class="tab-content" id="yml-tab">
<div id="yml-status" class="status-text"></div>
<div class="profile-card">
<h3>Интеграция с каталогом витрины сайта</h3>
<p style="font-size: 13px; color: #666;">Вставьте сюда вашу ссылку на YML-экспорт товаров из панели Tilda. Катя автоматически изучит все готовые наборы шаров.</p>
<div class="input-group">
<label>Ссылка на YML-файл каталога товаров</label>
<input type="text" id="yml-url" placeholder="https://tilda.ws...">
</div>
<button class="btn" onclick="syncYmlCatalog()" style="background: #a91e63; width: auto; padding: 12px 25px;">Запустить синхронизацию товаров</button>
</div>
</div>
<!-- ВКЛАДКА 3: НАСТРОЙКИ + ТУМБЛЕРЫ МЕССЕНДЖЕРОВ -->
<div class="tab-content" id="settings-tab">
<div id="settings-status" class="status-text"></div>
<div class="input-group" style="margin-top: 10px;">
<label>Фирменный цвет виджета чата</label>
<input type="color" id="widget-color" style="width:80px; padding:2px; height:40px;" value="#ff69b4">
</div>
<label>Интерактивные кнопки мессенджеров под приветствием</label>
<div class="checkbox-group">
<label class="checkbox-item"><input type="checkbox" id="show-wa" checked> Показывать кнопку WhatsApp</label>
<label class="checkbox-item"><input type="checkbox" id="show-tg" checked> Показывать кнопку Telegram</label>
</div>
<div class="input-group">
<label>Приветственный текст Кати на сайте</label>
<textarea id="bot-welcome" rows="3" placeholder="Привет! ✨ Я ИИ-менеджер..."></textarea>
</div>
<div class="input-group">
<label>Адрес студии (самовывоз)</label>
<input type="text" id="bot-address" placeholder="г. Владивосток, ул...">
</div>
<div class="input-group">
<label>Режим работы</label>
<input type="text" id="bot-time" placeholder="9:00 - 21:00">
</div>
<div class="input-group">
<label>Телефон студии</label>
<input type="text" id="bot-phone" placeholder="+7 (999) ...">
</div>
<div class="input-group">
<label>Ссылка на WhatsApp</label>
<input type="text" id="bot-whatsapp" placeholder="https://wa.me...">
</div>
<div class="input-group">
<label>Ссылка на Telegram</label>
<input type="text" id="bot-telegram" placeholder="https://t.me...">
</div>
<div class="input-group">
<label>Условия и тарифы доставки</label>
<textarea id="bot-delivery" rows="2" placeholder="Доставка по городу..."></textarea>
</div>
<button class="btn" onclick="saveMainSettings()">Сохранить конфигурацию Кати</button>
</div>
<!-- ВКЛАДКА 4: ГЕНЕРАТОР КОДА ВИДЖЕТА ДЛЯ ТИЛЬДЫ -->
<div class="tab-content" id="widget-code-tab">
<div class="profile-card">
<h3>Установка ИИ-виджета на ваш сайт</h3>
<p style="font-size: 13px; color: #666; margin-bottom: 15px;">Скопируйте этот код и вставьте его в блок <strong>T123 (HTML-код)</strong> на вашем сайте в Tilda. Катя мгновенно появится на страницах и начнет продавать шары.</p>
<div class="code-box" id="tilda-code-block">// Загрузка кода...</div>
</div>
</div>
<!-- ВКЛАДКА: ОБУЧЕНИЕ ИИ И КОРРЕКТИРОВКА ХАРАКТЕРА -->
<div class="tab-content" id="training-tab">
<div id="training-status" class="status-text"></div>
<div class="profile-card">
<h3>Инструкции для ИИ-менеджера</h3>
<p style="font-size: 13px; color: #666; margin-bottom: 15px;">Пропишите здесь правила общения, скрипты продаж и важные нюансы вашей студии. Катя будет строго соблюдать эти указания при ответах клиентам.</p>
<div class="input-group">
<label>Глобальные правила и инструкции (Характер Кати)</label>
<textarea id="ai-instructions" rows="6" placeholder="Например: Всегда предлагай фонтан из шаров к цифрам. Отвечай вежливо. Если клиент сомневается, предложи скидку 5%..."></textarea>
</div>
<button class="btn" onclick="saveAiInstructions()" style="background: #a91e63; width: auto; padding: 12px 25px;">Сохранить инструкции в Базу Знаний</button>
</div>
<div class="profile-card">
<h3>🎭 Интерактивные сценарии диалогов и работы по фото</h3>
<p style="font-size: 13px; color: #666; margin-bottom: 10px;">
Покажите Кате на живых примерах, как вести диалог, как реагировать на сомнения и как работать с фотографиями-референсами от клиентов. Катя поймет логику и будет гибко применять её в продажах.
</p>
<table class="price-table">
<thead>
<tr>
<th style="width: 45%;">Что пишет или присылает Клиент (Действие)</th>
<th style="width: 55%;">Как гибко реагирует и продает Менеджер (Сценарий)</th>
</tr>
</thead>
<tbody id="trainingTableBody">
<tr id="empty-training-row">
<td colspan="2" style="text-align:center; color:#aaa; font-style:italic;">
Сценариев обучения пока нет. Нажмите кнопку ниже, чтобы заложить первый кейс!
</td>
</tr>
</tbody>
</table>
<button class="btn" onclick="addNewTrainingRow()" style="background: #ff69b4; width: auto; padding: 10px 20px; margin-bottom: 15px; display: inline-block;">+ Добавить кейс / Сценарий диалога</button>
<button class="btn" onclick="saveTrainingRowsToCloud()" style="background: #2ecc71; width: auto; padding: 10px 20px; margin-bottom: 15px; display: inline-block; margin-left: 10px;">Сохранить сценарии в память ИИ</button>
</div>
<!-- НОВЫЙ МОДУЛЬ: КОРРЕКТИРОВКА ОШИБОК И РАЗБОР ДИАЛОГОВ -->
<div class="profile-card" style="margin-top: 25px; border-color: #d63031; background: #fff5f6;">
<h3 style="color: #d63031;">🛑 Корректировка ошибок и Работа над косяками</h3>
<p style="font-size: 13px; color: #555; margin-bottom: 15px;">Заметили, что Катя ошиблась в диалоге на сайте? Вставьте фрагмент её косяка сюда, укажите в чем ошибка, и пропишите, как нужно было ответить правильно. ИИ больше никогда не повторит эту ошибку!</p>
<div id="error-log-status" class="status-text" style="color: #d63031;"></div>
<div style="display: flex; gap: 15px; margin-bottom: 15px;">
<div style="width: 50%;">
<label style="color: #d63031;">Сбойный диалог (Что написал клиент + Что не так ответила Катя)</label>
<textarea id="err-bad-dialog" rows="4" placeholder="Клиент: Сколько стоит Синий Трактор?\nКатя: Ой, у нас нет такой фигуры (Хотя она есть за 1450 руб)"></textarea>
</div>
<div style="width: 50%;">
<label style="color: #2ecc71;">В чём ошибка + Как отвечать ПРАВИЛЬНО</label>
<textarea id="err-good-fix" rows="4" placeholder="Ошибка: Сказала что нет фигуры.\nПравильно: Фигура Синий Трактор всегда в наличии, цена 1450 рублей, код товара из прайса..."></textarea>
</div>
</div>
<button class="btn" onclick="sendErrorCorrectionToCloud()" style="background: #d63031; width: auto; padding: 12px 25px;">Запечатать исправление ошибки в память ИИ</button>
</div>
</div>
<!-- ВКЛАДКА 5: ИНТЕРАКТИВНЫЕ ДИАЛОГОВЫЕ ОКНА (ЖИВОЙ ЧАТ) -->
<div class="tab-content" id="logs-tab">
<p style="font-size: 13px; color: #666; margin-bottom: 15px;">Здесь отображаются живые сессии клиентов с вашего сайта. Менеджер-человек может написать сообщение и перехватить диалог у ИИ-Кати.</p>
<div class="chat-layout">
<div class="chat-sessions-list" id="sessionsList">
<div style="text-align:center; color:#aaa; font-style:italic; padding-top:20px;">Нет активных чатов.</div>
</div>
<div class="chat-window-box">
<div class="chat-messages-area" id="chatMessagesArea">
<div style="text-align:center; color:#aaa; font-style:italic; padding-top:100px;">Выберите чат-сессию слева для просмотра диалога.</div>
</div>
<!-- МОДЕРНИЗИРОВАННЫЙ БЛОК ВВОДА С ПОДДЕРЖКОЙ ФОТОГРАФИЙ -->
<div class="chat-input-row" style="position: relative; align-items: center; display: flex; gap: 10px; padding: 10px; background: white; border-top: 1px solid #ffe5ec;">
<!-- Скрытый системный элемент загрузки файлов -->
<input type="file" id="managerFileInput" accept="image/*" style="display: none;" onchange="handleManagerFileSelect(this)">
<!-- Кнопка-скрепка для выбора картинок -->
<button onclick="document.getElementById('managerFileInput').click()" id="clipBtn" style="background: none; border: none; font-size: 20px; cursor: pointer; padding: 0 5px;" disabled>📎</button>
<!-- Поле ввода текста с поддержкой Ctrl+V -->
<input type="text" id="managerMessageInput" placeholder="Введите ответ от лица менеджера или вставьте фото (Ctrl+V)..." onpaste="handleManagerPaste(event)" disabled>
<button class="btn" id="sendManagerBtn" onclick="sendManagerMessage()" style="width:120px; margin:0;" disabled>Отправить</button>
</div>
<!-- Индикатор прикрепленного файла перед отправкой -->
<div id="file-preview-container" style="display: none; padding: 5px 15px; background: #fff0f3; border-top: 1px solid #ffe5ec; font-size: 12px; color: #a91e63; align-items: center; gap: 10px;">
<span id="preview-file-name">📎 Файл готов к отправке</span>
<span onclick="clearSelectedManagerFile()" style="cursor: pointer; font-weight: bold; color: red;">✕ Удалить</span>
</div>
</div>
</div>
</div>
<!-- ВКЛАДКА 6: ПРОФИЛЬ, БИЛЛИНГ И СМЕНА ПАРОЛЯ -->
<div class="tab-content" id="profile-tab">
<div id="profile-status" class="status-text"></div>
<div class="profile-card">
<h3>Параметры подписки и Баланс</h3>
<p>📊 Текущий коммерческий тариф: <strong id="profile-status-sub" style="color:#a91e63;">Демо-период</strong></p>
<p>📅 Действует до: <strong id="profile-expire-date">--.--.----</strong></p>
<p>💰 Доступный баланс платформы: <strong id="profile-balance" style="color:#2ecc71;">0.00 руб.</strong></p>
<p style="font-size: 11px; color: #888;">* Списание за текстовые сообщения: 2.00 руб. | За анализ фотографий-референсов (Vision): 5.00 руб.</p>
</div>
<div class="profile-card">
<h3>Безопасность: Смена пароля</h3>
<div class="input-group">
<label>Новый пароль аккаунта</label>
<input type="password" id="new-password" placeholder="••••••••">
</div>
<button class="btn" onclick="changeAccountPassword()" style="background:#34495e; width:auto; padding:12px 25px;">Обновить пароль в Реестре</button>
</div>
</div>
</div>
<script>
const gateUrl = "ai-gate.php";
let publicToken = localStorage.getItem('saas_public_token') || "";
let activeChatSessionId = null;
window.onload = function() {
if (publicToken !== "") {
document.getElementById('authBlock').style.display = "none";
document.getElementById('panelBlock').style.display = "block";
loadDashboardData();
generateTildaCode();
}
};
function loginUser() {
const email = document.getElementById('login-email').value.trim();
const pass = document.getElementById('login-password').value.trim();
const errorBox = document.getElementById('login-error');
errorBox.innerText = "⏳ Авторизация..."; errorBox.style.display = "block";
fetch(gateUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ action: "login", email: email, password: pass })
})
.then(res => res.json())
.then(data => {
if (data.success && data.token) {
localStorage.setItem('saas_public_token', data.token);
window.location.reload();
} else {
errorBox.innerText = "❌ Ошибка: " + (data.error || "Неверные данные");
}
});
}
function generateTildaCode() {
const scriptUrl = window.location.origin + "/widget.js";
document.getElementById('tilda-code-block').innerText =
`<script>
window.neuroBalloonToken = "${publicToken}";
(function() {
var s = document.createElement('script');
s.type = 'text/javascript'; s.async = true;
s.src = "${scriptUrl}?v=" + Date.now();
var x = document.getElementsByTagName('script')[0];
x.parentNode.insertBefore(s, x);
})();
<\/script>`;
}
// ЖИВАЯ ВЫГРУЗКА ИЗ ПРАЙС_CRM КЛИЕНТА
function loadDashboardData() {
fetch(gateUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ action: "load_dashboard", public_token: publicToken })
})
.then(res => res.json())
.then(data => {
if (data.success) {
document.getElementById('widget-color').value = data.color || "#ff69b4";
document.getElementById('bot-welcome').value = data.welcome_text || "";
document.getElementById('bot-address').value = data.address || "";
document.getElementById('bot-time').value = data.work_time || "";
document.getElementById('bot-phone').value = data.phone || "";
document.getElementById('bot-whatsapp').value = data.whatsapp || "";
document.getElementById('bot-telegram').value = data.telegram || "";
document.getElementById('bot-delivery').value = data.delivery_rates || "";
document.getElementById('yml-url').value = data.yml_url || "";
document.getElementById('show-wa').checked = data.show_wa !== false;
document.getElementById('show-tg').checked = data.show_tg !== false;
if(data.status) document.getElementById('profile-status-sub').innerText = data.status;
if(data.expire_date) document.getElementById('profile-expire-date').innerText = data.expire_date;
if(data.balance !== undefined) document.getElementById('profile-balance').innerText = data.balance + " руб.";
// 1. ОТРИСОВКА ЖИВОГО ПРАЙСА ИЗ CRM
const tbodyPrice = document.getElementById('priceTableBody');
tbodyPrice.innerHTML = "";
if (data.priceList && data.priceList.length > 0) {
data.priceList.forEach(item => {
tbodyPrice.innerHTML += `
<tr>
<td><strong>${item.name}</strong></td>
<td><input type="text" class="price-input" data-name="${item.name}" value="${item.price}" style="padding:8px;"></td>
</tr>`;
});
} else {
tbodyPrice.innerHTML = `<tr><td colspan="2" style="text-align:center; color:#666;">Лист Прайс_CRM пуст. Заполните его в Google таблице.</td></tr>`;
}
// 2. ИСПРАВЛЕНО: ОТРИСОВКА ЖИВЫХ ПРИМЕРОВ ОБУЧЕНИЯ ИЗ AI_ПРИМЕРЫ
renderTrainingRows(data.trainingList || []);
// 3. ОТРИСОВКА ИНТЕРАКТИВНЫХ СЕССИЙ ЧАТОВ
renderChatSessions(data.chatSessions || []);
}
});
}
function savePriceToCloud() {
const status = document.getElementById('price-status');
status.innerText = "⏳ Сохранение изменений в Прайс_CRM...";
const inputs = document.querySelectorAll('.price-input');
const updatedPrice = [];
inputs.forEach(input => {
updatedPrice.push({ name: input.getAttribute('data-name'), price: input.value.trim() });
});
fetch(gateUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ action: "save_price", public_token: publicToken, priceData: updatedPrice })
})
.then(res => res.json())
.then(data => {
if(data.success) status.innerText = "✅ Изменения успешно сохранены в Прайс_CRM вашей таблицы!";
});
}
function saveMainSettings() {
const status = document.getElementById('settings-status');
status.innerText = "⏳ Сохранение настроек...";
const payload = {
action: "save_settings",
public_token: publicToken,
color: document.getElementById('widget-color').value,
welcome_text: document.getElementById('bot-welcome').value.trim(),
address: document.getElementById('bot-address').value.trim(),
work_time: document.getElementById('bot-time').value.trim(),
phone: document.getElementById('bot-phone').value.trim(),
whatsapp: document.getElementById('bot-whatsapp').value.trim(),
telegram: document.getElementById('bot-telegram').value.trim(),
delivery_rates: document.getElementById('bot-delivery').value.trim(),
show_wa: document.getElementById('show-wa').checked,
show_tg: document.getElementById('show-tg').checked
};
fetch(gateUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
})
.then(res => res.json())
.then(data => {
if(data.success) status.innerText = "✅ Конфигурация Кати сохранена!";
});
}
function syncYmlCatalog() {
const status = document.getElementById('yml-status');
status.innerText = "⏳ Парсинг YML витрины сайта и выгрузка товаров в Каталог_Tilda...";
const url = document.getElementById('yml-url').value.trim();
fetch(gateUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ action: "sync_yml", public_token: publicToken, yml_url: url })
})
.then(res => res.json())
.then(data => {
if(data.success) status.innerText = "✅ Выгрузка товаров завершена! Катя изучила готовые наборы.";
});
}
function changeAccountPassword() {
const status = document.getElementById('profile-status');
const pass = document.getElementById('new-password').value.trim();
if(!pass) return;
status.innerText = "⏳ Смена пароля...";
fetch(gateUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ action: "change_password", public_token: publicToken, new_pass: pass })
})
.then(res => res.json())
.then(data => {
if(data.success) status.innerText = "✅ Пароль успешно изменен в Реестре!";
});
}
// ИНТЕРАКТИВНЫЕ ДИАЛОГОВЫЕ ОКНА (ЛОГГЕР С ПЕРЕХВАТОМ ЧЕЛОВЕКОМ)
function renderChatSessions(sessions) {
const listContainer = document.getElementById('sessionsList');
if(!sessions || sessions.length === 0) return;
listContainer.innerHTML = "";
sessions.forEach(sess => {
const item = document.createElement('div');
item.className = "session-item";
item.innerText = "💬 Клиент (" + sess.chatId.substring(0,8) + "...)";
item.onclick = function() { loadActiveSessionMessages(sess.chatId, item, sess.messages); };
listContainer.appendChild(item);
});
}
let selectedManagerImageBase64 = "";
// 1. ИСПРАВЛЕНО: Функция визуализации пузырей сообщений с поддержкой картинок!
function loadActiveSessionMessages(chatId, element, messages) {
activeChatSessionId = chatId;
document.querySelectorAll('.session-item').forEach(i => i.classList.remove('active'));
element.classList.add('active');
document.getElementById('managerMessageInput').disabled = false;
document.getElementById('sendManagerBtn').disabled = false;
document.getElementById('clipBtn').disabled = false;
const area = document.getElementById('chatMessagesArea');
area.innerHTML = "";
messages.forEach(msg => {
const bubble = document.createElement('div');
bubble.className = "msg-bubble " + (msg.sender === "client" ? "msg-user" : (msg.sender === "manager" ? "msg-manager" : "msg-bot"));
let contentHtml = "<strong>" + (msg.sender === "client" ? "Клиент" : (msg.sender === "manager" ? "Менеджер" : "Катя (ИИ)")) + ":</strong><br>";
// Регулярное выражение для поиска Base64 картинок или стандартных URL изображений
const rawText = msg.text || "";
const imageRegex = /(data:image\/[a-zA-Z]+;base64,[^\s]+|https?:\/\/[^\s]+(?:\.png|\.jpg|\.jpeg|\.gif))/gi;
const matches = rawText.match(imageRegex);
if (matches && matches.length > 0) {
matches.forEach(imgUrl => {
contentHtml += `<img src="${imgUrl}" style="max-width: 100%; max-height: 180px; border-radius: 8px; margin-top: 5px; display: block; cursor: pointer; border: 1px solid #ffe5ec;" onclick="window.open('${imgUrl}')">`;
});
// Выводим текст сообщения, очищенный от длинного кода картинки, чтобы не захламлять чат
const cleanText = rawText.replace(imageRegex, "").trim();
if (cleanText !== "") {
contentHtml += `<span style="display:block; margin-top:5px;">${cleanText}</span>`;
}
} else {
contentHtml += rawText !== "" ? rawText : "📎 [Фото референса]";
}
bubble.innerHTML = contentHtml;
area.appendChild(bubble);
});
area.scrollTop = area.scrollHeight;
}
// 2. Функция обработки перехвата картинок через Ctrl+V (Вставка из буфера обмена)
function handleManagerPaste(e) {
const items = (e.clipboardData || e.originalEvent.clipboardData).items;
for (let i = 0; i < items.length; i++) {
if (items[i].type.indexOf("image") !== -1) {
const blob = items[i].getAsFile();
convertManagerFileToBase64(blob, "pasted_image.png");
}
}
}
// 3. Функция обработки выбора файла через классическую скрепку
function handleManagerFileSelect(input) {
if (input.files && input.files[0]) {
convertManagerFileToBase64(input.files[0], input.files[0].name);
}
}
// Внутренний конвертер картинок в Base64 поток для трансляции через шлюз
function convertManagerFileToBase64(file, filename) {
const reader = new FileReader();
reader.onload = function(e) {
selectedManagerImageBase64 = e.target.result;
document.getElementById('file-preview-container').style.display = "flex";
document.getElementById('preview-file-name').innerText = "📎 Изображение прикреплено (" + filename + ")";
};
reader.readAsDataURL(file);
}
function clearSelectedManagerFile() {
selectedManagerImageBase64 = "";
document.getElementById('managerFileInput').value = "";
document.getElementById('file-preview-container').style.display = "none";
}
// 4. ИСПРАВЛЕНО: Функция отправки ответа менеджера с поддержкой текста ИЛИ картинок!
function sendManagerMessage() {
const input = document.getElementById('managerMessageInput');
const text = input.value.trim();
if (!text && !selectedManagerImageBase64 || !activeChatSessionId) return;
const area = document.getElementById('chatMessagesArea');
const bubble = document.createElement('div');
bubble.className = "msg-bubble msg-manager";
let managerLogText = text;
let bubbleHtml = "<strong>Менеджер:</strong><br>";
if (selectedManagerImageBase64) {
bubbleHtml += `<img src="${selectedManagerImageBase64}" style="max-width: 100%; max-height: 150px; border-radius: 8px; display: block; margin-top:5px;">`;
// Склеиваем Base64 поток и текст ответа в одну строку для логгера CRM
managerLogText = selectedManagerImageBase64 + (text ? " " + text : "");
}
if (text) {
bubbleHtml += `<span style="display:block; margin-top:5px;">${text}</span>`;
}
bubble.innerHTML = bubbleHtml;
area.appendChild(bubble);
input.value = "";
clearSelectedManagerFile();
area.scrollTop = area.scrollHeight;
// Отправляем пакет на шлюз: Катя перехватит его, запишет в CRM и мгновенно перешлет клиенту на сайт!
fetch(gateUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
action: "manager_reply",
public_token: publicToken,
chat_id: activeChatSessionId,
message: managerLogText
})
});
}
// Сохранение текстовых инструкций обучения
function saveAiInstructions() {
const status = document.getElementById('training-status');
status.innerText = "⏳ Сохранение инструкций в память ИИ...";
const text = document.getElementById('ai-instructions').value.trim();
fetch(gateUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ action: "save_instructions", public_token: publicToken, instructions: text })
})
.then(res => res.json())
.then(data => { if(data.success) status.innerText = "✅ База знаний Кати успешно обновлена!"; });
}
// Отправка разбора ошибок в CRM-базу данных клиента
function sendErrorCorrectionToCloud() {
const status = document.getElementById('error-log-status');
const badDialog = document.getElementById('err-bad-dialog').value.trim();
const goodFix = document.getElementById('err-good-fix').value.trim();
if (!badDialog || !goodFix) {
alert("⚠️ Пожалуйста, заполните оба поля: и сбойный диалог, и правильное исправление!");
return;
}
status.innerText = "⏳ Вшивание исправления в нейросетевую память...";
fetch(gateUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
action: "save_error_fix",
public_token: publicToken,
bad_dialog: badDialog,
good_fix: goodFix
})
})
.then(res => res.json())
.then(data => {
if(data.success) {
status.innerText = "✅ Исправление зафиксировано! Катя провела работу над ошибками.";
document.getElementById('err-bad-dialog').value = "";
document.getElementById('err-good-fix').value = "";
}
});
}
// Динамическая генерация сетки Вопрос-Ответ на экране ЛК
function renderTrainingRows(examples) {
const tbody = document.getElementById('trainingTableBody');
tbody.innerHTML = "";
if (!examples || examples.length === 0) {
tbody.innerHTML = `<tr id="empty-training-row"><td colspan="2" style="text-align:center; color:#aaa; font-style:italic;">Обучающих примеров пока нет. Нажмите кнопку выше, чтобы добавить первый пример!</td></tr>`;
return;
}
examples.forEach(item => {
tbody.innerHTML += `
<tr class="training-row-item">
<td><input type="text" class="trigger-input" value="${item.trigger || ''}" placeholder="Например: Какое время доставки?" style="padding:8px; font-weight:bold;"></td>
<td><textarea class="reply-input" placeholder="Например: Доставка работает круглосуточно..." style="padding:8px; width:100%; height:50px; resize:vertical;">${item.reply || ''}</textarea></td>
</tr>`;
});
}
function addNewTrainingRow() {
const tbody = document.getElementById('trainingTableBody');
const emptyRow = document.getElementById('empty-training-row');
if (emptyRow) emptyRow.remove();
const newTr = document.createElement('tr');
newTr.className = "training-row-item";
newTr.innerHTML = `
<td>
<textarea class="trigger-input" rows="3" placeholder="Пример:\n[Клиент присылает фото нежно-розового фонтана] или спрашивает:\n'Привет! Хочу точно такой же сет на годик девочке, сколько стоит?'" style="padding:8px; width:100%; box-sizing:border-box; font-weight:bold;"></textarea>
</td>
<td>
<textarea class="reply-input" rows="3" placeholder="Сценарий поведения Кати:\n1. Оценить состав по фото (цифра + 5 латексных + 2 с конфетти).\n2. Рассчитать цену по Прайс_CRM.\n3. Спросить про дату и мягко предложить добавить баблс с перьями для вау-эффекта." style="padding:8px; width:100%; box-sizing:border-box; resize:vertical;"></textarea>
</td>
`;
tbody.appendChild(newTr);
}
// Отправка всего массива примеров обучения из ЛК в Google Таблицу
function saveTrainingRowsToCloud() {
const status = document.getElementById('training-status');
status.innerText = "⏳ Синхронизация примеров с Базой Знаний ИИ...";
const rows = document.querySelectorAll('.training-row-item');
const updatedTrainingData = [];
rows.forEach(row => {
const triggerVal = row.querySelector('.trigger-input').value.trim();
const replyVal = row.querySelector('.reply-input').value.trim();
if (triggerVal && replyVal) {
updatedTrainingData.push({ trigger: triggerVal, reply: replyVal });
}
});
fetch(gateUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ action: "save_training", public_token: publicToken, trainingData: updatedTrainingData })
})
.then(res => res.json())
.then(data => {
if (data.success) {
status.innerText = "✅ Все примеры диалогов успешно запечатаны в память ИИ!";
}
})
["catch"](err => {
status.innerText = "❌ Ошибка сохранения: " + err.toString();
});
}
function switchTab(tabId, btn) {
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
document.getElementById(tabId).classList.add('active');
btn.classList.add('active');
}
function logoutUser() {
localStorage.removeItem('saas_public_token');
window.location.reload();
}
</script>
</body>
</html>