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

---
 src/api/ai.js                                 |   29 +
 src/views/ai/ElectronicManual.vue             |  826 ++++++++++++++++++++++++++++++++
 vue.config.js                                 |   58 +-
 public/index.html                             |   28 
 src/assets/page/electronicManual/back.png     |    0 
 src/views/ai/LanguageModel.vue                |  517 ++++++++++++++------
 src/assets/page/electronicManual/search.png   |    0 
 src/assets/page/electronicManual/document.png |    0 
 8 files changed, 1,243 insertions(+), 215 deletions(-)

diff --git a/public/index.html b/public/index.html
index 83db454..9853e46 100644
--- a/public/index.html
+++ b/public/index.html
@@ -223,21 +223,35 @@
     /* 婊氬姩鏉′紭鍖� start */
     ::-webkit-scrollbar{
       width:0;
-      height:8px;
+      height:0;
     }
+    /*::-webkit-scrollbar-track{*/
+      /*background: #f6f6f6;*/
+      /*border-radius:20px;*/
+    /*}*/
+    /*::-webkit-scrollbar-thumb{*/
+      /*background: #e9e9e9;*/
+      /*border-radius:20px;*/
+    /*}*/
+    /*::-webkit-scrollbar-thumb:hover{*/
+      /*background: #cfcfcf;*/
+    /*}*/
+    /*::-webkit-scrollbar-corner {*/
+      /*background: #f6f6f6;*/
+    /*}*/
     ::-webkit-scrollbar-track{
-      background: #f6f6f6;
-      border-radius:2px;
+    background: transparent;
+    border-radius:20px;
     }
     ::-webkit-scrollbar-thumb{
-      background: #cdcdcd;
-      border-radius:2px;
+    background: transparent;
+    border-radius:20px;
     }
     ::-webkit-scrollbar-thumb:hover{
-      background: #747474;
+    background: transparent;
     }
     ::-webkit-scrollbar-corner {
-      background: #f6f6f6;
+    background: transparent;
     }
     /* 婊氬姩鏉′紭鍖� end */
   </style>
diff --git a/src/api/ai.js b/src/api/ai.js
new file mode 100644
index 0000000..76c6153
--- /dev/null
+++ b/src/api/ai.js
@@ -0,0 +1,29 @@
+import { axios } from '@/utils/request'
+import { getAction, deleteAction, putAction, postAction, httpAction } from '@/api/manage'
+
+/*------------------------------------------------鐢靛瓙璇存槑涔�-----------------------------------------------------*/
+export const getPdfImgApi = (params) => getAction('/ai/fileImg/list', params)
+
+export const getPdfDocumentApi = () => getAction('/ai/filePdf/filePdfList')
+
+export const getFurtherFilterImgApi = params => putAction('/ai/filePdf/findImgList', params)
+
+/*------------------------------------------------璇█澶фā鍨�-----------------------------------------------------*/
+export const askToLanguageModelApi = params => fetch('/chat/test_chat', {
+  method: 'POST',
+  headers: {
+    'Content-Type': 'application/json'
+  },
+  body: JSON.stringify(params)
+})
+
+export const addNewConversationApi = params => postAction('/ai/languageModel/addLanguage', params)
+
+export const getChatHistoryListApi = () => getAction('/ai/languageModel/languageTitles')
+
+export const getCurrentConversationApi = params => getAction('/ai/languageModel/languageProblems', params)
+
+export const deleteSingleChatHistoryApi = params => deleteAction('/ai/languageModel/deleteLanguage', params)
+
+
+
diff --git a/src/assets/page/electronicManual/back.png b/src/assets/page/electronicManual/back.png
new file mode 100644
index 0000000..16b7da2
--- /dev/null
+++ b/src/assets/page/electronicManual/back.png
Binary files differ
diff --git a/src/assets/page/electronicManual/document.png b/src/assets/page/electronicManual/document.png
new file mode 100644
index 0000000..a9b5e4a
--- /dev/null
+++ b/src/assets/page/electronicManual/document.png
Binary files differ
diff --git a/src/assets/page/electronicManual/search.png b/src/assets/page/electronicManual/search.png
new file mode 100644
index 0000000..1008174
--- /dev/null
+++ b/src/assets/page/electronicManual/search.png
Binary files differ
diff --git a/src/views/ai/ElectronicManual.vue b/src/views/ai/ElectronicManual.vue
index 86e9525..8fff29b 100644
--- a/src/views/ai/ElectronicManual.vue
+++ b/src/views/ai/ElectronicManual.vue
@@ -1,48 +1,832 @@
 <template>
