From ff2d01588d4e69d3bf1ad856e8e225b7f6a3c0f2 Mon Sep 17 00:00:00 2001
From: zhaowei <zhaowei>
Date: 星期一, 26 八月 2024 11:01:48 +0800
Subject: [PATCH] 1、基本实现电子说明书页面布局及功能 2、基本实现语言大模型页面与后端数据联动

---
 src/views/ai/LanguageModel.vue | 1028 ++++++++++++++++++++++++++++++++++++++++++++++----------
 1 files changed, 836 insertions(+), 192 deletions(-)

diff --git a/src/views/ai/LanguageModel.vue b/src/views/ai/LanguageModel.vue
index 3a17391..36c4eb6 100644
--- a/src/views/ai/LanguageModel.vue
+++ b/src/views/ai/LanguageModel.vue
@@ -1,64 +1,147 @@
 <template>
   <div class="page-container">
     <div class="outer-container">
+      <!--宸︿晶鍘嗗彶浼氳瘽鍖哄煙-->
       <div class="left-container">
-        <div><img src="@/assets/page/languageModel/logo.png" style="width: 100%"></div>
-        <a-button style="margin: 20px 0 10px" @click="createNewConversation">鏂板浼氳瘽</a-button>
-        <div class="chat-history-container">
+        <!--logo鍖哄煙-->
+        <div class="logo-container"><img src="@/assets/page/languageModel/logo.png"></div>
 
-          <div v-for="(item,index) in chatHistoryList" :key="index"
-               :class="[item.id===activeHistoryIndex?'single-history-active':'']"
-               @click="switchToCurrentConversation(item,index)" @mouseenter="item.iconVisible=true"
+        <!--鍔熻兘鎸夐敭鍖哄煙-->
+        <div class="manage-history-container">
+          <div @click="createNewConversation"
+               :class="[isAtNewConversation?'create-history-container-active':'',isDeletingBatch?'fold-create-history-container':'expand-create-history-container',isModelResponding?'disable-expand':'']"
+               class="create-new-conversation">
+            <a-icon type="plus"></a-icon>
+            <span v-if="!isDeletingBatch">鏂板浼氳瘽</span>
+          </div>
+          <div @click="expandDeleteBatchContainer"
+               :class="[isDeletingBatch?'expand-delete-batch-container':'fold-delete-batch-container',chatHistoryList.length===0?'disable-expand':'']"
+               class="delete-batch-container">
+            <a-icon type="delete" v-if="!isDeletingBatch"/>
+            <template v-else>
+              <div class="expand-delete-batch-inner">
+                <div class="select-all-button">
+                  <label class="checkbox-custom">
+                    <input type="checkbox" @change="allHistoryCheckedChange"
+                           id="select-all-checkbox"></input>
+                    <span class="check-mark"></span>
+                    <div>鍏ㄩ��</div>
+                  </label>
+                </div>
+                <div class="split-line"></div>
+                <a-popover placement="top" :visible="deleteBatchPopVisible" trigger="click"
+                           :getPopupContainer="node=>node.parentNode" overlayClassName="delete-batch-popover">
+                  <template slot="content">
+                    <div class="popover-content">
+                      <div>鍒犻櫎鍚庢棤娉曟仮澶嶏紝鏄惁缁х画鍒犻櫎锛�</div>
+                      <div>
+                        <button class="cancel-delete-button" @click="deleteBatchPopVisible=false">鍙栨秷</button>
+                        <button id="confirm-delete-batch-button" @click="confirmDeleteBatchConversation">鍒犻櫎
+                        </button>
+                      </div>
+                    </div>
+                  </template>
+                  <div @click="deleteBatchConversation" id="delete-batch-button"
+                       :class="[checkedConversationIdList.length&&!isModelResponding?'able-delete-button':'disable-delete-button']">
+                    <a-icon type="delete"/>
+                    <div>鍒犻櫎</div>
+                  </div>
+                </a-popover>
+                <div class="split-line"></div>
+                <div @click.stop="cancelDeleteBatchConversation">
+                  <a-icon type="close"/>
+                  <div>閫�鍑�</div>
+                </div>
+              </div>
+            </template>
+          </div>
+        </div>
+
+        <!--鍘嗗彶浼氳瘽鍒楄〃鍖哄煙-->
+        <div class="chat-history-container">
+          <template v-if="!chatHistoryList.length">
+            <a-empty>
+              <span slot="description">鏃犲巻鍙蹭細璇�</span>
+            </a-empty
+            >
+          </template>
+
+          <div v-for="(item,index) in chatHistoryList" :key="item.id"
+               :class="[item.id===activeHistoryIndex?'single-history-active':'',item.inputVisible?'input-visible-class':'',isModelResponding?'disable-switch':'']"
+               @click="switchToCurrentConversation(item,$event)"
+               @mouseenter="item.iconVisible=true"
                @mouseleave="item.iconVisible=false">
-            <a-popconfirm ok-text="鍒犻櫎" cancel-text="鍙栨秷" @confirm="confirmDeleteConversation(item,index)"
-                          @cancel="cancelDeleteConversation(item)"
-                          :visible="item.deletePopVisible" :arrowPointAtCenter="true">
-              <template slot="title">
-                鍒犻櫎鍚庢棤娉曟仮澶嶏紝鏄惁缁х画鍒犻櫎锛�
+            <!--input鏀剧疆鍦╬opover涓棤娉曚娇鐢ㄥ姛鑳�-->
+            <label v-if="isDeletingBatch" class="checkbox-custom">
+              <input type="checkbox" v-model="checkedConversationIdList" :value="item.id"
+                     @change="singleHistoryCheckedChange"/>
+              <span class="check-mark"></span>
+            </label>
+
+            <a-popover placement="top" :visible="item.deletePopVisible" trigger="click"
+                       :getPopupContainer="node=>node.parentNode">
+              <template slot="content">
+                <div class="popover-content">
+                  <div>鍒犻櫎鍚庢棤娉曟仮澶嶏紝鏄惁缁х画鍒犻櫎锛�</div>
+                  <div>
+                    <button class="cancel-delete-button" @click="cancelDeleteConversation(item,$event)">鍙栨秷</button>
+                    <button @click.stop="confirmDeleteConversation(item,index)" id="delete-conversation-button">鍒犻櫎
+                    </button>
+                  </div>
+                </div>
               </template>
               <template v-if="!item.inputVisible">
