|
|
|
@ -1,8 +1,6 @@
|
|
|
|
|
<script setup lang="ts"> |
|
|
|
|
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue' |
|
|
|
|
import { onBeforeRouteLeave } from 'vue-router' |
|
|
|
|
import { Button, Image, Spin, UploadDragger, message } from 'ant-design-vue' |
|
|
|
|
import { SvgIcon } from '@/components/SvgIcon' |
|
|
|
|
import { Button, Spin, message } from 'ant-design-vue' |
|
|
|
|
import { AppTextarea } from '@/components/AppTextarea' |
|
|
|
|
import { AppContainerBox } from '@/components/AppContainerBox' |
|
|
|
|
import { AppSubMenuTitle } from '@/components/AppSubMenuTitle' |
|
|
|
@ -15,16 +13,15 @@ import type { PictureType } from '@/components/AppPicture/index.d'
|
|
|
|
|
|
|
|
|
|
import { AppMessage } from '@/components/AppMessage' |
|
|
|
|
import type { MessageItem } from '@/components/AppMessage/index.d' |
|
|
|
|
import type { TopPickItem } from '@/components/AppTopPicks/index.d' |
|
|
|
|
import { AppModelSelect } from '@/components/AppModelSelect' |
|
|
|
|
import type { ModelSelect } from '@/components/AppModelSelect/index.d' |
|
|
|
|
import { useMessageStore } from '@/store/moules/messageStore/index' |
|
|
|
|
import { MenuTypeEnum } from '@/enums/menuEnum' |
|
|
|
|
import { MessageStatusEnum, MessageTypeEnum, ModelTypeEnum } from '@/enums/messageEnum' |
|
|
|
|
import { addMessage, conversationList, conversationToTop, historyMessage, removeMessage, sendVisualAnalysis, updateMessage } from '@/api/base/message' |
|
|
|
|
import { uploadApi } from '@/api/base/file' |
|
|
|
|
import { useMqtt } from '@/hooks/useMqtt' |
|
|
|
|
import { useMessage } from '@/hooks/useMessage' |
|
|
|
|
import type { UploadFileResult } from '/#/axios' |
|
|
|
|
|
|
|
|
|
const { createConfirm } = useMessage() |
|
|
|
|
|
|
|
|
@ -39,12 +36,11 @@ const appMessageRef = ref()
|
|
|
|
|
|
|
|
|
|
const modelOptions: ModelSelect[] = [ |
|
|
|
|
{ |
|
|
|
|
label: 'DALL-E 3', |
|
|
|
|
value: ModelTypeEnum.DALL_E3, |
|
|
|
|
label: 'GPT-4-Vision', |
|
|
|
|
value: ModelTypeEnum.GPT4_VISION, |
|
|
|
|
}, |
|
|
|
|
] |
|
|
|
|
const modelIndex = ref(0) |
|
|
|
|
const fileList = ref<string[]>([]) |
|
|
|
|
const roleList = ref<PictureType[]>([]) |
|
|
|
|
const applyList = ref<PictureType[]>([]) |
|
|
|
|
const messageList = computed(() => messageStore.getMessageList) |
|
|
|
@ -60,13 +56,16 @@ const historyMessageParams = ref({
|
|
|
|
|
const conversationDefaultShow = ref(false) |
|
|
|
|
const appMessageShow = ref(true) |
|
|
|
|
const spinning = ref(true) |
|
|
|
|
const uploadDisabled = ref(false) |
|
|
|
|
const elIndex = ref(0) // 新分页数据的数量 |
|
|
|
|
const defaultFileUrl = ref('') |
|
|
|
|
|
|
|
|
|
watch( |
|
|
|
|
() => messageStatus.value, |
|
|
|
|
(val) => { |
|
|
|
|
if (val === MessageStatusEnum.END) { |
|
|
|
|
sendBtnLoading.value = false |
|
|
|
|
uploadDisabled.value = false |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
) |
|
|
|
@ -140,14 +139,20 @@ async function handleSubMenuChange(index: number, item?: SubMenuItem) {
|
|
|
|
|
// item有的话说明是点击切换,需要重新获取历史消息,否则是不是点击切换调用,不需要获取历史记录 |
|
|
|
|
if (item) { |
|
|
|
|
getHistoryMessage() |
|
|
|
|
fileList.value = [] |
|
|
|
|
defaultFileUrl.value = '' |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* @description: 发送消息 |
|
|
|
|
*/ |
|
|
|
|
async function handleSend(value: string) { |
|
|
|
|
async function handleSend(value: string, fileUrl: string) { |
|
|
|
|
if (!fileUrl) { |
|
|
|
|
message.error('请先上传图片!') |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
uploadDisabled.value = true |
|
|
|
|
sendBtnLoading.value = true |
|
|
|
|
conversationDefaultShow.value = false |
|
|
|
|
|
|
|
|
@ -164,7 +169,8 @@ async function handleSend(value: string) {
|
|
|
|
|
message.error('对话发送,请稍后重试!') |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
sendMessage(conversationData.value.id, value) |
|
|
|
|
await sendMessage(conversationData.value.id, value, fileUrl) |
|
|
|
|
defaultFileUrl.value = '' |
|
|
|
|
spinning.value = false |
|
|
|
|
} |
|
|
|
|
catch (error) { |
|
|
|
@ -175,8 +181,8 @@ async function handleSend(value: string) {
|
|
|
|
|
if (!conversationData.value) { |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
sendMessage(conversationData.value.id, value) |
|
|
|
|
fileList.value = [] |
|
|
|
|
await sendMessage(conversationData.value.id, value, fileUrl) |
|
|
|
|
defaultFileUrl.value = '' |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -252,7 +258,7 @@ async function onScrollTop(scrollTop: number) {
|
|
|
|
|
/** |
|
|
|
|
* @description: 发送消息hook |
|
|
|
|
*/ |
|
|
|
|
async function sendMessage(conversationId: string, question: string): Promise<void> { |
|
|
|
|
async function sendMessage(conversationId: string, question: string, fileUrl: string): Promise<void> { |
|
|
|
|
if (!messageStore.getConversationData) { |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
@ -261,7 +267,7 @@ async function sendMessage(conversationId: string, question: string): Promise<vo
|
|
|
|
|
messageStore.setMessageStatus(MessageStatusEnum.LOADING) |
|
|
|
|
messageStore.setMessagePushItem({ |
|
|
|
|
messageType: MessageTypeEnum.USER, |
|
|
|
|
content: `${question}`, |
|
|
|
|
content: `${question}`, |
|
|
|
|
time: String(new Date().getTime()), |
|
|
|
|
avatar: '', |
|
|
|
|
}) |
|
|
|
@ -276,24 +282,13 @@ async function sendMessage(conversationId: string, question: string): Promise<vo
|
|
|
|
|
sendVisualAnalysis({ |
|
|
|
|
conversationId, |
|
|
|
|
question, |
|
|
|
|
fileUrl: fileList.value[0], |
|
|
|
|
fileUrl, |
|
|
|
|
}).catch(() => { |
|
|
|
|
messageStore.getMessageList.splice(-2) |
|
|
|
|
messageStore.setMessageStatus(MessageStatusEnum.END) |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* @description: 重新回答 |
|
|
|
|
*/ |
|
|
|
|
function reloadMessage() { |
|
|
|
|
if (!conversationData.value) { |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
const question = messageList.value[messageList.value.length - 2]?.content |
|
|
|
|
sendMessage(conversationData.value.id, question) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* @description: 点击新建会话按钮 |
|
|
|
|
*/ |
|
|
|
@ -306,13 +301,6 @@ function handleAddMessage() {
|
|
|
|
|
subMenuActiveIndex.value = -1 |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* @description: 点击默认看板的精选话题 |
|
|
|
|
*/ |
|
|
|
|
function handlePick(_index: number, item: TopPickItem) { |
|
|
|
|
handleSend(item.label) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* @description: gpt模型切换 |
|
|
|
|
*/ |
|
|
|
@ -320,31 +308,14 @@ function handleModel(index: number) {
|
|
|
|
|
modelIndex.value = index |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* @description: 图片上传前的处理 |
|
|
|
|
*/ |
|
|
|
|
function handleBeforeUpload(file: File) { |
|
|
|
|
const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'image/gif' |
|
|
|
|
if (!isJpgOrPng) { |
|
|
|
|
message.error('只能上传JPG、JPEG、PNG、GIF格式图片!') |
|
|
|
|
} |
|
|
|
|
const isLt10M = file.size / 1024 / 1024 < 10 |
|
|
|
|
if (!isLt10M) { |
|
|
|
|
message.error('图片尺寸不能超过10MB!') |
|
|
|
|
} |
|
|
|
|
return isJpgOrPng && isLt10M |
|
|
|
|
function onUploadSuccess(res: UploadFileResult) { |
|
|
|
|
defaultFileUrl.value = res.link |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* @description: 自定义图片上传服务器 |
|
|
|
|
*/ |
|
|
|
|
async function handleCustomRequest(option: any) { |
|
|
|
|
const formData = new FormData() |
|
|
|
|
formData.append('file', option.file) |
|
|
|
|
|
|
|
|
|
uploadApi(formData).then((res) => { |
|
|
|
|
fileList.value.push(res.link) |
|
|
|
|
}) |
|
|
|
|
function onUploadFailed() { |
|
|
|
|
defaultFileUrl.value = '' |
|
|
|
|
} |
|
|
|
|
function handleDeleteFile() { |
|
|
|
|
defaultFileUrl.value = '' |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 下面是子菜单操作项 |
|
|
|
@ -377,14 +348,6 @@ onUnmounted(() => {
|
|
|
|
|
messageStore.setConversationData(undefined) |
|
|
|
|
useMqtt().end() |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
// 路由离开时的操作 |
|
|
|
|
onBeforeRouteLeave(() => { |
|
|
|
|
if (messageStatus.value !== MessageStatusEnum.END) { |
|
|
|
|
message.warn('请先结束对话') |
|
|
|
|
return false |
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
</script> |
|
|
|
|
|
|
|
|
|
<template> |
|
|
|
@ -427,12 +390,11 @@ onBeforeRouteLeave(() => {
|
|
|
|
|
<!-- 默认导语 --> |
|
|
|
|
<AppConversationDefault |
|
|
|
|
v-if="conversationDefaultShow" |
|
|
|
|
:is-pick="true" |
|
|
|
|
:is-pick="false" |
|
|
|
|
:is-hot="false" |
|
|
|
|
:role-list="roleList" |
|
|
|
|
:apply-list="applyList" |
|
|
|
|
height="calc(100% - 160px)" |
|
|
|
|
@handle-pick="handlePick" |
|
|
|
|
height="calc(100% - 148px)" |
|
|
|
|
> |
|
|
|
|
</AppConversationDefault> |
|
|
|
|
|
|
|
|
@ -442,49 +404,26 @@ onBeforeRouteLeave(() => {
|
|
|
|
|
v-if="!conversationDefaultShow && appMessageShow" |
|
|
|
|
ref="appMessageRef" |
|
|
|
|
class="pl-27 pr-5" |
|
|
|
|
height="calc(100% - 200px)" |
|
|
|
|
height="calc(100% - 120px)" |
|
|
|
|
:el-index="elIndex" |
|
|
|
|
:list="messageList" |
|
|
|
|
@on-scroll-top="onScrollTop" |
|
|
|
|
@reload-message="reloadMessage" |
|
|
|
|
> |
|
|
|
|
</AppMessage> |
|
|
|
|
|
|
|
|
|
<!-- 发送框 --> |
|
|
|
|
<div v-if="!fileList.length" class="upload-box absolute right-0 bottom-5 pl-44 pr-24 mt-10s w-full"> |
|
|
|
|
<UploadDragger |
|
|
|
|
name="file" |
|
|
|
|
:multiple="false" |
|
|
|
|
:max-count="1" |
|
|
|
|
:show-upload-list="false" |
|
|
|
|
:custom-request="handleCustomRequest" |
|
|
|
|
:before-upload="handleBeforeUpload" |
|
|
|
|
> |
|
|
|
|
<SvgIcon class="upload-icon" name="image"></SvgIcon> |
|
|
|
|
<p class="ant-upload-text"> |
|
|
|
|
拖拽图片到这里,或者点击添加 |
|
|
|
|
</p> |
|
|
|
|
<p class="ant-upload-hint"> |
|
|
|
|
图片格式支持jpg、jpeg、png、gif、webp |
|
|
|
|
</p> |
|
|
|
|
</UploadDragger> |
|
|
|
|
</div> |
|
|
|
|
<div |
|
|
|
|
v-else |
|
|
|
|
class="w-full" |
|
|
|
|
> |
|
|
|
|
<div class="pl-44 pr-24 mt-2"> |
|
|
|
|
<Image |
|
|
|
|
class="rounded" |
|
|
|
|
:width="60" |
|
|
|
|
:height="60" |
|
|
|
|
:src="fileList[0]" |
|
|
|
|
fallback="data:image/png;base64,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" |
|
|
|
|
></Image> |
|
|
|
|
</div> |
|
|
|
|
<AppTextarea |
|
|
|
|
class="pl-44 pr-24 mt-2" |
|
|
|
|
class="pl-44 pr-24 mt-10" |
|
|
|
|
:is-upload="true" |
|
|
|
|
:default-file-url="defaultFileUrl" |
|
|
|
|
:upload-disabled="uploadDisabled" |
|
|
|
|
:btn-loading="sendBtnLoading" |
|
|
|
|
@on-upload-success="onUploadSuccess" |
|
|
|
|
@on-upload-failed="onUploadFailed" |
|
|
|
|
@delete-file="handleDeleteFile" |
|
|
|
|
@send="handleSend" |
|
|
|
|
></AppTextarea> |
|
|
|
|
</div> |
|
|
|
|