Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 59 Next »

Переписка с пользователями

Простые сценарии, включающие в себя опрос пользователя с пользователем и подготовку ответа на стороне сценария.

Короткий опрос пользователя и возврат ответов

Сценарий ведет небольшую переписку с пользователем и возвращает ответы в зависимости от реакции пользователя.

Во время работы сценария все реплики пользователя будут передаваться в этот сценарий. После завершения сценария сервис будет ждать ответную реакцию пользователя в течение определенного в настройках времени. Если пользователь напишет реплику - она будет передана в базы знаний. Если пользователь не напишет реплику в указанное время - сервис закроет диалог.

 Нажмите здесь, чтобы развернуть пример сценария
//
// Сценарий простой переписки с пользователем
//
// Приветственное сообщение и первый вопрос
сообщениеПользователю.сШаблоном("Добрый день! Сейчас я помогу!") +
задатьПользователюВопрос("Я правильно понял, что вы заблудились в лесу?").сВариантамиОтвета("Да", "Нет").сохранитьРезультат("answer_1")+
//
// Если пользователь выберет нет - сценарий завершается
если("{answer_1} == Нет").то(
    сообщениеПользователю.сШаблоном("Ок, тогда я отключаюсь. Всего хорошего!") +
    завершить
)+
//
// Сообщения пользователю и начало опроса
сообщениеПользователю.сШаблоном("Если вы заблудились в лесу - главное сохранять спокойствие!") +
сообщениеПользователю.сШаблоном("Ваша главная задача - решить куда идти и выйти из леса.") +
задатьПользователюВопрос("Посмотрите вокруг - вы видите деревья?").сВариантамиОтвета("Да", "Нет").сохранитьРезультат("answer_2")+
//
// Пользователь ответил Нет - сценарий возвращает сообщение для этого случая и завершается 
если("{answer_2} == Нет").то(
    сообщениеПользователю.сШаблоном("Поздравляю! Вы не в лесу. Проблема решена. Всего хорошего") +
    завершить
)+
//
// Пользователь ответил Да - сценарий продолжает передавать сообщения пользователю
сообщениеПользователю.сШаблоном("Посмотрите на мох на стволах деревьев. Мох зеленый, зеленый цвет успокаивает.") +
сообщениеПользователю.сШаблоном("Обратите внимание на какой стороне дерева чаще всего встречается мох - это направление на север.") +
сообщениеПользователю.сШаблоном("Теперь важно вспомнить как вы пришли сюда и идти обратно.") +
задатьПользователюВопрос("Вы можете вспомнить откуда пришли?").сВариантамиОтвета("Да","Нет").сохранитьРезультат("answer_3") +
//
// Пользователь ответил Нет - сценарий возвращает сообщение для этого случая и завершается
если("{answer_3} == Нет").то(
    сообщениеПользователю.сШаблоном("Тогда вам все равно куда идти. Идите на север, ориентируясь на мох. Всего хорошего и удачи!") +
    завершить  
) + 
//
// Пользователь выбрал вариант Да - сценарий возвращает сообщение для этого случая и завершается
сообщениеПользователю.сШаблоном("Отлично! Идите обратно. Всего хорошего и удачи!") +
завершить

Короткий опрос пользователя и передача запроса операторам

Сценарий проведет первичный опрос пользователя и передаст диалог операторам. Сценарий можно использовать в документах базы знаний для предметных опросов или в качестве приветственного сценарий для общих для всех опросов.

 Нажмите здесь, чтобы развернуть пример сценария
//
// Сценарий первичного анкетирования и передачи запроса операторам
//
// Начинается опрос
задатьПользователюВопрос("Уточните, пожалуйста, что для Вас является приоритетным?").сВариантамиОтвета("Получение максимальной суммы","Простота получения").сохранитьРезультат("UserSaid_Priority")+
задатьПользователюВопрос("Укажите, на какие цели предназначается займ?").сохранитьРезультат("UserSaid_Priority_2")+
сообщениеПользователю.сШаблоном("Пожалуйста не закрывайте окно чата оператор подберет для Вас наиболее выгодное предложение!")+
//
// завершаем сценарий и передаем диалог операторам
завершитьИПеревестиНаОператора

Анкетирование пользователя и сохранение результатов в профиле пользователя

Сценарий проверит данные в профиле пользователя по списку и попросит пользователя указать отсутствующие данные. Список переменных указывается в самом сценарии. Для каждого вопроса предусмотрена кнопка Пропустить, нажатие пользователя на эту кнопку фиксируется в переменной пользователя как символ -.

 Нажмите здесь, чтобы развернуть пример сценария
//
// Сценарий заполнения данных пользователя
//
установитьПеременную("enteredEmail", "") +
установитьПеременную("enteredPhone", "") +
установитьПеременную("enteredFIO", "") +
установитьПеременную("enteredTall", "") +
установитьПеременную("enteredRegion", "") +
установитьПеременную("enteredLink", "") +
установитьПеременную("enteredCheck", "") +
//
// Опрос
если("{userFullName} == ").то(
	задатьПользователюВопрос("Укажите как вас зовут").сВариантамиОтвета("Пропустить").сохранитьРезультат("enteredFIO") 	
) +
если("{userEmail} == ").то(
	задатьПользователюВопрос("Укажите адрес вашей электронной почты").сВариантамиОтвета("Пропустить").сохранитьРезультат("enteredEmail") 	
) +
если("{userPhone} == ").то(
	задатьПользователюВопрос("Укажите номер вашего телефона").сВариантамиОтвета("Пропустить").сохранитьРезультат("enteredPhone") 	
) +
если("{userPayload.рост} == ").то(
	задатьПользователюВопрос("Укажите ваш рост").сВариантамиОтвета("Пропустить").сохранитьРезультат("enteredTall") 	
) +
если("{userPayload.регион} == ").то(
	задатьПользователюВопрос("Укажите ваш город").сВариантамиОтвета("Пропустить").сохранитьРезультат("enteredRegion") 	
) +
если("{userPayload.ссылка} == ").то(
	задатьПользователюВопрос("Укажите ссылку на ваш профиль").сВариантамиОтвета("Пропустить").сохранитьРезультат("enteredLink") 	
) +
//
// Замена нажатой кнопки Пропустить на символ -
если("{enteredFIO} == Пропустить").то(
	установитьПеременную("enteredFIO", "-")
) +
если("{enteredEmail} == Пропустить").то(
	установитьПеременную("enteredEmail", "-")
) +
если("{enteredPhone} == Пропустить").то(
	установитьПеременную("enteredPhone", "-")
) +
если("{enteredTall} == Пропустить").то(
	установитьПеременную("enteredTall", "-")
) +
если("{enteredRegion} == Пропустить").то(
	установитьПеременную("enteredRegion", "-")
) +
если("{enteredLink} == Пропустить").то(
	установитьПеременную("enteredLink", "-")
) +
//
// Проверка данных и сохранение тех переменных пользователя для которых в опросе были указаны данные
если("{enteredFIO} != ").то(
	установитьПеременнуюПользователю("userFullName", "{enteredFIO}") +
	установитьПеременную("enteredCheck", "1")
) +
если("{enteredEmail} != ").то(
	установитьПеременнуюПользователю("userEmail", "{enteredEmail}") +
	установитьПеременную("enteredCheck", "1")
) +
если("{enteredPhone} != ").то(
	установитьПеременнуюПользователю("userPhone", "{enteredPhone}") +
	установитьПеременную("enteredCheck", "1")
) +
если("{enteredTall} != ").то(
	установитьПеременнуюПользователю("userPayload.рост", "{enteredTall}") +
	установитьПеременную("enteredCheck", "1")
) +
если("{enteredRegion} != ").то(
	установитьПеременнуюПользователю("userPayload.регион", "{enteredRegion}") +
	установитьПеременную("enteredCheck", "1")
) +
если("{enteredLink} != ").то(
	установитьПеременнуюПользователю("userPayload.ссылка", "{enteredLink}") +
	установитьПеременную("enteredCheck", "1")
) +
//
// Если хоть одно значение было указано сценарий сообщит со сохранении данных
если("{enteredCheck} == 1").то(
	сообщениеПользователю.сШаблоном("Спасибо. Все записал.")
) + 
//
завершить

Опрос пользователя и получение ответа из Confluence

Сценарий уточняет вопрос у пользователя и в зависимости от выбора пользователя возвращает в ответ одну из двух статей из Confluence.

Токен для обращения сервиса в Confluence следует получать у администраторов Confluence или по документации.

 Нажмите здесь, чтобы развернуть пример сценария
//
// Сценарий получения ответа из статьи Confluence
//
// Настройки сценария
//
// Укажите адрес установки Confluence
установитьПеременную("confluense_host", "https://ХХХХХХХХХ.atlassian.net/") +
// URL для запросов получения контента статей
установитьПеременную("confluense_url", "wiki/rest/api/content/") +
// Укажите токен для API запросов в Confluence 
установитьПеременную("confluense_token", "ХХХХХХХХХХХХХХХХХ")+
//
// Основной код сценария
//
// Приветственное сообщение и первый вопрос
сообщениеПользователю.сШаблоном("Добрый день! Сейчас я помогу!") +
задатьПользователюВопрос("Подскажите, описание какого релиза вас интересует?").сВариантамиОтвета("5.0.4", "5.0.5").сохранитьРезультат("answer_1")+
//
если("{answer_1} == 5.0.4").то(
	установитьПеременную("article", "3154706435")
) +
если("{answer_1} == 5.0.5").то(
	установитьПеременную("article", "3160571905") 
) +
//
// 
вызвать.внешнийСервис("{confluense_host}{confluense_url}{article}", "GET").сПараметрами(("expand", "body.storage")).сЗаголовками(("Content-Type", "application/json"),("charset", "utf-8"),("Authorization", "{confluense_token}")).сохранитьРезультат(("body.storage.value","body"))+
//
// Проверка результатов запроса, в случае ошибки сценарий сообщит об ошибке и завершится 
если("{http_code} > 210").то(
	комментарий("Сценарий не смог получить данные. {http_code}")+
	сообщениеПользователю.сШаблоном("Что-то сломалось и я не могу получить для вас ответ.")+
	завершить
)+
//
// 
сообщениеПользователю.сШаблоном("{body}")+
завершить

Стартовые и приветственные сценарии

Сценарии, которые автоматически запускаются при поступлении первого сообщения пользователя в диалоге. В такие сценарии удобно вставлять автоматические проверки данных пользователя, заполнение служебных переменных и приветсвенные меню доступных действий.

Такие сценарии размещаются в Приветствии или Интеграции на поступление диалога.

Разные приветствия для пользователей разных каналов

Сценарий приветствует пользователя разными сообщениями в зависимости от типа или идентификатора канала, через который обратился пользователь. После приветствия запрос пользователя передается в базы знаний.

 Нажмите здесь, чтобы развернуть пример сценария
//
// Сценарий описывает различные приветствия в зависимости от типа и ИД канала
//
если("{channelType} == Telegram").то(
    сообщениеПользователю.сШаблоном("Добро пожаловать в наш Telegram")
)+
если("{channelId} == 2f7ce72f-8003-4e17-b879-e3c71f239149").то(
    сообщениеПользователю.сШаблоном("Здравствуйте!")
)+
//
// Завершение сценария и передача запроса в базы знаний
завершитьСНовымЗапросом("{platformInMessageQuery}")

Сообщение об отсуствии операторов поддержки вне рабочего времени

В зависимости от текущего времени сценарий возвращает различные приветствия. В отсутствии операторов сценарий проверяет может ли бот ответить на запрос. Если ответ на запрос есть, сценарий предупреждает об отсутствии операторов и передает запрос боту. Если в базах знаний нет овтета на запрос сценарий сообщает об отсутствии операторов и закрывает диалог.

 Нажмите здесь, чтобы развернуть пример сценария

//
// Сценарий сообщает об отсутствии операторов поддержки в ночное время
//
// Настройка сценария
// Смещение текущего времени службы поддержки относительно GMT в часах
установитьПеременную("HourOffset", "3")+
// время начала работы службы поддержки
установитьПеременную("startHour", "9")+
// время окончания работы службы поддержки (не включая)
установитьПеременную("endHour", "22") +
//
// Основной код сценария
// 
// проверка текущего времени
выполнитьJs("""
var serverTime = new Date();
var serverTimeStamp = serverTime.getTime();
var timeZoneOffset = serverTime.getTimezoneOffset();

var currentTimeStamp = serverTimeStamp + (parseInt(HourOffset) *  3600000) - (timeZoneOffset * 60000);

var currentDate = new Date(currentTimeStamp);
var currentHour = parseInt(currentDate.getHours());

var start = parseInt(startHour);
var stop = parseInt(endHour);

if (currentHour >= start && currentHour < stop) {
	var workTime = 1;
} else {
	var workTime = 0;	
}

var exit = {'workTime':workTime};
exit;
""") +
//
// Сейчас рабочее время, сценарий передает запрос в базы знаний без уведомлений пользователя
если("{workTime} == 1").то(
	комментарий("Рабочее время. Продолжаем штатно") + 
	завершитьСНовымЗапросом("{platformInMessageQuery}")
)+
//
// Сейчас нерабочее время, перед сообщением пользователю сценарий уточняет наличие ответа в базе знаний
запросВБазыЗнаний("{platformInMessageQuery}", "botKnows")+
//
// В базе знаний есть ответ, сценарий уведомляет пользователя об отсутствии операторов и передает запрос в базы знаний
если("{botKnows} == true").то(
	комментарий("Бот знает ответ, продолжаем не взирая на график.") + 
	сообщениеПользователю.сШаблоном("Операторы службы поддержки сейчас не работают, но Вы можете получить консультацию у нашего бота. Операторы смогут подключиться с {startHour}:00 по МСК.")+ 
	завершитьСНовымЗапросом("{platformInMessageQuery}")
)+
//
// В базе знаний нет ответа, сценарий сообщает об отсутствии операторов службы поддержки, завершается и закрывает диалог
комментарий("Бот не понял. Операторы не работают")+
сообщениеПользователю.сШаблоном("Сейчас служба поддержки не работает. Просим писать нам с {startHour}:00 по {endHour}:00 МСК.<br>Unfortunately you contacted us outside of our support service hours. We will reply to you in the morning. Thank you for waiting.")+
завершитьИЗакрытьДиалог()

Сценарий проверяет не только на указанный период работы, но и на выходные и праздничные дни.

В зависимости от рабочего/нерабочего времени уведомляется пользователя соответствующим сообщением.

 Нажмите здесь, чтобы развернуть пример сценария
