提取 useDataProcessDraft、useDataProcessGeneration 与 dataProcessCreateState 管理向导状态,新增 StructuredOptionsPanel、UnstructuredOptionsPanel、DatasetSplitEditor 子面板组件,样式抽离为独立 scss,DataProcessCreateView 与 TaskSetupStep 大幅瘦身,回归脚本适配。
271 lines
6.9 KiB
Vue
271 lines
6.9 KiB
Vue
<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"
|
||
/>
|
||
<div class="generation-option-row">
|
||
<div class="generation-option-copy">
|
||
<strong>语义丰富表达</strong>
|
||
<small>使用大模型将问答表述得更自然、柔和</small>
|
||
</div>
|
||
<el-switch
|
||
:model-value="options.semanticEnrichment"
|
||
inline-prompt
|
||
active-text="开"
|
||
inactive-text="关"
|
||
@update:model-value="updateField('semanticEnrichment', Boolean($event))"
|
||
/>
|
||
</div>
|
||
<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 {
|
||
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>
|