2026-07-13 15:28:48 +08:00
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
|
import type { ModelItem } from '@/types'
|
|
|
|
|
|
import type {
|
|
|
|
|
|
GenerationControlOptions,
|
|
|
|
|
|
PreprocessOption,
|
|
|
|
|
|
StructuredProcessOptions,
|
|
|
|
|
|
} from './types'
|
|
|
|
|
|
import DatasetSplitEditor from './DatasetSplitEditor.vue'
|
|
|
|
|
|
import GenerationOptionsPanel from './GenerationOptionsPanel.vue'
|
|
|
|
|
|
|
|
|
|
|
|
const props = defineProps<{
|
|
|
|
|
|
options: StructuredProcessOptions
|
|
|
|
|
|
generationModels: ModelItem[]
|
|
|
|
|
|
validationAttempted: boolean
|
|
|
|
|
|
generationValidationMessage: string
|
|
|
|
|
|
}>()
|
|
|
|
|
|
|
|
|
|
|
|
const emit = defineEmits<{
|
|
|
|
|
|
'update:options': [value: StructuredProcessOptions]
|
|
|
|
|
|
}>()
|
|
|
|
|
|
|
|
|
|
|
|
const PREPROCESS_OPTIONS: Array<{
|
|
|
|
|
|
value: PreprocessOption
|
|
|
|
|
|
label: string
|
|
|
|
|
|
description: string
|
|
|
|
|
|
}> = [
|
|
|
|
|
|
{ value: 'clean_invalid', label: '清理无效数据', description: '处理空行、空列和残缺行' },
|
|
|
|
|
|
{ value: 'detect_structure', label: '识别表格结构', description: '识别表头、多级表头和合并单元格' },
|
|
|
|
|
|
{ value: 'deduplicate', label: '重复数据去重', description: '删除完全重复或关键字段重复的数据' },
|
|
|
|
|
|
{ value: 'normalize_format', label: '数据格式标准化', description: '统一日期、数字、单位和枚举值格式' },
|
|
|
|
|
|
{ value: 'filter_anomaly', label: '异常数据过滤', description: '过滤乱码、无效内容和异常记录' },
|
|
|
|
|
|
{ value: 'desensitize', label: '敏感信息脱敏', description: '处理姓名、手机号、邮箱等敏感信息' },
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
function updateField<K extends keyof StructuredProcessOptions>(
|
|
|
|
|
|
field: K,
|
|
|
|
|
|
value: StructuredProcessOptions[K],
|
|
|
|
|
|
) {
|
|
|
|
|
|
emit('update:options', { ...props.options, [field]: value })
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function updateGenerationOptions(value: GenerationControlOptions) {
|
|
|
|
|
|
emit('update:options', { ...props.options, ...value })
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function updatePreprocessOptions(value: Array<string | number | boolean>) {
|
|
|
|
|
|
const allowedValues = new Set(PREPROCESS_OPTIONS.map((option) => option.value))
|
|
|
|
|
|
const preprocessOptions = value.filter(
|
|
|
|
|
|
(option): option is PreprocessOption => typeof option === 'string' && allowedValues.has(option as PreprocessOption),
|
|
|
|
|
|
)
|
|
|
|
|
|
updateField('preprocessOptions', preprocessOptions)
|
|
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
|
<div class="form-section structured-options-section">
|
|
|
|
|
|
<div class="section-title-row">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<h3>预处理选项</h3>
|
|
|
|
|
|
<p>选择在生成问答对之前需要执行的数据处理方式</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<el-checkbox-group
|
|
|
|
|
|
:model-value="options.preprocessOptions"
|
|
|
|
|
|
class="preprocess-option-grid"
|
|
|
|
|
|
@update:model-value="updatePreprocessOptions"
|
|
|
|
|
|
>
|
|
|
|
|
|
<el-checkbox
|
|
|
|
|
|
v-for="option in PREPROCESS_OPTIONS"
|
|
|
|
|
|
:key="option.value"
|
|
|
|
|
|
:value="option.value"
|
|
|
|
|
|
class="preprocess-option"
|
|
|
|
|
|
>
|
|
|
|
|
|
<span class="preprocess-option-copy">
|
|
|
|
|
|
<strong>{{ option.label }}</strong>
|
|
|
|
|
|
<small>{{ option.description }}</small>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</el-checkbox>
|
|
|
|
|
|
</el-checkbox-group>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="form-section generation-options-section">
|
|
|
|
|
|
<div class="section-title-row">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<h3>生成选项</h3>
|
|
|
|
|
|
<p>配置每条结构化记录生成问答对的方式和数量</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="generation-option-list">
|
|
|
|
|
|
<GenerationOptionsPanel
|
|
|
|
|
|
:options="options"
|
|
|
|
|
|
:models="generationModels"
|
|
|
|
|
|
section="quality"
|
|
|
|
|
|
:validation-message="validationAttempted ? generationValidationMessage : ''"
|
|
|
|
|
|
@update:options="updateGenerationOptions"
|
|
|
|
|
|
/>
|
2026-07-13 16:39:16 +08:00
|
|
|
|
|
2026-07-13 15:28:48 +08:00
|
|
|
|
<div class="generation-option-row">
|
|
|
|
|
|
<div class="generation-option-copy">
|
|
|
|
|
|
<strong>每行生成数量</strong>
|
|
|
|
|
|
<small>每行结构化数据生成的问答对数量</small>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<el-input-number
|
|
|
|
|
|
:model-value="options.qaPairsPerRow"
|
|
|
|
|
|
:min="1"
|
|
|
|
|
|
:max="5"
|
|
|
|
|
|
:step="1"
|
|
|
|
|
|
controls-position="right"
|
|
|
|
|
|
@update:model-value="updateField('qaPairsPerRow', Number($event) || 1)"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<DatasetSplitEditor
|
|
|
|
|
|
:model-value="options.datasetSplit"
|
|
|
|
|
|
@update:model-value="updateField('datasetSplit', $event)"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="form-section model-options-section">
|
|
|
|
|
|
<div class="section-title-row">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<h3>大模型</h3>
|
|
|
|
|
|
<p>选择数据生成模型,并设置模型输出内容的要求</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="generation-option-list">
|
|
|
|
|
|
<GenerationOptionsPanel
|
|
|
|
|
|
:options="options"
|
|
|
|
|
|
:models="generationModels"
|
|
|
|
|
|
section="model"
|
|
|
|
|
|
:validation-message="validationAttempted ? generationValidationMessage : ''"
|
|
|
|
|
|
@update:options="updateGenerationOptions"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
|
|
|
.form-section {
|
|
|
|
|
|
padding: 0 0 26px;
|
|
|
|
|
|
margin-bottom: 26px;
|
|
|
|
|
|
border-bottom: 1px solid #edf0f5;
|
|
|
|
|
|
|
|
|
|
|
|
&:last-child {
|
|
|
|
|
|
margin-bottom: 0;
|
|
|
|
|
|
border-bottom: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
h3 {
|
|
|
|
|
|
margin: 0 0 16px;
|
|
|
|
|
|
color: #2f3747;
|
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
|
font-weight: 650;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.section-title-row {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
gap: 16px;
|
|
|
|
|
|
|
|
|
|
|
|
h3 {
|
|
|
|
|
|
margin-bottom: 5px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
p {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
color: #8a93a3;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.preprocess-option-grid {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
margin-top: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.preprocess-option {
|
2026-07-13 16:16:50 +08:00
|
|
|
|
display: flex;
|
2026-07-13 16:39:16 +08:00
|
|
|
|
gap: 10px;
|
2026-07-13 15:28:48 +08:00
|
|
|
|
width: 100%;
|
|
|
|
|
|
min-height: 64px;
|
|
|
|
|
|
margin-right: 0;
|
|
|
|
|
|
padding: 12px 14px;
|
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
|
border: 1px solid #e2e5ec;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
transition: border-color 0.18s ease, background-color 0.18s ease;
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
border-color: #b7b2f7;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.is-checked {
|
|
|
|
|
|
background: #fafaff;
|
|
|
|
|
|
border-color: #8b82f4;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.el-checkbox__input) {
|
|
|
|
|
|
margin-top: 3px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.el-checkbox__label) {
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
padding-left: 10px;
|
|
|
|
|
|
white-space: normal;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.preprocess-option-copy,
|
|
|
|
|
|
.generation-option-copy {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 4px;
|
|
|
|
|
|
|
|
|
|
|
|
strong {
|
|
|
|
|
|
color: #344054;
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
small {
|
|
|
|
|
|
color: #8a93a3;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
line-height: 1.5;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.generation-option-list {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
margin-top: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.generation-option-row {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
min-height: 64px;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
gap: 24px;
|
|
|
|
|
|
padding: 12px 14px;
|
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
border: 1px solid #e2e5ec;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@media (max-width: 900px) {
|
|
|
|
|
|
.preprocess-option-grid {
|
|
|
|
|
|
grid-template-columns: minmax(0, 1fr);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.generation-option-row {
|
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|