毕设记录(三)——医疗领域知识图谱实现智能问答与分析服务(前后端联动逻辑功能)

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)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值