SanHQin 1 anno fa
parent
commit
ddebced2eb

+ 47 - 0
src/api/classObserve.js

@@ -175,3 +175,50 @@ export function getChatListRequest(data){//获取聊天记录
     hideloading: true
 	})
 }
+
+export function insertChatListRequest(data){//保存聊天记录
+	return request2({
+		url: 'https://gpt4.cocorobo.cn/insert_chat',
+    method: 'post',
+    data,
+    hideloading: true
+	})
+}
+
+export function aiChatRequest(data){//聊天请求
+	return request2({
+		url: 'https://gpt4.cocorobo.cn/chat',
+    method: 'post',
+    data,
+    hideloading: true
+	})
+}
+
+export function aiRoleChatRequest(data){//聊天请求
+	return request2({
+		url: 'https://gpt4.cocorobo.cn/ai_agent_park_chat_new',
+    method: 'post',
+    data,
+    hideloading: true
+	})
+}
+
+export function getRoleList(data){//获取自己的角色列表
+	return request2({
+		url: 'https://gpt4.cocorobo.cn/get_ai_agent_assistant_list',
+    method: 'post',
+    data,
+    hideloading: true
+	})
+}
+
+
+export function getShareRoleList(data){//获取自己的角色列表
+	return request2({
+		url: 'https://gpt4.cocorobo.cn/get_ai_agent_assistant_share_list',
+    method: 'post',
+    data,
+    hideloading: true
+	})
+}
+

+ 440 - 19
src/views/classObserve/aiChat.vue

@@ -5,11 +5,12 @@
       <template v-for="(item, index) in chatList">
         <div class="chat1" style="justify-content: flex-end;" v-if="item.content">
           <div class="conText">
-            <div class="Txt" v-text="item.content"></div>
+            <div class="Txt" v-html="item.content"></div>
             <img
               style="position: absolute;left: -25px;bottom: -10px;"
               src="../../assets/images/classObserve/copyTextZ.png"
               alt=""
+              @click.stop="copy(item.content)"
             />
             <div class="DateT" style="right: 0 !important;">{{ item.createtime }}</div>
           </div>
@@ -17,16 +18,17 @@
             <img src="../../assets/images/classObserve/userAvatar.png" alt="" />
           </div>
         </div>
-        <div class="chat1" v-loading="item.loading">
+        <div class="chat1">
           <div class="PicturePop">
-            <img src="../../assets/images/classObserve/ai.png" alt="" />
+            <img :src="item.filename?item.filename:require('../../assets/images/classObserve/ai.png')" alt="" />
           </div>
-          <div class="conText">
+          <div class="conText" v-loading="item.loading">
             <div class="Txt" v-html="item.aiContent"></div>
             <img
               style="position: absolute;right: -25px;bottom: -10px;"
               src="../../assets/images/classObserve/copyTextZ.png"
               alt=""
+              @click.stop="copy(item.aiContent)"
             />
             <div class="DateT">{{ item.createtime }}</div>
           </div>
@@ -38,8 +40,23 @@
       </div> -->
     </div>
     <div class="bot" v-loading="chatLoading">
+      <div class="b_roleList" v-if="showRoleList && choseRoleList.length > 0">
+        <div class="b_rl_item" v-for="item in choseRoleList" :key="item.id" @click.stop="choiceRole(item)">
+          <img :src="item.headUrl" />
+          <div class="b_rl_i_message">
+            <div>{{ item.assistantName }}</div>
+            <span>作者:{{ item.username }}</span>
+            <div class="des">{{ item.description }}</div>
+          </div>
+        </div>
+      </div>
       <!-- <img class="pict1" src="../../assets/images/classObserve/maike.png" alt="" /> -->
-      <el-input v-model="input" placeholder="请在此输入您想了解的内容"></el-input>
+      <el-input
+        ref="textareaRef"
+        v-model="inputValue"
+        @input="inputChange"
+        placeholder="请在此输入您想了解的内容"
+      ></el-input>
       <img class="pict2" src="../../assets/images/classObserve/sendimg.png" alt="" @click.stop="send" />
     </div>
   </div>