//
// Сценарий сообщает об отсутствии операторов поддержки в ночное время, в выходные и в заданные даты дд.мм.гггг
//
// Смещение текущего времени службы поддержки относительно GMT в часах
установитьПеременную("HourOffset", "3") +
// время начала работы службы поддержки
установитьПеременную("startHour", "9") +
// время окончания работы службы поддержки (не включая)
установитьПеременную("endHour", "18") +
// Основной код сценария
// проверка текущего времени на рабочее время, выходной и праздник
выполнитьJs("""
// устанавливаем список праздничных дат
var holidayDates = ["13.03.2023", "14.03.2023", "04.08.2023"];
//
var serverTime = new Date();
var serverTimeStamp = serverTime.getTime();
var timeZoneOffset = serverTime.getTimezoneOffset();
var currentTimeStamp = serverTimeStamp + (parseInt(HourOffset) *  3600000) - (timeZoneOffset * 60000);
var currentDate = new Date(currentTimeStamp);
var currentHour = parseInt(currentDate.getHours());
var month = currentDate.getMonth()+1; 
var tdate = currentDate.getDate();
var newdate = (tdate < 10 ? '0' : '') + tdate + '.' + (month < 10 ? '0' : '') + month + '.' + currentDate.getFullYear();
var currentDay = currentDate.getDay();
var start = parseInt(startHour);
var stop = parseInt(endHour);
// проверяем дату на наличие в списке праздников
if (holidayDates.indexOf(newdate) != -1) {
	var resultHoliday = 1;
} else {
	var resultHoliday = 0;
}
// получаем признак рабочего/нерабочего времени
if (currentHour >= start && currentHour < stop && currentDay != 0 && currentDay != 6 && resultHoliday != 1 ) {
	var workTime = 1;
} else {
	var workTime = 0;	
}
var exit = {'workTime':workTime,'newdate':newdate};
exit;
""")+
//
// Сейчас рабочее время, сценарий передает запрос в базы знаний без уведомлений пользователя
если("{workTime} == 1").то(
//комментарий("Рабочее время")
сообщениеПользователю.сШаблоном("Рабочее время")
) +
если("{workTime} == 0").то(
	сообщениеПользователю.сШаблоном("Операторы технической поддержки на ваш вопрос в рабочее время. График работы виджета: пн-пт, 9:00-18:00 (МСК) за исключением праздничных и предпраздничных дней.")
)+
завершить

Меню наиболее частых запросов

Приветственный сценарий сперва проверяет наличие в базах знаний ответа с уверенностью, соотвествующей текущим настройкам бота. Если ответ есть, то сценарий передает запрос в базы знаний. Если ответа нет или его уверенность недостаточна, то сценарий отображает меню с вариантами вопроса. При выборе одного из вариантов сценарий передает соотвествующий запрос в базу знаний.

Если пользователь вместо выбора варианта напишет свой запрос, то сценарий передаст его в базы знаний.

 Нажмите здесь, чтобы развернуть пример сценария
//
// Сценарий проверяет наличие ответа и если ответа нет то предлагает пользователю меню запросов
// 
// Проверка наличия ответа на запрос в базах знаний
запросВБазыЗнаний("{platformInMessageQuery}", "botKnows")+
//
// В базе знаний есть ответ, сценарий передает запрос в базу знаний
если("{botKnows} == true").то(	
	завершитьСНовымЗапросом("{platformInMessageQuery}")
)+
//
// В базе знаний нет прямого ответа на запрос, сценарий предлагает выбрать из готовых вариантов
задатьПользователюВопрос("Здравствуйте 😊<br>Что Вас интересует?").сВариантамиОтвета("Первое","Второе","Компот").сохранитьРезультат("userChoice")+
//
// Если пользователь выбрал первый вариант, то сценарий передает запрос "Первые блюда" в базу знаний
если("{userChoice} == Первое").то(
	завершитьСНовымЗапросом("Первые блюда")
)+
//
// Если пользователь выбрал второй вариант, то сценарий передает запрос "Вторые блюда" в базу знаний
если("{userChoice} == Второе").то(
	завершитьСНовымЗапросом("Вторые блюда")
)+
//
// Если пользователь выбрал третий вариант, то сценарий отправляет ответ и завершается
если("{userChoice} == Компот").то(
	сообщениеПользователю.сШаблоном("Закончился. Всего хорошего!") +
	завершить
)+
//
// Если пользователь не выбирал вариантов а написал другой запрос, то он передается в базы знаний
завершитьСНовымЗапросом("{userChoice}")

Создание тегов к диалогу

Сценарий создает требуемые переменные диалога. Сценарий задает тип переменной - текстовое поле, числовое поле, меню выбора, переключатель, видимость для опеартора и необходимость заполнения переменной перед закрытием диалога.

Переменные диалога могут применяться для сохранения данных относящихся к отдельному диалогу - тегирования, сохранения комментарием или выбору тематик.

Переменные диалога могут заполняться автоматически в сценариях или вручную оператором в правой боковой панели диалога. Каждый диалог может обладать своим набором переменных - список переменных не задается жестко, а определяется сценарием. В каждом диалоге одна и та же переменная может иметь различное содержание. Подробнее о переменных диалога в разделе Справочник шагов сценария.

 Нажмите здесь, чтобы развернуть пример сценария
//
// Сценарий создает переменные диалога 
//
// Настройки сценария
//
// Сценарий описывает варианты для одноуровневого списка приоритетов
установитьПеременную("priorities_list","""{"1": "Критичный", "2": "Срочный", "3": "Средний", "4": "Низкий"}""") +
//
// Сценарий описывает варианты многоуровневневого списка тематик через JS код
выполнитьJs("""
var network = [{"label":"Разрыв соединения","value":"11"}, {"label":"Низкая скорость","value":"12"}];
var equipment = [{"label":"Клиентский роутер","value":"21"}, {"label":"Коммутатор","value":"22"},{"label":"Кабели","value":"23"}];
var other = [{"label":"Кривые руки","value":"31"}, {"label":"Проблемы с учетной записью","value":"32"}, {"label":"Проблемы с документацией","value":"33"}];

var topics = {};
topics[JSON.stringify(network)] = 'Сетевые проблемы';
topics[JSON.stringify(equipment)] = 'Проблемы с оборудованием';
topics[JSON.stringify(other)] = 'Прочие проблемы';

var exit = {'topics': JSON.stringify(topics)};
exit;
""") +
//
// Основной код сценария
//
// Создание переменной выбора приоритета из списка
установитьПеременнуюВДиалог(
    ключ = "1_priority",
    значение = "",
    название = "Выбор срочности",
    редактируемое = true,
    показыватьОператору = true,
    обязательное = true,
    множественныйВыбор = true,
    тип = "Выпадающий список",
    варианты = "{priorities_list}"
)+
//
// Создание переменной выбора сервиса из списка
установитьПеременнуюВДиалог(
    ключ = "2_topicId",
    значение = "",
    название = "Выбор тематики",
    редактируемое = true,
    показыватьОператору = true,
    обязательное = false,
    множественныйВыбор = false,
    тип = "Выпадающий список",
	варианты = "{topics}"
)+
//
// Создание переменной указания адреса
установитьПеременнуюВДиалог(
    ключ = "3_address",
    значение = "",
    название = "Адрес",
    редактируемое = true,
    показыватьОператору = true,
    обязательное = false,
    множественныйВыбор = false,
    тип = "Текст"
)+
//
// Создание переменной указания комментария к заявке
установитьПеременнуюВДиалог(
    ключ = "4_comment",
    значение = "",
    название = "Комментарий к вызову",
    редактируемое = true,
    показыватьОператору = true,
    обязательное = false,
    множественныйВыбор = false,
    тип = "Текст"
)+
завершить

Создание тегов к диалогу и автоматическое предзаполнение полей

Сценарий создает переменную для выбора продукта и автоматически предзаполняет ее на основе классификации запроса пользователя.

Сценарий классифицирует запрос пользователя по базе знаний со списком продуктов компании. Если классификация проходит с уверенностью выше установленной, то при открытии диалога оператором переменная будет предзаполнена наиболее вероятным продуктом. Список всех вариантов берется из базы знаний. При этом оператор в любой момент может выбрать другой вариант.

Если классификация проходит с недостаточной уверенностью, то переменная диалога будет пустой.

Сценарий удобен тем, что список вариантов для выбора хранится в отдельной базе знаний и изменять его может любой супервизор сервиса без вмешательства в текст сценария. База знаний может находиться в режиме “Не используется” и применяться только для хранения спсика продуктов. Автоматическая классификация и предзаполнение переменной позволят значительно экономить время оператора на заполнение переменных диалога

 Нажмите здесь, чтобы развернуть пример сценария
//
// Сценарий создает переменные диалога и автоматически предзаполняет часть из них 
// на основе классификации запроса пользователя 
//
// Настройки сценария
//
// Настройки для проведения классификации запроса пользователя
//
// Адрес API ендпойнтов для запросов баз знаний 
установитьПеременную("kb_qna_enpoint", "https://chat.autofaq.ai/core-api/query/api/v1/query") +
установитьПеременную("kb_crud_endpoint", "https://chat.autofaq.ai/core-api/crud/api/v1/services/") +
//
// Укажите токен для запроса в базу знаний, брать из поля "user token" в разделе Настройки - Общие - Параметры прямого подключения к базам знаний через API
установитьПеременную("autorizationToken", "ХХХХХХХХХ")+
//
// Укажите ID и токен базы знаний -  классификатора продуктов, брать в настройках базы знаний
установитьПеременную("kb_product_id", "ХХХХ")+
установитьПеременную("kb_product_token", "ХХХХХХХХХХХ")+
//
// Укажите минимальный уровень уверенности ответа для успешной классификации
установитьПеременную("minimumScore", "10") +
//
// Основной код сценарий
//
// Сценарий отправляет запрос на классификацию сообщения пользователя
вызвать.внешнийСервис("{kb_qna_enpoint}","POST").сЗаголовками(("Content-Type", "application/json"),("charset", "utf-8"),("AUTOFAQ-User-Token", "{autorizationToken}")).сТеломСообщения("""{"service_token":"{kb_product_token}","service_id":"{kb_product_id}","query":"{platformInMessageQuery}"}""").сохранитьРезультатКакСтроку("answer")+
//
// Проверка резльтатов запрос, если запрос завершился с ошибкой сценарий оставляет комментарий для администратора сервиса 
если("{http_code} != 200").то(  
  комментарий("При запросе на классификацию возникла ошибка {http_code}, текст ошибки {answer}")
)+
//
// Разбор ответа от классификатора, если результаты классификации содержат результат с уверенностью выше установленной, то сценарий вернет название этого документа
выполнитьJs("""
var docName = '';
var docId = '';
var docScore = 0.0;

try {
    var answerParsed = JSON.parse(answer);
} catch(err) {    
    var answerParsed = {};
}

if (answerParsed.hasOwnProperty('results') && answerParsed.results.length > 0)  {
    if (Math.floor(answerParsed.results[0].score * 100) > minimumScore) {
        docName = answerParsed.results[0]['name'];
        docId = answerParsed.results[0]['document_id'].toString();
        docScore = answerParsed.results[0]['score'];
    }
}
var exit = {'docName':docName, 'docId':docId, 'docScore':docScore};
exit;
""")+
//
// Сценарий оставляет комментарий с результатами классификации для администратора сервиса
комментарий("Результаты классификации: определен документ {docName} с уверенностью {docScore}")+
//
// Сценарий получает полный список документов из базы знаний - классификатора для отображения в переменной диалога
вызвать.внешнийСервис("{kb_crud_endpoint}{kb_product_id}","GET").сЗаголовками(("Content-Type", "application/json"),("charset", "utf-8"),("AUTOFAQ-User-Token", "{autorizationToken}")).сПараметрами(("include_documents","1")).сохранитьРезультатКакСтроку("answerDocs")+
//
// Проверка резльтатов запрос, если запрос завершился с ошибкой сценарий оставляет комментарий для администратора сервиса 
если("{http_code} != 200").то(
  // добавление комментария в диалог и завершение сценария
  комментарий("При запросе на получение списка документов возникла ошибка {http_code}, текст ошибки {answerDocs}")
)+
//
// Разбор ответа из базы знаний, сценарий на выходе сохранит список документов в формате "ID документа" - "Название документа" 
выполнитьJs("""
try {
    var answerParsed = JSON.parse(answerDocs);
} catch(err) {    
    var answerParsed = {};
}

var docs = {};

if (answerParsed.hasOwnProperty('documents') && answerParsed.documents.length > 0) {
    for (var i = 0; i < answerParsed.documents.length; i++)  {
        docs[answerParsed.documents[i].document_id] = answerParsed.documents[i].name;    
    } 
}
var exit = {'docs':JSON.stringify(docs)};
exit;
""") +
//
// Сценарий создает переменню диалога для выбора продукта и предзаполняет его
установитьПеременнуюВДиалог(
    ключ = "service",
    значение = "{docId}",
    название = "Выберите продукт",
    редактируемое = true,
    показыватьОператору = true,
    обязательное = true,
    множественныйВыбор = false,
    тип = "Выпадающий список",
    варианты = "{docs}"
)+
завершить

Автоматическое определение группы

Сценарий автоматически определяет группу по тексту запроса пользователя, что может быть актуально при подключении пользовательского канала одновременно к нескольким группам. В этом случае сервис AutoFAQ не будет запрашивать у пользователя выбор группы, а пределит ее автоматически.

Сценарий последовательно отправит запрос пользователя во все базы знаний каждой группы. Целевой группой для запроса будет назначена та, чьи базы знаний ответят с максимальной уверенностью при условии что уверенность выше установленного минимального порога. Если в процессе автоматического определения возникнет ошибка или все ответы будут ниже минимального порога, то сценарий завершится без установки группы и сервис AutoFAQ предложит пользователю выбрать группу самостоятельно.

При применении сценария важно соблюсти корректную очередность указания баз знаний по группам. Если в начале сценария идентификаторы базы знаний одной группы указаны как принадлежащие группе 1, то в конце сценария важно указать идентификатор этой группы под номером 1. Идентификаторы групп для перевода можно получить в разделе настроек Группы.

 Нажмите здесь, чтобы развернуть пример сценария
