Browse Source

【增加】AI 角色定制

cherishsince 1 year ago
parent
commit
b2c15ab2cb

+ 19 - 1
src/api/ai/model/chatRole/index.ts

@@ -10,6 +10,7 @@ export interface ChatRoleVO {
   sort: number // 角色排序
   description: string // 角色描述
   systemMessage: string // 角色设定
+  welcomeMessage: string // 角色设定
   publicStatus: boolean // 是否公开
   status: number // 状态
 }
@@ -50,6 +51,8 @@ export const ChatRoleApi = {
     return await request.delete({ url: `/ai/chat-role/delete?id=` + id })
   },
 
+  // ======= chat 聊天
+
   // 获取 my role
   getMyPage: async (params: ChatRolePageReqVO) => {
     return await request.get({ url: `/ai/chat-role/my-page`, params})
@@ -58,5 +61,20 @@ export const ChatRoleApi = {
   // 获取角色分类
   getCategoryList: async () => {
     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 })
+  },
 }

+ 53 - 1
src/views/ai/chat/role/RoleList.vue

@@ -1,6 +1,28 @@
 <template>
   <div class="card-list">
     <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>
         <img class="avatar" :src="role.avatar"/>
       </div>
@@ -8,6 +30,7 @@
         <div class="content-container">
           <div class="title">{{ role.name }}</div>
           <div class="description">{{ role.description }}</div>
+
         </div>
         <div class="btn-container">
           <el-button type="primary" size="small">使用</el-button>
@@ -20,6 +43,7 @@
 <script setup lang="ts">
 import {ChatRoleVO} from '@/api/ai/model/chatRole'
 import {PropType} from "vue";