-  <div>
-    鐢靛瓙璇存槑涔�
-    <a-card>
-      {{answer}}
-    </a-card>
-    <a-input-search placeholder="input search text" enter-button @search="onSearch"/>
+  <div class="page-container">
+    <!--鐢靛瓙璇存槑涔�-->
+    <div class="outer-container">
+      <!--宸︿晶闃呰妯″紡缂╃暐鍥惧尯鍩�-->
+      <div class="left-spin-container">
+        <a-spin :spinning="thumbnailSpinning" :delay="spinningDelayTime">
+          <a-icon slot="indicator" type="loading" spin/>
+        </a-spin>
+
+        <div class="left-container">
+          <div v-for="item in imgListConfig.records" :key="item.id" class="single-thumbnail-container"
+               :class="[item.id===activeImageId?'single-thumbnail-active':'']" :id="'thumbnail-container-'+item.id"
+               @click="activeCurrentThumbnail(item.id)">
+            <!--<img src="@/assets/page/electronicManual/document.png">-->
+            <div class="thumbnail-image-container">
+              <a-skeleton :loading="item.loading" :avatar="{shape:'square'}" :title="false" :paragraph="false" active/>
+              <img :id="'thumbnail-image-'+item.id" :data-src="getImgView(item.imgPath+item.imgEncodeName)"
+                   @load="imageLoadDone(item)" :style="{opacity:item.loading?0:1}">
+            </div>
+            <div>-{{item.pageNumber}}-</div>
+          </div>
+        </div>
+      </div>
+
+      <!--鍙充晶鍖哄煙-->
+      <div class="right-container">
+        <div class="right-top-container">
+          <div class="document-spin-container">
+            <a-spin :spinning="documentSpinning" :delay="spinningDelayTime">
+              <a-icon slot="indicator" type="loading" spin/>
+            </a-spin>
+
+            <!--鍙充笂鏂囨。鍖哄煙-->
+            <div class="document-container" @wheel="horizontalScroll">
+              <div v-for="item in documentList" :key="item.id" class="single-document-container"
+                   @click="activeCurrentDocument(item.id)"
+                   :class="[item.id===activeDocumentId?'single-document-active':'']">
+                <div><img src="@/assets/page/electronicManual/document.png"></div>
+                <div class="single-document-name">
+                  {{item.fileName?item.fileName.length>20?item.fileName.slice(0,20)+'..':item.fileName:'鏈懡鍚嶈鏄庝功'}}
+                </div>
+              </div>
+            </div>
+          </div>
+
+          <!--鍙充笂杈撳叆鏌ヨ鍖哄煙-->
+          <div class="search-container">
+            <div>鎮ㄥ綋鍓嶉�夋嫨鐨勭數鎺х郴缁熶负锛歺xx-xxxx-xxxxx&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;鏈哄簥鍨嬪彿涓�:xxx-xx</div>
+            <div class="input-container">
+              <input @keydown.enter="furtherFilter" placeholder="璇风敤涓�鍙ヨ瘽鎻忚堪鎮ㄥ綋鍓嶉亣鍒扮殑闂" v-model="inputQuestion"/>
+              <img src="@/assets/page/electronicManual/search.png" @click="furtherFilter"
+                   :style="{cursor:!thumbnailSpinning?'pointer':'not-allowed'}">
+            </div>
+          </div>
+        </div>
+
+        <!--鍙充笅鍖哄煙-->
+        <div class="right-bottom-spin-container">
+          <a-spin :spinning="largeImageSpinning" :delay="spinningDelayTime">
+            <a-icon slot="indicator" type="loading" spin/>
+          </a-spin>
+
+          <!--鍙充笅闃呰妯″紡鍖哄煙-->
+          <div class="right-bottom-container" :class="[isFurtherFilter?'further-filter-container':'']">
+            <template v-if="!isFurtherFilter">
+              <div v-for="item in imgListConfig.records" :key="item.id" class="single-largeImg-container"
+                   :id="'large-image-container-'+item.id">
+                <a-skeleton :loading="item.loading" :avatar="{shape:'square'}" :title="false"
+                            :paragraph="false" active/>
+                <img :id="'large-image-'+item.id" :data-src="getImgView(item.imgPath+item.imgEncodeName)"
+                     @load="imageLoadDone(item)" :style="{opacity:item.loading?0:1}">
+                <!--<img src="@/assets/page/electronicManual/document.png">-->
+              </div>
+            </template>
+
+            <!--鍙充笅娣卞眰杩囨护鍖哄煙-->
+            <template v-else>
+              <div v-for="item in furtherFilterImgList" :key="item.id" class="single-filterImg-container"
+                   @click="locateToDocument(item)">
+                <!--<img src="@/assets/page/electronicManual/document.png">-->
+                <a-skeleton :loading="item.loading" :avatar="{shape:'square'}" :title="false"
+                            :paragraph="false" active/>
+                <img :id="'filter-image-'+item.id" :data-src="getImgView(item.imgPath+item.imgEncodeName)"
+                     @load="imageLoadDone(item)" :style="{opacity:item.loading?0:1}"
+                >
+              </div>
+              <img src="@/assets/page/electronicManual/back.png" id="back-to-largeImg" @click="cancelFurtherFilter"
+                   v-if="!largeImageSpinning">
+            </template>
+          </div>
+        </div>
+
+      </div>
+    </div>
   </div>
 </template>
 
 <script>