//
// Сценарий автоматически определяет группу по тексту запроса пользователя  
// актуально при подключении пользовательского канала одновременно к нескольким группам
// в этом случае АФ не будет запрашивать выбор группы у пользователя 
//
// Настройки сценария
//
// Настройки для проведения классификации запроса пользователя
//
// Адрес API ендпойнтов для запросов баз знаний 
установитьПеременную("kb_qna_enpoint", "https://chat.autofaq.ai/core-api/query/api/v1/query/batch") +
//
// Укажите токен для запроса в базы знаний, брать из поля "user token" в разделе Настройки - Общие - Параметры прямого подключения к базам знаний через API
установитьПеременную("autorizationToken", "ХХХХХХХХХХХХХ")+
//
// Укажите порог минимальной уверенности для автоопределения группы, в процентах
установитьПеременную("minimumConfidence", "50") +
//
// Укажите количество групп
установитьПеременную("groups_amount", "3")+
//
// Укажите ID и токены баз знаний групп - по паре переменных на каждую группу по аналогии
// Данные первой группы 
установитьПеременную("kb_ids_1", "ХХХХ")+
установитьПеременную("kb_tokens_1", "ХХХХХХХХХХХХХХ")+
// Данные второй группы
установитьПеременную("kb_ids_2", "ХХХХ, ХХХХ")+
установитьПеременную("kb_tokens_2", "ХХХХХХХХХХХХХ, ХХХХХХХХХХХХ")+
// Данные третей группы
установитьПеременную("kb_ids_3", "ХХХХ")+
установитьПеременную("kb_tokens_3", "ХХХХХХХХХХХХХХХХХ")+
//
// Сценарий сохраняет текст запроса для отправки в базы знаний
установитьПеременную("queryText", "{platformInMessageQuery}")+
//
выполнитьJs("""
var g_num = Number(groups_amount);
var kb_ids = {};
kb_ids[0] = kb_ids_1.split(',').map(function(item) {return item.trim();});
kb_ids[1] = kb_ids_2.split(',').map(function(item) {return item.trim();});
kb_ids[2] = kb_ids_3.split(',').map(function(item) {return item.trim();});

var kb_tokens = {};
kb_tokens[0] = kb_tokens_1.split(',').map(function(item) {return item.trim();});
kb_tokens[1] = kb_tokens_2.split(',').map(function(item) {return item.trim();});
kb_tokens[2] = kb_tokens_3.split(',').map(function(item) {return item.trim();});

var exit = {};
for (var i = 0; i < g_num; i++) {
    var query_temp = [];
    for (var j = 0; j < kb_ids[i].length; j++) {
        var q = {'service_id':Number(kb_ids[i][j]),'service_token':kb_tokens[i][j],'query':queryText,'top_k':1};
        var z = query_temp.push(q);
    }
    var qq = 'query_text_' + (i+1).toString();
    exit[qq] = JSON.stringify(query_temp);
}
exit;
""")+
//
// Сценарий отправляет запросы на классификацию сообщения пользователя в базы знаний группы 1
вызвать.внешнийСервис("{kb_qna_enpoint}","POST").сЗаголовками(("Content-Type", "application/json"),("charset", "utf-8"),("AUTOFAQ-User-Token", "{autorizationToken}")).сТеломСообщения("{query_text_1}").сохранитьРезультатКакСтроку("answer_1")+
// Проверка резльтатов запрос, если запрос завершился с ошибкой сценарий оставляет комментарий для администратора сервиса 
если("{http_code} != 200").то(  
  комментарий("При запросе на классификацию возникла ошибка {http_code}, текст ошибки {answer_1}")
)+
//
// Сценарий отправляет запросы на классификацию сообщения пользователя в базы знаний группы 2
вызвать.внешнийСервис("{kb_qna_enpoint}","POST").сЗаголовками(("Content-Type", "application/json"),("charset", "utf-8"),("AUTOFAQ-User-Token", "{autorizationToken}")).сТеломСообщения("{query_text_2}").сохранитьРезультатКакСтроку("answer_2")+
// Проверка резльтатов запрос, если запрос завершился с ошибкой сценарий оставляет комментарий для администратора сервиса 
если("{http_code} != 200").то(  
  комментарий("При запросе на классификацию возникла ошибка {http_code}, текст ошибки {answer_2}")
)+
//
// Сценарий отправляет запросы на классификацию сообщения пользователя в базы знаний группы 3
вызвать.внешнийСервис("{kb_qna_enpoint}","POST").сЗаголовками(("Content-Type", "application/json"),("charset", "utf-8"),("AUTOFAQ-User-Token", "{autorizationToken}")).сТеломСообщения("{query_text_3}").сохранитьРезультатКакСтроку("answer_3")+
// Проверка резльтатов запрос, если запрос завершился с ошибкой сценарий оставляет комментарий для администратора сервиса 
если("{http_code} != 200").то(  
  комментарий("При запросе на классификацию возникла ошибка {http_code}, текст ошибки {answer_3}")
)+
//
// Разбор ответов
выполнитьJs("""
var answers = {};
var results = {};
var res = 0.0;
var minimumConfidenceLevel = parseFloat(minimumConfidence) / 100;

try {
    answers[0] = JSON.parse(answer_1);
    answers[1] = JSON.parse(answer_2);
    answers[2] = JSON.parse(answer_3);
} catch(err) {
    answers[0] = [];
    answers[1] = [];
    answers[2] = [];
}
var keys = Object.keys(answers);
for (var i = 0; i < keys.length; i++) {
    res = 0.0;

    for (var j = 0; j < answers[i].length; j++) {
        if (answers[i][j].hasOwnProperty('results') && answers[i][j].results.length > 0) {
            if (answers[i][j].results[0].score > res && answers[i][j].results[0].score > minimumConfidenceLevel) {
                res = answers[i][j].results[0].score;
            }
        }
    }
    results[i] = res;
}
var position = Object.keys(results).reduce(function(a, b){ return results[a] > results[b] ? a : b });
if (results[Object.keys(results)[position]] == 0.0) {position = -1;}
try {
    var group = Number(position) + 1;
} catch(err) {
    var group = 0;
}
var exit = {'group':JSON.stringify(group)};
exit;
""")+
//
// Сценарий определил группу, ответ из баз знаний которой пришел с максимальной уверенностью и переводит запрос на эту группу
// Идентификаторы групп можно получить в разделе настроек Группы
комментарий("Определил группу номер {group}")+
// Сценарий переводит запрос на группу 1
если("{group} == 1").то(
    комментарий("Перевожу чат на группу Вторая линия HR")+
    перевестиНаГруппу("ХХХХХ-ХХХХ-ХХХХ-ХХХХ-ХХХХХ").иЗавершить
)+
// Сценарий переводит запрос на группу 2
если("{group} == 2").то(
    комментарий("Перевожу чат на группу Первая линия")+
    перевестиНаГруппу("ХХХХХ-ХХХХ-ХХХХ-ХХХХ-ХХХХХ").иЗавершить
)+
// Сценарий переводит запрос на группу 3
если("{group} == 3").то(
    комментарий("Перевожу чат на группу Вторая линия IT")+
    перевестиНаГруппу("ХХХХХ-ХХХХ-ХХХХ-ХХХХ-ХХХХХ").иЗавершить
)+
//
// Сценарий не смог определить группу, пользователю будет предложено выбрать группу самому
комментарий("Не смог определить группу")+
завершить

Аутентификация пользователей

Приветственный сценарий проводит аутентификацию пользователя через отправку проверочного кода на электронную почту и блокирует общение пользователя с сервисом в случае если пользователь не прошел аутенетификацию. Может использоваться для работы сервисов внутренней поддержки в публичных каналах, например Телеграм или публично доступный раздел на сайте.

Сценарий проверяет наличие табельного номера в профиле пользователя. Если табельного номера не обнаружено, то сценарий считает что пользователь не аутентифицирован и просит указать табельный номер. По табельному номеру сценария получает зарегистрированный за пользователем адрес электронной почты и отправляет на него проверочный код. Если пользователь введет верный код, то сценарий сохранит табельный номер и адрес электронной почты в профиле пользователя и разрешит работу пользователя с сервисом. В дальнейшем сценарий не будет проводить проверку пользователя.

К сценарию можно добавить регулярную проверку пользователя на увольнение или истечение времени аутентификации.

 Нажмите здесь, чтобы развернуть пример сценария
//
// Приветственный сценарий с авторизацией пользователя и отправкой проверочного кода на почту
//
// Настройки сценария
// Укажите адреса для API вызовов  
установитьПеременную("get_user_url", "https://itsm.company.ru/api/v1/getUser")+
установитьПеременную("post_url", "http://porter/api/postman/mail")+
//
// Укажите API ключ для запросов на получение данных о пользователях
установитьПеременную("apikey", "ХХХХХХХХХ==")+
//
// Сценарий считывает табельный номер из профиля пользователя
установитьПеременную("user_tabNumber", "{userPayload.tabNumber}") +
//
// Если табельный номер проставлен, то сценарий сразу передает запрос в базы знаний без процедуры аутентификации
// В дальнейшем сбда можно вставить проверку времени действия аутентификации или проверку на увольнение сотрудника
если("{user_tabNumber} != ").то(
  завершитьСНовымЗапросом("{platformInMessageQuery}")
)+
//
// Приветственное сообщение для неавторизованных пользователей
сообщениеПользователю.сШаблоном("Привет, я - бот-помощник. Для работы с ботом приготовьте, пожалуйста, свой табельный номер и проверьте доступ к рабочей электронной почте.") +
//
// Задаем пользователю вопрос с просьбой ввести табельный номер, для того чтобы использовать его в сервисах для получения данных о пользователе.
задатьПользователюВопрос("Введите свой табельный номер.").сохранитьРезультат("entered_tabNumber") +
//
// Запрос в ITSM систему на получение данных о пользователе по табельному номеру
вызвать.внешнийСервис("{get_user_url}","GET").сПараметрами(("tabNumber","{entered_tabNumber}")).сЗаголовками(("Content-Type", "application/json"),("apikey","{apikey}")).сохранитьРезультатКакСтроку("answer") +
//
// Проверка результатов запроса, в случае ошибки сценарий сообщит об ошибке, завершится и закроет диалог
если("{http_code} > 200").то(  
    комментарий("При запросе на поиск пользователя возникла ошибка {http_code}, текст ошибки {answer}")+
    сообщениеПользователю.сШаблоном("При поиске учетной записи возникла ошибка {http_code}. Обратитесь в техническую поддержку.")+ 
    завершитьИЗакрытьДиалог()  
  )+
//
// Разбор ответа от ITSM системы, получение адреса электронной почты пользователя и генерация проверочного кода
// В дальнейшем можно добавить получение и сохранение данных пользователя помимо электронной почты
выполнитьJs("""
var error = 0;
var user_email = '';

try {
    var answerParced = JSON.parse(answer);
} catch(err) {    
    var answerParced = {};
}  

if (answerParced.hasOwnProperty('email')) {
  user_email = answerParced.email;
  var splited = user_email.split('@');
  
  var firstPart = splited[0];
  if (firstPart.length > 4) {
    var tempString = firstPart.slice(2,firstPart.length-2);
    var draftString = '***************************************'.slice(0,tempString.length);
    var firstPartString = firstPart.slice(0,2) + draftString + firstPart.slice(firstPart.length-2,firstPart.length);
  }   

  if (firstPart.length == 4) {var firstPartString = firstPart.slice(0,1) + '**';}
  if (firstPart.length == 3) {var firstPartString = firstPart.slice(0,1) + '*';}
  if (firstPart.length == 2) {var firstPartString = firstPart[0] + '*';}
  if (firstPart.length < 2) {var firstPartString =  '*';}

  var res = firstPartString+'@'+splited[1];

  var pass_code = Math.floor(Math.random() * (9999 - 1000 + 1) + 1000);
} else {
  error = 1;
}
    
var exit = {'error':error, 'user_email':user_email, 'user_email_hidden':res, 'pass_code':pass_code};
exit;
""") +
//
// Если при разборе ответа от ITSM системы возникли ошибки, то сценарий сообщит пользователю, завершится и закроет диалог
если("{error} == 1").то(
  комментарий("При разборе ответа возникла ошибка, текст ответа {answer}")+
  сообщениеПользователю.сШаблоном("При поиске учетной записи возникла ошибка. Обратитесь в техническую поддержку.")+ 
  завершитьИЗакрытьДиалог()
) +
//
// Сценарий отправляет письмо с кодом на адрес полученный из ITSM системы
вызвать.внешнийСервис("{post_url}","POST").сЗаголовками(("Content-Type", "application/json")).сТеломСообщения("""{"to": "{user_email}","from":"no-reply@autofaq.ai", "subject":"Подключение к боту","body":"Кто-то пытается подключиться к боту используя ваш табельный номер. Если это сделали не Вы, то обратитесь пожалуйста в службу технической поддержки. Если это вы, то введите этот секретный код {pass_code}. <br>Внимание! Никому не сообщайте этот код!"}""").сохранитьРезультатКакСтроку("answer_email") +
//
// Проверка результатов запроса, в случае ошибки сценарий сообщит об ошибке, завершится и закроет диалог
если("{http_code} > 200").то(  
    комментарий("При отправке письма с проверочным кодом возникла ошибка {http_code}, текст ошибки {answer_email}")+
    сообщениеПользователю.сШаблоном("При отправке письма с проверочным кодом возникла ошибка {http_code}. Пожалуйста, попробуйте подключиться к боту позднее.")+ 
    завершитьИЗакрытьДиалог()  
)+
//
// Сценарий выводит отправленный пользователю пароль и адрес почты в комментарий диалога для администратора сервиса
комментарий("Пользователю на адрес {user_email} был отправлен код {pass_code}")+
//
// Метка для повторного ввода кода
установитьМетку("point_password_enter") +
//
// Сценарий просит пользователя ввести секретный код
задатьПользователюВопрос("На вашу электронную почту {user_email_hidden} был отправлен код. Пожалуйста введите его").сохранитьРезультат("entered_pass_code") +
//
// ПЕрвая проверка корректности введенного кода, если код введен неверно то сценарий переход на метку для повторного ввода, но не более 3 раз
если("{entered_pass_code} != {pass_code}").то(
  сообщениеПользователю.сШаблоном("Вы ввели неверный код. Попробуйте ввести еще раз. Максимальное число попыток - 3.")+  
  перейтиНаМетку("point_password_enter").неБольше(3)
) +
//
// Финальная проверка корректности кода, если на этом этапе код указан неверно, то сценарий сообщит об ошибке, завершится и закроет диалог
если("{entered_pass_code} != {pass_code}").то(
  сообщениеПользователю.сШаблоном("Вы три раза ввели неверный код. Обратитесь в техническую поддержку.")+  
  завершитьИЗакрытьДиалог() 
) +
//
// В случае успешного ввода кода сценарий сохраняет адрес электронной почты и табельный номер в профиле пользователя
установитьПеременнуюПользователю("userPayload.tabNumber", "{entered_tabNumber}") +
установитьПеременнуюПользователю("userEmail", "{user_email}") +
сообщениеПользователю.сШаблоном("Вы успешно подключились к боту.") +
// 
// Проверка наличия ответа на запрос пользователя в базах знаний
запросВБазыЗнаний("{platformInMessageQuery}", "botKnows")+
//
// В базах знаний есть прямой ответ, сценарий передает запрос в базы знаний
если("{botKnows} == true").то(  
  завершитьСНовымЗапросом("{platformInMessageQuery}")
)+
//
// В базах знаний нет прямого ответа на запрос пользователя, сценарий предоставляет выбор основных вариантов действий - пользователь может выбрать один из вариантов или написать запрос еще раз
задатьПользователюВопрос("Не совсем поняла, чем помочь. Вот основные варианты действий - выбери вариант или напиши свой запрос иначе.").сВариантамиОтвета("Создать заявку","Посмотреть список заявок").сохранитьРезультат("userChoice") +
//
// Сценарий передает выбор пользователя в базы знаний и завершается
завершитьСНовымЗапросом("{userChoice}")

Проверка срока действия авторизации

Сценарий является дополнением сценария аутентификации, позволяет записать дату обращения пользователя, сравнить дату обращения пользователя с предыдущей датой, например, для запуска повторной авторизации

 Нажмите здесь, чтобы развернуть пример сценария
