- 修正知识库与 Bot 设置页相关组件的类型定义和空值处理 - 补齐工作流与公开聊天页的前端类型约束和动态导入类型 - 收敛本次改动文件的局部格式与样式规范,确保 pnpm check:type 通过
394 lines
9.7 KiB
Vue
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>
|