<commit_before>app.chat = (function (_const, utils, uikit, apiHelper, channel, profile, eventListener, satisfaction, startOrStopPollAgentState) {
	var isEmojiInitilized;
	var isMessageChannelReady;
	var inputBoxPosition;
	var config;

	// DOM init
	var topBar = document.querySelector('.em-widget-header');
	var editorView = document.querySelector('.em-widget-send-wrapper');
	easemobim.imBtn = document.getElementById('em-widgetPopBar');

	// todo: 把dom都移到里边
	var doms = {
		imChat: document.getElementById('em-kefu-webim-chat'),
		agentStatusText: topBar.querySelector('.em-header-status-text'),
		nickname: topBar.querySelector('.em-widget-header-nickname'),
		dragBar: topBar.querySelector('.drag-bar'),
		minifyBtn: topBar.querySelector('.btn-min'),
		audioBtn: topBar.querySelector('.btn-audio'),
		switchKeyboardBtn: topBar.querySelector('.btn-keyboard'),

		emojiBtn: editorView.querySelector('.em-bar-emoji'),
		sendImgBtn: editorView.querySelector('.em-widget-img'),
		sendFileBtn: editorView.querySelector('.em-widget-file'),
		sendBtn: editorView.querySelector('.em-widget-send'),
		satisfaction: editorView.querySelector('.em-widget-satisfaction'),
		toKefuBtn: editorView.querySelector('.em-widget-to-kefu'),
		textInput: editorView.querySelector('.em-widget-textarea'),
		noteBtn: editorView.querySelector('.em-widget-note'),

		imgInput: document.querySelector('.upload-img-container'),
		fileInput: document.querySelector('.upload-file-container'),
		emojiContainer: document.querySelector('.em-bar-emoji-container'),
		chatWrapper: document.querySelector('.chat-wrapper'),
		emojiWrapper: document.querySelector('.em-bar-emoji-wrapper'),

		topBar: topBar,
		editorView: editorView,
		block: null
	};

	var _startOrStopAgentInputStatePoller = (function(){
		var isStarted = false;
		var timerHandler;
		var preventTimestamp = 0;
		var inputState = topBar.querySelector('.em-agent-input-state');

		return function(officialAccount){
			if (officialAccount !== profile.currentOfficialAccount) return;

			var state = officialAccount.sessionState;
			var agentType = officialAccount.agentType;

			if (
				state === _const.SESSION_STATE.PROCESSING
				&& agentType !== _const.AGENT_ROLE.ROBOT
			){
				_start(officialAccount);
			}
			else {
				_stop();
			}
		};

		function _start() {
			isStarted = true;
			// 保证当前最多只有1个timerHandler
			clearInterval(timerHandler);
			apiHelper.getCurrentServiceSession().then(function (response){
				var sessionId = response && response.serviceSessionId;

				if (
					sessionId
					&& isStarted
					// 仅当聊天窗口打开时才发送请求
					&& profile.isChatWindowOpen
					// 仅当浏览器非最小化状态时才发送请求
					&& !utils.isBrowserMinimized()
				) {
					timerHandler = setInterval(function(){
						apiHelper.getAgentInputState(sessionId).then(function (entity){
							var currentTimestamp = entity.timestamp;
							var ifDisplayTypingState = entity.input_state_tips;

							// 为了先发送的请求后回来的异步问题，仅处理时间戳比当前大的response
							if (currentTimestamp > preventTimestamp){
								preventTimestamp = currentTimestamp;
								utils.toggleClass(inputState, 'hide', !displayTypingState);
							}
						});
					}, _const.AGENT_INPUT_STATE_INTERVAL);
				}
			});
		}

		function _stop() {
			clearInterval(timerHandler);
			utils.addClass(inputState, 'hide');
			isStarted = false;
		}
	}());

	var _startOrStopQueuingNumberPoller = (function () {
		var isStarted = false;
		var timerHandler;
		var preventTimestamp = 0;
		var $queuingNumberStatus = document.querySelector('.queuing-number-status');
		var $queuingNumberLabel = $queuingNumberStatus.querySelector('label');

		return function(officialAccount){
			if (profile.currentOfficialAccount !== officialAccount) return;

			var state = officialAccount.sessionState;

			if (state === _const.SESSION_STATE.WAIT){
				_start();
			}
			else {
				_stop();
			}
		};

		function _start() {
			isStarted = true;
			// 保证当前最多只有1个timer
			// 每次调用start都必须重新获取queueId
			clearInterval(timerHandler);
			apiHelper.getSessionQueueId().then(function (entity){
				var queueId;
				var sessionId;

				if (entity.state === 'Wait' && isStarted) {
					queueId = entity.queueId;
					sessionId = entity.serviceSessionId;
					// 避免请求在 轮询停止以后回来 而导致误开始
					// todo: use promise to optimize it
					timerHandler = setInterval(function () {
						apiHelper.getWaitListNumber().then(function (entity){
							var waitingNumber = entity.visitorUserWaitingNumber;
							var currentTimestamp = entity.visitorUserWaitingTimestamp;

							if (waitingNumber === 'no') {
								utils.addClass($queuingNumberStatus, 'hide');
							}
							else if (currentTimestamp > preventTimestamp) {
								preventTimestamp = currentTimestamp;
								utils.removeClass($queuingNumberStatus, 'hide');
								$queuingNumberLabel.innerHTML = entity.visitorUserWaitingNumber;
							}
						});
					}, 1000);
				}
			});
		}

		function _stop() {
			clearInterval(timerHandler);
			preventTimestamp = 0;
			isStarted = false;
			utils.addClass($queuingNumberStatus, 'hide');
		}
	}());

	function _displayOrHideTransferToKefuBtn(officialAccount){
		if (profile.currentOfficialAccount !== officialAccount) return;

		var state = officialAccount.sessionState;
		var agentType = officialAccount.agentType;
		var isRobotAgent = agentType === _const.AGENT_ROLE.ROBOT;

		if (state === _const.SESSION_STATE.PROCESSING){
			utils.toggleClass(doms.toKefuBtn, 'hide', !isRobotAgent);
		}
		else{
			apiHelper.getRobertIsOpen().then(function (isRobotEnable) {
				utils.toggleClass(doms.toKefuBtn, 'hide', !isRobotEnable);
			});
		}
	}

	var chat = {
		doms: doms,
		init: _init,
		handleReady: _handleReady,
		close: _close,
		show: _show,
		playSound: function(){},
		block: null
	};

	return chat;

	function _initSystemEventListener(){
		// 更新坐席在线状态标志
		eventListener.add(_const.SYSTEM_EVENT.SESSION_OPENED, startOrStopPollAgentState);
		eventListener.add(_const.SYSTEM_EVENT.SESSION_TRANSFERED, startOrStopPollAgentState);
		eventListener.add(_const.SYSTEM_EVENT.SESSION_CLOSED, startOrStopPollAgentState);
		eventListener.add(_const.SYSTEM_EVENT.SESSION_TRANSFERING, startOrStopPollAgentState);
		eventListener.add(_const.SYSTEM_EVENT.SESSION_RESTORED, startOrStopPollAgentState);

		// 更新坐席名称
		eventListener.add(_const.SYSTEM_EVENT.SESSION_OPENED, _updateAgentNickname);
		eventListener.add(_const.SYSTEM_EVENT.SESSION_TRANSFERED, _updateAgentNickname);
		eventListener.add(_const.SYSTEM_EVENT.SESSION_CLOSED, _updateAgentNickname);
		eventListener.add(_const.SYSTEM_EVENT.AGENT_NICKNAME_CHANGE, _updateAgentNickname);

		// report visitor info
		eventListener.add(_const.SYSTEM_EVENT.SESSION_OPENED, _reportVisitorInfo);
		eventListener.add(_const.SYSTEM_EVENT.SESSION_RESTORED, _reportVisitorInfo);

		// get greetings
		eventListener.add(_const.SYSTEM_EVENT.SESSION_RESTORED, _getGreetings);
		eventListener.add(_const.SYSTEM_EVENT.SESSION_NOT_CREATED, _getGreetings);

		if (config.toolbar.transferToKefu){
			// update transferToKefu button state
			// todo: add listener to official changed
			eventListener.add(_const.SYSTEM_EVENT.SESSION_OPENED, _displayOrHideTransferToKefuBtn);
			eventListener.add(_const.SYSTEM_EVENT.SESSION_TRANSFERED, _displayOrHideTransferToKefuBtn);
			eventListener.add(_const.SYSTEM_EVENT.SESSION_RESTORED, _displayOrHideTransferToKefuBtn);
			eventListener.add(_const.SYSTEM_EVENT.SESSION_NOT_CREATED, _displayOrHideTransferToKefuBtn);
		}

		if (config.grayList.agentInputStateEnable){
			// update transferToKefu button state
			// todo: add listener to official changed
			eventListener.add(_const.SYSTEM_EVENT.SESSION_OPENED, _startOrStopAgentInputStatePoller);
			eventListener.add(_const.SYSTEM_EVENT.SESSION_CLOSED, _startOrStopAgentInputStatePoller);
			eventListener.add(_const.SYSTEM_EVENT.SESSION_TRANSFERED, _startOrStopAgentInputStatePoller);
			eventListener.add(_const.SYSTEM_EVENT.SESSION_RESTORED, _startOrStopAgentInputStatePoller);
		}

		if (config.grayList.waitListNumberEnable){
			// update transferToKefu button state
			// todo: add listener to official changed
			eventListener.add(_const.SYSTEM_EVENT.SESSION_OPENED, _startOrStopQueuingNumberPoller);
			eventListener.add(_const.SYSTEM_EVENT.SESSION_CLOSED, _startOrStopQueuingNumberPoller);
			eventListener.add(_const.SYSTEM_EVENT.SESSION_TRANSFERED, _startOrStopQueuingNumberPoller);
			eventListener.add(_const.SYSTEM_EVENT.SESSION_CREATED, _startOrStopQueuingNumberPoller);
			eventListener.add(_const.SYSTEM_EVENT.SESSION_RESTORED, _startOrStopQueuingNumberPoller);
		}
	}

	function _getGreetings(officialAccount){
		if (!officialAccount.isSessionOpen){
			_getGreeting();
			_getSkillgroupMenu();
		}
	}

	function _reportVisitorInfo(officialAccount){
		if (officialAccount.hasReportedAttributes) return;

		var officialAccountId = officialAccount.official_account_id;
		var sessionId = officialAccount.sessionId;
		var isSessionOpen = officialAccount.isSessionOpen;

		if (isSessionOpen){
			officialAccount.hasReportedAttributes = true;
			apiHelper.reportVisitorAttributes(sessionId);
		}
	}

	function _initUI(){
		(utils.isTop || !config.minimum) && utils.removeClass(doms.imChat, 'hide');

		// 设置联系客服按钮文字
		document.querySelector('.em-widget-pop-bar').innerText = config.buttonText;

		// 添加移动端样式类
		utils.isMobile && utils.addClass(document.body, 'em-mobile');

		// 留言按钮
		config.ticket && utils.removeClass(doms.noteBtn, 'hide');

		// 最小化按钮
		config.minimum
			&& !utils.isTop
			&& utils.removeClass(doms.minifyBtn, 'hide');

		// 低版本浏览器不支持上传文件/图片
		if (WebIM.utils.isCanUploadFileAsync){
			utils.removeClass(doms.sendImgBtn, 'hide');
			utils.removeClass(doms.sendFileBtn, 'hide');
		}

		// h5title设置
		if(config.ui.H5Title.enabled){
			document.title = config.ui.H5Title.content;
		}

		// 静音按钮
		window.HTMLAudioElement
			&& !utils.isMobile
			&& config.soundReminder
			&& utils.removeClass(doms.audioBtn, 'hide');

		// 输入框位置开关
		utils.isMobile
			&& !config.hideKeyboard
			&& utils.removeClass(doms.switchKeyboardBtn, 'hide');

		// 满意度评价按钮
		config.satisfaction
			&& utils.removeClass(
				document.querySelector('.em-widget-satisfaction'),
				'hide'
			);

		// 设置企业名称
		_updateAgentNickname();
	}

	function _initSoundReminder(){
		if (!window.HTMLAudioElement || utils.isMobile || !config.soundReminder) {
			return;
		}

		var audioDom = document.createElement('audio');
		var slienceSwitch = document.querySelector('.em-widget-header .btn-audio');
		var isSlienceEnable = false;
		var play = _.throttle(function () {
			audioDom.play();
		}, 3000, { trailing: false });

		audioDom.src = config.staticPath + '/mp3/msg.m4a';

		//音频按钮静音
		utils.on(slienceSwitch, 'click', function () {
			isSlienceEnable = !isSlienceEnable;
			utils.toggleClass(slienceSwitch, 'icon-slience', isSlienceEnable);
			utils.toggleClass(slienceSwitch, 'icon-bell', !isSlienceEnable);
		});

		chat.playSound = function () {
			if (!isSlienceEnable) {
				play();
			}
		};
	}

	function _setLogo() {
		if (!config.logo.enabled) return;
		var logoImgWapper = document.querySelector('.em-widget-tenant-logo');
		var logoImg = logoImgWapper.querySelector('img');

		utils.removeClass(logoImgWapper, 'hide');
		logoImg.src = config.logo.url;
	}

	function _setNotice() {
		var noticeContent = document.querySelector('.em-widget-tip .content');
		var noticeCloseBtn = document.querySelector('.em-widget-tip .tip-close');

		apiHelper.getNotice().then(function(notice){
			if(!notice.enabled) return;
			var slogan = notice.content;

			// 设置信息栏内容
			noticeContent.innerHTML = WebIM.utils.parseLink(slogan);
			// 显示信息栏
			utils.addClass(doms.imChat, 'has-tip');

			// 隐藏信息栏按钮
			utils.on(
				noticeCloseBtn,
				utils.click,
				function () {
					// 隐藏信息栏
					utils.removeClass(doms.imChat, 'has-tip');
				}
			);
		});
	}

	function _initEmoji() {
		utils.on(doms.emojiBtn, utils.click, function () {
			doms.textInput.blur();
			utils.toggleClass(doms.emojiWrapper, 'hide');

			// 懒加载，打开表情面板时才初始化图标
			if (!isEmojiInitilized) {
				isEmojiInitilized = true;
				doms.emojiContainer.innerHTML = genHtml();
			}
		});

		// 表情的选中
		utils.live('img.emoji', utils.click, function (e) {
			!utils.isMobile && doms.textInput.focus();
			doms.textInput.value += this.getAttribute('data-value');
			utils.trigger(doms.textInput, 'change');
		}, doms.emojiWrapper);

		// todo: kill .e-face to make it more elegant
		// ie8 does not support stopPropagation -_-||
		// 点击别处时隐藏表情面板
		utils.on(document, utils.click, function (ev) {
			var e = window.event || ev;
			var target = e.srcElement || e.target;

			if (!utils.hasClass(target, 'e-face')) {
				utils.addClass(doms.emojiWrapper, 'hide');
			}
		});

		function genHtml() {
			var path = WebIM.Emoji.path;
			var EMOJI_COUNT_PER_LINE = 7;

			return _.chain(WebIM.Emoji.map)
				// 生成图标html
				.map(function (value, key) {
					return '<div class="em-bar-emoji-bg e-face">'
						+ '<img class="e-face emoji" src="'
						+ path + value
						+ '" data-value=' + key + ' />'
						+ '</div>';
				})
				// 按照下标分组
				.groupBy(function (elem, index) {
					return Math.floor(index / EMOJI_COUNT_PER_LINE);
				})
				// 增加wrapper
				.map(function (elem) {
					return '<li class="e-face">' + elem.join('') + '</li>';
				})
				// 结束链式调用
				.value()
				// 把数组拼接成字符串
				.join('');
		}
	}

	function _setOffline() {
		switch (config.offDutyType) {
		case 'none':
			// 下班禁止留言、禁止接入会话
			utils.appendHTMLToBody([
				'<div class="em-model"></div>',
				'<div class="em-dialog off-duty-prompt">',
				'<div class="bg-color header">提示</div>',
				'<div class="body">',
				'<p class="content">' + config.offDutyWord + '</p>',
				'</div>',
				'</div>'
			].join(''));
			break;
		default:
			// 只允许留言此时无法关闭留言页面
			app.leaveMessage({hideCloseBtn: true});
			break;
		}
	}

	function _getGreeting() {
		Promise.all([
			apiHelper.getSystemGreeting(),
			apiHelper.getRobertGreeting()
		]).then(function(result){
			var systemGreetingText = result[0];
			var robotGreetingObj = result[1];
			var greetingTextType = robotGreetingObj.greetingTextType;
			var greetingText = robotGreetingObj.greetingText;
			var greetingObj = {};

			// 系统欢迎语
			systemGreetingText && channel.handleReceive({
				data: systemGreetingText,
				noprompt: true
			}, 'txt');

			// 机器人欢迎语
			switch (greetingTextType) {
			case 0:
				// 文本消息
				channel.handleReceive({
					data: greetingText,
					noprompt: true
				}, 'txt');
				break;
			case 1:
				// 菜单消息
				greetingObj = JSON.parse(greetingText.replace(/&amp;quot;/g, '"'));

				greetingObj.ext && channel.handleReceive({
					ext: greetingObj.ext,
					noprompt: true
				});
				break;
			case undefined:
				// 未设置机器人欢迎语
				break;
			default:
				console.error('unknown robot greeting type.');
				break;
			}
		});
	}

	function _getSkillgroupMenu() {
		apiHelper.getSkillgroupMenu().then(function(groupMenus) {
			// 当skillGroupMenuEnable 为false时  返回值为空
			if(!groupMenus) return;
			channel.handleReceive({
				data: groupMenus,
				type: 'skillgroupMenu',
				noprompt: true
			});
		});
	}

	function _scrollToBottom(){
		var currentMessageView = profile.currentOfficialAccount.messageView;<commit_msg>先显示欢迎语 再显示技能组列表 CLOUD-6468<commit_after>app.chat = (function (_const, utils, uikit, apiHelper, channel, profile, eventListener, satisfaction, startOrStopPollAgentState) {
	var isEmojiInitilized;
	var isMessageChannelReady;
	var inputBoxPosition;
	var config;

	// DOM init
	var topBar = document.querySelector('.em-widget-header');
	var editorView = document.querySelector('.em-widget-send-wrapper');
	easemobim.imBtn = document.getElementById('em-widgetPopBar');

	// todo: 把dom都移到里边
	var doms = {
		imChat: document.getElementById('em-kefu-webim-chat'),
		agentStatusText: topBar.querySelector('.em-header-status-text'),
		nickname: topBar.querySelector('.em-widget-header-nickname'),
		dragBar: topBar.querySelector('.drag-bar'),
		minifyBtn: topBar.querySelector('.btn-min'),
		audioBtn: topBar.querySelector('.btn-audio'),
		switchKeyboardBtn: topBar.querySelector('.btn-keyboard'),

		emojiBtn: editorView.querySelector('.em-bar-emoji'),
		sendImgBtn: editorView.querySelector('.em-widget-img'),
		sendFileBtn: editorView.querySelector('.em-widget-file'),
		sendBtn: editorView.querySelector('.em-widget-send'),
		satisfaction: editorView.querySelector('.em-widget-satisfaction'),
		toKefuBtn: editorView.querySelector('.em-widget-to-kefu'),
		textInput: editorView.querySelector('.em-widget-textarea'),
		noteBtn: editorView.querySelector('.em-widget-note'),

		imgInput: document.querySelector('.upload-img-container'),
		fileInput: document.querySelector('.upload-file-container'),
		emojiContainer: document.querySelector('.em-bar-emoji-container'),
		chatWrapper: document.querySelector('.chat-wrapper'),
		emojiWrapper: document.querySelector('.em-bar-emoji-wrapper'),

		topBar: topBar,
		editorView: editorView,
		block: null
	};

	var _startOrStopAgentInputStatePoller = (function(){
		var isStarted = false;
		var timerHandler;
		var preventTimestamp = 0;
		var inputState = topBar.querySelector('.em-agent-input-state');

		return function(officialAccount){
			if (officialAccount !== profile.currentOfficialAccount) return;

			var state = officialAccount.sessionState;
			var agentType = officialAccount.agentType;

			if (
				state === _const.SESSION_STATE.PROCESSING
				&& agentType !== _const.AGENT_ROLE.ROBOT
			){
				_start(officialAccount);
			}
			else {
				_stop();
			}
		};

		function _start() {
			isStarted = true;
			// 保证当前最多只有1个timerHandler
			clearInterval(timerHandler);
			apiHelper.getCurrentServiceSession().then(function (response){
				var sessionId = response && response.serviceSessionId;

				if (
					sessionId
					&& isStarted
					// 仅当聊天窗口打开时才发送请求
					&& profile.isChatWindowOpen
					// 仅当浏览器非最小化状态时才发送请求
					&& !utils.isBrowserMinimized()
				) {
					timerHandler = setInterval(function(){
						apiHelper.getAgentInputState(sessionId).then(function (entity){
							var currentTimestamp = entity.timestamp;
							var ifDisplayTypingState = entity.input_state_tips;

							// 为了先发送的请求后回来的异步问题，仅处理时间戳比当前大的response
							if (currentTimestamp > preventTimestamp){
								preventTimestamp = currentTimestamp;
								utils.toggleClass(inputState, 'hide', !displayTypingState);
							}
						});
					}, _const.AGENT_INPUT_STATE_INTERVAL);
				}
			});
		}

		function _stop() {
			clearInterval(timerHandler);
			utils.addClass(inputState, 'hide');
			isStarted = false;
		}
	}());

	var _startOrStopQueuingNumberPoller = (function () {
		var isStarted = false;
		var timerHandler;
		var preventTimestamp = 0;
		var $queuingNumberStatus = document.querySelector('.queuing-number-status');
		var $queuingNumberLabel = $queuingNumberStatus.querySelector('label');

		return function(officialAccount){
			if (profile.currentOfficialAccount !== officialAccount) return;

			var state = officialAccount.sessionState;

			if (state === _const.SESSION_STATE.WAIT){
				_start();
			}
			else {
				_stop();
			}
		};

		function _start() {
			isStarted = true;
			// 保证当前最多只有1个timer
			// 每次调用start都必须重新获取queueId
			clearInterval(timerHandler);
			apiHelper.getSessionQueueId().then(function (entity){
				var queueId;
				var sessionId;

				if (entity.state === 'Wait' && isStarted) {
					queueId = entity.queueId;
					sessionId = entity.serviceSessionId;
					// 避免请求在 轮询停止以后回来 而导致误开始
					// todo: use promise to optimize it
					timerHandler = setInterval(function () {
						apiHelper.getWaitListNumber().then(function (entity){
							var waitingNumber = entity.visitorUserWaitingNumber;
							var currentTimestamp = entity.visitorUserWaitingTimestamp;

							if (waitingNumber === 'no') {
								utils.addClass($queuingNumberStatus, 'hide');
							}
							else if (currentTimestamp > preventTimestamp) {
								preventTimestamp = currentTimestamp;
								utils.removeClass($queuingNumberStatus, 'hide');
								$queuingNumberLabel.innerHTML = entity.visitorUserWaitingNumber;
							}
						});
					}, 1000);
				}
			});
		}

		function _stop() {
			clearInterval(timerHandler);
			preventTimestamp = 0;
			isStarted = false;
			utils.addClass($queuingNumberStatus, 'hide');
		}
	}());

	function _displayOrHideTransferToKefuBtn(officialAccount){
		if (profile.currentOfficialAccount !== officialAccount) return;

		var state = officialAccount.sessionState;
		var agentType = officialAccount.agentType;
		var isRobotAgent = agentType === _const.AGENT_ROLE.ROBOT;

		if (state === _const.SESSION_STATE.PROCESSING){
			utils.toggleClass(doms.toKefuBtn, 'hide', !isRobotAgent);
		}
		else{
			apiHelper.getRobertIsOpen().then(function (isRobotEnable) {
				utils.toggleClass(doms.toKefuBtn, 'hide', !isRobotEnable);
			});
		}
	}

	var chat = {
		doms: doms,
		init: _init,
		handleReady: _handleReady,
		close: _close,
		show: _show,
		playSound: function(){},
		block: null
	};

	return chat;

	function _initSystemEventListener(){
		// 更新坐席在线状态标志
		eventListener.add(_const.SYSTEM_EVENT.SESSION_OPENED, startOrStopPollAgentState);
		eventListener.add(_const.SYSTEM_EVENT.SESSION_TRANSFERED, startOrStopPollAgentState);
		eventListener.add(_const.SYSTEM_EVENT.SESSION_CLOSED, startOrStopPollAgentState);
		eventListener.add(_const.SYSTEM_EVENT.SESSION_TRANSFERING, startOrStopPollAgentState);
		eventListener.add(_const.SYSTEM_EVENT.SESSION_RESTORED, startOrStopPollAgentState);

		// 更新坐席名称
		eventListener.add(_const.SYSTEM_EVENT.SESSION_OPENED, _updateAgentNickname);
		eventListener.add(_const.SYSTEM_EVENT.SESSION_TRANSFERED, _updateAgentNickname);
		eventListener.add(_const.SYSTEM_EVENT.SESSION_CLOSED, _updateAgentNickname);
		eventListener.add(_const.SYSTEM_EVENT.AGENT_NICKNAME_CHANGE, _updateAgentNickname);

		// report visitor info
		eventListener.add(_const.SYSTEM_EVENT.SESSION_OPENED, _reportVisitorInfo);
		eventListener.add(_const.SYSTEM_EVENT.SESSION_RESTORED, _reportVisitorInfo);

		// get greetings
		eventListener.add(_const.SYSTEM_EVENT.SESSION_RESTORED, _getGreetings);
		eventListener.add(_const.SYSTEM_EVENT.SESSION_NOT_CREATED, _getGreetings);

		if (config.toolbar.transferToKefu){
			// update transferToKefu button state
			// todo: add listener to official changed
			eventListener.add(_const.SYSTEM_EVENT.SESSION_OPENED, _displayOrHideTransferToKefuBtn);
			eventListener.add(_const.SYSTEM_EVENT.SESSION_TRANSFERED, _displayOrHideTransferToKefuBtn);
			eventListener.add(_const.SYSTEM_EVENT.SESSION_RESTORED, _displayOrHideTransferToKefuBtn);
			eventListener.add(_const.SYSTEM_EVENT.SESSION_NOT_CREATED, _displayOrHideTransferToKefuBtn);
		}

		if (config.grayList.agentInputStateEnable){
			// update transferToKefu button state
			// todo: add listener to official changed
			eventListener.add(_const.SYSTEM_EVENT.SESSION_OPENED, _startOrStopAgentInputStatePoller);
			eventListener.add(_const.SYSTEM_EVENT.SESSION_CLOSED, _startOrStopAgentInputStatePoller);
			eventListener.add(_const.SYSTEM_EVENT.SESSION_TRANSFERED, _startOrStopAgentInputStatePoller);
			eventListener.add(_const.SYSTEM_EVENT.SESSION_RESTORED, _startOrStopAgentInputStatePoller);
		}

		if (config.grayList.waitListNumberEnable){
			// update transferToKefu button state
			// todo: add listener to official changed
			eventListener.add(_const.SYSTEM_EVENT.SESSION_OPENED, _startOrStopQueuingNumberPoller);
			eventListener.add(_const.SYSTEM_EVENT.SESSION_CLOSED, _startOrStopQueuingNumberPoller);
			eventListener.add(_const.SYSTEM_EVENT.SESSION_TRANSFERED, _startOrStopQueuingNumberPoller);
			eventListener.add(_const.SYSTEM_EVENT.SESSION_CREATED, _startOrStopQueuingNumberPoller);
			eventListener.add(_const.SYSTEM_EVENT.SESSION_RESTORED, _startOrStopQueuingNumberPoller);
		}
	}

	function _reportVisitorInfo(officialAccount){
		if (officialAccount.hasReportedAttributes) return;

		var officialAccountId = officialAccount.official_account_id;
		var sessionId = officialAccount.sessionId;
		var isSessionOpen = officialAccount.isSessionOpen;

		if (isSessionOpen){
			officialAccount.hasReportedAttributes = true;
			apiHelper.reportVisitorAttributes(sessionId);
		}
	}

	function _initUI(){
		(utils.isTop || !config.minimum) && utils.removeClass(doms.imChat, 'hide');

		// 设置联系客服按钮文字
		document.querySelector('.em-widget-pop-bar').innerText = config.buttonText;

		// 添加移动端样式类
		utils.isMobile && utils.addClass(document.body, 'em-mobile');

		// 留言按钮
		config.ticket && utils.removeClass(doms.noteBtn, 'hide');

		// 最小化按钮
		config.minimum
			&& !utils.isTop
			&& utils.removeClass(doms.minifyBtn, 'hide');

		// 低版本浏览器不支持上传文件/图片
		if (WebIM.utils.isCanUploadFileAsync){
			utils.removeClass(doms.sendImgBtn, 'hide');
			utils.removeClass(doms.sendFileBtn, 'hide');
		}

		// h5title设置
		if(config.ui.H5Title.enabled){
			document.title = config.ui.H5Title.content;
		}

		// 静音按钮
		window.HTMLAudioElement
			&& !utils.isMobile
			&& config.soundReminder
			&& utils.removeClass(doms.audioBtn, 'hide');

		// 输入框位置开关
		utils.isMobile
			&& !config.hideKeyboard
			&& utils.removeClass(doms.switchKeyboardBtn, 'hide');

		// 满意度评价按钮
		config.satisfaction
			&& utils.removeClass(
				document.querySelector('.em-widget-satisfaction'),
				'hide'
			);

		// 设置企业名称
		_updateAgentNickname();
	}

	function _initSoundReminder(){
		if (!window.HTMLAudioElement || utils.isMobile || !config.soundReminder) {
			return;
		}

		var audioDom = document.createElement('audio');
		var slienceSwitch = document.querySelector('.em-widget-header .btn-audio');
		var isSlienceEnable = false;
		var play = _.throttle(function () {
			audioDom.play();
		}, 3000, { trailing: false });

		audioDom.src = config.staticPath + '/mp3/msg.m4a';

		//音频按钮静音
		utils.on(slienceSwitch, 'click', function () {
			isSlienceEnable = !isSlienceEnable;
			utils.toggleClass(slienceSwitch, 'icon-slience', isSlienceEnable);
			utils.toggleClass(slienceSwitch, 'icon-bell', !isSlienceEnable);
		});

		chat.playSound = function () {
			if (!isSlienceEnable) {
				play();
			}
		};
	}

	function _setLogo() {
		if (!config.logo.enabled) return;
		var logoImgWapper = document.querySelector('.em-widget-tenant-logo');
		var logoImg = logoImgWapper.querySelector('img');

		utils.removeClass(logoImgWapper, 'hide');
		logoImg.src = config.logo.url;
	}

	function _setNotice() {
		var noticeContent = document.querySelector('.em-widget-tip .content');
		var noticeCloseBtn = document.querySelector('.em-widget-tip .tip-close');

		apiHelper.getNotice().then(function(notice){
			if(!notice.enabled) return;
			var slogan = notice.content;

			// 设置信息栏内容
			noticeContent.innerHTML = WebIM.utils.parseLink(slogan);
			// 显示信息栏
			utils.addClass(doms.imChat, 'has-tip');

			// 隐藏信息栏按钮
			utils.on(
				noticeCloseBtn,
				utils.click,
				function () {
					// 隐藏信息栏
					utils.removeClass(doms.imChat, 'has-tip');
				}
			);
		});
	}

	function _initEmoji() {
		utils.on(doms.emojiBtn, utils.click, function () {
			doms.textInput.blur();
			utils.toggleClass(doms.emojiWrapper, 'hide');

			// 懒加载，打开表情面板时才初始化图标
			if (!isEmojiInitilized) {
				isEmojiInitilized = true;
				doms.emojiContainer.innerHTML = genHtml();
			}
		});

		// 表情的选中
		utils.live('img.emoji', utils.click, function (e) {
			!utils.isMobile && doms.textInput.focus();
			doms.textInput.value += this.getAttribute('data-value');
			utils.trigger(doms.textInput, 'change');
		}, doms.emojiWrapper);

		// todo: kill .e-face to make it more elegant
		// ie8 does not support stopPropagation -_-||
		// 点击别处时隐藏表情面板
		utils.on(document, utils.click, function (ev) {
			var e = window.event || ev;
			var target = e.srcElement || e.target;

			if (!utils.hasClass(target, 'e-face')) {
				utils.addClass(doms.emojiWrapper, 'hide');
			}
		});

		function genHtml() {
			var path = WebIM.Emoji.path;
			var EMOJI_COUNT_PER_LINE = 7;

			return _.chain(WebIM.Emoji.map)
				// 生成图标html
				.map(function (value, key) {
					return '<div class="em-bar-emoji-bg e-face">'
						+ '<img class="e-face emoji" src="'
						+ path + value
						+ '" data-value=' + key + ' />'
						+ '</div>';
				})
				// 按照下标分组
				.groupBy(function (elem, index) {
					return Math.floor(index / EMOJI_COUNT_PER_LINE);
				})
				// 增加wrapper
				.map(function (elem) {
					return '<li class="e-face">' + elem.join('') + '</li>';
				})
				// 结束链式调用
				.value()
				// 把数组拼接成字符串
				.join('');
		}
	}

	function _setOffline() {
		switch (config.offDutyType) {
		case 'none':
			// 下班禁止留言、禁止接入会话
			utils.appendHTMLToBody([
				'<div class="em-model"></div>',
				'<div class="em-dialog off-duty-prompt">',
				'<div class="bg-color header">提示</div>',
				'<div class="body">',
				'<p class="content">' + config.offDutyWord + '</p>',
				'</div>',
				'</div>'
			].join(''));
			break;
		default:
			// 只允许留言此时无法关闭留言页面
			app.leaveMessage({hideCloseBtn: true});
			break;
		}
	}

	function _getGreetings(officialAccount) {
		if (officialAccount.isSessionOpen) return;
		Promise.all([
			apiHelper.getSystemGreeting(),
			apiHelper.getRobertGreeting(),
			apiHelper.getSkillgroupMenu()
		]).then(function(result){
			var systemGreetingText = result[0];
			var robotGreetingObj = result[1];
			var groupMenus = result[2];
			var greetingTextType = robotGreetingObj.greetingTextType;
			var greetingText = robotGreetingObj.greetingText;
			var greetingObj = {};

			// 系统欢迎语
			systemGreetingText && channel.handleReceive({
				data: systemGreetingText,
				noprompt: true
			}, 'txt');

			// 机器人欢迎语
			switch (greetingTextType) {
			case 0:
				// 文本消息
				channel.handleReceive({
					data: greetingText,
					noprompt: true
				}, 'txt');
				break;
			case 1:
				// 菜单消息
				greetingObj = JSON.parse(greetingText.replace(/&amp;quot;/g, '"'));

				greetingObj.ext && channel.handleReceive({
					ext: greetingObj.ext,
					noprompt: true
				});
				break;
			case undefined:
				// 未设置机器人欢迎语
				break;
			default:
				console.error('unknown robot greeting type.');
				break;
			}

			// 技能组列表
			groupMenus && channel.handleReceive({
				data: groupMenus,
				type: 'skillgroupMenu',
				noprompt: true
			});
		});
	}

	function _scrollToBottom(){
		var currentMessageView = profile.currentOfficialAccount.messageView;
{'fraction_non_alphanumeric': 0.13008076261088308, 'fraction_numerical': 0.0017542698265589833, 'mean_word_length': 4.049807788734748, 'pattern_counts': {'":': 0, '<': 33, '<?xml version=': 0, '>': 37, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4981070', 'n_tokens_mistral': 12088, 'n_tokens_neox': 10564, 'n_words': 1560}