//Смещение текущего времени относительно GMT в часах
установитьПеременную("HourOffset", "3")+
//Задаем время в часах для повторной авторизации
установитьПеременную("retryAuth", "8")+
установитьПеременную("dateAuth","{userPayload.dateAuth}")+
//если пользователь ранее не заходил в чат, устанавливаем пользователю дату авторизации
если("{dateAuth} == ").то(
  выполнитьJs("""
  var serverTime = new Date();
  var serverTimeStamp = serverTime.getTime();
  var timeZoneOffset = serverTime.getTimezoneOffset();
  var currentTimeStamp = serverTimeStamp + (parseInt(HourOffset) *  3600000) - (timeZoneOffset * 60000);
  var currentDate = new Date(currentTimeStamp);
  var exit = {'dateAuth':currentDate.toString()};
  exit;
  """) +
  комментарий("{dateAuth}")+
  установитьПеременнуюПользователю("userPayload.dateAuth","{dateAuth}")+
  завершить
)+ 
//если пользователь повторно приходит в чат, выполняем проверку текущего времени и вычисляем разницу в часах между последней авторизацией
выполнитьJs("""
var serverTime = new Date();
var serverTimeStamp = serverTime.getTime();
var timeZoneOffset = serverTime.getTimezoneOffset();
dateAuth = new Date(dateAuth);
var currentTimeStamp = serverTimeStamp + (parseInt(HourOffset) *  3600000) - (timeZoneOffset * 60000);
var currentDate = new Date(currentTimeStamp);
var diff = currentDate - dateAuth;
var hours = diff / (1000 * 60 * 60);
var exit = {'hours':hours};
exit;
""") +
комментарий("тут разница {hours}")+
если("{hours} > {retryAuth}").то(
  комментарий("Разница больще {retryAuth}. Требуется запустить повторную авторизацию")+
  завершить
)+
комментарий("Срок авторизации еще не истек. Можно продолжать диалог")+
завершить

Генерируем ответ для общих вопросов из GPT

Сценарий интеграции на поступление диалога в систему проверяет последовательно каждый вопрос пользователя, пока уровень уверенности предоставленного ответа из БЗ болталка будет равен или выше заданного уровня.

Для подключения сценария необходимо наличие токена для авторизации в GPT

 Click here to expand...
// Адрес API ендпойнтов для отправки вопроса пользователя в БЗ болталка 
установитьПеременную("kb_qna_enpoint", "https://<url_сервиса>/core-api/query/api/v1/query")+
// Укажите токен авторизации для отправки запроса в базы знаний 
установитьПеременную("autorizationToken", "<ID_токен_сервиса>")+
// Укажите порог уверенности, при котором обращаемся в GPT
установитьПеременную("mConfidence", "0.9") +
// Данные БЗ болталка - ИД БЗ болталка и токен БЗ болталка
установитьПеременную("kb_ids", "<ID_БЗ_болталка>")+
установитьПеременную("kb_tokens", "<токен_БЗ_болталка>")+
// Сохраняем текст запроса для отправки в базы знаний
установитьПеременную("queryText", "{platformInMessageQuery}")+
//Адрес API ендпойнтов GPT и токен авторизации 
установитьПеременную("gpt","https://api.openai.com/v1/chat/completions")+
установитьПеременную("autorizationTokengpt","<токен_gpt>")+
//отправляем вопрос пользователя в БЗ болталка
установитьМетку("запросвболталку")+
выполнитьJs("""
var tag = {"service_id":kb_ids,"service_token":kb_tokens,"query":queryText,"top_k":"1"};
var exit = {'tag':JSON.stringify(tag)};
exit;
""")+
вызвать.внешнийСервис("{kb_qna_enpoint}","POST").сЗаголовками(("Content-Type", "application/json"),("charset", "utf-8"),("AUTOFAQ-User-Token", "{autorizationToken}")).сТеломСообщения("{tag}").сохранитьРезультатКакСтроку("uanswer")+
// Проверка резльтатов запрос, если запрос завершился с ошибкой сценарий оставляет комментарий для администратора сервиса 
если("{http_code} != 200").то(  
 комментарий("При запросе на классификацию возникла ошибка {http_code}, текст ошибки {uanswer}")
)+
//
комментарий("{uanswer}")+
//получаем уровень уверенности ответа топ 1 и сравниваем c заданным нами порогом
выполнитьJs("""
var a;
var result = JSON.parse(uanswer);
var score = result.results[0].score;
if (score >= mConfidence){
  a = 1;
}
else {
  a = 0;
}
var exit = {'score':result.results[0].score, 'a':a};
exit;
""")+
//пропускаем обращение в GPT
если("{a} == 0").то(
  завершитьСНовымЗапросом("{queryText}")
)+
//обращаемся в GPT и проверяем следующий вопрос пользователя на наличие ответа в болталке
если("{a} == 1").то(
выполнитьJs("""
var taggpt = {"model":"gpt-3.5-turbo","temperature":0.7,"messages":[{"role":"system","content":"Я умный чатбот, который является почетным представителем компании Autofaq.ai. На вопрос я дам ответ. На оскорбительный вопрос или оскорбления я отвечу вежливо и учтиво. Email для связи с компанией Autofaq.ai - info@autofaq.ai."},{"role": "user","content":queryText}]};
var exit = {'taggpt':JSON.stringify(taggpt)};
exit;
""")+
вызвать.внешнийСервис("{gpt}","POST").сЗаголовками(("Content-Type", "application/json"),("Authorization","Bearer {autorizationTokengpt}")).сТеломСообщения("{taggpt}").сохранитьРезультатКакСтроку("answer")+
выполнитьJs("""
var result = JSON.parse(answer);
var exit = {'gptanswer':result.choices[0].message.content};
exit;
""")+
сообщениеПользователю.сШаблоном("{gptanswer}")+
задатьПользователюВопрос("Подскажите, чем я могу вам помочь?").сохранитьРезультат("queryText")+
перейтиНаМетку("запросвболталку")
)

Работа с API вызовами

Создание инцидента в ITSM системе через REST API

Классификация запроса и создание инцидента в ITSM системе через REST API

Регистрация запроса в CRM системе через REST API

Получение списка открытых инцидентов

Получение баланса по карте лояльности через SOAP вызов

Сценарий по номеру телефона пользователя получает данные о балансе карт в сервисе лояльности.

Сценарий проверяет номер телефона в профиле пользователя. Если номер телефона не указан, то сценарий запросит номер у пользователя и после проверки сохранит его в профиле. Сценарий верифицирует введенный номер телефона отправив сообщение с проверочным кодом через сторонний сервис отправки СМС.

По номеру телефона сценарий запросит наличие карт лояльности в произвольной платформе лояльности. По каждой карте сцеарий запросит ее баланс и выведет суммарный баланс пользователю.

Все текстовки сообщений сценария настраиваются в начале сценария

 Нажмите здесь, чтобы развернуть пример сценария
// Сценарий получения баланса клубной карты пользователя по номеру его телефона
// - сценарий считывает номер телефона из профиля пользователя, если номер в профиле не указан то сценарий запросит его у пользователя
// - сценарий отправит проверочный код через сервис отправки СМС и запрос ввод кода
// - сценарий получит в сервисе лояльности по номеру телефона список карт пользователя, для каждой карты запросит ее баланс и выведет суммарный баланс пользователю  
//
// Настройки сценария
// 
// Тексты сообщений сценария в сторону пользователя
//
// Текст просьбы указать номер телефона
установитьПеременную("messageAskPhoneNumber","Для уточнения информации по клубной карте напишите, пожалуйста, номер телефона, к которому она привязана.") +
// Текст сообщения в случае отсутствия привязанных к номеру телефона карт
установитьПеременную("messageUnknownPhoneNumber","Вы ввели некорректный номер телефона.") +
// Текст сообщения в случае отсутствия привязанных к номеру телефона карт
установитьПеременную("finalMessageUnknownPhoneNumber","Вы дважды ввели некорректный номер телефона. Я не могу вынести такое, поэтому завершаю свою работу.") +
// Текст сообщения об ошибке при отправке СМС
установитьПеременную("messageErrorSMS","К сожалению, мы не смогли отправить СМС-сообщение на указанный вами номер, попробуйте, пожалуйста, позднее.") +
// Текст сообщения c информацией об отправленном СМС
установитьПеременную("messageSMS","На ваш номер телефона отправлен СМС с проверочным кодом.") +
// Текст сообщения c просьбой ввести секретный код
установитьПеременную("messageAskCode","Пожалуйста, напишите проверочный код из СМС.") +
// Текст сообщения об ошибке в присланном коде
установитьПеременную("messageCodeError","Вы ввели неверный код.") +
// Текст финального сообщения об ошибке в присланном коде
установитьПеременную("messageFinalCodeError","Неверный проверочный код.") +
// Текст сообщения в случае отсутствия привязанных к номеру телефона карт
установитьПеременную("messageNoCardsForUser","К этому номеру телефона не привязана клубная карта.") +
// Текст сообщения в случае ошибки в запросе на получение списка привязанных к номеру телефона карт
установитьПеременную("messageUnknownErrorGetCards","При получении списка карт произошла неизвестная ошибка. Попробуйте позже") +
// Текст сообщения в случае ошибки в запросе на получение баланса по одной из карт
установитьПеременную("messageUnknownErrorGetBalance","При получении списка карт произошла неизвестная ошибка. Попробуйте позже") +
// Текст сообщения в случае непридвиденной ошибки 
установитьПеременную("messageUnknownError","Произошла непредвиденная ошибка. Попробуйте запросить позже.") +
// Текст первой части финального сообщения с размером баланса по всем картам, между частями будет вставлен размер баланса 
установитьПеременную("finalMessage_1","На вашей клубной карте <b>") +
// Текст второй части финального сообщения с размером баланса по всем картам
установитьПеременную("finalMessage_2","</b> баллов.") +
// Текст финального сообщения В случае нулевого баланса по карте 
установитьПеременную("finalMessage_zeroBalance","На вашей клубной карте нет баллов. Грустно.") +
//
// Адрес сервиса по отправке СМС
установитьПеременную("sendSMSUrl","https://XXXXXX.ru/send")+
// Адрес сервиса программы лояльности для получения баланса по клубной карте
установитьПеременную("cardRequestUrl","https://XXXXXX.ru/processing")+ //
// Логин и пароль для отправки запросов в сервис программы лояльности в формате base64 
установитьПеременную("query_token","XXXXXXXXXXXXXXXXX")+
//
// Основная часть сценария
// 
// Служебная переменная для хранения суммарного баланса по всем картам
установитьПеременную("finalBalance","0")+
//
// Сценарий считывает номер телефона из профиля пользователя
установитьПеременную("phoneNumber", "{userPhone}") +
//
// Если в профиле пользователя не был указан номер телефона то сценарий запросит его у пользователя 
если("{phoneNumber} == ").то(
	задатьПользователюВопрос("{messageAskPhoneNumber}").сохранитьРезультат("phoneNumber")
)+
// Метка для повторного запроса номера телефона
установитьМетку("ask_phone_number")+
// Сценарий проверяет введеный текст на соотвествие формату номеру телефона, на выходе очищенный от лишних символов номер из 10 цифр без +7 
выполнитьJs("""
var num = phoneNumber.replace(/[^+\d]/g, '');
if (num.length < 10) {
	var numError = 1;
	var number = '';
} else {
	var numError = 0;
	var number = num.substring(num.length-10,num.length);
}
var exit = {'number': number, 'numError':numError};
exit;
""") +
//
// В случае ошибочного формата номера телефона сценарий вернет пользователю просьбу ввести номер повторно и вернется на шаг ввода номера, но не более 2-х раз
если("{numError} != 0").то(
	сообщениеПользователю.сШаблоном("{messageUnknownPhoneNumber}")+
	перейтиНаМетку("ask_phone_number").неБольше(1)
)+ 
//
// Если после двух попыток пользователь все равно ввел неверный номер телефона, то сценарий вернет соответствущее сообщение и завершит работу
если("{numError} != 0").то(
	сообщениеПользователю.сШаблоном("{finalMessageUnknownPhoneNumber}")+
	завершить
)+ 
//
// Сценарий генерирует секретный код для верификации номера телефона через смс 
выполнитьJs("""
var smscode = String(Math.floor(Math.random() * 898) + 101).substring(0,3);
var exit = {'smscode':smscode};
exit;
""") +
//
// Сценарий записывает проверочный комментарий с информацией о отправленном коде
комментарий("Отправлен код {smscode} на номер {number}")+
//
// Сценарий отправляет запрос на отправку СМС с секретным кодом 
вызвать.внешнийСервис("{sendSMSUrl}", "POST").сЗаголовками(("Content-Type", "application/json"), ("charset", "utf-8")).сТеломСообщения("""{"phone":"{number}","auth":"{query_token}","text":"Проверочный код {smscode}"}""").сохранитьРезультат(("Status", "status"),("Error","error")) +
//
// Если запрос вернул ошибку сценарий записывает отладочный комментарий, возвращает пользователю сообщение об ошибке и завершает работу
если("{http_code} > 210").то(
  комментарий("При отправке запроса на отправку СМС возникла ошибка: {http_code}")+
  сообщениеПользователю.сШаблоном("{messageErrorSMS}") +
  завершить
)+
//
// Если в результате попытки отправить СМС сценарий получает ошибку, то сценарий возвращает пользователю соответствующее сообщение и завершается
если("{status} == ERROR").то(
	комментарий("При отправке запроса на отправку СМС произошла ошибка, статус - {http_body}")+
	сообщениеПользователю.сШаблоном("{messageErrorSMS}") +
	завершить
)+
//
// Сценарий сообщает об отправленном СМС с кодом
сообщениеПользователю.сШаблоном("{messageSMS}")+
//
// Метка для повторного запроса секретного кода 
установитьМетку("getCode")+
//
// В случае успешной отправки СМС, сценарий просит пользователя ввести код 
задатьПользователюВопрос("{messageAskCode}").сохранитьРезультат("smsCodeEntered")+
// Сценарий сохраняет отладочный комментарий с текстом введенного кода
комментарий("Введен проверочный код {smsCodeEntered}")+
//
// Сценарий очищает введенный код от возможных служебных символов
выполнитьJs("""
var code = smsCodeEntered.replace(/[^+\d]/g, '');
var exit = {'smsCodeEntered': code};
exit;
""") +
//
// В случае если введенный и отправленный код не совпадают то сценарий возвращает соотвествующее сообщение и переходит обратно на ввод кода, но не более двух раз
если("{smsCodeEntered} != {smscode}").то(
	сообщениеПользователю.сШаблоном("{messageCodeError}")+
	перейтиНаМетку("getCode").неБольше(1)
) +
//
// В случае если пользователь ввел неверный код 2 раза то сценарий сообщает об ошибке и завершает работу
если("{smsCodeEntered} != {smscode}").то(
	сообщениеПользователю.сШаблоном("{messageFinalCodeError}")+
	завершить
)+
//
// Сценарий сохраняет введенный номер телефона в профиле пользователя чтобы не спрашивать в следующий раз
комментарий("Номер телефона +7{number} сохранен в профиле пользователя")+
установитьПеременнуюПользователю("userPhone", "+7{number}") +
//
// Сценарий фомирует запрос на получение баланса списка карт по номеру телефона
выполнитьJs("""
var queryTemplate = 
`<?xml version="1.0" encoding="UTF-8"?>                    
<soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope">
<soapenv:Header/>
<soapenv:Body>
<ProcessRequest xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<request>
<CardRequest>
<RequestID>XXXXXXXX</RequestID>
<DateTime>2022-10-12T07:07:06.21</DateTime>
<Organization>XXXXXXXX</Organization>
<POS>XXXXXXXX</POS>
<Phone>+7${number}</Phone>
</CardRequest>
</request>
<orgName>XXXXXXXX</orgName>
</ProcessRequest>
</soapenv:Body>
</soapenv:Envelope>`
 
var exit = {'queryEncoded': queryTemplate.replace(/(\r\n|\n|\r)/gm,'')};
exit;
""")+
//
// Сценарий отправляет запрос на получение списка карт по номеру телефона в сервис лояльности
вызвать.внешнийСервис("{cardRequestUrl}", "POST").сЗаголовками(("Content-Type", "text/xml;charset=UTF-8"), ("Authorization", "Basic {query_token}")).сТеломСообщения("{queryEncoded}").сохранитьРезультатКакСтроку("answerCardRequest")+
//
// Если запрос вернул ошибку сценарий записывает отладочный комментарий, возвращает пользователю сообщение об ошибке и завершает работу
если("{http_code} > 210").то(
  комментарий("При отправке запроса CardRequest возникла ошибка: {http_code}, {answerCardRequest}")+
  сообщениеПользователю.сШаблоном("{messageUnknownErrorGetCards}")+
  завершить
)+
//
// Сценарий разбирает ответа от сервиса лояльности
выполнитьJs("""
var parser = new marknote.Parser();
var error = 1;
var ReturnCode = '';
var messageFrom = '';
var message = messageUnknownErrorGetCards;
var cardList = [];

try {
	var doc = parser.parse(answerCardRequest);
	var body = doc.getRootElement();
	var CardResponse = body.getChildElement("ProcessRequestResponse").getChildElement("ProcessRequestResult").getChildElement("CardResponse");
	var cards = CardResponse.getChildElements("Card");
	ReturnCode = CardResponse.getChildElement("ReturnCode").getText();
	messageFrom = CardResponse.getChildElement("Message").getText();
} catch(e) {
  ReturnCode = '-1';
}

if (ReturnCode == '1') {
	message = messageNoCardsForUser;
	error = 1;
}

if (ReturnCode == '0') {
	for (var i in cards){ 
		var CardNumber = cards[i].getChildElement("CardNumber").getText(); 
  		var CardTypeID = cards[i].getChildElement("CardTypeID").getText();
  		if (CardTypeID == '1' || CardTypeID == '4') {
  			cardList.push(CardNumber);
  		}  		
	}

	if (cardList.length > 0) {
		message = 'OK';
		error = 0;
	} else {
		message = messageNoCardsForUser;
		error = 1;
	}
}
  		
var exit = {'error': error, 'message': message, 'ReturnCode': ReturnCode, 'cardList': JSON.stringify(cardList), 'messageFrom':messageFrom};
exit;
""")+
//
// Сценарий сохраняет отладочные комментарии с ReturnCode и списком карточек пользователя (отбираются карты только тип 1 и тип 4)
комментарий("Код ответа от сервиса лояльности {ReturnCode}")+
комментарий("Получен список карт пользователя {cardList}")+
//
// Если запрос прошел с ошибкой сценарий сообщит об этом пользователю и завершится. В комментарии будет отображет текст ошибки сервиса лояльности
если("{error} == 1").то(
	сообщениеПользователю.сШаблоном("{message}")+
	комментарий("Запрос неуспешен. Текст ошибки из сервиса лояльности: {messageFrom}")+
	завершить
) +
// 
// Метка для запроса баланса по следующей карте из списка карт 
установитьМетку("balance_request")+
//
// Сценарий выбирает первую карту из списка для получения баланса карты
выполнитьJs("""
try {	
	var cardListParced = JSON.parse(cardList);
} catch(e) {
  var cardListParced = [];
}

var cardId = '';
if (cardListParced.length > 0) {
	cardId = cardListParced.shift();
} 
var cardListLength = cardListParced.length;

var exit = {'cardId': cardId, 'cardList': JSON.stringify(cardListParced), 'cardListLength':cardListLength};
exit;
""")+
//
// Сценарий оставляет отладочный комментарий с номуром выбранной карты
комментарий("Получаем данные о балансе карты {cardId}")+
//
// Аварийная проверка - если номер карта не определен, то сценарий возвращает пользователю сообщение об ошибке и завершается
если("{cardId} == ").то(
	сообщениеПользователю.сШаблоном("{messageUnknownError}")+
	завершить
)+
//
// Сценарий формирует запрос на получение баланса карты
выполнитьJs("""
var queryTemplate = 
`<?xml version="1.0" encoding="UTF-8"?>                    
<soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope">
<soapenv:Header/>
<soapenv:Body>
<ProcessRequest xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<request>
<BalanceRequest>
<RequestID>XXXXXXXX</RequestID>
<DateTime>2022-07-16T08:01:06.21</DateTime>
<Organization>XXXXXXXX</Organization>
<POS>XXXXXXXX</POS>
<Card>
<CardNumber>${cardId}</CardNumber>
</Card>
</BalanceRequest>
</request>
<orgName>XXXXXXXX</orgName>
</ProcessRequest>
</soapenv:Body>
</soapenv:Envelope>`
 
var exit = {'queryEncoded': queryTemplate.replace(/(\r\n|\n|\r)/gm,'')};
exit;
""")+
//
// Сценарий отправляет запрос на получение баланса карты
вызвать.внешнийСервис("{cardRequestUrl}", "POST").сЗаголовками(("Content-Type", "text/xml;charset=UTF-8"), ("Authorization", "Basic {query_token}")).сТеломСообщения("{queryEncoded}").сохранитьРезультатКакСтроку("answerBalanceRequest")+
//
// В случае ошибки при выполнении запроса сценарий записывает отладочный комментарий, сообщает пользователю об ошибке и завершается 
если("{http_code} > 210").то(
  комментарий("При отправке запроса BalanceRequest возникла ошибка: {http_code}, {answerBalanceRequest}")+
  сообщениеПользователю.сШаблоном("{messageUnknownErrorGetCards}")+
  завершить
)+
//  
// Сценарий разбирает ответ системы лояльности c данными баланса по карте, полученный баланс суммируется с общим балансом 
выполнитьJs("""
var parser = new marknote.Parser();
var error = 1;
var ReturnCode = '';
var messageFrom = '';
var balance = 0;
var message = messageUnknownErrorGetBalance;

var _finalBalance = parseInt(finalBalance);
var doc = parser.parse(answerBalanceRequest);
var body = doc.getRootElement();
var BalanceResponse = body.getChildElement("ProcessRequestResponse").getChildElement("ProcessRequestResult").getChildElement("BalanceResponse");

try {
  messageFrom = BalanceResponse.getChildElement("Message").getText();
  ReturnCode = BalanceResponse.getChildElement("ReturnCode").getText();
} catch(e) {
  ReturnCode = '-2';
}

if (ReturnCode != '0') {
	error = 1;
}

if (ReturnCode == '0') {	
	try {
		var CardBalance = BalanceResponse.getChildElement("CardBalance").getText();

		balance = parseInt(CardBalance);
		_finalBalance = _finalBalance + balance;
	} catch(e) {
  		balance = 0;
	}	
}
  		
var exit = {'error': error, 'message': message, 'ReturnCode': ReturnCode, 'balance': balance, 'finalBalance': _finalBalance};
exit;
""")+
//
// Сценарий оставляет отладочный комментарий о полученном статусе ответа и балансе по каждой карте
комментарий("Для карты {cardId} получен код ответа от системы лояльности {ReturnCode} с балансом {balance}, суммарный баланс {finalBalance}")+
//
// Если в списке карт еще остались карты то сценарий переходит назад и запрашивает баланс у системы лояльности до тех пор пока список карт не опустеет 
если("{cardListLength} != 0").то(
	перейтиНаМетку("balance_request").неБольше(5)
)+ 
//
// В случае если баланс по картам нулевой, то сценарий отдает пользователю соотвествующее сообщение и завершает работу 
если("{finalBalance} == 0").то(
	сообщениеПользователю.сШаблоном("{finalMessage_zeroBalance}")+
	завершить
) +
//
// В случае если баланс не нулевой, то сценарий формирует финальное сообщение пользователю с указанием его суммарного баланса и завершает работу
сообщениеПользователю.сШаблоном("{finalMessage_1}{finalBalance}{finalMessage_2}")+
завершить