-  import { getDataAfterSearchByApi } from '@/api/AI'
+  import { getPdfImgApi, getPdfDocumentApi, getFurtherFilterImgApi } from '@/api/ai'
+  import { getFileAccessHttpUrl } from '@/api/manage'
+  import { message } from 'ant-design-vue'
+
+  message.config({
+    maxCount: 1,
+    duration: 2
+  })
 
   export default {
     name: 'ElectronicManual',
     components: {},
     data() {
       return {
-        answer:''
+        rightBottomContainer: null,
+        activeDocumentId: null,
+        activeImageId: null,
+        inputQuestion: '',
+        isFurtherFilter: false,
+        beforeFilterActiveDocumentId: null,
+        beforeFilterActiveImageId: null,
+        documentList: [],
+        // documentList: [
+        //   {
+        //     id: 1,
+        //     fileName: '鏈哄簥鎵嬪唽1'
+        //   },
+        //   {
+        //     id: 2,
+        //     fileName: '鏈哄簥鎵嬪唽2'
+        //   },
+        //   {
+        //     id: 3,
+        //     fileName: '鏈哄簥鎵嬪唽3'
+        //   },
+        //   {
+        //     id: 4,
+        //     fileName: '鏈哄簥鎵嬪唽4'
+        //   },
+        //   {
+        //     id: 5,
+        //     fileName: '鏈哄簥鎵嬪唽5'
+        //   },
+        //   {
+        //     id: 6,
+        //     fileName: '鏈哄簥鎵嬪唽6'
+        //   },
+        //   {
+        //     id: 7,
+        //     fileName: '鏈哄簥鎵嬪唽7'
+        //   },
+        //   {
+        //     id: 8,
+        //     fileName: '鏈哄簥鎵嬪唽8'
+        //   },
+        //   {
+        //     id: 9,
+        //     fileName: '鏈哄簥鎵嬪唽9'
+        //   },
+        //   {
+        //     id: 10,
+        //     fileName: '鏈哄簥鎵嬪唽10'
+        //   }
+        // ],
+        imgListParams: {
+          pageNo: 1,
+          pageSize: 20
+        },
+        imgListConfig: {
+          records: []
+        },
+        scrollToMarginConfig: {
+          hasScrollToTopCount: 0,
+          hasScrollToBottomCount: 0
+        },
+        thumbnailSpinning: false,
+        documentSpinning: false,
+        largeImageSpinning: false,
+        spinningDelayTime: 500,
+        furtherFilterImgListParams: {},
+        furtherFilterImgList: []
       }
     },
+    created() {
+      this.getPdfDocumentByApi()
+    },
+    mounted() {
+      this.leftContainer = document.querySelector('.left-container')
+      this.rightBottomContainer = document.querySelector('.right-bottom-container')
+      this.handleScrollEventSwitch(true)
+    },
     methods: {
-      onSearch() {
-        const param = {
-          'id': '683a65fd-8feb-4446-ad32-714c4785f667',
-          'messages': [
-            {
-              'role': 'user',
-              'content': '缁欐垜璁蹭釜鏁呬簨锛�'
+      /* 璋冪敤鎺ュ彛鑾峰彇pdf鏂囨。 */
+      getPdfDocumentByApi() {
+        this.documentSpinning = true
+        getPdfDocumentApi()
+          .then(res => {
+            if (res.success) {
+              this.documentList = res.result
+              this.documentSpinning = false
+              this.activeCurrentDocument(res.result[0].id)
             }
-          ],
-          'stream': false,
-          'max_tokens': 500
+          })
+      },
+
+      /**
+       * 鐐瑰嚮鏂囨。鍚庤Е鍙�
+       * @param pdfFileId 鏂囨。Id
+       * @param furtherFilterImageId 娣卞眰杩囨护妯″紡涓偣鍑荤殑鍥剧墖Id
+       */
+      activeCurrentDocument(pdfFileId, furtherFilterImageId = null, furtherFilterImagePageNumber = null) {
+        if (pdfFileId === this.activeDocumentId && !furtherFilterImageId) return
+        if (this.isFurtherFilter) {
+          this.isFurtherFilter = false
+          this.handleScrollEventSwitch(true)
         }
-        getDataAfterSearchByApi()
+        if (!furtherFilterImageId) this.scrollToTop()
+        this.imgListConfig = {}
+        this.activeDocumentId = pdfFileId
+
+        this.imgListParams.pageNo = 1
+        this.resetImgListScrollConfig()
+        if (furtherFilterImagePageNumber && furtherFilterImagePageNumber > this.imgListParams.pageSize) this.computeImgListPageNo(furtherFilterImagePageNumber)
+
+        const params = Object.assign({ pdfFileId }, this.imgListParams)
+        this.thumbnailSpinning = true
+        this.largeImageSpinning = true
+
+        getPdfImgApi(params)
+          .then(res => {
+            if (res.success) {
+              this.imgListConfig = res.result
+              this.imgListConfig.records = this.imgListConfig.records.map(item => {
+                return {
+                  ...item,
+                  loading: true
+                }
+              })
+              this.activeImageId = res.result.records[0].id
+              this.$nextTick(() => {
+                this.lazyLoadImgByIntersectionObserver(res.result.records, 'thumbnail-image', 4, furtherFilterImageId)
+                this.lazyLoadImgByIntersectionObserver(res.result.records, 'large-image', 1, furtherFilterImageId)
+              })
+              this.thumbnailSpinning = false
+              this.largeImageSpinning = false
+              // 濡傛灉鐐瑰嚮鐨勬槸娣卞眰杩囨护鐨勫浘鐗囧垯杩斿洖闃呰妯″紡骞舵斁澶ч�変腑鐨勫浘鐗�
+              if (furtherFilterImageId) this.$nextTick(() => this.activeCurrentThumbnail(furtherFilterImageId, true))
+            }
+          })
+      },
+
+      /**
+       * 鐐瑰嚮缂╃暐鍥惧悗瑙﹀彂鍦ㄥ彸渚ф斁澶х缉鐣ュ浘
+       * @param id 缂╃暐鍥綢d
+       * @param isFromFurtherFilter 鏄惁閫氳繃娣卞眰杩囨护妯″紡瑙﹀彂
+       */
+      activeCurrentThumbnail(id, isFromFurtherFilter = false) {
+        if (id === this.activeImageId && !isFromFurtherFilter) return
+        this.activeImageId = id
+        if (isFromFurtherFilter) this.scrollToImagePosition('thumbnail-container', true, 'left', 0)
+        this.scrollToImagePosition()
+      },
+
+      computeImgListPageNo(furtherFilterImagePageNumber) {
+        const integer = Math.floor(furtherFilterImagePageNumber / this.imgListParams.pageSize)
+        const remainder = furtherFilterImagePageNumber % this.imgListParams.pageSize
+        if (remainder !== 0) {
+          // 涓嶄负鏁存暟
+          this.imgListParams.pageNo = integer + 1
+        } else {
+          // 涓烘暣鏁�
+          this.imgListParams.pageNo = integer
+        }
+      },
+
+      /* 闃呰妯″紡涓嬬殑婊氬姩瑙﹂《搴曞埛鏂� */
+      lazyLoadData(event) {
+        if (this.thumbnailSpinning && this.largeImageSpinning) return // 鍔犺浇涓椂鍙栨秷璋冪敤
+        const containerScrollTop = event.target.scrollTop
+        const containerClientHeight = event.target.clientHeight
+        const containerScrollHeight = event.target.scrollHeight
+
+        if (containerScrollTop + containerClientHeight >= containerScrollHeight) {
+          console.log('瑙﹀簳')
+          this.scrollToMarginConfig.hasScrollToBottomCount++
+          const params = Object.assign({ pdfFileId: this.activeDocumentId }, this.imgListParams)
+          params.pageNo = params.pageNo + this.scrollToMarginConfig.hasScrollToBottomCount
+          this.thumbnailSpinning = true
+          this.largeImageSpinning = true
+          getPdfImgApi(params)
+            .then(res => {
+              if (res.success) {
+                const newImgList = res.result.records.map(item => {
+                  return {
+                    ...item,
+                    loading: true
+                  }
+                })
+                this.imgListConfig.records.push(...newImgList)
+                this.$nextTick(() => {
+                  this.lazyLoadImgByIntersectionObserver(newImgList, 'thumbnail-image', 4)
+                  this.lazyLoadImgByIntersectionObserver(newImgList, 'large-image', 1)
+                })
+                this.thumbnailSpinning = false
+                this.largeImageSpinning = false
+              }
+            })
+        } else if (containerScrollTop <= 0 && this.imgListConfig.records[0].pageNumber !== 1) {
+          console.log('瑙﹂《')
+          this.scrollToMarginConfig.hasScrollToTopCount++
+          const params = Object.assign({ pdfFileId: this.activeDocumentId }, this.imgListParams)
+          params.pageNo = params.pageNo - this.scrollToMarginConfig.hasScrollToTopCount
+          this.thumbnailSpinning = true
+          this.largeImageSpinning = true
+          getPdfImgApi(params)
+            .then(res => {
+              if (res.success) {
+                const newImgList = res.result.records.map(item => {
+                  return {
+                    ...item,
+                    loading: true
+                  }
+                })
+                this.imgListConfig.records.unshift(...newImgList)
+                this.$nextTick(() => {
+                  this.lazyLoadImgByIntersectionObserver(newImgList, 'thumbnail-image', 4)
+                  this.lazyLoadImgByIntersectionObserver(newImgList, 'large-image', 1)
+                })
+                event.target.scrollTo({ top: 1 }) // 瑙e喅瑙﹂《鍒锋柊鏁版嵁鍚庢粴鍔ㄦ潯鑷虫渶椤堕儴闂锛屼絾涓嶇煡閬撲负浠�涔�
+                this.thumbnailSpinning = false
+                this.largeImageSpinning = false
+              }
+            })
+        }
+      },
+
+      /* 寮�鍚繁灞傝繃婊ゆā寮� */
+      furtherFilter() {
+        if (this.thumbnailSpinning) return
+        if (!this.inputQuestion) {
+          this.$message.error('浣犳病鏈夎緭鍏ュ唴瀹瑰摝')
+          return
+        }
+
+        this.furtherFilterImgList = []
+        this.isFurtherFilter = true
+
+        // 寮�鍚繁灞傝繃婊ゆ椂鍙栨秷鏂囦欢閫変腑骞剁Щ闄ゆ粴鍔ㄥ埛鏂颁簨浠�
+        this.beforeFilterActiveDocumentId = this.activeDocumentId
+        this.activeDocumentId = null
+        this.imgListConfig = {}
+        this.handleScrollEventSwitch(false)
+
+        const params = Object.assign({}, this.furtherFilterImgListParams)
+        params.pdfFileId = '1823231131943862273'
+        params.pdfContent = this.inputQuestion
+        this.largeImageSpinning = true
+
+        getFurtherFilterImgApi(params)
           .then(res => {
             console.log('res', res)
-            this.answer = res.result
+            if (res.success) {
+              this.furtherFilterImgList = res.result.map(item => {
+                return {
+                  ...item,
+                  loading: true
+                }
+              })
+              this.largeImageSpinning = false
+              this.$nextTick(() => this.lazyLoadImgByIntersectionObserver(res.result, 'filter-image', 2))
+            }
           })
+      },
 
+      /* 鐐瑰嚮娣卞眰杩囨护涓殑鍥剧墖鍚庡畾浣嶈嚦鏂囦欢 */
+      locateToDocument(record) {
+        this.isFurtherFilter = false
+        this.handleScrollEventSwitch(true)
+        this.$nextTick(() => this.activeCurrentDocument(record.fileId, record.id, record.pageNumber))
+      },
+
+      /* 鐐瑰嚮杩斿洖鎸夐挳閫�鍑烘繁灞傝繃婊ゆā寮� */
+      cancelFurtherFilter() {
+        const beforeFilterActiveRecord = { fileId: this.beforeFilterActiveDocumentId, id: this.activeImageId }
+        this.locateToDocument(beforeFilterActiveRecord)
+        this.inputQuestion = ''
+      },
+
+      /**
+       * 婊氬姩鑷冲浘鐗囩浉搴斾綅缃《閮�
+       * @param activePreId 婊氬姩鑷虫煇鍏冪礌鐨処d鍓嶇紑
+       * @param isParentNodeOpenPosition 鐖跺厓绱犳槸鍚﹀紑鍚畾浣�
+       * @param parentNodePreClass 鐖跺厓绱犵被鍚嶅墠缂�
+       * @param marginValue 杈硅窛鍊� 姝e�间负top锛岃礋鍊间负bottom
+       */
+      scrollToImagePosition(activePreId = 'large-image-container', isParentNodeOpenPosition = true, parentNodePreClass = 'rightBottom', marginValue = 25) {
+        console.log(activePreId + '-' + this.activeImageId)
+        const activeLargeImageContainer = document.getElementById(activePreId + '-' + this.activeImageId)
+        console.log('activeLargeImageContainer', activeLargeImageContainer)
+        let scrollTop
+        if (isParentNodeOpenPosition) {
+          console.log('鐖跺厓绱犲紑鍚畾浣�')
+          scrollTop = activeLargeImageContainer.offsetTop - marginValue
+        } else {
+          console.log('鐖跺厓绱犳湭寮�鍚畾浣�')
+          scrollTop = activeLargeImageContainer.offsetTop - this[parentNodePreClass + 'Container'].offsetTop - marginValue
+        }
+
+        this[parentNodePreClass + 'Container'].scrollTo({ top: scrollTop })
+      },
+
+      /**
+       * 閲囩敤娴忚鍣ㄧ洃娴嬭�匒PI瀹炵幇鍥剧墖鎳掑姞杞�
+       * @param dataList 鍏冪礌瀵瑰簲鏁版嵁闆嗗悎鐢ㄦ潵鏄犲皠瀵瑰簲鍏冪礌
+       * @param targetPreClass 鐩爣鍏冪礌绫诲悕鍓嶇紑
+       * @param loadCount 鍔犺浇鏁伴噺
+       * @param furtherFilterImageId 娣卞眰杩囨护妯″紡涓偣鍑荤殑鍥剧墖Id
+       */
+      lazyLoadImgByIntersectionObserver(dataList, targetPreClass, loadCount, furtherFilterImageId = null) {
+        let hasLoadImageCount = 0
+        let observer = new IntersectionObserver(entries => {
+          const furtherFilterImageIndex = entries.findIndex(item => item.target.id === targetPreClass + '-' + furtherFilterImageId)
+          // 娣卞眰杩囨护閫変腑鍥剧墖鍚庡噺灏戞暟缁勫惊鐜鏁帮紝浠呭姞杞藉寘鍚�変腑鍥剧墖鐨勫彲瑙嗗浘鐗囨暟閲�
+          if (furtherFilterImageIndex > 0) entries = entries.slice(furtherFilterImageIndex)
+          entries = entries.slice(0, loadCount)
+          entries.forEach(item => {
+            if (item.isIntersecting) {
+              item.target.src = item.target.dataset.src
+              hasLoadImageCount++
+              observer.unobserve(item.target)
+            }
+          })
+          if (hasLoadImageCount === dataList.length) {
+            observer.disconnect()
+            observer = null
+          }
+        })
+        dataList.forEach(item => observer.observe(document.getElementById(targetPreClass + '-' + item.id)))
+      },
+
+      horizontalScroll(event) {
+        event.preventDefault()
+        const documentContainer = document.querySelector('.document-container')
+        // deltaY灞炴�ф槸榧犳爣婊氬姩婊氳疆婊氬姩涓�涓嬶紝椤甸潰婊氬姩鐨勮窛绂�
+        // Y鏄瀭鐩存柟鍚�
+        // 榧犳爣婊氳疆鍚戝墠婊氬姩涓�杞紝deltaY=-100锛涘悜鍚庢粴鍔ㄤ竴杞紝deltaY=100;
+        // deltaY鐨勫唴閮ㄥ疄鐜拌偗瀹氭槸鐩戝惉榧犳爣婊氳疆鏄悜鍓嶆粴鍔ㄨ繕鏄悜鍚庢粴鍔ㄤ簨浠讹紝鐒跺悗鏇存敼deltaY鐨勫�间负姝f垨鑰呰礋銆�
+        // 姘村钩婊氬姩涔熼渶瑕佹湁绫讳技deltaY杩欐牱鐨勭姸鎬侊紝鎵�浠ョ洿鎺ヤ娇鐢╠eltaY灏卞彲浠ヤ簡锛岃櫧鐒禮琛ㄧず鐨勬槸鍨傜洿鏂瑰悜鐨勬粴鍔ㄣ��
+        const deltaY = event.deltaY
+        // scrollLeft灞炴�ф槸鍏冪礌宸︿晶鐨勬粴鍔ㄨ窛绂伙紝閫氳繃鏀瑰彉杩欎釜灞炴�х殑鍊硷紝瀹炵幇姘村钩婊氬姩鐨勬晥鏋溿��
+        documentContainer.scrollLeft += deltaY
+      },
+
+      imageLoadDone(record) {
+        console.log('鍥剧墖鍔犺浇瀹屾垚')
+        record.loading = false
+      },
+
+      scrollToTop() {
+        console.log('瑙﹀彂鍥炲埌椤堕儴')
+        this.leftContainer.scrollTo({ top: 0 })
+        this.rightBottomContainer.scrollTo({ top: 0 })
+      },
+
+      /**
+       * 鍥剧墖棰勮
+       * @param text 鍥剧墖鍦板潃
+       */
+      getImgView(text) {
+        if (text && text.indexOf(',') > 0) {
+          text = text.substring(0, text.indexOf(','))
+        }
+        return getFileAccessHttpUrl(text)
+      },
+
+      handleScrollEventSwitch(isAddEvent) {
+        if (isAddEvent) {
+          this.leftContainer.addEventListener('scroll', this.lazyLoadData)
+          this.rightBottomContainer.addEventListener('scroll', this.lazyLoadData)
+        } else {
+          this.leftContainer.removeEventListener('scroll', this.lazyLoadData)
+          this.rightBottomContainer.removeEventListener('scroll', this.lazyLoadData)
+        }
+      },
+
+      resetImgListScrollConfig() {
+        this.scrollToMarginConfig.hasScrollToTopCount = 0
+        this.scrollToMarginConfig.hasScrollToBottomCount = 0
       }
+    },
+    beforeDestroy() {
+      this.handleScrollEventSwitch(false)
     }
   }
 </script>
 
-<style scoped>
+<style scoped lang="less">
+  @main-container-background: rgba(255, 255, 255, .7);
+  @container-border-radius: 12px;
+  @container-padding: 10px;
+  @single-history-edit-border: 3px solid #ABC0CC;
+  @single-history-hover-background: #f1f1f1;
+  @single-history-active-background: #e5ebed;
+  @input-container-border: 3px solid #B8CAD5;
+  @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;
+  @largeImg-container-box-shadow: 0 -3px 10px 0px #ddd;
+  @scrollbar-track-background: #f6f6f6;
+  @scrollbar-thumb-background: #e9e9e9;
+  @scrollbar-thumb-hover-background: #cfcfcf;
+  @scrollbar-corner-background: #f6f6f6;
 
+  .page-container {
+    display: flex;
+    justify-content: flex-end;
+    -webkit-justify-content: flex-end;
+    flex-direction: column;
+    align-items: center;
+    font-size: 18px;
+    height: 100%;
+    font-family: ali_r_main;
+
+    .outer-container {
+      width: 100%;
+      height: 850px;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+
+      .left-spin-container {
+        width: 10%;
+        height: 100%;
+        background-color: @main-container-background;
+        border-radius: @container-border-radius;
+        margin-right: 25px;
+        font-size: 16px;
+        position: relative;
+
+        .left-container {
+          width: 100%;
+          height: 100%;
+          padding: @container-padding;
+          display: flex;
+          flex-direction: column;
+          justify-content: space-between;
+          overflow: auto;
+
+          &::-webkit-scrollbar {
+            width: 8px;
+            height: 0;
+          }
+
+          .single-thumbnail-container {
+            padding: @container-padding;
+            cursor: pointer;
+            border-radius: @container-border-radius;
+
+            &:not(:last-child) {
+              margin-bottom: 20px;
+            }
+
+            img {
+              width: 100%;
+              height: 189px;
+            }
+
+            & > div:last-child {
+              margin-top: 10px;
+              text-align: center;
+            }
+
+            .thumbnail-image-container {
+              position: relative;
+            }
+
+            &:hover {
+              background-color: @single-history-hover-background;
+            }
+
+            &.single-thumbnail-active {
+              background-color: @single-history-active-background;
+            }
+
+            /deep/ .ant-skeleton {
+              margin: 0;
+              position: absolute;
+              width: 100%;
+              height: 189px;
+              .ant-skeleton-header {
+                padding: 0;
+                .ant-skeleton-avatar-lg {
+                  width: 100%;
+                  height: 189px;
+                  border-radius: @container-border-radius;
+                }
+              }
+            }
+          }
+
+        }
+      }
+
+      .right-container {
+        width: 80%;
+        height: 100%;
+        border-radius: @container-border-radius;
+        display: flex;
+        flex-direction: column;
+        justify-content: space-between;
+
+        .right-top-container {
+          height: 120px;
+          margin-bottom: 25px;
+          display: flex;
+          justify-content: center;
+
+          .document-spin-container {
+            width: 50%;
+            height: 100%;
+            font-size: 14px;
+            background-color: @main-container-background;
+            border-radius: @container-border-radius;
+            margin-right: 25px;
+            position: relative;
+
+            .document-container {
+              overflow-y: hidden;
+              overflow-x: auto;
+              display: flex;
+              align-items: center;
+              padding: @container-padding;
+
+              &::-webkit-scrollbar {
+                width: 0;
+                height: 8px;
+              }
+
+              .single-document-container {
+                width: 22%;
+                height: 100%;
+                cursor: pointer;
+                display: flex;
+                flex-direction: column;
+                justify-content: space-evenly;
+                align-items: center;
+                flex-shrink: 0;
+                border-radius: @container-border-radius;
+                padding: @container-padding;
+
+                img {
+                  width: 40px;
+                }
+
+                .single-document-name {
+                  width: 100%;
+                  text-align: center;
+                  /*overflow: hidden;*/
+                  /*white-space: nowrap;*/
+                  /*text-overflow: ellipsis;*/
+                }
+
+                &:not(:last-child) {
+                  margin-right: 4%;
+                }
+
+                &:hover {
+                  background-color: @single-history-hover-background;
+                }
+
+                &.single-document-active {
+                  background-color: @single-history-active-background;
+                }
+              }
+            }
+          }
+
+          .search-container {
+            flex: 1;
+            display: flex;
+            flex-direction: column;
+            justify-content: space-evenly;
+            align-items: center;
+
+            .input-container {
+              width: 100%;
+              display: flex;
+              background-color: transparent;
+              border: @single-history-edit-border;
+              border-radius: @container-border-radius;
+              padding: @container-padding;
+
+              input {
+                flex: 1;
+                outline: none;
+                background-color: transparent;
+                letter-spacing: 1px;
+                border: none;
+
+                &::placeholder {
+                  color: #9A9C9C;
+                }
+              }
+
+              img {
+                width: 32px;
+                cursor: pointer;
+              }
+            }
+          }
+        }
+
+        .right-bottom-spin-container {
+          width: 100%;
+          flex: 1;
+          background-color: @main-container-background;
+          border-radius: @container-border-radius;
+          position: relative;
+          overflow: hidden;
+
+          .right-bottom-container {
+            height: 100%;
+            background-color: @main-container-background;
+            border-radius: @container-border-radius;
+            overflow: auto;
+            text-align: center;
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+
+            .single-largeImg-container {
+              box-shadow: @largeImg-container-box-shadow;
+              width: 70%;
+              margin-top: 25px;
+              position: relative;
+            }
+
+            &.further-filter-container {
+              flex-direction: row;
+              justify-content: left;
+              flex-wrap: wrap;
+              padding: 12px 0;
+
+              .single-filterImg-container {
+                box-shadow: @largeImg-container-box-shadow;
+                width: 45%;
+                margin-top: 12px;
+                margin-bottom: 12px;
+                cursor: pointer;
+                position: relative;
+
+                &:nth-child(odd) {
+                  margin-left: 2%;
+                  margin-right: 2%;
+                }
+              }
+            }
+
+            /deep/ .ant-skeleton {
+              position: absolute;
+              top: 0;
+              left: 0;
+              right: 0;
+              bottom: 0;
+              margin: 0;
+              .ant-skeleton-header {
+                padding: 0;
+                .ant-skeleton-avatar-lg {
+                  width: 100%;
+                  height: 1640px;
+                }
+              }
+            }
+
+            img {
+              width: 100%;
+              height: 1640px;
+            }
+
+            #back-to-largeImg {
+              width: 3%;
+              height: auto;
+              position: absolute;
+              right: 2%;
+              top: auto;
+              cursor: pointer;
+            }
+
+            &::-webkit-scrollbar {
+              width: 8px;
+              height: 0;
+            }
+          }
+        }
+      }
+
+      .left-container, .document-container, .right-bottom-container {
+        &:hover {
+          &::-webkit-scrollbar-track {
+            background: @scrollbar-track-background;
+          }
+          &::-webkit-scrollbar-thumb {
+            background: @scrollbar-thumb-background;
+          }
+          &::-webkit-scrollbar-thumb:hover {
+            background: @scrollbar-thumb-hover-background;
+          }
+          &::-webkit-scrollbar-corner {
+            background: @scrollbar-corner-background;
+          }
+        }
+      }
+
+      /deep/ .ant-spin {
+        position: absolute;
+        top: 0;
+        left: 0;
+        right: 0;
+        bottom: 0;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        pointer-events: none;
+        z-index: 999;
+
+        .anticon {
+          font-size: 24px;
+        }
+      }
+    }
+  }
 </style>
\ No newline at end of file
diff --git a/src/views/ai/LanguageModel.vue b/src/views/ai/LanguageModel.vue
index e2d3666..36c4eb6 100644
--- a/src/views/ai/LanguageModel.vue
+++ b/src/views/ai/LanguageModel.vue
@@ -1,11 +1,15 @@
 <template>
   <div class="page-container">
     <div class="outer-container">
+      <!--宸︿晶鍘嗗彶浼氳瘽鍖哄煙-->
       <div class="left-container">
+        <!--logo鍖哄煙-->
         <div class="logo-container"><img src="@/assets/page/languageModel/logo.png"></div>
+
+        <!--鍔熻兘鎸夐敭鍖哄煙-->
         <div class="manage-history-container">
           <div @click="createNewConversation"
-               :class="[isAtNewConversation?'create-history-container-active':'',isDeletingBatch?'fold-create-history-container':'expand-create-history-container']"
+               :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>
@@ -38,7 +42,7 @@
                     </div>
                   </template>
                   <div @click="deleteBatchConversation" id="delete-batch-button"
-                       :class="[checkedConversationIdList.length?'able-delete-button':'disable-delete-button']">
+                       :class="[checkedConversationIdList.length&&!isModelResponding?'able-delete-button':'disable-delete-button']">
                     <a-icon type="delete"/>
                     <div>鍒犻櫎</div>
                   </div>
@@ -52,9 +56,18 @@
             </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':'']"
+               :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">
@@ -65,7 +78,6 @@
               <span class="check-mark"></span>
             </label>
 
-
             <a-popover placement="top" :visible="item.deletePopVisible" trigger="click"
                        :getPopupContainer="node=>node.parentNode">
               <template slot="content">
@@ -73,13 +85,14 @@
                   <div>鍒犻櫎鍚庢棤娉曟仮澶嶏紝鏄惁缁х画鍒犻櫎锛�</div>
                   <div>
                     <button class="cancel-delete-button" @click="cancelDeleteConversation(item,$event)">鍙栨秷</button>
-                    <button @click="confirmDeleteConversation(item,index)" id="delete-conversation-button">鍒犻櫎</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 hover-icon-container" v-show="!isDeletingBatch">
+                <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)"/>
@@ -102,28 +115,33 @@
         </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="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>
@@ -136,6 +154,16 @@
   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,
@@ -151,62 +179,22 @@
         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
         },
