id
wujingjing
2025-04-18 c376e8bc29095620d0cdb556965b08dcbf8bab0e
src/views/project/yw/dataManage/workFlowMgr/WorkFlowIndex.vue
@@ -1,6 +1,7 @@
<template>
   <AHMContainer type="card">
      <template #aside>
         <!-- 目录树 -->
         <LeftTreeByMgr
            v-loading="treeLoading"
            class="h100"
@@ -10,20 +11,18 @@
               label: 'group_name',
               children: 'children',
            }"
            defaultExpandAll
            :treedata="listTreeData"
            title-name="分组列表"
            :treedata="listLeftData"
            title-name="场景列表"
            :show-more-operate="false"
            :show-add="false"
            :current-node-key="currentListID"
            :node-icon="() => 'ele-Document'"
            @click="handleClickNode"
            defaultExpandAll
         >
         </LeftTreeByMgr>
      </template>
      <template #header>
         <!-- 查询、重置、排序、增加表单 -->
         <el-form :inline="true" :model="queryParams">
            <el-form-item label="名称" prop="title">
               <el-input
@@ -38,11 +37,10 @@
               <el-button icon="ele-Refresh" @click="resetQuery">重置 </el-button>
               <el-button icon="ele-Plus" @click="openOptDlg()" type="primary"> 添加 </el-button>
            </el-form-item>
         </el-form></template
      >
      <template #main
         ><!-- 数据展示表格 -->
         <div class="flex-auto flex-column h-full" v-if="state.isShowTrendFun">
         </el-form>
      </template>
      <template #main>
         <div class="flex-auto flex-column h-full">
            <el-table
               v-loading="workFlowDataLoading"
               ref="draggableFormulaTableRef"
@@ -54,7 +52,11 @@
               :data="displayTableData"
               highlight-current-row
            >
               <el-table-column prop="title" label="工作流名称" fixed="left" show-overflow-tooltip align="left" />
               <el-table-column type="index" label="序号" width="55" fixed="left" align="center"></el-table-column>
               <el-table-column prop="id" width="310" label="ID" fixed="left" show-overflow-tooltip align="left" />
               <el-table-column prop="title" width="200" label="工作流名称" fixed="left" show-overflow-tooltip align="left" />
               <el-table-column prop="prompt" width="280" label="工作流提示" show-overflow-tooltip align="center"> </el-table-column>
               <el-table-column prop="published" width="120" label="发布状态" show-overflow-tooltip align="center">
                  <template #default="scope">
@@ -63,15 +65,55 @@
                     }}</el-tag>
                  </template>
               </el-table-column>
               <el-table-column label="调用方式" prop="inner_call" width="100" show-overflow-tooltip>
                  <template #default="scope">
                     {{ scope.row.inner_call === 'Y' ? '内部' : '外部' }}
                  </template>
               </el-table-column>
               <el-table-column prop="create_user" width="100" label="创建者" show-overflow-tooltip align="center" />
               <el-table-column prop="create_time" width="180" label="创建时间" show-overflow-tooltip align="center" />
               <el-table-column prop="note" width="180" label="说明" show-overflow-tooltip align="center" />
               <el-table-column label="操作" width="120" fixed="right" show-overflow-tooltip align="center">
               <el-table-column label="操作" width="180" fixed="right" show-overflow-tooltip align="center">
                  <template #default="scope">
                     <div class="space-x-2.5">
                        <el-tooltip effect="dark" content="编辑" placement="top">
                           <i class="ywifont ywicon-bianji !text-[15px] text-blue-400 cursor-pointer"></i>
                           <i class="ywifont ywicon-bianji !text-[15px] text-blue-400 cursor-pointer" @click="openOptDlg(scope.row)"></i>
                        </el-tooltip>
                        <el-tooltip effect="dark" content="测试工作流" placement="top">
                           <i class="ywifont ywicon-ceshi !text-[20px] text-blue-400 cursor-pointer" @click="openChatTest(scope.row)"></i>
                        </el-tooltip>
                        <el-tooltip effect="dark" content="工作流设计" placement="top">
                           <i
                              class="ywifont ywicon-jiegousheji !text-[15px] text-blue-400 cursor-pointer"
                              @click="gotoFlowDesign(scope.row)"
                           ></i>
                        </el-tooltip>
                        <el-tooltip effect="dark" content="工作流查看" placement="top">
                           <i
                              class="ywifont ywicon-yulan !text-[16px] text-blue-400 cursor-pointer"
                              @click="gotoFlowView(scope.row)"
                           ></i>
                        </el-tooltip>
                        <el-tooltip
                           effect="dark"
                           :content="scope.row.published === SupervisorPublished.Y ? '取消发布' : '发布'"
                           placement="top"
                        >
                           <i
                              class="ywifont !text-[20px] cursor-pointer"
                              :class="[
                                 scope.row.published === SupervisorPublished.Y ? 'ywicon-quxiaofabu text-red-400' : 'ywicon-fabu text-blue-400',
                              ]"
                              @click="
                                 publishStatusChange(
                                    scope.row.published === SupervisorPublished.Y ? SupervisorPublished.N : SupervisorPublished.Y,
                                    scope.row.id,
                                    scope.$index
                                 )
                              "
                           ></i>
                        </el-tooltip>
                        <el-tooltip effect="dark" content="删除" placement="top">
                           <i
                              class="ywifont ywicon-shanchu !text-[17px] text-red-400 cursor-pointer"