Получение данных через SQL запрос

Сценарии вызываемые операторами

Добавление и редактирование адреса Email в профиле пользователя

Сценарий позволяет оператору добавить или отредактировать адрес электронной почты в профиле пользователя. Если адрес электронной почты уже указан, то он будет заполнен в поле редактирования.

Сразу после сохранения обновленного адреса комментария он будет сразу обновлен в боковой панели оператора во всех переписках с данным пользователем.

 Click here to expand...
// 
// Сценарий добавления и редактирования комментария в профиле пользователя 
// Сценарий создает переменную пользователя и вносит все правки туда, все изменения будут доступны во всех переписках этого пользователя
// Сценарий предназначен для подключения в Интеграцию на событие "Нажата кнопка оператора"
//
// Сценарий считывает данные из переменной пользователя, если переменная не существует то будет получена пустая строка 
установитьПеременную("_email", "{userEmail}")+
// 
// Сценарий собирает форму для оператора
добавитьПолеВФормуОператора(
  форма = "Обновление данных пользователя",
  переменная = "formName",
  название = "",
  значение = "Редактирование EMAIL пользователя",
  тип = "Нередактируемый текст",
  описание = "",
  обязательное = false
) +
// Добавляем поля пользователя
добавитьПолеВФормуОператора(
  форма = "Обновление данных пользователя",
  переменная = "_email",
  значение = "{_email}",
  название = "Email",
  тип = "Однострочный редактируемый текст",
  описание = "",
  обязательное = true
) +
//  
// Сценарий отображает форму оператора
показатьФормуОператору(имя = "Обновление данных пользователя") +
//
// Сценарий обрабатывает сохранение данных пользователя в случае когда оператор нажимает Продолжить на форме
установитьПеременнуюПользователю("userEmail", "{_email}") +
комментарий("Обовлены данные пользователя") +
//
// Сценарий завершает свою работу
завершить

Добавление и редактирование комментария к профилю пользователя

Сценарий позволяет оператору добавить комментарий к профилю пользователя. Если коментарий уже создан и заполнен, то его текст будет показан оператору.

Сразу после сохранения обновленного текста комментария он будет сразу обновлен в боковой панели оператора во всех переписках с данным пользователем.

 Нажмите здесь, чтобы развернуть пример сценария
// 
// Сценарий добавления и редактирования комментария в профиле пользователя 
// Сценарий создает переменную пользователя и вносит все правки туда, все изменения будут доступны во всех переписках этого пользователя
// Сценарий предназначен для подключения в Интеграцию на событие "Нажата кнопка оператора"
//
// Сценарий считывает данные из переменной пользователя, если переменная не существует то будет получена пустая строка 
установитьПеременную("_comment", "{userPayload.comment}")+
// 
// Сценарий собирает форму для оператора
добавитьПолеВФормуОператора(
  форма = "Обновление данных пользователя",
  переменная = "formName",
  название = "",
  значение = "Редактирование комментария о пользователе",
  тип = "Нередактируемый текст",
  описание = "",
  обязательное = false
) +
// Добавляем поля пользователя
добавитьПолеВФормуОператора(
  форма = "Обновление данных пользователя",
  переменная = "_comment",
  значение = "{_comment}",
  название = "Комментарий",
  тип = "Многострочный редактируемый текст",
  описание = "Комментарий",
  обязательное = true
) +
//  
// Сценарий отображает форму оператора
показатьФормуОператору(имя = "Обновление данных пользователя") +
//
// Сценарий обрабатывает сохранение данных пользователя в случае когда оператор нажимает Продолжить на форме
установитьПеременнуюПользователю("userPayload.comment", "{_comment}") +
комментарий("Обовлены данные пользователя") +
//
// Сценарий завершает свою работу
завершить

Отправка текста переписки на электронную почту

Сценарий позволяет оператору отправить текст переписки и все файлы из переписки на электронную почту.

Сценарий может быть использован как для отправки переписки самому пользователю так и для отправки текста переписки в произвольные внешние службы. Сценарий поддерживает форматирование письма HTML или plain-text.

 Нажмите здесь, чтобы развернуть пример сценария

