wujingjing
2025-01-02 31adf735882bc748da682e17748ee029656c6b47
大模型管理
已修改2个文件
已添加5个文件
479 ■■■■■ 文件已修改
customer_list/yw/static/config/route.js 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/admin/llm.ts 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/login/UserMenuData.ts 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/yw/systemManage/llmMgr/LLMMgr.vue 244 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/yw/systemManage/llmMgr/components/LLMConfigDlg.vue 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/yw/systemManage/llmMgr/components/LLMConnectDrawer.vue 120 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/project/yw/systemManage/llmMgr/components/ModelConfigDlg.vue 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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,
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',
        },
    });
};
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,
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 ====================== è¡¨æ ¼æ•°æ®ï¼Œtable 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>
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: '代理',
};
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>
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>
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>