public/index.html | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/api/ai.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/assets/page/electronicManual/back.png | 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/assets/page/electronicManual/document.png | 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/assets/page/electronicManual/search.png | 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/views/ai/ElectronicManual.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/views/ai/LanguageModel.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
vue.config.js | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
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> 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) src/assets/page/electronicManual/back.png
src/assets/page/electronicManual/document.png
src/assets/page/electronicManual/search.png
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>æ¨å½åéæ©ççµæ§ç³»ç»ä¸ºï¼xxx-xxxx-xxxxx æºåºåå·ä¸º: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 缩ç¥å¾Id * @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 }) // è§£å³è§¦é¡¶å·æ°æ°æ®åæ»å¨æ¡è³æé¡¶é¨é®é¢ï¼ä½ä¸ç¥é为ä»ä¹ 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 æ»å¨è³æå ç´ çIdåç¼ * @param isParentNodeOpenPosition ç¶å ç´ æ¯å¦å¼å¯å®ä½ * @param parentNodePreClass ç¶å ç´ ç±»ååç¼ * @param marginValue è¾¹è·å¼ æ£å¼ä¸º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 }) }, /** * éç¨æµè§å¨çæµè APIå®ç°å¾çæå è½½ * @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çå¼ä¸ºæ£æè è´ã // æ°´å¹³æ»å¨ä¹éè¦æç±»ä¼¼deltaYè¿æ ·çç¶æï¼æä»¥ç´æ¥ä½¿ç¨deltaYå°±å¯ä»¥äºï¼è½ç¶Yè¡¨ç¤ºçæ¯åç´æ¹åçæ»å¨ã 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> 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': 'ä½ æ¯è°ï¼ä½ æ¯è°ä½ æ¯è°ä½ æ¯è°ä½ æ¯è°ä½ æ¯è°ä½ æ¯è°ä½ æ¯è°ä½ æ¯è°ä½ æ¯è°ä½ æ¯è°ä½ æ¯è°ä½ æ¯è°ä½ æ¯è°ä½ æ¯è°ä½ æ¯è°ä½ æ¯è°ä½ æ¯è°' id: '', messages: [], stream: true, max_tokens: 500 }, { '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 }, // 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 = '请å°è¯é®æï¼ä½ æ¯è°ï¼' }, /* å建ä¸ä¸ªæ°ä¼è¯ */ createNewConversation() { if (this.isAtNewConversation) this.$message.info('å½åå·²æ¯ææ°å¯¹è¯') this.isAtNewConversation = true this.currentConversation = { id: randomUUID(), title: '', messages: [], stream: true, max_tokens: 500, 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: '', messages: [], stream: true, max_tokens: 500 } // æ°å»ºä¼è¯æ¶åæ¶åå 被éä¸çåå²å¯¹è¯ï¼æ´æ¸ æ°åè¯ç¨æ·ç°å¨çé¢å¤äºæ°å»ºå¯¹è¯ä¸ if (this.activeHistoryIndex !== null) this.activeHistoryIndex = null this.textareaPlaceholder = '请å°è¯é®æï¼ä½ æ¯è°ï¼' }, /* è°ç¨æ¥å£è·åå½åä¼è¯è®°å½ */ getConversationByApi(id) { // TODO æ ¹æ®ç¹å»çåå²ä¼è¯IDè·å对åºåå²ä¼è¯ç对è¯è®°å½å表 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 è°ç¨å端æ¥å£ä¿åå½åé®é¢ä¸åå»ºä¸æ¡åå²ä¼è¯è®°å½ï¼ç¶ååéæ°è°ç¨è·ååå²è®°å½å表æ¥å£å·æ°å表 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 // åéPOST请æ±å°æ¨¡åï¼è·åååºæµ 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) // æ¤å¤ä¸ºå±è½è¿å带æpingå符串 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 // 彿¡ä¼è¯æ£å¨è¢«ä¿®æ¹æ¶å次ç¹å»æ¬æ¡ä¼è¯æ åé¦ // å ³éç¹å»ç¼è¾æé®åçè¾å ¥æ¡ 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 // 彿¡ä¼è¯æ£å¨è¢«ä¿®æ¹æ¶å次ç¹å»æ¬æ¡ä¼è¯æ åé¦ // å ³éç¹å»ç¼è¾æé®åçè¾å ¥æ¡ 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) { 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 (this.chatHistoryList[index]) { console.log('å é¤éæå䏿¡') this.currentConversation = this.chatHistoryList[index] this.activeHistoryIndex = this.chatHistoryList[index].id if (index !== 0) { console.log('å é¤éç¬¬ä¸æ¡è®°å½') this.switchToCurrentConversation(this.chatHistoryList[index - 1]) } else { console.log('å 餿å䏿¡') this.currentConversation = this.chatHistoryList[this.chatHistoryList.length - 1] this.activeHistoryIndex = this.chatHistoryList[this.chatHistoryList.length - 1].id console.log('å é¤ç¬¬ä¸æ¡è®°å½') this.switchToCurrentConversation(this.chatHistoryList[index + 1]) } } } else { console.log('å é¤ååªæä¸æ¡') console.log('å 餿å䏿¡è®°å½') this.activeHistoryIndex = null this.createNewConversation() } record.deletePopVisible = false this.deletingHistoryIndex = null this.$message.success('å 餿åï¼') this.getChatHistoryListByApi() this.$message.success(res.message) } }) .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 scrollToConversationContainerBottom(scrollBehavior = 'auto') { this.$nextTick(() => { this.conversationContainer.scrollTo({ top: 9999999999999999999999999999, behavior: 'smooth' }) this.conversationContainer.scrollTo({ top: this.conversationContainer.scrollHeight, behavior: scrollBehavior }) }, 1000) } }) } } } </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; 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) @@ -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:[ 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,6 +115,7 @@ // headers: { // 'Access-Control-Allow-Origin': '*', // }, compress: false, proxy: { /* '/api': { target: 'https://mock.ihx.me/mock/5baf3052f7da7e07e04a5116/antd-pro', //mock APIæ¥å£ç³»ç» @@ -131,6 +132,11 @@ ws: false, changeOrigin: true }, '/chat': { target: 'https://836u458t54.vicp.fun', ws: false, changeOrigin: true } } },