feat: 重构知识库文档导入任务化流程

- 新增上传建单、异步解析、分块处理与异步向量化闭环

- 收口分享页权限、完成态检索过滤与 SSE 局部状态刷新
This commit is contained in:
2026-04-15 19:27:22 +08:00
parent a41b50959e
commit 2689adfa40
56 changed files with 6376 additions and 1060 deletions

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue';
import { computed, nextTick, onMounted, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useAccess } from '@easyflow/access';
@@ -7,7 +7,7 @@ import { $t } from '@easyflow/locales';
import { useUserStore } from '@easyflow/stores';
import { ArrowLeft, Plus } from '@element-plus/icons-vue';
import { ElIcon, ElImage } from 'element-plus';
import { ElButton, ElIcon, ElImage } from 'element-plus';
import { api } from '#/api/request';
import bookIcon from '#/assets/ai/knowledge/book.svg';
@@ -18,8 +18,8 @@ import DocumentTable from '#/views/ai/documentCollection/DocumentTable.vue';
import FaqTable from '#/views/ai/documentCollection/FaqTable.vue';
import ImportKnowledgeDocFile from '#/views/ai/documentCollection/ImportKnowledgeDocFile.vue';
import KnowledgeSearch from '#/views/ai/documentCollection/KnowledgeSearch.vue';
import KnowledgeSearchConfig from '#/views/ai/documentCollection/KnowledgeSearchConfig.vue';
import KnowledgeShareManagement from '#/views/ai/documentCollection/KnowledgeShareManagement.vue';
import SegmenterDoc from '#/views/ai/documentCollection/SegmenterDoc.vue';
const route = useRoute();
const router = useRouter();
@@ -147,8 +147,10 @@ const headerButtons = [
data: { action: 'importFile' },
},
];
const isImportFileVisible = ref(false);
const panelMode = ref<'chunk' | 'list' | 'process'>('list');
const documentTableRef = ref();
const importDocModalRef = ref<InstanceType<typeof ImportKnowledgeDocFile>>();
const documentTitle = ref('');
const handleSearch = (searchParams: string) => {
documentTableRef.value.search(searchParams);
};
@@ -160,30 +162,39 @@ const handleButtonClick = (event: any) => {
break;
}
case 'importFile': {
isImportFileVisible.value = true;
importDocModalRef.value?.openDialog?.();
break;
}
}
};
const handleCategoryClick = (menuKey: string) => {
selectedCategory.value = menuKey;
viewDocVisible.value = false;
panelMode.value = 'list';
};
const viewDocVisible = ref(false);
const documentId = ref('');
// 子组件传递事件,显示查看文档详情
const viewDoc = (docId: string) => {
viewDocVisible.value = true;
panelMode.value = 'chunk';
documentId.value = docId;
};
const backDoc = () => {
isImportFileVisible.value = false;
const continueProcess = (doc: any) => {
panelMode.value = 'process';
documentId.value = String(doc?.id || '');
documentTitle.value = doc?.title || '';
};
const backDoc = async () => {
if (panelMode.value !== 'list') {
panelMode.value = 'list';
}
documentTitle.value = '';
await nextTick();
documentTableRef.value?.reload?.();
};
</script>
<template>
<div class="document-container">
<div v-if="!isImportFileVisible" class="doc-header-container">
<div class="doc-header-container">
<div class="doc-knowledge-container">
<div @click="back()" style="cursor: pointer">
<ElIcon><ArrowLeft /></ElIcon>
@@ -215,9 +226,10 @@ const backDoc = () => {
<div class="doc-content">
<div
class="doc-table-content menu-container border border-[var(--el-border-color)]"
:class="{ 'doc-table-content--process': panelMode === 'process' }"
>
<div v-if="selectedCategory === 'documentList'" class="doc-table">
<div class="doc-header" v-if="!viewDocVisible">
<div class="doc-header" v-if="panelMode === 'list'">
<HeaderSearch
v-if="!isFaqCollection"
:buttons="canManageCurrentKnowledge ? headerButtons : []"
@@ -225,20 +237,38 @@ const backDoc = () => {
@button-click="handleButtonClick"
/>
</div>
<div v-if="panelMode === 'chunk'" class="doc-sub-back">
<ElButton @click="backDoc">
{{ $t('button.back') }}
</ElButton>
</div>
<DocumentTable
v-if="panelMode === 'list'"
ref="documentTableRef"
:knowledge-id="knowledgeId"
:manageable="canManageCurrentKnowledge"
:permissions="{
canCreateContent: canManageCurrentKnowledge,
canDeleteContent: canManageCurrentKnowledge,
canDownloadContent: true,
}"
@continue-process="continueProcess"
@view-doc="viewDoc"
v-if="!viewDocVisible"
/>
<ChunkDocumentTable
v-else
v-else-if="panelMode === 'chunk'"
:document-id="documentId"
:manageable="canManageCurrentKnowledge"
:default-summary-prompt="knowledgeInfo.summaryPrompt"
/>
<SegmenterDoc
v-else-if="panelMode === 'process'"
:knowledge-id="String(knowledgeId)"
:document-id="documentId"
:document-title="documentTitle"
@cancel="backDoc"
@started="backDoc"
/>
</div>
<div v-if="selectedCategory === 'faqList'" class="doc-table">
<FaqTable
@@ -251,11 +281,7 @@ const backDoc = () => {
v-if="selectedCategory === 'knowledgeSearch'"
class="doc-search-container"
>
<KnowledgeSearchConfig
:document-collection-id="knowledgeId"
:manageable="canManageCurrentKnowledge"
/>
<KnowledgeSearch :knowledge-id="knowledgeId" />
<KnowledgeSearch :knowledge-id="knowledgeId" :show-config="true" />
</div>
<!--配置-->
<div v-if="selectedCategory === 'config'">
@@ -275,9 +301,11 @@ const backDoc = () => {
</div>
</div>
</div>
<div v-else class="doc-imp-container">
<ImportKnowledgeDocFile @import-back="backDoc" />
</div>
<ImportKnowledgeDocFile
ref="importDocModalRef"
:knowledge-id-prop="String(knowledgeId)"
@imported="backDoc"
/>
</div>
</template>
@@ -306,6 +334,12 @@ const backDoc = () => {
border-radius: 8px;
}
.doc-table-content--process {
padding: 18px 20px 0;
border-color: rgb(15 23 42 / 6%);
border-radius: 18px;
}
.doc-header {
width: 100%;
padding-bottom: 21px;
@@ -378,10 +412,8 @@ const backDoc = () => {
background-color: var(--el-bg-color);
}
.doc-imp-container {
box-sizing: border-box;
flex: 1;
width: 100%;
.doc-sub-back {
padding: 0 6px 16px;
}
.doc-header-container {
@@ -428,6 +460,8 @@ const backDoc = () => {
display: flex;
width: 100%;
height: 100%;
min-width: 0;
min-height: 0;
}
.menu-container {