@@ -48,7 +65,16 @@
 <script>
 import bar from './components/bar.vue'
 import fromList from './components/fromList'
-import { getChatListRequest } from '@/api/classObserve'
+import {
+  getChatListRequest,
+  insertChatListRequest,
+  aiChatRequest,
+  getRoleList,
+  getShareRoleList,
+	aiRoleChatRequest
+} from '@/api/classObserve'
+import { v4 as uuidv4 } from 'uuid'
+import MarkdownIt from 'markdown-it'
 
 export default {
   components: {
@@ -63,16 +89,39 @@ export default {
     tid: {
       type: String,
       default: ''
-    }
+    },
+		fileId:{
+			type:String,
+			default:""
+		},
   },
   data() {
     return {
       selectValue: '',
-      input: '',
+      inputValue: '',
       loading: false,
       chatLoading: false,
       chatList: [],
-      userId: this.$store.state.user.id
+      userId: this.$store.state.user.id,
+      publicRoleList: [],
+      roleList: [],
+      showRoleList: false
+    }
+  },
+  computed: {
+    // 选择可以@的角色
+    choseRoleList() {
+      let result = []
+      this.roleList.map(i => result.push(i))
+      this.publicRoleList.map(i => result.push(i))
+      const _index = this.inputValue.lastIndexOf('@')
+      if (_index !== -1) {
+        let roleName = this.inputValue.substring(_index + 1)
+        result = result.filter(i => i.assistantName.indexOf(roleName) != -1)
+      } else {
+        result = []
+      }
+      return result
     }
   },
   methods: {
@@ -80,24 +129,161 @@ export default {
       this.$emit('cutPage', 1)
     },
     send() {
-      console.log('发送')
+      var OpenCC = require('opencc-js')
+      let converter = OpenCC.Converter({
+        from: 'cn',
+        to: 'hk'
+      })
+      // return console.log(this.inputValue)
+      let _text = this.inputValue
+      this.inputValue = ''
+      if (!_text) return this.$toast('请输入内容')
+
+      let _atRoleList = []
+			let _roleList = []
+			this.roleList.map(i=>_roleList.push(i))
+			this.publicRoleList.map(i=>_roleList.push(i))
+			// let _roleList = [...this.roleList, ...this.publicRoleList];
+      _roleList.forEach((i) => {
+        if (_text.indexOf(`@${i.assistantName}`) != -1) {
+          _atRoleList.push(i);
+        }
+      });
+      if (_atRoleList.length > 0) {
+        //有@角色
+        let _replaceText = _text
+        let _htmlText = _text
+        _atRoleList.forEach(_i => {
+          _replaceText = _replaceText.replaceAll(`@${_i.assistantName}`, ``)
+          _htmlText = _htmlText.replaceAll(
+            `@${_i.assistantName}`,
+            `<span class='aite-name'>@${_i.assistantName}</span>`
+          )
+        })
+        _atRoleList.forEach((_item, _index) => {
+          const _uid = uuidv4()
+          if (_index == 0) {
+            this.chatList.push({
+              loading: true,
+              role: 'user',
+              content: _htmlText,
+              uid: _uid,
+              AI: 'AI',
+              aiContent: '',
+              oldContent: '',
+              isShowSynchronization: false,
+              filename: _item.headUrl,
+              index: this.chatList.length,
+              is_mind_map: false,
+              fileid: _item.assistantName,
+              createtime: new Date().toLocaleString().replaceAll('/', '-')
+            })
+          } else {
+            this.chatList.push({
+              loading: true,
+              role: 'user',
+              content: '',
+              uid: _uid,
+              AI: 'AI',
+              aiContent: '',
+              oldContent: '',
+              isShowSynchronization: false,
+              filename: _item.headUrl,
+              index: this.chatList.length,
+              is_mind_map: false,
+              fileid: _item.assistantName,
+              createtime: new Date().toLocaleString().replaceAll('/', '-')
+            })
+          }
+          this.scrollBottom()
+          let params = {
+            assistant_id: _item.assistant_id,
+            userId: this.userId,
+            message: _replaceText,
+            session_name: `${this.tid}-classroomObservation`,
+            uid: _uid,
+            file_ids: this.fileId ? [this.fileId] : [],
+            model: 'gpt-4o-2024-08-06'
+          }
+          aiRoleChatRequest(params)
+            .then(res => {
+              if (converter(res.FunctionResponse.result) == converter('发送成功')) {
+              } else {
+                this.$toast.fail(res.FunctionResponse.result)
+              }
+            })
+            .catch(err => {
+              console.log(err)
+            })
+          this.getAtAuContent(_uid, _htmlText, _item.headUrl, _item.assistantName)
+        })
+      } else {
+        //未@角色
+        let _uuid = uuidv4()
+        this.chatList.push({
+          role: 'user',
+          content: `${_text}`,
+          uid: _uuid,
+          AI: 'AI',
+          aiContent: '',
+          oldContent: '',
+          isShowSynchronization: false,
+          filename: '',
+          index: this.chatList.length,
+          is_mind_map: false,
+          createtime: new Date().toLocaleString().replaceAll('/', '-'),
+          loading: true
+        })
+        this.scrollBottom()
+
+        // 连续对话
+        let _historyMessage = []
+        // this.chatList.forEach(i=>{
+
+        // })
+        _historyMessage.push({ role: 'user', content: _text })
+        let params = JSON.stringify({
+          model: 'gpt-4o-2024-08-06',
+          temperature: 0,
+          max_tokens: 4096,
+          top_p: 1,
+          frequency_penalty: 0,
+          presence_penalty: 0,
+          messages: _historyMessage,
+          uid: _uuid,
+          mind_map_question: ''
+        })
+
+        aiChatRequest(params)
+          // .post("https://claude3.cocorobo.cn/chat", params)
+          .then(res => {
+            if (converter(res.FunctionResponse.result) == converter('发送成功')) {
+            } else {
+              this.$toast.fail(res.FunctionResponse.result)
+            }
+          })
+          .catch(e => {
+            console.log(e)
+          })
+        this.getAiContent(_uuid)
+      }
     },
     scrollBottom() {
-			this.$nextTick(() => {
+      this.$nextTick(() => {
         // this.textareaChange();
         // this.$refs.tapeRef.$el.querySelector(
         //   ".t-chartArea"
         // ).scrollTop = this.$refs.tapeRef.$el.querySelector(".t-chartArea").scrollHeight;
-				this.$refs.chatAreaRef.scrollTop = this.$refs.chatAreaRef.scrollHeight
-      });
+        this.$refs.chatAreaRef.scrollTop = this.$refs.chatAreaRef.scrollHeight
+      })
     },
     getChatList() {
       return new Promise((resolve, reject) => {
         if (!this.tid) return
-        if (this.chatLoading) return this.$message.info('请稍等...')
+        if (this.chatLoading) return this.$toast('请稍等...')
         this.chatList = []
         this.chatLoading = true
-				this.loading = true;
+        this.loading = true
         let params = {
           userid: this.userId,
           groupid: '602def61-005d-11ee-91d8-005056b8q12w',
@@ -130,10 +316,10 @@ export default {
               this.chatLoading = false
             } else {
               //没有对话记录
-              this.chatLoading = false;
+              this.chatLoading = false
             }
             resolve()
-						this.loading = false;
+            this.loading = false
             this.scrollBottom()
           })
           .catch(err => {
@@ -141,14 +327,185 @@ export default {
             this.$toast.fail('获取对话记录失败')
             this.chatLoading = false
             this.scrollBottom()
-						this.loading = false;
-						resolve()
+            this.loading = false
+            resolve()
           })
       })
+    },
+    copy(content) {
+      // 创建临时textarea元素
+      const tempInput = document.createElement('textarea')
+      tempInput.value = content.replace(/<[^>]+>/g, '') // 设置要复制的内容
+      // 隐藏元素
+      tempInput.style.position = 'absolute'
+      tempInput.style.left = '-9999px'
+      // 将元素添加到DOM中
+      document.body.appendChild(tempInput)
+      // 选中元素内容
+      tempInput.select()
+      // 执行复制操作
+      document.execCommand('copy')
+      // 移除临时元素
+      document.body.removeChild(tempInput)
+      this.$toast.success('复制成功')
+    },
+    getAiContent(_uid) {
+      // let _source = new EventSource(
+      // 	`https://claude3.cocorobo.cn/streamChat/${_uid}`
+      // );
+      let _source = new EventSource(`https://gpt4.cocorobo.cn/stream/${_uid}`) //http://gpt4.cocorobo.cn:8011/stream/     https://gpt4.cocorobo.cn/stream/
+      let _allText = ''
+      let _mdText = ''
+      const md = new MarkdownIt()
+      _source.onmessage = _e => {
+        if (_e.data.replace("'", '').replace("'", '') == '[DONE]') {
+          //对话已经完成
+          _mdText = _mdText.replace('_', '')
+          _source.close()
+          this.chatList.find(i => i.uid == _uid).aiContent = _mdText
+          this.chatList.find(i => i.uid == _uid).isalltext = true
+          this.chatList.find(i => i.uid == _uid).isShowSynchronization = true
+          this.chatList.find(i => i.uid == _uid).loading = false
+          this.insertChat(_uid)
+          return
+        } else {
+          //对话还在继续
+          let _text = ''
+          _text = _e.data.replaceAll("'", '')
+          if (_allText == '') {
+            _allText = _text.replace(/^\n+/, '') //去掉回复消息中偶尔开头就存在的连续换行符
+          } else {
+            _allText += _text
+          }
+          _mdText = _allText + '_'
+          _mdText = _mdText.replace(/\\n/g, '\n')
+          _mdText = _mdText.replace(/\\/g, '')
+          if (_allText.split('```').length % 2 == 0) _mdText += '\n```\n'
+          //转化返回的回复流数据
+          _mdText = md.render(_mdText)
+          this.chatList.find(i => i.uid == _uid).aiContent = _mdText
+          this.chatList.find(i => i.uid == _uid).loading = false
+          this.scrollBottom()
+          // 处理流数据
+        }
+      }
+    },
+    getAtAuContent(_uid, _text, _headUrl, _assistantName) {
+      let _source = new EventSource(`https://gpt4.cocorobo.cn/question/${_uid}`) //http://gpt4.cocorobo.cn:8011/question/   https://gpt4.cocorobo.cn/question/
+      let _allText = ''
+      let _mdText = ''
+      const md = new MarkdownIt()
+      _source.onmessage = _e => {
+        let _eData = JSON.parse(_e.data)
+        if (_eData.content.replace("'", '').replace("'", '') == '[DONE]') {
+          let _result = []
+          if ('result' in _eData) {
+            _result = _eData.result
+            for (let i = 0; i < _result.length; i++) {
+              _mdText = _mdText.replace(_result[i].text, _result[i].fileName)
+            }
+          }
+          _mdText = _mdText.replace('_', '')
+          this.chatList.find(i => i.uid == _uid).aiContent = _mdText
+          this.chatList.find(i => i.uid == _uid).isalltext = true
+          this.chatList.find(i => i.uid == _uid).isShowSynchronization = true
+          this.chatList.find(i => i.uid == _uid).loading = false
+          this.scrollBottom()
+          this.insertChat(_uid)
+        } else {
+          let _text = _eData.content.replace("'", '').replace("'", '')
+          if (_allText == '') {
+            _allText = _text.replace(/^\n+/, '') //去掉回复消息中偶尔开头就存在的连续换行符
+          } else {
+            _allText += _text
+          }
+          _mdText = _allText + '_'
+          _mdText = _mdText.replace(/\\n/g, '\n')
+          _mdText = _mdText.replace(/\\/g, '')
+          if (_allText.split('```').length % 2 == 0) _mdText += '\n```\n'
+          //转化返回的回复流数据
+          _mdText = md.render(_mdText)
+          this.chatList.find(i => i.uid == _uid).aiContent = _mdText
+          this.chatList.find(i => i.uid == _uid).loading = false
+          this.scrollBottom()
+          // 处理流数据
+        }
+      }
+    },
+    inputChange() {
+      if (this.inputValue.at(-1) == '@') {
+        this.showRoleList = true
+      }
+    },
+    choiceRole(_data) {
+      let _lastAtIndex = this.inputValue.lastIndexOf('@')
+      this.inputValue = `${this.inputValue.slice(0, _lastAtIndex)}@${_data.assistantName} `
+      this.$refs.textareaRef.focus()
+      this.showRoleList = false
+    },
+    //保存消息
+    insertChat(_uid) {
+      let _data = this.chatList.find(i => i.uid == _uid)
+      if (!_data) return
+      let params = {
+        userId: this.userId,
+        userName: 'qgt',
+        groupId: '602def61-005d-11ee-91d8-005056b8q12w',
+        answer: _data.aiContent,
+        problem: _data.content,
+        file_id: _data.fileid ? _data.fileid : '',
+        alltext: _data.aiContent,
+        type: 'chat',
+        filename: _data.filename,
+        session_name: `${this.tid}-classroomObservation`
+      }
+      insertChatListRequest(params).then(res => {})
+    },
+    //获取角色
+    getRoleList() {
+      this.roleList = []
+      let params = {
+        userId: this.userId
+      }
+      getRoleList(params)
+        .then(res => {
+          let _data = res.FunctionResponse.result
+          if (_data) {
+            this.roleList = JSON.parse(_data)
+          }
+        })
+        .catch(e => {
+          console.log(e)
+          // this.$message.error("获取角色列表失败");
+          this.roleList = []
+        })
+    },
+    getPublicRoleList() {
+      this.publicRoleList = []
+      let params = {
+        userId: this.userId,
+        organizeid: '45facc0a-1211-11ec-80ad-005056b86db5'
+      }
+      getShareRoleList(params)
+        .then(res => {
+          let _data = res.FunctionResponse.result
+          if (_data) {
+            this.publicRoleList = JSON.parse(_data)
+          }
+        })
+        .catch(e => {
+          this.publicRoleList = []
+          console.log(e)
+          // console.log("获取公共角色失败", e);
+        })
     }
   },
   mounted() {
     this.getChatList()
+    this.getRoleList()
+    this.getPublicRoleList()
+    this.roleList = []
+    this.publicRoleList = []
   }
 }
 </script>