-        // currentConversation: {},
         isAtNewConversation: false,
         isDeletingBatch: false,
         activeHistoryIndex: null,
         editingHistoryIndex: null,
         deletingHistoryIndex: null,
-        iconVisible: false,
-        inputVisible: false,
         editedConversationTitle: '',
-        deletePopVisible: false,
         conversationContainer: null,
         inputQuestion: '',
-        textareaPlaceholder: 'Enter鍙戦�侊紝Shift+Enter鎹㈣',
+        textareaPlaceholder: '',
         textareaFocused: false,
-        isResponding: false
+        isModelResponding: false
       }
     },
 
@@ -219,40 +207,265 @@
             document.removeEventListener('click', this.handleDocumentClick)
           }
         }
+      },
+      isAtNewConversation: {
+        handler(val) {
+          if (val) {
+            this.textareaPlaceholder = '璇峰皾璇曢棶鎴戯細浣犳槸璋侊紵'
+          } else {
+            this.textareaPlaceholder = 'Enter鍙戦�侊紝Shift+Enter鎹㈣'
+          }
+        },
+        immediate: true
       }
     },
+    created() {
+      this.getChatHistoryListByApi()
+    },
     mounted() {
-      this.getConversationByApi()
       this.conversationContainer = document.querySelector('.conversation-container')
     },
     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
