青鸟ai,pc版仓库
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

451 lines
13 KiB

<script setup lang="ts">
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
import { Button, Spin, message } from 'ant-design-vue'
import { AppContainerBox } from '@/components/AppContainerBox'
import { AppSubMenuTitle } from '@/components/AppSubMenuTitle'
import { AppSubMenuList } from '@/components/AppSubMenuList'
import { AppRoleDefault } from '@/components/AppRoleDefault'
import { AppUserInfo } from '@/components/AppUserInfo'
import { SubMenuActionEnum } from '@/components/AppSubMenuList/index.d'
import type { SubMenuItem } from '@/components/AppSubMenuList/index.d'
import { AppTextarea } from '@/components/AppTextarea'
import { AppMessage } from '@/components/AppMessage'
// import { AppModelSelect } from '@/components/AppModelSelect'
import type { ModelSelect } from '@/components/AppModelSelect/index.d'
import type { RoleData, RoleInfoAppModel } from '@/components/AppRoleDefault/index.d'
import type { MessageItem } from '@/components/AppMessage/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, sendTextToText, updateMessage } from '@/api/base/message'
import { getRoleDecs } from '@/api/base/role'
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: '同聪3.5',
value: ModelTypeEnum.GPT3,
},
{
label: '同聪4.0',
value: ModelTypeEnum.GPT4,
},
]
const modelIndex = ref(0)
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)
const elIndex = ref(0) // 新分页数据的数量
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()
await handleSubMenuChange(subMenuActiveIndex.value)
})
},
})
}
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 (!conversationData.value) {
return
}
sendMessage(conversationData.value.id, value, conversationData.value.roleId)
}
/**
* @description: 获取会话列表
*/
async function getConversationList() {
const res = await conversationList(MenuTypeEnum.ROLE)
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)
spinning.value = false
if (!res || !res.records || !res.records.length) {
getRoleDecs(String(conversationData.value.roleId)).then((res) => {
conversationDefaultShow.value = false
messageStore.setMessagePushItem({
messageType: MessageTypeEnum.DESCRIBE,
content: res,
time: String(new Date().getTime()),
avatar: '',
messageStatus: MessageStatusEnum.END,
})
})
return
}
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)
})
elIndex.value = res.records.length
conversationDefaultShow.value = false
}
/**
* @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()
// 无缝滚动
await appMessageRef.value.getElementOffsetTop()
await appMessageRef.value.seamlessScrollToTop()
}
}
/**
* @description: 发送消息hook
*/
async function sendMessage(conversationId: string, question: string, roleId: number): 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,
})
sendTextToText({
conversationId,
question,
roleId,
modelType: modelOptions[modelIndex.value].value,
}).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, conversationData.value.roleId)
}
/**
* @description: 点击新建会话按钮
*/
function handleAddMessage() {
if (messageStatus.value !== MessageStatusEnum.END) {
message.warn('请先结束对话')
return
}
conversationDefaultShow.value = true
subMenuActiveIndex.value = -1
}
/**
* @description: 选择角色
*/
async function handleRole(item: RoleData) {
const obj = subMenuList.value.find(v => String(v.roleId) === item.id)
if (obj) {
const index = subMenuList.value.findIndex(v => String(v.roleId) === item.id)
await handleSubMenuChange(index)
getHistoryMessage()
}
else {
await addMessage({ type: MenuTypeEnum.ROLE, title: item.roleName, roleId: item.id, sort: subMenuList.value.length + 1, modelType: modelOptions[modelIndex.value].value })
await getConversationList()
await nextTick()
const index = subMenuList.value.findIndex(v => String(v.roleId) === item.id)
subMenuActiveIndex.value = index
await handleSubMenuChange(index)
getHistoryMessage()
}
}
/**
* @description: 选择应用
*/
async function handleApply(item: RoleInfoAppModel) {
const obj = subMenuList.value.find(v => v.roleId === item.id)
if (obj) {
const index = subMenuList.value.findIndex(v => v.roleId === item.id)
await handleSubMenuChange(index)
getHistoryMessage()
}
else {
await addMessage({ type: MenuTypeEnum.ROLE, title: item.roleName, roleId: String(item.id), sort: subMenuList.value.length + 1, modelType: modelOptions[modelIndex.value].value })
await getConversationList()
await nextTick()
const index = subMenuList.value.findIndex(v => v.roleId === item.id)
subMenuActiveIndex.value = index
await handleSubMenuChange(index)
getHistoryMessage()
}
}
/**
* @description: gpt模型切换
*/
// function handleModel(index: number) {
// modelIndex.value = index
// }
// 下面是子菜单操作项
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()
})
</script>
<template>
<AppContainerBox>
<template #subMenu>
<!-- 标题 -->
<AppSubMenuTitle title="角色会话"></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> -->
<!-- 默认导语 -->
<AppRoleDefault
v-if="conversationDefaultShow"
height="calc(100% - 120px)"
@handle-role="handleRole"
@handle-apply="handleApply"
>
</AppRoleDefault>
<div class="h-full flex flex-col">
<!-- 消息列表 -->
<AppMessage
v-if="!conversationDefaultShow && appMessageShow"
ref="appMessageRef"
:key="MenuTypeEnum.ROLE"
class="pl-27 pr-5"
:el-index="elIndex"
:list="messageList"
@on-scroll-top="onScrollTop"
@reload-message="reloadMessage"
>
</AppMessage>
<!-- 发送框 -->
<AppTextarea
v-if="!conversationDefaultShow"
class="pl-44 pr-24 mt-10"
:btn-loading="sendBtnLoading"
@send="handleSend"
></AppTextarea>
</div>
</Spin>
</template>
</AppContainerBox>
</template>
<style lang="scss" scoped>
</style>