-                <div class="conversation-title">{{item.title}}</div>
-                <div class="icon-container" v-show="item.iconVisible">
-                  <a-icon type="edit" @click.stop="editConversationTitle(item,index)"/>
-                  <a-icon type="delete" @click.stop="deleteConversation(item,index)"/>
+                <div class="conversation-title">{{item.problem.slice(0,15)}}</div>
+                <div class="icon-container hover-icon-container" v-show="!isDeletingBatch&&!isModelResponding">
+                  <a-icon type="edit" @click.stop="editConversationTitle(item)"/>
+                  <a-icon type="delete" @click.stop="deleteConversation(item)"
+                          @blur="cancelDeleteConversation(item,$event)"/>
                 </div>
               </template>
 
               <template v-else>
                 <input id="edit-input" v-model="editedConversationTitle"
-                       @keydown.enter="confirmEditConversationTitle(item)" maxlength="15"></input>
+                       @keydown.enter="confirmEditConversationTitle(item,$event)"
+                       @blur="cancelEditConversationTitle(item,$event)"
+                       maxlength="15">
+                </input>
                 <div class="icon-container">
-                  <a-icon type="check" @click.stop="confirmEditConversationTitle(item)"/>
-                  <a-icon type="close" @click.stop="cancelEditConversationTitle(item)"/>
+                  <a-icon type="check" @click.stop="confirmEditConversationTitle(item,$event)"/>
+                  <a-icon type="close"/>
                 </div>
               </template>
-            </a-popconfirm>
+            </a-popover>
           </div>
         </div>
       </div>
 
+      <!--鍙充晶浼氳瘽鍐呭鍖哄煙-->
       <div class="right-container">
+        <!--浼氳瘽鍐呭鍒楄〃鍖哄煙-->
         <div class="conversation-container">
           <div v-for="item in currentConversation.messages" class="single-conversation" :id="'id'+item.index">
             <div v-if="item.role==='user'" class="user-question">
               <div class="avatar">
                 <a-avatar :src="getAvatar()"/>
               </div>
-              <div class="content">{{item.content}}</div>
+              <div class="conversation-content">{{item.content}}</div>
             </div>
             <div v-else class="assistant-answer">
               <div class="avatar">
                 <img src="@/assets/page/languageModel/ai-avatar.png"/>
               </div>
-              <div class="content">{{item.content}}</div>
+              <div class="conversation-content" v-html="item.content.replace(/\n/g,'<br>')"></div>
             </div>
           </div>
         </div>
+
+        <!--鎻愰棶杈撳叆鍖哄煙-->
         <div class="input-container" :class="[textareaFocused?'input-container-active':'']">
-          <textarea v-model="inputQuestion" placeholder="Enter鍙戦�侊紝Shift+Enter鎹㈣"
+          <textarea v-model="inputQuestion" :placeholder="textareaPlaceholder"
                     @keydown.enter="sendQuestion($event)" @focus="textareaFocused=true"
                     @blur="textareaFocused=false"></textarea>
-          <img src="@/assets/page/languageModel/send-message.png" @click="sendQuestion($event)" v-if="!isResponding">
+          <img src="@/assets/page/languageModel/send-message.png" @click="sendQuestion($event)"
+               v-if="!isModelResponding">
           <a-icon type="loading" class="loading-icon" v-else/>
         </div>
       </div>
@@ -71,9 +154,20 @@
   import { mapGetters } from 'vuex'
   import { message } from 'ant-design-vue'
   import { randomUUID } from '@/utils/util'