-        if (!this.chatHistoryList.length) this.textareaPlaceholder = '璇峰皾璇曢棶鎴戯細浣犳槸璋侊紵'
+      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.activeHistoryIndex !== null) this.activeHistoryIndex = null
-        this.textareaPlaceholder = '璇峰皾璇曢棶鎴戯細浣犳槸璋侊紵'
+      },
+
+      /* 璋冪敤鎺ュ彛鑾峰彇褰撳墠浼氳瘽璁板綍 */
+      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, event = {}) {
+        if (record.id === this.activeHistoryIndex) return // 閬垮厤閲嶅鐐瑰嚮
+        if (event.target && event.target.type === 'checkbox') return //鐐瑰嚮澶氶�夋寜閽伩鍏嶄紶閫掔粰姝や簨浠�
+        if (record.inputVisible) return // 褰撴潯浼氳瘽姝e湪琚慨鏀规椂鍐嶆鐐瑰嚮鏈潯浼氳瘽鏃犲弽棣�
+        // 鍏抽棴鐐瑰嚮缂栬緫鎸夐挳鍚庣殑杈撳叆妗�
+        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 // 妯″瀷鍥炵瓟鏈熼棿绂佹鍒囨崲浼氳瘽
+
+        this.activeHistoryIndex = record.id
+        this.getConversationByApi(record.id)
       },
 
       /* 鐐瑰嚮鍏ㄩ�夋寜閽悗鏀瑰彉鍕鹃�夊悗瑙﹀彂 */