@@ -194,6 +551,16 @@ export default {
         box-sizing: border-box;
         font-size: 13px;
         border-radius: 2px;
+        :deep(*) {
+          margin: 0;
+        }
+				:deep(.aite-name){
+					background-color: #1B5AAF;
+					padding: 3px;
+					font-size: 10px;
+					color: #fff;
+					border-radius: 2px;
+				}
       }
       .DateT {
         color: rgba(0, 0, 0, 0.4);
@@ -231,6 +598,60 @@ export default {
   padding: 10px 10px;
   align-items: center;
   box-sizing: border-box;
+  position: relative;
+  .b_roleList {
+    width: 100%;
+    max-height: 50vh;
+    height: auto;
+    position: absolute;
+    bottom: 100%;
+    border-radius: 10px 10px 0 0;
+    background-color: #fff;
+    padding: 10px;
+    box-sizing: border-box;
+    overflow: auto;
+    .b_rl_item {
+      width: 100%;
+      height: 60px;
+      margin-bottom: 10px;
+      background-color: #fff;
+      border-radius: 8px;
+      transition: 0.3s;
+      display: flex;
+      align-items: center;
+      padding: 0 10px;
+      box-sizing: border-box;
+      img {
+        width: 50px;
+        height: 50px;
+        margin-right: 10px;
+        border-radius: 50%;
+      }
+      .b_rl_i_message {
+        width: 80%;
+        height: 50px;
+        display: flex;
+        flex-direction: column;
+        div {
+          font-size: 14px;
+        }
+        .des {
+          font-size: 10px;
+          overflow: hidden;
+          text-overflow: ellipsis;
+          white-space: nowrap;
+          width: 80%;
+        }
+        span {
+          font-size: 10px;
+          color: gray;
+        }
+      }
+      &:active {
+        background-color: #ebf1fd;
+      }
+    }
+  }
   .pict1 {
     width: 24px;
     height: 24px;

+ 34 - 7
src/views/classObserve/homePage.vue

@@ -218,17 +218,16 @@
               <div class="addNewCourseBtn" @click.stop="createDefaultCourse()">默认模板创建</div>
             </div>
             <template #reference>
-              <button :disabled="isParse" class="btn1" @click.stop="showAddNewCourse()">
+              <button :disabled="![0,3].includes(recordedForm.status)" class="btn1" @click.stop="showAddNewCourse()">
                 创建课堂
-                <div v-if="isParse" class="one"></div>
+                <div v-if="![0,3].includes(recordedForm.status)" class="one" @click.stop="showAddNewCourse()"></div>
               </button>
             </template>
           </van-popover>
         </div>
         <div>
-          <button :disabled="isParse" class="btn2" @click="cutPage(8)">
+          <button class="btn2" @click="cutPage(8)">
             查看分析
-            <div v-if="isParse" class="one"></div>
           </button>
         </div>
       </div>
@@ -414,6 +413,27 @@ export default {
     }
   },
   methods: {
+		init(){
+			this.jobContext = null;
+			this.historyShow= false
+      this.uploadFileLoading= false
+      this.abuShowId= ''
+      this.historyListLoading= false
+      this.addNewCourseShow= false
+      this.changeLanguageShow= false
+			this.recordedForm = {
+        time: '00:00:00', //时间
+        status: 0, //0--未录音  1--正在录音  2--暂停  3--录音结束
+        timer: null,
+        timeDuration: 0,
+        audioBlob: [],
+        startTime: 0,
+        endTime: 0,
+        textList: [],
+        loading: false
+      }
+			this.showTranscriptType = 0;
+		},
     changeTranscriptType(newType) {
       this.showTranscriptType = newType
     },
@@ -648,7 +668,12 @@ export default {
       this.$emit('cutPage', val)
     },
     showAddNewCourse() {
-      if (this.isParse) return (this.addNewCourseShow = false)
+			if(![0,3].includes(this.recordedForm.status)){
+				console.log("测试");
+				
+				return this.$toast.fail('请先结束录音')
+			};
+      // if (this.isParse) return (this.addNewCourseShow = false)
       this.addNewCourseShow = !this.addNewCourseShow
     },
     createDefaultCourse() {
@@ -752,6 +777,7 @@ export default {
         this.recordedForm.status = 1
         this.showTranscriptType = 1
         this.$toast('已开始录音')
+				this.recordedForm.loading = false;
         this.recordedForm.timer = setInterval(() => {
           this.recordedForm.timeDuration += 1
           this.recordedForm.time = this.updateRecordedTime({
@@ -817,7 +843,7 @@ export default {
       }
     },
     stopRecord() {
-      if (this.recordedForm.loading) return this.$toast('请稍等...')
+      // if (this.recordedForm.loading) return this.$toast('请稍等...')
       if ([1].includes(this.recordedForm.status)) {
         // 录音暂停
         //暂停
@@ -1251,7 +1277,8 @@ export default {
       iiframe.contentWindow.doContinuousPronunciationAssessment('', {
         files: [audioFile]
       })
-    }
+    },
+
   }
 }
 </script>

+ 4 - 2
src/views/classObserve/index.vue

@@ -44,7 +44,7 @@
       :dataList="dataList"
       :tid="tid"
     />
-		<aiChat ref="aiChatRef" @cutPage="cutPage" :tid="tid" v-if="page==9"/>
+		<aiChat ref="aiChatRef" @cutPage="cutPage" :fileId="fileId" :tid="tid" v-if="page==9"/>
   </div>
 </template>
 
@@ -381,7 +381,9 @@ export default {
       let _flag = this.tid !== newTid
       this.tid = newTid
       if (_flag) {
-        this.getData()
+        this.getData().then(_=>{
+					this.$refs.homePageRef.init();
+				})
       }
       // 获取fileId
       this.getFileIdId()