+import {Delete, EditPen, More} from "@element-plus/icons-vue";
 
 // 定义属性
 const props = defineProps({
@@ -28,6 +52,18 @@ const props = defineProps({
     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(() => {
   console.log('props', props.roleList)
@@ -38,13 +74,14 @@ onMounted(() => {
 <style lang="scss">
 // 重写 card 组件 body 样式
 .card-body {
-  width: auto;
   max-width: 300px;
+  width: 300px;
   padding: 15px;
 
   display: flex;
   flex-direction: row;
   justify-content: flex-start;
+  position: relative;
 
 }
 </style>
@@ -55,21 +92,36 @@ onMounted(() => {
   display: flex;
   flex-direction: row;
   flex-wrap: wrap;
+  position: relative;
 
   .card {
     margin-right: 20px;
     border-radius: 10px;
+    margin-bottom: 30px;
+    position: relative;
+
+    .more-container {
+      position: absolute;
+      right: 12px;
+      top: 0px;
+    }
 
     .avatar {
       width: 40px;
+      height: 40px;
       border-radius: 10px;
       overflow: hidden;
     }
 
     .right-container {
       margin-left: 10px;
+      width: 100%;
+      //height: 100px;
 
       .content-container {
+        height: 85px;
+        overflow: hidden;
+
         .title {
           font-size: 18px;
           font-weight: bold;

+ 55 - 17
src/views/ai/chat/role/index.vue

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

+ 67 - 29
src/views/ai/model/chatRole/ChatRoleForm.vue

@@ -8,12 +8,12 @@
       v-loading="formLoading"
     >
       <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 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 label="绑定模型" prop="modelId">
+      <el-form-item label="绑定模型" prop="modelId" v-if="!isUser(formType)">
         <el-select v-model="formData.modelId" placeholder="请选择模型" clearable>
           <el-option
             v-for="chatModel in chatModelList"
@@ -23,16 +23,19 @@
           />
         </el-select>
       </el-form-item>
-      <el-form-item label="角色类别" prop="category">
-        <el-input v-model="formData.category" placeholder="请输入角色类别" />
+      <el-form-item label="角色类别" prop="category" v-if="!isUser(formType)">
+        <el-input v-model="formData.category" placeholder="请输入角色类别"/>
       </el-form-item>
       <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 label="角色设定" prop="systemMessage">
         <el-input type="textarea" v-model="formData.systemMessage" placeholder="请输入角色设定" />
       </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
             v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
@@ -43,10 +46,10 @@
           </el-radio>
         </el-radio-group>
       </el-form-item>
-      <el-form-item label="角色排序" prop="sort">
-        <el-input-number v-model="formData.sort" placeholder="请输入角色排序" class="!w-1/1" />
+      <el-form-item label="角色排序" prop="sort" v-if="!isUser(formType)">
+        <el-input-number v-model="formData.sort" placeholder="请输入角色排序" class="!w-1/1"/>
       </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
             v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
@@ -65,15 +68,15 @@
   </Dialog>
 </template>
 <script setup lang="ts">
-import { getIntDictOptions, getBoolDictOptions, DICT_TYPE } from '@/utils/dict'
-import { ChatRoleApi, ChatRoleVO } from '@/api/ai/model/chatRole'
-import { CommonStatusEnum } from '@/utils/constants'
-import { ChatModelApi, ChatModelVO } from '@/api/ai/model/chatModel'
+import {getIntDictOptions, getBoolDictOptions, DICT_TYPE} from '@/utils/dict'
+import {ChatRoleApi, ChatRoleVO} from '@/api/ai/model/chatRole'
+import {CommonStatusEnum} from '@/utils/constants'
+import {ChatModelApi, ChatModelVO} from '@/api/ai/model/chatModel'
 
 /** AI 聊天角色 表单 */
-defineOptions({ name: 'ChatRoleForm' })
+defineOptions({name: 'ChatRoleForm'})
 
-const { t } = useI18n() // 国际化
+const {t} = useI18n() // 国际化
 const message = useMessage() // 消息弹窗
 
 const dialogVisible = ref(false) // 弹窗的是否展示
@@ -89,26 +92,49 @@ const formData = ref({
   sort: undefined,
   description: undefined,
   systemMessage: undefined,
+  welcomeMessage: undefined,
   publicStatus: true,
   status: CommonStatusEnum.ENABLE
 })
-const formRules = reactive({
-  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' }],
-  publicStatus: [{ required: true, message: '是否公开不能为空', trigger: 'blur' }]
-})
+
+// 是否
+const isUser = (type: string) => {
+  return (type === 'my-create' || type === 'my-update')
+}
+
+const formRules = ref() // reactive(formRulesObj)
 const formRef = ref() // 表单 Ref
 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
-  dialogTitle.value = t('action.' + type)
+  dialogTitle.value = title || t('action.' + type)
   formType.value = type
+  getFormRules(type)
   resetForm()
   // 修改时,设置数据
   if (id) {
@@ -122,7 +148,7 @@ const open = async (type: string, id?: number) => {
   // 获得下拉数据
   chatModelList.value = await ChatModelApi.getChatModelSimpleList(CommonStatusEnum.ENABLE)
 }
-defineExpose({ open }) // 提供 open 方法,用于打开弹窗
+defineExpose({open}) // 提供 open 方法,用于打开弹窗
 
 /** 提交表单 */
 const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
@@ -133,7 +159,17 @@ const submitForm = async () => {
   formLoading.value = true
   try {
     const data = formData.value as unknown as ChatRoleVO
-    if (formType.value === 'create') {
+
+    // tip: my-create、my-update 是 chat 角色仓库调用
+    // tip: create、else 是后台管理调用
+
+    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)
       message.success(t('common.createSuccess'))
     } else {
@@ -148,6 +184,7 @@ const submitForm = async () => {
   }
 }
 
+
 /** 重置表单 */
 const resetForm = () => {
   formData.value = {
@@ -159,6 +196,7 @@ const resetForm = () => {
     sort: undefined,
     description: undefined,
     systemMessage: undefined,
+    welcomeMessage: undefined,
     publicStatus: true,
     status: CommonStatusEnum.ENABLE
   }