1.聊天界面右上角消息提示
这里使用了vuex实现了跨组件的通信。我做出来的效果仅仅只能是展示使用,和具体的能用的效果还差很远,比如刷新页面后上次回复的消息数量就会被清空。这里就是一个可以优化的点,当时本想着做的更完善一点,但是时间有限,就用了一个偷懒的方式把最后相同的效果做了出来,各位同志们可以加加油实现一下呀。
首先,在store/index中定义一个news数组变量。
import Vue from "vue"
import Vuex from "vuex"
Vue.use(Vuex)
export default new Vuex.Store({
state:{
news:[],//用于管理员向用户回复修改后的消息
}
})
接着,在BackPlat界面中写好html和js方法。在该界面中提交对用户反馈信息的修改,并将消息存入vuex中的news变量
<el-dialog :visible.sync="dialogVisible1">
<el-form :model="form1" class="login">
<p class="title">消息回复</p>
<el-form-item label="用户提问" label-width="100px">
<el-input v-model="form1.request" style="width:530px"></el-input>
</el-form-item>
<el-form-item label="修改回答" label-width="100px">
<textarea v-model="form1.answer" cols="70" rows="10" style="resize:none;"></textarea>
</el-form-item>
<el-button type="warning" @click="submitvuex">提交</el-button>
</el-form>
</el-dialog>
submitvuex(){
this.$message.success('提交成功!')
this.dialogVisible1 = !this.dialogVisible1
this.$store.state.news.push({request:this.form1.request,answer:this.form1.answer})
},
然后返回ChatBot界面,定义一个标签i,并在其中嵌套一个div,用于展示回复消息中数组的个数。当newsnum被赋值后,通过v-show的控制,其css效果才会被展示出来。
<i class="el-icon-message" @click="shownews" title="消息">
<div class="newscircle" v-show="newsnum">
<span>{{newsnum}}</span>
</div>
</i>
该如何实现当界面返回聊天问答界面时,就会将消息的数量显示出来呢?就需要在watch里面对路由的切换进行监听,在内部通过store获取news数组,再对其长度进行统计。
watch:{
// 对路由的切换进行监听
'$route'(){
this.news = this.$store.state.news
this.newsnum = this.$store.state.news.length
}
},
具体的css样式如下
.el-icon-message{
line-height: 54px;
margin-right: 20px;
font-size:25px;
position: relative;
cursor:pointer;
.newscircle{
width: 15px;
height: 15px;
border-radius: 50%;
background-color: red;
position: absolute;
right:-3px;
top:12px;
display: flex; /* 将 .newscircle 设置为 Flexbox 容器 */
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
span{
font-size:15px;
color:white;
}
}
}
2.右上角实体识别动画
该功能的实现需要借助于实体识别的结果,而实体的识别在向后端发送问句时可以同步的返回识别出来的结果,从而获取该结果用以展示。
<div class="show1" v-show="!isshow">
<div class="main" ref="dialogueContainer">
<div class="screen-inner">
<!--v-for每次都会渲染一次dom中的内容-->
<div v-for="(message,index) in messages" :key="index" :class="message.sender==='robot'?'robot-dialogue':'man-dialogue'">
<!-- 若动态进行绑定src,则需要将引入的图片放到data中,然后再引用变量 -->
<img :src="message.sender==='robot' ? robotImg : manImg"
:class="message.sender==='robot'?'robotinfo':'userinfo'">
<div :class="message.sender==='robot'?'dialogue-text':'dialogue-input'">{{message.text}}</div>
<i class="el-icon-chat-dot-square" title="评价" v-if="message.sender==='robot'" @click="comment(message)"></i>
</div>
</div>
</div>
</div>
<div class="submit">
<textarea
id="dialogue-input"
@keydown.enter="submit"
@keydown.enter.prevent
v-model="notedata"
placeholder="请输入您的问题,按Enter键提交">
</textarea>
</div>
按下enter键后会触发submit方法
submit(){
const text = this.notedata.replace(/\n/g, "")//将最后的回车空格去掉
const requestData = {sent:text}
this.$axios.get('http://127.0.0.1:5000/index',{
params:requestData
})
.then((res)=>{
console.log(res);
this.messages.push({text:this.notedata,sender:'man'})
if(!(res.data.diseasename instanceof Array)){
this.diseasename = res.data.diseasename
// 调用疾病统计方法,在下一个函数中封装
this.updatenum(this.diseasename)
// diseasename用来控制实体识别结果动画的消失,将该效果持续三秒
setTimeout(()=>{
this.diseasename = ''
},3000)
}
//间隔1s后再将后端返回的答案加入到messages中
setTimeout(()=>{
this.messages.push({text:res.data.reply,sender:'robot'})
},1000)
this.notedata=''
})
},
updatenum(name){
// 注意,如果使用post进行数据的传递,一定要使用json格式将其进行传递,前面是后端requst调用的名称,后面写的是传入的值
this.$axios.post('http://127.0.0.1:5002/savenum',{diseasename:name})
.then((res)=>{
console.log(res)
})
.catch((err)=>{
console.log(err);
})
},
#chat.py
@app.route('/index',methods=['GET'])
def index():
diseasename = []
msg = request.args.get('sent')
user_intent = classifier(msg)
if user_intent in ["greet","goodbye","deny","isbot"]:
reply = gossip_robot(user_intent)
elif user_intent == "accept":
reply = load_user_dialogue_context('wjh')
reply = reply.get("choice_answer")
else:
reply = medical_robot(msg , 'wjh')#reply就是槽位模板,里面有已经填充好的模板信息
# 用作数据库统计疾病提问的数量
diseasename = get_disease_name(msg,'wjh')
if reply["slot_values"]:#如果存在新的疾病实体,那么就需要重新写入日志,用于下一轮对话
dump_user_dialogue_context('wjh',reply)
reply = reply.get("replay_answer")
#返回了diseasename,该变量即可向后端提交对疾病提问次数的统计,同时也获取了用于动画展示的实体
return jsonify({'reply': reply , 'diseasename':diseasename}),200
#modules.py
def get_disease_name(text,user):
semantic_slot = semantic_parser(text,user)
answer = get_answer(semantic_slot)
print('answer',answer['slot_values'])
return answer['slot_values']['Disease']
#countnum.py
@app.route('/savenum',methods=['POST'])
def savenum():
with app.app_context():
data = request.json
frontname = data.get('diseasename')
# 左边为数据库表项,右边为当前变量
# 通过查询,这是一个query对象,而不是单一的数据库表对象,因此需要调用first获得一个具体的对象
existing_disease = diseasenum.query.filter_by(diseasename=frontname).first()
if existing_disease:
existing_disease.num += 1
else:
new_disease = diseasenum(diseasename=frontname,num=1)
db.session.add(new_disease)
db.session.commit()
return jsonify({'reply':'保存成功'}),200
3.左侧实体识别及意图识别功能
通过getword方法获得多个实体识别结果以及意图识别结果,传递0和1参数是为了获取不同的返回结果。
<transition name="rec-in-left">
<div class="recognize" v-show="multishow">
<div class="title">识别检测功能</div>
<div class="searchbox">
<div style="margin-top:10px;font-weight:bold">请在下面的文本框输入问句</div>
<textarea cols="40" rows="10" style="resize:none;" v-model="multiword"></textarea>
<el-button type="primary" @click="getword(0)">实体识别</el-button>
<el-button type="primary" @click="getword(1)">意图识别</el-button>
</div>
<div class="answer">
<div style="font-weight:bold">识别结果</div>
<div class="realword" ref="showanswer" style="width:300px;height:150px;border:2px solid black;margin-left:15px;"></div>
<el-button type="danger" style="margin-top:5px;" @click="multishow=!multishow">退出</el-button>
</div>
</div>
</transition>
mutianswer[i]中的0和1如何确定?通过查看后端调用相应方法返回的结果,查看其层级结构而来。各位可以通过在控制台打印以下res,看看返回的结果中的具体数组结构。以此来获取相应的数据进行展示。
我是如何想到拓展这个功能的?一开始原up在视频演示时,在训练好两个模型下分别别写了app.py来对两个模型进行测试运用。在输入了测试的句子后,我看到模型返回的结果会有多个识别出来的实体,然后就想着可以利用这些结果再多做出一个展示功能。
getword(type){
const requestData = {sent:this.multiword}
this.$axios.get('http://127.0.0.1:5000/getmutiword',{
params:requestData
})
.then((res)=>{
console.log(res);
if(type===0)this.showmutiword = res.data.mutianswer[0]
else this.showmutiword = res.data.mutianswer[1]
this.$refs.showanswer.innerHTML = this.showmutiword
})
.catch(()=>{
this.$refs.showanswer.innerHTML = "未检测到相关结果!"
})
},
#python.py
@app.route('/getmutiword',methods=['GET'])
def getmutiword():
msg = request.args.get('sent')
answer = get_mult_disease(msg)
return jsonify({'mutianswer':answer}),200
#modules.py
def mult_intent_classifier(text):
result = bert_intent_recognize(text)
if result != -1:
print('intent:',result['data']['name'])
return result['data']['name']
else:
return -1
def mult_slot_recognizer(text):
result = medical_ner(text)
if result != -1:
print('slot:',result['data'][0]['entities'])
return result['data'][0]['entities']
else:
return -1
def get_mult_disease(text):
word_list = []
intent = ''
answer = mult_slot_recognizer(text)
intent = mult_intent_classifier(text)
for entity in answer:
word_list.append(entity['word'])
# 一个数组和一个字符串,应当使用括号返回,相当于向前端传递了一个数组回去
return (word_list,intent)
以下为调用mult_slot_recognizer所获得的返回结果,根据此层级结构返回result['data'][0]['entities']
slot:{
'success': 1,
'data':
[
{
'string': '我今天有点咳嗽和胸闷,会得什么病?',
'entities':
[
{'word': '咳嗽', 'type': 'symptom'},
{'word': '胸闷', 'type': 'symptom'}
],
'recog_label': 'model'
},
{
'string': '我今天有点咳嗽和胸闷,会得什么病?',
'entities':
[
{'word': '咳嗽', 'type': 'disease', 'recog_label': 'dict'}
]
}
]
}
4.问句评价信息的跨组件展示
以下为评价表单在ChatBot界面的html样式
<!-- 点击每条消息后面的消息提示标识所弹出的评价对话框 -->
<el-dialog :visible.sync="dialogVisible" :close-on-click-modal="false">
<el-form :model="form" :rules="rules" ref="ruleForm" class="comment">
<p style="font-weight:bold;font-size:20px;margin-top:-20px;text-align:center;">评价</p>
<el-form-item label="问题" label-width="50px">
<el-input v-model="form.request"></el-input>
</el-form-item>
<el-form-item label="回答" label-width="50px">
<textarea v-model="form.answer" cols="85" rows="10" style="resize:none;"></textarea>
</el-form-item>
<el-form-item label="时间" label-width="50px">
<el-input v-model="form.time"></el-input>
</el-form-item>
<el-form-item label="回答满意度" label-width="95px" prop="satisfication">
<el-input v-model="form.satisfication" placeholder="评分标准为0-5"></el-input>
</el-form-item>
<el-form-item label="建议" label-width="55px" prop="comment">
<el-input v-model="form.comment"></el-input>
</el-form-item>
<el-button type="primary" style="display:block;margin:0 auto" @click="submitcomment('ruleForm')">提交</el-button>
</el-form>
</el-dialog>
在调用submitcomment时,传递了一个ruleForm,这是表单的验证规则,当验证成功后才能继续执行后续的逻辑功能。
submitcomment(ruleForm){
console.log(this.form)
this.$refs[ruleForm].validate((valid)=>{
if(valid){
this.$axios.post('http://127.0.0.1:5001/saverecord',this.form)
.then(res=>{
if(res.status===200){
this.$message.success("提交成功!")
this.dialogVisible = !this.dialogVisible
this.form.request =''
this.form.answer =''
this.form.time =''
this.form.satisfication =''
this.form.comment =''
}
})
.catch(err=>{
console.error('ERROR',err)
})
}
else{
return false
}
})
},
以下为BackPlat中html部分。注意:将回复和删除两个按钮嵌入到表单中,需要使用template模板,然后再实现相应的方法时,传递的参数为scope.row,这样就能够直接获取表单数据。在js的senddata中,以row为对象,调用其中的列表的变量。
<el-tab-pane label="用户评价数据" name="first">
<el-table :data="filtertableData" style="width: 100%" v-if="!dialogVisible" border>
<el-table-column prop="id" label="序号" width="50" align="center"></el-table-column>
<el-table-column prop="username" label="用户名" width="100" align="center"></el-table-column>
<el-table-column prop="request" label="用户提问" width="250" align="center"></el-table-column>
<el-table-column prop="answer" label="系统回答" width="400" align="center">
</el-table-column>
<el-table-column prop="time" label="提问时间" width="200" align="center"></el-table-column>
<el-table-column prop="comment" label="用户建议" width="250" align="center"></el-table-column>
<el-table-column prop="score" label="用户评分" width="80" align="center"></el-table-column>
<el-table-column label="操作" width="150" align="center">
<template slot-scope="scope">
<el-button type="primary" @click="senddata(scope.row)" size="small">回复</el-button>
<el-button type="danger" @click="deletedata(scope.row)" size="small">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-tab-pane>
// 加载数据库的第一个表到第一个table中
methods:{
loaddata(){
this.$axios.get('http://127.0.0.1:5001/getrecord')
.then(res=>{
console.log(res);
for(let i=0;i<res.data.length;i++){
this.tableData.push(
{
id:res.data[i].id,
request:res.data[i].request,
answer:res.data[i].answer,
time:res.data[i].time,
comment:res.data[i].comment,
score:res.data[i].satisfication,
username:res.data[i].username
}
)
}
})
},
senddata(row){
this.dialogVisible1 = !this.dialogVisible1
console.log(row);
this.form1.request = row.request
this.form1.answer = row.answer
},
},
mounted:{
//当该页面一加载时就要像后端请求数据
this.loaddata()
}
#backdata.py
@app.route('/saverecord',methods=['POST'])
def handledata():
with app.app_context():
data = request.json
print(data)
new_record = bakcdata(
request = data['request'],
answer = data['answer'],
comment = data['comment'],
satisfication = data['satisfication'],
time = data['time'],
username = data['username']
)
db.session.add(new_record)
db.session.commit()
return jsonify({'message': '保存成功'}),200
@app.route('/getrecord',methods=['GET'])
def getrecord():
with app.app_context():
records = bakcdata.query.all()
records_dict = [{'id': record.id, 'request': record.request, 'answer': record.answer,
'time': record.time, 'comment': record.comment, 'satisfication': record.satisfication,
'username':record.username}
for record in records]
return jsonify(records_dict)