@@ -263,7 +476,7 @@
         } else {
           this.checkedConversationIdList = []
         }
-        console.log('瑙﹀彂鍏ㄩ��', event.target.checked)
+        console.log('瑙﹀彂鍏ㄩ��', this.checkedConversationIdList)
       },
 
       /* 鐐瑰嚮鍘嗗彶璁板綍涓閫夋鏀瑰彉鍕鹃�夊悗瑙﹀彂 */
@@ -298,6 +511,7 @@
       /* 鐐瑰嚮绾㈣壊鍥炬爣鎵归噺鍒犻櫎鎸夐挳鍚庤Е鍙� */
       deleteBatchConversation() {
         if (!this.checkedConversationIdList.length) return
+        if (this.isModelResponding) return // 妯″瀷鍥炵瓟鏈熼棿绂佹鍒犻櫎浼氳瘽
         this.deleteBatchPopVisible = !this.deleteBatchPopVisible
       },
 
@@ -326,23 +540,6 @@
         this.checkedConversationIdList = []
       },
 
-      /* 鍒囨崲鑷冲綋鍓嶇偣鍑讳細璇� */
-      switchToCurrentConversation(record, event = {}) {
-        if (event.target && event.target.type === 'checkbox') return //鐐瑰嚮澶氶�夋寜閽伩鍏嶄紶閫掔粰姝や簨浠�
-        if (record.inputVisible) return // 褰撴潯浼氳瘽姝e湪琚慨鏀规椂鍐嶆鐐瑰嚮鏈潯浼氳瘽鏃犲弽棣�
-        // 鍏抽棴鐐瑰嚮缂栬緫鎸夐挳鍚庣殑杈撳叆妗�
-        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.activeHistoryIndex = record.id
-        this.currentConversation = this.chatHistoryList.find(item => item.id === record.id)
-        this.isAtNewConversation = false
-
-        // 鍒囨崲瀵硅瘽鏃舵洿鏀规彁闂緭鍏ユ鎻愮ず鏂囧瓧
-        if (record.messages.length > 0) this.textareaPlaceholder = 'Enter鍙戦�侊紝Shift+Enter鎹㈣'
-      },
-
       /* 鐐瑰嚮缂栬緫浼氳瘽鏍囬鎸夐挳鏃惰Е鍙� */
       editConversationTitle(record) {
         // 浠呭紑鍚渶鍚庝竴娆$偣鍑荤紪杈戞寜閽悗鐨勮緭鍏ユ
@@ -352,7 +549,7 @@
 
         this.editingHistoryIndex = record.id
         record.inputVisible = true
-        this.editedConversationTitle = record.title
+        this.editedConversationTitle = record.problem
         this.$nextTick(() => document.getElementById('edit-input').focus())
       },
 
