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