@@ -84,103 +126,89 @@
            </el-table>
         </div>
      </template>
      <OptDlg v-model="optDlgIsShow" :item="optDlgMapRow" @insert="insertOpt" :currentListID="currentListID"></OptDlg>
      <OptDlg
         v-model="optDlgIsShow"
         :item="optDlgMapRow"
         @insert="insertOpt"
         :currentListID="currentListID"
         @update="updateOpt"
      ></OptDlg>
      <div
         v-if="chatTestIsShow"
         ref="draggableChatRef"
         :style="style"
         class="fixed z-50 w-[700px] h-[800px] flex flex-col bg-[rgb(239,244,253)] right-0 bottom-0 rounded-lg"
      >
         <div ref="chatDragHandlerRef" class="flex-0">
            <div class="flex items-center justify-between py-2 px-4">
               <div class="font-bold cursor-move">
                  WI水务智能管家——【{{ chatTestMapRow?.title }}】测试
                  <!-- <img src="/static/images/logo/logo-mini.svg" width="10" height="10" /> -->
               </div>
               <i class="ywifont ywicon-guanbi font-[10px] font-bold cursor-pointer" @click="closeChatTest"></i>
            </div>
         </div>
         <Chat ref="chatRef" class="flex-auto px-2" :questionApi="questionAi"> </Chat>
      </div>
   </AHMContainer>
