【增加】AI 角色定制

This commit is contained in:
cherishsince 2024-05-15 23:11:40 +08:00
parent c06bc71020
commit b2c15ab2cb
4 changed files with 194 additions and 48 deletions

View File

@ -10,6 +10,7 @@ export interface ChatRoleVO {
sort: number // 角色排序 sort: number // 角色排序
description: string // 角色描述 description: string // 角色描述
systemMessage: string // 角色设定 systemMessage: string // 角色设定
welcomeMessage: string // 角色设定
publicStatus: boolean // 是否公开 publicStatus: boolean // 是否公开
status: number // 状态 status: number // 状态
} }
@ -50,6 +51,8 @@ export const ChatRoleApi = {
return await request.delete({ url: `/ai/chat-role/delete?id=` + id }) return await request.delete({ url: `/ai/chat-role/delete?id=` + id })
}, },
// ======= chat 聊天
// 获取 my role // 获取 my role
getMyPage: async (params: ChatRolePageReqVO) => { getMyPage: async (params: ChatRolePageReqVO) => {
return await request.get({ url: `/ai/chat-role/my-page`, params}) return await request.get({ url: `/ai/chat-role/my-page`, params})
@ -58,5 +61,20 @@ export const ChatRoleApi = {
// 获取角色分类 // 获取角色分类
getCategoryList: async () => { getCategoryList: async () => {
return await request.get({ url: `/ai/chat-role/category-list`}) return await request.get({ url: `/ai/chat-role/category-list`})
} },
// 创建角色
createMy: async (data: ChatRoleVO) => {
return await request.post({ url: `/ai/chat-role/create-my`, data})
},
// 更新角色
updateMy: async (data: ChatRoleVO) => {
return await request.put({ url: `/ai/chat-role/update-my`, data})
},
// 删除角色 my
deleteMy: async (id: number) => {
return await request.delete({ url: `/ai/chat-role/delete-my?id=` + id })
},
} }

View File

@ -1,6 +1,28 @@
<template> <template>
<div class="card-list"> <div class="card-list">
<el-card class="card" body-class="card-body" v-for="role in roleList" :key="role.id"> <el-card class="card" body-class="card-body" v-for="role in roleList" :key="role.id">
<!-- 更多 -->
<div class="more-container">
<el-dropdown @command="handleMoreClick">
<span class="el-dropdown-link">
<el-button type="text" >
<el-icon><More /></el-icon>
</el-button>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item :command="['edit', role]" >
<el-icon><EditPen /></el-icon>
</el-dropdown-item>
<el-dropdown-item :command="['delete', role]" style="color: red;" >
<el-icon><Delete /></el-icon>
<span>删除</span>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
<!-- 头像 -->
<div> <div>
<img class="avatar" :src="role.avatar"/> <img class="avatar" :src="role.avatar"/>
</div> </div>
@ -8,6 +30,7 @@
<div class="content-container"> <div class="content-container">
<div class="title">{{ role.name }}</div> <div class="title">{{ role.name }}</div>
<div class="description">{{ role.description }}</div> <div class="description">{{ role.description }}</div>
</div> </div>
<div class="btn-container"> <div class="btn-container">
<el-button type="primary" size="small">使用</el-button> <el-button type="primary" size="small">使用</el-button>
@ -20,6 +43,7 @@
<script setup lang="ts"> <script setup lang="ts">
import {ChatRoleVO} from '@/api/ai/model/chatRole' import {ChatRoleVO} from '@/api/ai/model/chatRole'
import {PropType} from "vue"; import {PropType} from "vue";
import {Delete, EditPen, More} from "@element-plus/icons-vue";
// //
const props = defineProps({ const props = defineProps({
@ -28,6 +52,18 @@ const props = defineProps({
required: true required: true
} }
}) })
//
const emits = defineEmits(['onDelete', 'onEdit'])
// more
const handleMoreClick = async (data) => {
const type = data[0]
const role = data[1]
if (type === 'delete') {
emits('onDelete', role)
} else {
emits('onEdit', role)
}
}
onMounted(() => { onMounted(() => {
console.log('props', props.roleList) console.log('props', props.roleList)
@ -38,13 +74,14 @@ onMounted(() => {
<style lang="scss"> <style lang="scss">
// card body // card body
.card-body { .card-body {
width: auto;
max-width: 300px; max-width: 300px;
width: 300px;
padding: 15px; padding: 15px;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: flex-start; justify-content: flex-start;
position: relative;
} }
</style> </style>
@ -55,21 +92,36 @@ onMounted(() => {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
flex-wrap: wrap; flex-wrap: wrap;
position: relative;
.card { .card {
margin-right: 20px; margin-right: 20px;
border-radius: 10px; border-radius: 10px;
margin-bottom: 30px;
position: relative;
.more-container {
position: absolute;
right: 12px;
top: 0px;
}
.avatar { .avatar {
width: 40px; width: 40px;
height: 40px;
border-radius: 10px; border-radius: 10px;
overflow: hidden; overflow: hidden;
} }
.right-container { .right-container {
margin-left: 10px; margin-left: 10px;
width: 100%;
//height: 100px;
.content-container { .content-container {
height: 85px;
overflow: hidden;
.title { .title {
font-size: 18px; font-size: 18px;
font-weight: bold; font-weight: bold;

View File

@ -1,30 +1,38 @@
<!-- chat 角色仓库 --> <!-- chat 角色仓库 -->
<template> <template>
<el-container class="role-container"> <el-container class="role-container">
<ChatRoleForm ref="formRef" @success="handlerAddRoleSuccess" />
<Header title="角色仓库"/> <Header title="角色仓库"/>
<el-main class="role-main"> <el-main class="role-main">
<!-- 搜索按钮 --> <div class="search-container" @click="handlerAddRole">
<el-input <!-- 搜索按钮 -->
v-model="search" <el-input
class="search-input" v-model="search"
size="large" class="search-input"
placeholder="请输入搜索的内容" size="default"
:suffix-icon="Search" placeholder="请输入搜索的内容"
@change="getActiveTabsRole" :suffix-icon="Search"
/> @change="getActiveTabsRole"
/>
<el-button type="primary" style="margin-left: 20px;">
<el-icon><User /></el-icon>
添加角色
</el-button>
</div>
<!-- tabs --> <!-- tabs -->
<el-tabs v-model="activeRole" class="tabs" @tab-click="handleTabsClick"> <el-tabs v-model="activeRole" 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">
<RoleCategoryList :category-list="categoryList" :active="activeCategory" @onCategoryClick="handlerCategoryClick" /> <RoleList :role-list="myRoleList" @onDelete="handlerCardDelete" @onEdit="handlerCardEdit" style="margin-top: 20px;" />
<RoleList :role-list="myRoleList" style="margin-top: 20px;" />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="公共角色" name="public-role"> <el-tab-pane label="公共角色" name="public-role">
<RoleCategoryList :category-list="categoryList" :active="activeCategory" @onCategoryClick="handlerCategoryClick" /> <RoleCategoryList :category-list="categoryList" :active="activeCategory" @onCategoryClick="handlerCategoryClick" />
<RoleList :role-list="publicRoleList" style="margin-top: 20px;" /> <RoleList :role-list="publicRoleList" @onDelete="handlerCardDelete" @onEdit="handlerCardEdit" style="margin-top: 20px;" />
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
</el-main> </el-main>
</el-container> </el-container>
</template> </template>
<!-- setup --> <!-- setup -->
@ -32,10 +40,11 @@
import {ref} from "vue"; import {ref} from "vue";
import Header from '@/views/ai/chat/components/Header.vue' import Header from '@/views/ai/chat/components/Header.vue'
import RoleList from './RoleList.vue' import RoleList from './RoleList.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 {TabsPaneContext} from "element-plus"; import {TabsPaneContext} from "element-plus";
import {Search} from "@element-plus/icons-vue"; import {Search, User} from "@element-plus/icons-vue";
// //
const activeRole = ref<string>('my-role') // const activeRole = ref<string>('my-role') //
@ -47,9 +56,10 @@ const myRoleList = ref<ChatRoleVO[]>([]) // my 分页大小
const publicPageNo = ref<number>(1) // public const publicPageNo = ref<number>(1) // public
const publicPageSize = ref<number>(50) // public const publicPageSize = ref<number>(50) // public
const publicRoleList = ref<ChatRoleVO[]>([]) // public const publicRoleList = ref<ChatRoleVO[]>([]) // public
const activeCategory = ref<string>('writing') // const activeCategory = ref<string>('') //
const categoryList = ref<string[]>([]) // const categoryList = ref<string[]>([]) //
/** 添加/修改操作 */
const formRef = ref()
// tabs // tabs
const handleTabsClick = async (tab: TabsPaneContext) => { const handleTabsClick = async (tab: TabsPaneContext) => {
// //
@ -109,6 +119,31 @@ const handlerCategoryClick = async (category: string) => {
await getActiveTabsRole() await getActiveTabsRole()
} }
//
const handlerAddRole = async () => {
formRef.value.open('my-create', null, '添加角色')
}
// card
const handlerCardDelete = async (role) => {
await ChatRoleApi.deleteMy(role.id)
//
await getActiveTabsRole()
}
// card
const handlerCardEdit = async (role) => {
formRef.value.open('my-update', role.id, '编辑角色')
}
//
const handlerAddRoleSuccess = async (e) => {
console.log(e)
//
await getActiveTabsRole()
}
// //
onMounted( async () => { onMounted( async () => {
// //
@ -139,14 +174,17 @@ onMounted( async () => {
.role-main { .role-main {
position: relative; position: relative;
.search-input { .search-container {
width: 240px;
position: absolute; position: absolute;
right: 20px; right: 20px;
top: 10px; top: 10px;
z-index: 100; z-index: 100;
} }
.search-input {
width: 240px;
}
.tabs { .tabs {
position: relative; position: relative;
} }

View File

@ -8,12 +8,12 @@
v-loading="formLoading" v-loading="formLoading"
> >
<el-form-item label="角色名称" prop="name"> <el-form-item label="角色名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入角色名称" /> <el-input v-model="formData.name" placeholder="请输入角色名称"/>
</el-form-item> </el-form-item>
<el-form-item label="角色头像" prop="avatar"> <el-form-item label="角色头像" prop="avatar">
<UploadImg v-model="formData.avatar" height="60px" width="60px" /> <UploadImg v-model="formData.avatar" height="60px" width="60px"/>
</el-form-item> </el-form-item>
<el-form-item label="绑定模型" prop="modelId"> <el-form-item label="绑定模型" prop="modelId" v-if="!isUser(formType)">
<el-select v-model="formData.modelId" placeholder="请选择模型" clearable> <el-select v-model="formData.modelId" placeholder="请选择模型" clearable>
<el-option <el-option
v-for="chatModel in chatModelList" v-for="chatModel in chatModelList"
@ -23,16 +23,19 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="角色类别" prop="category"> <el-form-item label="角色类别" prop="category" v-if="!isUser(formType)">
<el-input v-model="formData.category" placeholder="请输入角色类别" /> <el-input v-model="formData.category" placeholder="请输入角色类别"/>
</el-form-item> </el-form-item>
<el-form-item label="角色描述" prop="description"> <el-form-item label="角色描述" prop="description">
<el-input type="textarea" v-model="formData.description" placeholder="请输入角色描述" /> <el-input type="textarea" v-model="formData.description" placeholder="请输入角色描述"/>
</el-form-item> </el-form-item>
<el-form-item label="角色设定" prop="systemMessage"> <el-form-item label="角色设定" prop="systemMessage">
<el-input type="textarea" v-model="formData.systemMessage" placeholder="请输入角色设定" /> <el-input type="textarea" v-model="formData.systemMessage" placeholder="请输入角色设定" />
</el-form-item> </el-form-item>
<el-form-item label="是否公开" prop="publicStatus"> <el-form-item label="欢迎语👏🏻" prop="welcomeMessage" v-if="isUser(formType)">
<el-input type="textarea" v-model="formData.welcomeMessage" placeholder="请输入欢迎语"/>
</el-form-item>
<el-form-item label="是否公开" prop="publicStatus" v-if="!isUser(formType)">
<el-radio-group v-model="formData.publicStatus"> <el-radio-group v-model="formData.publicStatus">
<el-radio <el-radio
v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)" v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
@ -43,10 +46,10 @@
</el-radio> </el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item label="角色排序" prop="sort"> <el-form-item label="角色排序" prop="sort" v-if="!isUser(formType)">
<el-input-number v-model="formData.sort" placeholder="请输入角色排序" class="!w-1/1" /> <el-input-number v-model="formData.sort" placeholder="请输入角色排序" class="!w-1/1"/>
</el-form-item> </el-form-item>
<el-form-item label="开启状态" prop="status"> <el-form-item label="开启状态" prop="status" v-if="!isUser(formType)">
<el-radio-group v-model="formData.status"> <el-radio-group v-model="formData.status">
<el-radio <el-radio
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
@ -65,15 +68,15 @@
</Dialog> </Dialog>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { getIntDictOptions, getBoolDictOptions, DICT_TYPE } from '@/utils/dict' import {getIntDictOptions, getBoolDictOptions, DICT_TYPE} from '@/utils/dict'
import { ChatRoleApi, ChatRoleVO } from '@/api/ai/model/chatRole' import {ChatRoleApi, ChatRoleVO} from '@/api/ai/model/chatRole'
import { CommonStatusEnum } from '@/utils/constants' import {CommonStatusEnum} from '@/utils/constants'
import { ChatModelApi, ChatModelVO } from '@/api/ai/model/chatModel' import {ChatModelApi, ChatModelVO} from '@/api/ai/model/chatModel'
/** AI 聊天角色 表单 */ /** AI 聊天角色 表单 */
defineOptions({ name: 'ChatRoleForm' }) defineOptions({name: 'ChatRoleForm'})
const { t } = useI18n() // const {t} = useI18n() //
const message = useMessage() // const message = useMessage() //
const dialogVisible = ref(false) // const dialogVisible = ref(false) //
@ -89,26 +92,49 @@ const formData = ref({
sort: undefined, sort: undefined,
description: undefined, description: undefined,
systemMessage: undefined, systemMessage: undefined,
welcomeMessage: undefined,
publicStatus: true, publicStatus: true,
status: CommonStatusEnum.ENABLE status: CommonStatusEnum.ENABLE
}) })
const formRules = reactive({
name: [{ required: true, message: '角色名称不能为空', trigger: 'blur' }], //
avatar: [{ required: true, message: '角色头像不能为空', trigger: 'blur' }], const isUser = (type: string) => {
category: [{ required: true, message: '角色类别不能为空', trigger: 'blur' }], return (type === 'my-create' || type === 'my-update')
sort: [{ required: true, message: '角色排序不能为空', trigger: 'blur' }], }
description: [{ required: true, message: '角色描述不能为空', trigger: 'blur' }],
systemMessage: [{ required: true, message: '角色设定不能为空', trigger: 'blur' }], const formRules = ref() // reactive(formRulesObj)
publicStatus: [{ required: true, message: '是否公开不能为空', trigger: 'blur' }]
})
const formRef = ref() // Ref const formRef = ref() // Ref
const chatModelList = ref([] as ChatModelVO[]) // const chatModelList = ref([] as ChatModelVO[]) //
const getFormRules = async (type: string) => {
let formRulesObj = {
name: [{required: true, message: '角色名称不能为空', trigger: 'blur'}],
avatar: [{required: true, message: '角色头像不能为空', trigger: 'blur'}],
category: [{required: true, message: '角色类别不能为空', trigger: 'blur'}],
sort: [{required: true, message: '角色排序不能为空', trigger: 'blur'}],
description: [{required: true, message: '角色描述不能为空', trigger: 'blur'}],
systemMessage: [{required: true, message: '角色设定不能为空', trigger: 'blur'}],
// welcomeMessage: [{ required: true, message: '', trigger: 'blur' }],
publicStatus: [{required: true, message: '是否公开不能为空', trigger: 'blur'}]
}
if (isUser(type)) {
formRulesObj['welcomeMessage'] = [{
required: true,
message: '欢迎语不能为空',
trigger: 'blur'
}]
}
formRules.value = reactive(formRulesObj)
}
/** 打开弹窗 */ /** 打开弹窗 */
const open = async (type: string, id?: number) => { const open = async (type: string, id?: number, title?: string) => {
dialogVisible.value = true dialogVisible.value = true
dialogTitle.value = t('action.' + type) dialogTitle.value = title || t('action.' + type)
formType.value = type formType.value = type
getFormRules(type)
resetForm() resetForm()
// //
if (id) { if (id) {
@ -122,7 +148,7 @@ const open = async (type: string, id?: number) => {
// //
chatModelList.value = await ChatModelApi.getChatModelSimpleList(CommonStatusEnum.ENABLE) chatModelList.value = await ChatModelApi.getChatModelSimpleList(CommonStatusEnum.ENABLE)
} }
defineExpose({ open }) // open defineExpose({open}) // open
/** 提交表单 */ /** 提交表单 */
const emit = defineEmits(['success']) // success const emit = defineEmits(['success']) // success
@ -133,7 +159,17 @@ const submitForm = async () => {
formLoading.value = true formLoading.value = true
try { try {
const data = formData.value as unknown as ChatRoleVO const data = formData.value as unknown as ChatRoleVO
if (formType.value === 'create') {
// tip: my-createmy-update chat
// tip: createelse
if (formType.value === 'my-create') {
await ChatRoleApi.createMy(data)
message.success(t('common.createSuccess'))
} else if (formType.value === 'my-update') {
await ChatRoleApi.updateMy(data)
message.success(t('common.updateSuccess'))
} else if (formType.value === 'create') {
await ChatRoleApi.createChatRole(data) await ChatRoleApi.createChatRole(data)
message.success(t('common.createSuccess')) message.success(t('common.createSuccess'))
} else { } else {
@ -148,6 +184,7 @@ const submitForm = async () => {
} }
} }
/** 重置表单 */ /** 重置表单 */
const resetForm = () => { const resetForm = () => {
formData.value = { formData.value = {
@ -159,6 +196,7 @@ const resetForm = () => {
sort: undefined, sort: undefined,
description: undefined, description: undefined,
systemMessage: undefined, systemMessage: undefined,
welcomeMessage: undefined,
publicStatus: true, publicStatus: true,
status: CommonStatusEnum.ENABLE status: CommonStatusEnum.ENABLE
} }