| | |
| | | <template> |
| | | <div class="flex flex-col h100"> |
| | | <div class="mb-[10px] flex flex-shrink-0 items-center"> |
| | | <el-button style="margin-left: 8px; width: 40px" text @click="handleExitFlow"> |
| | | <el-icon style="font-size: 24px !important"> |
| | | <ArrowLeft /> |
| | | </el-icon> |
| | | </el-button> |
| | | <span class="text-[24px] text-[#26244c] font-[700]">{{ state.detailTitle }}</span> |
| | | </div> |
| | | <div class="set-table-height"> |
| | | <div class="text-[16px] font-[500]">数据管理</div> |
| | | <!-- 查询、重置、排序、增加表单 --> |
| | | <el-form :inline="true" :model="graphQueryParams"> |
| | | <el-form-item label="名称" prop="title"> |
| | | <el-input v-model="graphQueryParams.title" style="width: 226.4px" placeholder="文件名称" clearable></el-input> |
| | | </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="importData()" type="primary"> 导入数据 </el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | <div class="flex-auto flex-column h-full"> |
| | | <el-table |
| | | ref="viewKnowledgeTableRef" |
| | | border |
| | | row-key="id" |
| | | class="flex-auto" |
| | | :header-cell-style="{ textAlign: 'center' }" |
| | | :data="displayTableData" |
| | | highlight-current-row |
| | | > |
| | | <el-table-column prop="name" label="文件名称" fixed="left" show-overflow-tooltip align="left" /> |
| | | <el-table-column prop="type" width="120" label="文件格式" show-overflow-tooltip align="center" /> |
| | | <el-table-column prop="time" label="上传时间" show-overflow-tooltip width="280" align="center"></el-table-column> |
| | | <el-table-column label="操作" width="80" fixed="right" show-overflow-tooltip align="center"> |
| | | <template #default="scope"> |
| | | <el-tooltip effect="dark" content="删除" placement="top"> |
| | | <i |
| | | class="ywifont ywicon-shanchu !text-[17px] text-red-400 cursor-pointer" |
| | | @click="deleteKnowledgeFileData(scope.row)" |
| | | ></i> |
| | | </el-tooltip> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <div class="h100" v-show="!state.showKnowledgeForm"> |
| | | <div class="mb-[10px] flex flex-shrink-0 items-center"> |
| | | <el-button style="margin-left: 8px; width: 40px" link @click="handleExitFlow"> |
| | | <el-icon style="font-size: 24px !important"> |
| | | <ArrowLeft /> |
| | | </el-icon> |
| | | </el-button> |
| | | <span class="text-[24px] text-[#26244c] font-[700]">{{ state.knowledgeInfo.knowledge_title }}</span> |
| | | </div> |
| | | <div class="set-table-height"> |
| | | <!-- 查询、重置、排序、增加表单 --> |
| | | <el-form :inline="true" :model="graphQueryParams"> |
| | | <el-form-item label="名称" prop="title"> |
| | | <el-input v-model="graphQueryParams.title" style="width: 226.4px" placeholder="文件名称" clearable></el-input> |
| | | </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="importKnowledgeData()" type="primary"> 导入数据 </el-button> |
| | | </el-form-item> |
| | | </el-form> |
| | | <div class="flex-auto flex-column h-full"> |
| | | <el-table |
| | | ref="viewKnowledgeTableRef" |
| | | border |
| | | row-key="id" |
| | | class="flex-auto" |
| | | :header-cell-style="{ textAlign: 'center' }" |
| | | :data="displayTableData" |
| | | highlight-current-row |
| | | > |
| | | <el-table-column prop="name" label="文件名称" fixed="left" show-overflow-tooltip align="left" /> |
| | | <el-table-column prop="type" width="120" label="文件类型" show-overflow-tooltip align="center" /> |
| | | <el-table-column prop="time" label="上传时间" show-overflow-tooltip width="280" align="center"></el-table-column> |
| | | <el-table-column label="操作" width="120" 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-wendang !text-[17px] cursor-pointer text-blue-400" |
| | | @click="openOperateDemoPage(scope.row)" |
| | | ></i> |
| | | </el-tooltip> |
| | | <el-tooltip effect="dark" content="分块" placement="top"> |
| | | <i |
| | | class="ywifont ywicon-01wenjianfenkuai !text-[17px] cursor-pointer text-blue-400" |
| | | @click="handleViewKnowledgeFile(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="deleteKnowledgeFileData(scope.row)" |
| | | ></i> |
| | | </el-tooltip> |
| | | </div> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="h100" v-show="state.showKnowledgeForm"> |
| | | <div class="mb-[10px] flex flex-shrink-0 items-center"> |
| | | <el-button style="margin-left: 8px; width: 40px" link @click="handleShowKnowledge"> |
| | | <el-icon style="font-size: 24px !important"> |
| | | <ArrowLeft /> |
| | | </el-icon> |
| | | </el-button> |
| | | <span class="text-[24px] text-[#26244c] font-[700]">{{ state.knowledgeInfo.knowledge_import_data }}</span> |
| | | </div> |
| | | <div class="set-table-height"> |
| | | <el-form :model="state.knowledgeFile" label-width="120px" label-position="left" class="h100" ref="knowledgeFileRef"> |
| | | <el-divider content-position="left"><span class="text-[18px] font-[500]">选择文件</span></el-divider> |
| | | <div class="flex items-start gap-[16px] max-h-[686px] min-h-[360px] set-file-height"> |
| | | <div class="min-h-[360px] h100 set_file_left"> |
| | | <div class="font-[500] mb-2">请选择文档</div> |
| | | <div class="left_content"> |
| | | <div class="file_menu"> |
| | | <LeftTreeByMgr |
| | | title-name="类目" |
| | | ref="leftTreeRef" |
| | | :treedata="state.knowledgeBaseData" |
| | | :current-node-key="currentListID" |
| | | :defaultProps="{ |
| | | children: 'Children', |
| | | label: 'title', |
| | | id: 'id', |
| | | }" |
| | | @click="handleClickNode" |
| | | > |
| | | </LeftTreeByMgr> |
| | | </div> |
| | | <div class="file_table"> |
| | | <el-table |
| | | :data="state.fileData" |
| | | border |
| | | @select="handleSelectItem" |
| | | highlight-current-row |
| | | ref="multipleTableRef" |
| | | @select-all=" |
| | | (selection) => { |
| | | handleSelectAll(selection, state.fileData); |
| | | } |
| | | " |
| | | > |
| | | <el-table-column type="selection" width="55" /> |
| | | <el-table-column prop="name" label="文件名称" /> |
| | | </el-table> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="set_file_right"> |
| | | <div class="h100"> |
| | | <div class="font-[500] mb-[12px]">已选文档{{ multipleSelection.length }}/50个</div> |
| | | <div class="selected_file"> |
| | | <div v-for="(item, index) in multipleSelection" :key="index" class="set_file_item"> |
| | | <span class="set_file_name">{{ item.name }}</span> |
| | | <span class="ywifont ywicon-guanbi set_file_close" @click="handleRemoveItem(item)"></span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <el-divider content-position="left"><span class="text-[18px]">数据处理</span></el-divider> |
| | | <el-form-item label="文档切分chunk:"> |
| | | <div class="flex-auto flex flex-col"> |
| | | <div class="flex"> |
| | | <div v-for="(item, index) in state.segmentationList" :key="index"> |
| | | <div |
| | | class="bg-[#fff] border-[1px] border-solid border-[#d8d9e6] py-[12px] w-[215px] mr-[10px] px-[16px] rounded-lg cursor-pointer" |
| | | :class="{ activeColor: state.knowledgeForm.segmentationMode === item.ID }" |
| | | @click="activeDataProcessType(item.ID)" |
| | | > |
| | | <div class="flex items-center"> |
| | | <div class="data_left"> |
| | | <img :src="item.ImageURL" class="w-[40px] h-[40px] mr-[10px]" /> |
| | | </div> |
| | | <div class="data_right"> |
| | | <el-radio v-model="state.knowledgeForm.segmentationMode" size="large" :label="item.ID"> |
| | | <span class="font-[500] text[16px]">{{ item.Name }}</span> |
| | | </el-radio> |
| | | |
| | | <el-tooltip :content="item.DemoDesc" placement="top" effect="light" popper-class="set_tooltip_demo"> |
| | | <div class="text-[#878aab] text-[12px] mx-0 mt-[2px] mb-0 set-desc"> |
| | | {{ item.DemoDesc }} |
| | | </div> |
| | | </el-tooltip> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-form-item> |
| | | </el-form> |
| | | </div> |
| | | </div> |
| | | <div class="set-form-footer" v-show="state.showKnowledgeForm"> |
| | | <el-button @click="importCompleted" type="primary">导入完成</el-button> |
| | | <el-button @click="handleShowKnowledge">取消</el-button> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup lang="ts"> |
| | | import { ElMessage, ElMessageBox } from 'element-plus'; |
| | | import { reactive, ref } from 'vue'; |
| | | import { useRouter } from 'vue-router'; |
| | | import { delete_docvector_file } from '/@/api/knowledge/docvector'; |
| | | import { computed, nextTick, onMounted, reactive, ref } from 'vue'; |
| | | import { useRoute, useRouter } from 'vue-router'; |
| | | import { add_docvector_file, delete_docvector_file, get_docvector_file_list } from '/@/api/knowledge/docvector'; |
| | | import { get_knowledge_group_list, list_knowledge_file } from '/@/api/knowledge/group'; |
| | | import LeftTreeByMgr from '/@/components/tree/leftTreeByMgr.vue'; |
| | | import { useQueryTable } from '/@/hooks/useQueryTable'; |
| | | import { convertListToTree } from '/@/utils/util'; |
| | | const viewKnowledgeTableRef = ref(); |
| | | const router = useRouter(); |
| | | const route = useRoute(); |
| | | const knowledgeTableData = ref([]); |
| | | const knowledgeFileRef = ref(); |
| | | // 定义变量内容 |
| | | const state = reactive({ |
| | | detailTitle: '', |
| | | knowledgeInfo: { |
| | | knowledge_title: '', |
| | | knowledge_id: '', |
| | | knowledge_import_data: '导入数据', |
| | | } as any, |
| | | knowledgeForm: { |
| | | segmentationMode: 1, |
| | | }, |
| | | segmentationList: [ |
| | | { |
| | | ID: 1, |
| | | Name: '智能切分', |
| | | ImageURL: 'static/images/knowledge/data_type_3.png', |
| | | DemoDesc: '在通用文档上的最优chunk切分方法,经过评测可在多数文档上获得最佳的检索效果', |
| | | }, |
| | | { |
| | | ID: 3, |
| | | Name: '自定义切分', |
| | | ImageURL: 'static/images/knowledge/data_type_4.png', |
| | | DemoDesc: '完全开放的chunk切分配置,按照实际文档情况自由配置,通过调试获得更好的检索效果', |
| | | }, |
| | | ], |
| | | showKnowledgeForm: false, |
| | | knowledgeFile: {}, |
| | | knowledgeBaseData: [], //知识库类目 |
| | | fileData: [], //文件数据源 |
| | | }); |
| | | //返回 |
| | | //返回到知识库列表 |
| | | const handleExitFlow = () => { |
| | | //是否显示返回 |
| | | router.back(); |
| | | }; |
| | | //返回到知识库查看页面 |
| | | const handleShowKnowledge = () => { |
| | | state.showKnowledgeForm = false; |
| | | }; |
| | | //#region ====================== 搜索表格,对表格排序 ====================== |
| | |
| | | }); |
| | | //#endregion |
| | | //#region ====================== 对知识库进行操作 ====================== |
| | | const getKnowledgeFileData = async () => { |
| | | const res = await get_docvector_file_list({ |
| | | knowlg_id: state.knowledgeInfo.knowledge_id, |
| | | }); |
| | | if (res.json_ok) { |
| | | knowledgeTableData.value = res.values; |
| | | } |
| | | }; |
| | | //查看文档 |
| | | const openOperateDemoPage = (row: any) => { |
| | | router.push({ |
| | | name: 'ViewFile', |
| | | query: { |
| | | id: row.id, |
| | | }, |
| | | }); |
| | | }; |
| | | //查看文档块 |
| | | const handleViewKnowledgeFile = (row) => { |
| | | router.push({ |
| | | name: 'ViewSegmentationFile', |
| | | query: { |
| | | id: row.id, |
| | | }, |
| | | }); |
| | | }; |
| | | //删除知识库的文件列表 |
| | | const deleteKnowledgeFileData = (row: any) => { |
| | | ElMessageBox.confirm(`确定删除文档列表:【${row.name}】?`, '提示', { |
| | |
| | | type: 'warning', |
| | | }).then(async () => { |
| | | const res = await delete_docvector_file({ |
| | | knowlg_id: state.knowledgeInfo.knowledge_id, |
| | | file_id: row.id, |
| | | }); |
| | | |
| | |
| | | }); |
| | | }; |
| | | //#endregion |
| | | //#region ====================== 新建知识库的数据 ====================== |
| | | //导入数据 |
| | | const importData = () => { |
| | | router.push({ |
| | | name: 'Knowledge', |
| | | //#region ====================== 导入知识库数据 ====================== |
| | | const importKnowledgeData = () => { |
| | | state.showKnowledgeForm = true; |
| | | getImportantFileTreeData(true); |
| | | }; |
| | | //导入完成 |
| | | const importCompleted = async () => { |
| | | // 上传文件 |
| | | if (multipleSelection.value.length == 0) return ElMessage.warning('请先选择文件'); |
| | | let isCreateIndex = false; |
| | | const file_id = multipleSelection.value.map((v) => v.id).join(','); |
| | | knowledgeTableData.value.forEach((element) => { |
| | | if (element.id == file_id) { |
| | | ElMessage.warning('文件已存在,请选择其他文件'); |
| | | isCreateIndex = true; |
| | | } |
| | | }); |
| | | if (isCreateIndex) return; |
| | | const res = await add_docvector_file({ |
| | | knowlg_id: state.knowledgeInfo.knowledge_id, |
| | | file_id: file_id, |
| | | }); |
| | | if (res.json_ok) { |
| | | ElMessage.success('导入数据成功'); |
| | | state.showKnowledgeForm = false; |
| | | getKnowledgeFileData(); |
| | | //清空表单 |
| | | knowledgeFileRef.value.resetFields(); |
| | | } |
| | | }; |
| | | //#endregion |
| | | //#region ====================== 知识库文件上传 ========== |
| | | const treeLoading = ref(false); |
| | | const currentTreeNode = ref(null); |
| | | const leftTreeRef = ref(null); |
| | | const currentListID = computed(() => currentTreeNode.value?.id); |
| | | //获取文件列表 |
| | | const getImportantFileTreeData = async (selectFirst = false) => { |
| | | treeLoading.value = true; |
| | | const res = await get_knowledge_group_list().finally(() => { |
| | | treeLoading.value = false; |
| | | }); |
| | | if (res?.json_ok) { |
| | | const resData = (res.values || []) as []; |
| | | state.knowledgeBaseData = convertListToTree(resData, { |
| | | ID: 'id', |
| | | Children: 'Children', |
| | | ParentID: 'parent', |
| | | }); |
| | | if (selectFirst) { |
| | | const firstListTreeNode = state.knowledgeBaseData[0]; |
| | | if (firstListTreeNode) { |
| | | handleClickNode(firstListTreeNode); |
| | | } else { |
| | | state.fileData = []; |
| | | multipleSelection.value = []; |
| | | } |
| | | } else { |
| | | currentTreeNode.value && handleClickNode(currentTreeNode.value); |
| | | } |
| | | } |
| | | }; |
| | | const handleClickNode = (data: any) => { |
| | | multipleSelection.value = []; |
| | | nextTick(() => { |
| | | leftTreeRef.value?.treeRef.setCurrentKey(data.id); |
| | | }); |
| | | currentTreeNode.value = data; |
| | | getFileTableData(); |
| | | }; |
| | | //获取文件表格列表 |
| | | const getFileTableData = async () => { |
| | | const res = await list_knowledge_file({ |
| | | group_id: currentListID.value, |
| | | }).finally(() => {}); |
| | | if (res?.json_ok) { |
| | | const resData = (res.values || []) as []; |
| | | state.fileData = resData; |
| | | } else { |
| | | ElMessage.error('获取文档列表失败' + (res?.json_msg ? `,${JSON.stringify(res.json_msg)}` : '')); |
| | | } |
| | | }; |
| | | let multipleSelection = ref([]); |
| | | const multipleTableRef = ref(null); |
| | | //表格单选和多选 |
| | | const handleSelectAll = (selection: any[], pageSelectionData: any[]) => { |
| | | let checked = selection.length ? true : false; // selection为空数组时代表取消全选 |
| | | if (checked) { |
| | | let mIds = multipleSelection.value.map((v) => v.id); |
| | | let filterData = pageSelectionData.filter((v) => { |
| | | // 筛选出非重复项 |
| | | return !mIds.includes(v.id); |
| | | }); |
| | | multipleSelection.value = multipleSelection.value.concat(filterData); |
| | | } else { |
| | | let pIds = pageSelectionData.map((v) => v.id); |
| | | multipleSelection.value = multipleSelection.value.filter((v) => !pIds.includes(v.id)); |
| | | } |
| | | }; |
| | | |
| | | const handleSelectItem = (selection: any[], row) => { |
| | | multipleSelection.value = selection; |
| | | }; |
| | | // 移除已选文件 |
| | | const handleRemoveItem = (item) => { |
| | | multipleSelection.value = multipleSelection.value.filter((v) => v.id !== item.id); |
| | | multipleTableRef.value.toggleRowSelection(item, false); |
| | | }; |
| | | //选择数据文档切分 |
| | | const activeDataProcessType = (id: number) => { |
| | | state.knowledgeForm.segmentationMode = id; |
| | | }; |
| | | //#endregion |
| | | onMounted(() => { |
| | | const { knowledge_id, knowledge_title } = route.query; |
| | | state.knowledgeInfo.knowledge_title = knowledge_title; |
| | | state.knowledgeInfo.knowledge_id = knowledge_id; |
| | | getKnowledgeFileData(); |
| | | }); |
| | | </script> |
| | | <style scoped lang="scss"> |
| | | .set-table-height { |
| | | background: #fff; |
| | | border-radius: 16px; |
| | | height: calc(100% - 80px); |
| | | height: calc(100% - 50px); |
| | | margin-top: 12px; |
| | | overflow-y: auto; |
| | | padding: 20px 24px; |
| | | box-sizing: border-box; |
| | | } |
| | | .set-file-height { |
| | | height: calc(100% - 48px); |
| | | } |
| | | .set_file_left { |
| | | border: 1px solid #e7e8ee; |
| | | border-radius: 12px; |
| | | flex: 1; |
| | | padding: 12px 16px 16px; |
| | | box-sizing: border-box; |
| | | .left_content { |
| | | align-items: flex-start; |
| | | display: flex; |
| | | gap: 12px; |
| | | height: calc(100% - 29px); |
| | | .file_menu { |
| | | flex-shrink: 0; |
| | | height: 100%; |
| | | overflow-y: auto; |
| | | border: 1px solid #e7e8ee; |
| | | border-radius: 6px; |
| | | max-height: 627px; |
| | | padding: 20px; |
| | | width: 280px; |
| | | } |
| | | .file_table { |
| | | flex-shrink: 0; |
| | | height: 100%; |
| | | overflow-y: auto; |
| | | border-radius: 8px 8px 0 0; |
| | | flex: 1; |
| | | } |
| | | } |
| | | } |
| | | .set_file_right { |
| | | border: 1px solid #e7e8ee; |
| | | border-radius: 6px; |
| | | flex-shrink: 0; |
| | | height: 100%; |
| | | padding: 12px 16px 16px; |
| | | width: 417px; |
| | | min-height: 360px; |
| | | } |
| | | .selected_file { |
| | | max-height: calc(100% - 36px); |
| | | overflow-y: auto; |
| | | box-sizing: border-box; |
| | | .set_file_item { |
| | | align-items: center; |
| | | background: linear-gradient(0deg, #f7f8fe, #f7f8fe), #fff; |
| | | border-radius: 8px; |
| | | display: flex; |
| | | font-size: 12px; |
| | | gap: 8px; |
| | | height: 40px; |
| | | justify-content: space-between; |
| | | padding: 0 16px; |
| | | margin-bottom: 9px; |
| | | .set_file_name { |
| | | max-width: calc(100% - 22px); |
| | | } |
| | | .set_file_close { |
| | | color: #878aab; |
| | | cursor: pointer; |
| | | flex-shrink: 0; |
| | | font-size: 14px; |
| | | align-items: center; |
| | | display: inline-flex; |
| | | justify-content: center; |
| | | } |
| | | } |
| | | } |
| | | .activeColor { |
| | | border-color: #0062be; |
| | | background-color: #f6f5ff; |
| | | } |
| | | .set-desc { |
| | | -webkit-line-clamp: 2; |
| | | display: -webkit-box; |
| | | -webkit-box-orient: vertical; |
| | | overflow: hidden; |
| | | word-break: break-word; |
| | | line-height: 1.5714285714285714; |
| | | font-family: PingFangSC; |
| | | box-sizing: border-box; |
| | | } |
| | | .set-form-footer { |
| | | align-items: center; |
| | | background: #fff; |
| | | bottom: 0; |
| | | box-shadow: 4px 0 5px 1px rgba(16, 9, 65, 0.06); |
| | | display: flex; |
| | | flex-shrink: 0; |
| | | gap: 8px; |
| | | height: 64px; |
| | | padding-left: 24px; |
| | | // position: fixed; |
| | | width: 100%; |
| | | left: 220px; |
| | | } |
| | | </style> |
| | | import { delete_knowledge_file } from '/@/api/knowledge/group'; |
| | | <style> |
| | | .set_tooltip_demo { |
| | | max-width: 326px; |
| | | max-height: 150px; |
| | | min-width: 32px; |
| | | min-height: 32px; |
| | | padding: 6px 8px; |
| | | color: #26244c; |
| | | text-align: start; |
| | | text-decoration: none; |
| | | word-wrap: break-word; |
| | | background-color: #fff; |
| | | border-radius: 6px; |
| | | box-shadow: 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 9px 28px 8px rgba(0, 0, 0, 0.05); |
| | | box-sizing: border-box; |
| | | } |
| | | </style> |