// =========================================================================
// ГЛОБАЛЬНАЯ ИИ-ПЛАТФОРМА v66.0 — ОФИЦИАЛЬНАЯ ИСПРАВЛЕННАЯ ШАПКА БЭКЕНДА
// =========================================================================
const CORE_SPREADSHEET_ID = '1GUDZRWnTo9-ArQZ6CbaQj4zfmm62-RoFtpsxcWDO7rY'; // ЖЕСТКИЙ ID БАЗЫ СУПЕРАДМИНА ДЛЯ ВХОДА!
const CORE_SHEET_NAME = "Реестр_Клиентов";
const MASTER_CRM_TEMPLATE_ID = '1MUMAOy7ieXTResq_qvrj62FoKwNjZ59OUiDsgLzkmG4'; // ID ШАБЛОНА ДЛЯ НОВЫХ КЛИЕНТОВ
const AI_API_KEY = 'sk-fY5xBwXa239NqLyiZM24byTFJCTYUiSN'; // Ваш токен ProxyAPI
const AI_URL = "https://api.proxyapi.ru/v1/chat/completions";
const TELEGRAM_TOKEN = "8712358274:AAHJwHXtg80pWiSLC9nyKg3qk4AD6sQ7_tk"; // Токен вашего бота
/**
* =========================================================================
* 1. СЛУЖБА РЕГИСТРАЦИИ И ВАЛИДАЦИИ КЛИЕНТОВ (B2B CORE)
* =========================================================================
*/
// Генератор уникальных защищенных токенов доступа для виджетов Tilda
function generateSecureToken() {
var chars = "abcdefghijklmnopqrstuvwxyz0123456789";
var result = "pk_live_";
for (var i = 0; i < 16; i++) {
result += chars[Math.floor(Math.random() * chars.length)];
}
return result;
}
// Функция создания нового личного кабинета студии аэродизайна
function handleRegistration(contents) {
try {
var db = SpreadsheetApp.openById(CORE_SPREADSHEET_ID);
var sheet = db.getSheetByName(CORE_SHEET_NAME);
var companyName = contents.companyName ? contents.companyName.toString().trim() : "Студия Аэродизайна";
var cleanEmail = contents.email ? contents.email.toString().trim().toLowerCase() : "";
var password = contents.password ? contents.password.toString().trim() : "123456";
var domain = contents.domain ? contents.domain.toString().trim().toLowerCase() : "";
// Проверяем, нет ли уже такого Email в системе Суперадмина
var data = sheet.getDataRange().getValues();
for (var i = 1; i < data.length; i++) {
if (data[i][1] && data[i][1].toString().trim().toLowerCase() === cleanEmail) {
return { success: false, error: "Пользователь с таким Email уже зарегистрирован!" };
}
}
var newClientId = "ID_" + Math.floor(Math.random() * 900000 + 100000);
var generatedToken = generateSecureToken();
var defaultBalance = 100.00; // Подарочный баланс на баланс суперадмина для тестов Кати
// Рассчитываем демо-период подписки на 14 дней вперед
var now = new Date();
var expDate = new Date(now.getTime() + 14 * 24 * 60 * 60 * 1000);
var expirationDate = Utilities.formatDate(expDate, "GMT+3", "dd.mm.yyyy HH:mm");
// Создаем кастомную CRM-таблицу для нового клиента путем копирования шаблона
var templateFile = DriveApp.getFileById("1MUMAOy7ieXTResq_qvrj62FoKwNjZ59OUiDsgLzkmG4"); // ID эталона CRM
var clientCrmFile = templateFile.makeCopy("CRM — " + companyName);
var clientCrmId = clientCrmFile.getId();
// Открываем права доступа по ссылке на редактирование для работы ИИ-панели
clientCrmFile.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.EDIT);
// Запись в Реестр Суперадмина строго по колонкам A-J (Фикс сдвига колонок!)
sheet.appendRow([
newClientId, // A — ID Клиента
cleanEmail, // B — Email
password, // C — Пароль
companyName, // D — Название_Студии
domain ? domain : "balloondog.ru", // E — Домен
clientCrmId, // F — ID_CRM_Таблицы
generatedToken, // G — Токен_Доступа
defaultBalance, // H — Баланс_Руб
"Демо-период", // I — Статус_Подписки
expirationDate // J — Дата_Окончания
]);
// Создаем строку инициализации параметров на втором листе Настройки_Ботов
var settingsSheet = db.getSheetByName("Настройки_Ботов");
settingsSheet.appendRow([
generatedToken, // A — Токен
"#ff69b4", // B — Цвет виджета
"Привет! ✨ Я Катя, ИИ-менеджер премиум-студии " + companyName + ". Помогу вам в подборе композиций из воздушных шаров, под ваш запрос и критерии! Напишите ваши пожелания и я вам соберу подходящие варианты 🎈", // C — Приветствие
"Не указан", // D — Адрес
"9:00 - 21:00", // E — Режим работы
"", // F — Телефон
"", // G — WhatsApp
"", // H — Telegram
"По согласованию", // I — Доставка
"", // J — YML Ссылка
true, // K — Показ WA (Тумблер)
true // L — Показ TG (Тумблер)
]);
return { success: true, token: generatedToken, crm_id: clientCrmId };
} catch(err) {
return { success: false, error: "Ошибка регистрации: " + err.toString() };
}
}
function validateClientSubscription(token) {
try {
var db = SpreadsheetApp.openById(CORE_SPREADSHEET_ID);
// ИСПРАВЛЕНО: Вместо имени берем самый первый лист в таблице Суперадмина (индекс 0)!
var sheets = db.getSheets();
var sheet = sheets[0];
if (!sheet) return { authorized: false };
var data = sheet.getDataRange().getValues();
var cleanToken = token ? token.toString().trim() : "";
if (cleanToken === "") return { authorized: false };
for (var i = 1; i < data.length; i++) {
var row = data[i];
// Жесткая защита: пропускаем пустые, короткие или поврежденные строки реестра
if (!row || row.length < 7) continue;
// Колонка G (индекс 6) — Токен доступа
var tableToken = row[6] ? row[6].toString().trim() : "";
if (tableToken === cleanToken) {
return {
authorized: true,
rowIndex: i + 1,
crmTableId: row[5] ? row[5].toString().trim() : "", // Колонка F (индекс 5) — ID личной CRM
domain: row[4] ? row[4].toString().trim() : "balloondog.ru", // Колонка E (индекс 4) — Домен сайта
name: row[3] ? row[3].toString().trim() : "Студия аэродизайна", // Колонка D (индекс 3) — Название студии
status: row[8] ? row[8].toString().trim() : "Демо-период", // Колонка I (индекс 8) — Тариф подписки
token: cleanToken
};
}
}
} catch(e) {
Logger.log("Критический сбой валидатора реестра: " + e.toString());
}
return { authorized: false };
}
/**
* =========================================================================
* 2. ЦЕНТРАЛЬНЫЕ КОРНЕВЫЕ СЕТЕВЫЕ ПРИЕМНИКИ (DO_GET / DO_POST)
* =========================================================================
*/
function doGet(e) {
try {
var params = e.parameter;
var token = params.public_token || params.token || "";
if (token === "") return ContentService.createTextOutput(JSON.stringify({ success: true, is_login_page: true })).setMimeType(ContentService.MimeType.JSON);
var client = validateClientSubscription(token);
if (!client.authorized) return ContentService.createTextOutput(JSON.stringify({ success: false, error: "Неверный токен" })).setMimeType(ContentService.MimeType.JSON);
var db = SpreadsheetApp.openById(CORE_SPREADSHEET_ID); // ИСПРАВЛЕНО ДЛЯ GET ЗАПРОСОВ!
var globalDb = db.getSheetByName("Настройки_Ботов");
var sData = globalDb.getDataRange().getValues();
var settingsRow = -1;
for (var s = 1; s < sData.length; s++) {
if (sData[s][0] && sData[s][0].toString().trim() === token.toString().trim()) { settingsRow = s + 1; break; }
}
if (settingsRow === -1) settingsRow = client.rowIndex;
var resData = {
success: true,
color: globalDb.getRange(settingsRow, 2).getValue().toString().trim() || "#ff69b4",
welcome_text: globalDb.getRange(settingsRow, 3).getValue().toString().trim() || "Привет! ✨ Я Катя, ИИ-менеджер.",
show_wa: globalDb.getRange(settingsRow, 11).getValue() !== false,
show_tg: globalDb.getRange(settingsRow, 12).getValue() !== false,
whatsapp: globalDb.getRange(settingsRow, 7).getValue().toString().trim(),
telegram: globalDb.getRange(settingsRow, 8).getValue().toString().trim()
};
return ContentService.createTextOutput(JSON.stringify(resData)).setMimeType(ContentService.MimeType.JSON);
} catch(err) {
return ContentService.createTextOutput(JSON.stringify({ success: false, error: err.toString() })).setMimeType(ContentService.MimeType.JSON);
}
}
function doPost(e) {
try {
// 1. Бронебойная проверка входящего сетевого пакета от хостинга
if (!e || !e.postData || !e.postData.contents) {
return ContentService.createTextOutput(JSON.stringify({ success: false, error: "Критический сбой: Сервер Google получил пустой POST-пакет!" })).setMimeType(ContentService.MimeType.JSON);
}
var raw = e.postData.contents.toString().trim();
var contents;
// 2. Безопасный парсинг JSON-потока
try {
contents = JSON.parse(raw);
} catch(jsonParseErr) {
return ContentService.createTextOutput(JSON.stringify({ success: false, error: "Ошибка парсинга JSON на бэкенде Google! Входящий текст: " + raw })).setMimeType(ContentService.MimeType.JSON);
}
var action = contents.action;
var token = contents.public_token || contents.token || "";
var db = SpreadsheetApp.openById(CORE_SPREADSHEET_ID); // Теперь строка на месте внутри функции!
// СВЕРХБЕЗОПАСНЫЙ МАРШРУТ АВТОРИЗАЦИИ (v67.0 — ЖЕСТКАЯ ЗАЩИТА ОТ ТИПОВ ДАННЫХ)
if (action === "login") {
var sheet = db.getSheetByName(CORE_SHEET_NAME);
var data = sheet.getDataRange().getValues();
var cleanEmail = contents.email ? contents.email.toString().trim().toLowerCase() : "";
var cleanPass = contents.password ? contents.password.toString().trim() : "";
for (var i = 1; i < data.length; i++) {
var row = data[i];
if (!row || row.length < 3) continue; // Пропускаем поврежденные строки
// Безопасное извлечение Email и Пароля по индексам 1 и 2
var tableEmail = row[1] ? row[1].toString().trim().toLowerCase() : "";
var tablePass = row[2] ? row[2].toString().trim() : "";
if (tableEmail === cleanEmail && tablePass === cleanPass) {
// Безопасное извлечение без риска вызова методов у undefined ячеек
var resToken = (row.length > 6 && row[6]) ? row[6].toString().trim() : "";
var statusSubscription = (row.length > 8 && row[8]) ? row[8].toString().trim() : "Демо-период";
var expireDate = (row.length > 9 && row[9]) ? row[9].toString().trim() : "";
if (resToken !== "") {
return ContentService.createTextOutput(JSON.stringify({
"success": true,
"token": resToken,
"status": statusSubscription,
"expire_date": expireDate
})).setMimeType(ContentService.MimeType.JSON);
}
}
}
return ContentService.createTextOutput(JSON.stringify({ "success": false, "error": "Неверный email или пароль!" })).setMimeType(ContentService.MimeType.JSON);
}
// МАРШРУТ РЕГИСТРАЦИИ НОВОГО ПОЛЬЗОВАТЕЛЯ ПЛАТФОРМЫ
if (action === "register") {
var regResult = handleRegistration(contents);
return ContentService.createTextOutput(JSON.stringify(regResult)).setMimeType(ContentService.MimeType.JSON);
}
// Валидируем токен для всех остальных входящих коммерческих запросов
var client = validateClientSubscription(token);
if (!client.authorized) {
return ContentService.createTextOutput(JSON.stringify({ success: false, error: "Ошибка: недействительный токен подписки!" })).setMimeType(ContentService.MimeType.JSON);
}
// МАРШРУТ СОХРАНЕНИЯ НАСТРОЕК БОТА ИЗ КЛИЕНТСКОГО ИНТЕРФЕЙСА ЛК
if (action === "save_settings") {
var globalDb = db.getSheetByName("Настройки_Ботов");
var sData = globalDb.getDataRange().getValues();
var targetRow = -1;
for (var s = 1; s < sData.length; s++) {
if (sData[s] && sData[s][0].toString().trim() === token.toString().trim()) {
targetRow = s + 1;
break;
}
}
if (targetRow === -1) targetRow = client.rowIndex;
globalDb.getRange(targetRow, 1).setValue(token);
globalDb.getRange(targetRow, 2).setValue(contents.color);
globalDb.getRange(targetRow, 3).setValue(contents.welcome_text);
globalDb.getRange(targetRow, 4).setValue(contents.address);
globalDb.getRange(targetRow, 5).setValue(contents.work_time);
globalDb.getRange(targetRow, 6).setValue(contents.phone);
globalDb.getRange(targetRow, 7).setValue(contents.whatsapp);
globalDb.getRange(targetRow, 8).setValue(contents.telegram);
globalDb.getRange(targetRow, 9).setValue(contents.delivery_rates);
// Конструктор тумблеров кнопок мессенджеров под приветствием Кати
globalDb.getRange(targetRow, 11).setValue(contents.show_wa !== false);
globalDb.getRange(targetRow, 12).setValue(contents.show_tg !== false);
return ContentService.createTextOutput(JSON.stringify({ success: true })).setMimeType(ContentService.MimeType.JSON);
}
// МАРШРУТ ЧАТА ВИДЖЕТА К НЕЙРОСЕТИ (ИИ-ЯДРО КАТИ)
if (action === "chat") {
var reply = executeAiLogic(client, contents.message, contents.chat_id, contents.image_data);
return ContentService.createTextOutput(JSON.stringify({ success: true, reply: reply })).setMimeType(ContentService.MimeType.JSON);
}
// МАРШРУТ ПЕРВИЧНОЙ ЗАГРУЗКИ ПАНЕЛИ УПРАВЛЕНИЯ ЛК КЛИЕНТА (ЗАЩИЩЕННЫЙ)
if (action === "load_dashboard") {
var globalDb = db.getSheetByName("Настройки_Ботов");
var sData = globalDb.getDataRange().getValues();
var targetRow = -1;
for (var s = 1; s < sData.length; s++) {
// Жесткая проверка: сначала убеждаемся, что строка и ячейка физически существуют!
if (sData[s] && sData[s][0] && sData[s][0].toString().trim() === token.toString().trim()) {
targetRow = s + 1;
break;
}
}
if (targetRow === -1) targetRow = client.rowIndex;
var res = {
success: true,
color: globalDb.getRange(targetRow, 2).getValue().toString().trim() || "#ff69b4",
welcome_text: globalDb.getRange(targetRow, 3).getValue().toString().trim() || "Привет! ✨ Я Катя.",
address: globalDb.getRange(targetRow, 4).getValue().toString().trim(),
work_time: globalDb.getRange(targetRow, 5).getValue().toString().trim(),
phone: globalDb.getRange(targetRow, 6).getValue().toString().trim(),
whatsapp: globalDb.getRange(targetRow, 7).getValue().toString().trim(),
telegram: globalDb.getRange(targetRow, 8).getValue().toString().trim(),
delivery_rates: globalDb.getRange(targetRow, 9).getValue().toString().trim(),
show_wa: globalDb.getRange(targetRow, 11).getValue() !== false,
show_tg: globalDb.getRange(targetRow, 12).getValue() !== false
};
return ContentService.createTextOutput(JSON.stringify(res)).setMimeType(ContentService.MimeType.JSON);
}
return ContentService.createTextOutput(JSON.stringify({ success: false, error: "Действие action не распознано" })).setMimeType(ContentService.MimeType.JSON);
} catch(err) {
return ContentService.createTextOutput(JSON.stringify({ success: false, error: err.toString() })).setMimeType(ContentService.MimeType.JSON);
}
}
/**
* =========================================================================
* 3. ИИ-ЯДРО КАТИ: СБОР КОНТЕКСТА, ОПТИКА VISION И МЕДИАМОСТ ЯНДЕКС.ДИСКА
* =========================================================================
*/
function executeAiLogic(client, userMessage, chatId, base64Image) {
var priceContext = "ШТУЧНЫЙ ПРАЙС CRM ДЛЯ РАСЧЕТА КАСТОМНЫХ СМЕТ И СБОРНЫХ КОМПОЗИЦИЙ:\n";
var ymlCatalogContext = "ГОТОВЫЕ НАБОРЫ ИЗ YML-ФИДА ВИТРИНЫ:\n";
var historyContext = "";
var hasImage = (base64Image && base64Image.toString().includes(","));
try {
var clientSpreadsheet = SpreadsheetApp.openById(client.crmTableId.toString().trim());
var chatSheet = clientSpreadsheet.getSheetByName("AI_Чаты");
var cleanUserMsg = (userMessage || "").toLowerCase();
var isCheckoutTriggered = false;
if (cleanUserMsg.indexOf("оформ") !== -1 || cleanUserMsg.indexOf("заказ") !== -1 || cleanUserMsg.indexOf("купить") !== -1) {
isCheckoutTriggered = true;
}
// 1. Извлечение контекста предыстории чата
if (chatSheet && chatSheet.getLastRow() >= 2) {
var chatData = chatSheet.getDataRange().getValues();
var historyCount = 0;
for (var h = chatData.length - 1; h >= 1; h--) {
if (chatData[h] && chatData[h][1] && chatData[h][1].toString() === chatId.toString() && historyCount < 4) {
var oldUserMsg = chatData[h][3] ? chatData[h][3].toString() : "";
var oldBotReply = chatData[h][4] ? chatData[h][4].toString() : "";
if (!isCheckoutTriggered && oldBotReply.indexOf("Для подтверждения заказа") !== -1) {
oldBotReply = "Состав комплекта и стоимость полностью утверждены.";
}
if (oldUserMsg !== "" && oldBotReply !== "") {
historyContext = "Клиент: " + oldUserMsg + "\nКатя (ИИ): " + oldBotReply + "\n" + historyContext;
historyCount++;
}
}
}
}
// 2. Извлечение штучного ручного прайса CRM клиента
var priceSheet = clientSpreadsheet.getSheetByName("Прайс_CRM");
if (priceSheet) {
var priceData = priceSheet.getDataRange().getValues();
for (var i = 1; i < Math.min(priceData.length, 60); i++) {
if (priceData[i] && priceData[i][0]) {
priceContext += "- [" + priceData[i][0] + "]: " + priceData[i][1] + " руб.\n";
}
}
}
// 3. Сканирование каталога YML (Умный парсер Tilda по стемам)
var catalogSheet = clientSpreadsheet.getSheetByName("Каталог_Tilda");
if (catalogSheet && catalogSheet.getLastRow() >= 2) {
var catalogData = catalogSheet.getDataRange().getValues();
var preparedQuery = cleanUserMsg.replace(/навыписку/gi, "на выписку").replace(/деньрождения/gi, "день рождения").replace(/[^а-яёa-z0-9\s]/g, " ").trim();
var rawWords = preparedQuery.split(/\s+/);
var searchWords = [];
var targetTarget = ""; var targetEvent = "";
for (var w = 0; w < rawWords.length; w++) {
var word = rawWords[w].trim();
if (word.length > 2 && ["есть", "нужен", "хочу", "найти", "шары", "пришли", "фото", "привет"].indexOf(word) === -1) {
if (word.indexOf("выпуск") !== -1) { targetEvent = "выписка"; continue; }
if (word.indexOf("рожд") !== -1) { targetEvent = "рождения"; continue; }
if (word.indexOf("мальч") !== -1) { targetTarget = "мальчик"; continue; }
if (word.indexOf("дев") !== -1) { targetTarget = "девоч"; continue; }
var stem = word;
if (stem.endsWith("чко") || stem.endsWith("чка")) stem = stem.substring(0, stem.length - 3);
searchWords.push(stem.length > 3 ? stem.substring(0, Math.max(3, stem.length - 1)) : stem);
}
}
var matchCount = 0;
for (var j = 1; j < catalogData.length; j++) {
var rowCells = catalogData[j];
if (rowCells && rowCells[0]) {
var name = rowCells[0].toString(); var price = rowCells[1].toString(); var desc = rowCells[2] ? rowCells[2].toString() : "";
var url = rowCells[3] ? rowCells[3].toString() : ""; var pic = rowCells[4] ? rowCells[4].toString() : ""; var meta = rowCells[5] ? rowCells[5].toString() : "";
var cleanDesc = desc.replace(/<!\[CDATA\[/gi, "").replace(/\]\]>/gi, "").replace(/<\/?[^>]+(>|$)/g, "").trim();
var fullTextLower = (name + " " + cleanDesc + " " + meta).toLowerCase(); var metaLower = meta.toLowerCase();
var isEventValid = true; var isTargetValid = true;
if (targetEvent !== "" && metaLower.indexOf(targetEvent) === -1) isEventValid = false;
if (targetTarget !== "" && metaLower.indexOf(targetTarget) === -1) isTargetValid = false;
var isKeywordsValid = searchWords.length === 0 ? true : searchWords.some(function(stem) { return fullTextLower.indexOf(stem) !== -1; });
if ((isEventValid && isTargetValid && isKeywordsValid) || hasImage) {
ymlCatalogContext += "- Название: " + name + " | Цена: " + price + " руб. | Состав: " + cleanDesc + " | Ссылка на карточку товара: " + url + " | Фото: " + pic + "\n";
matchCount++; if (matchCount >= 15) break;
}
}
}
}
} catch(e) { Logger.log("Ошибка сбора контекста: " + e.toString()); }
// =========================================================================
// ИИ-МЕДИАМОСТ С ЯНДЕКС ДИСКА (АКТИВИРУЕТСЯ СТРОГО ПО ТАРИФУ КЛИЕНТА!)
// =========================================================================
var globalPhotoBank = "ДОСТУП К НАТУРНОМУ ФОТОБАНКУ ОГРАНИЧЕН (ВАШ ТАРИФ БАЗОВЫЙ).\nСсылки на палитру недоступны.";
var clientStatus = (client.status || "").toString().trim().toLowerCase();
if (clientStatus === "премиум-эксперт" || clientStatus === "демо-период") {
globalPhotoBank = "ГЛОБАЛЬНЫЙ МЕДИАМОСТ ФОТОБАНКА ПЛАТФОРМЫ ИЗ ЯНДЕКС.ДИСКА (ДОСТУП ОТКРЫТ):\n" +
"- [Посмотреть натурный оттенок Нежной Оливы пастель](https://yandex.ru)\n" +
"- [Посмотреть премиальный Синий Глянцевый Хром](https://yandex.ru)\n" +
"- [Посмотреть Золото Хром металлик эталон](https://yandex.ru)\n" +
"- [Посмотреть Розовый Макаронс пастель палитра](https://yandex.ru)\n" +
"- [Посмотреть фольгированное нежное Облачко белое](https://yandex.ru);\n";
}
// Выкачиваем кастомные параметры и контакты для вшивания в промпт Кати
var welcomeText = "Привет! ✨ Я Катя, ИИ-менеджер.";
var botAddr = "Не указан";
var botTime = "9:00 - 21:00";
var botPhone = "";
var botWa = "";
var botTg = "";
var botDelivery = "По согласованию с менеджером.";
try {
var globalDb = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Настройки_Ботов");
var sData = globalDb.getDataRange().getValues();
var targetRow = -1;
for (var s = 1; s < sData.length; s++) {
if (sData[s] && sData[s].toString().trim() === client.token.toString().trim()) { targetRow = s + 1; break; }
}
if (targetRow === -1) targetRow = client.rowIndex;
welcomeText = globalDb.getRange(targetRow, 3).getValue().toString().trim() || welcomeText;
botAddr = globalDb.getRange(targetRow, 4).getValue().toString().trim() || botAddr;
botTime = globalDb.getRange(targetRow, 5).getValue().toString().trim() || botTime;
botPhone = globalDb.getRange(targetRow, 6).getValue().toString().trim();
botWa = globalDb.getRange(targetRow, 7).getValue().toString().trim();
botTg = globalDb.getRange(targetRow, 8).getValue().toString().trim();
botDelivery = globalDb.getRange(targetRow, 9).getValue().toString().trim() || botDelivery;
} catch(e) {}
var systemPrompt = "Ты — Катя, ведущий аэродизайнер премиум-студии '" + client.name + "' (Регион/Домен студии: " + client.domain + "). Ты общаешься тепло, как реальная девушка-эксперт. Твоя цель — продавать и закрывать сделки!\n\n" +
"ТВОИ УСТАНОВЛЕННЫЕ КОНТАКТЫ ДЛЯ КЛИЕНТОВ:\n" +
"- Адрес самовывоза: " + botAddr + " (Режим работы: " + botTime + ")\n" +
"- Телефон студии: " + botPhone + "\n" +
"- WhatsApp для связи: " + botWa + "\n" +
"- Telegram канал/аккаунт: " + botTg + "\n" +
"- Условия и тарифы доставки: " + botDelivery + "\n\n" +
"УЛЬТИМАТИВНЫЙ ИНТЕЛЛЕКТУАЛЬНЫЙ АНАЛИЗ ПРАЙСА И СЛОЖНОГО АЭРОДИЗАЙНА:\n" +
"1. СБОРКА ИЗ ПРАЙСА И ФОТОБАНКА: Если клиент просит собрать композицию поштучно, или готового сета нет в YML, ты ОБЯЗАНА рассчитать стоимость кастомно по блоку 'ШТУЧНЫЙ ПРАЙС CRM'. Если доступ к фотобанку открыт, ты ОБЯЗАНА использовать Markdown ссылки из блока ЯНДЕКС.ДИСКА в формате кнопок, например: [Посмотреть оттенок синего хрома](ссылка).\n" +
"2. ДИНАМИЧЕСКИЙ МАРШРУТ РАЗМЕРОВ ФИГУР: Если в штучном прайсе CRM заданы общие категории ('Фигура малая', 'Фигура большая', 'Звезда фольга'), а на фото или в запросе фигурирует конкретный персонаж (например, 'Синий Трактор'), ты ОБЯЗАНА понять по интернету, что это большая лицензионная фигура (80-90 см), и взять цену из строки 'Фигура большая'.\n" +
"3. СЛОЖНЫЙ КОНСТРУКТОР (БАБЛС, НАДПИСИ, ПЕРЬЯ, ДАБЛ-СТАФФ): Если позиций нет в прайсе CRM — примени глобальный региональный фаллбэк цен для города (" + client.domain + "): надпись — 350 руб, перья/наполнитель — 250 руб, шар баблс — 1200 руб, атласная лента и бант ручной работы — 150 руб.\n" +
"4. АКТИВНЫЕ ДОПРОДАЖИ И ПРИВЕТСТВИЕ: Всегда используй стартовый шаблон приветствия: '" + welcomeText + "'. мягко предлагай перейти в мессенджеры.\n" +
"5. ЗАКРЫТИЕ СДЕЛКИ: Когда клиент оставил анкету данных — выведи итоговый чек списком для проверки, посчитай доставку по правилам студии ('" + botDelivery + "') и напиши: 'Данные зафиксировала, заказ официально передан на сборку аэродизайнерам! Ждем вас!'";
var userContent = [];
var cleanText = userMessage ? userMessage.toString().trim() : "";
if (cleanText === "" && hasImage) {
cleanText = "Рассчитай, пожалуйста, полную стоимость всей этой композиции из шаров целиком.";
}
userContent.push({ "type": "text", "text": historyContext !== "" ? "ПРЕДЫСТОРИЯ НАШЕГО ДИАЛОГА:\n" + historyContext + "\nТЕКУЩЕЕ НОВОЕ СООБЩЕНИЕ КЛИЕНТА:\n" + cleanText : cleanText });
if (hasImage) {
userContent.push({ "type": "image_url", "image_url": { "url": base64Image } });
}
var fullSystemContent = systemPrompt + "\n\n" + globalPhotoBank + "\n" + ymlCatalogContext + "\n" + priceContext;
var finalUserContent = hasImage ? userContent : cleanText;
var options = {
"method": "post",
"contentType": "application/json",
"headers": { "Authorization": "Bearer " + AI_API_KEY },
"payload": JSON.stringify({
"model": "openai/gpt-4o-mini",
"messages": [
{ "role": "system", "content": fullSystemContent },
{ "role": "user", "content": finalUserContent }
],
"temperature": 0.1
}),
"muteHttpExceptions": true
};
try {
var response = UrlFetchApp.fetch(AI_URL, options);
var resText = response.getContentText();
var json = JSON.parse(resText);
var aiReply = "Ой, у меня ленточка запуталась! Повторите запрос. 🎈";
// ДИАГНОСТИКА: Выводим прямую ошибку ProxyAPI на экран сайта, если она есть
if (json && json.error) {
return "⚠️ Ошибка ProxyAPI: " + json.error.message + " (Тип: " + json.error.type + ")";
}
if (json && json.choices && json.choices.length > 0 && json.choices.message) {
aiReply = json.choices.message.content;
// КОММЕРЧЕСКИЙ БИЛЛИНГ: Автоматическое списание баланса с Реестра Суперадмина
try {
var registrySheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(CORE_SHEET_NAME);
if (client && client.rowIndex) {
var currentBalance = parseFloat(registrySheet.getRange(client.rowIndex, 8).getValue()) || 0;
registrySheet.getRange(client.rowIndex, 8).setValue(currentBalance - (hasImage ? 5.00 : 2.00));
}
} catch(e) {
Logger.log("Ошибка биллинга: " + e.toString());
}
}
// Запись диалога в расширенный 5-колоночный лог CRM клиента
try {
if (chatSheet) {
chatSheet.appendRow([new Date(), chatId, "Сессия_Сайта", userMessage || "📎 Отправлено фото референса", aiReply]);
}
} catch(e) {
Logger.log("Ошибка записи лога: " + e.toString());
}
return aiReply;
} catch(globalErr) {
return "⚠️ Критическая ошибка ИИ-ядра при отправке пакета: " + globalErr.toString();
}
}