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 鏈哄簥鍨嬪彿涓�: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