【代码优化】AI:聊天对话 index.vue 代码梳理 80%(message 部分)
parent
1a6afa3263
commit
6224602152
|
@ -1,8 +1,8 @@
|
||||||
<!-- AI 对话 -->
|
<!-- AI 对话 -->
|
||||||
<template>
|
<template>
|
||||||
<el-aside width="260px" class="conversation-container" style="height: 100%">
|
<el-aside width="260px" class="conversation-container h-100%">
|
||||||
<!-- 左顶部:对话 -->
|
<!-- 左顶部:对话 -->
|
||||||
<div style="height: 100%">
|
<div class="h-100%">
|
||||||
<el-button class="w-1/1 btn-new-conversation" type="primary" @click="createConversation">
|
<el-button class="w-1/1 btn-new-conversation" type="primary" @click="createConversation">
|
||||||
<Icon icon="ep:plus" class="mr-5px" />
|
<Icon icon="ep:plus" class="mr-5px" />
|
||||||
新建对话
|
新建对话
|
||||||
|
@ -23,8 +23,9 @@
|
||||||
|
|
||||||
<!-- 左中间:对话列表 -->
|
<!-- 左中间:对话列表 -->
|
||||||
<div class="conversation-list">
|
<div class="conversation-list">
|
||||||
|
<!-- 情况一:加载中 -->
|
||||||
<el-empty v-if="loading" description="." :v-loading="loading" />
|
<el-empty v-if="loading" description="." :v-loading="loading" />
|
||||||
|
<!-- 情况二:按照 group 分组,展示聊天会话 list 列表 -->
|
||||||
<div v-for="conversationKey in Object.keys(conversationMap)" :key="conversationKey">
|
<div v-for="conversationKey in Object.keys(conversationMap)" :key="conversationKey">
|
||||||
<div
|
<div
|
||||||
class="conversation-item classify-title"
|
class="conversation-item classify-title"
|
||||||
|
@ -50,7 +51,7 @@
|
||||||
<span class="title">{{ conversation.title }}</span>
|
<span class="title">{{ conversation.title }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="button-wrapper" v-show="hoverConversationId === conversation.id">
|
<div class="button-wrapper" v-show="hoverConversationId === conversation.id">
|
||||||
<el-button class="btn" link @click.stop="handlerTop(conversation)">
|
<el-button class="btn" link @click.stop="handleTop(conversation)">
|
||||||
<el-icon title="置顶" v-if="!conversation.pinned"><Top /></el-icon>
|
<el-icon title="置顶" v-if="!conversation.pinned"><Top /></el-icon>
|
||||||
<el-icon title="置顶" v-if="conversation.pinned"><Bottom /></el-icon>
|
<el-icon title="置顶" v-if="conversation.pinned"><Bottom /></el-icon>
|
||||||
</el-button>
|
</el-button>
|
||||||
|
@ -68,13 +69,12 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 底部站位 -->
|
<!-- 底部占位 -->
|
||||||
<div style="height: 160px; width: 100%"></div>
|
<div class="h-160px w-100%"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 左底部:工具栏 -->
|
<!-- 左底部:工具栏 -->
|
||||||
<!-- TODO @fan:下面两个 icon,可以使用类似 <Icon icon="ep:question-filled" /> 替代哈 -->
|
|
||||||
<div class="tool-box">
|
<div class="tool-box">
|
||||||
<div @click="handleRoleRepository">
|
<div @click="handleRoleRepository">
|
||||||
<Icon icon="ep:user" />
|
<Icon icon="ep:user" />
|
||||||
|
@ -86,19 +86,16 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ============= 额外组件 ============= -->
|
|
||||||
|
|
||||||
<!-- 角色仓库抽屉 -->
|
<!-- 角色仓库抽屉 -->
|
||||||
<el-drawer v-model="drawer" title="角色仓库" size="754px">
|
<el-drawer v-model="roleRepositoryOpen" title="角色仓库" size="754px">
|
||||||
<Role />
|
<RoleRepository />
|
||||||
</el-drawer>
|
</el-drawer>
|
||||||
</el-aside>
|
</el-aside>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ChatConversationApi, ChatConversationVO } from '@/api/ai/chat/conversation'
|
import { ChatConversationApi, ChatConversationVO } from '@/api/ai/chat/conversation'
|
||||||
import { ref } from 'vue'
|
import RoleRepository from '../role/RoleRepository.vue'
|
||||||
import Role from '../role/index.vue'
|
|
||||||
import { Bottom, Top } from '@element-plus/icons-vue'
|
import { Bottom, Top } from '@element-plus/icons-vue'
|
||||||
import roleAvatarDefaultImg from '@/assets/ai/gpt.svg'
|
import roleAvatarDefaultImg from '@/assets/ai/gpt.svg'
|
||||||
|
|
||||||
|
@ -106,11 +103,10 @@ const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
// 定义属性
|
// 定义属性
|
||||||
const searchName = ref<string>('') // 对话搜索
|
const searchName = ref<string>('') // 对话搜索
|
||||||
const activeConversationId = ref<string | null>(null) // 选中的对话,默认为 null
|
const activeConversationId = ref<number | null>(null) // 选中的对话,默认为 null
|
||||||
const hoverConversationId = ref<string | null>(null) // 悬浮上去的对话
|
const hoverConversationId = ref<number | null>(null) // 悬浮上去的对话
|
||||||
const conversationList = ref([] as ChatConversationVO[]) // 对话列表
|
const conversationList = ref([] as ChatConversationVO[]) // 对话列表
|
||||||
const conversationMap = ref<any>({}) // 对话分组 (置顶、今天、三天前、一星期前、一个月前)
|
const conversationMap = ref<any>({}) // 对话分组 (置顶、今天、三天前、一星期前、一个月前)
|
||||||
const drawer = ref<boolean>(false) // 角色仓库抽屉 TODO @fan:roleDrawer 会不会好点哈
|
|
||||||
const loading = ref<boolean>(false) // 加载中
|
const loading = ref<boolean>(false) // 加载中
|
||||||
const loadingTime = ref<any>() // 加载中定时器
|
const loadingTime = ref<any>() // 加载中定时器
|
||||||
|
|
||||||
|
@ -130,75 +126,58 @@ const emits = defineEmits([
|
||||||
'onConversationDelete'
|
'onConversationDelete'
|
||||||
])
|
])
|
||||||
|
|
||||||
/**
|
/** 搜索对话 */
|
||||||
* 对话 - 搜索
|
|
||||||
*/
|
|
||||||
const searchConversation = async (e) => {
|
const searchConversation = async (e) => {
|
||||||
// 恢复数据
|
// 恢复数据
|
||||||
if (!searchName.value.trim().length) {
|
if (!searchName.value.trim().length) {
|
||||||
conversationMap.value = await conversationTimeGroup(conversationList.value)
|
conversationMap.value = await getConversationGroupByCreateTime(conversationList.value)
|
||||||
} else {
|
} else {
|
||||||
// 过滤
|
// 过滤
|
||||||
const filterValues = conversationList.value.filter((item) => {
|
const filterValues = conversationList.value.filter((item) => {
|
||||||
return item.title.includes(searchName.value.trim())
|
return item.title.includes(searchName.value.trim())
|
||||||
})
|
})
|
||||||
conversationMap.value = await conversationTimeGroup(filterValues)
|
conversationMap.value = await getConversationGroupByCreateTime(filterValues)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** 点击对话 */
|
||||||
* 对话 - 点击
|
const handleConversationClick = async (id: number) => {
|
||||||
*/
|
|
||||||
const handleConversationClick = async (id: string) => {
|
|
||||||
// 过滤出选中的对话
|
// 过滤出选中的对话
|
||||||
const filterConversation = conversationList.value.filter((item) => {
|
const filterConversation = conversationList.value.filter((item) => {
|
||||||
return item.id === id
|
return item.id === id
|
||||||
})
|
})
|
||||||
// 回调 onConversationClick
|
// 回调 onConversationClick
|
||||||
// TODO @fan: 这里 idea 会报黄色警告,有办法解下么?
|
// noinspection JSVoidFunctionReturnValueUsed
|
||||||
const res = emits('onConversationClick', filterConversation[0])
|
const success = emits('onConversationClick', filterConversation[0])
|
||||||
// 切换对话
|
// 切换对话
|
||||||
if (res) {
|
if (success) {
|
||||||
activeConversationId.value = id
|
activeConversationId.value = id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** 获取对话列表 */
|
||||||
* 对话 - 获取列表
|
|
||||||
*/
|
|
||||||
const getChatConversationList = async () => {
|
const getChatConversationList = async () => {
|
||||||
try {
|
try {
|
||||||
// 0. 加载中
|
// 加载中
|
||||||
loadingTime.value = setTimeout(() => {
|
loadingTime.value = setTimeout(() => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
}, 50)
|
}, 50)
|
||||||
// 1. 获取 对话数据
|
|
||||||
const res = await ChatConversationApi.getChatConversationMyList()
|
// 1.1 获取 对话数据
|
||||||
// 2. 排序
|
conversationList.value = await ChatConversationApi.getChatConversationMyList()
|
||||||
res.sort((a, b) => {
|
// 1.2 排序
|
||||||
|
conversationList.value.sort((a, b) => {
|
||||||
return b.createTime - a.createTime
|
return b.createTime - a.createTime
|
||||||
})
|
})
|
||||||
conversationList.value = res
|
// 1.3 没有任何对话情况
|
||||||
// 3. 默认选中
|
|
||||||
if (!activeId?.value) {
|
|
||||||
// await handleConversationClick(res[0].id)
|
|
||||||
} else {
|
|
||||||
// tip: 删除的刚好是选中的,那么需要重新挑选一个来进行选中
|
|
||||||
// const filterConversationList = conversationList.value.filter(item => {
|
|
||||||
// return item.id === activeId.value
|
|
||||||
// })
|
|
||||||
// if (filterConversationList.length <= 0) {
|
|
||||||
// await handleConversationClick(res[0].id)
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
// 4. 没有任何对话情况
|
|
||||||
if (conversationList.value.length === 0) {
|
if (conversationList.value.length === 0) {
|
||||||
activeConversationId.value = null
|
activeConversationId.value = null
|
||||||
conversationMap.value = {}
|
conversationMap.value = {}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 5. 对话根据时间分组(置顶、今天、一天前、三天前、七天前、30天前)
|
|
||||||
conversationMap.value = await conversationTimeGroup(conversationList.value)
|
// 2. 对话根据时间分组(置顶、今天、一天前、三天前、七天前、30 天前)
|
||||||
|
conversationMap.value = await getConversationGroupByCreateTime(conversationList.value)
|
||||||
} finally {
|
} finally {
|
||||||
// 清理定时器
|
// 清理定时器
|
||||||
if (loadingTime.value) {
|
if (loadingTime.value) {
|
||||||
|
@ -209,8 +188,10 @@ const getChatConversationList = async () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const conversationTimeGroup = async (list: ChatConversationVO[]) => {
|
/** 按照 creteTime 创建时间,进行分组 */
|
||||||
|
const getConversationGroupByCreateTime = async (list: ChatConversationVO[]) => {
|
||||||
// 排序、指定、时间分组(今天、一天前、三天前、七天前、30天前)
|
// 排序、指定、时间分组(今天、一天前、三天前、七天前、30天前)
|
||||||
|
// noinspection NonAsciiCharacters
|
||||||
const groupMap = {
|
const groupMap = {
|
||||||
置顶: [],
|
置顶: [],
|
||||||
今天: [],
|
今天: [],
|
||||||
|
@ -233,7 +214,7 @@ const conversationTimeGroup = async (list: ChatConversationVO[]) => {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// 计算时间差(单位:毫秒)
|
// 计算时间差(单位:毫秒)
|
||||||
const diff = now - conversation.updateTime
|
const diff = now - conversation.createTime
|
||||||
// 根据时间间隔判断
|
// 根据时间间隔判断
|
||||||
if (diff < oneDay) {
|
if (diff < oneDay) {
|
||||||
groupMap['今天'].push(conversation)
|
groupMap['今天'].push(conversation)
|
||||||
|
@ -250,9 +231,7 @@ const conversationTimeGroup = async (list: ChatConversationVO[]) => {
|
||||||
return groupMap
|
return groupMap
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** 新建对话 */
|
||||||
* 对话 - 新建
|
|
||||||
*/
|
|
||||||
const createConversation = async () => {
|
const createConversation = async () => {
|
||||||
// 1. 新建对话
|
// 1. 新建对话
|
||||||
const conversationId = await ChatConversationApi.createChatConversationMy(
|
const conversationId = await ChatConversationApi.createChatConversationMy(
|
||||||
|
@ -266,9 +245,7 @@ const createConversation = async () => {
|
||||||
emits('onConversationCreate')
|
emits('onConversationCreate')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** 修改对话的标题 */
|
||||||
* 对话 - 更新标题
|
|
||||||
*/
|
|
||||||
const updateConversationTitle = async (conversation: ChatConversationVO) => {
|
const updateConversationTitle = async (conversation: ChatConversationVO) => {
|
||||||
// 1. 二次确认
|
// 1. 二次确认
|
||||||
const { value } = await ElMessageBox.prompt('修改标题', {
|
const { value } = await ElMessageBox.prompt('修改标题', {
|
||||||
|
@ -296,9 +273,7 @@ const updateConversationTitle = async (conversation: ChatConversationVO) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** 删除聊天对话 */
|
||||||
* 删除聊天对话
|
|
||||||
*/
|
|
||||||
const deleteChatConversation = async (conversation: ChatConversationVO) => {
|
const deleteChatConversation = async (conversation: ChatConversationVO) => {
|
||||||
try {
|
try {
|
||||||
// 删除的二次确认
|
// 删除的二次确认
|
||||||
|
@ -313,11 +288,26 @@ const deleteChatConversation = async (conversation: ChatConversationVO) => {
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** 清空对话 */
|
||||||
* 对话置顶
|
const handleClearConversation = async () => {
|
||||||
*/
|
try {
|
||||||
// TODO @fan:应该是 handleXXX,handler 是名词哈
|
await message.confirm('确认后对话会全部清空,置顶的对话除外。')
|
||||||
const handlerTop = async (conversation: ChatConversationVO) => {
|
await ChatConversationApi.deleteChatConversationMyByUnpinned()
|
||||||
|
ElMessage({
|
||||||
|
message: '操作成功!',
|
||||||
|
type: 'success'
|
||||||
|
})
|
||||||
|
// 清空 对话 和 对话内容
|
||||||
|
activeConversationId.value = null
|
||||||
|
// 获取 对话列表
|
||||||
|
await getChatConversationList()
|
||||||
|
// 回调 方法
|
||||||
|
emits('onConversationClear')
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 对话置顶 */
|
||||||
|
const handleTop = async (conversation: ChatConversationVO) => {
|
||||||
// 更新对话置顶
|
// 更新对话置顶
|
||||||
conversation.pinned = !conversation.pinned
|
conversation.pinned = !conversation.pinned
|
||||||
await ChatConversationApi.updateChatConversationMy(conversation)
|
await ChatConversationApi.updateChatConversationMy(conversation)
|
||||||
|
@ -325,60 +315,29 @@ const handlerTop = async (conversation: ChatConversationVO) => {
|
||||||
await getChatConversationList()
|
await getChatConversationList()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO @fan:类似 ============ 分块的,最后后面也有 ============ 哈
|
// ============ 角色仓库 ============
|
||||||
// ============ 角色仓库
|
|
||||||
|
|
||||||
/**
|
/** 角色仓库抽屉 */
|
||||||
* 角色仓库抽屉
|
const roleRepositoryOpen = ref<boolean>(false) // 角色仓库是否打开
|
||||||
*/
|
|
||||||
const handleRoleRepository = async () => {
|
const handleRoleRepository = async () => {
|
||||||
drawer.value = !drawer.value
|
roleRepositoryOpen.value = !roleRepositoryOpen.value
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============= 清空对话
|
/** 监听选中的对话 */
|
||||||
|
|
||||||
/**
|
|
||||||
* 清空对话
|
|
||||||
*/
|
|
||||||
const handleClearConversation = async () => {
|
|
||||||
// TODO @fan:可以使用 await message.confirm( 简化,然后使用 await 改成同步的逻辑,会更简洁
|
|
||||||
ElMessageBox.confirm('确认后对话会全部清空,置顶的对话除外。', '确认提示', {
|
|
||||||
confirmButtonText: '确认',
|
|
||||||
cancelButtonText: '取消',
|
|
||||||
type: 'warning'
|
|
||||||
})
|
|
||||||
.then(async () => {
|
|
||||||
await ChatConversationApi.deleteChatConversationMyByUnpinned()
|
|
||||||
ElMessage({
|
|
||||||
message: '操作成功!',
|
|
||||||
type: 'success'
|
|
||||||
})
|
|
||||||
// 清空 对话 和 对话内容
|
|
||||||
activeConversationId.value = null
|
|
||||||
// 获取 对话列表
|
|
||||||
await getChatConversationList()
|
|
||||||
// 回调 方法
|
|
||||||
emits('onConversationClear')
|
|
||||||
})
|
|
||||||
.catch(() => {})
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============ 组件 onMounted
|
|
||||||
|
|
||||||
const { activeId } = toRefs(props)
|
const { activeId } = toRefs(props)
|
||||||
watch(activeId, async (newValue, oldValue) => {
|
watch(activeId, async (newValue, oldValue) => {
|
||||||
// 更新选中
|
|
||||||
activeConversationId.value = newValue as string
|
activeConversationId.value = newValue as string
|
||||||
})
|
})
|
||||||
|
|
||||||
// 定义 public 方法
|
// 定义 public 方法
|
||||||
defineExpose({ createConversation })
|
defineExpose({ createConversation })
|
||||||
|
|
||||||
|
/** 初始化 */
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
// 获取 对话列表
|
// 获取 对话列表
|
||||||
await getChatConversationList()
|
await getChatConversationList()
|
||||||
// 默认选中
|
// 默认选中
|
||||||
if (props.activeId != null) {
|
if (props.activeId) {
|
||||||
activeConversationId.value = props.activeId
|
activeConversationId.value = props.activeId
|
||||||
} else {
|
} else {
|
||||||
// 首次默认选中第一个
|
// 首次默认选中第一个
|
||||||
|
|
|
@ -1,13 +1,20 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="category-list">
|
<div class="category-list">
|
||||||
<div class="category" v-for="(category) in categoryList" :key="category">
|
<div class="category" v-for="category in categoryList" :key="category">
|
||||||
<el-button plain round size="small" v-if="category !== active" @click="handleCategoryClick(category)">{{ category }}</el-button>
|
<el-button
|
||||||
<el-button plain round size="small" v-else type="primary" @click="handleCategoryClick(category)">{{ category }}</el-button>
|
plain
|
||||||
|
round
|
||||||
|
size="small"
|
||||||
|
:type="category === active ? 'primary' : ''"
|
||||||
|
@click="handleCategoryClick(category)"
|
||||||
|
>
|
||||||
|
{{ category }}
|
||||||
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {PropType} from "vue";
|
import { PropType } from 'vue'
|
||||||
|
|
||||||
// 定义属性
|
// 定义属性
|
||||||
defineProps({
|
defineProps({
|
||||||
|
@ -25,11 +32,10 @@ defineProps({
|
||||||
// 定义回调
|
// 定义回调
|
||||||
const emits = defineEmits(['onCategoryClick'])
|
const emits = defineEmits(['onCategoryClick'])
|
||||||
|
|
||||||
// 处理分类点击事件
|
/** 处理分类点击事件 */
|
||||||
const handleCategoryClick = async (category) => {
|
const handleCategoryClick = async (category: string) => {
|
||||||
emits('onCategoryClick', category)
|
emits('onCategoryClick', category)
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.category-list {
|
.category-list {
|
||||||
|
|
|
@ -1,22 +1,22 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="card-list" ref="tabsRef" @scroll="handleTabsScroll">
|
<div class="card-list" ref="tabsRef" @scroll="handleTabsScroll">
|
||||||
<div class="card-item" v-for="role in roleList" :key="role.id">
|
<div class="card-item" v-for="role in roleList" :key="role.id">
|
||||||
<el-card class="card" body-class="card-body">
|
<el-card class="card" body-class="card-body">
|
||||||
<!-- 更多 -->
|
<!-- 更多操作 -->
|
||||||
<div class="more-container" v-if="showMore">
|
<div class="more-container" v-if="showMore">
|
||||||
<el-dropdown @command="handleMoreClick">
|
<el-dropdown @command="handleMoreClick">
|
||||||
<span class="el-dropdown-link">
|
<span class="el-dropdown-link">
|
||||||
<el-button type="text" >
|
<el-button type="text">
|
||||||
<el-icon><More /></el-icon>
|
<el-icon><More /></el-icon>
|
||||||
</el-button>
|
</el-button>
|
||||||
</span>
|
</span>
|
||||||
<!-- TODO @fan:下面两个 icon,可以使用类似 <Icon icon="ep:question-filled" /> 替代哈 -->
|
<!-- TODO @fan:下面两个 icon,可以使用类似 <Icon icon="ep:question-filled" /> 替代哈 -->
|
||||||
<template #dropdown>
|
<template #dropdown>
|
||||||
<el-dropdown-menu>
|
<el-dropdown-menu>
|
||||||
<el-dropdown-item :command="['edit', role]" >
|
<el-dropdown-item :command="['edit', role]">
|
||||||
<el-icon><EditPen /></el-icon>编辑
|
<el-icon><EditPen /></el-icon>编辑
|
||||||
</el-dropdown-item>
|
</el-dropdown-item>
|
||||||
<el-dropdown-item :command="['delete', role]" style="color: red;" >
|
<el-dropdown-item :command="['delete', role]" style="color: red">
|
||||||
<el-icon><Delete /></el-icon>
|
<el-icon><Delete /></el-icon>
|
||||||
<span>删除</span>
|
<span>删除</span>
|
||||||
</el-dropdown-item>
|
</el-dropdown-item>
|
||||||
|
@ -24,9 +24,9 @@
|
||||||
</template>
|
</template>
|
||||||
</el-dropdown>
|
</el-dropdown>
|
||||||
</div>
|
</div>
|
||||||
<!-- 头像 -->
|
<!-- 角色信息 -->
|
||||||
<div>
|
<div>
|
||||||
<img class="avatar" :src="role.avatar"/>
|
<img class="avatar" :src="role.avatar" />
|
||||||
</div>
|
</div>
|
||||||
<div class="right-container">
|
<div class="right-container">
|
||||||
<div class="content-container">
|
<div class="content-container">
|
||||||
|
@ -43,9 +43,9 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {ChatRoleVO} from '@/api/ai/model/chatRole'
|
import { ChatRoleVO } from '@/api/ai/model/chatRole'
|
||||||
import {PropType, ref} from "vue";
|
import { PropType, ref } from 'vue'
|
||||||
import {Delete, EditPen, More} from "@element-plus/icons-vue";
|
import { Delete, EditPen, More } from '@element-plus/icons-vue'
|
||||||
|
|
||||||
const tabsRef = ref<any>() // tabs ref
|
const tabsRef = ref<any>() // tabs ref
|
||||||
|
|
||||||
|
@ -65,10 +65,11 @@ const props = defineProps({
|
||||||
default: false
|
default: false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 定义钩子
|
// 定义钩子
|
||||||
const emits = defineEmits(['onDelete', 'onEdit', 'onUse', 'onPage'])
|
const emits = defineEmits(['onDelete', 'onEdit', 'onUse', 'onPage'])
|
||||||
|
|
||||||
// more 点击
|
/** 操作:编辑、删除 */
|
||||||
const handleMoreClick = async (data) => {
|
const handleMoreClick = async (data) => {
|
||||||
const type = data[0]
|
const type = data[0]
|
||||||
const role = data[1]
|
const role = data[1]
|
||||||
|
@ -79,28 +80,20 @@ const handleMoreClick = async (data) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用
|
/** 选中 */
|
||||||
const handleUseClick = (role) => {
|
const handleUseClick = (role) => {
|
||||||
emits('onUse', role)
|
emits('onUse', role)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 滚动 */
|
||||||
const handleTabsScroll = async () => {
|
const handleTabsScroll = async () => {
|
||||||
if (tabsRef.value) {
|
if (tabsRef.value) {
|
||||||
const { scrollTop, scrollHeight, clientHeight } = tabsRef.value;
|
const { scrollTop, scrollHeight, clientHeight } = tabsRef.value
|
||||||
console.log('scrollTop', scrollTop)
|
|
||||||
if (scrollTop + clientHeight >= scrollHeight - 20 && !props.loading) {
|
if (scrollTop + clientHeight >= scrollHeight - 20 && !props.loading) {
|
||||||
console.log('分页')
|
|
||||||
// page.value++;
|
|
||||||
// fetchData(page.value);
|
|
||||||
await emits('onPage')
|
await emits('onPage')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
console.log('props', props.roleList)
|
|
||||||
})
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@ -114,11 +107,9 @@ onMounted(() => {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|
||||||
// 卡片列表
|
// 卡片列表
|
||||||
.card-list {
|
.card-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -180,9 +171,6 @@ onMounted(() => {
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
<template>
|
<template>
|
||||||
<el-container class="role-container">
|
<el-container class="role-container">
|
||||||
<ChatRoleForm ref="formRef" @success="handlerAddRoleSuccess" />
|
<ChatRoleForm ref="formRef" @success="handlerAddRoleSuccess" />
|
||||||
<!-- header -->
|
<!-- header -->
|
||||||
<Header title="角色仓库" style="position: relative" />
|
<RoleHeader title="角色仓库" class="relative" />
|
||||||
<!-- main -->
|
<!-- main -->
|
||||||
<el-main class="role-main">
|
<el-main class="role-main">
|
||||||
<div class="search-container">
|
<div class="search-container">
|
||||||
|
@ -18,10 +18,10 @@
|
||||||
@change="getActiveTabsRole"
|
@change="getActiveTabsRole"
|
||||||
/>
|
/>
|
||||||
<el-button
|
<el-button
|
||||||
v-if="activeRole == 'my-role'"
|
v-if="activeTab == 'my-role'"
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="handlerAddRole"
|
@click="handlerAddRole"
|
||||||
style="margin-left: 20px"
|
class="ml-20px"
|
||||||
>
|
>
|
||||||
<!-- TODO @fan:下面两个 icon,可以使用类似 <Icon icon="ep:question-filled" /> 替代哈 -->
|
<!-- TODO @fan:下面两个 icon,可以使用类似 <Icon icon="ep:question-filled" /> 替代哈 -->
|
||||||
<el-icon>
|
<el-icon>
|
||||||
|
@ -31,7 +31,7 @@
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
<!-- tabs -->
|
<!-- tabs -->
|
||||||
<el-tabs v-model="activeRole" class="tabs" @tab-click="handleTabsClick">
|
<el-tabs v-model="activeTab" class="tabs" @tab-click="handleTabsClick">
|
||||||
<el-tab-pane class="role-pane" label="我的角色" name="my-role">
|
<el-tab-pane class="role-pane" label="我的角色" name="my-role">
|
||||||
<RoleList
|
<RoleList
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
|
@ -41,7 +41,7 @@
|
||||||
@on-edit="handlerCardEdit"
|
@on-edit="handlerCardEdit"
|
||||||
@on-use="handlerCardUse"
|
@on-use="handlerCardUse"
|
||||||
@on-page="handlerCardPage('my')"
|
@on-page="handlerCardPage('my')"
|
||||||
style="margin-top: 20px"
|
class="mt-20px"
|
||||||
/>
|
/>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane label="公共角色" name="public-role">
|
<el-tab-pane label="公共角色" name="public-role">
|
||||||
|
@ -57,7 +57,7 @@
|
||||||
@on-edit="handlerCardEdit"
|
@on-edit="handlerCardEdit"
|
||||||
@on-use="handlerCardUse"
|
@on-use="handlerCardUse"
|
||||||
@on-page="handlerCardPage('public')"
|
@on-page="handlerCardPage('public')"
|
||||||
style="margin-top: 20px"
|
class="mt-20px"
|
||||||
loading
|
loading
|
||||||
/>
|
/>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
|
@ -68,27 +68,30 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import Header from '../Header.vue'
|
import RoleHeader from './RoleHeader.vue'
|
||||||
import RoleList from './RoleList.vue'
|
import RoleList from './RoleList.vue'
|
||||||
import ChatRoleForm from '@/views/ai/model/chatRole/ChatRoleForm.vue'
|
import ChatRoleForm from '@/views/ai/model/chatRole/ChatRoleForm.vue'
|
||||||
import RoleCategoryList from './RoleCategoryList.vue'
|
import RoleCategoryList from './RoleCategoryList.vue'
|
||||||
import { ChatRoleApi, ChatRolePageReqVO, ChatRoleVO } from '@/api/ai/model/chatRole'
|
import { ChatRoleApi, ChatRolePageReqVO, ChatRoleVO } from '@/api/ai/model/chatRole'
|
||||||
import { ChatConversationApi, ChatConversationVO } from '@/api/ai/chat/conversation'
|
import { ChatConversationApi, ChatConversationVO } from '@/api/ai/chat/conversation'
|
||||||
import { TabsPaneContext } from 'element-plus'
|
|
||||||
import { Search, User } from '@element-plus/icons-vue'
|
import { Search, User } from '@element-plus/icons-vue'
|
||||||
|
import { TabsPaneContext } from 'element-plus'
|
||||||
|
|
||||||
const router = useRouter() // 路由对象
|
const router = useRouter() // 路由对象
|
||||||
|
|
||||||
// 属性定义
|
// 属性定义
|
||||||
const loading = ref<boolean>(false) // 加载中
|
const loading = ref<boolean>(false) // 加载中
|
||||||
const activeRole = ref<string>('my-role') // 选中的角色 TODO @fan:是不是叫 activeTab 会更明确一点哈。选中的角色,会以为是某个角色
|
const activeTab = ref<string>('my-role') // 选中的角色 Tab
|
||||||
const search = ref<string>('') // 加载中
|
const search = ref<string>('') // 加载中
|
||||||
// TODO @fan:要不 myPage、pubPage,搞成类似 const queryParams = reactive({ ,分别搞成两个大的参数哈?
|
const myRoleParams = reactive({
|
||||||
const myPageNo = ref<number>(1) // my 分页下标
|
pageNo: 1,
|
||||||
const myPageSize = ref<number>(50) // my 分页大小
|
pageSize: 50
|
||||||
|
})
|
||||||
const myRoleList = ref<ChatRoleVO[]>([]) // my 分页大小
|
const myRoleList = ref<ChatRoleVO[]>([]) // my 分页大小
|
||||||
const publicPageNo = ref<number>(1) // public 分页下标
|
const publicRoleParams = reactive({
|
||||||
const publicPageSize = ref<number>(50) // public 分页大小
|
pageNo: 1,
|
||||||
|
pageSize: 50
|
||||||
|
})
|
||||||
const publicRoleList = ref<ChatRoleVO[]>([]) // public 分页大小
|
const publicRoleList = ref<ChatRoleVO[]>([]) // public 分页大小
|
||||||
const activeCategory = ref<string>('全部') // 选择中的分类
|
const activeCategory = ref<string>('全部') // 选择中的分类
|
||||||
const categoryList = ref<string[]>([]) // 角色分类类别
|
const categoryList = ref<string[]>([]) // 角色分类类别
|
||||||
|
@ -96,7 +99,7 @@ const categoryList = ref<string[]>([]) // 角色分类类别
|
||||||
/** tabs 点击 */
|
/** tabs 点击 */
|
||||||
const handleTabsClick = async (tab: TabsPaneContext) => {
|
const handleTabsClick = async (tab: TabsPaneContext) => {
|
||||||
// 设置切换状态
|
// 设置切换状态
|
||||||
activeRole.value = tab.paneName + ''
|
activeTab.value = tab.paneName + ''
|
||||||
// 切换的时候重新加载数据
|
// 切换的时候重新加载数据
|
||||||
await getActiveTabsRole()
|
await getActiveTabsRole()
|
||||||
}
|
}
|
||||||
|
@ -104,12 +107,11 @@ const handleTabsClick = async (tab: TabsPaneContext) => {
|
||||||
/** 获取 my role 我的角色 */
|
/** 获取 my role 我的角色 */
|
||||||
const getMyRole = async (append?: boolean) => {
|
const getMyRole = async (append?: boolean) => {
|
||||||
const params: ChatRolePageReqVO = {
|
const params: ChatRolePageReqVO = {
|
||||||
pageNo: myPageNo.value,
|
...myRoleParams,
|
||||||
pageSize: myPageSize.value,
|
|
||||||
name: search.value,
|
name: search.value,
|
||||||
publicStatus: false
|
publicStatus: false
|
||||||
}
|
}
|
||||||
const { total, list } = await ChatRoleApi.getMyPage(params)
|
const { list } = await ChatRoleApi.getMyPage(params)
|
||||||
if (append) {
|
if (append) {
|
||||||
myRoleList.value.push.apply(myRoleList.value, list)
|
myRoleList.value.push.apply(myRoleList.value, list)
|
||||||
} else {
|
} else {
|
||||||
|
@ -120,8 +122,7 @@ const getMyRole = async (append?: boolean) => {
|
||||||
/** 获取 public role 公共角色 */
|
/** 获取 public role 公共角色 */
|
||||||
const getPublicRole = async (append?: boolean) => {
|
const getPublicRole = async (append?: boolean) => {
|
||||||
const params: ChatRolePageReqVO = {
|
const params: ChatRolePageReqVO = {
|
||||||
pageNo: publicPageNo.value,
|
...publicRoleParams,
|
||||||
pageSize: publicPageSize.value,
|
|
||||||
category: activeCategory.value === '全部' ? '' : activeCategory.value,
|
category: activeCategory.value === '全部' ? '' : activeCategory.value,
|
||||||
name: search.value,
|
name: search.value,
|
||||||
publicStatus: true
|
publicStatus: true
|
||||||
|
@ -136,20 +137,18 @@ const getPublicRole = async (append?: boolean) => {
|
||||||
|
|
||||||
/** 获取选中的 tabs 角色 */
|
/** 获取选中的 tabs 角色 */
|
||||||
const getActiveTabsRole = async () => {
|
const getActiveTabsRole = async () => {
|
||||||
if (activeRole.value === 'my-role') {
|
if (activeTab.value === 'my-role') {
|
||||||
myPageNo.value = 1
|
myRoleParams.pageNo = 1
|
||||||
await getMyRole()
|
await getMyRole()
|
||||||
} else {
|
} else {
|
||||||
publicPageNo.value = 1
|
publicRoleParams.pageNo = 1
|
||||||
await getPublicRole()
|
await getPublicRole()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 获取角色分类列表 */
|
/** 获取角色分类列表 */
|
||||||
const getRoleCategoryList = async () => {
|
const getRoleCategoryList = async () => {
|
||||||
const res = await ChatRoleApi.getCategoryList()
|
categoryList.value = ['全部', ...(await ChatRoleApi.getCategoryList())]
|
||||||
const defaultRole = ['全部']
|
|
||||||
categoryList.value = [...defaultRole, ...res]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 处理分类点击 */
|
/** 处理分类点击 */
|
||||||
|
@ -165,6 +164,10 @@ const formRef = ref()
|
||||||
const handlerAddRole = async () => {
|
const handlerAddRole = async () => {
|
||||||
formRef.value.open('my-create', null, '添加角色')
|
formRef.value.open('my-create', null, '添加角色')
|
||||||
}
|
}
|
||||||
|
/** 编辑角色 */
|
||||||
|
const handlerCardEdit = async (role) => {
|
||||||
|
formRef.value.open('my-update', role.id, '编辑角色')
|
||||||
|
}
|
||||||
|
|
||||||
/** 添加角色成功 */
|
/** 添加角色成功 */
|
||||||
const handlerAddRoleSuccess = async (e) => {
|
const handlerAddRoleSuccess = async (e) => {
|
||||||
|
@ -172,28 +175,22 @@ const handlerAddRoleSuccess = async (e) => {
|
||||||
await getActiveTabsRole()
|
await getActiveTabsRole()
|
||||||
}
|
}
|
||||||
|
|
||||||
// card 删除
|
/** 删除角色 */
|
||||||
const handlerCardDelete = async (role) => {
|
const handlerCardDelete = async (role) => {
|
||||||
await ChatRoleApi.deleteMy(role.id)
|
await ChatRoleApi.deleteMy(role.id)
|
||||||
// 刷新数据
|
// 刷新数据
|
||||||
await getActiveTabsRole()
|
await getActiveTabsRole()
|
||||||
}
|
}
|
||||||
|
|
||||||
// card 编辑
|
/** 角色分页:获取下一页 */
|
||||||
const handlerCardEdit = async (role) => {
|
|
||||||
formRef.value.open('my-update', role.id, '编辑角色')
|
|
||||||
}
|
|
||||||
|
|
||||||
/** card 分页:获取下一页 */
|
|
||||||
const handlerCardPage = async (type) => {
|
const handlerCardPage = async (type) => {
|
||||||
console.log('handlerCardPage', type)
|
|
||||||
try {
|
try {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
if (type === 'public') {
|
if (type === 'public') {
|
||||||
publicPageNo.value++
|
publicRoleParams.pageNo++
|
||||||
await getPublicRole(true)
|
await getPublicRole(true)
|
||||||
} else {
|
} else {
|
||||||
myPageNo.value++
|
myRoleParams.pageNo++
|
||||||
await getMyRole(true)
|
await getMyRole(true)
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -208,10 +205,10 @@ const handlerCardUse = async (role) => {
|
||||||
roleId: role.id
|
roleId: role.id
|
||||||
} as unknown as ChatConversationVO
|
} as unknown as ChatConversationVO
|
||||||
const conversationId = await ChatConversationApi.createChatConversationMy(data)
|
const conversationId = await ChatConversationApi.createChatConversationMy(data)
|
||||||
|
|
||||||
// 2. 跳转页面
|
// 2. 跳转页面
|
||||||
// TODO @fan:最好用 name,后续可能会改~~~
|
|
||||||
await router.push({
|
await router.push({
|
||||||
path: `/ai/chat`,
|
name: 'AiChat',
|
||||||
query: {
|
query: {
|
||||||
conversationId: conversationId
|
conversationId: conversationId
|
||||||
}
|
}
|
|
@ -19,11 +19,10 @@
|
||||||
<div class="btns" v-if="activeConversation">
|
<div class="btns" v-if="activeConversation">
|
||||||
<el-button type="primary" bg plain size="small" @click="openChatConversationUpdateForm">
|
<el-button type="primary" bg plain size="small" @click="openChatConversationUpdateForm">
|
||||||
<span v-html="activeConversation?.modelName"></span>
|
<span v-html="activeConversation?.modelName"></span>
|
||||||
<Icon icon="ep:setting" style="margin-left: 10px" />
|
<Icon icon="ep:setting" class="ml-10px" />
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button size="small" class="btn" @click="handlerMessageClear">
|
<el-button size="small" class="btn" @click="handlerMessageClear">
|
||||||
<!-- TODO @fan:style 部分,可以考虑用 unocss 替代 -->
|
<img src="@/assets/ai/clear.svg" class="h-14px" />
|
||||||
<img src="@/assets/ai/clear.svg" style="height: 14px" />
|
|
||||||
</el-button>
|
</el-button>
|
||||||
<!-- TODO @fan:下面两个 icon,可以使用类似 <Icon icon="ep:question-filled" /> 替代哈 -->
|
<!-- TODO @fan:下面两个 icon,可以使用类似 <Icon icon="ep:question-filled" /> 替代哈 -->
|
||||||
<el-button size="small" :icon="Download" class="btn" />
|
<el-button size="small" :icon="Download" class="btn" />
|
||||||
|
@ -76,7 +75,7 @@
|
||||||
<div class="prompt-btns">
|
<div class="prompt-btns">
|
||||||
<div>
|
<div>
|
||||||
<el-switch v-model="enableContext" />
|
<el-switch v-model="enableContext" />
|
||||||
<span style="font-size: 14px; color: #8f8f8f">上下文</span>
|
<span class="ml-5px text-14px text-#8f8f8f">上下文</span>
|
||||||
</div>
|
</div>
|
||||||
<el-button
|
<el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
|
@ -119,6 +118,9 @@ import MessageLoading from './components/message/MessageLoading.vue'
|
||||||
import MessageNewConversation from './components/message/MessageNewConversation.vue'
|
import MessageNewConversation from './components/message/MessageNewConversation.vue'
|
||||||
import { Download, Top } from '@element-plus/icons-vue'
|
import { Download, Top } from '@element-plus/icons-vue'
|
||||||
|
|
||||||
|
/** AI 聊天对话 列表 */
|
||||||
|
defineOptions({ name: 'AiChat' })
|
||||||
|
|
||||||
const route = useRoute() // 路由
|
const route = useRoute() // 路由
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue