Files
EasyFlow/easyflow-ui-admin/app/src/views/ai/documentCollection/SegmenterDoc.vue
陈子默 bb72e19c84 fix: 修复管理端前端类型校验问题
- 修正知识库与 Bot 设置页相关组件的类型定义和空值处理

- 补齐工作流与公开聊天页的前端类型约束和动态导入类型

- 收敛本次改动文件的局部格式与样式规范,确保 pnpm check:type 通过
2026-04-05 20:36:25 +08:00

394 lines
9.7 KiB
Vue

<script setup lang="ts">
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';
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.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 mdLevels = [1, 2, 3, 4, 5, 6];
const formMap = reactive<Record<string, StrategyConfig>>({});
function createDefaultStrategyConfig(item?: AnalyzeItem): StrategyConfig {
return {
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',
};
}
function getStrategyForm(filePath: string, item?: AnalyzeItem): StrategyConfig {
if (!formMap[filePath]) {
formMap[filePath] = createDefaultStrategyConfig(item);
}
return formMap[filePath]!;
}
watch(
() => props.analysisItems,
(items) => {
for (const item of items || []) {
formMap[item.filePath] = createDefaultStrategyConfig(item);
}
},
{ immediate: true },
);
const items = computed(() => props.analysisItems ?? []);
defineExpose({
getPreviewRequestItems() {
return items.value.map((item) => ({
fileName: item.fileName,
filePath: item.filePath,
strategyConfig: {
...getStrategyForm(item.filePath, item),
},
}));
},
});
function showLengthSettings(strategyCode?: string) {
return [
'AUTO',
'MARKDOWN_SECTION',
'OUTLINE_SECTION',
'PARAGRAPH_LENGTH',
].includes(strategyCode || '');
}
</script>
<template>
<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"
>
<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="getStrategyForm(item.filePath, item)"
label-position="top"
class="strategy-form"
>
<ElFormItem
:label="$t('documentCollection.importDoc.strategySelection')"
>
<ElSelect
v-model="getStrategyForm(item.filePath, item).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(
getStrategyForm(item.filePath, item).strategyCode,
)
"
:label="$t('documentCollection.splitterDoc.chunkSize')"
>
<ElSlider
v-model="getStrategyForm(item.filePath, item).chunkSize"
:max="2048"
:min="128"
show-input
/>
</ElFormItem>
<ElFormItem
v-if="
getStrategyForm(item.filePath, item).strategyCode ===
'PARAGRAPH_LENGTH' ||
getStrategyForm(item.filePath, item).strategyCode === 'AUTO'
"
:label="$t('documentCollection.splitterDoc.overlapSize')"
>
<ElSlider
v-model="getStrategyForm(item.filePath, item).overlapSize"
:max="512"
:min="0"
show-input
/>
</ElFormItem>
<ElFormItem
v-if="
getStrategyForm(item.filePath, item).strategyCode ===
'MARKDOWN_SECTION'
"
:label="$t('documentCollection.splitterDoc.mdSplitterLevel')"
>
<ElSelect
v-model="getStrategyForm(item.filePath, item).mdSplitterLevel"
class="w-full"
>
<ElOption
v-for="level in mdLevels"
:key="level"
:label="'#'.repeat(level)"
:value="level"
/>
</ElSelect>
</ElFormItem>
<ElFormItem
v-if="
getStrategyForm(item.filePath, item).strategyCode ===
'CUSTOM_REGEX'
"
:label="$t('documentCollection.splitterDoc.regex')"
>
<ElInput v-model="getStrategyForm(item.filePath, item).regex" />
</ElFormItem>
</ElForm>
</ElCol>
</ElRow>
</ElCard>
</div>
</div>
</template>
<style scoped>
.strategy-container {
display: flex;
flex-direction: column;
gap: 16px;
}
.strategy-tip {
border-radius: 12px;
}
.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>