|
|
|
<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 type { UploadChangeParam, UploadProps } from 'ant-design-vue'
|
|
|
|
import { SvgIcon } from '@/components/SvgIcon'
|
|
|
|
import { AppTextarea } from '@/components/AppTextarea'
|
|
|
|
import { AppContainerBox } from '@/components/AppContainerBox'
|
|
|
|
import { AppSubMenuTitle } from '@/components/AppSubMenuTitle'
|
|
|
|
import { AppSubMenuList } from '@/components/AppSubMenuList'
|
|
|
|
import { AppConversationDefault } from '@/components/AppConversationDefault'
|
|
|
|
import { AppUserInfo } from '@/components/AppUserInfo'
|
|
|
|
import { SubMenuActionEnum } from '@/components/AppSubMenuList/index.d'
|
|
|
|
import type { SubMenuItem } from '@/components/AppSubMenuList/index.d'
|
|
|
|
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, stopMessage, updateMessage } from '@/api/base/message'
|
|
|
|
import { uploadApi } from '@/api/base/file'
|
|
|
|
import { useMqtt } from '@/hooks/useMqtt'
|
|
|
|
import { useMessage } from '@/hooks/useMessage'
|
|
|
|
|
|
|
|
const { createConfirm } = useMessage()
|
|
|
|
|
|
|
|
const messageStore = useMessageStore()
|
|
|
|
|
|
|
|
const sendBtnLoading = ref(false)
|
|
|
|
const subMenuActiveIndex = ref(-1) // 当前会话索引
|
|
|
|
const subMenuActionIndex = ref(-1) // 会话操作索引
|
|
|
|
const subMenuList = ref<SubMenuItem[]>([])
|
|
|
|
const subMenuInputValue = ref<string>('')
|
|
|
|
const appMessageRef = ref()
|
|
|
|
|
|
|
|
const modelOptions: ModelSelect[] = [
|
|
|
|
{
|
|
|
|
label: 'DALL-E 3',
|
|
|
|
value: ModelTypeEnum.DALL_E3,
|
|
|
|
},
|
|
|
|
]
|
|
|
|
const modelIndex = ref(0)
|
|
|
|
const fileList = ref<string[]>([])
|
|
|
|
const roleList = ref<PictureType[]>([])
|
|
|
|
const applyList = ref<PictureType[]>([])
|
|
|
|
const messageList = computed(() => messageStore.getMessageList)
|
|
|
|
const messageStatus = computed(() => messageStore.getMessageStatus)
|
|
|
|
const conversationData = computed(() => messageStore.getConversationData)
|
|
|
|
const historyMessageParams = ref({
|
|
|
|
conversationId: '',
|
|
|
|
current: 1,
|
|
|
|
size: 10,
|
|
|
|
total: 0,
|
|
|
|
})
|
|
|
|
|
|
|
|
const conversationDefaultShow = ref(false)
|
|
|
|
const appMessageShow = ref(true)
|
|
|
|
const spinning = ref(true)
|
|
|
|
|
|
|
|
watch(
|
|
|
|
() => messageStatus.value,
|
|
|
|
(val) => {
|
|
|
|
if (val === MessageStatusEnum.END) {
|
|
|
|
sendBtnLoading.value = false
|
|
|
|
}
|
|
|
|
},
|
|
|
|
)
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @description: 点击会话操作项
|
|
|
|
*/
|
|
|
|
function handlesubMenuActionIndex(type: SubMenuActionEnum, item: SubMenuItem, index: number) {
|
|
|
|
subMenuActionIndex.value = index
|
|
|
|
const subMenuActionData = subMenuList.value[index]
|
|
|
|
if (type === SubMenuActionEnum.EDIT) {
|
|
|
|
subMenuList.value.forEach((item) => {
|
|
|
|
item.actionType = SubMenuActionEnum.NOT
|
|
|
|
})
|
|
|
|
subMenuList.value[index].actionType = SubMenuActionEnum.EDIT
|
|
|
|
subMenuInputValue.value = item.title
|
|
|
|
}
|
|
|
|
|
|
|
|
if (type === SubMenuActionEnum.DELETE) {
|
|
|
|
createConfirm({
|
|
|
|
title: '删除',
|
|
|
|
content: `确定要删除${item.title}会话吗?`,
|
|
|
|
iconType: 'warning',
|
|
|
|
onOk: () => {
|
|
|
|
removeMessage(item.id).then(async () => {
|
|
|
|
message.success('删除成功')
|
|
|
|
subMenuActiveIndex.value = -1
|
|
|
|
conversationDefaultShow.value = true
|
|
|
|
await getConversationList()
|
|
|
|
})
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
if (type === SubMenuActionEnum.TO_TOP) {
|
|
|
|
conversationToTop(item.id).then(async () => {
|
|
|
|
message.success('置顶成功')
|
|
|
|
await getConversationList()
|
|
|
|
const index = subMenuList.value.findIndex(item => item.id === subMenuActionData.id)
|
|
|
|
await handleSubMenuChange(index)
|
|
|
|
getHistoryMessage()
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @description: 切换会话
|
|
|
|
*/
|
|
|
|
async function handleSubMenuChange(index: number, item?: SubMenuItem) {
|
|
|
|
if (messageStatus.value !== MessageStatusEnum.END) {
|
|
|
|
message.warn('请先结束对话')
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if (!subMenuList.value.length) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
subMenuActiveIndex.value = index
|
|
|
|
historyMessageParams.value.current = 1
|
|
|
|
historyMessageParams.value.total = 0
|
|
|
|
messageStore.setConversationData(subMenuList.value[subMenuActiveIndex.value])
|
|
|
|
messageStore.setMessageClear()
|
|
|
|
const modelIdx = modelOptions.findIndex(v => v.value === Number(subMenuList.value[subMenuActiveIndex.value].modelType))
|
|
|
|
if (modelIdx === -1) {
|
|
|
|
message.warn('未找到该对话下的模型信息')
|
|
|
|
return
|
|
|
|
}
|
|
|
|
modelIndex.value = modelIdx
|
|
|
|
useMqtt().end()
|
|
|
|
useMqtt().connect()
|
|
|
|
|
|
|
|
// item有的话说明是点击切换,需要重新获取历史消息,否则是不是点击切换调用,不需要获取历史记录
|
|
|
|
if (item) {
|
|
|
|
getHistoryMessage()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @description: 发送消息
|
|
|
|
*/
|
|
|
|
async function handleSend(value: string) {
|
|
|
|
sendBtnLoading.value = true
|
|
|
|
conversationDefaultShow.value = false
|
|
|
|
|
|
|
|
if (subMenuActiveIndex.value === -1) {
|
|
|
|
try {
|
|
|
|
spinning.value = true
|
|
|
|
messageStore.setMessageClear()
|
|
|
|
await addMessage({ type: MenuTypeEnum.VISUAL_ANALYSIS, title: '新的对话', sort: subMenuList.value.length + 1, modelType: modelOptions[modelIndex.value].value })
|
|
|
|
await getConversationList()
|
|
|
|
await nextTick()
|
|
|
|
subMenuActiveIndex.value = subMenuList.value.length - 1
|
|
|
|
await handleSubMenuChange(subMenuActiveIndex.value)
|
|
|
|
if (!conversationData.value) {
|
|
|
|
message.error('对话发送,请稍后重试!')
|
|
|
|
return
|
|
|
|
}
|
|
|
|
sendMessage(conversationData.value.id, value)
|
|
|
|
spinning.value = false
|
|
|
|
}
|
|
|
|
catch (error) {
|
|
|
|
message.error('新建对话失败,请稍后重试!')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (!conversationData.value) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
sendMessage(conversationData.value.id, value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @description: 获取会话列表
|
|
|
|
*/
|
|
|
|
async function getConversationList() {
|
|
|
|
const res = await conversationList(MenuTypeEnum.VISUAL_ANALYSIS)
|
|
|
|
res.forEach((item: SubMenuItem) => {
|
|
|
|
item.actionType = SubMenuActionEnum.NOT
|
|
|
|
})
|
|
|
|
subMenuList.value = res
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @description: 获取历史对话记录
|
|
|
|
*/
|
|
|
|
async function getHistoryMessage() {
|
|
|
|
if (!conversationData.value) {
|
|
|
|
spinning.value = false
|
|
|
|
conversationDefaultShow.value = true
|
|
|
|
return
|
|
|
|
}
|
|
|
|
spinning.value = true
|
|
|
|
historyMessageParams.value.conversationId = conversationData.value.id
|
|
|
|
const res = await historyMessage(historyMessageParams.value)
|
|
|
|
messageStore.setMessageClear()
|
|
|
|
spinning.value = false
|
|
|
|
if (!res || !res.records || !res.records.length) {
|
|
|
|
conversationDefaultShow.value = true
|
|
|
|
return
|
|
|
|
}
|
|
|
|
console.log(res.records)
|
|
|
|
|
|
|
|
res.records.forEach((item: any) => {
|
|
|
|
const itemData: MessageItem = {
|
|
|
|
messageType: item.roleType === MessageTypeEnum.USER ? MessageTypeEnum.USER : MessageTypeEnum.AI,
|
|
|
|
content: item.messageContent,
|
|
|
|
time: item.messageTime,
|
|
|
|
avatar: '',
|
|
|
|
messageStatus: MessageStatusEnum.END,
|
|
|
|
}
|
|
|
|
historyMessageParams.value.total = res.total
|
|
|
|
messageStore.setMessageUnshiftItem(itemData)
|
|
|
|
})
|
|
|
|
|
|
|
|
conversationDefaultShow.value = false
|
|
|
|
|
|
|
|
// 因为会话切换时滚动底部有问题,所以重新加载下
|
|
|
|
// appMessageShow.value = false
|
|
|
|
// setTimeout(() => {
|
|
|
|
// appMessageShow.value = true
|
|
|
|
// }, 0)
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @description: 滚动监听
|
|
|
|
*/
|
|
|
|
async function onScrollTop(scrollTop: number) {
|
|
|
|
if (scrollTop !== 0) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if (historyMessageParams.value.current * historyMessageParams.value.size > historyMessageParams.value.total) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if (historyMessageParams.value.current < historyMessageParams.value.total) {
|
|
|
|
historyMessageParams.value.current++
|
|
|
|
await getHistoryMessage()
|
|
|
|
|
|
|
|
// 无缝滚动
|
|
|
|
appMessageRef.value.seamlessScrollToTop()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @description: 发送消息hook
|
|
|
|
*/
|
|
|
|
async function sendMessage(conversationId: string, question: string): Promise<void> {
|
|
|
|
if (!messageStore.getConversationData) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
conversationDefaultShow.value = false
|
|
|
|
messageStore.setMessageStatus(MessageStatusEnum.LOADING)
|
|
|
|
messageStore.setMessagePushItem({
|
|
|
|
messageType: MessageTypeEnum.USER,
|
|
|
|
content: question,
|
|
|
|
time: String(new Date().getTime()),
|
|
|
|
avatar: '',
|
|
|
|
})
|
|
|
|
messageStore.setMessagePushItem({
|
|
|
|
messageType: MessageTypeEnum.AI,
|
|
|
|
content: '正在思考中...',
|
|
|
|
time: String(new Date().getTime()),
|
|
|
|
avatar: '',
|
|
|
|
messageStatus: MessageStatusEnum.LOADING,
|
|
|
|
})
|
|
|
|
|
|
|
|
sendVisualAnalysis({
|
|
|
|
conversationId,
|
|
|
|
question,
|
|
|
|
fileUrl: fileList.value[0],
|
|
|
|
}).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: 点击新建会话按钮
|
|
|
|
*/
|
|
|
|
function handleAddMessage() {
|
|
|
|
if (messageStatus.value !== MessageStatusEnum.END) {
|
|
|
|
message.warn('请先结束对话')
|
|
|
|
return
|
|
|
|
}
|
|
|
|
conversationDefaultShow.value = true
|
|
|
|
subMenuActiveIndex.value = -1
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @description: 点击默认看板的精选话题
|
|
|
|
*/
|
|
|
|
function handlePick(_index: number, item: TopPickItem) {
|
|
|
|
handleSend(item.label)
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @description: gpt模型切换
|
|
|
|
*/
|
|
|
|
function handleModel(index: number) {
|
|
|
|
modelIndex.value = index
|
|
|
|
}
|
|
|
|
|
|
|
|
function handleUploadChange(info: UploadChangeParam) {
|
|
|
|
const status = info.file.status
|
|
|
|
if (status !== 'uploading') {
|
|
|
|
console.log(info.file, info.fileList)
|
|
|
|
}
|
|
|
|
if (status === 'done') {
|
|
|
|
message.success(`${info.file.name} file uploaded successfully.`)
|
|
|
|
}
|
|
|
|
else if (status === 'error') {
|
|
|
|
message.error(`${info.file.name} file upload failed.`)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function handleUploadDrop(e: DragEvent) {
|
|
|
|
console.log('drop', e)
|
|
|
|
}
|
|
|
|
|
|
|
|
async function handleCustomRequest(option: any) {
|
|
|
|
console.log(option)
|
|
|
|
|
|
|
|
const formData = new FormData()
|
|
|
|
formData.append('file', option.file)
|
|
|
|
|
|
|
|
uploadApi(formData).then((res) => {
|
|
|
|
fileList.value.push(res.link)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// 下面是子菜单操作项
|
|
|
|
function handleSubMenuInputAffirm(index: number, item: SubMenuItem, inputValue: string) {
|
|
|
|
updateMessage({ ...item, title: inputValue }).then(() => {
|
|
|
|
message.success('修改成功')
|
|
|
|
subMenuList.value[index].title = inputValue
|
|
|
|
subMenuList.value[index].actionType = SubMenuActionEnum.NOT
|
|
|
|
})
|
|
|
|
}
|
|
|
|
function handleSubMenuInputClose(index: number) {
|
|
|
|
subMenuList.value[index].actionType = SubMenuActionEnum.NOT
|
|
|
|
}
|
|
|
|
function handleSubMenuInputBlur(index: number, item: SubMenuItem, inputValue: string) {
|
|
|
|
subMenuActionIndex.value = -1
|
|
|
|
handleSubMenuInputAffirm(index, item, inputValue)
|
|
|
|
}
|
|
|
|
|
|
|
|
onMounted(async () => {
|
|
|
|
await getConversationList()
|
|
|
|
if (subMenuList.value.length) {
|
|
|
|
subMenuActiveIndex.value = 0
|
|
|
|
}
|
|
|
|
await handleSubMenuChange(subMenuActiveIndex.value)
|
|
|
|
getHistoryMessage()
|
|
|
|
})
|
|
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
|
messageStore.setMessageClear()
|
|
|
|
messageStore.setConversationData(undefined)
|
|
|
|
useMqtt().end()
|
|
|
|
})
|
|
|
|
|
|
|
|
// 路由离开时的操作
|
|
|
|
onBeforeRouteLeave(() => {
|
|
|
|
if (messageStatus.value !== MessageStatusEnum.END) {
|
|
|
|
message.warn('请先结束对话')
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
})
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<template>
|
|
|
|
<AppContainerBox>
|
|
|
|
<template #subMenu>
|
|
|
|
<!-- 标题 -->
|
|
|
|
<AppSubMenuTitle></AppSubMenuTitle>
|
|
|
|
|
|
|
|
<!-- 按钮 -->
|
|
|
|
<div class="px-5 mb-5">
|
|
|
|
<Button type="primary" class="w-full" @click="handleAddMessage">
|
|
|
|
新建会话
|
|
|
|
</Button>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<!-- 会话列表 -->
|
|
|
|
<AppSubMenuList
|
|
|
|
v-model:input-value="subMenuInputValue"
|
|
|
|
:list="subMenuList"
|
|
|
|
:active-index="subMenuActiveIndex"
|
|
|
|
:action-index="subMenuActionIndex"
|
|
|
|
@change="handleSubMenuChange"
|
|
|
|
@handle-action="handlesubMenuActionIndex"
|
|
|
|
@input-affirm="handleSubMenuInputAffirm"
|
|
|
|
@input-close="handleSubMenuInputClose"
|
|
|
|
@input-blur="handleSubMenuInputBlur"
|
|
|
|
>
|
|
|
|
</AppSubMenuList>
|
|
|
|
<AppUserInfo />
|
|
|
|
</template>
|
|
|
|
<template #content>
|
|
|
|
<Spin :spinning="spinning" wrapper-class-name="app-content-spin">
|
|
|
|
<AppModelSelect
|
|
|
|
v-if="conversationDefaultShow"
|
|
|
|
:active-index="modelIndex"
|
|
|
|
:options="modelOptions"
|
|
|
|
@change="handleModel"
|
|
|
|
>
|
|
|
|
</AppModelSelect>
|
|
|
|
<!-- 默认导语 -->
|
|
|
|
<AppConversationDefault
|
|
|
|
v-if="conversationDefaultShow"
|
|
|
|
:is-pick="true"
|
|
|
|
:is-hot="false"
|
|
|
|
:role-list="roleList"
|
|
|
|
:apply-list="applyList"
|
|
|
|
height="calc(100% - 160px)"
|
|
|
|
@handle-pick="handlePick"
|
|
|
|
>
|
|
|
|
</AppConversationDefault>
|
|
|
|
|
|
|
|
<!-- 消息列表 -->
|
|
|
|
<AppMessage
|
|
|
|
v-if="!conversationDefaultShow && appMessageShow"
|
|
|
|
ref="appMessageRef"
|
|
|
|
class="pl-27 pr-5"
|
|
|
|
height="calc(100% - 160px)"
|
|
|
|
:list="messageList"
|
|
|
|
@on-scroll-top="onScrollTop"
|
|
|
|
@reload-message="reloadMessage"
|
|
|
|
>
|
|
|
|
</AppMessage>
|
|
|
|
</Spin>
|
|
|
|
|
|
|
|
<!-- 发送框 -->
|
|
|
|
<div v-if="!fileList.length" class="upload-box absolute right-0 bottom-5 pl-52 pr-32 mt-10s w-full">
|
|
|
|
<UploadDragger
|
|
|
|
name="file"
|
|
|
|
:multiple="false"
|
|
|
|
:max-count="1"
|
|
|
|
:show-upload-list="false"
|
|
|
|
:custom-request="handleCustomRequest"
|
|
|
|
@change="handleUploadChange"
|
|
|
|
@drop="handleUploadDrop"
|
|
|
|
>
|
|
|
|
<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="absolute left-0 bottom-35 pl-52 pr-32 mt-10 ">
|
|
|
|
已上传图片:
|
|
|
|
<Image
|
|
|
|
class="rounded"
|
|
|
|
:width="60"
|
|
|
|
:height="60"
|
|
|
|
:src="fileList[0]"
|
|
|
|
fallback="data:image/png;base64,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"
|
|
|
|
></Image>
|
|
|
|
</div>
|
|
|
|
<AppTextarea
|
|
|
|
class="pl-52 pr-32 mt-10 "
|
|
|
|
:btn-loading="sendBtnLoading"
|
|
|
|
@send="handleSend"
|
|
|
|
></AppTextarea>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
</AppContainerBox>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
.upload-box {
|
|
|
|
.upload-icon {
|
|
|
|
width: 50px;
|
|
|
|
height: 50px;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</style>
|