+  import {
+    addNewConversationApi,
+    getChatHistoryListApi,
+    getCurrentConversationApi,
+    deleteSingleChatHistoryApi,
+    askToLanguageModelApi
+  } from '@/api/ai'
+  import { EventSourcePolyfill } from 'event-source-polyfill'
+  import Vue from 'vue'
+  import { ACCESS_TOKEN, TENANT_ID } from '@/store/mutation-types'
 
   message.config({
-    maxCount: 1
+    maxCount: 1,
+    duration: 2
   })
 
   export default {
@@ -82,64 +176,51 @@
     data() {
       return {
         chatHistoryList: [],
+        checkedConversationIdList: [],
+        deleteBatchPopVisible: false,
         currentConversation: {
-          'id': '683a65fd-8feb-4446-ad32-714c4785f667',
-          'messages': [
-            {
-              'role': 'user',
-              'content': '浣犳槸璋侊紵浣犳槸璋佷綘鏄皝浣犳槸璋佷綘鏄皝浣犳槸璋佷綘鏄皝浣犳槸璋佷綘鏄皝浣犳槸璋佷綘鏄皝浣犳槸璋佷綘鏄皝浣犳槸璋佷綘鏄皝浣犳槸璋佷綘鏄皝浣犳槸璋�'
-            },
-            {
-              'role': 'assistant',
-              'content': '鎴戞槸26涓敓娑寚瀵煎笀灏忕埍'
-            },
-            {
-              'role': 'user',
-              'content': '浣犻兘鑳藉仛浜涗粈涔堬紵'
-            },
-            {
-              'role': 'assistant',
-              'content': '鎴戣兘绔欏湪鏈潵瑙嗚甯姪鍚屽浠仛濂界敓娑鍒掓寚瀵笺��'
-            },
-            {
-              'role': 'user',
-              'content': '浠婂ぉ澶╂皵濡備綍锛�'
-            },
-            {
-              'role': 'assistant',
-              'content': '鎴戞槸26涓敓娑寚瀵煎笀灏忕埍'
-            },
-            {
-              'role': 'user',
-              'content': '浣犻兘鑳藉仛浜涗粈涔堬紵'
-            },
-            {
-              'role': 'assistant',
-              'content': '鎴戣兘绔欏湪鏈潵瑙嗚甯姪鍚屽浠仛濂界敓娑鍒掓寚瀵笺��'
-            }
-          ],
-          'stream': false,
-          'max_tokens': 500,
-          iconVisible: false,
-          inputVisible: false,
-          deletePopVisible: false
+          id: '',
+          messages: [],
+          stream: true,
+          max_tokens: 500
         },
+        isAtNewConversation: false,
+        isDeletingBatch: false,
         activeHistoryIndex: null,
         editingHistoryIndex: null,
         deletingHistoryIndex: null,
-        iconVisible: false,
-        inputVisible: false,
         editedConversationTitle: '',
-        deletePopVisible: false,
         conversationContainer: null,
         inputQuestion: '',
+        textareaPlaceholder: '',
         textareaFocused: false,
-        isResponding: false
+        isModelResponding: false
+      }
+    },
+
+    watch: {
+      deleteBatchPopVisible: {
+        handler(val) {
+          if (val) {
+            document.addEventListener('click', this.handleDocumentClick)
+          } else {
+            document.removeEventListener('click', this.handleDocumentClick)
+          }
+        }
+      },
+      isAtNewConversation: {
+        handler(val) {
+          if (val) {
+            this.textareaPlaceholder = '璇峰皾璇曢棶鎴戯細浣犳槸璋侊紵'
+          } else {
+            this.textareaPlaceholder = 'Enter鍙戦�侊紝Shift+Enter鎹㈣'
+          }
+        },
+        immediate: true
       }
     },
     created() {
-      this.getConversationByApi()
-
+      this.getChatHistoryListByApi()
     },
     mounted() {
       this.conversationContainer = document.querySelector('.conversation-container')
@@ -147,111 +228,397 @@
     methods: {
       ...mapGetters(['avatar']),
 
-      /* 璋冪敤鎺ュ彛鑾峰彇褰撳墠浼氳瘽璁板綍 */
-      getConversationByApi() {
-        this.currentConversation.title = this.currentConversation.messages[0].content.slice(0, 15)
-        this.chatHistoryList.push(this.currentConversation)
-        this.activeHistoryIndex = this.chatHistoryList[0].id
+      getChatHistoryListByApi() {
+        //  TODO 璋冪敤鎺ュ彛鑾峰彇鍘嗗彶浼氳瘽鍒楄〃,濡傛灉鍘嗗彶浼氳瘽涓嶄负绌哄垯璺宠浆鑷崇涓�鏉″巻鍙蹭細璇濆唴瀹逛腑
+        getChatHistoryListApi()
+          .then(res => {
+            console.log('res', res)
+            if (res.success && res.result && res.result.length > 0) {
+              this.chatHistoryList = res.result.map(item => {
+                return {
+                  ...item,
+                  iconVisible: false,
+                  inputVisible: false,
+                  deletePopVisible: false
+                }
+              })
+              if (!this.activeHistoryIndex) this.switchToCurrentConversation(this.chatHistoryList[0])
+              if (this.isDeletingBatch) this.singleHistoryCheckedChange()
+            } else {
+              this.chatHistoryList = []
+              this.createNewConversation()
+            }
+          })
+          .catch(err => {
+            this.chatHistoryList = []
+            this.createNewConversation()
+            console.log('err', err)
+          })
       },
 
-      /* 鍒涘缓涓�涓柊浼氳瘽 */
+      /* 鐐瑰嚮鏂板浼氳瘽鍚庤Е鍙� */
       createNewConversation() {
+        if (this.isAtNewConversation) this.$message.info('褰撳墠宸叉槸鏈�鏂板璇�')
+        if (this.isModelResponding) return // 妯″瀷鍥炵瓟鏈熼棿绂佹鏂板浼氳瘽
+        this.isAtNewConversation = true
         this.currentConversation = {
-          id: randomUUID(),
-          title: '鏈懡鍚嶅璇�',
+          id: '',
           messages: [],
           stream: true,
-          max_tokens: 500,
-          iconVisible: false,
-          inputVisible: false,
-          deletePopVisible: false
+          max_tokens: 500
         }
-        // 閫�鍑哄叾浠栧姛鑳�
-        if (this.editingHistoryIndex !== null) this.cancelEditConversationTitle(this.chatHistoryList.find(item => item.id === this.editingHistoryIndex))
-        if (this.deletingHistoryIndex !== null) this.cancelDeleteConversation(this.chatHistoryList.find(item => item.id === this.deletingHistoryIndex))
+        // 鏂板缓浼氳瘽鏃跺彇娑堝師鍏堣閫変腑鐨勫巻鍙插璇濓紝鏇存竻鏅板憡璇夌敤鎴风幇鍦ㄧ晫闈㈠浜庢柊寤哄璇濅腑
+        if (this.activeHistoryIndex !== null) this.activeHistoryIndex = null
+      },
 
-        this.chatHistoryList.unshift(this.currentConversation)
-        this.activeHistoryIndex = this.chatHistoryList[0].id
-        console.log(this.chatHistoryList)
+      /* 璋冪敤鎺ュ彛鑾峰彇褰撳墠浼氳瘽璁板綍 */
+      getConversationByApi(id) {
+        // TODO 鏍规嵁鐐瑰嚮鐨勫巻鍙蹭細璇滻D鑾峰彇瀵瑰簲鍘嗗彶浼氳瘽鐨勫璇濊褰曞垪琛�
+        getCurrentConversationApi({ id })
+          .then(res => {
+            console.log('currentRes', res)
+            if (res.success && res.result) {
+              this.currentConversation.messages = res.result.map(item => {
+                return {
+                  id: item.id,
+                  role: item.aiType === 2 ? 'user' : 'assistant',
+                  content: item.aiType === 2 ? item.problem : item.answer
+                }
+              })
+              this.scrollToConversationContainerBottom()
+              this.currentConversation.id = res.result[0].parentId
+              if (this.isModelResponding) this.isModelResponding = false
+              if (res.result[res.result.length - 1].aiType === 2) {
+                console.log('瑙﹀彂鍚戞ā鍨嬫彁闂�', res.result)
+                this.askToLanguageModel()
+              }
+            }
+          })
+      },
+
+      addNewConversationByApi(params) {
+        this.inputQuestion = ''
+        // TODO 璋冪敤鍚庣鎺ュ彛淇濆瓨褰撳墠闂涓斿垱寤轰竴鏉″巻鍙蹭細璇濊褰曪紝鐒跺悗鍐嶉噸鏂拌皟鐢ㄨ幏鍙栧巻鍙茶褰曞垪琛ㄦ帴鍙e埛鏂板垪琛�
+        addNewConversationApi(params)
+          .then(res => {
+            if (res.success) {
+              switch (+params.aiType) {
+                case 1:
+                  this.getChatHistoryListByApi()
+                  this.$message.success('浼氳瘽鍒楄〃璁板綍' + res.message)
+                  break
+                case 2:
+                  this.getConversationByApi(this.activeHistoryIndex)
+                  this.$message.success('鏂板浼氳瘽鍐呭闂璁板綍' + res.message)
+                  break
+                case 3:
+                  this.getConversationByApi(this.activeHistoryIndex)
+                  // this.currentConversation.messages[this.currentConversation.messages.length - 1].content += '\n' + '瀵硅瘽缁撴潫'
+                  this.$message.success('鏂板浼氳瘽鍐呭绛旀璁板綍' + res.message)
+                  break
+              }
+            } else {
+              this.$message.error(res.message)
+            }
+          })
+      },
+
+      /* 鍚戞ā鍨嬫彁闂� */
+      sendQuestion(event) {
+        //鐩戞祴鏄惁鎸変笅shift閿�
+        if (!event.shiftKey) {
+          event.preventDefault()
+          if (this.isModelResponding) {
+            this.$message.error('璇风瓑寰呮満鍣ㄤ汉鍥炲鍚庡啀鍙戦�佸摝~')
+            return
+          }
+          if (!this.inputQuestion) {
+            this.$message.error('浣犳病鏈夎緭鍏ュ唴瀹瑰摝')
+            return
+          }
+
+          const params = {
+            problem: this.inputQuestion,
+            aiType: ''
+          }
+          if (this.isAtNewConversation) {
+            params.aiType = 1
+            params.parentId = ''
+          } else {
+            params.aiType = 2
+            params.parentId = this.activeHistoryIndex
+          }
+          this.addNewConversationByApi(params)
+        }
+      },
+
+      askToLanguageModel() {
+        const messages = JSON.parse(JSON.stringify(this.currentConversation))
+        const answer = {
+          id: '',
+          role: 'assistant',
+          content: ''
+        }
+        this.currentConversation.messages.push(answer)
+        let lastElement
+        this.$nextTick(() => {
+          const elementArr = document.querySelectorAll('.single-conversation')
+          lastElement = elementArr[elementArr.length - 1]
+          console.log('elementArr', elementArr)
+        })
+        console.log('beforeAnswerConversation', messages)
+        console.log('this.currentConversation', this.currentConversation)
+        this.isModelResponding = true
+        // 鍙戦�丳OST璇锋眰鍒版ā鍨嬶紝鑾峰彇鍝嶅簲娴�
+        askToLanguageModelApi(messages)
+          .then(async (response) => {
+            if (!response.body) return
+
+            const reader = response.body.pipeThrough(new TextDecoderStream()).getReader()
+            // const decoder = new TextDecoder()
+            // console.log(reader)
+            let discontinuousJsonArray = []
+            let isContinuous = null
+
+            while (true) {
+              const { value, done } = await reader.read()
+              if (done) {
+                const params = {
+                  parentId: this.activeHistoryIndex,
+                  answer: this.currentConversation.messages.find(item => !item.id && item.role === 'assistant').content,
+                  aiType: 3
+                }
+                this.addNewConversationByApi(params)
+                break
+              }
+
+              this.scrollToConversationContainerBottom()
+              const objectArray = parsePack(value)
+              // console.log('objectArray', objectArray)
+              if (Array.isArray(objectArray) && objectArray.length > 0) {
+                if (discontinuousJsonArray.length === 2) discontinuousJsonArray = []
+                objectArray.forEach(json => {
+                  if (!json.choices || json.choices.length === 0) {
+                    return
+                  }
+                  const text = json.choices[0].delta.content
+                  this.currentConversation.messages.find(item => !item.id && item.role === 'assistant').content += text
+                })
+                if (isContinuous) {
+                  discontinuousJsonArray = []
+                  isContinuous = null
+                }
+              }
+            }
+
+            // 閫掑綊鎵惧埌DOM涓嬫渶鍚庝竴涓厓绱犺妭鐐�
+            function parsePack(str) {
+              const pattern = /data:\s*(?!\[DONE\])(\{.*?\})\s*\n/g
+              const result = []
+              let match
+              while ((match = pattern.exec(str)) !== null) {
+                const jsonStr = match[1]
+                console.log('jsonStr', jsonStr)
+                try {
+                  const object = JSON.parse(jsonStr)
+                  if (object) result.push(object)
+                } catch (err) {
+                  console.log('err', err)
+                }
+              }
+
+              // 姝ゅ垽鏂鐞嗚繑鍥炵殑涓嶅畬鏁寸殑鏁扮粍
+              if (match = pattern.exec(str) === null) {
+                isContinuous = false
+                // console.log('str', str)
+                // 姝ゅ涓哄睆钄借繑鍥炲甫鏈塸ing瀛楃涓�
+                if (!str.includes('ping')) {
+                  discontinuousJsonArray.push(str)
+                  // 鍒ゆ柇鏉′欢涓�2鏄敱浜庝笉瀹屾暣鏁扮粍浠呯粡杩�2娆¤繑鍥炲�煎氨鍙互鎷兼帴瀹屾暣锛屼絾杩樻槸涓嶅簲璇ョ敤鏁板瓧浣滀负鍒ゆ柇鏉′欢锛屼互闃蹭笉姝�2娆�
+                  if (discontinuousJsonArray.length === 2) {
+                    // console.log('discontinuousJsonArray', discontinuousJsonArray[0], '---', discontinuousJsonArray[1])
+                    const discontinuousMatch = pattern.exec(discontinuousJsonArray[0] + discontinuousJsonArray[1])
+                    discontinuousJsonArray = [JSON.parse(discontinuousMatch[1])]
+                    isContinuous = true
+                    return discontinuousJsonArray
+                  }
+                }
+              }
+              return result
+            }
+          })
+          .catch(error => {
+            console.error('璇锋眰澶辫触:', error)
+          })
       },
 
       /* 鍒囨崲鑷冲綋鍓嶇偣鍑讳細璇� */
-      switchToCurrentConversation(record, index) {
+      switchToCurrentConversation(record, event = {}) {
+        if (record.id === this.activeHistoryIndex) return // 閬垮厤閲嶅鐐瑰嚮
+        if (event.target && event.target.type === 'checkbox') return //鐐瑰嚮澶氶�夋寜閽伩鍏嶄紶閫掔粰姝や簨浠�
         if (record.inputVisible) return // 褰撴潯浼氳瘽姝e湪琚慨鏀规椂鍐嶆鐐瑰嚮鏈潯浼氳瘽鏃犲弽棣�
-        this.activeHistoryIndex = record.id
-        this.currentConversation = this.chatHistoryList[index]
+        // 鍏抽棴鐐瑰嚮缂栬緫鎸夐挳鍚庣殑杈撳叆妗�
+        if (this.editingHistoryIndex !== null && this.editingHistoryIndex !== record.id) this.cancelEditConversationTitle(this.chatHistoryList.find(item => item.id === this.editingHistoryIndex))
+        // 鍏抽棴鎵�鏈夌‘璁ゅ垹闄ゅ脊绐�
+        if (this.deletingHistoryIndex !== null) this.cancelDeleteConversation(this.chatHistoryList.find(item => item.id === this.deletingHistoryIndex))
+        if (this.isAtNewConversation) this.isAtNewConversation = false // 濡傛灉鍦ㄥ垱寤烘柊瀵硅瘽鐣岄潰鍒欏皢鏂板璇濈晫闈㈠叧闂�
+        if (this.isModelResponding) return // 妯″瀷鍥炵瓟鏈熼棿绂佹鍒囨崲浼氳瘽
 
-        if (this.editingHistoryIndex !== null) {
-          this.chatHistoryList.find(item => item.id === this.editingHistoryIndex).inputVisible = false
-          this.editingHistoryIndex = null
+        this.activeHistoryIndex = record.id
+        this.getConversationByApi(record.id)
+      },
+
+      /* 鐐瑰嚮鍏ㄩ�夋寜閽悗鏀瑰彉鍕鹃�夊悗瑙﹀彂 */
+      allHistoryCheckedChange(event) {
+        // event.target.indeterminate = true
+        if (event.target.checked) {
+          this.checkedConversationIdList = this.chatHistoryList.map(item => item.id)
+        } else {
+          this.checkedConversationIdList = []
+        }
+        console.log('瑙﹀彂鍏ㄩ��', this.checkedConversationIdList)
+      },
+
+      /* 鐐瑰嚮鍘嗗彶璁板綍涓閫夋鏀瑰彉鍕鹃�夊悗瑙﹀彂 */
+      singleHistoryCheckedChange() {
+        // 浠呭湪鎵归噺鍒犻櫎灞曞紑鏃惰幏鍙栧埌
+        const selectAllCheckbox = document.getElementById('select-all-checkbox')
+
+        if (this.checkedConversationIdList.length > 0) {
+          if (this.checkedConversationIdList.length !== this.chatHistoryList.length) {
+            console.log('瑙﹀彂鏈叏閫変腑', selectAllCheckbox.indeterminate)
+            this.$nextTick(() => selectAllCheckbox.indeterminate = true)
+          } else {
+            console.log('瑙﹀彂鍏ㄨ閫変腑', selectAllCheckbox)
+            // document.getElementById('select-all-checkbox').indeterminate = false
+            this.$nextTick(() => {
+              selectAllCheckbox.indeterminate = false
+              selectAllCheckbox.checked = true
+            })
+          }
+        } else {
+          selectAllCheckbox.indeterminate = false
+          selectAllCheckbox.checked = false
         }
       },
 
-      editConversationTitle(record, index) {
-        // 浠呭紑鍚渶鍚庝竴娆$偣鍑荤紪杈戞寜閽悗鐨勮緭鍏ユ
-        if (this.editingHistoryIndex !== null && this.editingHistoryIndex !== record.id) this.chatHistoryList.find(item => item.id === this.editingHistoryIndex).inputVisible = false
-        // 杩涘叆缂栬緫鍚庡叧闂墍鏈夌‘璁ゅ垹闄ゅ脊绐�
-        if (this.deletingHistoryIndex !== null) this.cancelDeleteConversation(this.chatHistoryList.find(item => item.id === this.deletingHistoryIndex))
-        console.log('杩涘叆淇敼', record)
-        this.editingHistoryIndex = record.id
-        record.inputVisible = true
-        this.editedConversationTitle = record.title
-        this.$nextTick(() => {
-          document.getElementById('edit-input').focus()
+      /* 鐐瑰嚮鎵归噺鍒犻櫎浼氳瘽鍥炬爣鏃跺睍寮�鎵归噺绠$悊鍖哄煙 */
+      expandDeleteBatchContainer() {
+        if (this.chatHistoryList.length === 0) return
+        this.isDeletingBatch = true
+      },
+
+      /* 鐐瑰嚮绾㈣壊鍥炬爣鎵归噺鍒犻櫎鎸夐挳鍚庤Е鍙� */
+      deleteBatchConversation() {
+        if (!this.checkedConversationIdList.length) return
+        if (this.isModelResponding) return // 妯″瀷鍥炵瓟鏈熼棿绂佹鍒犻櫎浼氳瘽
+        this.deleteBatchPopVisible = !this.deleteBatchPopVisible
+      },
+
+      /* 鐐瑰嚮鏂囨。绌虹櫧鍏抽棴鎵归噺鍒犻櫎popover */
+      handleDocumentClick(e) {
+        const popover = document.querySelector('.delete-batch-popover')
+        const button = document.getElementById('delete-batch-button')
+        if (popover && !popover.contains(e.target) && !button.contains(e.target)) this.deleteBatchPopVisible = false
+      },
+
+      /* 纭鎵归噺鍒犻櫎瀵硅瘽 */
+      confirmDeleteBatchConversation() {
+        if (this.checkedConversationIdList.includes(this.activeHistoryIndex)) this.createNewConversation()
+        this.chatHistoryList = this.chatHistoryList.filter(item => !this.checkedConversationIdList.includes(item.id))
+        this.$message.success('鍒犻櫎鎴愬姛')
+        const timer = setTimeout(() => {
+          this.cancelDeleteBatchConversation()
+          clearTimeout(timer)
         })
       },
 
-      deleteConversation(record, index) {
+      /* 鍙栨秷鎵归噺鍒犻櫎浼氳瘽鍔熻兘 */
+      cancelDeleteBatchConversation() {
+        this.deleteBatchPopVisible = false
+        this.isDeletingBatch = false
+        this.checkedConversationIdList = []
+      },
+
+      /* 鐐瑰嚮缂栬緫浼氳瘽鏍囬鎸夐挳鏃惰Е鍙� */
+      editConversationTitle(record) {
+        // 浠呭紑鍚渶鍚庝竴娆$偣鍑荤紪杈戞寜閽悗鐨勮緭鍏ユ
+        if (this.editingHistoryIndex !== null && this.editingHistoryIndex !== record.id) this.cancelEditConversationTitle(this.chatHistoryList.find(item => item.id === this.editingHistoryIndex))
+        // 杩涘叆缂栬緫鍚庡叧闂墍鏈夌‘璁ゅ垹闄ゅ脊绐�
+        if (this.deletingHistoryIndex !== null) this.cancelDeleteConversation(this.chatHistoryList.find(item => item.id === this.deletingHistoryIndex))
+
+        this.editingHistoryIndex = record.id
+        record.inputVisible = true
+        this.editedConversationTitle = record.problem
+        this.$nextTick(() => document.getElementById('edit-input').focus())
+      },
+
+      /* 鐐瑰嚮鍗曚釜鍒犻櫎浼氳瘽鎸夐挳鏃惰Е鍙� */
+      deleteConversation(record) {
         // 鐐瑰嚮鍒犻櫎鎸夐挳鏃跺叧闂墍鏈夋鍦ㄧ紪杈戠殑杈撳叆妗�
         if (this.editingHistoryIndex !== null) {
           this.cancelEditConversationTitle(this.chatHistoryList.find(item => item.id === this.editingHistoryIndex))
           return
         }
         // 浠呭彲浣跨敤鏈�鍚庝竴娆$偣鍑诲垹闄ゆ寜閽殑鍔熻兘
-        if (this.deletingHistoryIndex !== null && this.deletingHistoryIndex !== record.id) this.chatHistoryList.find(item => item.id === this.deletingHistoryIndex).deletePopVisible = false
-        record.deletePopVisible = true
+        if (this.deletingHistoryIndex !== null && this.deletingHistoryIndex !== record.id) this.cancelDeleteConversation(this.chatHistoryList.find(item => item.id === this.deletingHistoryIndex))
+        if (this.isModelResponding) return // 妯″瀷鍥炵瓟鏈熼棿绂佹鍒犻櫎浼氳瘽
+        record.deletePopVisible = !record.deletePopVisible
         this.deletingHistoryIndex = record.id
       },
 
-      confirmEditConversationTitle(record) {
+      /* 纭缂栬緫浼氳瘽鏍囬 */
+      confirmEditConversationTitle(record, event) {
+        // TODO 璋冪敤缂栬緫浼氳瘽鎺ュ彛骞堕噸鏂拌幏鍙栧巻鍙蹭細璇濇暟鎹�
         record.title = this.editedConversationTitle
-        record.inputVisible = false
-        this.editingHistoryIndex = null
-        this.editedConversationTitle = ''
+        this.cancelEditConversationTitle(record, event)
       },
 
+      /* 纭鍒犻櫎浼氳瘽鏃惰Е鍙� */
       confirmDeleteConversation(record, index) {
-        this.chatHistoryList = this.chatHistoryList.filter(item => item.id !== this.deletingHistoryIndex)
-
-        if (this.chatHistoryList.length > 0) {
-          // 鍒ゆ柇褰撳墠浼氳瘽鏄笉鏄鍒犻櫎鐨勪細璇�
-          // TODO 鐢变簬鐩墠娌℃湁鍞竴鏍囪瘑ID锛屾殏鏃朵娇鐢ㄥ巻鍙茶褰曢泦鍚堥暱搴︿綔涓篒D浣跨敤锛屽悗鏈熷繀椤昏皟鏁�
-          if (this.activeHistoryIndex === record.id) {
-            if (this.chatHistoryList[index]) {
-              console.log('鍒犻櫎闈炴渶鍚庝竴鏉�')
-              this.currentConversation = this.chatHistoryList[index]
-              this.activeHistoryIndex = this.chatHistoryList[index].id
-            } else {
-              console.log('鍒犻櫎鏈�鍚庝竴鏉�')
-              this.currentConversation = this.chatHistoryList[this.chatHistoryList.length - 1]
-              this.activeHistoryIndex = this.chatHistoryList[this.chatHistoryList.length - 1].id
+        deleteSingleChatHistoryApi({ id: record.id })
+          .then(res => {
+            if (res.success) {
+              if (this.chatHistoryList.length !== 1) {
+                // 鍒ゆ柇褰撳墠浼氳瘽鏄笉鏄鍒犻櫎鐨勪細璇�
+                console.log('record', record)
+                console.log('activeHistoryIndex', this.activeHistoryIndex)
+                if (this.activeHistoryIndex === record.id) {
+                  if (index !== 0) {
+                    console.log('鍒犻櫎闈炵涓�鏉¤褰�')
+                    this.switchToCurrentConversation(this.chatHistoryList[index - 1])
+                  } else {
+                    console.log('鍒犻櫎绗竴鏉¤褰�')
+                    this.switchToCurrentConversation(this.chatHistoryList[index + 1])
+                  }
+                }
+              } else {
+                console.log('鍒犻櫎鏈�鍚庝竴鏉¤褰�')
+                this.activeHistoryIndex = null
+              }
+              record.deletePopVisible = false
+              this.deletingHistoryIndex = null
+              this.getChatHistoryListByApi()
+              this.$message.success(res.message)
             }
-          }
-        } else {
-          console.log('鍒犻櫎鍓嶅彧鏈変竴鏉�')
-          this.currentConversation = {}
-          this.activeHistoryIndex = null
-        }
-        this.deletingHistoryIndex = null
-        this.$message.success('鍒犻櫎鎴愬姛锛�')
+          })
+          .catch(err => {
+            this.$message.error(err.message)
+          })
       },
 
-      cancelEditConversationTitle(record) {
+      /* 鍙栨秷缂栬緫浼氳瘽鏍囬鏃惰Е鍙� */
+      cancelEditConversationTitle(record, event) {
+        // 澶卞幓鐒︾偣浜嬩欢鏃惰嫢鐐瑰嚮鐨勫厓绱犳槸纭缂栬緫鎸夐挳鍒欎笉杩涜澶卞幓鐒︾偣浜嬩欢锛岀洿鎺ヨ繘鍏ョ‘璁ょ紪杈戜簨浠�
+        if (event.relatedTarget && event.relatedTarget.className === 'anticon anticon-check') return
         record.inputVisible = false
         this.editingHistoryIndex = null
       },
 
-      cancelDeleteConversation(record) {
+      /* 鍙栨秷鍒犻櫎浼氳瘽鏃惰Е鍙� */
+      cancelDeleteConversation(record, event) {
+        // 澶卞幓鐒︾偣浜嬩欢鏃惰嫢鐐瑰嚮鐨勫厓绱犳槸纭鍒犻櫎鎸夐挳鍒欎笉杩涜澶卞幓鐒︾偣浜嬩欢锛岀洿鎺ヨ繘鍏ョ‘璁ゅ垹闄や簨浠�
+        if (event && event.relatedTarget && event.relatedTarget.id === 'delete-conversation-button') return
         record.deletePopVisible = false
         this.deletingHistoryIndex = null
       },
@@ -261,55 +628,30 @@
         return getFileAccessHttpUrl(this.avatar())
       },
 
-      /* 鍚戞ā鍨嬫彁闂� */
-      sendQuestion(e) {
-        //鐩戞祴鏄惁鎸変笅shift閿�
-        if (!e.shiftKey) {
-          e.preventDefault()
-          if (this.isResponding) {
-            this.$message.error('璇风瓑寰呮満鍣ㄤ汉鍥炲鍚庡啀鍙戦�佸摝~')
-            return
-          }
-
-          if (!this.inputQuestion) {
-            this.$message.error('浣犳病鏈夎緭鍏ュ唴瀹瑰摝')
-            return
-          }
-
-          const newQuestion = {
-            role: 'user',
-            content: this.inputQuestion
-          }
-          this.currentConversation.messages.push(newQuestion)
-
-          this.isResponding = true
-          const response = {
-            role: 'assistant',
-            content: '杩欎釜闂鎴戜篃涓嶅お娓呮'
-          }
-          setTimeout(() => {
-            this.currentConversation.messages.push(response)
-            this.inputQuestion = ''
-            this.isResponding = false
-            this.$nextTick(() => {
-              this.conversationContainer.scrollTo({ top: 9999999999999999999999999999, behavior: 'smooth' })
-            })
-          }, 1000)
-        }
+      scrollToConversationContainerBottom(scrollBehavior = 'auto') {
+        this.$nextTick(() => {
+          this.conversationContainer.scrollTo({
+            top: this.conversationContainer.scrollHeight,
+            behavior: scrollBehavior
+          })
+        })
       }
     }
   }
 </script>
 
 <style scoped lang="less">
-  @background: rgba(255, 255, 255, .8);
+  @main-container-background: rgba(255, 255, 255, .7);
   @container-border-radius: 12px;
   @container-padding: 10px;
-  @single-history-border: 3px solid #ABC0CC;
-  @single-conversation-border: 1px solid #7295AB;
+  @single-history-edit-border: 3px solid #ABC0CC;
+  @single-history-hover-background: #f1f1f1;
+  @single-history-active-background: #e5ebed;
   @input-container-border: 3px solid #B8CAD5;
-  @conversation-content-container-box-shadow: 2px 2px 10px 0px #eeeeee;
-  @input-container-box-shadow: 2px 2px 10px 0px #7295AB;
+  @user-question-background: #e5ebed;
+  @assistant-answer-background: #F0F5F5;
+  @conversation-content-container-box-shadow: 2px 2px 10px 0px #ddd;
+  @input-container-box-shadow: 2px 2px 10px 0px #ABC0CC;
 
   .page-container {
     display: flex;
@@ -317,6 +659,7 @@
     align-items: center;
     font-size: 18px;
     height: 100%;
+    font-family: ali_r_main;
 
     .outer-container {
       width: 100%;
@@ -328,7 +671,7 @@
       .left-container {
         width: 20%;
         height: 100%;
-        background-color: @background;
+        background-color: @main-container-background;
         border-radius: @container-border-radius;
         padding: @container-padding;
         margin-right: 25px;
@@ -336,25 +679,168 @@
         flex-direction: column;
         justify-content: space-between;
 
-        .chat-history-container {
-          height: 582px;
-          overflow: auto;
+        .logo-container {
+          height: 80px;
+          img {
+            width: 100%;
+            height: 100%;
+          }
+        }
+
+        .manage-history-container {
+          margin: 20px 0 15px;
+          display: flex;
 
           & > div {
+            border: 1px solid #d9d9d9;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            cursor: pointer;
+            color: #585258;
+            transition: box-shadow .2s ease-in-out;
+
+            &:hover {
+              box-shadow: @conversation-content-container-box-shadow;
+            }
+          }
+
+          .create-new-conversation {
+            margin-right: 10px;
+
+            &.create-history-container-active {
+              border: 1px solid transparent;
+              background-color: @single-history-active-background;
+
+              &:hover {
+                box-shadow: none;
+              }
+            }
+
+            &.fold-create-history-container {
+              flex: none;
+              width: 40px;
+              height: 40px;
+              border-radius: 50%;
+            }
+
+            &.expand-create-history-container {
+              flex: 1;
+              height: 100%;
+              border-radius: 20px;
+
+              .anticon {
+                margin-right: 10px;
+              }
+            }
+
+            &.disable-expand {
+              cursor: not-allowed;
+              &:hover {
+                box-shadow: none;
+              }
+            }
+          }
+
+          .delete-batch-container {
+            &.expand-delete-batch-container {
+              flex: 1;
+              border-radius: 20px;
+              cursor: default;
+              box-shadow: none;
+
+              .expand-delete-batch-inner {
+                display: flex;
+                justify-content: space-evenly;
+                align-items: center;
+                height: 100%;
+                width: 100%;
+
+                & > div {
+                  display: flex;
+                  align-items: center;
+
+                  &.select-all-button {
+                    height: 100%;
+                  }
+
+                  &.able-delete-button {
+                    color: #D9737A;
+                  }
+
+                  &.disable-delete-button {
+                    color: #bbb;
+                    cursor: not-allowed !important;
+                  }
+
+                  &:not(.split-line) {
+                    cursor: pointer;
+                  }
+
+                  .anticon {
+                    margin-right: 10px;
+                  }
+
+                  &.split-line {
+                    width: 1px;
+                    background-color: #000;
+                    height: 50%;
+                  }
+                }
+              }
+            }
+
+            &.fold-delete-batch-container {
+              width: 40px;
+              height: 40px;
+              border-radius: 50%;
+            }
+
+            &.disable-expand {
+              cursor: not-allowed;
+              &:hover {
+                box-shadow: none;
+              }
+            }
+          }
+        }
+
+        .chat-history-container {
+          flex: 1;
+          overflow: auto;
+
+          .ant-empty {
+            height: 100%;
+            display: flex;
+            flex-direction: column;
+            justify-content: center;
+          }
+
+          & > div:not(.ant-empty) {
             border: 3px solid transparent;
             border-radius: 10px;
             padding: 10px 20px;
             cursor: pointer;
+            display: flex;
+            align-items: center;
 
             & > span {
               display: flex;
               justify-content: space-between;
+              position: relative;
+              flex: 1;
 
               .conversation-title {
                 flex: 1;
-                white-space: nowrap;
-                text-overflow: ellipsis;
                 overflow: hidden;
+                white-space: nowrap;
+              }
+
+              .hover-icon-container {
+                // display锛歯one鐨勬秷澶辨柟寮忎細璁╃偣鍑诲垹闄ゅ脊鍑虹殑popover鍗$墖鏃犳硶鐐瑰嚮绌虹櫧澶勬秷澶�
+                opacity: 0;
+                position: absolute;
+                right: 0;
               }
 
               .icon-container {
@@ -365,12 +851,12 @@
                   margin-left: 5px;
 
                   &:hover {
-                    background-color: rgba(0, 0, 0, .2);
+                    background-color: rgba(0, 0, 0, .1);
                   }
                 }
               }
 
-              input {
+              #edit-input {
                 flex: 1;
                 height: 100%;
                 border: none;
@@ -386,12 +872,161 @@
             }
 
             &:hover {
-              background-color: #eee;
+              background-color: @single-history-hover-background;
+              .icon-container {
+                opacity: 1;
+                background-color: @single-history-hover-background;
+              }
             }
 
             &.single-history-active {
-              // border: @single-history-border;
-              background-color: #e5ebed;
+              background-color: @single-history-active-background;
+              &:hover {
+                .icon-container {
+                  background-color: @single-history-active-background;
+                }
+              }
+            }
+
+            &.input-visible-class {
+              background-color: @main-container-background;
+              border: @single-history-edit-border;
+              &:hover {
+                .icon-container {
+                  background-color: transparent;
+                }
+              }
+
+            }
+
+            &.disable-switch {
+              cursor: not-allowed;
+
+              &:not(.single-history-active):hover {
+                background-color: transparent;
+              }
+            }
+          }
+        }
+
+        /deep/ .ant-popover {
+          padding-bottom: 10px;
+
+          .ant-popover-arrow {
+            display: none;
+          }
+
+          .ant-popover-inner {
+            border-radius: @container-border-radius;
+            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
+
+            .ant-popover-inner-content {
+              padding: 12px 16px;
+
+              .popover-content {
+                font-size: 16px;
+                font-weight: bold;
+                display: flex;
+                flex-direction: column;
+                align-items: center;
+                font-family: ali_r_main;
+
+                & > div {
+                  width: 100%;
+                  display: flex;
+                  justify-content: space-evenly;
+                  margin-top: 10px;
+
+                  button {
+                    border: none;
+                    padding: 5px 30px;
+                    border-radius: 20px;
+                    font-weight: bold;
+                    cursor: pointer;
+                    outline: none;
+                  }
+
+                  .cancel-delete-button {
+                    background-color: #D9D9D9;
+                  }
+
+                  #delete-conversation-button, #confirm-delete-batch-button {
+                    background-color: #7295AB;
+                    color: #fff;
+                    font-weight: normal;
+                  }
+                }
+              }
+            }
+          }
+        }
+
+        .checkbox-custom {
+          height: 100%;
+          position: relative;
+          display: flex;
+          justify-content: flex-end;
+          align-items: center;
+          cursor: pointer;
+          -webkit-user-select: none;
+          -moz-user-select: none;
+          -ms-user-select: none;
+          user-select: none;
+
+          input {
+            position: absolute;
+            opacity: 0;
+            cursor: pointer;
+            height: 0;
+            width: 0;
+
+            &:checked ~ .check-mark {
+              background-color: #7295AB;
+              border: none;
+
+              &:after {
+                display: block;
+              }
+            }
+
+            &:indeterminate ~ .check-mark {
+              background-color: #fff;
+              border: 1px solid #818181;
+
+              &:after {
+                display: block;
+                width: 10px;
+                height: 3px;
+                background: #818181;
+                border-radius: 12px;
+              }
+            }
+          }
+
+          .check-mark {
+            position: relative;
+            height: 16px;
+            width: 16px;
+            background-color: transparent;
+            border: 1px solid #818181;
+            border-radius: 50%;
+            margin-right: 10px;
+
+            &:after {
+              content: "";
+              position: absolute;
+              display: none;
+              top: 50%;
+              left: 50%;
+              width: 8px;
+              height: 8px;
+              border-radius: 50%;
+              background: #fff;
+              transform: translate(-50%, -50%);
+            }
+
+            &:hover {
+              border: 3px solid #7295AB;
             }
           }
         }
@@ -407,7 +1042,7 @@
 
         .conversation-container {
           height: 570px;
-          background-color: @background;
+          background-color: @main-container-background;
           padding: @container-padding*2;
           border-radius: @container-border-radius;
           margin-bottom: 25px;
@@ -422,10 +1057,21 @@
 
               &.user-question {
                 align-items: flex-end;
+                .conversation-content {
+                  background-color: @user-question-background;
+                  text-align: justify;
+                  text-align-last: left;
+                }
               }
 
               &.assistant-answer {
                 align-items: flex-start;
+
+                .conversation-content {
+                  background-color: @assistant-answer-background;
+                  text-align: justify;
+                  text-align-last: left;
+                }
               }
 
               .avatar {
@@ -438,12 +1084,11 @@
                 }
               }
 
-              .content {
+              .conversation-content {
                 max-width: 80%;
                 box-shadow: @conversation-content-container-box-shadow;
                 border-radius: @container-border-radius;
                 padding: @container-padding;
-                background-color: #e5ebed;
               }
             }
 
@@ -455,7 +1100,7 @@
 
         .input-container {
           flex: 1;
-          background-color: @background;
+          background-color: @main-container-background;
           border-radius: @container-border-radius;
           padding: @container-padding*2;
           border: 3px solid transparent;
@@ -495,6 +1140,5 @@
         }
       }
     }
-
   }
 </style>
\ No newline at end of file

--
Gitblit v1.9.3