| | |
| | | <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> |
| | |
| | | </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> |
| | |
| | | </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"> |
| | |
| | | <span class="check-mark"></span> |
| | | </label> |
| | | |
| | | |
| | | <a-popover placement="top" :visible="item.deletePopVisible" trigger="click" |
| | | :getPopupContainer="node=>node.parentNode"> |
| | | <template slot="content"> |
| | |
| | | <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)"/> |
| | |
| | | </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> |
| | |
| | | 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, |
| | |
| | | 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 |
| | | } |
| | | }, |
| | | |
| | |
| | | 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 根据点击的历史会话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) |
| | | }, |
| | | |
| | | /* 点击全选按钮后改变勾选后触发 */ |
| | |
| | | } else { |
| | | this.checkedConversationIdList = [] |
| | | } |
| | | console.log('触发全选', event.target.checked) |
| | | console.log('触发全选', this.checkedConversationIdList) |
| | | }, |
| | | |
| | | /* 点击历史记录中多选框改变勾选后触发 */ |
| | |
| | | /* 点击红色图标批量删除按钮后触发 */ |
| | | deleteBatchConversation() { |
| | | if (!this.checkedConversationIdList.length) return |
| | | if (this.isModelResponding) return // 模型回答期间禁止删除会话 |
| | | this.deleteBatchPopVisible = !this.deleteBatchPopVisible |
| | | }, |
| | | |
| | |
| | | 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) { |
| | | // 仅开启最后一次点击编辑按钮后的输入框 |
| | |
| | | |
| | | this.editingHistoryIndex = record.id |
| | | record.inputVisible = true |
| | | this.editedConversationTitle = record.title |
| | | this.editedConversationTitle = record.problem |
| | | this.$nextTick(() => document.getElementById('edit-input').focus()) |
| | | }, |
| | | |
| | |
| | | } |
| | | // 仅可使用最后一次点击删除按钮的功能 |
| | | 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) |
| | | }) |
| | | }, |
| | | |
| | | /* 取消编辑会话标题时触发 */ |
| | |
| | | /* 取消删除会话时触发 */ |
| | | 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 |
| | | }, |
| | |
| | | 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; |
| | |
| | | margin-right: 10px; |
| | | } |
| | | } |
| | | |
| | | &.disable-expand { |
| | | cursor: not-allowed; |
| | | &:hover { |
| | | box-shadow: none; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .delete-batch-container { |
| | |
| | | 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; |
| | |
| | | } |
| | | } |
| | | |
| | | } |
| | | |
| | | &.disable-switch { |
| | | cursor: not-allowed; |
| | | |
| | | &:not(.single-history-active):hover { |
| | | background-color: transparent; |
| | | } |
| | | } |
| | | } |
| | | } |
| | |
| | | |
| | | &.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; |
| | | } |
| | | } |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | .content { |
| | | .conversation-content { |
| | | max-width: 80%; |
| | | box-shadow: @conversation-content-container-box-shadow; |
| | | border-radius: @container-border-radius; |