Приближается новый 2015 год и мы, немного поразмышляв, решили развлечь людей и заодно реализовать идею новогоднего колл-центра с блекджеком Дедами Морозами и Снегурочками, отвечающими на звонки желающих пообщаться в предновогоднее время. Каждый желающий может стать оператором этого колл-центра, выбрав пол оператора, так же как каждый желающий может позвонить в этот колл-центр прямо из браузера (потребуется микрофон) или просто набрав номер телефона. Операторское место Деда Мороза/Снегурочки будет работать прямо в браузере (привет WebRTC) и тут без микрофона уже никак не обойтись. Для реализации такого сервиса может потребоваться достаточно много времени, если все делать с нуля, но мы воспользуемся платформой VoxImplant, которая нам значительно облегчит и ускорит весь процесс. Итого, нужно сделать веб-сервис для регистрации желающих стать операторами, а также 2 веб-приложения — звонилку и операторское место + написать парочку сценариев на javascript. Мы надеемся, что найдется достаточно желающих выступить операторами нашего колл-центра, а то звонящим придется долго ждать разговора в очереди. Чтобы было интереснее мы организуем рейтинг самых разговорчивых операторов и им дадим подарочные сертификаты VoxImplant, чтобы они могли потом сами реализовать свой собственный сервис с блекджеком… ну, в общем, вы поняли. Все самое интересное, как обычно, под катом!
Результат
Для тех кому не терпится попробовать сервис сразу даю ссылку http://demos.zingaya.com/newyear/, думаю что интерфейс не требует особых комментариев. Выбираем свою сторону и звоним/принимаем звонки.
Регистрируем аккаунт оператора, логинимся и ставим статус «Готов принимать звонки»
Как только какой-нибудь из входящих вызовов распределится на оператора, то заиграет мелодия и появится всплывающее окно, где можно ответить на звонок.
Создание приложения
Авторизуемся в панели управления VoxImplant (https://manage.voximplant.com) и создаем в разделе Applications приложение newyear, это просто виртуальная сущность, к которой мы будем цеплять юзеров-операторов, а также опишем правила обработки звонков (какой сценарий должен обрабатывать какие звонки).
Создание сценария VoxEngine
Теперь надо написать сценарий, который будет обрабатывать входящие звонки и распределять их по операторам. Сценарии для VoxImplant пишутся на JS. Наш будет выглядеть так:
// Подключаем модули ACD (распределение вызовов) и ASR (распознавание речи)
require(Modules.ACD);
require(Modules.ASR);
var request,
originalCall,
callerid,
statusInterval,
asrTimeout,
asr,
queueName = 'MainQueue';
// Вешаем обработчик входящего звонка
VoxEngine.addEventListener(AppEvents.CallAlerting, handleInboundCall);
// Функция-обработчик входящего звонка
function handleInboundCall(e) {
originalCall = e.call; // сохраняем экземпляр входящего звонка
callerid = e.callerid; // сохраняем caller id
// Вешаем обработчики
originalCall.addEventListener(CallEvents.Connected, handleCallConnected);
originalCall.addEventListener(CallEvents.Failed, cleanup);
originalCall.addEventListener(CallEvents.Disconnected, cleanup);
// Отвечаем на входящий вызов
originalCall.answer();
}
// Завершаем звонок и убиваем сессию
function cleanup(e) {
if (request) {
// Если поставили звонок в очередь - отменяем
request.cancel();
request = null;
}
// Убиваем сессию
VoxEngine.terminate();
}
// Проиграть музыку после того как отработает TTS
function handlePlaybackFinished(e) {
e.call.startPlayback("http://cdn.voximplant.com/newyear.mp3");
}
// Функция для преобразования окончаний времени ожидания для TTS
function getNumEnding(iNumber, aEndings) {
var sEnding, i;
iNumber = iNumber % 100;
if (iNumber >= 11 && iNumber <= 19) {
sEnding = aEndings[2];
} else {
i = iNumber % 10;
switch (i) {
case (1):
sEnding = aEndings[0];
break;
case (2):
case (3):
case (4):
sEnding = aEndings[1];
break;
default:
sEnding = aEndings[2];
}
}
return sEnding;
}
// Звонок соединен
function handleCallConnected(e) {
// Проигрываем сообщение
e.call.say("Новогодний колл-центр приветствует васс!!! " +
"Вы хотите поговорить со снегурочкой или с дедом морозом?", Language.RU_RUSSIAN_FEMALE);
e.call.addEventListener(CallEvents.PlaybackFinished, handleIntroPlayed);
}
// После проигрывания интро
function handleIntroPlayed(e) {
e.call.removeEventListener(CallEvents.PlaybackFinished, handleIntroPlayed);
// Создаем инстанс для распознавания речи с указанным словарем
asr = VoxEngine.createASR(ASRLanguage.RUSSIAN_RU, ["со снегурочкой",
"снегурочкой",
"снегурочка",
"с дедом морозом",
"дедом морозом",
"дед мороз"
]);
// Если начался захват речи
asr.addEventListener(ASREvents.CaptureStarted, function (e) {
clearTimeout(asrTimeout);
});
// Результат распознавания
asr.addEventListener(ASREvents.Result, function (e) {
// Выключить распознавание
asr.stop();
// Если выбрали снегурочку, то меняем название очереди на SnegurQueue
if ((e.text == 'со снегурочкой' || e.text == 'снегурочкой' || e.text == 'снегурочка') && e.confidence >= 50) {
originalCall.say("Отлично! Первая свободная снегурочка ответит на ваш звонок.", Language.RU_RUSSIAN_FEMALE);
queueName = 'SnegurQueue';
originalCall.addEventListener(CallEvents.PlaybackFinished, addToQueue);
} else if ((e.text == 'с дедом морозом' || e.text == 'дедом морозом' || e.text == 'дед мороз') && e.confidence >= 50) {
// Если выбрали деда мороза, то очередь - MorozQueue
originalCall.say("Отлично! Первый свободный дед мороз ответит на ваш звонок.", Language.RU_RUSSIAN_FEMALE);
queueName = 'MorozQueue';
originalCall.addEventListener(CallEvents.PlaybackFinished, addToQueue);
} else {
// Если уверенности нет (точность < 50%), то отправляем в общую очередь - MainQueue
originalCall.say("Первый свободный дед мороз или снегурочка ответят на ваш звонок.", Language.RU_RUSSIAN_FEMALE);
originalCall.addEventListener(CallEvents.PlaybackFinished, addToQueue);
}
});
// Отправляем звук в инстанс ASR
originalCall.sendMediaTo(asr);
// Если в течение 3 секунд не сказали с кем хотят поговорить, то отправляем в общую очередь
asrTimeout = setTimeout(function () {
asr.stop();
originalCall.say("Первый свободный дед мороз или снегурочка ответят на ваш звонок.", Language.RU_RUSSIAN_FEMALE);
originalCall.addEventListener(CallEvents.PlaybackFinished, addToQueue);
}, 3000);
}
// Добавляем звонок в определенную очередь в зависимости от queueName
function addToQueue(e) {
Logger.write('Adding call to queue: '+queueName);
originalCall.removeEventListener(CallEvents.PlaybackFinished, addToQueue);
// После завершения TTS включаем проигрывание музычки
originalCall.addEventListener(CallEvents.PlaybackFinished, handlePlaybackFinished);
// Добавляем в очередь
request = VoxEngine.enqueueACDRequest(queueName, callerid);
// Получаем статус звонка в очереди
request.addEventListener(ACDEvents.Queued, function (acdevent) {
request.getStatus();
});
// Сообщить о положении звонка в очереди
request.addEventListener(ACDEvents.Waiting, function (acdevent) {
var minutesLeft = acdevent.ewt + 1,
txt = 'Дед мороз или снегурочка ответят вам через';
if (queueName == 'SnegurQueue') txt = "Снегурочка ответит вам через";
else if (queueName == 'MorozQueue') txt = "Дед мороз ответит вам через";
originalCall.say("Вы в очереди под номером " + acdevent.position +
". " + txt + " " + (acdevent.ewt + 1) + getNumEnding(minutesLeft, ['минуту', 'минуты', 'минут']), Language.RU_RUSSIAN_FEMALE);
});
// Дошла очередь - соединяем звонящего с оператором
request.addEventListener(ACDEvents.OperatorReached, function (acdevent) {
VoxEngine.sendMediaBetween(acdevent.operatorCall, originalCall);
acdevent.operatorCall.sendMessage(JSON.stringify({
number: originalCall.callerid()
}));
acdevent.operatorCall.addEventListener(CallEvents.Disconnected, VoxEngine.terminate);
clearInterval(statusInterval);
});
// Нет доступных операторов
request.addEventListener(ACDEvents.Offline, function (acdevent) {
clearInterval(statusInterval);
// Если были в очереди к снегуркам или дедам морозом, то пытаемся переопределить в общую очередь
if (queueName == 'SnegurQueue') {
originalCall.say("К сожалению, нет ни одной снегурочки отвечающей на звонки. Попробуем найти деда мороза!", Language.RU_RUSSIAN_FEMALE);
queueName = 'MainQueue';
originalCall.addEventListener(CallEvents.PlaybackFinished, addToQueue);
} else if (queueName == 'MorozQueue') {
originalCall.say("К сожалению, нет ни одного деда мороза отвечающего на звонки. Попробуем найти снегурочку!", Language.RU_RUSSIAN_FEMALE);
queueName = 'MainQueue';
originalCall.addEventListener(CallEvents.PlaybackFinished, addToQueue);
} else {
// Если ни одного деда мороза и снегурки нет, то предлагаем стать оператором и завершаем звонок
originalCall.say("К сожалению, нет ни одного деда мороза или снегурочки отвечающих на звонки. Попробуйте позвонить позднее " +
"или сами станьте оператором нашего новогоднего колл-центра! Спасибо за звонок!", Language.RU_RUSSIAN_FEMALE);
originalCall.addEventListener(CallEvents.PlaybackFinished, VoxEngine.Terminate);
}
});
// Обновлять информацию о положении в очереди каждые 30 секунд
statusInterval = setInterval(request.getStatus, 30000);
}
Созданием правил обработки
Входящие звонки с номера и с web sdk надо направить на обработку нашим сценарием. В веб-приложении для исходящих звонков мы зашили номер newyearcall, а в разделе с номерами телефонов подключили номер 74951330204 к нашему приложению. Создаем 2 правила:
Организация очередей
В контексте данного проекта нам потребуется создать 3 разных очереди (Settings -> Queues) — MainQueue (все), MorozQueue (только деды морозы) и SnegurQueue (только снегурочки). Звонящему предлагается выбрать с кем он хочет поговорить — со снегурочкой или с дедом морозом, выбор производится с помощью системы распознавания голоса, которая доступна в VoxImplant, из заранее описанного словаря. Если в течение 3 секунд выбор не был озвучен или система не уверена в распознанном варианте (вероятность < 50%), то звонок направляется в общую очередь, которую обслуживаю и деды морозы и снегурочки. Если выбор был сделан успешно, то оправляем в конкретную очередь. В случае если данную очередь не обслуживает ни один оператор, то переопределяем вызов в общую очередь, а если и там никого из операторов нет — проигрываем сообщение и предлагаем самом стать оператором.
Помимо создания очередей нужно еще создать соответствующие скилл-группы, так как привязка пользователей приложения (операторов) к очередям делается именно через скиллы (Settings -> Skills). Создаем 3 скилла: NewYearSkillAll, NewYearSkillMoroz и NewYearSkillSnegur, соответственно каждому скиллу задаем свою очередь — MainQueue, MorozQueue и SnegurQueue. При создании юзеров-операторов, мы будем их цеплять сразу к 2 скилл-группам — NewYearSkillAll + NewYearSkillMoroz (деды морозы) или NewYearSkillSnegur (снегурки).
Операторское место
Операторское место, как и веб-звонилка, делаются с помощью Web SDK VoxImplant. Из важных моментов можно отметить реализацию переключателя статуса оператора с помощью функции setOperatorACDStatus.
vox.setOperatorACDStatus(VoxImplant.OperatorACDStatuses.Ready); // где vox - инстанс VoxImplant.Client
Звонки будут распределяться на оператора только когда он находится в состоянии Ready. Нахождение в остальных состояниях отражается в статистике по работе оператора.
В общем, этого достаточно, остальные прикручивания (удаленное создание юзеров приложения и т.д.) уже делаются на базе HTTP API VoxImplant.
Еще раз ссылка на результат http://demos.zingaya.com/newyear/
Автор: aylarov