Browse Source

feat:角色会话;

dxj
李朋徽 1 year ago
parent
commit
c2d6721115
  1. 66
      src/components/AppConversationDefault/index.vue
  2. 22
      src/components/AppMessage/index.vue
  3. 9
      src/components/AppPicture/index.d.ts
  4. 8
      src/enums/menuEnum.ts
  5. 2
      src/store/moules/messageStore/index.d.ts
  6. 2
      src/store/moules/userStore/index.ts
  7. 2
      src/utils/mqtt.ts
  8. 347
      src/views/role/index.vue
  9. 4
      src/views/textToImage/index.vue

66
src/components/AppConversationDefault/index.vue

@ -12,6 +12,7 @@ import { AppTopPicks } from '@/components/AppTopPicks'
import type { TopPickItem } from '@/components/AppTopPicks/index.d' import type { TopPickItem } from '@/components/AppTopPicks/index.d'
import { AppPicture } from '@/components/AppPicture' import { AppPicture } from '@/components/AppPicture'
import type { PictureType } from '@/components/AppPicture/index.d' import type { PictureType } from '@/components/AppPicture/index.d'
import { getAppList, getRole } from '@/api/base/role'
withDefaults(defineProps<Props>(), { withDefaults(defineProps<Props>(), {
height: '100%', height: '100%',
@ -29,52 +30,45 @@ const leadData = {
const topPickList = ref<TopPickItem[]>([ const topPickList = ref<TopPickItem[]>([
{ {
id: '1', id: '1',
label: '啦啦啦啦啦啦', label: '写一首赞美祖国的诗',
}, },
{ {
id: '2', id: '2',
label: '啦啦啦啦啦啦', label: '帮我指定一个路旅游计划',
}, },
{ {
id: '3', id: '3',
label: '啦啦啦啦啦啦啦啦啦啦啦啦', label: 'AI会代替人类工作吗?',
}, },
{ {
id: '4', id: '4',
label: '啦啦啦啦啦啦', label: '历史上的今天发生了什么?',
}, },
{ {
id: '5', id: '5',
label: '啦啦啦啦啦啦', label: '红烧牛肉的做法',
}, },
]) ])
const roleList = ref<PictureType[]>([ const roleList = ref<PictureType[]>([])
{ const applyList = ref<PictureType[]>([])
id: '1',
image: TestImg, //
name: '法律顾问', function getRoleData() {
type: '2', getRole(1).then((res) => {
}, roleList.value = res
{ })
id: '1', }
image: TestImg,
name: '法律顾问', //
type: '2', function getAppData() {
}, getAppList().then((res) => {
{ applyList.value = res[0].roleInfoAppModelList
id: '1', })
image: TestImg, }
name: '法律顾问',
type: '2', getRoleData()
}, getAppData()
{
id: '1',
image: TestImg,
name: '法律顾问',
type: '2',
},
])
</script> </script>
<template> <template>
@ -104,8 +98,8 @@ const roleList = ref<PictureType[]>([
v-for="(item, index) in roleList" v-for="(item, index) in roleList"
:key="index" :key="index"
class="picture-item-role" class="picture-item-role"
:name="item.name" :name="item.roleName"
:image="item.image" :image="item.roleImg"
></AppPicture> ></AppPicture>
</div> </div>
</div> </div>
@ -121,11 +115,11 @@ const roleList = ref<PictureType[]>([
</div> </div>
<div class="picture-box flex"> <div class="picture-box flex">
<AppPicture <AppPicture
v-for="(item, index) in roleList" v-for="(item, index) in applyList"
:key="index" :key="index"
class="picture-item" class="picture-item"
:name="item.name" :name="item.roleName"
:image="item.image" :image="item.roleImg"
type="entire" type="entire"
></AppPicture> ></AppPicture>
</div> </div>

22
src/components/AppMessage/index.vue

@ -45,12 +45,10 @@ async function scrollToBottom() {
return return
} }
await nextTick() await nextTick()
setTimeout(() => { if (messageRef.value) {
if (messageRef.value) { defaultScrollTop.value = messageRef.value.scrollTop
defaultScrollTop.value = messageRef.value.scrollTop messageRef.value.scrollTo(0, messageRef.value.scrollHeight)
messageRef.value.scrollTo(0, messageRef.value.scrollHeight) }
}
}, 200)
} }
/** /**
@ -61,8 +59,10 @@ function onScroll() {
return return
} }
// - > 20px // - > -1px
if (messageRef.value.scrollTop - defaultScrollTop.value > 20) { console.log(messageRef.value.scrollTop - defaultScrollTop.value)
if (messageRef.value.scrollTop - defaultScrollTop.value > -1) {
isAutoScroll.value = true isAutoScroll.value = true
} }
else { else {
@ -125,7 +125,9 @@ onMounted(async () => {
<img class="icon-ai" src="@/assets/images/conversation/logo.png" alt=""> <img class="icon-ai" src="@/assets/images/conversation/logo.png" alt="">
<div class="content"> <div class="content">
<MdPreview <MdPreview
v-if="conversationData?.type === MenuTypeEnum.TEXT_TO_TEXT" v-if="
conversationData?.type === MenuTypeEnum.TEXT_TO_TEXT
|| conversationData?.type === MenuTypeEnum.ROLE"
editor-id="preview-only-ai" editor-id="preview-only-ai"
:model-value="item.content" :model-value="item.content"
/> />
@ -224,12 +226,14 @@ onMounted(async () => {
border: 1px solid #e3ebfc; border: 1px solid #e3ebfc;
} }
.error { .error {
width: 100%;
color: red; color: red;
height: 30px; height: 30px;
padding-left: 60px; padding-left: 60px;
margin-top: 10px; margin-top: 10px;
} }
.stop { .stop {
width: 100%;
height: 30px; height: 30px;
color: #4670e3; color: #4670e3;
padding-left: 60px; padding-left: 60px;

9
src/components/AppPicture/index.d.ts vendored

@ -1,6 +1,3 @@
export interface PictureType { import type { ConversationData } from '@/store/moules/messageStore/index.d'
id: string
image: string export interface PictureType extends ConversationData {}
name: string
type: string
}

8
src/enums/menuEnum.ts

@ -15,14 +15,14 @@ export enum MenuTypeEnum {
REPOSITORY, REPOSITORY,
// 任务 // 任务
TASK = 'task', TASK,
// 渠道 // 渠道
CHANNEL = 'channel', CHANNEL,
// 小程序 // 小程序
APPLET = 'applet', APPLET,
// 我的 // 我的
USER = 'user', USER,
} }

2
src/store/moules/messageStore/index.d.ts vendored

@ -9,6 +9,8 @@ export interface ConversationData {
id: string id: string
isDeleted: number isDeleted: number
roleId: number roleId: number
roleImg: string
roleName: string
type: number type: number
status: number status: number
title: string title: string

2
src/store/moules/userStore/index.ts

@ -43,8 +43,6 @@ export const useUserStore = defineStore('useUserStore', {
async login(params: TokenParams) { async login(params: TokenParams) {
return new Promise<any>((resolve, reject) => { return new Promise<any>((resolve, reject) => {
token(params).then((res) => { token(params).then((res) => {
console.log(res)
this.setToken(crypto.encryptAES(res.access_token, crypto.localKey)) this.setToken(crypto.encryptAES(res.access_token, crypto.localKey))
this.setUserInfo(crypto.encryptAES(JSON.stringify(res), crypto.localKey)) this.setUserInfo(crypto.encryptAES(JSON.stringify(res), crypto.localKey))
this.getChatInfoFun() this.getChatInfoFun()

2
src/utils/mqtt.ts

@ -63,8 +63,6 @@ export class MqttService {
'message', 'message',
(receivedTopic: string, message: { toString: () => any }) => { (receivedTopic: string, message: { toString: () => any }) => {
if (receivedTopic === topic) { if (receivedTopic === topic) {
console.log(JSON.parse(message.toString()))
callback(JSON.parse(message.toString())) callback(JSON.parse(message.toString()))
} }
}, },

347
src/views/role/index.vue

@ -1,48 +1,347 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
import { onBeforeRouteLeave } from 'vue-router'
import { Button, Spin, message } from 'ant-design-vue'
import { AppContainerBox } from '@/components/AppContainerBox' import { AppContainerBox } from '@/components/AppContainerBox'
import { AppSubMenuTitle } from '@/components/AppSubMenuTitle' import { AppSubMenuTitle } from '@/components/AppSubMenuTitle'
import { AppSubMenuList } from '@/components/AppSubMenuList' import { AppSubMenuList } from '@/components/AppSubMenuList'
import { AppRoleDefault } from '@/components/AppRoleDefault' import { AppRoleDefault } from '@/components/AppRoleDefault'
import type { SubMenuItem } from '@/components/AppSubMenuList/index.d'
import { AppUserInfo } from '@/components/AppUserInfo' import { AppUserInfo } from '@/components/AppUserInfo'
import { SubMenuActionEnum } from '@/components/AppSubMenuList/index.d'
import type { SubMenuItem } from '@/components/AppSubMenuList/index.d'
const subMenuActive = ref(0) import { AppTextarea } from '@/components/AppTextarea'
const subMenuList = ref<SubMenuItem[]>([ import { AppMessage } from '@/components/AppMessage'
{ import type { MessageItem } from '@/components/AppMessage/index.d'
title: '新对话1', import { useMessageStore } from '@/store/moules/messageStore/index'
content: '这是一个新的对话哦;啦啦啦', import { MenuTypeEnum } from '@/enums/menuEnum'
id: '1', import { MessageStatusEnum, MessageTypeEnum } from '@/enums/messageEnum'
}, import { addMessage, conversationList, historyMessage, removeMessage, sendTextToText, updateMessage } from '@/api/base/message'
{ import { useMqtt } from '@/hooks/useMqtt'
title: '新对话2', import { useMessage } from '@/hooks/useMessage'
content: '这是一个新的对话哦',
id: '2', const { createConfirm } = useMessage()
const messageStore = useMessageStore()
const sendBtnLoading = ref(false)
const subMenuActiveIndex = ref(0) //
const subMenuActionIndex = ref(-1) //
const subMenuList = ref<SubMenuItem[]>([])
const subMenuInputValue = ref<string>('')
const appMessageRef = ref()
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
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(() => {
message.success('删除成功')
getConversationList()
})
},
})
}
}
function handleSubMenuChange(index: number) { /**
subMenuActive.value = index * @description: 切换会话
*/
async function handleSubMenuChange(index: number) {
if (messageStatus.value !== MessageStatusEnum.END) {
message.warn('请先结束对话')
return
}
subMenuActiveIndex.value = index
historyMessageParams.value.current = 1
historyMessageParams.value.total = 0
messageStore.setConversationData(subMenuList.value[subMenuActiveIndex.value])
messageStore.setMessageClear()
useMqtt().end()
useMqtt().connect()
getHistoryMessage()
//
appMessageShow.value = false
setTimeout(() => {
appMessageShow.value = true
}, 0)
}
/**
* @description: 发送消息
*/
async function handleSend(value: string) {
sendBtnLoading.value = true
conversationDefaultShow.value = false
if (!conversationData.value) {
return
}
if (subMenuActiveIndex.value === -1) {
try {
await addMessage({ type: MenuTypeEnum.ROLE, title: '新的对话' })
await getConversationList()
await nextTick()
sendMessage(conversationData.value.id, value)
}
catch (error) {
message.error('新建对话失败,请稍后重试!')
}
}
else {
sendMessage(conversationData.value.id, value)
}
}
/**
* @description: 获取会话列表
*/
async function getConversationList() {
const res = await conversationList(MenuTypeEnum.ROLE)
res.forEach((item: SubMenuItem) => {
item.actionType = SubMenuActionEnum.NOT
})
console.log(res)
subMenuList.value = res
subMenuActiveIndex.value = 0
if (subMenuList.value.length) {
await handleSubMenuChange(0)
}
}
/**
* @description: 获取历史对话记录
*/
async function getHistoryMessage() {
if (!conversationData.value) {
return
}
spinning.value = true
historyMessageParams.value.conversationId = conversationData.value.id
const res = await historyMessage(historyMessageParams.value)
console.log(res)
spinning.value = false
if (!res || !res.records || !res.records.length) {
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)
})
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()
//
appMessageRef.value.seamlessScrollToTop()
}
}
/**
* @description: 发送消息hook
*/
async function sendMessage(conversationId: string, question: string): Promise<void> {
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,
})
if (!messageStore.getConversationData) {
return
}
sendTextToText({
conversationId,
question,
}).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() {
conversationDefaultShow.value = true
subMenuActiveIndex.value = -1
}
//
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(() => {
getConversationList()
})
onUnmounted(() => {
useMqtt().end()
})
//
onBeforeRouteLeave(() => {
if (messageStatus.value !== MessageStatusEnum.END) {
message.warn('请先结束对话')
return false
}
})
</script> </script>
<template> <template>
<AppContainerBox> <AppContainerBox>
<template #subMenu> <template #subMenu>
<AppSubMenuTitle title="角色会话"></AppSubMenuTitle> <!-- 标题 -->
<!-- <div class="px-5 mb-5"> <AppSubMenuTitle></AppSubMenuTitle>
<Button type="primary" class="w-full">
<!-- 按钮 -->
<div class="px-5 mb-5">
<Button type="primary" class="w-full" @click="handleAddMessage">
新建会话 新建会话
</Button> </Button>
</div> --> </div>
<AppSubMenuList :list="subMenuList" :active-index="subMenuActive" @change="handleSubMenuChange"></AppSubMenuList>
<!-- 会话列表 -->
<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 /> <AppUserInfo />
</template> </template>
<template #content> <template #content>
<AppRoleDefault></AppRoleDefault> <Spin :spinning="spinning" wrapper-class-name="app-content-spin">
<!-- 默认导语 -->
<AppRoleDefault
v-if="conversationDefaultShow"
height="calc(100% - 120px)"
>
</AppRoleDefault>
<!-- 消息列表 -->
<AppMessage
v-if="!conversationDefaultShow && appMessageShow"
ref="appMessageRef"
class="pl-27 pr-5"
:list="messageList"
@on-scroll-top="onScrollTop"
@reload-message="reloadMessage"
>
</AppMessage>
</Spin>
<!-- 发送框 -->
<AppTextarea
class="pl-52 pr-32 mt-10"
:btn-loading="sendBtnLoading"
@send="handleSend"
></AppTextarea>
</template> </template>
</AppContainerBox> </AppContainerBox>
</template> </template>
<style scoped></style> <style lang="scss" scoped>
</style>

4
src/views/textToImage/index.vue

@ -139,7 +139,6 @@ async function getConversationList() {
res.forEach((item: SubMenuItem) => { res.forEach((item: SubMenuItem) => {
item.actionType = SubMenuActionEnum.NOT item.actionType = SubMenuActionEnum.NOT
}) })
console.log(res)
subMenuList.value = res subMenuList.value = res
subMenuActiveIndex.value = 0 subMenuActiveIndex.value = 0
@ -158,7 +157,6 @@ async function getHistoryMessage() {
spinning.value = true spinning.value = true
historyMessageParams.value.conversationId = conversationData.value.id historyMessageParams.value.conversationId = conversationData.value.id
const res = await historyMessage(historyMessageParams.value) const res = await historyMessage(historyMessageParams.value)
console.log('文生图', res)
spinning.value = false spinning.value = false
if (!res || !res.records || !res.records.length) { if (!res || !res.records || !res.records.length) {
@ -213,7 +211,7 @@ async function sendMessage(conversationId: string, question: string): Promise<vo
}) })
messageStore.setMessagePushItem({ messageStore.setMessagePushItem({
messageType: MessageTypeEnum.AI, messageType: MessageTypeEnum.AI,
content: '正在思考中...', content: '正在生成中...',
time: String(new Date().getTime()), time: String(new Date().getTime()),
avatar: '', avatar: '',
messageStatus: MessageStatusEnum.LOADING, messageStatus: MessageStatusEnum.LOADING,

Loading…
Cancel
Save