</template>
<script setup lang="ts">
import { useDraggable } from '@vueuse/core';
import type { CancelTokenSource } from 'axios';
import axios from 'axios';
import { ElMessage, ElMessageBox } from 'element-plus';
import { computed, nextTick, onMounted, reactive, ref } from 'vue';
import { computed, nextTick, onMounted, ref } from 'vue';
import OptDlg from './optDlg/OptDlg.vue';
import * as agentGroupApi from '/@/api/ai/agentGroup';
import { delete_workflow_agent, get_workflow_agent_list } from '/@/api/workflow/index';
import {
   check_workflow_agent_validate,
   delete_workflow_agent,
   get_workflow_agent_list,
   publish_workflow_agent,
} from '/@/api/workflow/index';
import Chat from '/@/components/chat/Chat.vue';
import AHMContainer from '/@/components/layout/AHMContainer.vue';
import LeftTreeByMgr from '/@/components/tree/leftTreeByMgr.vue';
import { useQueryTable } from '/@/hooks/useQueryTable';
import router from '/@/router';
import { useCompRef } from '/@/utils/types';
import { convertListToTree, debounce } from '/@/utils/util';
import { convertListToTree, debounce, travelTree } from '/@/utils/util';
import { SupervisorPublished, supervisorPublishedMap } from '/@/views/project/yw/lowCode/sqlAmis/types';
import { OptClassificationMap, classificationEnum } from '/@/views/types/metrics';
const state = reactive({
   tableParams: {
      PageIndex: 1,
      PageSize: 10,
   },
   tableTotal: 0,
   isShowTrendFun: true,
   showViewBack: false,
   detailTitle: '',
});
//#region ====================== 左侧树数据,tree init ======================
const leftTreeRef = useCompRef(LeftTreeByMgr);
const treeLoading = ref(false);
const listData = ref([]);
const listLeftData = ref([]);
const currentListID = computed(() => currentNode.value?.group_id);
const currentNode = ref(null);
const listTreeData = computed(() => {
   const byParentData = convertListToTree(listData.value, {
      ID: 'group_id',
      ParentID: 'p_group_id',
      Children: 'children',
   });
   const result = [];
   byParentData.forEach((item) => {
      if (item.group_type == OptClassificationMap[classificationEnum.Knowledge]) {
         result.push(item);
      }
   });
   return result;
});
const handleClickNode = (data) => {
   nextTick(() => {
      leftTreeRef.value?.treeRef.setCurrentKey(data.id);
      leftTreeRef.value?.treeRef.setCurrentKey(data.group_id);
   });
   currentNode.value = data;
   getWorkFlowData();
};
const getListTreeData = async () => {
   const res = await agentGroupApi.getSceneGroupTreeByPost();
   listData.value = res.groups || [];
   const firstListTreeNode = listTreeData.value[0];
   if (firstListTreeNode) {
      handleClickNode(firstListTreeNode);
   } else {
      tableData.value = [];
      currentNode.value = null;
   }
   tableData.value = data.sampleList;
};
//#endregion
//#region ====================== 获取、删除表格数据 ======================
//#region ====================== 工作流列表 ======================
const workFlowDataLoading = ref(false);
const workFlowTableData = ref([]);
const tableData = ref([]);
const isFormulaTableDrag = ref(false);
const getWorkFlowData = async () => {
   workFlowDataLoading.value = true;
   const res = await get_workflow_agent_list().finally(() => {
      workFlowDataLoading.value = false;
   });
   if (res?.json_ok) {
      const resData = res.values ?? [];
      resData.forEach((item) => {
         item.published = item.published;
         item.create_time = item.create_time.slice(0, 19);
         item.prompt = item.supervisor.prompt;
         item.note = item.note;
         item.create_user = item.create_user;
      });
      workFlowTableData.value = resData;
   } else {
      workFlowDataLoading.value = false;
      ElMessage.error('获取工作流列表失败' + (res?.json_msg ? `,${JSON.stringify(res.json_msg)}` : ''));
   }
   tableData.value = workFlowTableData.value.filter(
      (item) =>
         item.agent_group === currentListID.value ||
         currentNode.value.children?.some((treeItem) => treeItem.group_id === item.agent_group)
   );
const tree_update_data = ref([]);
const work_update_data = ref([]);
const tableData = ref([]);
//获取场景list
const initData = async () => {
   const [treeData, workData] = await Promise.all([agentGroupApi.getSceneGroupTreeByPost(), get_workflow_agent_list()]);
   const tree_Data = treeData.groups ?? [];
   const work_Data = workData.values ?? [];
   tree_update_data.value = tree_Data;
   work_update_data.value = work_Data;
   const byParent_Data = updateSampleListsAndGroupNames(tree_Data, work_Data);
   listLeftData.value = byParent_Data;
   const firstListTreeNode = byParent_Data[0];
   tableData.value = firstListTreeNode.sampleList;
   currentNode.value = firstListTreeNode;
};
// 删除一条数据
const deleteCurrentFormulaRow = (row: any) => {
   ElMessageBox.confirm(`确定删除工作流:【${row.title}】?`, '提示', {
      confirmButtonText: '确定',
@@ -195,25 +223,74 @@
         ElMessage.success('删除工作流成功');
         const index = tableData.value.findIndex((d) => d.id === row.id);
         tableData.value.splice(index, 1);
         work_update_data.value = work_update_data.value.filter((work) => work.id !== row.id);
         removeDataAndRecalculateGroupNames(tree_update_data.value, work_update_data.value, row.id);
      } else {
         ElMessage.error('删除工作流失败' + (res?.json_msg ? `,${JSON.stringify(res.json_msg)}` : ''));
      }
   });
};
//#endregion
//#region ====================== 添加修改操作 ======================
const optDlgIsShow = ref(false);
const optDlgMapRow = ref(null);
const openOptDlg = (row?: any) => {
   optDlgMapRow.value = row;
   optDlgIsShow.value = true;
// 更新树形结构和工作流列表
const updateSampleListsAndGroupNames = (tree_Data, work_Data) => {
   let new_tree_Data = [];
   tree_Data.forEach((node, index) => {
      new_tree_Data.push(node);
   });
   // 为每个节点关联工作流数据
   new_tree_Data.forEach((node) => {
      node.sampleList = work_Data
         .filter((work) => work.agent_group === node.group_id)
         .map((work) => ({
            ...work,
            published: work.published,
            create_user: work.create_user,
            create_time: work.create_time,
            prompt: work.supervisor.prompt,
            note: work.note,
         }));
      if (node.p_group_id) {
         node.group_name = node.group_name.replace(/ \([^)]*\)$/, '') + ` (${node.sampleList.length})`;
      }
   });
   // 将列表转换为树形结构
   const byParentData = convertListToTree(new_tree_Data, {
      ID: 'group_id',
      ParentID: 'p_group_id',
      Children: 'children',
   });
   const updatedByParentData = byParentData.reduce((accumulator, item) => {
      // 处理子节点,累加sampleList
      if (item.children && item.children.length > 0) {
         item.children.forEach((child_node) => {
            item.sampleList = item.sampleList.concat(child_node.sampleList);
         });
      }
      // 移除旧的计数并添加新的计数
      item.group_name = item.group_name.replace(/ \([^)]*\)$/, '') + ` (${item.sampleList.length})`;
      // 将当前项添加到累加器中
      accumulator.push(item);
      // 返回累加器
      return accumulator;
   }, []); // 初始化累加器为空数组
   return updatedByParentData;
};
// 插入一条数据时调用
const addDataAndRecalculateGroupNames = (new_tree_Data, work_Data, newWorkData) => {
   // 添加新数据到 work_Data
   work_Data.push(newWorkData);
   // 重新计算工作流列表和组名
   return updateSampleListsAndGroupNames(new_tree_Data, work_Data);
};
// 删除一条数据时调用
const removeDataAndRecalculateGroupNames = (new_tree_Data, work_Data, workIdToRemove) => {
   // 过滤掉要删除的数据
   work_Data = work_Data.filter((work) => work.id !== workIdToRemove);
   // 重新计算工作流列表和组名
   return updateSampleListsAndGroupNames(new_tree_Data, work_Data);
};
const updateOpt = (formValue) => {};
//新增一条信息
const insertOpt = (newData) => {
   tableData.value.unshift({ ...newData });
};
//#endregion
//#region ====================== 表格查询、排序,search form init ======================
@@ -225,15 +302,144 @@
});
const debounceQueryTable = debounce(handleQueryTable, 400);
//#endregion
//#region ====================== 添加修改操作 ======================
const optDlgIsShow = ref(false);
const optDlgMapRow = ref(null);
const openOptDlg = (row?: any) => {
   optDlgMapRow.value = row;
   optDlgIsShow.value = true;
};
//#region ====================== 挂载时获取初始数据 ======================
onMounted(() => {
   getListTreeData();
});
const updateOpt = (formValue) => {
   travelTree(tableData.value, (value, index, array) => {
      if (value.id === formValue.id) {
         array[index] = {
            ...array[index],
            ...formValue,
         };
         return true;
      }
   });
};
//新增一条信息
const insertOpt = (newData) => {
   const itemToAdd = {
      ...newData,
      supervisor: {
         ...newData.supervisor, // 保留原有的 supervisor 属性
         prompt: newData.prompt, // 但确保 prompt 是最新的
      },
   };
   tableData.value.unshift(itemToAdd);
   addDataAndRecalculateGroupNames(tree_update_data.value, work_update_data.value, itemToAdd);
};
//#endregion
//#region ====================== Chat 测试 ======================
const chatRef = useCompRef(Chat);
const chatTestMapRow = ref(null);
const chatTestIsShow = ref(false);
const openChatTest = (row) => {
   chatTestMapRow.value = row;
   chatTestIsShow.value = true;
   nextTick(() => {
      chatRef.value.clear();
   });
};
const closeChatTest = () => {
   chatTestMapRow.value = null;
   chatTestIsShow.value = false;
};
const draggableChatRef = ref<HTMLElement | null>(null);
const chatDragHandlerRef = ref<HTMLDivElement>(null);
const { x, y, style } = useDraggable(draggableChatRef, {
   handle: chatDragHandlerRef,
   initialValue: {
      x: document.body.clientWidth / 2 - 350,
      y: document.body.clientHeight / 2 - 400,
   },
});
const questionAi = async (text, sourceObj: { source: CancelTokenSource }) => {
   const currentSource = axios.CancelToken.source();
   sourceObj.source = currentSource;
   const res = await check_workflow_agent_validate(
      {
         agent_id: chatTestMapRow.value.id,
         question: text,
      },
      {
         loading: false,
         cancelToken: currentSource.token,
      }
   );
   return res;
};
//#endregion
//#region ====================== 工作流设计 ======================
const gotoFlowDesign = (row) => {
   router.push({
      name: 'FlowApp',
      query: {
         id: row.id,
      },
   });
};
const gotoFlowView = (row) => {
   router.push({
      name: 'FlowAppView',
      query: {
         id: row.id,
      },
   });
};
//#endregion
//#region ====================== 改变发布状态 ======================
const publishStatusChange = async (published: SupervisorPublished, id, index) => {
   const res = await publish_workflow_agent(
      {
         agent_id: id,
         publish: published,
      },
      {
         loading: false,
      }
   );
   const origin = published === SupervisorPublished.Y ? SupervisorPublished.N : SupervisorPublished.Y;
   const final = res.publish ?? origin;
   if (final === origin) {
      ElMessage.warning('操作失败' + (res.fail_msg ? `:${res.fail_msg}` : ''));
      return;
   }
   tableData.value[index].published = final;
   published === SupervisorPublished.Y ? ElMessage.success('发布成功') : ElMessage.info('已取消发布');
};
//#endregion
onMounted(async () => {
   initData();
});
</script>
<style scoped lang="scss">
:deep(.el-card__body) {
   padding-bottom: unset;
.set-permission {
   padding-block: 16px;
   padding-block-end: 0;
}
.set-form {
   display: block;
   box-sizing: border-box;
   height: 100%;
   padding-block: 16px;
   .set-form-item {
      font-weight: 500;
      color: #667085;
      font-size: 14px;
   }
}
</style>