mirror of
https://github.com/chatopera/cosin.git
synced 2025-08-01 16:38:02 +08:00
#75 增加chatbot socket.io server
This commit is contained in:
parent
45bf5d6333
commit
546b8e6ddc
@ -705,7 +705,7 @@ public class UKDataContext {
|
||||
IM("/im/user"),
|
||||
AGENT("/im/agent"),
|
||||
ENTIM("/im/ent"),
|
||||
AIIM("/im/ai"),
|
||||
CHATBOT("/im/chatbot"),
|
||||
CALLCENTER("/callcenter/event"),
|
||||
CALLOUT("/callout/event");
|
||||
|
||||
|
@ -15,6 +15,8 @@ public abstract interface ChatbotRepository extends JpaRepository<Chatbot, Strin
|
||||
|
||||
public abstract boolean existsBySnsAccountIdentifierAndOrgi(String snsid, String orgi);
|
||||
|
||||
public abstract List<Chatbot> findByIdAndOrgi(String id, String orgi);
|
||||
|
||||
@Query(value = "select c from Chatbot c where " +
|
||||
"(:myorgans is null or c.organ IN :myorgans)")
|
||||
public Page<Chatbot> findByOrgans(@Param("myorgans") List<String> myorgans, Pageable pageRequest);
|
||||
|
@ -19,7 +19,7 @@ package com.chatopera.cc.webim.util.server;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
||||
import com.chatopera.cc.webim.util.server.handler.AiIMEventHandler;
|
||||
import com.chatopera.cc.webim.util.server.handler.ChatbotEventHandler;
|
||||
import com.chatopera.cc.webim.util.server.handler.EntIMEventHandler;
|
||||
import com.chatopera.cc.webim.util.server.handler.IMEventHandler;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@ -38,7 +38,7 @@ public class ServerRunner implements CommandLineRunner {
|
||||
private final SocketIONamespace imSocketNameSpace ;
|
||||
private final SocketIONamespace agentSocketIONameSpace ;
|
||||
private final SocketIONamespace entIMSocketIONameSpace ;
|
||||
private final SocketIONamespace aiIMSocketIONameSpace ;
|
||||
private final SocketIONamespace chatbotSocketIONameSpace ;
|
||||
private final SocketIONamespace callCenterSocketIONameSpace ;
|
||||
private final SocketIONamespace calloutSocketIONameSpace ;
|
||||
|
||||
@ -48,7 +48,7 @@ public class ServerRunner implements CommandLineRunner {
|
||||
imSocketNameSpace = server.addNamespace(UKDataContext.NameSpaceEnum.IM.getNamespace()) ;
|
||||
agentSocketIONameSpace = server.addNamespace(UKDataContext.NameSpaceEnum.AGENT.getNamespace()) ;
|
||||
entIMSocketIONameSpace = server.addNamespace(UKDataContext.NameSpaceEnum.ENTIM.getNamespace()) ;
|
||||
aiIMSocketIONameSpace = server.addNamespace(UKDataContext.NameSpaceEnum.AIIM.getNamespace()) ;
|
||||
chatbotSocketIONameSpace = server.addNamespace(UKDataContext.NameSpaceEnum.CHATBOT.getNamespace()) ;
|
||||
|
||||
if(UKDataContext.model.get("sales") != null && UKDataContext.model.get("sales") == true){
|
||||
calloutSocketIONameSpace = server.addNamespace(UKDataContext.NameSpaceEnum.CALLOUT.getNamespace());
|
||||
@ -81,10 +81,10 @@ public class ServerRunner implements CommandLineRunner {
|
||||
return entIMSocketIONameSpace;
|
||||
}
|
||||
|
||||
@Bean(name="aiimNamespace")
|
||||
public SocketIONamespace getAiIMSocketIONameSpace(SocketIOServer server){
|
||||
aiIMSocketIONameSpace.addListeners(new AiIMEventHandler(server));
|
||||
return aiIMSocketIONameSpace;
|
||||
@Bean(name="chatbotNamespace")
|
||||
public SocketIONamespace getChatbotSocketIONameSpace(SocketIOServer server){
|
||||
chatbotSocketIONameSpace.addListeners(new ChatbotEventHandler(server));
|
||||
return chatbotSocketIONameSpace;
|
||||
}
|
||||
|
||||
@Bean(name="callCenterNamespace")
|
||||
|
@ -0,0 +1,198 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Chatopera Inc, <https://www.chatopera.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.chatopera.cc.webim.util.server.handler;
|
||||
|
||||
import com.chatopera.cc.core.UKDataContext;
|
||||
import com.chatopera.cc.util.IPTools;
|
||||
import com.chatopera.cc.util.UKTools;
|
||||
import com.chatopera.cc.util.client.NettyClients;
|
||||
import com.chatopera.cc.webim.service.acd.ServiceQuene;
|
||||
import com.chatopera.cc.webim.service.cache.CacheHelper;
|
||||
import com.chatopera.cc.webim.service.repository.ConsultInviteRepository;
|
||||
import com.chatopera.cc.webim.util.MessageUtils;
|
||||
import com.chatopera.cc.webim.util.OnlineUserUtils;
|
||||
import com.chatopera.cc.webim.util.router.OutMessageRouter;
|
||||
import com.chatopera.cc.webim.util.server.message.AgentStatusMessage;
|
||||
import com.chatopera.cc.webim.util.server.message.ChatMessage;
|
||||
import com.chatopera.cc.webim.util.server.message.NewRequestMessage;
|
||||
import com.chatopera.cc.webim.web.model.AgentService;
|
||||
import com.chatopera.cc.webim.web.model.AiUser;
|
||||
import com.chatopera.cc.webim.web.model.CousultInvite;
|
||||
import com.chatopera.cc.webim.web.model.MessageOutContent;
|
||||
import com.corundumstudio.socketio.AckRequest;
|
||||
import com.corundumstudio.socketio.SocketIOClient;
|
||||
import com.corundumstudio.socketio.SocketIOServer;
|
||||
import com.corundumstudio.socketio.annotation.OnConnect;
|
||||
import com.corundumstudio.socketio.annotation.OnDisconnect;
|
||||
import com.corundumstudio.socketio.annotation.OnEvent;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Date;
|
||||
|
||||
public class ChatbotEventHandler
|
||||
{
|
||||
protected SocketIOServer server;
|
||||
|
||||
@Autowired
|
||||
public ChatbotEventHandler(SocketIOServer server)
|
||||
{
|
||||
this.server = server ;
|
||||
}
|
||||
|
||||
@OnConnect
|
||||
public void onConnect(SocketIOClient client)
|
||||
{
|
||||
try {
|
||||
String user = client.getHandshakeData().getSingleUrlParam("userid") ;
|
||||
String orgi = client.getHandshakeData().getSingleUrlParam("orgi") ;
|
||||
// String session = client.getHandshakeData().getSingleUrlParam("session") ;
|
||||
String appid = client.getHandshakeData().getSingleUrlParam("appid") ;
|
||||
String aiid = client.getHandshakeData().getSingleUrlParam("aiid") ;
|
||||
// String agent = client.getHandshakeData().getSingleUrlParam("agent") ;
|
||||
// String skill = client.getHandshakeData().getSingleUrlParam("skill") ;
|
||||
|
||||
if(!StringUtils.isBlank(user)){
|
||||
// /**
|
||||
// * 加入到 缓存列表
|
||||
// */
|
||||
NettyClients.getInstance().putIMEventClient(user, client);
|
||||
MessageOutContent outMessage = new MessageOutContent() ;
|
||||
CousultInvite invite = OnlineUserUtils.cousult(appid , orgi, UKDataContext.getContext().getBean(ConsultInviteRepository.class));
|
||||
if(invite!=null && !StringUtils.isBlank(invite.getAisuccesstip())) {
|
||||
outMessage.setMessage(invite.getAisuccesstip());
|
||||
}else{
|
||||
outMessage.setMessage("欢迎使用优客服小E,我来帮您解答问题");
|
||||
}
|
||||
|
||||
outMessage.setMessageType(UKDataContext.MessageTypeEnum.MESSAGE.toString());
|
||||
outMessage.setCalltype(UKDataContext.CallTypeEnum.IN.toString());
|
||||
outMessage.setNickName("AI");
|
||||
outMessage.setCreatetime(UKTools.dateFormate.format(new Date()));
|
||||
|
||||
client.sendEvent(UKDataContext.MessageTypeEnum.STATUS.toString(), outMessage);
|
||||
|
||||
InetSocketAddress address = (InetSocketAddress) client.getRemoteAddress() ;
|
||||
String ip = UKTools.getIpAddr(client.getHandshakeData().getHttpHeaders(), address.getHostString()) ;
|
||||
AiUser aiUser = new AiUser(user, user, System.currentTimeMillis() , orgi,IPTools.getInstance().findGeography(ip)) ;
|
||||
aiUser.setSessionid(UKTools.getContextID(client.getSessionId().toString()));
|
||||
aiUser.setAppid(appid);
|
||||
aiUser.setAiid(aiid);
|
||||
aiUser.setUsername(UKDataContext.GUEST_USER+"_"+UKTools.genIDByKey(aiUser.getId()));
|
||||
aiUser.setChannel(UKDataContext.ChannelTypeEnum.WEBIM.toString());
|
||||
|
||||
AgentService agentService = ServiceQuene.processAiService(aiUser, orgi) ;
|
||||
aiUser.setAgentserviceid(agentService.getId());
|
||||
|
||||
CacheHelper.getOnlineUserCacheBean().put(user, aiUser, UKDataContext.SYSTEM_ORGI);
|
||||
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//添加@OnDisconnect事件,客户端断开连接时调用,刷新客户端信息
|
||||
@OnDisconnect
|
||||
public void onDisconnect(SocketIOClient client) throws Exception
|
||||
{
|
||||
String user = client.getHandshakeData().getSingleUrlParam("userid") ;
|
||||
String orgi = client.getHandshakeData().getSingleUrlParam("orgi") ;
|
||||
if(!StringUtils.isBlank(user)){
|
||||
NettyClients.getInstance().removeIMEventClient(user , UKTools.getContextID(client.getSessionId().toString()));
|
||||
AiUser aiUser = (AiUser) CacheHelper.getOnlineUserCacheBean().getCacheObject(user, orgi) ;
|
||||
if(aiUser!=null) {
|
||||
ServiceQuene.processAiService(aiUser, orgi) ;
|
||||
CacheHelper.getOnlineUserCacheBean().delete(user,UKDataContext.SYSTEM_ORGI) ;
|
||||
}
|
||||
}
|
||||
client.disconnect();
|
||||
}
|
||||
|
||||
//消息接收入口,网站有新用户接入对话
|
||||
@OnEvent(value = "new")
|
||||
public void onEvent(SocketIOClient client, AckRequest request, NewRequestMessage data)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
//消息接收入口,坐席状态更新
|
||||
@OnEvent(value = "agentstatus")
|
||||
public void onEvent(SocketIOClient client, AckRequest request, AgentStatusMessage data)
|
||||
{
|
||||
System.out.println(data.getMessage());
|
||||
}
|
||||
|
||||
//消息接收入口,收发消息,用户向坐席发送消息和 坐席向用户发送消息
|
||||
@OnEvent(value = "message")
|
||||
public void onEvent(SocketIOClient client, AckRequest request, ChatMessage data)
|
||||
{
|
||||
String orgi = client.getHandshakeData().getSingleUrlParam("orgi") ;
|
||||
String aiid = client.getHandshakeData().getSingleUrlParam("aiid") ;
|
||||
String user = client.getHandshakeData().getSingleUrlParam("userid") ;
|
||||
if(data.getType() == null){
|
||||
data.setType("message");
|
||||
}
|
||||
/**
|
||||
* 以下代码主要用于检查 访客端的字数限制
|
||||
*/
|
||||
CousultInvite invite = OnlineUserUtils.cousult(data.getAppid(),data.getOrgi(), UKDataContext.getContext().getBean(ConsultInviteRepository.class));
|
||||
if(invite!=null && invite.getMaxwordsnum() > 0) {
|
||||
if(!StringUtils.isBlank(data.getMessage()) && data.getMessage().length() > invite.getMaxwordsnum()){
|
||||
data.setMessage(data.getMessage().substring(0 , invite.getMaxwordsnum()));
|
||||
}
|
||||
}else if(!StringUtils.isBlank(data.getMessage()) && data.getMessage().length() > 300){
|
||||
data.setMessage(data.getMessage().substring(0 , 300));
|
||||
}
|
||||
data.setSessionid(UKTools.getContextID(client.getSessionId().toString()));
|
||||
/**
|
||||
* 处理表情
|
||||
*/
|
||||
data.setMessage(UKTools.processEmoti(data.getMessage()));
|
||||
data.setTousername(UKDataContext.ChannelTypeEnum.AI.toString());
|
||||
|
||||
data.setAiid(aiid);
|
||||
|
||||
Object cacheData = (AiUser) CacheHelper.getOnlineUserCacheBean().getCacheObject(user,orgi) ;
|
||||
if(cacheData!=null && cacheData instanceof AiUser){
|
||||
AiUser aiUser = (AiUser)cacheData ;
|
||||
data.setAgentserviceid(aiUser.getAgentserviceid());
|
||||
data.setChannel(aiUser.getChannel());
|
||||
/**
|
||||
* 一定要设置 ContextID
|
||||
*/
|
||||
data.setContextid(aiUser.getAgentserviceid());
|
||||
}
|
||||
MessageOutContent outMessage = MessageUtils.createAiMessage(data , data.getAppid() , data.getChannel() , UKDataContext.CallTypeEnum.IN.toString() , UKDataContext.AiItemType.USERINPUT.toString() , UKDataContext.MediaTypeEnum.TEXT.toString(), data.getUserid()) ;
|
||||
if(!StringUtils.isBlank(data.getUserid()) && UKDataContext.MessageTypeEnum.MESSAGE.toString().equals(data.getType())){
|
||||
if(!StringUtils.isBlank(data.getTouser())){
|
||||
OutMessageRouter router = null ;
|
||||
router = (OutMessageRouter) UKDataContext.getContext().getBean(data.getChannel()) ;
|
||||
if(router!=null){
|
||||
router.handler(data.getTouser(), UKDataContext.MessageTypeEnum.MESSAGE.toString(), data.getAppid(), outMessage);
|
||||
}
|
||||
}
|
||||
if(cacheData!=null && cacheData instanceof AiUser){
|
||||
AiUser aiUser = (AiUser)cacheData ;
|
||||
aiUser.setTime(System.currentTimeMillis());
|
||||
CacheHelper.getOnlineUserCacheBean().put(user, aiUser, UKDataContext.SYSTEM_ORGI);
|
||||
}
|
||||
}
|
||||
UKTools.ai(data);
|
||||
}
|
||||
}
|
@ -56,6 +56,8 @@ import java.io.IOException;
|
||||
import java.net.URLDecoder;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
@Controller
|
||||
@ -474,32 +476,19 @@ public class IMController extends Handler {
|
||||
if (UKDataContext.model.get("chatbot") != null &&
|
||||
StringUtils.isNotBlank(invite.getAiid()) &&
|
||||
invite.isAi() &&
|
||||
(StringUtils.equals(ai, "true") || (invite.isAifirst() && ai == null))) { //启用 AI , 并且 AI优先 接待
|
||||
DataExchangeInterface dataInterface = (DataExchangeInterface) UKDataContext.getContext().getBean("aiconfig");
|
||||
AiConfig aiConfig = (AiConfig) dataInterface.getDataByIdAndOrgi(aiid, invite.getOrgi());
|
||||
if (aiConfig != null) {
|
||||
map.addAttribute("aiConfig", aiConfig);
|
||||
invite.isAifirst()) { //启用 AI , 并且 AI优先 接待
|
||||
HashMap<String, String> chatbotConfig = new HashMap<String, String>();
|
||||
chatbotConfig.put("botname", invite.getAiname());
|
||||
chatbotConfig.put("botid", invite.getAiid());
|
||||
chatbotConfig.put("botwelcome", invite.getAimsg());
|
||||
chatbotConfig.put("botfirst", Boolean.toString(invite.isAifirst()));
|
||||
chatbotConfig.put("isai", Boolean.toString(invite.isAi()));
|
||||
if (chatbotConfig != null) {
|
||||
map.addAttribute("chatbotConfig", chatbotConfig);
|
||||
}
|
||||
view = request(super.createRequestPageTempletResponse("/apps/im/ai/index"));
|
||||
view = request(super.createRequestPageTempletResponse("/apps/im/chatbot/index"));
|
||||
if (CheckMobile.check(request.getHeader("User-Agent")) || !StringUtils.isBlank(mobile)) {
|
||||
view = request(super.createRequestPageTempletResponse("/apps/im/ai/mobile")); //智能机器人 移动端
|
||||
}
|
||||
if (UKDataContext.model.get("xiaoe") != null) {
|
||||
List<Topic> topicList = OnlineUserUtils.cacheHotTopic((DataExchangeInterface) UKDataContext.getContext().getBean("topic"), super.getUser(request), orgi, aiid);
|
||||
|
||||
/**
|
||||
* 初步按照地区匹配分类筛选
|
||||
*/
|
||||
List<KnowledgeType> topicTypeList = OnlineUserUtils.topicType(orgi, ipdata, OnlineUserUtils.cacheHotTopicType((DataExchangeInterface) UKDataContext.getContext().getBean("topictype"), super.getUser(request), orgi, aiid));
|
||||
|
||||
/**
|
||||
* 第二步按照 有 热点主题的 分类做筛选
|
||||
*/
|
||||
map.addAttribute("topicList", OnlineUserUtils.topic(orgi, topicTypeList, topicList));
|
||||
/**
|
||||
* 第三步筛选 分类,如果无热点知识,则不显示分类
|
||||
*/
|
||||
map.addAttribute("topicTypeList", OnlineUserUtils.filterTopicType(topicTypeList, topicList));
|
||||
view = request(super.createRequestPageTempletResponse("/apps/im/chatbot/mobile")); //智能机器人 移动端
|
||||
}
|
||||
} else {
|
||||
if (CheckMobile.check(request.getHeader("User-Agent")) || !StringUtils.isBlank(mobile)) {
|
||||
|
@ -20,6 +20,7 @@ import com.chatopera.chatbot.ChatbotAPIRuntimeException;
|
||||
import org.hibernate.annotations.GenericGenerator;
|
||||
|
||||
import javax.persistence.*;
|
||||
import java.io.Serializable;
|
||||
import java.net.MalformedURLException;
|
||||
import java.util.Date;
|
||||
|
||||
|
@ -0,0 +1,581 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta http-equiv="Cache-Control" content="no-siteapp" />
|
||||
<meta name="viewport"
|
||||
content="width=device-width, maximum-scale=1.0, initial-scale=1.0,initial-scale=1.0,user-scalable=no" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<title>在线咨询</title>
|
||||
|
||||
<link rel="shortcut icon" type="image/x-icon" href="/images/favicon.ico?t=1489039620156"/>
|
||||
<link rel="stylesheet" type="text/css" href="/im/css/ukefu.css">
|
||||
<link rel="stylesheet" id="skin" type="text/css" href="/im/css/default/ukefu.css">
|
||||
<!-- kindeditor -->
|
||||
<link rel="stylesheet" type="text/css" href="/im/js/kindeditor/themes/default/default.css">
|
||||
|
||||
<script src="/js/jquery-1.10.2.min.js"></script>
|
||||
<script type="text/javascript" src="/im/js/kindeditor/kindeditor.js"></script>
|
||||
<script type="text/javascript" src="/im/js/kindeditor/lang/zh-CN.js"></script>
|
||||
<script src="/im/js/socket.io.js"></script>
|
||||
<script type="text/javascript">
|
||||
var editor , words;
|
||||
var newmessage = [] , ring = [];
|
||||
newmessage['mp3'] = '/images/message.mp3';
|
||||
ring['mp3'] = '/images/ring.mp3';
|
||||
KindEditor.ready(function (K) {
|
||||
editor = K.create('textarea[name="content"]', {
|
||||
autoHeightMode: false,
|
||||
width: "100%",
|
||||
resizeType: 0,
|
||||
themeType: 'simple',
|
||||
fontsize: 16,
|
||||
newlineTag : "br" ,
|
||||
uploadJson : "/im/image/upload.html?userid=${userid!''}",
|
||||
allowFileManager : false,
|
||||
allowInsertUpload:false, //增加的参数,上传图片后是否插入到当前区域
|
||||
allowImageRemote:false,
|
||||
filterMode:true,
|
||||
items: ['emoticons', 'cut' , 'image','insertfile'],
|
||||
htmlTags: {img : ['src', 'width', 'height', 'border', 'alt', 'title', 'align', '.width', '.height', '.border'] , br:[]} ,
|
||||
afterChange : function() {
|
||||
var count = this.count() ;
|
||||
var limitNum = <#if inviteData.maxwordsnum gt 0>${inviteData.maxwordsnum}<#else>300</#if>; //设定限制字数
|
||||
var pattern = '还可以输入' + limitNum + '字';
|
||||
var strValue = this.html();
|
||||
if(count > limitNum) {
|
||||
pattern = ('字数超过限制,请适当删除部分内容');
|
||||
//超过字数限制自动截取
|
||||
strValue = strValue.substring(0,limitNum);
|
||||
editor.html(strValue);
|
||||
} else {
|
||||
//计算剩余字数
|
||||
var result = limitNum - this.count();
|
||||
pattern = '还可以输入' + result + '字';
|
||||
if(result < 20){
|
||||
document.getElementById('surplus').style.color = "red" ;
|
||||
}else{
|
||||
document.getElementById('surplus').style.color = "#000000" ;
|
||||
}
|
||||
}
|
||||
if(this.count("text") == 0){
|
||||
strValue= "" ;
|
||||
}
|
||||
if(words != this.count("text")){
|
||||
socket.emit('message', {
|
||||
appid : "${appid!''}",
|
||||
userid:"${userid!''}",
|
||||
type:"writing",
|
||||
session:"${sessionid!''}",
|
||||
orgi:"${orgi!''}",
|
||||
message : strValue
|
||||
});
|
||||
}
|
||||
words = this.count("text") ;
|
||||
document.getElementById('surplus').innerHTML = count+"/"+limitNum+" , " + pattern; //输入显示
|
||||
////////
|
||||
},
|
||||
afterCreate : function() { //设置编辑器创建后执行的回调函数
|
||||
var self = this;
|
||||
<#if inviteData?? && inviteData.ctrlenter?? && inviteData.ctrlenter == true>
|
||||
//Ctrl+Enter提交表单
|
||||
K.ctrl(document, 13, function() {
|
||||
self.sync();
|
||||
sendMessage();
|
||||
});
|
||||
K.ctrl(self.edit.doc, 13, function() {
|
||||
self.sync();
|
||||
sendMessage();
|
||||
});
|
||||
<#else>
|
||||
var kindEditorIframe = $("iframe").contents().find("body");
|
||||
kindEditorIframe.keydown(function (event) {
|
||||
if(event.keyCode==13 && !event.ctrlKey){
|
||||
self.sync();
|
||||
sendMessage();
|
||||
return false;
|
||||
}else if(event.keyCode==13 && event.ctrlKey){
|
||||
editor.insertHtml('<br/>');
|
||||
}
|
||||
});
|
||||
//Ctrl+Enter提交表单
|
||||
K.ctrl(document, 13, function() {
|
||||
editor.insertHtml('<br/>');
|
||||
});
|
||||
K.ctrl(self.edit.doc, 13, function() {
|
||||
editor.insertHtml('<br/>');
|
||||
});
|
||||
</#if>
|
||||
}
|
||||
});
|
||||
});
|
||||
KindEditor.options.cssData = "body { font-size: 15px; font-family:'Microsoft Yahei', 'Helvetica', 'Simsun', 'Arial';}";
|
||||
var R3Ajax = {
|
||||
ajax:function(opt){
|
||||
var xhr = this.createXhrObject();
|
||||
xhr.onreadystatechange = function(){
|
||||
if(xhr.readyState!=4) return ;
|
||||
(xhr.status===200 ?
|
||||
opt.success(xhr.responseText,xhr.responseXML):
|
||||
opt.error(xhr.responseText,xhr.status));
|
||||
}
|
||||
xhr.open(opt.type,opt.url,true);
|
||||
if(opt.type!=='post')
|
||||
opt.data=null;
|
||||
else
|
||||
xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
|
||||
opt.data = this.parseQuery(opt.data);
|
||||
xhr.send(opt.data);
|
||||
},
|
||||
post:function(url,success,data){
|
||||
var popt = {
|
||||
url:url,
|
||||
type:'post',
|
||||
data:data,
|
||||
success:success,
|
||||
error:function(data){}
|
||||
}
|
||||
this.ajax(popt);
|
||||
},
|
||||
get:function(url,success){
|
||||
var gopt = {
|
||||
url:url,
|
||||
type:'get',
|
||||
success:success,
|
||||
error:function(){}
|
||||
}
|
||||
this.ajax(gopt);
|
||||
},
|
||||
createXhrObject:function(){
|
||||
var methods = [
|
||||
function(){ return new XMLHttpRequest();},
|
||||
function(){ return new ActiveXObject('Msxml2.XMLHTTP');},
|
||||
function(){ return new ActiveXObject('Microsoft.XMLHTTP');}
|
||||
];
|
||||
for(var i=0;len=methods.length,i<len;i++){
|
||||
try{
|
||||
methods[i]();
|
||||
}catch(e){
|
||||
continue;
|
||||
}
|
||||
this.createXhrObject = methods[i];
|
||||
return methods[i]();
|
||||
}
|
||||
throw new Error('Could not create an XHR object.');
|
||||
},
|
||||
parseQuery:function(json){
|
||||
if(typeof json == 'object'){
|
||||
var str = '';
|
||||
for(var i in json){
|
||||
str += "&"+i+"="+encodeURIComponent(json[i]);
|
||||
}
|
||||
return str.length==0 ? str : str.substring(1);
|
||||
}else{
|
||||
return json;
|
||||
}
|
||||
},
|
||||
audioplayer:function(id, file, loop) {
|
||||
var audioplayer = document.getElementById(id);
|
||||
if (audioplayer != null) {
|
||||
document.body.removeChild(audioplayer);
|
||||
}
|
||||
|
||||
if (typeof(file) != 'undefined') {
|
||||
if (navigator.userAgent.indexOf("MSIE") > 0) { // IE
|
||||
var player = document.createElement('bgsound');
|
||||
player.id = id;
|
||||
player.src = file['mp3'];
|
||||
player.setAttribute('autostart', 'true');
|
||||
if (loop) {
|
||||
player.setAttribute('loop', 'infinite');
|
||||
}
|
||||
document.body.appendChild(player);
|
||||
|
||||
} else { // Other FF Chome Safari Opera
|
||||
var player = document.createElement('audio');
|
||||
player.id = id;
|
||||
player.setAttribute('autoplay', 'autoplay');
|
||||
if (loop) {
|
||||
player.setAttribute('loop', 'loop');
|
||||
}
|
||||
document.body.appendChild(player);
|
||||
|
||||
var mp3 = document.createElement('source');
|
||||
mp3.src = file['mp3'];
|
||||
mp3.type = 'audio/mpeg';
|
||||
player.appendChild(mp3);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Date.prototype.format = function(fmt) {
|
||||
var o = {
|
||||
"M+" : this.getMonth()+1, //月份
|
||||
"d+" : this.getDate(), //日
|
||||
"h+" : this.getHours(), //小时
|
||||
"m+" : this.getMinutes(), //分
|
||||
"s+" : this.getSeconds(), //秒
|
||||
"q+" : Math.floor((this.getMonth()+3)/3), //季度
|
||||
"S" : this.getMilliseconds() //毫秒
|
||||
};
|
||||
if(/(y+)/.test(fmt)) {
|
||||
fmt=fmt.replace(RegExp.$1, (this.getFullYear()+"").substr(4 - RegExp.$1.length));
|
||||
}
|
||||
for(var k in o) {
|
||||
if(new RegExp("("+ k +")").test(fmt)){
|
||||
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length==1) ? (o[k]) : (("00"+ o[k]).substr((""+ o[k]).length)));
|
||||
}
|
||||
}
|
||||
return fmt;
|
||||
}
|
||||
var R3Helper = {
|
||||
resize : function(){
|
||||
var height = document.body.offsetHeight ;
|
||||
document.getElementById('above').style.height = (height - 194 - 50)+"px" ;
|
||||
}
|
||||
}
|
||||
function submitForm(form){
|
||||
R3Ajax.post("/im/satis.html?orgi=${orgi!''}" , function(){
|
||||
document.getElementById("diaShade").style.display = "none" ;
|
||||
document.getElementById("dialogWrap").style.display = "none" ;
|
||||
alert("服务评价已提及,请关闭浏览器!");
|
||||
service_end = true;
|
||||
} , "id="+form.id.value+"&satislevel="+document.getElementById("satislevel_input").value +"&satiscomment="+encodeURIComponent(document.getElementById("comment_input").value ));
|
||||
return false ;
|
||||
}
|
||||
window.onbeforeunload = function(){
|
||||
if(service_end == false){
|
||||
<#if sessionConfig?? && sessionConfig.satisfaction?? && sessionConfig.satisfaction>
|
||||
document.getElementById("diaShade").style.display = "block" ;
|
||||
document.getElementById("dialogWrap").style.display = "block" ;
|
||||
</#if>
|
||||
return "您确定断开对话?" ;
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
<!-- kindeditor -->
|
||||
</head>
|
||||
<body style="overflow:hidden;" class="ukefu-point-text">
|
||||
<div class="large ukefu-im-theme <#if type?? && type='text'>ukefu-theme-border-${inviteData.consult_dialog_color!''}</#if>">
|
||||
<div id="containter" class="clearfix">
|
||||
<div id="header" class="theme${inviteData.consult_dialog_color!''}">
|
||||
<img src="<#if inviteData?? && inviteData.consult_dialog_logo??>/res/image.html?id=${inviteData.consult_dialog_logo?url}<#else>/images/logo.png</#if>" style="height:30px;padding:10px;">
|
||||
<div class="ukefu-func-tab">
|
||||
<ul>
|
||||
<#if models?? && models["xiaoe"]?? && models["xiaoe"] == true && inviteData.ai && aiid??>
|
||||
<#if !exchange?? || exchange == "true">
|
||||
<li><a href="/im/index.html?appid=${appid!''}&orgi=${orgi!''}<#if aiid??>&aiid=${aiid}</#if>&ai=true<#if client??>&client=${client!''}</#if><#if type??>&type=text</#if><#if skill??>&skill=${skill!''}</#if><#if agent??>&agent=${agent!''}</#if><#if title??>&title=${title?url}</#if><#if url??>&url=${url?url}</#if><#if traceid??>&traceid=${traceid}</#if>&userid=${userid!''}&sessionid=${sessionid!''}&t=${.now?long}">智能客服</a></li>
|
||||
</#if>
|
||||
<li class="cur"><a href="javascript:void(0)">人工坐席</a></li>
|
||||
<#else>
|
||||
<li class="cur"><a href="javascript:void(0)">人工坐席</a></li>
|
||||
</#if>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-left">
|
||||
<div class="chat-above" id="above">
|
||||
<#if welcomeAd>
|
||||
<div class="clearfix message welcome">
|
||||
<span id="welcome-message">
|
||||
<#if welcomeAd.adtype =="image">
|
||||
<a href='${welcomeAd.url!''}' title='${welcomeAd.tiptext!''}' target='_blank'><img src='${welcomeAd.imgurl!''}' style='max-width:420px;max-height:178px;margin:0px;vertical-align: middle;'/></a>
|
||||
<#else>
|
||||
<div style='padding:0px 5px 10px 5px;border-bottom:1px solid #dedede;'><a href='${welcomeAd.url!''}' title='${welcomeAd.tiptext!''}' target='_blank' id='point_ad_text'>${(welcomeAd.content!'')?no_esc}</a></div>
|
||||
</#if>
|
||||
</span>
|
||||
</div>
|
||||
</#if>
|
||||
<div class="clearfix message welcome">
|
||||
<span id="welcome-message">${(inviteData.dialog_message!'欢迎您来咨询!欢迎使用春松客服!如需帮助请联系 info@chatopera.com')?no_esc}</span></div>
|
||||
<#if chatMessageList?? && chatMessageList.content??>
|
||||
<#list chatMessageList.content?reverse as chatMessage>
|
||||
<#if chatMessage.userid?? && userid?? && chatMessage.calltype?? && chatMessage.calltype = "呼入">
|
||||
<div class="clearfix chat-block">
|
||||
<div class="chat-right">
|
||||
<img class="user-img" src="/im/img/user.png" alt="">
|
||||
<div class="chat-message">
|
||||
<label class="time">${chatMessage.createtime!''}</label>
|
||||
<label class="user">${chatMessage.username!''}</label>
|
||||
</div>
|
||||
<div class="chatting-right">
|
||||
<i class="arrow arrow${inviteData.consult_dialog_color!''}"></i>
|
||||
<div class="chat-content theme${inviteData.consult_dialog_color!''}"><#include "/apps/im/media/message.html"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<#else>
|
||||
<div class="clearfix chat-block">
|
||||
<div class="chat-left">
|
||||
<img class="user-img" src="<#if inviteData?? && inviteData.consult_dialog_headimg??>/res/image.html?id=${inviteData.consult_dialog_headimg?url}<#else>/images/agent.png</#if>" alt="">
|
||||
<div class="chat-message">
|
||||
<label class="user"><#if chatMessage?? && chatMessage.chatype?? && chatMessage.chatype == 'aireply'>${inviteData.ainame!'小E'}<#else>${chatMessage.username!''}</#if></label>
|
||||
<label class="time">${chatMessage.createtime!''}</label>
|
||||
</div>
|
||||
<div class="chatting-left">
|
||||
<i class="arrow"></i>
|
||||
<div class="chat-content"><#include "/apps/im/media/message.html"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</#if>
|
||||
</#list>
|
||||
</#if>
|
||||
</div>
|
||||
<div class="chat-bottom" id="bottom">
|
||||
<textarea id="message" name="content" style="visibility:hidden;"></textarea>
|
||||
<div class="btn-push clearfix">
|
||||
<div style="float:left;height:34px;line-height:34px;margin: 10px 20px 10px 5px;" id="surplus">0/200</div>
|
||||
<button type="button" class="send-btn active special clearfix" id="sent" onclick="sendMessage()">
|
||||
发送
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-rig">
|
||||
<div class="content-list" style="padding-top:50px;">
|
||||
<div class="content-head">
|
||||
<p>信息提示</p>
|
||||
</div>
|
||||
<ul>
|
||||
<#if inviteData.dialog_name?? && inviteData.dialog_name != "">
|
||||
<li>
|
||||
<p>名称:${inviteData.dialog_name!''}</p>
|
||||
</li>
|
||||
</#if>
|
||||
<#if inviteData.dialog_address?? && inviteData.dialog_address != "">
|
||||
<li>
|
||||
<p>地址:${inviteData.dialog_address!''}</p>
|
||||
</li>
|
||||
</#if>
|
||||
<#if inviteData.dialog_phone?? && inviteData.dialog_phone != "">
|
||||
<li>
|
||||
<p>电话:${inviteData.dialog_phone!''}</p>
|
||||
</li>
|
||||
</#if>
|
||||
<#if inviteData.dialog_mail?? && inviteData.dialog_mail != "">
|
||||
<li>
|
||||
<p>邮件:${inviteData.dialog_mail!''}</p>
|
||||
</li>
|
||||
</#if>
|
||||
<#if inviteData.dialog_mail?? && inviteData.dialog_introduction != "">
|
||||
<li>
|
||||
<p style="text-indent:25px;line-height:25px;">${(inviteData.dialog_introduction!'')?no_esc}</p>
|
||||
</li>
|
||||
</#if>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="content-pic" style="width:100%;height:192px;">
|
||||
<#if imageAd>
|
||||
<#if imageAd.adtype =="image">
|
||||
<a href='${imageAd.url!''}' title='${imageAd.tiptext!''}' target='_blank'><img src='${imageAd.imgurl!''}' style='max-width:100%;max-height:190px;margin:0px;vertical-align: middle;'/></a>
|
||||
<#else>
|
||||
<div style='padding:0px 5px 10px 5px;border-bottom:1px solid #dedede;'><a href='${imageAd.url!''}' title='${imageAd.tiptext!''}' target='_blank' id='point_ad_text'>${(imageAd.content!'')?no_esc}</a></div>
|
||||
</#if>
|
||||
<#elseif inviteData.dialog_ad??>
|
||||
<img src="/res/image.html?id=${inviteData.dialog_ad!''}" style="height:190px;width:100%;">
|
||||
</#if>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="footer"></div>
|
||||
</div>
|
||||
<#if sessionConfig?? && sessionConfig.satisfaction?? && sessionConfig.satisfaction>
|
||||
<!--调查问卷弹框-->
|
||||
<div class="diaShade" id="diaShade" style="display: none"></div>
|
||||
<div class="dialogWrap" id="dialogWrap" style="display: none">
|
||||
<div class="dialogCon">
|
||||
<form id="commentContent" onSubmit="return submitForm(this)">
|
||||
<input type="hidden" name="id" id="agentserviceid" name="agentserviceid">
|
||||
<h2 class="diaHeader clearfix">
|
||||
<span>评价</span>
|
||||
<hr>
|
||||
</h2>
|
||||
<p class="title">您是否对此次服务满意?</p>
|
||||
<!--评价-->
|
||||
<p style="margin-top:20px;">
|
||||
<span style="float:left;">评价:</span>
|
||||
<span style="position: relative;top: 0px;left: 13px;">
|
||||
<#assign defaultvalue = "">
|
||||
<#if commentList??>
|
||||
<#list commentList as comment>
|
||||
<input type="radio" name="satislevel" value="${comment.code!''}" <#if comment_index == 0>checked="checked"</#if> id="dic_${comment.id!''}" onclick="document.getElementById('satislevel_input').value = this.value">
|
||||
<label for="dic_${comment.id!''}" class="radio">${comment.name!''}</label>
|
||||
<#if defaultvalue == "">
|
||||
<#assign defaultvalue = comment.code>
|
||||
</#if>
|
||||
</#list>
|
||||
</#if>
|
||||
<input type="hidden" id="satislevel_input" name="t" value="${defaultvalue}">
|
||||
</span>
|
||||
</p>
|
||||
<!--描述-->
|
||||
<p style="margin-top:20px;">
|
||||
<span style="float:left;">意见:</span>
|
||||
<span style="position: relative;top: 0px;left: 10px;">
|
||||
<input type="hidden" id="comment_input" name="t">
|
||||
<#if commentItemList??>
|
||||
<#list commentItemList as item>
|
||||
<div style="margin-left:55px;margin-bottom:20px;">
|
||||
<input type="radio" name="comment" id="item_${item.id!''}" value="${item.id!''}" onclick="document.getElementById('comment_input').value = this.value">
|
||||
<label for="item_${item.id!''}" class="radio">
|
||||
${item.name!''}
|
||||
</label>
|
||||
</div>
|
||||
</#list>
|
||||
</#if>
|
||||
</span>
|
||||
</p>
|
||||
<!--按钮-->
|
||||
<p class="submitBtnWrap">
|
||||
<input type="submit" class="btn submitBtn" id="submitBtn" value="提 交">
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</#if>
|
||||
<script>
|
||||
var service_end = false ;
|
||||
R3Helper.resize();
|
||||
// 调查问卷
|
||||
var diaShade=document.getElementById('diaShade');
|
||||
var dialogWrap=document.getElementById('dialogWrap');
|
||||
function popup(para) {
|
||||
diaShade.style.display=para;
|
||||
dialogWrap.style.display=para;
|
||||
}
|
||||
<#if !welcomeAd>
|
||||
document.getElementById('above').scrollTop = document.getElementById('above').scrollHeight ; //滚动到 对话内容的 底部
|
||||
</#if>
|
||||
// 参数连接
|
||||
var hostname = location.hostname ;
|
||||
var protocol = window.location.protocol.replace(/:/g,'');
|
||||
var socket = io.connect(protocol + '://'+hostname+':${port}/im/chatbot?userid=${userid!''}&orgi=${orgi!''}&session=${sessionid!''}&appid=${appid!''}&osname=${(osname!'')?url}&browser=${(browser!'')?url}<#if skill??>&skill=${skill}</#if><#if username??>&nickname=${username}</#if><#if agent??>&agent=${agent}</#if><#if title??>&title=${title?url}</#if><#if traceid??>&url=${url?url}</#if><#if traceid??>&traceid=${traceid}</#if>');
|
||||
socket.on('connect',function(){
|
||||
<#if contacts?? && contacts.name??>
|
||||
socket.emit('new', {
|
||||
name : "${contacts.name!''}",
|
||||
phone:"${contacts.phone!''}",
|
||||
email:"${contacts.email}",
|
||||
memo:"${contacts.memo!''}",
|
||||
orgi:"${inviteData.orgi!''}",
|
||||
appid : "${appid!''}"
|
||||
});
|
||||
</#if>
|
||||
})
|
||||
socket.on("agentstatus",function(data){
|
||||
document.getElementById('connect-message').innerHTML=data.message;
|
||||
})
|
||||
socket.on("status",function(data){
|
||||
<#if welcomeAd>
|
||||
output('<span id="connect-message">'+data.message+'</span>' , 'message connect-message' , false);
|
||||
<#else>
|
||||
output('<span id="connect-message">'+data.message+'</span>' , 'message connect-message' , true);
|
||||
</#if>
|
||||
if(data.messageType == "end"){
|
||||
service_end = true ;
|
||||
editor.readonly();
|
||||
<#if sessionConfig?? && sessionConfig.satisfaction?? && sessionConfig.satisfaction>
|
||||
|
||||
document.getElementById("diaShade").style.display = "block" ;
|
||||
document.getElementById("dialogWrap").style.display = "block" ;
|
||||
</#if>
|
||||
}
|
||||
if(document.getElementById("agentserviceid")){
|
||||
document.getElementById("agentserviceid").value = data.agentserviceid ;
|
||||
}
|
||||
})
|
||||
socket.on('message', function(data) {
|
||||
var chat=document.getElementsByClassName('chatting-left').innerText;
|
||||
chat = data.message;
|
||||
if(data.messageType == "image"){
|
||||
chat = "<a href='"+data.message+"_original' target='_blank'><img src='"+data.message+"' class='ukefu-media-image'/></a>" ;
|
||||
}else if(data.messageType == "file"){
|
||||
chat = "<div class='ukefu-message-file'><div class='ukefu-file-icon'><img src='/im/img/file.png'></div><div class='ukefu-file-desc'><a href='"+data.message+"' target='_blank'><div>"+data.filename+"</div><div>"+(data.filesize/1024).toFixed(3)+"Kb</div></a></div></div>" ;
|
||||
}
|
||||
if(data.calltype == "呼入"){
|
||||
output('<div class="chat-right"> <img class="user-img" src="/im/img/user.png" alt=""><div class="chat-message"><label class="time">'+data.createtime+'</label><label class="user">'+data.nickName+'</label> </div><div class="chatting-right"><i class="arrow arrow${inviteData.consult_dialog_color!''}"></i><div class="chat-content theme${inviteData.consult_dialog_color!''}">'+chat+'</div></div>' , "chat-block");
|
||||
}else if(data.calltype == "呼出"){
|
||||
output('<div class="chat-left"> <img class="user-img" src="<#if inviteData?? && inviteData.consult_dialog_headimg??>/res/image.html?id=${inviteData.consult_dialog_headimg?url}<#else>/images/agent.png</#if>" alt=""><div class="chat-message"><label class="user">'+data.nickName+'</label><label class="time">'+data.createtime+'</label> </div><div class="chatting-left"><i class="arrow"></i><div class="chat-content">'+chat+'</div></div>' , "chat-block");
|
||||
R3Ajax.audioplayer('audioplane', newmessage, false); // 播放
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('disconnect',function() {
|
||||
output('<span id="connect-message">连接坐席失败,在线咨询服务不可用</span>' , 'message connect-message');
|
||||
});
|
||||
function sendDisconnect(){
|
||||
socket.disconnect();
|
||||
}
|
||||
function sendMessage() {
|
||||
editor.sync();
|
||||
var count = editor.count("text");
|
||||
if(count>0 && service_end == false){
|
||||
var message = document.getElementById('message').value;
|
||||
if(message!= ""){
|
||||
socket.emit('message', {
|
||||
appid : "${appid!''}",
|
||||
userid:"${userid!''}",
|
||||
type:"message" ,
|
||||
session:"${sessionid!''}",
|
||||
orgi:"${orgi!''}",
|
||||
message : message
|
||||
});
|
||||
}
|
||||
}else if(service_end == true){
|
||||
alert("坐席已断开和您的对话");
|
||||
}
|
||||
editor.html('');
|
||||
}
|
||||
function output(message , clazz , scroll) {
|
||||
if(clazz == "message connect-message"){
|
||||
var messages = document.getElementsByClassName("connect-message") ;
|
||||
for(inx =0 ; inx < messages.length ; ){
|
||||
document.getElementById('above').removeChild(messages[inx]) ;
|
||||
inx++ ;
|
||||
}
|
||||
}
|
||||
var element = ("<div class='clearfix "+clazz+"'>" +" " + message + "</div>");
|
||||
document.getElementById('above').innerHTML= (document.getElementById('above').innerHTML + element);
|
||||
if(scroll == null || scroll == true){
|
||||
document.getElementById('above').scrollTop = document.getElementById('above').scrollHeight ;
|
||||
}
|
||||
}
|
||||
function update(id , message) {
|
||||
document.getElementById(id).innerHTML= message;
|
||||
}
|
||||
|
||||
var message={
|
||||
// text:data.message,
|
||||
// picture:function(){
|
||||
|
||||
// }
|
||||
// file:function(){
|
||||
|
||||
// }
|
||||
// lang:function(){
|
||||
|
||||
// }
|
||||
// goods:function(){
|
||||
|
||||
// }
|
||||
// POI:function(){
|
||||
|
||||
// }
|
||||
|
||||
}
|
||||
// 回车事件
|
||||
document.onkeyup=function(e){
|
||||
if(!e) e=window.event;
|
||||
if((e.keyCode||e.which)==13){
|
||||
document.getElementById('sent').click();
|
||||
}
|
||||
}
|
||||
window.onresize = function(){
|
||||
R3Helper.resize();
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,431 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="Cache-Control" content="no-siteapp" />
|
||||
<meta name="viewport"
|
||||
content="width=device-width, maximum-scale=1.0, initial-scale=1.0,initial-scale=1.0,user-scalable=no" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<title>智能客服</title>
|
||||
|
||||
<link rel="shortcut icon" type="image/x-icon" href="/images/favicon.ico?t=1489039620156"/>
|
||||
<link rel="stylesheet" type="text/css" href="/im/css/ukefu.css">
|
||||
<link rel="stylesheet" id="skin" type="text/css" href="/im/css/default/ukefu.css">
|
||||
|
||||
<!-- kindeditor -->
|
||||
<link rel="stylesheet" type="text/css" href="/im/js/kindeditor/themes/default/default.css">
|
||||
|
||||
<script type="text/javascript" src="/js/jquery-1.10.2.min.js"></script>
|
||||
<script src="/js/jquery.form.js"></script>
|
||||
|
||||
<script type="text/javascript" src="/im/js/kindeditor/kindeditor.js"></script>
|
||||
<script type="text/javascript" src="/im/js/kindeditor/lang/zh-CN.js"></script>
|
||||
<style>
|
||||
* {
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
outline: none;
|
||||
}
|
||||
*:not(input,textarea) {
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script src="/im/js/socket.io.js"></script>
|
||||
<script type="text/javascript">
|
||||
var editor , words , textheight , wordinx = 0 ;
|
||||
|
||||
/**
|
||||
* 文本框根据输入内容自适应高度
|
||||
* @param {HTMLElement} 输入框元素
|
||||
* @param {Number} 设置光标与输入框保持的距离(默认0)
|
||||
* @param {Number} 设置最大高度(可选)
|
||||
*/
|
||||
var autoTextarea = function (elem, extra, maxHeight) {
|
||||
extra = extra || 0;
|
||||
var isFirefox = !!document.getBoxObjectFor || 'mozInnerScreenX' in window,
|
||||
isOpera = !!window.opera && !!window.opera.toString().indexOf('Opera'),
|
||||
addEvent = function (type, callback) {
|
||||
elem.addEventListener ?
|
||||
elem.addEventListener(type, callback, false) :
|
||||
elem.attachEvent('on' + type, callback);
|
||||
},
|
||||
getStyle = elem.currentStyle ? function (name) {
|
||||
var val = elem.currentStyle[name];
|
||||
|
||||
if (name === 'height' && val.search(/px/i) !== 1) {
|
||||
var rect = elem.getBoundingClientRect();
|
||||
return rect.bottom - rect.top -
|
||||
parseFloat(getStyle('paddingTop')) -
|
||||
parseFloat(getStyle('paddingBottom')) + 'px';
|
||||
};
|
||||
|
||||
return val;
|
||||
} : function (name) {
|
||||
return getComputedStyle(elem, null)[name];
|
||||
},
|
||||
minHeight = parseFloat(getStyle('height'));
|
||||
|
||||
|
||||
elem.style.resize = 'none';
|
||||
|
||||
var textchange = function(){
|
||||
changeTextArea(38);
|
||||
}
|
||||
|
||||
var change = function () {
|
||||
changeTextArea(0);
|
||||
wordinx = getPositionForTextArea(document.getElementById('message'));
|
||||
};
|
||||
|
||||
addEvent('propertychange', textchange);
|
||||
addEvent('input', textchange);
|
||||
addEvent('focus', changeTextArea);
|
||||
change();
|
||||
};
|
||||
//多行文本框
|
||||
function getPositionForTextArea(ctrl) {
|
||||
var CaretPos = 0;
|
||||
if (document.selection) { // IE Support
|
||||
ctrl.focus();
|
||||
var Sel = document.selection.createRange();
|
||||
var Sel2 = Sel.duplicate();
|
||||
Sel2.moveToElementText(ctrl);
|
||||
var CaretPos = -1;
|
||||
while (Sel2.inRange(Sel)) {
|
||||
Sel2.moveStart('character');
|
||||
CaretPos++;
|
||||
}
|
||||
} else if (ctrl.selectionStart || ctrl.selectionStart == '0') { // Firefox support
|
||||
CaretPos = ctrl.selectionStart;
|
||||
}
|
||||
return (CaretPos);
|
||||
}
|
||||
|
||||
function openFaceDialog(){
|
||||
if(document.getElementById("faceindex").style.display == "none"){
|
||||
document.getElementById("faceindex").style.display= "block";
|
||||
document.getElementById("bottom").style.height = $('#message').height()+ 20 + document.getElementById("faceindex").offsetHeight + "px" ;
|
||||
document.getElementById("above").style.height = "calc(100% - "+($('#bottom').height())+"px)"
|
||||
}else{
|
||||
closeFaceDialog(0);
|
||||
}
|
||||
return false ;
|
||||
}
|
||||
|
||||
function closeFaceDialog(height){
|
||||
document.getElementById("faceindex").style.display= "none";
|
||||
document.getElementById("bottom").style.height = $('#message').height() + height + "px" ;
|
||||
document.getElementById("above").style.height = "calc(100% - "+($('#message').height())+"px)" ;
|
||||
}
|
||||
|
||||
function changeTextArea(height){
|
||||
if(document.getElementById("faceindex").style.display == "none"){
|
||||
$('#bottom').height($('#message').height() + height);
|
||||
document.getElementById("above").style.height = "calc(100% - "+($('#message').height())+"px)"
|
||||
}else{
|
||||
$('#bottom').height($('#message').height() + $('#faceindex').height());
|
||||
document.getElementById("above").style.height = "calc(100% - "+($('#message').height())+"px)"
|
||||
}
|
||||
}
|
||||
function insertImg(obj){
|
||||
if(wordinx >= 0){
|
||||
var text = $('#message').val();
|
||||
var value = text.substring(0 , wordinx) + "["+obj+"]" + text.substring(wordinx , text.length);
|
||||
$('#message').val(value);
|
||||
wordinx = wordinx + 2 + obj.length ;
|
||||
}
|
||||
}
|
||||
$(document).ready(function(){
|
||||
window.addEventListener('resize', function () {
|
||||
if (document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA') {
|
||||
window.setTimeout(function () {
|
||||
document.activeElement.scrollIntoViewIfNeeded()
|
||||
}, 0)
|
||||
}
|
||||
});
|
||||
// 在输入框获取焦点, 键盘弹起后, 真的是一行代码
|
||||
|
||||
$(document).on('submit.form.data-api','[data-toggle="ajax-form"]', function ( e ) {
|
||||
var formValue = $(e.target) ;
|
||||
$(this).ajaxSubmit({
|
||||
url:formValue.attr("action"),
|
||||
success: function(data){
|
||||
|
||||
},
|
||||
error:function(xhr, type, s){
|
||||
//notification("",false); //结束
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<!-- kindeditor -->
|
||||
</head>
|
||||
<body class="ukefu-im-theme" style="overflow:hidden;height:calc(100%);overflow-y: atuo;/* 或者scroll */-webkit-overflow-scrolling: touch;/* 解决ios滑动不流畅问题 */" class="ukefu-point-text">
|
||||
<div id="header" class="theme${inviteData.consult_dialog_color!''}">
|
||||
<img
|
||||
src="<#if inviteData?? && inviteData.consult_dialog_logo??>/res/image.html?id=${inviteData.consult_dialog_logo?url}<#else>/images/logo.png</#if>"
|
||||
style="height:50px;padding:10px;">
|
||||
|
||||
<div class="ukefu-func-tab">
|
||||
<ul>
|
||||
<#if models?? && models["chatbot"]?? && models["chatbot"] == true && inviteData.ai?? && inviteData.ai == true && aiid??>
|
||||
<li><a href="/im/index.html?appid=${appid!''}&orgi=${orgi!''}<#if aiid??>&aiid=${aiid}</#if>&ai=true<#if client??>&client=${client!''}</#if><#if type??>&type=text</#if><#if skill??>&skill=${skill!''}</#if><#if agent??>&agent=${agent!''}</#if>&userid=${userid!''}&sessionid=${sessionid!''}&t=${.now?long}">智能客服</a></li>
|
||||
<li class="cur"><a href="javascript:void(0)">人工坐席</a></li>
|
||||
<#else>
|
||||
<li class="cur"><a href="javascript:void(0)">人工坐席</a></li>
|
||||
</#if>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div id="cooperation" class="ukefu-cooperation" style="display:none;z-index: 100;background-color: #ffffff;position: fixed;left: 0px;width: 100%;top: 0px;top:0px;height: 100%;">
|
||||
<div class='ukefu-image-canvas' id='ukefu-image-content' style="margin-top:2px;">
|
||||
<img id="ukefu_img_ctx" style="max-width: 100%;max-height: 100%;">
|
||||
</div>
|
||||
<div class='drawBoard' style="position: absolute;left: 0;top: 0;margin-top:2px;z-index: 998;">
|
||||
<canvas id="canvas-borad" class="brushBorad">你的浏览器不支持 canvas 绘图</canvas>
|
||||
</div>
|
||||
<div style="position: absolute;bottom: 10px;width: 100%;text-align: center;z-index:10000">
|
||||
<button id="offcoop-btn" style="border-color:#009688 !important;color:#FFFFFF;display: inline-block;height: 38px;line-height: 38px;padding: 0 18px;background-color: #009688;color: #fff;white-space: nowrap;text-align: center;font-size: 14px;margin-right:10px;border: none;border-radius: 2px;cursor: pointer;opacity: .9;filter: alpha(opacity=90);background-color: #377FED !important;border:1px solid #FFFFFF;" onclick="offCoop();">退出协作</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chat-above" id="above" style="height:calc(100% - 60px);padding-bottom:100px;">
|
||||
<div class="clearfix message welcome">
|
||||
<span id="welcome-message">${(inviteData.dialog_message!'欢迎您来咨询!欢迎使用春松客服!如需帮助请联系 info@chatopera.com')?no_esc}</span>
|
||||
</div>
|
||||
<#if chatMessageList?? && chatMessageList.content??>
|
||||
<#list chatMessageList.content?reverse as chatMessage>
|
||||
<#if chatMessage.userid?? && userid?? && chatMessage.calltype?? && chatMessage.calltype = "呼入">
|
||||
<div class="clearfix chat-block">
|
||||
<div class="chat-right">
|
||||
<img class="user-img" src="/im/img/user.png" alt="">
|
||||
<div class="chat-message">
|
||||
<label class="time">${chatMessage.createtime!''}</label> <label
|
||||
class="user">${chatMessage.username!''}</label>
|
||||
</div>
|
||||
<div class="chatting-right">
|
||||
<i class="arrow arrow${inviteData.consult_dialog_color!''}"></i>
|
||||
<div
|
||||
class="chat-content theme${inviteData.consult_dialog_color!''}"><#include
|
||||
"/apps/im/media/message.html"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<#else>
|
||||
<div class="clearfix chat-block">
|
||||
<div class="chat-left">
|
||||
<img class="user-img"
|
||||
src="<#if inviteData?? && inviteData.consult_dialog_headimg??>/res/image.html?id=${inviteData.consult_dialog_headimg?url}<#else>/images/agent.png</#if>"
|
||||
alt="">
|
||||
<div class="chat-message">
|
||||
<label class="user">${chatMessage.username!''}</label> <label
|
||||
class="time">${chatMessage.createtime!''}</label>
|
||||
</div>
|
||||
<div class="chatting-left">
|
||||
<i class="arrow"></i>
|
||||
<div class="chat-content"><#include "/apps/im/media/message.html"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</#if> </#list> </#if>
|
||||
</div>
|
||||
|
||||
<div class="mobile-chat-bottom" id="bottom">
|
||||
<form id="imgForm" action="/im/image/upload.html?userid=${userid!''}&appid=${appid!''}&username=${username!''}&orgi=${orgi!''}" data-toggle="ajax-form"" enctype="multipart/form-data">
|
||||
<a href="javascript:;" class="imgFile" onclick="closeFaceDialog(0)">
|
||||
<img src="/im/img/img.png" class="chat-type" style="width:32px;height:32px;">
|
||||
<input type="file" name="imgFile" id="imgFile" accept="image/*" onChange="$('#imgForm').submit();$(this).val('');">
|
||||
</a>
|
||||
</form>
|
||||
<textarea id="message" name="content" maxlength="<#if inviteData.maxwordsnum gt 0>${inviteData.maxwordsnum}<#else>300</#if>"></textarea>
|
||||
<div class="btn-push clearfix" class="tools">
|
||||
<img id="facedialog" onclick="return openFaceDialog()" src="/im/img/face.png" style="width:32px;height:32px;"></a>
|
||||
<a href="javascript:void(0)" onClick="sendMessage();return false;"><img src="/im/img/send.png" style="width:32px;height:32px;"></a>
|
||||
</div>
|
||||
|
||||
<div id="faceindex" style="display:none;height:200px;position: absolute;bottom: 0px;width:100%;overflow-x:auto;">
|
||||
<table class="ke-table" cellpadding="0" cellspacing="0" border="0" style="min-width:100%;">
|
||||
<tbody>
|
||||
<#list 0..4 as row>
|
||||
<tr>
|
||||
<#list 0..20 as col>
|
||||
<td class="ke-cell"><span class="ke-img"><img
|
||||
src="/im/js/kindeditor/plugins/emoticons/images/${row*20 + col}.png"
|
||||
border="0" alt="" onClick="insertImg('${row*20 + col}')"></span></td>
|
||||
</#list>
|
||||
</tr>
|
||||
</#list>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var service_end = false;
|
||||
// 调查问卷
|
||||
var diaShade = document.getElementById('diaShade');
|
||||
var dialogWrap = document.getElementById('dialogWrap');
|
||||
function popup(para) {
|
||||
diaShade.style.display = para;
|
||||
dialogWrap.style.display = para;
|
||||
}
|
||||
document.getElementById('above').scrollTop = document
|
||||
.getElementById('above').scrollHeight; //滚动到 对话内容的 底部
|
||||
// 参数连接
|
||||
// 参数连接
|
||||
var hostname = location.hostname ;
|
||||
var protocol = window.location.protocol.replace(/:/g,'');
|
||||
var socket = io.connect(protocol + '://'+hostname+':${port}/im/chatbot?userid=${userid!''}<#if aiid??>&aiid=${aiid}</#if>&orgi=${orgi!''}&session=${sessionid!''}&appid=${appid!''}&osname=${(osname!'')?url}&browser=${(browser!'')?url}<#if skill??>&skill=${skill}</#if><#if agent??>&agent=${agent}</#if>');
|
||||
socket.on('connect',function(){
|
||||
//service.sendRequestMessage();
|
||||
//output('<span id="callOutConnect-message">'+ new Date().format("yyyy-MM-dd hh:mm:ss") + ' 开始沟通' +'</span>' , 'message callOutConnect-message');
|
||||
})
|
||||
socket.on("agentstatus",function(data){
|
||||
document.getElementById('connect-message').innerHTML=data.message;
|
||||
})
|
||||
socket.on("status",function(data){
|
||||
output('<span id="connect-message">'+data.message+'</span>' , 'message connect-message');
|
||||
if(data.messageType == "end"){
|
||||
service_end = true ;
|
||||
//editor.readonly();
|
||||
}
|
||||
})
|
||||
socket.on('message', function(data) {
|
||||
var chat=document.getElementsByClassName('chatting-left').innerText;
|
||||
chat = data.message;
|
||||
if(data.messageType == "image"){
|
||||
chat = "<a href='"+data.message+"_original' target='_blank'><img src='"+data.message+"' class='ukefu-media-image'/></a>" ;
|
||||
}else if(data.messageType == "cooperation"){
|
||||
chat = "<a href='javascript:void(0)' onclick='acceptInvite(\""+data.message+"\", \""+data.attachmentid+"\")'>您收到一个协作邀请,点击进入协作</a>" ;
|
||||
}else if(data.messageType == "action"){
|
||||
//检查访客是否在协作页面上,如果在协作页面上,就开始执行重绘,否则不做处理
|
||||
drawCanvasImage(data.attachmentid) ;
|
||||
}
|
||||
if(data.calltype == "呼入"){
|
||||
output('<div class="chat-right"> <img class="user-img" src="/im/img/user.png" alt=""><div class="chat-message"><label class="time">'+data.createtime+'</label><label class="user">'+data.nickName+'</label> </div><div class="chatting-right"><i class="arrow arrow${inviteData.consult_dialog_color!''}"></i><div class="chat-content theme${inviteData.consult_dialog_color!''}">'+chat+'</div></div>' , "chat-block");
|
||||
}else if(data.calltype == "呼出"){
|
||||
output('<div class="chat-left"> <img class="user-img" src="<#if inviteData?? && inviteData.consult_dialog_headimg??>/res/image.html?id=${inviteData.consult_dialog_headimg?url}<#else>/images/agent.png</#if>" alt=""><div class="chat-message"><label class="user">'+data.nickName+'</label><label class="time">'+data.createtime+'</label> </div><div class="chatting-left"><i class="arrow"></i><div class="chat-content">'+chat+'</div></div>' , "chat-block");
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('disconnect',function() {
|
||||
output('<span id="connect-message">连接坐席失败,在线咨询服务不可用</span>' , 'message connect-message');
|
||||
});
|
||||
function sendDisconnect(){
|
||||
socket.disconnect();
|
||||
}
|
||||
function acceptInvite(msgid,fileid){
|
||||
document.getElementById("cooperation").style.display = "block" ;
|
||||
document.getElementById("ukefu_img_ctx").src = "/res/image.html?id=upload/"+fileid ;
|
||||
|
||||
$("#ukefu_img_ctx").load(function() {
|
||||
var height = document.getElementById("ukefu-image-content").offsetHeight;
|
||||
var width = document.getElementById("ukefu-image-content").offsetWidth;
|
||||
var canvas = document.getElementById("canvas-borad") ;
|
||||
if(canvas.getContext){
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
|
||||
drawCanvasImage(fileid) ;
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
function drawCanvasImage(fileid){
|
||||
var canvas = document.getElementById("canvas-borad") ;
|
||||
|
||||
if (canvas.getContext && document.getElementById("cooperation").style.display == "block") {
|
||||
var ctx = canvas.getContext("2d");
|
||||
|
||||
//创建新的图片对象
|
||||
var img = new Image();
|
||||
//指定图片的URL
|
||||
img.src = "/res/image.html?id=upload/" + fileid + "_cooperation";
|
||||
//浏览器加载图片完毕后再绘制图片
|
||||
img.onload = function() {
|
||||
ctx.clearRect(0,0,canvas.width,canvas.height);
|
||||
//以Canvas画布上的坐标(10,10)为起始点,绘制图像
|
||||
ctx.drawImage(img, 0, 0 , canvas.width,canvas.height);
|
||||
};
|
||||
}
|
||||
}
|
||||
function offCoop(){
|
||||
document.getElementById("cooperation").style.display = "none" ;
|
||||
}
|
||||
function sendMessage() {
|
||||
var count = document.getElementById('message').value.length;
|
||||
if (count > 0 && service_end == false) {
|
||||
var message = $("#message").val();
|
||||
sendMessageText(message);
|
||||
$("#message").val("");
|
||||
wordinx = 0;
|
||||
} else if (service_end == true) {
|
||||
alert("服务器已断开和您的对话");
|
||||
}
|
||||
closeFaceDialog(0);
|
||||
}
|
||||
function sendMessageText(message) {
|
||||
if (message != "") {
|
||||
socket.emit('message', {
|
||||
appid : "${appid!''}",
|
||||
userid : "${userid!''}",
|
||||
username : "${username!''}",
|
||||
channel : "webim",
|
||||
type : "message",
|
||||
contextid : "${sessionid!''}",
|
||||
orgi : "${orgi!''}",
|
||||
message : message
|
||||
});
|
||||
}
|
||||
}
|
||||
function output(message, clazz) {
|
||||
if (clazz == "message connect-message") {
|
||||
var messages = document.getElementsByClassName("connect-message");
|
||||
for (inx = 0; inx < messages.length;) {
|
||||
document.getElementById('above').removeChild(messages[inx]);
|
||||
inx++;
|
||||
}
|
||||
}
|
||||
var element = ("<div class='clearfix "+clazz+"'>" + " " + message + "</div>");
|
||||
document.getElementById('above').innerHTML = (document
|
||||
.getElementById('above').innerHTML + element);
|
||||
document.getElementById('above').scrollTop = document.getElementById('above').scrollHeight ;
|
||||
//$("#welcome-message").html(document.getElementById('above').scrollHeight);
|
||||
}
|
||||
function update(id, message) {
|
||||
document.getElementById(id).innerHTML = message;
|
||||
}
|
||||
autoTextarea(document.getElementById("message"));// 调用
|
||||
$('#message').click(function() {
|
||||
wordinx = getPositionForTextArea(document.getElementById('message'));
|
||||
closeFaceDialog(44);
|
||||
});
|
||||
|
||||
var message = {
|
||||
// text:data.message,
|
||||
// picture:function(){
|
||||
|
||||
// }
|
||||
// file:function(){
|
||||
|
||||
// }
|
||||
// lang:function(){
|
||||
|
||||
// }
|
||||
// goods:function(){
|
||||
|
||||
// }
|
||||
// POI:function(){
|
||||
|
||||
// }
|
||||
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Loading…
x
Reference in New Issue
Block a user