Эх сурвалжийг харах

feat:知识库和智能体图标编辑功能

hanyang 1 жил өмнө
parent
commit
6b6ba01d9d

+ 1 - 1
public/i18n/lang-en.json

@@ -67,7 +67,7 @@
     "upBtn": "Collapse",
     "useBtn": "Use",
     "addLabelBtn": "Add Label",
-    "loadingData": "Loading data, please wait...",
+    "loadingDataTip": "Loading data, please wait...",
     "noMoreData": "No more data available"
   },
   "permission": {

+ 15 - 0
src/apis/intelligent.js

@@ -116,3 +116,18 @@ export function changeDialogStatus(id, status) {
     }
   });
 }
+/**
+ * @description 编辑智能体的图标
+ * @param {string} id 智能体ID
+ * @param {string} icon 图标名称
+ * */
+export function updateOwnIntelligentIcon(id, icon) {
+  return request({
+    url: `/api/dialog/update_icon`,
+    method: 'put',
+    data: {
+      id,
+      icon
+    }
+  });
+}

+ 11 - 0
src/apis/knowledge.js

@@ -39,3 +39,14 @@ export function syncOwnKnowledge(id) {
     method: 'get'
   });
 }
+// 知识库图标更新
+export function updateOwnKnowledgeIcon(id, icon) {
+  return request({
+    url: `/api/knowledge/update_icon`,
+    method: 'put',
+    data: {
+      id,
+      icon
+    }
+  });
+}

+ 7 - 1
src/base/event-bus.js

@@ -9,7 +9,13 @@ import EventBus from './event-bus/EventBus';
  */
 export const bus_themeChange = new EventBus('theme_change');
 
+// 知识库搜索
 export const bus_knowledgeSearch = new EventBus('knowledge_search');
+// 知识库图标编辑
+export const bus_knowledgeIconEdit = new EventBus('knowledge_icon_edit');
+// 智能体搜索
 export const bus_intelligentSearch = new EventBus('intelligent_search');
-
+// 智能体编辑
 export const bus_intelligentEdit = new EventBus('intelligent_edit');
+// 智能体图标编辑
+export const bus_intelligentIconEdit = new EventBus('intelligent_icon_edit');

+ 12 - 6
src/modules/intelligent/MessageBody.vue

@@ -7,6 +7,7 @@
       :name="item.name"
       :img_src="item.img_src"
       :height="200"
+      @clickIcon="editIconHandle(item)"
     >
       <template #description> {{ item.desc }} </template>
       <template #settings>
@@ -55,11 +56,11 @@
       </template>
     </CardItem>
     <div class="loading-tips">
-      <a-spin v-if="scroll_bottom && pages.current < total_pages" :tip="$t('management.loadingData')" />
+      <a-spin v-if="scroll_bottom && pages.current < total_pages" :tip="$t('management.loadingDataTip')" />
       <div v-if="scroll_bottom && pages.current >= total_pages">{{ $t('management.noMoreData') }}</div>
     </div>
   </div>
-  <a-spin v-if="loading_data" :size="32" />
+  <a-spin class="init-loading" v-if="loading_data" :size="32" />
   <a-empty v-if="!moke_data.length"></a-empty>
   <ManageTags v-model:visible="visible" :labels="label_list" @updateLabels="updateLabels"></ManageTags>
 </template>
@@ -72,8 +73,9 @@ import ManageTags from './ManageTags.vue';
 import { getIconIntelligent, getImageIntelligent } from '../common/icon-map.js';
 import LabelSelected from './LabelSelected.vue';
 import { getDialogList } from '../../apis/intelligent';
-import { bus_intelligentSearch, bus_intelligentEdit } from '../../base/event-bus';
+import { bus_intelligentSearch, bus_intelligentEdit, bus_intelligentIconEdit } from '../../base/event-bus';
 import { dialogType } from '../../views/manage/common';
