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

22
src/components/AppMessage/index.vue

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

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

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

8
src/enums/menuEnum.ts

@ -15,14 +15,14 @@ export enum MenuTypeEnum {
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
isDeleted: number
roleId: number
roleImg: string
roleName: string
type: number
status: number
title: string

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

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

2
src/utils/mqtt.ts

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

347
src/views/role/index.vue

@ -1,48 +1,347 @@
<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 { AppSubMenuTitle } from '@/components/AppSubMenuTitle'
import { AppSubMenuList } from '@/components/AppSubMenuList'
import { AppRoleDefault } from '@/components/AppRoleDefault'
import type { SubMenuItem } from '@/components/AppSubMenuList/index.d'
import { AppUserInfo } from '@/components/AppUserInfo'
import { SubMenuActionEnum } from '@/components/AppSubMenuList/index.d'
import type { SubMenuItem } from '@/components/AppSubMenuList/index.d'
const subMenuActive = ref(0)
const subMenuList = ref<SubMenuItem[]>([
{
title: '新对话1',
content: '这是一个新的对话哦;啦啦啦',
id: '1',
},
{
title: '新对话2',
content: '这是一个新的对话哦',
id: '2',
import { AppTextarea } from '@/components/AppTextarea'
import { AppMessage } from '@/components/AppMessage'
import type { MessageItem } from '@/components/AppMessage/index.d'
import { useMessageStore } from '@/store/moules/messageStore/index'
import { MenuTypeEnum } from '@/enums/menuEnum'
import { MessageStatusEnum, MessageTypeEnum } from '@/enums/messageEnum'
import { addMessage, conversationList, historyMessage, removeMessage, sendTextToText, updateMessage } from '@/api/base/message'
import { useMqtt } from '@/hooks/useMqtt'
import { useMessage } from '@/hooks/useMessage'
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>
<template>
<AppContainerBox>
<template #subMenu>
<AppSubMenuTitle title="角色会话"></AppSubMenuTitle>
<!-- <div class="px-5 mb-5">
<Button type="primary" class="w-full">
<!-- 标题 -->
<AppSubMenuTitle></AppSubMenuTitle>
<!-- 按钮 -->
<div class="px-5 mb-5">
<Button type="primary" class="w-full" @click="handleAddMessage">
新建会话
</Button>
</div> -->
<AppSubMenuList :list="subMenuList" :active-index="subMenuActive" @change="handleSubMenuChange"></AppSubMenuList>
</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>
<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>
</AppContainerBox>
</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) => {
item.actionType = SubMenuActionEnum.NOT
})
console.log(res)
subMenuList.value = res
subMenuActiveIndex.value = 0
@ -158,7 +157,6 @@ async function getHistoryMessage() {
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) {
@ -213,7 +211,7 @@ async function sendMessage(conversationId: string, question: string): Promise<vo
})
messageStore.setMessagePushItem({
messageType: MessageTypeEnum.AI,
content: '正在思考中...',
content: '正在生成中...',
time: String(new Date().getTime()),
avatar: '',
messageStatus: MessageStatusEnum.LOADING,

Loading…
Cancel
Save