From 31adf735882bc748da682e17748ee029656c6b47 Mon Sep 17 00:00:00 2001 From: wujingjing <gersonwu@qq.com> Date: 星期四, 02 一月 2025 17:38:59 +0800 Subject: [PATCH] 大模型管理 --- src/views/project/yw/systemManage/llmMgr/components/ModelConfigDlg.vue | 39 ++++ src/api/login/UserMenuData.ts | 16 ++ src/views/project/yw/systemManage/llmMgr/components/LLMConfigDlg.vue | 43 +++++ src/api/admin/llm.ts | 10 + customer_list/yw/static/config/route.js | 7 src/views/project/yw/systemManage/llmMgr/components/LLMConnectDrawer.vue | 120 +++++++++++++++ src/views/project/yw/systemManage/llmMgr/LLMMgr.vue | 244 ++++++++++++++++++++++++++++++ 7 files changed, 479 insertions(+), 0 deletions(-) diff --git a/customer_list/yw/static/config/route.js b/customer_list/yw/static/config/route.js index 32a6499..5cbc31f 100644 --- a/customer_list/yw/static/config/route.js +++ b/customer_list/yw/static/config/route.js @@ -21,6 +21,13 @@ component: '/project/yw/systemManage/flowApp/FlowApp.vue', }, { + name: 'LLMMgr', + isKeepAlive: true, + isAffix: false, + path: '/llm/mgr', + component: '/project/yw/systemManage/llmMgr/LLMMgr.vue', + }, + { name: 'FlowAppView', isKeepAlive: true, isAffix: false, diff --git a/src/api/admin/llm.ts b/src/api/admin/llm.ts new file mode 100644 index 0000000..716e479 --- /dev/null +++ b/src/api/admin/llm.ts @@ -0,0 +1,10 @@ +import request from '/@/utils/request'; +export const GetLLMInfoList = async ( req: any = request) => { + return req({ + url: '/admin/sample/get_llm_info_list', + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + }); +}; diff --git a/src/api/login/UserMenuData.ts b/src/api/login/UserMenuData.ts index 574be5c..ef5767d 100644 --- a/src/api/login/UserMenuData.ts +++ b/src/api/login/UserMenuData.ts @@ -113,6 +113,22 @@ }, { Children: [], + ID: '1126', + ParentID: '1742436890822447104', + Type: 2, + Name: '澶фā鍨嬬鐞�', + Path: '/llm/mgr', + Permission: '', + Icon: 'ywifont ywicon-tishici', + IsIframe: false, + OutLink: '', + IsHide: false, + Weight: 0, + SortCode: 2, + Description: '', + }, + { + Children: [], ID: '1127', ParentID: '1742436890822447104', Type: 2, diff --git a/src/views/project/yw/systemManage/llmMgr/LLMMgr.vue b/src/views/project/yw/systemManage/llmMgr/LLMMgr.vue new file mode 100644 index 0000000..8cb4f32 --- /dev/null +++ b/src/views/project/yw/systemManage/llmMgr/LLMMgr.vue @@ -0,0 +1,244 @@ +<template> + <HMContainer type="card"> + <template #header> + <el-form ref="queryFormRef" :inline="true" :model="queryParams"> + <el-form-item label="鏍囬" prop="title"> + <el-input v-model="queryParams.title" style="width: 226.4px" placeholder="鏍囬" clearable @input="debounceQueryTable" /> + </el-form-item> + + <el-form-item> + <!-- <el-button type="primary" icon="ele-Search" @click="handleQueryTable"> 鏌ヨ </el-button> --> + <el-button icon="ele-Refresh" @click="resetQuery">閲嶇疆 </el-button> + <!-- <el-button icon="ele-Plus" @click="openOptDlg()"> 娣诲姞 </el-button> --> + </el-form-item> + </el-form> + </template> + <template #main> + <div class="h-full flex-column"> + <!-- <div class="flex-0 flex"> + <ColFilter class="ml-auto" :columnList="columnList" /> + </div> --> + <el-table + v-loading="tableLoading" + ref="draggableTableRef" + class="flex-auto" + border + :row-class-name="isDragStatus ? 'cursor-move' : 'cursor-pointer'" + :data="displayTableData" + highlight-current-row + > + <template v-for="item in columnList"> + <el-table-column + :key="item.prop" + v-if="item.isShow ?? true" + :type="item.type" + :prop="item.prop" + :label="item.label" + :fixed="item.fixed" + :width="item.width" + :align="item.align" + showOverflowTooltip + > + <template #default="scope" v-if="item.prop === 'operate'"> + <div class="space-x-3 items-center flex"> + <!-- <el-tooltip effect="dark" content="AMIS浣庝唬鐮佺紪杈�" placement="top"> + <i class="ywifont ywicon-didaima !text-[21px] text-blue-400 cursor-pointer" @click="gotoAmisPage(scope.row)"></i> + </el-tooltip> + <el-tooltip effect="dark" content="鏁版嵁瀵规帴" placement="top"> + <i class="ywifont ywicon-sjdj !text-[17px] text-blue-400 cursor-pointer" @click="editSqlClick(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-shezhi !text-[19px] text-blue-400 cursor-pointer" @click="openConfigDlg(scope.row)"></i> + </el-tooltip> + <el-tooltip effect="dark" content="杩炴帴妯″瀷" placement="top"> + <i class="ywifont ywicon-lizi !text-[16px] text-blue-400 cursor-pointer" @click="openConnectDrawer(scope.row)"></i> + </el-tooltip> + + <!-- <el-tooltip effect="dark" content="缂栬緫" placement="top"> + <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-shanchu !text-[17px] text-red-400 cursor-pointer" + @click=" + () => { + deleteCurrentRow(scope.row, '椤甸潰', supervisorAdminApi.deleteSupervisor, () => { + const foundIndex = tableData.findIndex((item) => item === scope.row); + foundIndex > -1 && tableData.splice(foundIndex, 1); + }); + } + " + ></i> + </el-tooltip> --> + </div> + </template> + </el-table-column> + </template> + </el-table> + </div> + </template> + <LLMConfigDlg v-model="configDlgIsShow" :item="configDlgMapRow"></LLMConfigDlg> + <!-- <OptDlg v-model="optDlgIsShow" :item="optDlgMapRow" @insert="insertOpt" @update="updateOpt"></OptDlg> --> + <LLMConnectDrawer v-model="connectDrawerIsShow" :item="connectDrawerMapRow"></LLMConnectDrawer> + </HMContainer> +</template> + +<script setup lang="ts"> +import { debounce, deleteCurrentRow } from '/@/utils/util'; + +import { onMounted, ref } from 'vue'; +import { usePageDisplay } from '/@/hooks/usePageDisplay'; +import { useQueryTable } from '/@/hooks/useQueryTable'; +// import { useTableSort } from '/@/hooks/useTableSort'; +// import { useValidateUniqueness } from '/@/hooks/useValidateUniqueness'; +import { ElMessage } from 'element-plus'; +import { SupervisorPublished } from '../../lowCode/sqlAmis/types'; +// import OptDlg from './optDlg/OptDlg.vue'; +import * as supervisorAdminApi from '/@/api/supervisorAdmin'; +import { updatePublishStatus } from '/@/api/supervisorAdmin'; +import { GetLLMInfoList } from '/@/api/admin/llm'; +import HMContainer from '/@/components/layout/HMContainer.vue'; +import ColFilter from '/@/components/table/colFilter/ColFilter.vue'; +import type { TableCol } from '/@/components/table/colFilter/types'; +import LLMConnectDrawer from './components/LLMConnectDrawer.vue'; +import { useUpdateData } from '/@/hooks/useUpdateData'; +import LLMConfigDlg from './components/LLMConfigDlg.vue'; +const columnList = ref<TableCol[]>([ + { type: 'index', label: '搴忓彿', width: 55, fixed: 'left', align: 'center' }, + { prop: 'title', label: '鏍囬', fixed: 'left' }, + + { prop: 'operate', label: '鎿嶄綔', width: 200, fixed: 'right' }, +]); + +//#region ====================== 琛ㄦ牸鏁版嵁锛宼able init ====================== + +const tableLoading = ref(false); +const tableData = ref(null); +const isDragStatus = ref(false); +const getTableData = async () => { + const res = await GetLLMInfoList(); + tableData.value = Object.keys(res.values || {}).map((key) => { + return { + id: key, + ...res.values[key], + }; + }); +}; +//#endregion + +//#region ====================== 琛ㄦ牸鏌ヨ銆佹帓搴忥紝search form init ====================== + +const queryParams = ref({ + title: '', +}); +const { resetQuery, handleQueryTable, displayTableData } = useQueryTable(tableData, queryParams, () => { + displayTableData.value = tableData.value; +}); +const debounceQueryTable = debounce(handleQueryTable, 400); +//#endregion + +//#region ====================== 鏌ヨ蹇嵎閿� ====================== +const queryFormRef = ref(null); +const pressEnterSearch = (ev: KeyboardEvent) => { + if (ev.key === 'Enter') { + handleQueryTable(); + } +}; +usePageDisplay( + () => { + queryFormRef.value?.$el?.addEventListener('keypress', pressEnterSearch); + }, + () => { + queryFormRef.value?.$el?.removeEventListener('keypress', pressEnterSearch); + } +); +//#endregion + +//#region ====================== 娣诲姞淇敼鎿嶄綔 ====================== +const optDlgIsShow = ref(false); +const optDlgMapRow = ref(null); +const openOptDlg = (row?: any) => { + optDlgMapRow.value = row; + optDlgIsShow.value = true; +}; + +const updateOpt = (formValue) => { + const foundIndex = tableData.value.findIndex((item) => item.id === formValue.id); + if (foundIndex > -1) { + tableData.value[foundIndex] = { + ...tableData.value[foundIndex], + ...formValue, + }; + } +}; + +const insertOpt = (newData) => { + tableData.value.unshift({ ...newData, published: SupervisorPublished.N }); +}; +//#endregion + +const updatePublishedById = (id: string, published: SupervisorPublished) => { + const row = tableData.value.find((item) => item.id === id); + if (row) { + row.published = published; + } +}; +//#region ====================== 鏀瑰彉鍙戝竷鐘舵�� ====================== +const publishStatusChange = async (published: SupervisorPublished, id, index) => { + const res = await updatePublishStatus( + { + 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 + +//#region ====================== 鏇存柊鍙戝竷鐘舵�� ====================== +useUpdateData({ + event: 'supervisor.publish', + updateFun({ id, published }) { + updatePublishedById(id, published); + }, +}); +//#endregion +//#region ====================== 澶фā鍨嬮厤缃� ====================== +const configDlgIsShow = ref(false); +const configDlgMapRow = ref(null); +const openConfigDlg = (row?: any) => { + configDlgMapRow.value = row; + configDlgIsShow.value = true; +}; + +//#endregion +//#region ====================== 澶фā鍨嬭繛鎺� ====================== +const connectDrawerIsShow = ref(false); +const connectDrawerMapRow = ref(null); +const openConnectDrawer = (row?: any) => { + connectDrawerMapRow.value = row; + connectDrawerIsShow.value = true; +}; +//#endregion + +onMounted(() => { + getTableData(); +}); +</script> +<style scoped lang="scss"></style> diff --git a/src/views/project/yw/systemManage/llmMgr/components/LLMConfigDlg.vue b/src/views/project/yw/systemManage/llmMgr/components/LLMConfigDlg.vue new file mode 100644 index 0000000..2f12ae5 --- /dev/null +++ b/src/views/project/yw/systemManage/llmMgr/components/LLMConfigDlg.vue @@ -0,0 +1,43 @@ +<template> + <yw-dialog v-model="isShow" :showFooter="false" width="500" :title="title"> + <el-form label-width="76" v-if="item.config"> + <el-form-item v-for="key in Object.keys(item.config)" :label="keyMapLabel[key]" :key="key"> + <el-input v-model="item.config[key]" readonly /> + </el-form-item> + </el-form> + </yw-dialog> +</template> + +<script setup lang="ts" name="LLMConfigDlg"> +import _ from 'lodash'; +import { computed, ref, watch } from 'vue'; +import ywDialog from '/@/components/dialog/yw-dialog.vue'; + +const props = defineProps(['item']); +const isShow = defineModel({ + type: Boolean, +}); + +const keyMapLabel = { + key: '瀵嗛挜', + base_url: '鍩虹URL', + proxy: '浠g悊', +}; + +const title = computed(() => props.item?.title + '鈥斺�旈厤缃�'); + +// 璁$畻鏈�闀跨殑label瀹藉害 +// const labelWidth = computed(() => { +// const labels = Object.keys(props.item?.config ?? {}).map((key) => keyMapLabel[key]); +// const maxLengthLabel = labels.reduce((prev, current) => { +// return prev.length > current.length ? prev : current; +// }, ''); +// // 姣忎釜涓枃瀛楃鎸�16px璁$畻,棰濆鍔�20px鐣欑櫧 +// return `${maxLengthLabel.length * 16 + 20}px`; +// }); +</script> +<style scoped lang="scss"> +:deep(.el-card__body) { + position: relative; +} +</style> diff --git a/src/views/project/yw/systemManage/llmMgr/components/LLMConnectDrawer.vue b/src/views/project/yw/systemManage/llmMgr/components/LLMConnectDrawer.vue new file mode 100644 index 0000000..dc5e0b4 --- /dev/null +++ b/src/views/project/yw/systemManage/llmMgr/components/LLMConnectDrawer.vue @@ -0,0 +1,120 @@ +<template> + <div class="custom-drawer"> + <el-drawer v-model="drawerIsShow" direction="rtl" size="40%"> + <template #header> + <div> + <i class="ywifont ywicon-lizi !text-[15px] text-blue-400 cursor-pointer font-bold"></i> + <!-- <SvgIcon name="ele-User" :size="16" style="margin-right: 3px; display: inline; vertical-align: middle" /> --> + <span> {{ `銆�${props.item?.title}銆戞ā鍨媊 }} </span> + </div> + </template> + <div class="flex-column h-full"> + <div class="flex-0 flex-col flex"> + <el-form :inline="true" :model="queryParams"> + <el-form-item label="鏍囬" prop="title"> + <el-input v-model="queryParams.title" style="width: 226.4px" placeholder="鏍囬" clearable @input="debounceQueryTable" /> + </el-form-item> + </el-form> + <!-- <span class="mt-[-12px] mb-[12px]">鍏辨湁 {{ displayTableData.length }} 鏉℃暟鎹�</span> --> + </div> + <el-table class="flex-auto" size="small" v-loading="accountTableLoading" border :data="displayTableData" style="width: 100%"> + <el-table-column label="搴忓彿" fixed="left" width="55" show-overflow-tooltip> + <template #default="scope"> + {{ scope.$index + 1 }} + </template> + </el-table-column> + + <el-table-column prop="title" width="120" label="鏍囬" fixed="left" show-overflow-tooltip /> + <el-table-column prop="class" label="绫�" show-overflow-tooltip /> + <el-table-column label="鎿嶄綔" width="80" fixed="right"> + <template #default="scope"> + <div class="space-x-3 items-center flex"> + <el-tooltip effect="dark" content="鏌ョ湅閰嶇疆" placement="top"> + <i + class="ywifont ywicon-shezhi !text-[19px] text-blue-400 cursor-pointer" + @click="openModelConfigDlg(scope.row)" + ></i> + </el-tooltip> + </div> + </template> + </el-table-column> + </el-table> + </div> + </el-drawer> + <teleport to="body"> + <ModelConfigDlg v-model="modelConfigDlg" :item="modelConfigDlgItem" /> + </teleport> + </div> +</template> + +<script setup lang="ts"> +import { ref, watch } from 'vue'; +import { getUserSampleListByPost } from '/@/api/sampleAdmin/index'; +import { useQueryTable } from '/@/hooks/useQueryTable'; +import { convertListToTree, debounce, travelTree } from '/@/utils/util'; +import { onMounted } from 'vue'; +import { getSceneGroupTreeByPost } from '/@/api/scene'; +import ModelConfigDlg from './ModelConfigDlg.vue'; +const props = defineProps(['item']); +//#region ====================== 鐢ㄦ埛璐︽埛 ====================== + +const drawerIsShow = defineModel('modelValue', { + type: Boolean, + default: false, +}); +const accountTableData = ref([]); +const accountTableLoading = ref(false); + +const getSystemAccountByUserID = async () => { + accountTableData.value = Object.keys(props.item?.connects ?? {}).map((key) => { + return { + id: key, + ...props.item?.connects[key], + }; + }); +}; + +//#region ====================== 鏌ヨ ====================== +const getEmptyParams = () => { + return { + title: '', + }; +}; +const queryParams = ref(getEmptyParams()); +const { resetQuery, handleQueryTable, displayTableData } = useQueryTable(accountTableData, queryParams, () => { + displayTableData.value = accountTableData.value; +}); +const debounceQueryTable = debounce(handleQueryTable, 400); +const groupChange = (val) => { + handleQueryTable(); +}; + +//#endregion + +const groupTree = ref(null); + +watch( + () => drawerIsShow.value, + (val) => { + if (!val) { + accountTableData.value = []; + queryParams.value = getEmptyParams(); + return; + } + + getSystemAccountByUserID(); + } +); + +//#endregion + +//#region ====================== 鎵撳紑妯″瀷閰嶇疆瀵硅瘽妗� ====================== +const modelConfigDlg = ref(false); +const modelConfigDlgItem = ref(null); +const openModelConfigDlg = (row) => { + modelConfigDlg.value = true; + modelConfigDlgItem.value = row; +}; +//#endregion +</script> +<style scoped lang="scss"></style> diff --git a/src/views/project/yw/systemManage/llmMgr/components/ModelConfigDlg.vue b/src/views/project/yw/systemManage/llmMgr/components/ModelConfigDlg.vue new file mode 100644 index 0000000..4493a4b --- /dev/null +++ b/src/views/project/yw/systemManage/llmMgr/components/ModelConfigDlg.vue @@ -0,0 +1,39 @@ +<template> + <yw-dialog v-model="isShow" :showFooter="false" width="500" :title="title"> + <el-form label-width="56" v-if="item.config"> + <el-form-item v-for="key in Object.keys(item.config)" :label="keyMapLabel[key]" :key="key"> + <el-input v-model="item.config[key]" readonly /> + </el-form-item> + </el-form> + </yw-dialog> +</template> + +<script setup lang="ts" name="ModelConfigDlg"> +import _ from 'lodash'; +import { computed, ref, watch } from 'vue'; +import ywDialog from '/@/components/dialog/yw-dialog.vue'; + +const props = defineProps(['item']); +const isShow = defineModel({ + type: Boolean, +}); +const keyMapLabel = { + model: '妯″瀷', +}; +const title = computed(() => props.item?.title + '鈥斺�旈厤缃�'); + +// 璁$畻鏈�闀跨殑label瀹藉害 +// const labelWidth = computed(() => { +// const labels = ['妯″瀷']; +// const maxLengthLabel = labels.reduce((prev, current) => { +// return prev.length > current.length ? prev : current; +// }, ''); +// // 姣忎釜涓枃瀛楃鎸�16px璁$畻,棰濆鍔�20px鐣欑櫧 +// return `${maxLengthLabel.length * 16 + 20}px`; +// }); +</script> +<style scoped lang="scss"> +:deep(.el-card__body) { + position: relative; +} +</style> -- Gitblit v1.9.3