+import { funcThrottle } from '../../utils/utils';
 import { getLabelList, addLabelToDialog, changeDialogStatus, deleteDialog } from '../../apis/intelligent';
 import { deleteDify, deleteRgFlow } from '../../apis/rgflow-dify';
 
@@ -241,6 +243,10 @@ const changeStatus = async (item) => {
     });
   }
 };
+const editIconHandle = (item) => {
+  bus_intelligentIconEdit.emit(item);
+};
+
 bus_intelligentSearch.on((text) => {
   search_text.value = text;
   moke_data.value = [];
@@ -251,7 +257,7 @@ bus_intelligentSearch.on((text) => {
 onMounted(() => {
   getDialog();
   getLabels();
-  body_container.value.addEventListener('scroll', scrollHandler);
+  body_container.value.addEventListener('scroll', funcThrottle(scrollHandler, 500));
 });
 
 onBeforeUnmount(() => {
@@ -315,13 +321,13 @@ onBeforeUnmount(() => {
   visibility: visible;
   background-color: var(--color-bg-1);
 }
-.arco-spin {
+.arco-empty {
   position: absolute;
   top: 50%;
   left: 50%;
   transform: translate(-50%, -50%);
 }
-.arco-empty {
+.init-loading {
   position: absolute;
   top: 50%;
   left: 50%;

+ 25 - 7
src/modules/intelligent/ModalSelectIcon.vue

@@ -14,7 +14,7 @@
           class="icon-border"
           v-for="icon in icons"
           :key="icon.icon"
-          :class="{ 'icon-border-active': icon.id === target_icon }"
+          :class="{ 'icon-border-active': icon.id === target_icon_id }"
           @click="changeItem(icon.id)"
         >
           <img class="icon" :src="getIconIntelligent(icon.name)" alt="" />
@@ -25,15 +25,20 @@
 </template>
 
 <script setup>
-import { ref, computed } from 'vue';
+import { ref, computed, watch } from 'vue';
 import { getIconIntelligent } from '../common/icon-map.js';
 
 const props = defineProps({
   show_select_icon: {
     type: Boolean,
     default: false
+  },
+  target_icon: {
+    type: String,
+    default: ''
   }
 });
+const emit = defineEmits(['update:show_model_item', 'sendIcon']);
 const icons = ref([
   {
     id: 1,
@@ -61,22 +66,35 @@ const icons = ref([
   }
 ]);
 
-const target_icon = ref(icons.value[0].id);
-const emit = defineEmits(['update:show_model_item', 'sendIcon']);
+const target_icon_id = ref(icons.value[0].id);
 
 const showModal = computed(() => props.show_select_icon);
 
 const changeItem = (id) => {
-  target_icon.value = id;
+  target_icon_id.value = id;
 };
 const handleBeforeOk = (done) => {
-  emit('sendIcon', icons.value.filter((icon) => icon.id === target_icon.value)[0].name);
-  emit('update:show_select_icon', false);
+  emit('sendIcon', icons.value.filter((icon) => icon.id === target_icon_id.value)[0].name);
+  handleCancel();
   done();
 };
 const handleCancel = () => {
   emit('update:show_select_icon', false);
+  emit('clearTargetIcon');
 };
+watch(
+  () => props.target_icon,
+  (new_value) => {
+    if (new_value && new_value.length) {
+      target_icon_id.value = icons.value.filter((icon) => icon.name === new_value)[0]?.id || icons.value[0].id;
+    } else {
+      target_icon_id.value = icons.value[0].id;
+    }
+  },
+  {
+    immediate: true
+  }
+);
 </script>
 <style scoped lang="css">
 .section {

+ 42 - 7
src/modules/intelligent/TopSetting.vue

@@ -32,8 +32,13 @@
     @clearSelectIcon="clearSelectIcon"
   >
   </ModelAddItem>
-  <ModelSelectIcon v-model:show_select_icon="show_select_icon" @sendIcon="sendIcon"></ModelSelectIcon>
-  <!-- v-if="show_rg_config" -->
+  <ModelSelectIcon
+    v-model:show_select_icon="show_select_icon"
+    :target_icon="target_icon"
+    @sendIcon="sendIcon"
+    @clearTargetIcon="clearTargetIcon"
+  ></ModelSelectIcon
+  >>
   <AgentConfig
     v-model:show_rg_config="show_rg_config"
     ref="editAgentKuai"
@@ -47,15 +52,17 @@
 </template>
 
 <script setup>
-import { ref, reactive } from 'vue';
+import { ref, reactive, onUnmounted } from 'vue';
 import { useRouter } from 'vue-router';
+import { Message } from '@arco-design/web-vue';
 import ModalAddIntel from './ModalAddIntel.vue';
 import ModelAddItem from './ModelAddItem.vue';
 import ModelSelectIcon from './ModalSelectIcon.vue';
 import AgentConfig from './rgflow/agentConfig.vue';
 import { funcDebounce } from '../../utils/utils';
 import { getRagFlowData } from '../../apis/rgflow-dify';
-import { bus_intelligentSearch, bus_intelligentEdit } from '../../base/event-bus';
+import { updateOwnIntelligentIcon } from '../../apis/intelligent';
+import { bus_intelligentSearch, bus_intelligentEdit, bus_intelligentIconEdit } from '../../base/event-bus';
 
 const router = useRouter();
 
@@ -71,6 +78,7 @@ const show_rg_config = ref(false);
 const editAgentKuai = ref(null);
 let add_type = ref('add');
 let itemObj = reactive({});
+const edit_icon_data = ref(null);
 
 // 搜索
 const handleSearch = funcDebounce(
@@ -87,6 +95,7 @@ const handleCreate = () => {
 // 创建rgflow智能体
 const createRAGFLOWIntelligent = () => {
   show_rg_config.value = true;
+  add_type.value = 'add';
 };
 const addIntelligent = (data) => {
   //根据智能体类型,打开不同的配置面板data.type
@@ -97,8 +106,26 @@ const selectIcon = () => {
   show_select_icon.value = true;
 };
 const sendIcon = (icon) => {
-  console.log('sendIcon', icon);
   target_icon.value = icon;
+  if (!edit_icon_data.value) return;
+  // 编辑
+  updateOwnIntelligentIcon(edit_icon_data.value.id, target_icon.value)
+    .then((res) => {
+      if (res.code == 200) {
+        Message.success('修改成功');
+      } else {
+        Message.error('修改失败');
+      }
+    })
+    .catch(() => {
+      Message.error('修改失败');
+    })
+    .finally(() => {
+      target_icon.value = '';
+      // 更新知识库列表
+      bus_intelligentSearch.emit(searchText.value);
+      edit_icon_data.value = null;
+    });
 };
 
 const updateIntelligentList = () => {
@@ -140,12 +167,11 @@ bus_intelligentEdit.on((item) => {
       // TODO: 编辑rgflow智能体
       // itemObj = item;
       show_rg_config.value = true;
-      add_type = 'edit';
+      add_type.value = 'edit';
       getRagFlowDataFunc(item);
 
       break;
     case '4':
-      console.log(item);
       // TODO: 进入Dify智能体配置
       router.push({ path: '/intelligent/detail', query: { id: item.id, name: item.name, mode: item.mode } });
       break;
@@ -153,6 +179,15 @@ bus_intelligentEdit.on((item) => {
       console.log('暂不支持该智能体类型');
   }
 });
+bus_intelligentIconEdit.on((item) => {
+  show_select_icon.value = true;
+  edit_icon_data.value = item;
+  target_icon.value = item.icon;
+});
+onUnmounted(() => {
+  bus_intelligentEdit.off();
+  bus_intelligentIconEdit.off();
+});
 </script>
 <style scoped lang="css">
 .arco-input-wrapper {

+ 3 - 13
src/modules/intelligent/rgflow/AgentConfig.vue

@@ -26,16 +26,8 @@
                 <a-form-item field="name" label="智能体名称">
                   <a-input v-model="form.name" placeholder="请输入名称" />
                 </a-form-item>
-                <a-form-item label="智能体图标" required>
+                <a-form-item v-if="typeAngint === 'add'" label="智能体图标" required>
                   <a-space direction="vertical" :style="{ width: '100%' }">
-                    <!-- <Upload
-                      v-if="avatarShow"
-                      :action="uploadAction"
-                      :limit="1"
-                      :url="form.icon ? httpUrl + form.icon : ''"
-                      @update:fileList="updateFileList"
-                      @success="handleSuccess"
-                    ></Upload> -->
                     <div class="upload-empty" v-if="!props.target_icon.length" @click="handleUploadIcon">
                       <icon-plus class="icon"></icon-plus>
                     </div>
@@ -275,14 +267,11 @@
 import { onMounted, reactive, ref, nextTick, onUnmounted, computed, onActivated } from 'vue';
 import { Message } from '@arco-design/web-vue';
 import { getIconIntelligent } from '../../common/icon-map.js';
-import { queryKbList, queryModelList, createRgFlow } from '../../../apis/rgflow-dify';
+import { queryModelList, createRgFlow } from '../../../apis/rgflow-dify';
 import { createDialog } from '../../../apis/intelligent';
 import { getKnowledgeList } from '../../../apis/knowledge';
 import { dialogType } from '../../../views/manage/common.js';
 import useLoading from './loading';
-import Upload from './Upload.vue';
-
-// import { dialogSet } from '@/api/Agent';
 
 const props = defineProps(['show_rg_config', 'typeAngint', 'formData', 'target_icon']);
 const emit = defineEmits([
@@ -292,6 +281,7 @@ const emit = defineEmits([
   'show_select_icon',
   'clearSelectIcon'
 ]);
+console.log(props, 'props');
 
 const { loading, setLoading } = useLoading(true);
 const visible = computed(() => props.show_rg_config);

+ 13 - 6
src/modules/knowledge/MessageBody.vue

@@ -6,6 +6,7 @@
       :name="item.name"
       :img_src="item.img_src"
       @click="handleToDetail(item)"
+      @clickIcon="editIconHandle(item)"
     >
       <template #bottom>
         <div class="bottom">
@@ -25,11 +26,11 @@
       </template>
     </CardItem>
     <div class="loading-tips">
-      <a-spin v-if="scroll_bottom && pages.current < total_pages" :tip="$t('management.loadingData')" />
+      <a-spin v-if="scroll_bottom && pages.current < total_pages" :tip="$t('management.loadingDataTip')" />
       <div v-if="scroll_bottom && pages.current >= total_pages">{{ $t('management.noMoreData') }}</div>
     </div>
   </div>
-  <a-spin v-if="loading_data" :size="32" />
+  <a-spin class="init-loading" v-if="loading_data" :size="32" />
   <a-empty v-if="!moke_data.length"></a-empty>
 </template>
 
@@ -39,9 +40,10 @@ import { Message, Modal } from '@arco-design/web-vue';
 import { useRouter } from 'vue-router';
 import CardItem from '../../views/manage/CardItem.vue';
 import { getImageKnowLedge } from '../common/icon-map.js';
+import { funcThrottle } from '../../utils/utils';
 import { getKnowledgeList, deleteOwnKnowledge } from '../../apis/knowledge';
 import { deleteKnow } from '../../apis/rgflow-dify.js';
-import { bus_knowledgeSearch } from '../../base/event-bus';
+import { bus_knowledgeSearch, bus_knowledgeIconEdit } from '../../base/event-bus';
 
 const router = useRouter();
 
@@ -91,6 +93,8 @@ const toRemove = (item) => {
       });
       if (data.retcode == 0) {
         deleteOwnKnow(item.id);
+      } else {
+        Message.error('没有权限');
       }
     },
     onCancel: () => {}
@@ -145,6 +149,9 @@ const scrollHandler = (e) => {
     }
   }
 };
+const editIconHandle = (item) => {
+  bus_knowledgeIconEdit.emit(item);
+};
 
 bus_knowledgeSearch.on((text) => {
   search_text.value = text;
@@ -155,7 +162,7 @@ bus_knowledgeSearch.on((text) => {
 
 onMounted(() => {
   getKnowledge();
-  body_container.value.addEventListener('scroll', scrollHandler);
+  body_container.value.addEventListener('scroll', funcThrottle(scrollHandler, 500));
 });
 
 onBeforeUnmount(() => {
@@ -189,13 +196,13 @@ onBeforeUnmount(() => {
   /* width:  */
   height: calc(100% - 80px);
 }
-.arco-spin {
+.arco-empty {
   position: absolute;
   top: 50%;
   left: 50%;
   transform: translate(-50%, -50%);
 }
-.arco-empty {
+.init-loading {
   position: absolute;
   top: 50%;
   left: 50%;

+ 25 - 8
src/modules/knowledge/ModalSelectIcon.vue

@@ -14,7 +14,7 @@
           class="icon-border"
           v-for="icon in icons"
           :key="icon.icon"
-          :class="{ 'icon-border-active': icon.id === target_icon }"
+          :class="{ 'icon-border-active': icon.id === target_icon_id }"
           @click="changeItem(icon.id)"
         >
           <img class="icon" :src="getImageKnowLedge(icon.name)" alt="" />
@@ -25,15 +25,20 @@
 </template>
 
 <script setup>
-import { ref, computed } from 'vue';
+import { ref, computed, watch } from 'vue';
 import { getImageKnowLedge } from '../common/icon-map.js';
 
 const props = defineProps({
   show_select_icon: {
     type: Boolean,
     default: false
+  },
+  target_icon: {
+    type: String,
+    default: ''
   }
 });
+const emit = defineEmits(['update:show_model_item', 'sendIcon', 'clearTargetIcon']);
 const icons = ref([
   {
     id: 1,
@@ -53,22 +58,34 @@ const icons = ref([
   }
 ]);
 
-const target_icon = ref(icons.value[0].id);
-const emit = defineEmits(['update:show_model_item', 'sendIcon']);
-
+const target_icon_id = ref(icons.value[0].id);
 const showModal = computed(() => props.show_select_icon);
 
 const changeItem = (id) => {
-  target_icon.value = id;
+  target_icon_id.value = id;
 };
 const handleBeforeOk = (done) => {
-  emit('sendIcon', icons.value.filter((icon) => icon.id === target_icon.value)[0].name);
-  emit('update:show_select_icon', false);
+  emit('sendIcon', icons.value.filter((icon) => icon.id === target_icon_id.value)[0].name);
+  handleCancel();
   done();
 };
 const handleCancel = () => {
   emit('update:show_select_icon', false);
+  emit('clearTargetIcon');
 };
+watch(
+  () => props.target_icon,
+  (new_value) => {
+    if (new_value && new_value.length) {
+      target_icon_id.value = icons.value.filter((icon) => icon.name === new_value)[0]?.id || icons.value[0].id;
+    } else {
+      target_icon_id.value = icons.value[0].id;
+    }
+  },
+  {
+    immediate: true
+  }
+);
 </script>
 <style scoped lang="css">
 .section {

+ 42 - 4
src/modules/knowledge/TopSetting.vue

@@ -19,21 +19,29 @@
     </a-button>
   </a-space>
   <ModalCreateKb v-model:visible="visible" :target_icon="target_icon" @select_icon="selectIcon"></ModalCreateKb>
-  <ModalSelectIcon v-model:show_select_icon="show_select_icon" @sendIcon="sendIcon"></ModalSelectIcon>
+  <ModalSelectIcon
+    v-model:show_select_icon="show_select_icon"
+    :target_icon="target_icon"
+    @sendIcon="sendIcon"
+    @clearTargetIcon="clearTargetIcon"
+  ></ModalSelectIcon>
 </template>
 
 <script setup>
-import { ref } from 'vue';
+import { ref, onUnmounted } from 'vue';
+import { Message } from '@arco-design/web-vue';
 import ModalCreateKb from './ModalCreateKb.vue';
 import ModalSelectIcon from './ModalSelectIcon.vue';
 import { funcDebounce } from '../../utils/utils';
-import { bus_knowledgeSearch } from '../../base/event-bus';
+import { bus_knowledgeSearch, bus_knowledgeIconEdit } from '../../base/event-bus';
+import { updateOwnKnowledgeIcon } from '../../apis/knowledge';
 
 const searchText = ref('');
 // 模态框控制
 const visible = ref(false);
 const show_select_icon = ref(false);
 const target_icon = ref('');
+const edit_icon_data = ref(null);
 
 // 搜索
 const handleSearch = funcDebounce(
@@ -52,9 +60,39 @@ const selectIcon = () => {
   show_select_icon.value = true;
 };
 const sendIcon = (icon) => {
-  console.log('sendIcon', icon);
   target_icon.value = icon;
+  if (!edit_icon_data.value) return;
+  // 编辑
+  updateOwnKnowledgeIcon(edit_icon_data.value.id, target_icon.value)
+    .then((res) => {
+      if (res.code == 200) {
+        Message.success('修改成功');
+      } else {
+        Message.error('修改失败');
+      }
+    })
+    .catch(() => {
+      Message.error('修改失败');
+    })
+    .finally(() => {
+      target_icon.value = '';
+      // 更新知识库列表
+      bus_knowledgeSearch.emit(searchText.value);
+      edit_icon_data.value = null;
+    });
 };
+const clearTargetIcon = () => {
+  target_icon.value = '';
+};
+
+bus_knowledgeIconEdit.on((item) => {
+  show_select_icon.value = true;
+  edit_icon_data.value = item;
+  target_icon.value = item.icon;
+});
+onUnmounted(() => {
+  bus_knowledgeIconEdit.off();
+});
 </script>
 <style scoped lang="css">
 .arco-input-wrapper {

+ 29 - 0
src/utils/utils.js

@@ -41,3 +41,32 @@ export const funcDebounce = (func, wait, immediate) => {
     }, wait);
   };
 };
+/**
+ * 节流函数
+ * @param func 要执行的函数
+ * @param wait 延迟时间
+ * */
+export const funcThrottle = (func, wait) => {
+  if (typeof func !== 'function') throw new Error(`${func} is not a function`);
+  if (typeof wait === 'undefined') wait = 500;
+  let previous = 0;
+  let timer = null;
+  return function (...args) {
+    const that = this;
+    const now = new Date();
+    const interval = wait - (now - previous);
+    if (interval <= 0) {
+      func.call(that, ...args);
+      previous = new Date();
+      clearTimeout(timer);
+      timer = null;
+    } else if (!timer) {
+      timer = setTimeout(function () {
+        clearTimeout(timer);
+        timer = null;
+        func.call(that, ...args);
+        previous = new Date();
+      }, interval);
+    }
+  };
+};

+ 6 - 1
src/views/manage/CardItem.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="card-container" :style="{ height: `${height}px` }">
     <div class="card-name">
-      <img :src="img_src" class="logo" />
+      <img :src="img_src" class="logo" @click.stop="handleClickIcon" />
       <div class="name">{{ name }}</div>
     </div>
     <div class="card-label">
@@ -35,6 +35,11 @@ const props = defineProps({
     default: 175
   }
 });
+
+const emit = defineEmits(['clickIcon']);
+const handleClickIcon = () => {
+  emit('clickIcon', props.img_src);
+};
 </script>
 <style scoped lang="css">
 .card-container {