// Сценарий отправляет на указанный адрес текст переписки с пользователем.
// Дополнительно к пистму прикладываются все файлы в переписке меньше установленного размера.
// Сценарий может быть использован как для отправки переписки самому пользователю так и для отправки письма в сторонний сервис. 
//
// Настройки сценария
// 
// Формат текста письма. Установите true для HTML и false для обычного текста.
установитьПеременную("formated_text", "true") +
// Максимальный размер файлов для отправки в качестве вложения. Сценарий сообщит о пропущенных файлах и оператору и получателю письма.
установитьПеременную("maximumFileSize", "10000") +
// Предустановка адреса email пользователя в качестве получателя для быстрой отправки переписки пользователю на его почту.
установитьПеременную("useUserEmail", "true") +
// Адрес отправителя 
установитьПеременную("from_email", "noreply@autofaq.ai") +
// Предустановленная тема письма. Оператор сможет сменить тему перед отправкой письма.
установитьПеременную("subject", "Переписка с пользователем {userFullName}")+
//
// Адрес сервиса по отправке писем, встроенного в AutoFAQ
установитьПеременную("host", "http://porter/api/postman/mail") +
//
// Основной код сценария
//
// Сценарий предустанавливает адрес получателя из данных пользователя или оставляет поле пустым
если("{useUserEmail} == true").то(
  установитьПеременную("_email", "{userEmail}") 
)+
// Сценарий собирает форму для сбора данных у пользователя - адрес и комментарий к письму
добавитьПолеВФормуОператора(
  форма = "Отправка текста переписки на электронную почт",
  переменная = "formName",
  название = "",
  значение = "Сценарий отправит электронное письмо с текстом переписки на указанный адрес. Все файлы в переписке будут приложены к письму.",
  тип = "Нередактируемый текст",
  описание = "",
  обязательное = false
) +
добавитьПолеВФормуОператора(
  форма = "Отправка текста переписки на электронную почт",
  переменная = "email",
  значение = "{_email}",
  название = "Укажите адрес email",
  тип = "Однострочный редактируемый текст",
  обязательное = true
) +
добавитьПолеВФормуОператора(
  форма = "Отправка текста переписки на электронную почт",
  переменная = "subject",
  значение = "{subject}",
  название = "Укажите тему писма",
  тип = "Однострочный редактируемый текст",
  обязательное = true
) +
добавитьПолеВФормуОператора(
  форма = "Отправка текста переписки на электронную почт",
  переменная = "comment",
  значение = "",
  название = "Комментарий к письму",
  тип = "Многострочный редактируемый текст",
  обязательное = false
) +
//  
// Сценарий отображает форму для опроса оператора
показатьФормуОператору(имя = "Отправка текста переписки на электронную почт") +
//
// Сценарий проверяет файлы в переписке и формирует ссылки для отправки файлов
выполнитьJs("""
try {
  var parsedExtFiles = JSON.parse(externalFiles);
} catch(e) {
  var parsedExtFiles = [];
}

var numOfFiles = parsedExtFiles.length;
var filesMessage = '';
var filesUrls = [];
var sentFilesNum = 0;

if (numOfFiles > 0) {
  for (var f = 0; f < numOfFiles; f++) {
    if (parseInt(JSON.parse(parsedExtFiles[f])["size"]) < parseInt(maximumFileSize)) {      
      filesUrls.push( 'http://bot-platform-back:8090/api/files/' + JSON.parse(parsedExtFiles[f])['id'] );
      sentFilesNum += 1;
    } else {
      filesMessage = filesMessage + (JSON.parse(parsedExtFiles[f])['name']) + ', ';      
    }
  }
}

if (filesMessage != '') {
  filesMessage = 'При вложении были пропущены файлы ' + filesMessage.slice(0,filesMessage.length-2) + ' из-за ограничения по размеру вложений ' + maximumFileSize + ' байт';
} 
if (sentFilesNum > 0) {
    filesMessage = 'К письму были приложены файлы - ' + sentFilesNum.toString() + ' шт. ' + filesMessage;
} else {
  filesMessage = 'К письму нет вложенных файлов. ' + filesMessage;
}

var exit = {'filesMessage': filesMessage, 'filesUrls': filesUrls};
exit;
""") +
//
// Сценарий сообщает оператору о вложенных и пропущенных файлах
комментарий("{filesMessage}") +
//
// Сценарий получает историю сообщений и формирует запрос на отправку письма
выполнитьJs("""
var rawText = '';

var msgTypes = ['Question', 'AnswerOperator', 'AnswerChatterbox','AnswerOperatorWithBot','AnswerOperator', 'AnswerBot']; 
var labelMap = {
    'Question': 'Пользователь:',
    'AnswerOperator': 'Оператор:',
    'AnswerBot': 'Бот:',
    'AnswerChatterbox': 'Бот:',
    'AnswerOperatorWithBot': 'Оператор:'
  }

if (conversation.channelUser.fullName) {
  if (conversation.channelUser.email) {
    var line_1 = '<div>Переписка с пользователем <b>' + conversation.channelUser.fullName + '</b> (' + conversation.channelUser.email + ')</div>';
  } else {
    var line_1 = '<div>Переписка с пользователем <b>' + conversation.channelUser.fullName + '</b> (адрес EMAIL не зарегистрирован)</div>';
  }
} else {
  if (conversation.channelUser.email) {
    var line_1 = '<div>Переписка с пользователем ' + conversation.channelUser.email + ' (ФИО не указано)</div>';
  } else {
    var line_1 = '<div>Переписка с неизвестным пользователем</div>'
  }
}

if (conversation.messages.length > 0) {
  var first_event = conversation.messages[0];
  var line_2 = '<div>Дата начала диалога ' + first_event.ts.slice(8,10) + '.' + first_event.ts.slice(5,7) + '.' + first_event.ts.slice(0,4);
  line_2 = line_2 + ' ' + first_event.ts.slice(11,13) + ':' + first_event.ts.slice(14,16) + '</div><br>';
} else {
  var line_2 = '<div>Дата начала диалога не указана</div><br>';
}

if (comment == '' || comment == 'NaN') {
  var line_3 = '<div>Оператор не оставил комментарий к переписке.</div>';  
} else {
  var line_3 = '<div><b>Комментарий оператора к переписке:</b> ' + comment + '</div>';
}
line_3 = line_3 + '<div>' + filesMessage + '</div><br>';

var messages = conversation.messages.filter(function (str) {return  msgTypes.indexOf(str.tpe) > -1;});
for (var i = 0; i < messages.length; i++) {
  var message = messages[i];
  var label = labelMap[message.tpe] || '';
  if (label) {
    if (formated_text != 'true') {
      clean_txt = message.txt.replace(/<a[^>]*href="([^"]+)"[^>]*>(?:.*?<\/a>)?/g, '$1').replace(/<\/?[^>]+(>|$)/g, "").replace(/(\r\n|\n|\r)/gm, '\n');
      rawText = rawText + '\n' + message.ts.slice(11,16) + ' ' + label + '\n' + clean_txt;
    } else {
      clean_txt = message.txt.replace(/(\r\n|\n|\r)/gm, '');
      rawText = rawText + '<div>' + message.ts.slice(11,16) + ' ' + label + '</div><div>' + clean_txt + '</div>';
    }
    
  }
}
if (rawText == '') {
  var line_4 = '<div><b>Содержание переписки недоступно.</b></div>'
} else {
  var line_4 = '<div><b>Содержание переписки</b></div>';
}

if (formated_text == 'true') {
  var final_text = '<html><head></head><body>' + line_1 + line_2 + line_3 + line_4 + rawText + '</body></html>';
} else {
  var final_text = line_1 + '\n' + line_2 + '\n' + line_3 + '\n\n' + line_4;
  final_text = final_text.replace(/<\/?[^>]+(>|$)/g, "") + '\n' + rawText;
}

var request = {
  from: from_email,
  to: email,
  subject: subject,
  body: final_text,
  files: JSON.parse(filesUrls)
}

var exit = {'request': JSON.stringify(request)};
exit;
""")+
// 
// Сценарий отправляет запрос на формирование письма
вызвать.внешнийСервис("{host}", "POST").сЗаголовками(("Content-Type","application/json")).сТеломСообщения("{request}").сохранитьРезультатКакСтроку("answer")+
// 
// Если сервис по отправке писем ответил ошибкой сценарий сообщает об этом пользователю и завершает работу
если("{http_code} > 202").то(
  //если запрос завершен с ошибкой сценарий помещает в диалог соответствующий комментарий и завершает свою работу
  комментарий("При отправке письма возникла ошибка {http_code} - {answer}")+
  завершить
)+
//
// Если сервис по отправке писем успешно отправляет письмо то сценарий сообщает об отправке и завершает свою работу
комментарий("Письмо с перепиской отправлено на адрес {email}") + 
завершить

Сценарий позволяет оператору отправить текст переписки и выбранные файлы из переписки на электронную почту. Текущий пример отображает до пяти вложений.

 Нажмите здесь, чтобы развернуть пример сценария
// Сценарий отправляет на указанный адрес текст переписки с пользователем.
// Дополнительно к письму прикладываются файлы, выбранные оператором.
// Сценарий может быть использован как для отправки переписки самому пользователю так и для отправки письма в сторонний сервис. 
//
// Настройки сценария
// 
// Формат текста письма. Установите true для HTML и false для обычного текста.
установитьПеременную("formated_text", "true") +
// Максимальный размер файлов для отправки в качестве вложения. Сценарий сообщит о пропущенных файлах и оператору и получателю письма.
установитьПеременную("useUserEmail", "true") +
// Адрес отправителя 
установитьПеременную("from_email", "noreply@autofaq.ai") +
// Предустановленная тема письма. Оператор сможет сменить тему перед отправкой письма.
установитьПеременную("subject", "Переписка с пользователем {userFullName}")+
//
// Адрес сервиса по отправке писем, встроенного в AutoFAQ
установитьПеременную("host", "http://porter/api/postman/mail") +
//дополнительные переменные
установитьПеременную("t1","false")+
установитьПеременную("t2","false")+
установитьПеременную("t3","false")+
установитьПеременную("t4","false")+
установитьПеременную("t5","false")+
//
// Основной код сценария
//
// Сценарий проверяет файлы в переписке
выполнитьJs("""
try {
  var parsedExtFiles = JSON.parse(externalFiles);
} catch(e) {
  var parsedExtFiles = [];
}
var numOfFiles = parsedExtFiles.length;
var filesUrl_01 = ''; var filesUrl_name_01 = '';var filesUrl_size_01 = '';
var filesUrl_02 = ''; var filesUrl_name_02 = '';var filesUrl_size_02 = '';
var filesUrl_03 = ''; var filesUrl_name_03 = '';var filesUrl_size_03 = '';
var filesUrl_04 = ''; var filesUrl_name_04 = '';var filesUrl_size_04 = '';
var filesUrl_05 = ''; var filesUrl_name_05 = '';var filesUrl_size_05 = '';
var idx = 0;
var contentIndex = 0;
if (numOfFiles > 5) {
  numOfFiles = 5;
}
if (numOfFiles > 0) {
  for (var i = 0; i < numOfFiles; i++) {
      idx = idx + 1;
      if (idx == 1) {filesUrl_01 = 'http://bot-platform-back:8090/api/files/' + JSON.parse(parsedExtFiles[i])['id'];filesUrl_name_01 =JSON.parse(parsedExtFiles[i])['name'];contentIndex = 1;filesUrl_size_01 = JSON.parse(parsedExtFiles[i])['size'];}
      if (idx == 2) {filesUrl_02 = 'http://bot-platform-back:8090/api/files/' + JSON.parse(parsedExtFiles[i])['id'];filesUrl_name_02 =JSON.parse(parsedExtFiles[i])['name'];contentIndex = 2;filesUrl_size_02 = JSON.parse(parsedExtFiles[i])['size'];}
      if (idx == 3) {filesUrl_03 = 'http://bot-platform-back:8090/api/files/' + JSON.parse(parsedExtFiles[i])['id'];filesUrl_name_03 =JSON.parse(parsedExtFiles[i])['name'];contentIndex = 3;filesUrl_size_03 = JSON.parse(parsedExtFiles[i])['size'];}
      if (idx == 4) {filesUrl_04 = 'http://bot-platform-back:8090/api/files/' + JSON.parse(parsedExtFiles[i])['id'];filesUrl_name_04 =JSON.parse(parsedExtFiles[i])['name'];contentIndex = 4;filesUrl_size_04 = JSON.parse(parsedExtFiles[i])['size'];}
      if (idx == 5) {filesUrl_05 = 'http://bot-platform-back:8090/api/files/' + JSON.parse(parsedExtFiles[i])['id'];filesUrl_name_05 =JSON.parse(parsedExtFiles[i])['name'];contentIndex = 5;filesUrl_size_05 = JSON.parse(parsedExtFiles[i])['size'];}
    }
}
var exit = {'contentIndex': contentIndex, 'filesUrl_01':filesUrl_01, 'filesUrl_02':filesUrl_02, 'filesUrl_03':filesUrl_03, 'filesUrl_04':filesUrl_04, 'filesUrl_05':filesUrl_05, 'filesUrl_name_01':filesUrl_name_01, 'filesUrl_name_02':filesUrl_name_02, 'filesUrl_name_03':filesUrl_name_03, 'filesUrl_name_04':filesUrl_name_04, 'filesUrl_name_05':filesUrl_name_05, 'filesUrl_size_01': filesUrl_size_01, 'filesUrl_size_02':filesUrl_size_02,'filesUrl_size_03':filesUrl_size_03,'filesUrl_size_04':filesUrl_size_04,'filesUrl_size_05':filesUrl_size_05};
exit;
""") +
//отображаем список файлов для выбора
если("{contentIndex} > 0").то(
  добавитьПолеВФормуОператора(
  форма = "Отправка текста переписки на электронную почт",
  переменная = "t1_txt",
  значение = "{filesUrl_name_01} размер {filesUrl_size_01} b",
  название = "",
  тип = "Однострочный нередактируемый текст",
  обязательное = false
) +
  добавитьПолеВФормуОператора(
  форма = "Отправка текста переписки на электронную почт",
  переменная = "t1",
  название = "",
  тип = "Переключатель",
  описание = "",
  обязательное = false
)
)+
если("{contentIndex} > 1").то(
  
  добавитьПолеВФормуОператора(
  форма = "Отправка текста переписки на электронную почт",
  переменная = "t2_txt",
  значение = "{filesUrl_name_02} размер {filesUrl_size_02} b",
  название = "",
  тип = "Однострочный нередактируемый текст",
  обязательное = false
) +
  добавитьПолеВФормуОператора(
  форма = "Отправка текста переписки на электронную почт",
  переменная = "t2",
  название = "",
  тип = "Переключатель",
  описание = "",
  обязательное = false
)
)+
если("{contentIndex} > 2").то(
  добавитьПолеВФормуОператора(
  форма = "Отправка текста переписки на электронную почт",
  переменная = "t3_txt",
  значение = "{filesUrl_name_03} размер {filesUrl_size_03} b",
  название = "",
  тип = "Однострочный нередактируемый текст",
  обязательное = false
) +
  добавитьПолеВФормуОператора(
  форма = "Отправка текста переписки на электронную почт",
  переменная = "t3",
  название = "",
  тип = "Переключатель",
  описание = "",
  обязательное = false
)
)+
если("{contentIndex} > 3").то(
  добавитьПолеВФормуОператора(
  форма = "Отправка текста переписки на электронную почт",
  переменная = "t4_txt",
  значение = "{filesUrl_name_04} размер {filesUrl_size_04} b",
  название = "",
  тип = "Однострочный нередактируемый текст",
  обязательное = false
) +
  добавитьПолеВФормуОператора(
  форма = "Отправка текста переписки на электронную почт",
  переменная = "t4",
  название = "",
  тип = "Переключатель",
  описание = "",
  обязательное = false
)
)+
если("{contentIndex} == 5").то(
  добавитьПолеВФормуОператора(
  форма = "Отправка текста переписки на электронную почт",
  переменная = "t5_txt",
  значение = "{filesUrl_name_05} размер {filesUrl_size_05} b",
  название = "",
  тип = "Однострочный нередактируемый текст",
  обязательное = false
) +
  добавитьПолеВФормуОператора(
  форма = "Отправка текста переписки на электронную почт",
  переменная = "t5",
  название = "",
  тип = "Переключатель",
  описание = "",
  обязательное = false
)
)+
// Сценарий предустанавливает адрес получателя из данных пользователя или оставляет поле пустым
если("{useUserEmail} == true").то(
  установитьПеременную("_email", "{userEmail}") 
)+
// Сценарий собирает форму для сбора данных у пользователя - адрес и комментарий к письму
добавитьПолеВФормуОператора(
  форма = "Отправка текста переписки на электронную почт",
  переменная = "formName",
  название = "",
  значение = "Сценарий отправит электронное письмо с текстом переписки на указанный адрес. Выбранные файлы в переписке будут приложены к письму.",
  тип = "Нередактируемый текст",
  описание = "",
  обязательное = false
) +
добавитьПолеВФормуОператора(
  форма = "Отправка текста переписки на электронную почт",
  переменная = "email",
  значение = "{_email}",
  название = "Укажите адрес email",
  тип = "Однострочный редактируемый текст",
  обязательное = true
) +
добавитьПолеВФормуОператора(
  форма = "Отправка текста переписки на электронную почт",
  переменная = "subject",
  значение = "{subject}",
  название = "Укажите тему письма",
  тип = "Однострочный редактируемый текст",
  обязательное = true
) +
добавитьПолеВФормуОператора(
  форма = "Отправка текста переписки на электронную почт",
  переменная = "comment",
  значение = "",
  название = "Комментарий к письму",
  тип = "Многострочный редактируемый текст",
  обязательное = false
) +
//  
// Сценарий отображает форму для опроса оператора
показатьФормуОператору(имя = "Отправка текста переписки на электронную почт") +
//
//собирает выбранные вложения для отправки в письме
комментарий("{filesUrl_01}")+
выполнитьJs("""
var filesUrls = [];
if (t1 == 'true'){
filesUrls.push(filesUrl_01);
}
if (t2 == 'true'){
filesUrls.push(filesUrl_02);
}
if (t3 == 'true'){
filesUrls.push(filesUrl_03);
}
if (t4 == 'true'){
filesUrls.push(filesUrl_04);
}
if (t5 == 'true'){
filesUrls.push(filesUrl_05);
}
var exit = {'filesUrls' : filesUrls};
exit;
""")+
//
// Сценарий получает историю сообщений и формирует запрос на отправку письма
выполнитьJs("""
var rawText = '';

var msgTypes = ['Question', 'AnswerOperator', 'AnswerChatterbox','AnswerOperatorWithBot','AnswerOperator', 'AnswerBot']; 
var labelMap = {
    'Question': 'Пользователь:',
    'AnswerOperator': 'Оператор:',
    'AnswerBot': 'Бот:',
    'AnswerChatterbox': 'Бот:',
    'AnswerOperatorWithBot': 'Оператор:'
  }

if (conversation.channelUser.fullName) {
  if (conversation.channelUser.email) {
    var line_1 = '<div>Переписка с пользователем <b>' + conversation.channelUser.fullName + '</b> (' + conversation.channelUser.email + ')</div>';
  } else {
    var line_1 = '<div>Переписка с пользователем <b>' + conversation.channelUser.fullName + '</b> (адрес EMAIL не зарегистрирован)</div>';
  }
} else {
  if (conversation.channelUser.email) {
    var line_1 = '<div>Переписка с пользователем ' + conversation.channelUser.email + ' (ФИО не указано)</div>';
  } else {
    var line_1 = '<div>Переписка с неизвестным пользователем</div>'
  }
}

if (conversation.messages.length > 0) {
  var first_event = conversation.messages[0];
  var line_2 = '<div>Дата начала диалога ' + first_event.ts.slice(8,10) + '.' + first_event.ts.slice(5,7) + '.' + first_event.ts.slice(0,4);
  line_2 = line_2 + ' ' + first_event.ts.slice(11,13) + ':' + first_event.ts.slice(14,16) + '</div><br>';
} else {
  var line_2 = '<div>Дата начала диалога не указана</div><br>';
}

if (comment == '' || comment == 'NaN') {
  var line_3 = '<div>Оператор не оставил комментарий к переписке.</div>';  
} else {
  var line_3 = '<div><b>Комментарий оператора к переписке:</b> ' + comment + '</div>';
}
line_3 = line_3 + '<br>';

var messages = conversation.messages.filter(function (str) {return  msgTypes.indexOf(str.tpe) > -1;});
for (var i = 0; i < messages.length; i++) {
  var message = messages[i];
  var label = labelMap[message.tpe] || '';
  if (label) {
    if (formated_text != 'true') {
      clean_txt = message.txt.replace(/<a[^>]*href="([^"]+)"[^>]*>(?:.*?<\/a>)?/g, '$1').replace(/<\/?[^>]+(>|$)/g, "").replace(/(\r\n|\n|\r)/gm, '\n');
      rawText = rawText + '\n' + message.ts.slice(11,16) + ' ' + label + '\n' + clean_txt;
    } else {
      clean_txt = message.txt.replace(/(\r\n|\n|\r)/gm, '');
      rawText = rawText + '<div>' + message.ts.slice(11,16) + ' ' + label + '</div><div>' + clean_txt + '</div>';
    }
    
  }
}
if (rawText == '') {
  var line_4 = '<div><b>Содержание переписки недоступно.</b></div>'
} else {
  var line_4 = '<div><b>Содержание переписки</b></div>';
}

if (formated_text == 'true') {
  var final_text = '<html><head></head><body>' + line_1 + line_2 + line_3 + line_4 + rawText + '</body></html>';
} else {
  var final_text = line_1 + '\n' + line_2 + '\n' + line_3 + '\n\n' + line_4;
  final_text = final_text.replace(/<\/?[^>]+(>|$)/g, "") + '\n' + rawText;
}

var request = {
  from: from_email,
  to: email,
  subject: subject,
  body: final_text,
  files: JSON.parse(filesUrls)
}

var exit = {'request': JSON.stringify(request)};
exit;
""")+
// 
// Сценарий отправляет запрос на формирование письма
вызвать.внешнийСервис("{host}", "POST").сЗаголовками(("Content-Type","application/json")).сТеломСообщения("{request}").сохранитьРезультатКакСтроку("answer")+
// 
// Если сервис по отправке писем ответил ошибкой сценарий сообщает об этом пользователю и завершает работу
если("{http_code} > 202").то(
  //если запрос завершен с ошибкой сценарий помещает в диалог соответствующий комментарий и завершает свою работу
  комментарий("При отправке письма возникла ошибка {http_code} - {answer}")+
  завершить
)+
//
// Если сервис по отправке писем успешно отправляет письмо то сценарий сообщает об отправке и завершает свою работу
комментарий("Письмо с перепиской отправлено на адрес {email}") + 
завершить