@@ -365,42 +562,49 @@
         }
         // 浠呭彲浣跨敤鏈�鍚庝竴娆$偣鍑诲垹闄ゆ寜閽殑鍔熻兘
         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, event) {
+        // TODO 璋冪敤缂栬緫浼氳瘽鎺ュ彛骞堕噸鏂拌幏鍙栧巻鍙蹭細璇濇暟鎹�
         record.title = this.editedConversationTitle
         this.cancelEditConversationTitle(record, event)
       },
 
       /* 纭鍒犻櫎浼氳瘽鏃惰Е鍙� */
       confirmDeleteConversation(record, index) {
-        this.chatHistoryList = this.chatHistoryList.filter(item => item.id !== this.deletingHistoryIndex)
-
-        if (this.chatHistoryList.length > 0) {
-          // 鍒ゆ柇褰撳墠浼氳瘽鏄笉鏄鍒犻櫎鐨勪細璇�
-          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.activeHistoryIndex = null
-          this.createNewConversation()
-        }
-        record.deletePopVisible = false
-        this.deletingHistoryIndex = null
-        this.$message.success('鍒犻櫎鎴愬姛锛�')
+          })
+          .catch(err => {
+            this.$message.error(err.message)
+          })
       },
 
       /* 鍙栨秷缂栬緫浼氳瘽鏍囬鏃惰Е鍙� */
@@ -414,7 +618,7 @@
       /* 鍙栨秷鍒犻櫎浼氳瘽鏃惰Е鍙� */
       cancelDeleteConversation(record, event) {
         // 澶卞幓鐒︾偣浜嬩欢鏃惰嫢鐐瑰嚮鐨勫厓绱犳槸纭鍒犻櫎鎸夐挳鍒欎笉杩涜澶卞幓鐒︾偣浜嬩欢锛岀洿鎺ヨ繘鍏ョ‘璁ゅ垹闄や簨浠�
-        if (event.relatedTarget && event.relatedTarget.id === 'delete-conversation-button') return
+        if (event && event.relatedTarget && event.relatedTarget.id === 'delete-conversation-button') return
         record.deletePopVisible = false
         this.deletingHistoryIndex = null
       },
@@ -424,55 +628,20 @@
         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)
-
-          // 褰撴柊寤哄璇濇椂闇�鍏堟彁闂啀璁插璇濆姞鍏ュ埌鍘嗗彶璁板綍涓�
-          if (this.currentConversation.messages.length === 1) {
-            this.currentConversation.title = newQuestion.content.slice(0, 15)
-            this.chatHistoryList.unshift(this.currentConversation)
-            if (this.isDeletingBatch) this.singleHistoryCheckedChange()
-            this.switchToCurrentConversation(this.currentConversation)
-          }
-
-          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">
-  @main-container-background: rgba(255, 255, 255, .8);
+  @main-container-background: rgba(255, 255, 255, .7);
   @container-border-radius: 12px;
   @container-padding: 10px;
   @single-history-edit-border: 3px solid #ABC0CC;
