feat: 增强知识库分块策略流程
- 增加导入分析预览提交与预览态缓存键 - 支持知识库分块策略配置与分块预览 - 重构知识库导入与确认导入前端流程
This commit is contained in:
@@ -1,189 +1,373 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from 'vue';
|
||||
import { computed, reactive, watch } from 'vue';
|
||||
|
||||
import { $t } from '@easyflow/locales';
|
||||
|
||||
import {
|
||||
ElAlert,
|
||||
ElCard,
|
||||
ElCol,
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElInput,
|
||||
ElOption,
|
||||
ElRow,
|
||||
ElSelect,
|
||||
ElSlider,
|
||||
ElTag,
|
||||
} from 'element-plus';
|
||||
|
||||
const formRef = ref();
|
||||
const form = reactive({
|
||||
fileType: 'doc',
|
||||
splitterName: 'SimpleDocumentSplitter',
|
||||
chunkSize: 512,
|
||||
overlapSize: 128,
|
||||
regex: '',
|
||||
rowsPerChunk: 0,
|
||||
mdSplitterLevel: 1,
|
||||
});
|
||||
const fileTypes = [
|
||||
interface StrategyConfig {
|
||||
chunkSize?: number;
|
||||
mdSplitterLevel?: number;
|
||||
overlapSize?: number;
|
||||
regex?: string;
|
||||
rowsPerChunk?: number;
|
||||
strategyCode?: string;
|
||||
}
|
||||
|
||||
interface StrategyCandidate {
|
||||
score?: number;
|
||||
strategyCode: string;
|
||||
strategyLabel: string;
|
||||
}
|
||||
|
||||
interface AnalysisResult {
|
||||
candidateStrategies?: StrategyCandidate[];
|
||||
confidence?: number;
|
||||
reasons?: string[];
|
||||
recommendedStrategyCode?: string;
|
||||
recommendedStrategyLabel?: string;
|
||||
recommendedStructureType?: string;
|
||||
}
|
||||
|
||||
interface AnalyzeItem {
|
||||
analysis?: AnalysisResult;
|
||||
fileName: string;
|
||||
filePath: string;
|
||||
strategyConfig?: StrategyConfig;
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
analysisItems?: AnalyzeItem[];
|
||||
}>();
|
||||
|
||||
const strategyOptions = [
|
||||
{
|
||||
label: $t('documentCollection.splitterDoc.document'),
|
||||
value: 'doc',
|
||||
label: $t('documentCollection.splitterDoc.autoStrategy'),
|
||||
value: 'AUTO',
|
||||
},
|
||||
{
|
||||
label: $t('documentCollection.splitterDoc.markdownSection'),
|
||||
value: 'MARKDOWN_SECTION',
|
||||
},
|
||||
{
|
||||
label: $t('documentCollection.splitterDoc.outlineSection'),
|
||||
value: 'OUTLINE_SECTION',
|
||||
},
|
||||
{
|
||||
label: $t('documentCollection.splitterDoc.qaPair'),
|
||||
value: 'QA_PAIR',
|
||||
},
|
||||
{
|
||||
label: $t('documentCollection.splitterDoc.paragraphLength'),
|
||||
value: 'PARAGRAPH_LENGTH',
|
||||
},
|
||||
{
|
||||
label: $t('documentCollection.splitterDoc.customRegex'),
|
||||
value: 'CUSTOM_REGEX',
|
||||
},
|
||||
];
|
||||
const splitterNames = [
|
||||
{
|
||||
label: $t('documentCollection.splitterDoc.simpleDocumentSplitter'),
|
||||
value: 'SimpleDocumentSplitter',
|
||||
|
||||
const mdLevels = [1, 2, 3, 4, 5, 6];
|
||||
|
||||
const formMap = reactive<Record<string, StrategyConfig>>({});
|
||||
|
||||
watch(
|
||||
() => props.analysisItems,
|
||||
(items) => {
|
||||
for (const item of items || []) {
|
||||
formMap[item.filePath] = {
|
||||
chunkSize: item.strategyConfig?.chunkSize ?? 512,
|
||||
mdSplitterLevel: item.strategyConfig?.mdSplitterLevel ?? 2,
|
||||
overlapSize: item.strategyConfig?.overlapSize ?? 128,
|
||||
regex: item.strategyConfig?.regex ?? '',
|
||||
rowsPerChunk: item.strategyConfig?.rowsPerChunk ?? 1,
|
||||
strategyCode:
|
||||
item.strategyConfig?.strategyCode ||
|
||||
item.analysis?.recommendedStrategyCode ||
|
||||
'AUTO',
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
label: $t('documentCollection.splitterDoc.simpleTokenizeSplitter'),
|
||||
value: 'SimpleTokenizeSplitter',
|
||||
},
|
||||
{
|
||||
label: $t('documentCollection.splitterDoc.regexDocumentSplitter'),
|
||||
value: 'RegexDocumentSplitter',
|
||||
},
|
||||
{
|
||||
label: $t('documentCollection.splitterDoc.markdownHeaderSplitter'),
|
||||
value: 'MarkdownHeaderSplitter',
|
||||
},
|
||||
];
|
||||
const mdSplitterLevel = [
|
||||
{
|
||||
label: '#',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: '##',
|
||||
value: 2,
|
||||
},
|
||||
{
|
||||
label: '###',
|
||||
value: 3,
|
||||
},
|
||||
{
|
||||
label: '####',
|
||||
value: 4,
|
||||
},
|
||||
{
|
||||
label: '#####',
|
||||
value: 5,
|
||||
},
|
||||
{
|
||||
label: '######',
|
||||
value: 6,
|
||||
},
|
||||
];
|
||||
const rules = {
|
||||
name: [
|
||||
{ required: true, message: 'Please input Activity name', trigger: 'blur' },
|
||||
],
|
||||
region: [
|
||||
{
|
||||
required: true,
|
||||
message: 'Please select Activity zone',
|
||||
trigger: 'change',
|
||||
},
|
||||
],
|
||||
};
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
const items = computed(() => props.analysisItems ?? []);
|
||||
|
||||
defineExpose({
|
||||
getSplitterFormValues() {
|
||||
return form;
|
||||
getPreviewRequestItems() {
|
||||
return items.value.map((item) => ({
|
||||
fileName: item.fileName,
|
||||
filePath: item.filePath,
|
||||
strategyConfig: {
|
||||
...formMap[item.filePath],
|
||||
},
|
||||
}));
|
||||
},
|
||||
});
|
||||
|
||||
function showLengthSettings(strategyCode?: string) {
|
||||
return [
|
||||
'AUTO',
|
||||
'MARKDOWN_SECTION',
|
||||
'OUTLINE_SECTION',
|
||||
'PARAGRAPH_LENGTH',
|
||||
].includes(strategyCode || '');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="splitter-doc-container">
|
||||
<ElForm
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-width="auto"
|
||||
class="custom-form"
|
||||
>
|
||||
<ElFormItem
|
||||
:label="$t('documentCollection.splitterDoc.fileType')"
|
||||
prop="fileType"
|
||||
<div class="strategy-container">
|
||||
<ElAlert
|
||||
:title="$t('documentCollection.importDoc.analysisTip')"
|
||||
type="info"
|
||||
:closable="false"
|
||||
class="strategy-tip"
|
||||
/>
|
||||
|
||||
<div class="strategy-list">
|
||||
<ElCard
|
||||
v-for="item in items"
|
||||
:key="item.filePath"
|
||||
class="strategy-card"
|
||||
shadow="never"
|
||||
>
|
||||
<ElSelect v-model="form.fileType">
|
||||
<ElOption
|
||||
v-for="item in fileTypes"
|
||||
:key="item.value"
|
||||
v-bind="item"
|
||||
:label="item.label"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem
|
||||
:label="$t('documentCollection.splitterDoc.splitterName')"
|
||||
prop="splitterName"
|
||||
>
|
||||
<ElSelect v-model="form.splitterName">
|
||||
<ElOption
|
||||
v-for="item in splitterNames"
|
||||
:key="item.value"
|
||||
v-bind="item"
|
||||
:label="item.label"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem
|
||||
:label="$t('documentCollection.splitterDoc.chunkSize')"
|
||||
v-if="
|
||||
form.splitterName === 'SimpleDocumentSplitter' ||
|
||||
form.splitterName === 'SimpleTokenizeSplitter'
|
||||
"
|
||||
prop="chunkSize"
|
||||
>
|
||||
<ElSlider v-model="form.chunkSize" show-input :max="2048" />
|
||||
</ElFormItem>
|
||||
<ElFormItem
|
||||
:label="$t('documentCollection.splitterDoc.overlapSize')"
|
||||
v-if="
|
||||
form.splitterName === 'SimpleDocumentSplitter' ||
|
||||
form.splitterName === 'SimpleTokenizeSplitter'
|
||||
"
|
||||
prop="overlapSize"
|
||||
>
|
||||
<ElSlider v-model="form.overlapSize" show-input :max="2048" />
|
||||
</ElFormItem>
|
||||
<ElFormItem
|
||||
:label="$t('documentCollection.splitterDoc.regex')"
|
||||
prop="regex"
|
||||
v-if="form.splitterName === 'RegexDocumentSplitter'"
|
||||
>
|
||||
<ElInput v-model="form.regex" />
|
||||
</ElFormItem>
|
||||
<ElFormItem
|
||||
v-if="form.splitterName === 'MarkdownHeaderSplitter'"
|
||||
:label="$t('documentCollection.splitterDoc.mdSplitterLevel')"
|
||||
prop="splitterName"
|
||||
>
|
||||
<ElSelect v-model="form.mdSplitterLevel">
|
||||
<ElOption
|
||||
v-for="item in mdSplitterLevel"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
<div class="strategy-card__header">
|
||||
<div>
|
||||
<div class="strategy-card__title">{{ item.fileName }}</div>
|
||||
<div class="strategy-card__meta">
|
||||
{{ item.analysis?.recommendedStructureType || '-' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="strategy-card__badges">
|
||||
<ElTag type="success" effect="plain">
|
||||
{{
|
||||
item.analysis?.recommendedStrategyLabel ||
|
||||
$t('documentCollection.splitterDoc.autoStrategy')
|
||||
}}
|
||||
</ElTag>
|
||||
<ElTag effect="plain">
|
||||
{{ $t('documentCollection.importDoc.confidence') }}
|
||||
{{ item.analysis?.confidence ?? 0 }}
|
||||
</ElTag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ElRow :gutter="16" class="strategy-card__content">
|
||||
<ElCol :span="12">
|
||||
<div class="strategy-block">
|
||||
<div class="strategy-block__label">
|
||||
{{ $t('documentCollection.importDoc.recommendReason') }}
|
||||
</div>
|
||||
<ul class="strategy-reason-list">
|
||||
<li
|
||||
v-for="reason in item.analysis?.reasons || []"
|
||||
:key="reason"
|
||||
class="strategy-reason-list__item"
|
||||
>
|
||||
{{ reason }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="strategy-block">
|
||||
<div class="strategy-block__label">
|
||||
{{ $t('documentCollection.importDoc.candidateStrategies') }}
|
||||
</div>
|
||||
<div class="strategy-candidate-list">
|
||||
<ElTag
|
||||
v-for="candidate in item.analysis?.candidateStrategies || []"
|
||||
:key="candidate.strategyCode"
|
||||
effect="plain"
|
||||
>
|
||||
{{ candidate.strategyLabel }} / {{ candidate.score }}
|
||||
</ElTag>
|
||||
</div>
|
||||
</div>
|
||||
</ElCol>
|
||||
|
||||
<ElCol :span="12">
|
||||
<ElForm
|
||||
:model="formMap[item.filePath]"
|
||||
label-position="top"
|
||||
class="strategy-form"
|
||||
>
|
||||
<ElFormItem
|
||||
:label="$t('documentCollection.importDoc.strategySelection')"
|
||||
>
|
||||
<ElSelect
|
||||
v-model="formMap[item.filePath].strategyCode"
|
||||
class="w-full"
|
||||
>
|
||||
<ElOption
|
||||
v-for="option in strategyOptions"
|
||||
:key="option.value"
|
||||
:label="option.label"
|
||||
:value="option.value"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem
|
||||
v-if="showLengthSettings(formMap[item.filePath].strategyCode)"
|
||||
:label="$t('documentCollection.splitterDoc.chunkSize')"
|
||||
>
|
||||
<ElSlider
|
||||
v-model="formMap[item.filePath].chunkSize"
|
||||
:max="2048"
|
||||
:min="128"
|
||||
show-input
|
||||
/>
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem
|
||||
v-if="
|
||||
formMap[item.filePath].strategyCode === 'PARAGRAPH_LENGTH' ||
|
||||
formMap[item.filePath].strategyCode === 'AUTO'
|
||||
"
|
||||
:label="$t('documentCollection.splitterDoc.overlapSize')"
|
||||
>
|
||||
<ElSlider
|
||||
v-model="formMap[item.filePath].overlapSize"
|
||||
:max="512"
|
||||
:min="0"
|
||||
show-input
|
||||
/>
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem
|
||||
v-if="
|
||||
formMap[item.filePath].strategyCode === 'MARKDOWN_SECTION'
|
||||
"
|
||||
:label="$t('documentCollection.splitterDoc.mdSplitterLevel')"
|
||||
>
|
||||
<ElSelect
|
||||
v-model="formMap[item.filePath].mdSplitterLevel"
|
||||
class="w-full"
|
||||
>
|
||||
<ElOption
|
||||
v-for="level in mdLevels"
|
||||
:key="level"
|
||||
:label="'#'.repeat(level)"
|
||||
:value="level"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem
|
||||
v-if="formMap[item.filePath].strategyCode === 'CUSTOM_REGEX'"
|
||||
:label="$t('documentCollection.splitterDoc.regex')"
|
||||
>
|
||||
<ElInput v-model="formMap[item.filePath].regex" />
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
</ElCard>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.splitter-doc-container {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
.strategy-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
.custom-form {
|
||||
width: 500px;
|
||||
|
||||
.strategy-tip {
|
||||
border-radius: 12px;
|
||||
}
|
||||
.custom-form :deep(.el-input),
|
||||
.custom-form :deep(.ElSelect) {
|
||||
width: 100%;
|
||||
|
||||
.strategy-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.strategy-card {
|
||||
border: 1px solid var(--el-border-color-light);
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.strategy-card__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid var(--el-border-color-lighter);
|
||||
}
|
||||
|
||||
.strategy-card__title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
.strategy-card__meta {
|
||||
margin-top: 4px;
|
||||
font-size: 13px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
.strategy-card__badges {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.strategy-card__content {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.strategy-block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.strategy-block + .strategy-block {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.strategy-block__label {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
.strategy-reason-list {
|
||||
margin: 0;
|
||||
padding-left: 18px;
|
||||
color: var(--el-text-color-regular);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.strategy-reason-list__item {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.strategy-candidate-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.strategy-form {
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
background: var(--el-fill-color-light);
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user