Работа со спамом

Эти сценарии позволяют оператору установить признак “спам” пользователю, для закрытия всех следующих диалогов пользователя диалогов.

1. Пометить как “СПАМ”

Необходимо добавить сценарий интеграцию на событие “Нажатие кнопки оператором”, добавить кнопку в настройках оператора для выполнения интеграции.

Данный сценарий позволяет оператору установить признак “СПАМ” пользователю

 Нажмите здесь, чтобы развернуть пример сценария
установитьПеременнуюПользователю("userPayload.SPAM", "1")+
комментарий("Пользователь отмечен как СПАМ, можно закрывать диалог. Все последующие обращения будут закрываться автоматически")+
завершить

2. Закрыть “СПАМ”

Необходимо добавить сценарий интеграцию на событие Поступление диалога в систему.

При поступлении диалога будет выполняться проверка наличия признака “СПАМ” у пользователя. При наличии признака диалог будет закрываться.

 Нажмите здесь, чтобы развернуть пример сценария
если("{userPayload.SPAM} == 1").то(
завершитьИЗакрытьДиалог()
)+
завершить

3. Снять признак “СПАМ”

Необходимо добавить сценарий интеграцию на событие “Нажатие кнопки оператором”, добавить кнопку в настройках оператора для выполнения интеграции.

Для снятия признака “СПАМ” оператору необходимо будет инициировать диалог с пользователем, отмеченным как “СПАМ”, и выбрать действие “Снять признак СПАМ”

 Нажмите здесь, чтобы развернуть пример сценария
установитьПеременнуюПользователю("userPayload.SPAM", "")+
комментарий("С пользователя снята пометка СПАМ")+
завершить

Закрытие диалога без оценки

Сценарий позволяет оператору закрыть диалог без вызова сбора оценки.

Сценарий добавляем в базу типа “только рекомендации”. Для закрытия диалога без сбора оценки оператор вызывает данный сценарий через функционал расширенного поиска:

 Нажмите здесь, чтобы развернуть пример сценария
сообщениеПользователю.сШаблоном("Спасибо за общение, диалог будет закрыт без оценки")+
завершитьИЗакрытьДиалог(отключитьОценку = true)

Работа с таблицами Google Sheets

Простой поиск в таблице

Сценарий запрашивает у пользователя данные для поиска, ищет указанный текст по всем строкам и колонкам таблицы и сообщает о результатах поиска в формате “Да” или “Нет”

 Нажмите здесь, чтобы развернуть пример сценария
//
// Сценарий ищет текст в таблице Google Sheets по значению в любой колонке и возвращает сообщение "Да" если текст найден или "Нет" если не найден
//
// Укажите логин сервисного аккаунта Google
установитьПеременную("email", "autofaqaccount@autofaqproject.iam.gserviceaccount.com") +
// Укажите идентификатор таблицы Google Sheet
установитьПеременную("sheetId", "1UyTG1ag6kb-tKCD0Wc5nD21zE38TvYSiMMQ_vzwxWbs")+
// Укажите название листа в таблице Google Sheet
установитьПеременную("sheetName", "Sheet1")+
//
// Настройка источника текста для поиска - спросить у пользователя или взять текст изначального запроса
// запрос - изначальный запрос
// вопрос - спросить у пользователя
установитьПеременную("querySource", "вопрос")+
//
// Основной код сценария 
//
// Получение текста для поиска в таблице
если("{querySource} == запрос").то(
	установитьПеременную("searchQuery", "{platformInMessageQuery}")
)+
если("{querySource} == вопрос").то(
	задатьПользователюВопрос("Скажи номер").сохранитьРезультат("searchQuery")
)+
//
// Получение временного токена для работы с гугл таблицей
вызвать.внешнийСервис("https://denisk.autofaq.ai/gtoken","GET").сЗаголовками(("Content-Type", "application/json"),("charset", "utf-8")).сПараметрами(("email","{email}")).сохранитьРезультат(("message","message"),("token","token"),("status","status"))+
//
// Проверка результатов запроса, в случае ошибки сценарий сообщит об ошибке и завершится 
если("{http_code} > 210").то(
	комментарий("Сценарий не смог получить токен для работы с таблицей. {message}")+
	сообщениеПользователю.сШаблоном("Спасибо за участие, но что-то сломалось и я не могу связаться с Гугл.")+
	завершить
)+
если("{status} == error").то(
	комментарий("Сценарий не смог получить токен для работы с таблицей. {message}")+
	сообщениеПользователю.сШаблоном("Спасибо за опрос, но что-то сломалось и я не смог сохранить результаты.")+
	завершить
)+
//
// Отправка данных в гугл таблицу
вызвать.внешнийСервис("https://sheets.googleapis.com/v4/spreadsheets/{sheetId}/values/{sheetName}","GET").сЗаголовками(("Authorization", "Bearer {token}"),("Content-Type","application/json")).сПараметрами(("majorDimension", "COLUMNS")).сохранитьРезультатКакСтроку("result")+
если("{http_code} > 210").то(
	комментарий("Сценарий не смог отправить запрос на поиск в гугл таблице. Код {http_code}, ответ {result}")+
	сообщениеПользователю.сШаблоном("Спасибо за участие, но что-то сломалось и я не могу связаться с Google.")+
	завершить
)+
//
// Разбор ответа от гугл таблицы
выполнитьJs("""
var error = 0;
var message = '';
var rowIndex = 0;

try {
    var parsedresult = JSON.parse(result);
} catch(err) {
    var exit = {'error': 1, 'message':'Ошибка разбора ответа от Гугл'};
    var parsedresult = {};
}

if (parsedresult.hasOwnProperty('values')) {

    for (var i = 0; i < parsedresult.values.length; i++) {
        rowIndex = rowIndex + (parsedresult.values[i].indexOf(searchQuery) > -1 ? 1 : 0);
    }

    if (rowIndex > 0) { 
        var exit = {'error': 0, 'message':'Да'};
    } else {
        var exit = {'error': 0, 'message':'Нет'};
    }

} else {
    var exit = {'error': 1, 'message':'В ответе из таблицы не найдено данных'};
}

exit;
""") +
//
// В случае ошибки при поиске данных сценарий сообщит об ошибке и завершится
если("{error} == 1").то(
    сообщениеПользователю.сШаблоном("При получении данных из таблицы возникла ошибка. {message}")+
    завершить
) +
//
// Если при поиске данных ошибки не возникло, то сценарий сообщит о результате поиска и завершится 
сообщениеПользователю.сШаблоном("{message}")+
завершить

Поиск в таблице и возврашение всех данных из строки с заголовками

Сценарий запрашивает у пользователя данные для поиска, ищет указанный текст в первой колонке и возвращает данные из всей строки в формате “Заголовок колонки”: “Значение в строке”

 Нажмите здесь, чтобы развернуть пример сценария
//
// Сценарий ищет строку в таблице Google Sheets по значению в первой колонке и возвращает данные из всей строки
//
// Укажите логин сервисного аккаунта Google
установитьПеременную("email", "account_name@project_name.iam.gserviceaccount.com") +
// Укажите идентификатор таблицы Google Sheet
установитьПеременную("sheetId", "ХХХХХХ-ХХХХХХХХХХХХХ")+
// Укажите название листа в таблице Google Sheet
установитьПеременную("sheetName", "Sheet1")+
//
// Укажите источник текста для поиска - спросить у пользователя или взять текст изначального запроса
// запрос - текст запроса, с которым пользователь пришел в сценария
// вопрос - спросить у пользователя
установитьПеременную("querySource", "вопрос")+
//
// Основной код сценария 
//
// Получение текста для поиска в таблице
если("{querySource} == запрос").то(
	установитьПеременную("searchQuery", "{platformInMessageQuery}")
)+
если("{querySource} == вопрос").то(
	задатьПользователюВопрос("Скажи номер").сохранитьРезультат("searchQuery")
)+
//
// Получение временного токена для работы с гугл таблицей
вызвать.внешнийСервис("https://denisk.autofaq.ai/gtoken","GET").сЗаголовками(("Content-Type", "application/json"),("charset", "utf-8")).сПараметрами(("email","{email}")).сохранитьРезультат(("message","message"),("token","token"),("status","status"))+
//
// Проверка результатов запроса, в случае ошибки сценарий сообщит об ошибке и завершится 
если("{http_code} > 210").то(
	комментарий("Сценарий не смог получить токен для работы с таблицей. {message}")+
	сообщениеПользователю.сШаблоном("Спасибо за участие, но что-то сломалось и я не могу связаться с Гугл.")+
	завершить
)+
если("{status} == error").то(
	комментарий("Сценарий не смог получить токен для работы с таблицей. {message}")+
	сообщениеПользователю.сШаблоном("Спасибо за опрос, но что-то сломалось и я не смог сохранить результаты.")+
	завершить
)+
//
// Отправка запроса в Google Sheets
вызвать.внешнийСервис("https://sheets.googleapis.com/v4/spreadsheets/{sheetId}/values/{sheetName}","GET").сЗаголовками(("Authorization", "Bearer {token}"),("Content-Type","application/json")).сПараметрами(("majorDimension", "COLUMNS")).сохранитьРезультатКакСтроку("result")+
//
// Проверка результатов запроса, в случае ошибки сценарий сообщит об ошибке и завершится
если("{http_code} > 210").то(
	комментарий("Сценарий не смог отправить запрос на поиск в гугл таблице. Код {http_code}, ответ {result}")+
	сообщениеПользователю.сШаблоном("Спасибо за участие, но что-то сломалось и я не могу связаться с Google.")+
	завершить
)+
//
// Разбор ответа от гугл таблицы
выполнитьJs("""
var error = 0;
var message = '';
var rowIndex = 0;

try {
	var parsedresult = JSON.parse(result);
} catch(err) {
	var exit = {'error': 1, 'message':'Ошибка разбора ответа от Гугл'};
	var parsedresult = {};
}

if (parsedresult.hasOwnProperty('values') && parsedresult.values[0].length > 1) {
	var rowIndex = parsedresult.values[0].indexOf(searchQuery);
	
	if (rowIndex > 0) { 
		for (var i = 0; i < parsedresult.values.length; i++) {
			if (parsedresult.values[i].length > rowIndex) {
				message = message + parsedresult.values[i][0].toString() + ': ' + parsedresult.values[i][rowIndex] + '<br>';
			}			
		}
		var exit = {'error': 0, 'message':message};
	} else {
		var exit = {'error': 1, 'message':'Значение не найдено в первой колонке таблицы'};
	}

} else {
	var exit = {'error': 1, 'message':'В ответе из таблицы не найдено данных'};
}

exit;
""") +
//
// В случае ошибки при поиске данных сценарий сообщит об ошибке и завершится
если("{error} == 1").то(
	сообщениеПользователю.сШаблоном("При получении данных из таблицы возникла ошибка. {message}")+
	завершить
) +
//
// Если при поиске данных ошибки не возникло, то сценарий сообщит найденные данные и завершится 
сообщениеПользователю.сШаблоном("Найдены следующие данные")+
сообщениеПользователю.сШаблоном("{message}")+
завершить

Опрос пользователя и запись данных в новую строку таблицы

Сценарий проверяет наличие данных о пользователе в таблце и если данных в таблице нет то проводит опрос и сохраняет полученную информацию в новой строке таблицы.

