Делаем Новогодний колл-центр

в 12:13, , рубрики: acd, diy или сделай сам, ip-телефония, voximplant, WebRTC, Веб-разработка, дед мороз, колл-центр, Новый Год, снегурочка

Делаем Новогодний колл-центр - 1Приближается новый 2015 год и мы, немного поразмышляв, решили развлечь людей и заодно реализовать идею новогоднего колл-центра с блекджеком Дедами Морозами и Снегурочками, отвечающими на звонки желающих пообщаться в предновогоднее время. Каждый желающий может стать оператором этого колл-центра, выбрав пол оператора, так же как каждый желающий может позвонить в этот колл-центр прямо из браузера (потребуется микрофон) или просто набрав номер телефона. Операторское место Деда Мороза/Снегурочки будет работать прямо в браузере (привет WebRTC) и тут без микрофона уже никак не обойтись. Для реализации такого сервиса может потребоваться достаточно много времени, если все делать с нуля, но мы воспользуемся платформой VoxImplant, которая нам значительно облегчит и ускорит весь процесс. Итого, нужно сделать веб-сервис для регистрации желающих стать операторами, а также 2 веб-приложения — звонилку и операторское место + написать парочку сценариев на javascript. Мы надеемся, что найдется достаточно желающих выступить операторами нашего колл-центра, а то звонящим придется долго ждать разговора в очереди. Чтобы было интереснее мы организуем рейтинг самых разговорчивых операторов и им дадим подарочные сертификаты VoxImplant, чтобы они могли потом сами реализовать свой собственный сервис с блекджеком… ну, в общем, вы поняли. Все самое интересное, как обычно, под катом!

Результат

Для тех кому не терпится попробовать сервис сразу даю ссылку http://demos.zingaya.com/newyear/, думаю что интерфейс не требует особых комментариев. Выбираем свою сторону и звоним/принимаем звонки.
Регистрируем аккаунт оператора, логинимся и ставим статус «Готов принимать звонки»
Делаем Новогодний колл-центр - 2
Как только какой-нибудь из входящих вызовов распределится на оператора, то заиграет мелодия и появится всплывающее окно, где можно ответить на звонок.
Делаем Новогодний колл-центр - 3

Создание приложения

Авторизуемся в панели управления 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 правила:
Делаем Новогодний колл-центр - 4

Организация очередей

В контексте данного проекта нам потребуется создать 3 разных очереди (Settings -> Queues) — MainQueue (все), MorozQueue (только деды морозы) и SnegurQueue (только снегурочки). Звонящему предлагается выбрать с кем он хочет поговорить — со снегурочкой или с дедом морозом, выбор производится с помощью системы распознавания голоса, которая доступна в VoxImplant, из заранее описанного словаря. Если в течение 3 секунд выбор не был озвучен или система не уверена в распознанном варианте (вероятность < 50%), то звонок направляется в общую очередь, которую обслуживаю и деды морозы и снегурочки. Если выбор был сделан успешно, то оправляем в конкретную очередь. В случае если данную очередь не обслуживает ни один оператор, то переопределяем вызов в общую очередь, а если и там никого из операторов нет — проигрываем сообщение и предлагаем самом стать оператором.
Помимо создания очередей нужно еще создать соответствующие скилл-группы, так как привязка пользователей приложения (операторов) к очередям делается именно через скиллы (Settings -> Skills). Создаем 3 скилла: NewYearSkillAll, NewYearSkillMoroz и NewYearSkillSnegur, соответственно каждому скиллу задаем свою очередь — MainQueue, MorozQueue и SnegurQueue. При создании юзеров-операторов, мы будем их цеплять сразу к 2 скилл-группам — NewYearSkillAll + NewYearSkillMoroz (деды морозы) или NewYearSkillSnegur (снегурки).
Делаем Новогодний колл-центр - 5

Операторское место

Операторское место, как и веб-звонилка, делаются с помощью Web SDK VoxImplant. Из важных моментов можно отметить реализацию переключателя статуса оператора с помощью функции setOperatorACDStatus.

vox.setOperatorACDStatus(VoxImplant.OperatorACDStatuses.Ready); // где vox - инстанс VoxImplant.Client

Звонки будут распределяться на оператора только когда он находится в состоянии Ready. Нахождение в остальных состояниях отражается в статистике по работе оператора.

В общем, этого достаточно, остальные прикручивания (удаленное создание юзеров приложения и т.д.) уже делаются на базе HTTP API VoxImplant.

Еще раз ссылка на результат http://demos.zingaya.com/newyear/

Автор: aylarov

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js