refactor: 数据处理向导拆分源数据上传步骤
将任务设置中的文件上传与外来数据源拉取抽离为独立 SourceUploadStep 组件,TaskSetupStep 聚焦任务信息与处理配置,CreateView 同步接入新步骤与草稿同步,回归脚本补充上传步骤断言。
This commit is contained in:
@@ -4,6 +4,7 @@ import { onBeforeRouteLeave, useRouter } from 'vue-router'
|
||||
import { ElMessage, type UploadFile } from 'element-plus'
|
||||
import AppConfirmDialog from '@/components/AppConfirmDialog.vue'
|
||||
import TaskSetupStep from './create/TaskSetupStep.vue'
|
||||
import SourceUploadStep from './create/SourceUploadStep.vue'
|
||||
import PreviewCompareStep from './create/PreviewCompareStep.vue'
|
||||
import GenerationStep from './create/GenerationStep.vue'
|
||||
import ResultEditorStep from './create/ResultEditorStep.vue'
|
||||
@@ -23,15 +24,17 @@ const router = useRouter()
|
||||
const taskSetupRef = ref<InstanceType<typeof TaskSetupStep>>()
|
||||
const confirmDialogRef = ref<InstanceType<typeof AppConfirmDialog>>()
|
||||
const DRAFT_STORAGE_KEY = 'yg-data-process-create-draft'
|
||||
const DRAFT_SCHEMA_VERSION = 2
|
||||
const DRAFT_SCHEMA_VERSION = 4
|
||||
const PREVIEW_MODEL_VERSION = 'document-chunk-v2'
|
||||
|
||||
const WIZARD_STEPS = [
|
||||
{ id: 'create', title: '创建任务', desc: '填写任务信息与上传源数据' },
|
||||
{ id: 'create', title: '创建任务', desc: '填写任务信息与处理配置' },
|
||||
{ id: 'upload', title: '上传文件', desc: '上传或接入待处理的源数据' },
|
||||
{ id: 'preview', title: '数据预览', desc: '核对源文件与预览内容' },
|
||||
{ id: 'generate', title: '开始生成', desc: '确认摘要并启动处理' },
|
||||
{ id: 'results', title: '结果编辑与保存', desc: '检查、修改并保存结果' },
|
||||
] as const satisfies ReadonlyArray<{ id: StepId; title: string; desc: string }>
|
||||
const LEGACY_V3_STEP_IDS: ReadonlyArray<StepId> = ['create', 'preview', 'generate', 'results']
|
||||
|
||||
const currentStep = ref(0)
|
||||
const currentStepId = computed<StepId>(() => WIZARD_STEPS[currentStep.value]?.id ?? 'create')
|
||||
@@ -62,9 +65,6 @@ const unstructuredOptions = ref<UnstructuredProcessOptions>({
|
||||
preserveLists: true,
|
||||
semanticEnrichment: false,
|
||||
qaPairsPerChunk: 1,
|
||||
contextScope: 'adjacent',
|
||||
generationTypes: ['factual', 'concept', 'comprehensive'],
|
||||
skipUnanswerable: true,
|
||||
datasetSplit: { train: 80, validation: 10, test: 10 },
|
||||
})
|
||||
interface UploadedDataFile {
|
||||
@@ -125,7 +125,8 @@ const previewFiles = computed(() => uploadedFiles.value.map((file) => {
|
||||
}
|
||||
}))
|
||||
const primaryActionLabel = computed(() => {
|
||||
if (currentStepId.value === 'create') return '继续:数据预览'
|
||||
if (currentStepId.value === 'create') return '继续:上传文件'
|
||||
if (currentStepId.value === 'upload') return '继续:数据预览'
|
||||
if (currentStepId.value === 'preview') return '确认预览并继续'
|
||||
if (currentStepId.value === 'results') return '保存任务'
|
||||
if (generation.status === 'running') return '正在生成'
|
||||
@@ -144,10 +145,20 @@ const previousStepLabel = computed(() => currentStep.value > 0
|
||||
? WIZARD_STEPS[currentStep.value - 1].title
|
||||
: '')
|
||||
|
||||
function isStepId(value: unknown): value is StepId {
|
||||
return typeof value === 'string' && WIZARD_STEPS.some((step) => step.id === value)
|
||||
}
|
||||
|
||||
function goToStep(stepId: StepId) {
|
||||
const nextStepIndex = WIZARD_STEPS.findIndex((step) => step.id === stepId)
|
||||
if (nextStepIndex >= 0) currentStep.value = nextStepIndex
|
||||
}
|
||||
|
||||
function draftSnapshot() {
|
||||
return {
|
||||
schemaVersion: DRAFT_SCHEMA_VERSION,
|
||||
currentStep: currentStep.value,
|
||||
currentStepId: currentStepId.value,
|
||||
task: { ...task },
|
||||
processType: processType.value,
|
||||
structuredOptions: {
|
||||
@@ -158,7 +169,6 @@ function draftSnapshot() {
|
||||
unstructuredOptions: {
|
||||
...unstructuredOptions.value,
|
||||
preprocessOptions: [...unstructuredOptions.value.preprocessOptions],
|
||||
generationTypes: [...unstructuredOptions.value.generationTypes],
|
||||
datasetSplit: { ...unstructuredOptions.value.datasetSplit },
|
||||
},
|
||||
uploadedFiles: uploadedFiles.value,
|
||||
@@ -216,17 +226,11 @@ function generationAffectingOptions() {
|
||||
const {
|
||||
semanticEnrichment,
|
||||
qaPairsPerChunk,
|
||||
contextScope,
|
||||
generationTypes,
|
||||
skipUnanswerable,
|
||||
datasetSplit,
|
||||
} = unstructuredOptions.value
|
||||
return {
|
||||
semanticEnrichment,
|
||||
qaPairsPerChunk,
|
||||
contextScope,
|
||||
generationTypes,
|
||||
skipUnanswerable,
|
||||
datasetSplit,
|
||||
}
|
||||
}
|
||||
@@ -264,12 +268,21 @@ function restoreDraft() {
|
||||
if (!raw) return
|
||||
const snapshot = JSON.parse(raw) as DraftSnapshot
|
||||
if (!snapshot?.uploadedFiles && (!snapshot?.fileName || !snapshot?.sourceText)) return
|
||||
const requiresPreviewMigration = snapshot.schemaVersion !== DRAFT_SCHEMA_VERSION
|
||||
const snapshotSchemaVersion = Number(snapshot.schemaVersion) || 0
|
||||
const requiresPreviewMigration = snapshotSchemaVersion < 3
|
||||
const legacyStepId = snapshotSchemaVersion === 3
|
||||
? LEGACY_V3_STEP_IDS[Math.min(Math.max(Number(snapshot.currentStep) || 0, 0), LEGACY_V3_STEP_IDS.length - 1)]
|
||||
: undefined
|
||||
const indexedStepId = WIZARD_STEPS[Math.min(
|
||||
Math.max(Number(snapshot.currentStep) || 0, 0),
|
||||
WIZARD_STEPS.length - 1,
|
||||
)]?.id
|
||||
const restoredStepId = isStepId(snapshot.currentStepId)
|
||||
? snapshot.currentStepId
|
||||
: legacyStepId || indexedStepId || 'create'
|
||||
|
||||
restoringDraft.value = true
|
||||
currentStep.value = requiresPreviewMigration
|
||||
? 0
|
||||
: Math.min(Math.max(Number(snapshot.currentStep) || 0, 0), WIZARD_STEPS.length - 1)
|
||||
goToStep(requiresPreviewMigration ? 'create' : restoredStepId)
|
||||
task.name = snapshot.task?.name || ''
|
||||
task.description = snapshot.task?.description || ''
|
||||
processType.value = snapshot.processType === 'structured' || snapshot.processType === 'external'
|
||||
@@ -289,18 +302,51 @@ function restoreDraft() {
|
||||
}
|
||||
}
|
||||
if (snapshot.unstructuredOptions) {
|
||||
const restoredUnstructuredOptions = snapshot.unstructuredOptions
|
||||
const restoredDatasetSplit = restoredUnstructuredOptions.datasetSplit
|
||||
unstructuredOptions.value = {
|
||||
...unstructuredOptions.value,
|
||||
...snapshot.unstructuredOptions,
|
||||
preprocessOptions: Array.isArray(snapshot.unstructuredOptions.preprocessOptions)
|
||||
? snapshot.unstructuredOptions.preprocessOptions
|
||||
preprocessOptions: Array.isArray(restoredUnstructuredOptions.preprocessOptions)
|
||||
? restoredUnstructuredOptions.preprocessOptions
|
||||
: unstructuredOptions.value.preprocessOptions,
|
||||
generationTypes: Array.isArray(snapshot.unstructuredOptions.generationTypes)
|
||||
? snapshot.unstructuredOptions.generationTypes
|
||||
: unstructuredOptions.value.generationTypes,
|
||||
chunkMethod: restoredUnstructuredOptions.chunkMethod || unstructuredOptions.value.chunkMethod,
|
||||
chunkSize: Number.isFinite(restoredUnstructuredOptions.chunkSize)
|
||||
? restoredUnstructuredOptions.chunkSize
|
||||
: unstructuredOptions.value.chunkSize,
|
||||
chunkOverlap: Number.isFinite(restoredUnstructuredOptions.chunkOverlap)
|
||||
? restoredUnstructuredOptions.chunkOverlap
|
||||
: unstructuredOptions.value.chunkOverlap,
|
||||
minChunkSize: Number.isFinite(restoredUnstructuredOptions.minChunkSize)
|
||||
? restoredUnstructuredOptions.minChunkSize
|
||||
: unstructuredOptions.value.minChunkSize,
|
||||
customDelimiter: typeof restoredUnstructuredOptions.customDelimiter === 'string'
|
||||
? restoredUnstructuredOptions.customDelimiter
|
||||
: unstructuredOptions.value.customDelimiter,
|
||||
preserveTables: typeof restoredUnstructuredOptions.preserveTables === 'boolean'
|
||||
? restoredUnstructuredOptions.preserveTables
|
||||
: unstructuredOptions.value.preserveTables,
|
||||
preserveCodeBlocks: typeof restoredUnstructuredOptions.preserveCodeBlocks === 'boolean'
|
||||
? restoredUnstructuredOptions.preserveCodeBlocks
|
||||
: unstructuredOptions.value.preserveCodeBlocks,
|
||||
preserveLists: typeof restoredUnstructuredOptions.preserveLists === 'boolean'
|
||||
? restoredUnstructuredOptions.preserveLists
|
||||
: unstructuredOptions.value.preserveLists,
|
||||
semanticEnrichment: typeof restoredUnstructuredOptions.semanticEnrichment === 'boolean'
|
||||
? restoredUnstructuredOptions.semanticEnrichment
|
||||
: unstructuredOptions.value.semanticEnrichment,
|
||||
qaPairsPerChunk: Number.isFinite(restoredUnstructuredOptions.qaPairsPerChunk)
|
||||
? restoredUnstructuredOptions.qaPairsPerChunk
|
||||
: unstructuredOptions.value.qaPairsPerChunk,
|
||||
datasetSplit: {
|
||||
...unstructuredOptions.value.datasetSplit,
|
||||
...snapshot.unstructuredOptions.datasetSplit,
|
||||
train: Number.isFinite(restoredDatasetSplit?.train)
|
||||
? restoredDatasetSplit.train
|
||||
: unstructuredOptions.value.datasetSplit.train,
|
||||
validation: Number.isFinite(restoredDatasetSplit?.validation)
|
||||
? restoredDatasetSplit.validation
|
||||
: unstructuredOptions.value.datasetSplit.validation,
|
||||
test: Number.isFinite(restoredDatasetSplit?.test)
|
||||
? restoredDatasetSplit.test
|
||||
: unstructuredOptions.value.datasetSplit.test,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -336,7 +382,15 @@ function restoreDraft() {
|
||||
|| null
|
||||
results.value = !requiresPreviewMigration && Array.isArray(snapshot.results) ? snapshot.results : []
|
||||
selectedResultId.value = requiresPreviewMigration ? null : snapshot.selectedResultId || results.value[0]?.id || null
|
||||
if (!requiresPreviewMigration) Object.assign(generation, snapshot.generation || {})
|
||||
if (!requiresPreviewMigration) {
|
||||
const restoredGeneration = snapshot.generation || {}
|
||||
Object.assign(generation, restoredGeneration)
|
||||
if (generation.status === 'running') {
|
||||
generation.status = 'idle'
|
||||
generation.progress = 0
|
||||
generation.message = '草稿已恢复,请重新开始生成。'
|
||||
}
|
||||
}
|
||||
dirty.value = false
|
||||
nextTick(() => { restoringDraft.value = false })
|
||||
ElMessage.info(requiresPreviewMigration
|
||||
@@ -355,6 +409,12 @@ watch(
|
||||
{ deep: true },
|
||||
)
|
||||
|
||||
watch(processType, (nextType, previousType) => {
|
||||
if (restoringDraft.value || nextType === previousType || uploadedFiles.value.length === 0) return
|
||||
resetSourceDataForProcessTypeChange()
|
||||
ElMessage.info('处理类型已变更,请重新上传或拉取匹配的源数据')
|
||||
})
|
||||
|
||||
watch(generationOptionsSignature, (currentSignature, previousSignature) => {
|
||||
if (restoringDraft.value || currentSignature === previousSignature) return
|
||||
resetDownstream()
|
||||
@@ -418,8 +478,6 @@ function useSampleFile() {
|
||||
count: DEFAULT_SOURCE_TEXT.split('\n').filter((line) => line.trim()).length,
|
||||
content: DEFAULT_SOURCE_TEXT
|
||||
}]
|
||||
if (!task.name) task.name = '金融问答清洗任务'
|
||||
if (!task.description) task.description = '清洗金融领域问答数据,统一格式并生成高质量训练数据。'
|
||||
dirty.value = true
|
||||
}
|
||||
|
||||
@@ -459,7 +517,6 @@ function handlePullData() {
|
||||
count: Math.min(externalSource.limit, DEFAULT_SOURCE_TEXT.split('\n').filter((line) => line.trim()).length),
|
||||
content: DEFAULT_SOURCE_TEXT,
|
||||
})
|
||||
if (!task.name) task.name = `${typeName} 数据拉取任务`
|
||||
dirty.value = true
|
||||
ElMessage.success(`已成功拉取 ${uploadedFiles.value[uploadedFiles.value.length - 1].count.toLocaleString()} 条数据`)
|
||||
}, 2000)
|
||||
@@ -477,6 +534,17 @@ function handleRemoveFile(uid: string | number) {
|
||||
}
|
||||
}
|
||||
|
||||
function resetSourceDataForProcessTypeChange() {
|
||||
uploadedFiles.value = []
|
||||
previewSignature.value = ''
|
||||
previewItems.value = []
|
||||
selectedPreviewFileId.value = null
|
||||
selectedPreviewId.value = null
|
||||
selectedPreviewIdsByFile.value = {}
|
||||
externalConnected.value = false
|
||||
resetDownstream()
|
||||
}
|
||||
|
||||
function resetDownstream() {
|
||||
stopGenerationTimer()
|
||||
generation.status = 'idle'
|
||||
@@ -489,6 +557,10 @@ function resetDownstream() {
|
||||
async function nextFromCreate() {
|
||||
const valid = await taskSetupRef.value?.validate()
|
||||
if (!valid) return
|
||||
goToStep('upload')
|
||||
}
|
||||
|
||||
function nextFromUpload() {
|
||||
if (uploadedFiles.value.length === 0) {
|
||||
ElMessage.warning(processType.value === 'external' ? '请先拉取至少一个数据源' : '请上传至少一个源数据文件')
|
||||
return
|
||||
@@ -512,7 +584,7 @@ async function nextFromCreate() {
|
||||
previewSignature.value = signature
|
||||
resetDownstream()
|
||||
}
|
||||
currentStep.value = 1
|
||||
goToStep('preview')
|
||||
}
|
||||
|
||||
function selectPreviewFile(fileId: string) {
|
||||
@@ -671,17 +743,21 @@ async function handlePrimaryAction() {
|
||||
await nextFromCreate()
|
||||
return
|
||||
}
|
||||
if (currentStepId.value === 'upload') {
|
||||
nextFromUpload()
|
||||
return
|
||||
}
|
||||
if (currentStepId.value === 'preview') {
|
||||
if (!previewItems.value.length) {
|
||||
ElMessage.warning('当前没有可生成的预览内容')
|
||||
return
|
||||
}
|
||||
currentStep.value = 2
|
||||
goToStep('generate')
|
||||
return
|
||||
}
|
||||
if (currentStepId.value === 'generate') {
|
||||
if (generation.status === 'success') {
|
||||
currentStep.value = 3
|
||||
goToStep('results')
|
||||
} else if (generation.status !== 'running') {
|
||||
startGeneration()
|
||||
}
|
||||
@@ -760,6 +836,7 @@ onMounted(restoreDraft)
|
||||
'is-active': currentStep === index,
|
||||
'is-completed': currentStep > index
|
||||
}"
|
||||
:aria-current="currentStep === index ? 'step' : undefined"
|
||||
>
|
||||
<div v-if="index !== 0" class="step-connector"></div>
|
||||
<div class="step-node">
|
||||
@@ -784,6 +861,11 @@ onMounted(restoreDraft)
|
||||
v-model:process-type="processType"
|
||||
v-model:structured-options="structuredOptions"
|
||||
v-model:unstructured-options="unstructuredOptions"
|
||||
/>
|
||||
|
||||
<SourceUploadStep
|
||||
v-else-if="currentStepId === 'upload'"
|
||||
:process-type="processType"
|
||||
:uploaded-files="uploadedFiles"
|
||||
:external-source="externalSource"
|
||||
:external-pulling="externalPulling"
|
||||
@@ -826,7 +908,7 @@ onMounted(restoreDraft)
|
||||
/>
|
||||
|
||||
<ResultEditorStep
|
||||
v-else
|
||||
v-else-if="currentStepId === 'results'"
|
||||
v-model:selected-id="selectedResultId"
|
||||
:items="results"
|
||||
@update:field="updateResultField"
|
||||
@@ -1013,4 +1095,47 @@ onMounted(restoreDraft)
|
||||
.wizard-primary-action {
|
||||
min-width: 160px;
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.wizard-main {
|
||||
padding: 24px 20px;
|
||||
}
|
||||
|
||||
.custom-wizard-steps {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.step-connector {
|
||||
margin: 0 8px;
|
||||
}
|
||||
|
||||
.step-node {
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.step-title {
|
||||
max-width: 72px;
|
||||
font-size: 12px;
|
||||
line-height: 1.35;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.wizard-footer {
|
||||
padding: 0 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.step-title {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.step-connector {
|
||||
margin: 0 6px;
|
||||
}
|
||||
|
||||
.wizard-primary-action {
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user