Сценарий получает адрес электронной почты из данных пользователя. Если адрес не указан - запрашивает его у пользователя. Сценарий ищет адрес в первой колонке таблицы. Если адрес электронной почты пользователя найден, то сценарий завершает работу. Если адрес не найден - проводит опрос и сохраняет данные в новую строку таблицы.

Для работы сценария необходимо получить данные для подключения к Google Sheets и подготовить переменные сервиса. Детальное описание подключения доступно по ссылке - Подключение сценария к Google Sheets

 Нажмите здесь, чтобы развернуть пример сценария
//
// Сценарий опроса пользователя и сохранения результатов в таблице Google Sheet 
// 
// Идентификатор таблицы Google Sheet
установитьПеременную("sheetId", "1UyTG1ag6kb-tKCD0Wc5nD21zE38TvYSiMMQ_vzwxWbs")+
// Название листа в таблице Google Sheet
установитьПеременную("sheetName", "Sheet1")+
//
// Опрос пользователя
//
установитьПеременную("sendFio", "{userFullName}")+
если("{sendFio} == ").то(
    задатьПользователюВопрос("Укажите как вас зовут, пожалуйста").сохранитьРезультат("sendFio")
)+
установитьПеременную("sendEmail", "{userEmail}")+
если("{sendEmail} == ").то(
    задатьПользователюВопрос("Укажите адрес вашей электронной почты").сохранитьРезультат("sendEmail")
)+
задатьПользователюВопрос("Пожалуйста назовите ваш отдел").сохранитьРезультат("sendOtdel") +
задатьПользователюВопрос("Пожалуйста назовите вашу должность").сохранитьРезультат("sendDolznost") +
задатьПользователюВопрос("Уточните, пожалуйста, что для Вас является приоритетным?").сВариантамиОтвета("Получение максимальной суммы","Простота получения").сохранитьРезультат("sendPriority")+
задатьПользователюВопрос("Укажите, на какие цели предназначается займ?").сохранитьРезультат("sendGoal")+
//
// Отправка запроса в гугл
// 
// Попытка получения сохраненного ранее токена для запросов в google из переменных сервиса
получитьПеременнуюСервиса("google_token", "g_token")+
// Разбор сохраненного токена
выполнитьJs("""
try {
    var doc = JSON.parse(g_token);
} catch(e) {
    var doc = {'token': '', 'ts': 0};
}

var token = '';
var now_ts =  parseInt(Date.now() / 1000);

if (now_ts > doc.ts) {
    var error = 1;    
} else if (doc.ts == 0) {
    var error = 2;
} else if (doc.token == '') {
    var error = 3;
} else {
    var error = 0;
    var token = doc.token;
}

var exit = {'error':error, 'token':token, 'expired':doc.ts, 'now_ts':now_ts};
exit;
""") +
комментарий("Получил сохраненный токен, статус токена {error}")+ 
// Проверка актуальности сохраненного токена
если("{error} == 0").то(
    // Токен записан и его время жизни не истекло 
    // Сценарий переходит непосредственно к запросу на добавление данных в таблицу
    комментарий("Токен еще валиден, используем его")+
    перейтиНаМетку("google_query")
)+
// Токен или не сохранен или его время жизни истекло
// Сценарий получает новый токен
комментарий("Токен не валиден, получаем новый токен")+
// Сценарий получает из переменных сервиса секретный ключ и адрес аккаунта гугл 
получитьПеременнуюСервиса("google_private_key", "g_private_key") +
получитьПеременнуюСервиса("goggle_account_email", "goggle_account_email") +
// Проверка полученных данных
если("{g_private_key} == ").то(
    комментарий("Нет приватного ключа от аккаунта, не могу получить токен.")+
    завершить
)+
если("{goggle_account_email} == ").то(
    комментарий("Нет адреса аккаунта гугл, не могу получить токен.")+
    завершить
)+
// Сценарий формирует тело запроса на получение токена 
выполнитьJs("""
var header = {
        "alg": "RS256",
        "typ": "JWT"
    };
var data = {
        "iss": goggle_account_email,
        "scope": "https://www.googleapis.com/auth/spreadsheets",
        "aud": "https://oauth2.googleapis.com/token",
        "exp": parseInt(Date.now() / 1000) + 3600,
        "iat": parseInt(Date.now() / 1000)
    };

var sHeader = JSON.stringify(header);
var sPayload = JSON.stringify(data);
var s_g_private_key = g_private_key.replace(/\\n/g,'');
var signature = KJUR.jws.JWS.sign(header.alg, sHeader, sPayload, s_g_private_key);

var exit = {'signature':signature};
exit;
""") +
// Сценарий запрашивает гугла для получения токена
вызвать.внешнийСервис("https://oauth2.googleapis.com/token", "POST").сЗаголовками(("Content-Type", "application/json")).сТеломСообщения("{'grant_type':'urn:ietf:params:oauth:grant-type:jwt-bearer', 'assertion':'{signature}'}").сохранитьРезультатКакСтроку("result")+
если("{http_code} > 210").то(
    комментарий("Сценарий не смог получить токен для записи в таблицу. Код ошибки {http_code}, ответ {result}")+
    сообщениеПользователю.сШаблоном("Спасибо за участие, но что-то сломалось и я не могу связаться с Google.")+    
    завершить
)+
// Сценарий разбирает ответ от гугл на получения токена
выполнитьJs("""
try {
    var resultParced = JSON.parse(result)
} catch(e) {
    var resultParced = {'access_token':'','expires_in':0};
}
var token = resultParced.access_token;
var expires = resultParced.expires_in;
var g_token = {'token': token, 'ts': parseInt(Date.now() / 1000) + 3599};

var exit = {'token':token, 'expires':expires, 'g_token':JSON.stringify(g_token)};
exit;
""") +
// Сценарий разбирает ответ гугла
если("{token} == ").то(
    комментарий("Сценарий не смог распознать токен в ответе от гугла. Ответ {result}")+
    сообщениеПользователю.сШаблоном("Спасибо за участие, но что-то сломалось и я не могу связаться с Google.")+    
    завершить
)+
// Сценарий сохраняет полученный токен для будущего использования
изменитьПеременнуюСервиса("google_token", "{g_token}")
комментарий("Обновил сохраненный токен для запросов в гугл")+
// 
// Метка для запроса на запись данных в таблицу
установитьМетку("google_query") +
//
// Формирование тела запроса на добавление 1 строки в конец таблицы, количество столбцов не более 25
выполнитьJs("""
    // Дата добавления
    var sendDate = new Date();
    // Список добавляемых значений
    var addValues = [sendDate, sendEmail, sendFio, sendOtdel, sendDolznost, sendPriority, sendGoal];

    var sheetsNames = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','R','S','T','U','V','W','X','Y','Z'];
    var sheetEnd = sheetsNames[addValues.length];
    var requestBody = {"range": sheetName+"!A1:"+sheetEnd+"1", "majorDimension": "ROWS", "values":[addValues,]};

    var exit = {'requestBodyJSON':JSON.stringify(requestBody), 'sheetEnd':sheetEnd};
exit;
""") +
//
// Сценарий отправляет запрос на отправку данных в табличку
вызвать.внешнийСервис("https://sheets.googleapis.com/v4/spreadsheets/{sheetId}/values/{sheetName}!A1:{sheetEnd}1:append","POST").сПараметрами(("valueInputOption","USER_ENTERED")).сЗаголовками(("Authorization", "Bearer {token}"),("Content-Type","application/json")).сТеломСообщения("{requestBodyJSON}").сохранитьРезультатКакСтроку("result")+
// Сценарий разбирает результаты запроса
если("{http_code} > 210").то(
    комментарий("Сценарий не смог отправить запрос на добавление строчки в гугл таблице. Код {http_code}, ответ {result}")+
    сообщениеПользователю.сШаблоном("Спасибо за участие, но что-то сломалось и я не могу связаться с Google.")+    
    завершить
)+
// Запрос на добавление записи завершен успешно
комментарий("Добавил строчку в гугл таблицу.") +
сообщениеПользователю.сШаблоном("Все записал. Большое спасибо за участие!") +
завершить

Интеграционные сценарии

Логировать в комментарии к диалогу запись о выставленном оператором теге в диалоге.

Про добавление тегов в диалог подробный сценарий здесь:

https://deephack.atlassian.net/wiki/spaces/AKB/pages/3207102465#%D0%A1%D0%BE%D0%B7%D0%B4%D0%B0%D0%BD%D0%B8%D0%B5-%D1%82%D0%B5%D0%B3%D0%BE%D0%B2-%D0%BA-%D0%B4%D0%B8%D0%B0%D0%BB%D0%BE%D0%B3%D1%83

https://deephack.atlassian.net/wiki/spaces/AKB/pages/3207102465#%D0%A1%D0%BE%D0%B7%D0%B4%D0%B0%D0%BD%D0%B8%D0%B5-%D1%82%D0%B5%D0%B3%D0%BE%D0%B2-%D0%BA-%D0%B4%D0%B8%D0%B0%D0%BB%D0%BE%D0%B3%D1%83-%D0%B8-%D0%B0%D0%B2%D1%82%D0%BE%D0%BC%D0%B0%D1%82%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5-%D0%BF%D1%80%D0%B5%D0%B4%D0%B7%D0%B0%D0%BF%D0%BE%D0%BB%D0%BD%D0%B5%D0%BD%D0%B8%D0%B5-%D0%BF%D0%BE%D0%BB%D0%B5%D0%B9

Сценарий записывает email, ФИО оператора и текущий тег диалога для случаев, когда нужно записать каким оператором был выставлен тег при переназначении на другую группу/переназначении на другого оператора/возврате диалога в очередь оператором.

Если оператор не вносил изменение в тег, сценарий запишет email, ФИО оператора и текущий тег диалога.

Сценарий интеграции необходимо добавлять на события “Оператор передал чат другому оператору”, “Оператор вернул диалог в очередь”, “Оператор перевел чат на другую группу“

 Нажмите здесь, чтобы развернуть пример сценария
выполнитьJs("""
var initByOperator = JSON.parse(initByOperator);
var initByOperatorLogin = initByOperator.login;
var exit = {'operatorFIO': initByOperator.fullName, 'operatorEmail': initByOperator.email};
exit;
""")+
комментарий("Оператор с ФИО {operatorFIO} email {operatorEmail} указал тег {tag_name}")+
завершить

где {tag_name} - название переменной тега.

Получить тему письма, по которому открыт диалог в почтовом канале

Сценарий выводит тему входящего письма почтового канала в комментарий. Аналогично можно выводить “От кого”, “Кому”.

Сценарий интеграции необходимо добавлять на события “Поступление диалога в систему”

 Click here to expand...
//забираем первый комментарий из диалога и забираем из него адрес from
выполнитьJs("""
var msgTypes = ['OperatorComment']; 
var messages = conversation.messages.filter(function (str) {return  msgTypes.indexOf(str.tpe) > -1;});
messages = messages;
var arrayQuestions = [];
for (var i = 0; i < messages.length; i++) {
  var message = messages[i];
  clean_txt = message.txt.replace(/(\r\n|\n|\r)/gm, '');
  arrayQuestions.push(clean_txt);
}
if (arrayQuestions.length == 0) {
  var exit = {'countQuestion':0};
}
else {
  var operatorComment = arrayQuestions[0];
  var operatorComment = operatorComment.replace(/.*?Subject:/,'').replace(/From:.*$/,'');
  var exit = {'operatorComment':operatorComment};
}
exit;
""")+
//выводим в комментарий результат тему письма
комментарий("{operatorComment}")+
завершить

Оповещение операторов в Telegram группе о поступлении диалогов в АФ

Сценарий при поступлении диалога в систему выполняет отправку первой реплики пользователя в заданную в сценарии группу Telegram. Сценарий добавляем в интеграции на событие “Поступление диалога в систему”. Перед добавлением сценария необходимо создать группу в Telegram и получить id группы, например помощью бота https://t.me/myidbot

 Click here to expand...
//указываем токен бота
установитьПеременную("tgToken","123214:34sdfFR8LrSsdf45dsdf")+
//указываем ID чата, можно получить с помощью бота https://t.me/myidbot
установитьПеременную("chatId","-1234567")+
//записываем в переменную первую реплику
установитьПеременную("userAnswer", "{platformInMessageQuery}")+
//выполняем отправку реплики в ТГ группу
вызвать.внешнийСервис("https://api.telegram.org/bot{tgToken}/sendMessage", "POST").сПараметрами(("chat_id","{chatId}"),("text","{userAnswer}")).сЗаголовками(("Content-Type", "application/json")).сохранитьРезультатКакСтроку("answer")+
если("{http_code} > 210").то(
  комментарий("Произошла ошибка при выполнении запроса. Код ошибки: {http_code}. Ответ системы {answer}")+
  завершить
)+
завершить

Прочие примеры сценариев

Предлагать случайно один из нескольких вариантов ответа

Сценарий на вопрос пользователя предлагает случайно один из нескольких вариантов ответа

 Нажмите здесь, чтобы развернуть пример сценария
//количество сообщений для случайного отображения
//
установитьПеременную("krandom","3")+
//сообщения, которые будут случайно выдаваться
//
установитьПеременную("message1","Первое сообщение")+
установитьПеременную("message2","Второе сообщение")+
установитьПеременную("message3","Третье сообщение")+
//генерируем случайное число от 0 до krandom
//
выполнитьJs("""
var a = Number(krandom);
function getRandomInt(max) {
  return Math.floor(Math.random() * max);
}
var numbermessage = getRandomInt(a);
numbermessage = String(numbermessage);
var ext = {'numbermessage':numbermessage};
ext;
""")+
//отображаем сообщение в зависимости от сгенерированного числа
//
если("{numbermessage} == 0").то(
  сообщениеПользователю.сШаблоном("{message1}")
  )+
если("{numbermessage} == 1").то(
  сообщениеПользователю.сШаблоном("{message2}")
  )+
если("{numbermessage} == 2").то(
  сообщениеПользователю.сШаблоном("{message3}")
  )+
завершитьИЗакрытьДиалог()

Получить в сценарии дату\время с учетом часового пояса

 Нажмите здесь, чтобы развернуть пример сценария
выполнитьJs("""
var currentDate = new Date();
var timezoneOffset = 3;
var timeZoneOffsetInMilliseconds = timezoneOffset * 60 * 60 * 1000;
var adjustedDate = new Date(currentDate.getTime() + timeZoneOffsetInMilliseconds);
var sendtime = adjustedDate.toLocaleTimeString();;
var sendDate = currentDate.toLocaleDateString();
var exit = {'sendDate':sendDate, 'sendtime':sendtime};
exit;
""")+
сообщениеПользователю.сШаблоном("Вот {sendDate} {sendtime}")+
  завершитьИЗакрытьДиалог()

Получить в сценарии информацию об операторе

 Click here to expand...
выполнитьJs("""
var fileError = 0;
try {
  var parsedExtFiles = JSON.parse(initByOperator);
} catch(e) {
  var fileError = 1;
}
var Oname = '';
var Oemail = '';

Oname = parsedExtFiles.fullName;
Oemail = parsedExtFiles.email;

var exit = {'Oname': Oname, 'Oemail': Oemail, 'fileError': fileError};
exit;
""")+
комментарий("Оператор - {Oname} {Oemail}")+
комментарий("Ошибка - {fileError}")+
завершить
  • No labels