@@ -564,6 +733,13 @@
                 margin-right: 10px;
               }
             }
+
+            &.disable-expand {
+              cursor: not-allowed;
+              &:hover {
+                box-shadow: none;
+              }
+            }
           }
 
           .delete-batch-container {
@@ -633,7 +809,14 @@
           flex: 1;
           overflow: auto;
 
-          & > div {
+          .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;
@@ -714,6 +897,14 @@
                 }
               }
 
+            }
+
+            &.disable-switch {
+              cursor: not-allowed;
+
+              &:not(.single-history-active):hover {
+                background-color: transparent;
+              }
             }
           }
         }
@@ -866,16 +1057,20 @@
 
               &.user-question {
                 align-items: flex-end;
-                .content {
+                .conversation-content {
                   background-color: @user-question-background;
+                  text-align: justify;
+                  text-align-last: left;
                 }
               }
 
               &.assistant-answer {
                 align-items: flex-start;
 
-                .content {
+                .conversation-content {
                   background-color: @assistant-answer-background;
+                  text-align: justify;
+                  text-align-last: left;
                 }
               }
 
@@ -889,7 +1084,7 @@
                 }
               }
 
-              .content {
+              .conversation-content {
                 max-width: 80%;
                 box-shadow: @conversation-content-container-box-shadow;
                 border-radius: @container-border-radius;
diff --git a/vue.config.js b/vue.config.js
index 4e39c41..737ac20 100644
--- a/vue.config.js
+++ b/vue.config.js
@@ -1,5 +1,5 @@
 const path = require('path')
-const CompressionPlugin = require("compression-webpack-plugin")
+const CompressionPlugin = require('compression-webpack-plugin')
 
 function resolve(dir) {
   return path.join(__dirname, dir)
@@ -42,11 +42,11 @@
 
     //鐢熶骇鐜锛屽紑鍚痡s\css鍘嬬缉
     if (process.env.NODE_ENV === 'production') {
-        config.plugin('compressionPlugin').use(new CompressionPlugin({
-          test: /\.(js|css|less)$/, // 鍖归厤鏂囦欢鍚�
-          threshold: 10240, // 瀵硅秴杩�10k鐨勬暟鎹帇缂�
-          deleteOriginalAssets: false // 涓嶅垹闄ゆ簮鏂囦欢
-        }))
+      config.plugin('compressionPlugin').use(new CompressionPlugin({
+        test: /\.(js|css|less)$/, // 鍖归厤鏂囦欢鍚�
+        threshold: 10240, // 瀵硅秴杩�10k鐨勬暟鎹帇缂�
+        deleteOriginalAssets: false // 涓嶅垹闄ゆ簮鏂囦欢
+      }))
     }
 
     // 閰嶇疆 webpack 璇嗗埆 markdown 涓烘櫘閫氱殑鏂囦欢
@@ -62,9 +62,9 @@
       .rule('vxe')
       .test(/\.js$/)
       .include
-        .add(resolve('node_modules/vxe-table'))
-        .add(resolve('node_modules/vxe-table-plugin-antd'))
-        .end()
+      .add(resolve('node_modules/vxe-table'))
+      .add(resolve('node_modules/vxe-table-plugin-antd'))
+      .end()
       .use()
       .loader('babel-loader')
       .end()
@@ -78,26 +78,26 @@
           /* less 鍙橀噺瑕嗙洊锛岀敤浜庤嚜瀹氫箟 ant design 涓婚 */
           'primary-color': '#1890FF',
           'link-color': '#1890FF',
-          'border-radius-base': '4px',
+          'border-radius-base': '4px'
         },
-        javascriptEnabled: true,
+        javascriptEnabled: true
       },
-      postcss:{
-        plugins:[
+      postcss: {
+        plugins: [
           require('postcss-px-to-viewport')({
-            unitToConvert: "px",
+            unitToConvert: 'px',
             viewportWidth: 1920,
             unitPrecision: 3,
             propList: [
-              "*"
+              '*'
             ],
-            viewportUnit: "vw",
-            fontViewportUnit: "vw",
+            viewportUnit: 'vw',
+            fontViewportUnit: 'vw',
             selectorBlackList: [],
             minPixelValue: 0,
             mediaQuery: false,
             replace: true,
-            exclude: /(\/|\\)(node_modules)(\/|\\)/,
+            exclude: /(\/|\\)(node_modules)(\/|\\)/
           })
         ]
       }
@@ -115,15 +115,16 @@
     // headers: {
     //     'Access-Control-Allow-Origin': '*',
     // },
+    compress: false,
     proxy: {
-     /* '/api': {
-        target: 'https://mock.ihx.me/mock/5baf3052f7da7e07e04a5116/antd-pro', //mock API鎺ュ彛绯荤粺
-        ws: false,
-        changeOrigin: true,
-        pathRewrite: {
-          '/jeecg-boot': ''  //榛樿鎵�鏈夎姹傞兘鍔犱簡jeecg-boot鍓嶇紑锛岄渶瑕佸幓鎺�
-        }
-      },*/
+      /* '/api': {
+         target: 'https://mock.ihx.me/mock/5baf3052f7da7e07e04a5116/antd-pro', //mock API鎺ュ彛绯荤粺
+         ws: false,
+         changeOrigin: true,
+         pathRewrite: {
+           '/jeecg-boot': ''  //榛樿鎵�鏈夎姹傞兘鍔犱簡jeecg-boot鍓嶇紑锛岄渶瑕佸幓鎺�
+         }
+       },*/
       /* 娉ㄦ剰锛歫eecgboot鍓嶇鍋氫簡鏀归�狅紝姝ゅ涓嶉渶瑕侀厤缃法鍩熷拰鍚庡彴鎺ュ彛锛堝彧闇�瑕佹敼.env鐩稿叧閰嶇疆鏂囦欢鍗冲彲锛�
           issues/3462 寰堝浜烘澶勫仛浜嗛厤缃紝瀵艰嚧鍒锋柊鍓嶇404闂锛岃涓�瀹氭敞鎰�*/
       '/jeecg-boot': {
@@ -131,6 +132,11 @@
         ws: false,
         changeOrigin: true
       },
+      '/chat': {
+        target: 'https://836u458t54.vicp.fun',
+        ws: false,
+        changeOrigin: true
+      }
     }
   },
 

--
Gitblit v1.9.3