feat: 数据处理向导新增模型配置步骤

StepId 新增 model 步骤,DataProcessCreateView 增加模型选择与生成参数编排,各选项面板与样式适配新步骤流程,回归脚本补充断言。
This commit is contained in:
caoxiaozhu
2026-07-13 17:12:49 +08:00
parent 42f2efb449
commit 762d48d090
9 changed files with 257 additions and 121 deletions

View File

@@ -41,15 +41,20 @@ assert.match(viewSource, /onBeforeRouteLeave\(async \(\) =>/, '路由离开确
assert.doesNotMatch(viewSource, /window\.confirm|ElMessageBox/, '创建页仍在使用系统或 Element Plus 确认框') assert.doesNotMatch(viewSource, /window\.confirm|ElMessageBox/, '创建页仍在使用系统或 Element Plus 确认框')
assert.match(viewSource, /const WIZARD_STEPS = \[/, '向导步骤尚未改为固定常量') assert.match(viewSource, /const WIZARD_STEPS = \[/, '向导步骤尚未改为固定常量')
for (const title of ['创建任务', '上传文件', '数据预览', '开始生成', '结果编辑与保存']) { for (const title of ['创建任务', '大模型选择', '上传文件', '数据预览', '开始生成', '结果编辑与保存']) {
assert.ok(viewSource.includes(`title: '${title}'`), `缺少固定步骤:${title}`) assert.ok(viewSource.includes(`title: '${title}'`), `缺少固定步骤:${title}`)
} }
assert.match( assert.match(
viewSource, viewSource,
/\{ id: 'create',[\s\S]*?\{ id: 'upload',[\s\S]*?\{ id: 'preview',[\s\S]*?\{ id: 'generate',[\s\S]*?\{ id: 'results'/, /\{ id: 'create',[\s\S]*?\{ id: 'model',[\s\S]*?\{ id: 'upload',[\s\S]*?\{ id: 'preview',[\s\S]*?\{ id: 'generate',[\s\S]*?\{ id: 'results'/,
'步向导顺序必须为创建任务、上传文件、数据预览、开始生成、结果编辑与保存', '步向导顺序必须为创建任务、大模型选择、上传文件、数据预览、开始生成、结果编辑与保存',
) )
assert.doesNotMatch(viewSource, /steps\s*=\s*computed|all\.filter/, '步骤仍根据处理类型动态增减') assert.doesNotMatch(viewSource, /steps\s*=\s*computed|all\.filter/, '步骤仍根据处理类型动态增减')
assert.match(
viewStyleSource,
/@media \(max-width: 1100px\)[\s\S]*?\.step-title\s*\{[\s\S]*?display:\s*none[\s\S]*?\.step-item\.is-active \.step-title\s*\{[\s\S]*?display:\s*block/,
'六步向导在中等宽度下没有收起非当前步骤标题',
)
assert.match(draftSource, /localStorage\.setItem\(DATA_PROCESS_DRAFT_STORAGE_KEY/, '草稿没有持久化') assert.match(draftSource, /localStorage\.setItem\(DATA_PROCESS_DRAFT_STORAGE_KEY/, '草稿没有持久化')
assert.match(draftSource, /localStorage\.getItem\(DATA_PROCESS_DRAFT_STORAGE_KEY\)/, '草稿没有恢复读取') assert.match(draftSource, /localStorage\.getItem\(DATA_PROCESS_DRAFT_STORAGE_KEY\)/, '草稿没有恢复读取')
assert.match(viewSource, /restoreDraft\(\)/, '页面没有恢复草稿') assert.match(viewSource, /restoreDraft\(\)/, '页面没有恢复草稿')
@@ -57,6 +62,7 @@ assert.ok(viewSource.split('\n').length < 800, 'DataProcessCreateView 拆分后
const expectedComponents = [ const expectedComponents = [
'TaskSetupStep.vue', 'TaskSetupStep.vue',
'ModelSelectionStep.vue',
'SourceUploadStep.vue', 'SourceUploadStep.vue',
'PreviewCompareStep.vue', 'PreviewCompareStep.vue',
'GenerationStep.vue', 'GenerationStep.vue',
@@ -82,7 +88,7 @@ for (const field of ['sourceStart', 'sourceEnd', 'originalContent', 'editedConte
assert.ok(typesSource.includes(field), `PreviewItem 缺少字段:${field}`) assert.ok(typesSource.includes(field), `PreviewItem 缺少字段:${field}`)
} }
assert.match(typesSource, /sourceFileId/, 'PreviewItem 缺少来源文件标识') assert.match(typesSource, /sourceFileId/, 'PreviewItem 缺少来源文件标识')
assert.match(typesSource, /export type StepId = 'create' \| 'upload' \| 'preview' \| 'generate' \| 'results'/, '步骤类型缺少独立上传步骤') assert.match(typesSource, /export type StepId = 'create' \| 'model' \| 'upload' \| 'preview' \| 'generate' \| 'results'/, '步骤类型缺少独立大模型选择步骤')
assert.match(modelSource, /export function buildPreviewItems/, '缺少切片来源映射生成函数') assert.match(modelSource, /export function buildPreviewItems/, '缺少切片来源映射生成函数')
assert.match(modelSource, /export function sourceLines/, '缺少源文件行偏移生成函数') assert.match(modelSource, /export function sourceLines/, '缺少源文件行偏移生成函数')
assert.match(modelSource, /sourceFileId/, '切片生成没有写入来源文件标识') assert.match(modelSource, /sourceFileId/, '切片生成没有写入来源文件标识')
@@ -102,10 +108,10 @@ for (const marker of [
'preview-editor', 'preview-editor',
'scrollIntoView', 'scrollIntoView',
]) { ]) {
assert.ok(previewSource.includes(marker), `步缺少结构或行为:${marker}`) assert.ok(previewSource.includes(marker), `步缺少结构或行为:${marker}`)
} }
assert.match(previewSource, /sourceStart/, '第步未使用来源起始偏移') assert.match(previewSource, /sourceStart/, '第步未使用来源起始偏移')
assert.match(previewSource, /sourceEnd/, '第步未使用来源结束偏移') assert.match(previewSource, /sourceEnd/, '第步未使用来源结束偏移')
assert.match(previewSource, /filterable/, '文件选择器必须可搜索') assert.match(previewSource, /filterable/, '文件选择器必须可搜索')
assert.match(previewSource, /当前文件/, '预览缺少当前文件切换器') assert.match(previewSource, /当前文件/, '预览缺少当前文件切换器')
assert.doesNotMatch(previewSource, /located-badge|sync-label|已定位到/, '源文件栏不应显示冗余定位提示') assert.doesNotMatch(previewSource, /located-badge|sync-label|已定位到/, '源文件栏不应显示冗余定位提示')
@@ -130,13 +136,14 @@ assert.match(previewSource, />保存修改<\/el-button>/, '编辑器缺少保存
assert.match(previewSource, /\.editor-actions\s*\{[\s\S]*?justify-content:\s*flex-end/, '取消和保存按钮必须在编辑器右侧对齐') assert.match(previewSource, /\.editor-actions\s*\{[\s\S]*?justify-content:\s*flex-end/, '取消和保存按钮必须在编辑器右侧对齐')
assert.doesNotMatch(previewSource, /item-token|item-status|modifiedOnly|仅看已修改/, '切片列表不应再显示 Token 或修改状态') assert.doesNotMatch(previewSource, /item-token|item-status|modifiedOnly|仅看已修改/, '切片列表不应再显示 Token 或修改状态')
assert.match(previewSource, /\.preview-editor\s*\{[\s\S]*?flex:\s*1 1 auto[\s\S]*?overflow-y:\s*auto/, '编辑模式必须占据右侧剩余区域并可滚动') assert.match(previewSource, /\.preview-editor\s*\{[\s\S]*?flex:\s*1 1 auto[\s\S]*?overflow-y:\s*auto/, '编辑模式必须占据右侧剩余区域并可滚动')
assert.match(previewSource, /@media \(max-width: 900px\)/, '第步缺少窄屏上下布局') assert.match(previewSource, /@media \(max-width: 900px\)/, '第步缺少窄屏上下布局')
const taskSetupPath = path.join(createDir, 'TaskSetupStep.vue') const taskSetupPath = path.join(createDir, 'TaskSetupStep.vue')
const structuredOptionsPath = path.join(createDir, 'StructuredOptionsPanel.vue') const structuredOptionsPath = path.join(createDir, 'StructuredOptionsPanel.vue')
const unstructuredOptionsPath = path.join(createDir, 'UnstructuredOptionsPanel.vue') const unstructuredOptionsPath = path.join(createDir, 'UnstructuredOptionsPanel.vue')
const datasetSplitEditorPath = path.join(createDir, 'DatasetSplitEditor.vue') const datasetSplitEditorPath = path.join(createDir, 'DatasetSplitEditor.vue')
const generationOptionsPath = path.join(createDir, 'GenerationOptionsPanel.vue') const generationOptionsPath = path.join(createDir, 'GenerationOptionsPanel.vue')
const modelSelectionPath = path.join(createDir, 'ModelSelectionStep.vue')
const sourceUploadPath = path.join(createDir, 'SourceUploadStep.vue') const sourceUploadPath = path.join(createDir, 'SourceUploadStep.vue')
const [ const [
taskSetupSource, taskSetupSource,
@@ -144,6 +151,7 @@ const [
unstructuredOptionsSource, unstructuredOptionsSource,
datasetSplitEditorSource, datasetSplitEditorSource,
generationControlSource, generationControlSource,
modelSelectionSource,
sourceUploadSource, sourceUploadSource,
] = await Promise.all([ ] = await Promise.all([
readFile(taskSetupPath, 'utf8'), readFile(taskSetupPath, 'utf8'),
@@ -151,6 +159,7 @@ const [
readFile(unstructuredOptionsPath, 'utf8'), readFile(unstructuredOptionsPath, 'utf8'),
readFile(datasetSplitEditorPath, 'utf8'), readFile(datasetSplitEditorPath, 'utf8'),
readFile(generationOptionsPath, 'utf8'), readFile(generationOptionsPath, 'utf8'),
readFile(modelSelectionPath, 'utf8'),
readFile(sourceUploadPath, 'utf8'), readFile(sourceUploadPath, 'utf8'),
]) ])
const taskSetupFeatureSource = [ const taskSetupFeatureSource = [
@@ -172,20 +181,29 @@ assert.match(unstructuredOptionsSource, /<DatasetSplitEditor/, '非结构化选
for (const marker of ['<el-upload', '源数据上传', '数据源配置', 'uploadedFiles']) { for (const marker of ['<el-upload', '源数据上传', '数据源配置', 'uploadedFiles']) {
assert.ok(!taskSetupFeatureSource.includes(marker), `第一步仍包含上传职责:${marker}`) assert.ok(!taskSetupFeatureSource.includes(marker), `第一步仍包含上传职责:${marker}`)
} }
assert.match(viewSource, /<SourceUploadStep\s+[\s\S]*?v-else-if="currentStepId === 'upload'"/, '第二步没有挂载独立上传组件') assert.match(viewSource, /<ModelSelectionStep\s+[\s\S]*?v-else-if="currentStepId === 'model'"/, '第二步没有挂载独立大模型选择组件')
assert.match(viewSource, /if \(currentStepId\.value === 'create'\) return '继续:上传文件'/, '第一步主按钮没有指向上传件') assert.match(viewSource, /<SourceUploadStep\s+[\s\S]*?v-else-if="currentStepId === 'upload'"/, '第三步没有挂载独立上传件')
assert.match(viewSource, /if \(currentStepId\.value === 'upload'\) return '继续:数据预览'/, '第步主按钮没有指向数据预览') assert.match(viewSource, /if \(currentStepId\.value === 'create'\) return '继续:选择大模型'/, '第步主按钮没有指向大模型选择')
assert.match(viewSource, /if \(currentStepId\.value === 'model'\) return '继续:上传文件'/, '第二步主按钮没有指向上传文件')
assert.match(viewSource, /if \(currentStepId\.value === 'upload'\) return '继续:数据预览'/, '第三步主按钮没有指向数据预览')
assert.match(draftSource, /DATA_PROCESS_DRAFT_SCHEMA_VERSION = 6/, '安全草稿格式必须升级到 v6') assert.match(draftSource, /DATA_PROCESS_DRAFT_SCHEMA_VERSION = 6/, '安全草稿格式必须升级到 v6')
const nextFromCreateStart = viewSource.indexOf('async function nextFromCreate()') const nextFromCreateStart = viewSource.indexOf('async function nextFromCreate()')
const nextFromUploadStart = viewSource.indexOf('function nextFromUpload()', nextFromCreateStart) const nextFromModelStart = viewSource.indexOf('async function nextFromModel()', nextFromCreateStart)
const nextFromUploadStart = viewSource.indexOf('function nextFromUpload()', nextFromModelStart)
const selectPreviewFileStart = viewSource.indexOf('function selectPreviewFile(', nextFromUploadStart) const selectPreviewFileStart = viewSource.indexOf('function selectPreviewFile(', nextFromUploadStart)
assert.ok(nextFromCreateStart >= 0 && nextFromUploadStart > nextFromCreateStart, '缺少创建步骤与上传步骤的独立跳转函数') assert.ok(
const nextFromCreateSource = viewSource.slice(nextFromCreateStart, nextFromUploadStart) nextFromCreateStart >= 0 && nextFromModelStart > nextFromCreateStart && nextFromUploadStart > nextFromModelStart,
'缺少创建、大模型选择与上传步骤的独立跳转函数',
)
const nextFromCreateSource = viewSource.slice(nextFromCreateStart, nextFromModelStart)
const nextFromModelSource = viewSource.slice(nextFromModelStart, nextFromUploadStart)
const nextFromUploadSource = viewSource.slice(nextFromUploadStart, selectPreviewFileStart) const nextFromUploadSource = viewSource.slice(nextFromUploadStart, selectPreviewFileStart)
assert.match(nextFromCreateSource, /taskSetupRef\.value\?\.validate\(\)/, '创建步骤继续前没有校验任务配置') assert.match(nextFromCreateSource, /taskSetupRef\.value\?\.validate\(\)/, '创建步骤继续前没有校验任务配置')
assert.match(nextFromCreateSource, /goToStep\('upload'\)/, '创建步骤校验通过后没有进入上传文件') assert.match(nextFromCreateSource, /goToStep\('model'\)/, '创建步骤校验通过后没有进入大模型选择')
assert.doesNotMatch(nextFromCreateSource, /uploadedFiles|buildPreviewItems/, '创建步骤仍在校验文件或提前生成预览') assert.doesNotMatch(nextFromCreateSource, /uploadedFiles|buildPreviewItems/, '创建步骤仍在校验文件或提前生成预览')
assert.match(nextFromModelSource, /modelSelectionRef\.value\?\.validate\(\)/, '大模型选择步骤继续前没有校验模型配置')
assert.match(nextFromModelSource, /goToStep\('upload'\)/, '大模型选择完成后没有进入上传文件')
assert.match(nextFromUploadSource, /uploadedFiles\.value\.length === 0/, '上传步骤继续前没有校验源数据') assert.match(nextFromUploadSource, /uploadedFiles\.value\.length === 0/, '上传步骤继续前没有校验源数据')
assert.match(nextFromUploadSource, /buildPreviewItems\(/, '上传步骤没有在进入预览前生成预览数据') assert.match(nextFromUploadSource, /buildPreviewItems\(/, '上传步骤没有在进入预览前生成预览数据')
assert.match(nextFromUploadSource, /goToStep\('preview'\)/, '上传步骤完成后没有进入数据预览') assert.match(nextFromUploadSource, /goToStep\('preview'\)/, '上传步骤完成后没有进入数据预览')
@@ -217,8 +235,6 @@ for (const option of [
assert.ok(structuredOptionsSource.includes(option), `结构化预处理缺少选项:${option}`) assert.ok(structuredOptionsSource.includes(option), `结构化预处理缺少选项:${option}`)
} }
assert.ok(structuredOptionsSource.includes('生成选项'), '结构化配置缺少生成选项分类') assert.ok(structuredOptionsSource.includes('生成选项'), '结构化配置缺少生成选项分类')
assert.ok(structuredOptionsSource.includes('语义丰富表达'), '生成选项缺少语义丰富表达开关')
assert.ok(structuredOptionsSource.includes('使用大模型将问答表述得更自然、柔和'), '语义丰富表达缺少辅助说明')
for (const splitName of ['训练集', '验证集', '测试集']) { for (const splitName of ['训练集', '验证集', '测试集']) {
assert.ok(datasetSplitEditorSource.includes(splitName), `生成选项缺少数据集划分:${splitName}`) assert.ok(datasetSplitEditorSource.includes(splitName), `生成选项缺少数据集划分:${splitName}`)
} }
@@ -232,7 +248,6 @@ for (const splitField of ['train', 'validation', 'test']) {
`数据集划分字段 ${splitField} 缺少 0100 的整数限制`, `数据集划分字段 ${splitField} 缺少 0100 的整数限制`,
) )
} }
assert.match(structuredOptionsSource, /<el-switch[\s\S]*options\.semanticEnrichment/, '语义丰富表达必须使用开关控件')
assert.match(structuredOptionsSource, /<el-input-number[\s\S]*options\.qaPairsPerRow[\s\S]*:min="1"[\s\S]*:max="5"/, '每行生成数量必须限制在 1 到 5') assert.match(structuredOptionsSource, /<el-input-number[\s\S]*options\.qaPairsPerRow[\s\S]*:min="1"[\s\S]*:max="5"/, '每行生成数量必须限制在 1 到 5')
assert.match(viewSource, /const structuredOptions = ref<StructuredProcessOptions>/, '父页面缺少结构化配置状态') assert.match(viewSource, /const structuredOptions = ref<StructuredProcessOptions>/, '父页面缺少结构化配置状态')
assert.match(stateSource, /datasetSplit:\s*\{ train: 80, validation: 10, test: 10 \}/, '数据集划分默认值必须为 80/10/10') assert.match(stateSource, /datasetSplit:\s*\{ train: 80, validation: 10, test: 10 \}/, '数据集划分默认值必须为 80/10/10')
@@ -252,12 +267,16 @@ for (const field of [
assert.ok(typesSource.includes(field), `生成控制配置缺少字段:${field}`) assert.ok(typesSource.includes(field), `生成控制配置缺少字段:${field}`)
assert.ok(implementationSource.includes(field), `父页面默认值或草稿状态缺少字段:${field}`) assert.ok(implementationSource.includes(field), `父页面默认值或草稿状态缺少字段:${field}`)
} }
assert.match(structuredOptionsSource, /GenerationOptionsPanel/, '结构化生成选项没有复用统一的大模型与质量筛选组件') assert.match(structuredOptionsSource, /GenerationOptionsPanel/, '结构化生成选项没有复用统一的质量筛选组件')
assert.match(unstructuredOptionsSource, /GenerationOptionsPanel/, '非结构化生成选项没有复用统一的大模型与质量筛选组件') assert.match(unstructuredOptionsSource, /GenerationOptionsPanel/, '非结构化生成选项没有复用统一的质量筛选组件')
assert.match(structuredOptionsSource, /:options="options"/, '结构化生成选项未接入统一配置组件') assert.match(structuredOptionsSource, /:options="options"/, '结构化生成选项未接入统一配置组件')
assert.match(unstructuredOptionsSource, /:options="options"/, '非结构化生成选项未接入统一配置组件') assert.match(unstructuredOptionsSource, /:options="options"/, '非结构化生成选项未接入统一配置组件')
assert.match(taskSetupFeatureSource, /<h3>大模型<\/h3>/, '大模型必须作为与生成选项平级的独立分类') assert.doesNotMatch(taskSetupFeatureSource, /<h3>大模型<\/h3>|section="model"/, '第一步不应继续承载大模型配置')
assert.match(taskSetupFeatureSource, /section="model"/, '独立大模型分类没有挂载模型配置') assert.match(modelSelectionSource, /<h3[^>]*>大模型选择<\/h3>/, '独立步骤缺少大模型选择标题')
assert.match(modelSelectionSource, /section="model"/, '独立步骤没有挂载模型配置')
assert.match(modelSelectionSource, /defineExpose\(\{ validate \}\)/, '独立大模型选择步骤没有暴露继续前校验')
assert.match(modelSelectionSource, /class="form-section"/, '大模型选择步骤没有沿用第一步的通栏表单分区')
assert.doesNotMatch(modelSelectionSource, /max-width:\s*980px/, '大模型选择步骤不应使用比第一步更窄的固定内容宽度')
assert.match(taskSetupFeatureSource, /section="quality"/, '质量筛选没有保留在生成选项分类中') assert.match(taskSetupFeatureSource, /section="quality"/, '质量筛选没有保留在生成选项分类中')
assert.doesNotMatch(generationControlSource, /<h4>大模型<\/h4>/, '大模型不应继续作为生成选项内部子分类') assert.doesNotMatch(generationControlSource, /<h4>大模型<\/h4>/, '大模型不应继续作为生成选项内部子分类')
for (const label of ['大模型', '数据生成模型', '默认提示语', '质量筛选', '过滤低质量内容', '过滤过短内容', '最少字数']) { for (const label of ['大模型', '数据生成模型', '默认提示语', '质量筛选', '过滤低质量内容', '过滤过短内容', '最少字数']) {
@@ -267,15 +286,21 @@ assert.match(generationControlSource, /filterable/, '数据生成模型下拉必
assert.match(generationControlSource, /maxlength="500"/, '默认提示语缺少合理的长度限制') assert.match(generationControlSource, /maxlength="500"/, '默认提示语缺少合理的长度限制')
assert.match(generationControlSource, /\.model-field\s*\{[\s\S]*?display:\s*flex[\s\S]*?flex-direction:\s*column/, '大模型字段没有使用稳定的纵向表单布局') assert.match(generationControlSource, /\.model-field\s*\{[\s\S]*?display:\s*flex[\s\S]*?flex-direction:\s*column/, '大模型字段没有使用稳定的纵向表单布局')
assert.match(generationControlSource, /\.generation-config-group\s*\{[\s\S]*?border:\s*1px solid #e2e5ec/, '大模型配置没有保留统一配置面板边框') assert.match(generationControlSource, /\.generation-config-group\s*\{[\s\S]*?border:\s*1px solid #e2e5ec/, '大模型配置没有保留统一配置面板边框')
assert.match(generationControlSource, /\.model-config-group\s*\{[\s\S]*?padding:\s*0[\s\S]*?border:\s*0/, '独立大模型步骤仍存在嵌套卡片挤压')
assert.match(
generationControlSource,
/\.model-config-group \.advanced-settings-grid\s*\{[\s\S]*?grid-template-columns:\s*1fr/,
'大模型高级参数没有改为与第一步一致的纵向布局',
)
assert.match(stateSource, /const DEFAULT_GENERATION_PROMPT\s*=\s*['"][^'"]{40,}['"]/, '大模型配置缺少可直接使用的默认提示语') assert.match(stateSource, /const DEFAULT_GENERATION_PROMPT\s*=\s*['"][^'"]{40,}['"]/, '大模型配置缺少可直接使用的默认提示语')
assert.equal((stateSource.match(/generationPrompt:\s*DEFAULT_GENERATION_PROMPT/g) || []).length, 2, '结构化与非结构化任务必须共用默认提示语') assert.equal((stateSource.match(/generationPrompt:\s*DEFAULT_GENERATION_PROMPT/g) || []).length, 2, '结构化与非结构化任务必须共用默认提示语')
assert.match(generationControlSource, /v-if="options\.qualityFilterEnabled"/, '质量规则没有随总开关渐进显示') assert.match(generationControlSource, /v-if="options\.qualityFilterEnabled"/, '质量规则没有随总开关渐进显示')
assert.match(generationControlSource, /v-if="options\.filterShortContent"/, '最少字数没有随短内容规则显示') assert.match(generationControlSource, /v-if="options\.filterShortContent"/, '最少字数没有随短内容规则显示')
assert.match(generationControlSource, /:min="1"[\s\S]*:max="1000"/, '最少字数缺少 1 到 1000 的边界限制') assert.match(generationControlSource, /:min="1"[\s\S]*:max="1000"/, '最少字数缺少 1 到 1000 的边界限制')
assert.match(taskSetupSource, /generationValidationMessage/, '生成模型与质量规则缺少继续前校验') assert.match(taskSetupSource, /qualityValidationMessage/, '质量规则缺少继续前校验')
assert.match(viewSource, /useModelsStore/, '创建页没有加载模型列表') assert.match(viewSource, /useModelsStore/, '创建页没有加载模型列表')
assert.match(viewSource, /model\.type === 'LLM'/, '数据生成模型列表没有排除非大模型') assert.match(viewSource, /model\.type === 'LLM'/, '数据生成模型列表没有排除非大模型')
assert.match(viewSource, /:generation-models="generationModels"/, '创建页没有向生成选项传递模型列表') assert.match(viewSource, /<ModelSelectionStep[\s\S]*?:models="generationModels"/, '创建页没有向独立大模型选择步骤传递模型列表')
assert.match(typesSource, /export interface UnstructuredProcessOptions/, '缺少非结构化处理选项类型') assert.match(typesSource, /export interface UnstructuredProcessOptions/, '缺少非结构化处理选项类型')
for (const field of [ for (const field of [
@@ -312,11 +337,7 @@ for (const method of ['自动语义切分', '按标题和段落', '按固定长
for (const label of ['切片长度', '重叠长度', '最小切片长度', '保护表格、代码和列表']) { for (const label of ['切片长度', '重叠长度', '最小切片长度', '保护表格、代码和列表']) {
assert.ok(unstructuredOptionsSource.includes(label), `切分选项缺少配置:${label}`) assert.ok(unstructuredOptionsSource.includes(label), `切分选项缺少配置:${label}`)
} }
assert.ok(unstructuredOptionsSource.includes('高级设置'), '切分选项缺少渐进式高级设置入口') assert.doesNotMatch(unstructuredOptionsSource, /advancedChunkSettingsOpen|>高级设置</, '切分核心参数不应再隐藏在高级设置')
assert.match(unstructuredOptionsSource, /:aria-expanded="advancedChunkSettingsOpen"/, '高级设置入口缺少展开状态的可访问性标记')
assert.match(unstructuredOptionsSource, /v-if="advancedChunkSettingsOpen"/, '高级设置内容没有默认收起')
assert.match(unstructuredOptionsSource, /watch\(\(\) => props\.options\.chunkMethod,[\s\S]*?method === 'custom'[\s\S]*?advancedChunkSettingsOpen\.value = true/, '选择自定义分隔符时没有自动展开高级设置')
assert.match(unstructuredOptionsSource, /function revealValidation\(\)[\s\S]*?advancedChunkSettingsOpen\.value = true/, '非结构化面板没有暴露高级校验定位能力')
assert.match(taskSetupSource, /if \(chunkValidationMessage\.value\) \{[\s\S]*?revealValidation\(\)[\s\S]*?return false/, '高级切分配置校验失败时没有重新展开定位') assert.match(taskSetupSource, /if \(chunkValidationMessage\.value\) \{[\s\S]*?revealValidation\(\)[\s\S]*?return false/, '高级切分配置校验失败时没有重新展开定位')
assert.match(unstructuredOptionsSource, /const preserveSpecialContentEnabled = computed/, '特殊内容保护没有合并为单一开关') assert.match(unstructuredOptionsSource, /const preserveSpecialContentEnabled = computed/, '特殊内容保护没有合并为单一开关')
assert.match(unstructuredOptionsSource, /function updateSpecialContentProtection/, '特殊内容保护开关缺少更新逻辑') assert.match(unstructuredOptionsSource, /function updateSpecialContentProtection/, '特殊内容保护开关缺少更新逻辑')
@@ -324,7 +345,7 @@ assert.match(unstructuredOptionsSource, /options\.chunkSize[\s\S]*?:min="200"[\s
assert.match(unstructuredOptionsSource, /options\.chunkOverlap[\s\S]*?:min="0"[\s\S]*?:max="500"/, '重叠长度必须限制在 0 到 500 Token') assert.match(unstructuredOptionsSource, /options\.chunkOverlap[\s\S]*?:min="0"[\s\S]*?:max="500"/, '重叠长度必须限制在 0 到 500 Token')
assert.match(unstructuredOptionsSource, /options\.minChunkSize[\s\S]*?:min="20"[\s\S]*?:max="500"/, '最小切片长度必须限制在 20 到 500 Token') assert.match(unstructuredOptionsSource, /options\.minChunkSize[\s\S]*?:min="20"[\s\S]*?:max="500"/, '最小切片长度必须限制在 20 到 500 Token')
for (const label of ['语义丰富表达', '每个切片生成数量', '数据集划分']) { for (const label of ['每个切片生成数量', '数据集划分']) {
assert.ok(taskSetupFeatureSource.includes(label), `非结构化生成选项缺少:${label}`) assert.ok(taskSetupFeatureSource.includes(label), `非结构化生成选项缺少:${label}`)
} }
for (const removedLabel of ['上下文范围', '问题类型', '跳过无法回答的内容']) { for (const removedLabel of ['上下文范围', '问题类型', '跳过无法回答的内容']) {
@@ -862,4 +883,4 @@ assert.match(
) )
assert.match(previewSource, /height:\s*clamp\(560px,\s*calc\(100vh - 370px\),\s*720px\)/, '对照预览高度不足以展示切片正文') assert.match(previewSource, /height:\s*clamp\(560px,\s*calc\(100vh - 370px\),\s*720px\)/, '对照预览高度不足以展示切片正文')
console.log('数据处理步向导回归检查通过') console.log('数据处理步向导回归检查通过')

View File

@@ -5,6 +5,7 @@ import { ElMessage, type UploadFile } from 'element-plus'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import AppConfirmDialog from '@/components/AppConfirmDialog.vue' import AppConfirmDialog from '@/components/AppConfirmDialog.vue'
import TaskSetupStep from './create/TaskSetupStep.vue' import TaskSetupStep from './create/TaskSetupStep.vue'
import ModelSelectionStep from './create/ModelSelectionStep.vue'
import SourceUploadStep from './create/SourceUploadStep.vue' import SourceUploadStep from './create/SourceUploadStep.vue'
import PreviewCompareStep from './create/PreviewCompareStep.vue' import PreviewCompareStep from './create/PreviewCompareStep.vue'
import GenerationStep from './create/GenerationStep.vue' import GenerationStep from './create/GenerationStep.vue'
@@ -22,6 +23,7 @@ import { useDataProcessGeneration } from './create/useDataProcessGeneration'
import { useModelsStore } from '@/stores/models' import { useModelsStore } from '@/stores/models'
import type { import type {
ExternalDataSource, ExternalDataSource,
GenerationControlOptions,
PreviewItem, PreviewItem,
ProcessType, ProcessType,
StepId, StepId,
@@ -35,15 +37,17 @@ const modelsStore = useModelsStore()
const { list: modelList } = storeToRefs(modelsStore) const { list: modelList } = storeToRefs(modelsStore)
const generationModels = computed(() => modelList.value.filter((model) => model.type === 'LLM')) const generationModels = computed(() => modelList.value.filter((model) => model.type === 'LLM'))
const taskSetupRef = ref<InstanceType<typeof TaskSetupStep>>() const taskSetupRef = ref<InstanceType<typeof TaskSetupStep>>()
const modelSelectionRef = ref<InstanceType<typeof ModelSelectionStep>>()
const confirmDialogRef = ref<InstanceType<typeof AppConfirmDialog>>() const confirmDialogRef = ref<InstanceType<typeof AppConfirmDialog>>()
const PREVIEW_MODEL_VERSION = 'document-chunk-v2' const PREVIEW_MODEL_VERSION = 'document-chunk-v2'
const WIZARD_STEPS = [ const WIZARD_STEPS = [
{ id: 'create', title: '创建任务', desc: '填写任务信息与处理配置' }, { id: 'create', title: '创建任务', desc: '填写任务信息与处理配置' },
{ id: 'model', title: '大模型选择', desc: '选择生成模型并设置输出要求' },
{ id: 'upload', title: '上传文件', desc: '上传或接入待处理的源数据' }, { id: 'upload', title: '上传文件', desc: '上传或接入待处理的源数据' },
{ id: 'preview', title: '数据预览', desc: '核对源文件与预览内容' }, { id: 'preview', title: '数据预览', desc: '核对源文件与预览内容' },
{ id: 'generate', title: '开始生成', desc: '确认摘要并启动处理' }, { id: 'generate', title: '开始生成', desc: '确认摘要并启动处理' },
{ id: 'results', title: '结果编辑与保存', desc: '检查、修改并保存结果' }, { id: 'results', title: '编辑与保存', desc: '检查、修改并保存结果' },
] as const satisfies ReadonlyArray<{ id: StepId; title: string; desc: string }> ] as const satisfies ReadonlyArray<{ id: StepId; title: string; desc: string }>
const currentStep = ref(0) const currentStep = ref(0)
const currentStepId = computed<StepId>(() => WIZARD_STEPS[currentStep.value]?.id ?? 'create') const currentStepId = computed<StepId>(() => WIZARD_STEPS[currentStep.value]?.id ?? 'create')
@@ -51,6 +55,9 @@ const task = reactive({ name: '', description: '' })
const processType = ref<ProcessType>('structured') const processType = ref<ProcessType>('structured')
const structuredOptions = ref<StructuredProcessOptions>(createDefaultStructuredOptions()) const structuredOptions = ref<StructuredProcessOptions>(createDefaultStructuredOptions())
const unstructuredOptions = ref<UnstructuredProcessOptions>(createDefaultUnstructuredOptions()) const unstructuredOptions = ref<UnstructuredProcessOptions>(createDefaultUnstructuredOptions())
const modelSelectionOptions = computed<GenerationControlOptions>(() => (
processType.value === 'unstructured' ? unstructuredOptions.value : structuredOptions.value
))
const uploadedFiles = ref<UploadedDataFile[]>([]) const uploadedFiles = ref<UploadedDataFile[]>([])
const externalSource = reactive<ExternalDataSource>({ const externalSource = reactive<ExternalDataSource>({
@@ -119,7 +126,8 @@ const previewFiles = computed(() => uploadedFiles.value.map((file) => {
} }
})) }))
const primaryActionLabel = computed(() => { const primaryActionLabel = computed(() => {
if (currentStepId.value === 'create') return '继续:上传文件' if (currentStepId.value === 'create') return '继续:选择大模型'
if (currentStepId.value === 'model') return '继续:上传文件'
if (currentStepId.value === 'upload') return '继续:数据预览' if (currentStepId.value === 'upload') return '继续:数据预览'
if (currentStepId.value === 'preview') return '确认预览并继续' if (currentStepId.value === 'preview') return '确认预览并继续'
if (currentStepId.value === 'results') return '保存任务' if (currentStepId.value === 'results') return '保存任务'
@@ -144,6 +152,14 @@ function goToStep(stepId: StepId) {
if (nextStepIndex >= 0) currentStep.value = nextStepIndex if (nextStepIndex >= 0) currentStep.value = nextStepIndex
} }
function updateModelSelectionOptions(value: GenerationControlOptions) {
if (processType.value === 'unstructured') {
unstructuredOptions.value = { ...unstructuredOptions.value, ...value }
return
}
structuredOptions.value = { ...structuredOptions.value, ...value }
}
const { persistDraft, restoreDraft } = useDataProcessDraft({ const { persistDraft, restoreDraft } = useDataProcessDraft({
currentStepId, currentStepId,
task, task,
@@ -393,6 +409,12 @@ function resetSourceDataForProcessTypeChange() {
async function nextFromCreate() { async function nextFromCreate() {
const valid = await taskSetupRef.value?.validate() const valid = await taskSetupRef.value?.validate()
if (!valid) return if (!valid) return
goToStep('model')
}
async function nextFromModel() {
const valid = await modelSelectionRef.value?.validate()
if (!valid) return
goToStep('upload') goToStep('upload')
} }
@@ -504,6 +526,10 @@ async function handlePrimaryAction() {
await nextFromCreate() await nextFromCreate()
return return
} }
if (currentStepId.value === 'model') {
await nextFromModel()
return
}
if (currentStepId.value === 'upload') { if (currentStepId.value === 'upload') {
nextFromUpload() nextFromUpload()
return return
@@ -596,27 +622,31 @@ onMounted(() => {
<div class="wizard-main-inner"> <div class="wizard-main-inner">
<div class="wizard-steps-container"> <div class="wizard-steps-container">
<div class="custom-wizard-steps"> <div class="custom-wizard-steps">
<div <template v-for="(step, index) in WIZARD_STEPS" :key="step.id">
v-for="(step, index) in WIZARD_STEPS" <div
:key="step.id" v-if="index !== 0"
class="step-item" class="step-connector"
:class="{ :class="{ 'is-active': currentStep >= index }"
'is-active': currentStep === index, ></div>
'is-completed': currentStep > index <div
}" class="step-item"
:aria-current="currentStep === index ? 'step' : undefined" :class="{
> 'is-active': currentStep === index,
<div v-if="index !== 0" class="step-connector"></div> 'is-completed': currentStep > index
<div class="step-node"> }"
<div class="step-icon"> :aria-current="currentStep === index ? 'step' : undefined"
<i v-if="currentStep > index" class="fa fa-check" /> >
<span v-else>{{ index + 1 }}</span> <div class="step-node">
</div> <div class="step-icon">
<div class="step-text"> <i v-if="currentStep > index" class="fa fa-check" />
<div class="step-title">{{ step.title }}</div> <span v-else>{{ index + 1 }}</span>
</div>
<div class="step-text">
<div class="step-title">{{ step.title }}</div>
</div>
</div> </div>
</div> </div>
</div> </template>
</div> </div>
</div> </div>
@@ -629,7 +659,14 @@ onMounted(() => {
v-model:process-type="processType" v-model:process-type="processType"
v-model:structured-options="structuredOptions" v-model:structured-options="structuredOptions"
v-model:unstructured-options="unstructuredOptions" v-model:unstructured-options="unstructuredOptions"
:generation-models="generationModels" />
<ModelSelectionStep
v-else-if="currentStepId === 'model'"
ref="modelSelectionRef"
:options="modelSelectionOptions"
:models="generationModels"
@update:options="updateModelSelectionOptions"
/> />
<SourceUploadStep <SourceUploadStep

View File

@@ -5,7 +5,7 @@ import type { GenerationControlOptions } from './types'
const props = defineProps<{ const props = defineProps<{
options: GenerationControlOptions options: GenerationControlOptions
models: ModelItem[] models?: ModelItem[]
section: 'model' | 'quality' section: 'model' | 'quality'
validationMessage?: string validationMessage?: string
}>() }>()
@@ -59,7 +59,7 @@ function sectionValidationMessage() {
@update:model-value="updateField('generationModelId', $event)" @update:model-value="updateField('generationModelId', $event)"
> >
<el-option <el-option
v-for="model in models" v-for="model in models || []"
:key="model.id" :key="model.id"
:label="model.name" :label="model.name"
:value="model.id" :value="model.id"
@@ -234,7 +234,22 @@ function sectionValidationMessage() {
.model-config-group { .model-config-group {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 16px; gap: 20px;
padding: 0;
border: 0;
border-radius: 0;
background: transparent;
}
.model-config-group .advanced-settings-grid {
grid-template-columns: 1fr;
gap: 20px;
}
.model-config-group .config-field {
padding: 0;
border: 0;
border-radius: 0;
} }
.model-field { .model-field {

View File

@@ -0,0 +1,90 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import { ElMessage } from 'element-plus'
import type { ModelItem } from '@/types'
import type { GenerationControlOptions } from './types'
import GenerationOptionsPanel from './GenerationOptionsPanel.vue'
const props = defineProps<{
options: GenerationControlOptions
models: ModelItem[]
}>()
const emit = defineEmits<{
'update:options': [value: GenerationControlOptions]
}>()
const validationAttempted = ref(false)
const modelValidationMessage = computed(() => (
props.options.generationModelId === '' ? '请选择数据生成模型' : ''
))
function validate() {
validationAttempted.value = true
if (!modelValidationMessage.value) return true
ElMessage.error(modelValidationMessage.value)
return false
}
defineExpose({ validate })
</script>
<template>
<section class="model-selection-step" aria-labelledby="model-selection-title">
<div class="form-section">
<div class="section-title-row">
<div>
<h3 id="model-selection-title">大模型选择</h3>
<p>选择本次数据生成使用的模型并设置统一的输出要求</p>
</div>
</div>
<div class="model-form-content">
<GenerationOptionsPanel
:options="options"
:models="models"
section="model"
:validation-message="validationAttempted ? modelValidationMessage : ''"
@update:options="emit('update:options', $event)"
/>
</div>
</div>
</section>
</template>
<style scoped lang="scss">
.model-selection-step {
width: 100%;
}
.form-section {
padding: 0 0 26px;
margin-bottom: 26px;
border-bottom: 1px solid #edf0f5;
}
.section-title-row {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 16px;
h3 {
margin: 0 0 5px;
color: #2f3747;
font-size: 15px;
font-weight: 650;
}
p {
margin: 0;
color: #8a93a3;
font-size: 12px;
line-height: 1.6;
}
}
.model-form-content {
margin-top: 16px;
}
</style>

View File

@@ -1,5 +1,4 @@
<script setup lang="ts"> <script setup lang="ts">
import type { ModelItem } from '@/types'
import type { import type {
GenerationControlOptions, GenerationControlOptions,
PreprocessOption, PreprocessOption,
@@ -10,9 +9,8 @@ import GenerationOptionsPanel from './GenerationOptionsPanel.vue'
const props = defineProps<{ const props = defineProps<{
options: StructuredProcessOptions options: StructuredProcessOptions
generationModels: ModelItem[]
validationAttempted: boolean validationAttempted: boolean
generationValidationMessage: string qualityValidationMessage: string
}>() }>()
const emit = defineEmits<{ const emit = defineEmits<{
@@ -89,9 +87,8 @@ function updatePreprocessOptions(value: Array<string | number | boolean>) {
<div class="generation-option-list"> <div class="generation-option-list">
<GenerationOptionsPanel <GenerationOptionsPanel
:options="options" :options="options"
:models="generationModels"
section="quality" section="quality"
:validation-message="validationAttempted ? generationValidationMessage : ''" :validation-message="validationAttempted ? qualityValidationMessage : ''"
@update:options="updateGenerationOptions" @update:options="updateGenerationOptions"
/> />
@@ -116,23 +113,6 @@ function updatePreprocessOptions(value: Array<string | number | boolean>) {
</div> </div>
</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> </template>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@@ -1,7 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import { ElMessage, type FormInstance, type FormRules } from 'element-plus' import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
import type { ModelItem } from '@/types'
import type { import type {
GenerationControlOptions, GenerationControlOptions,
ProcessType, ProcessType,
@@ -17,7 +16,6 @@ const props = defineProps<{
processType: ProcessType processType: ProcessType
structuredOptions: StructuredProcessOptions structuredOptions: StructuredProcessOptions
unstructuredOptions: UnstructuredProcessOptions unstructuredOptions: UnstructuredProcessOptions
generationModels: ModelItem[]
}>() }>()
const emit = defineEmits<{ const emit = defineEmits<{
@@ -52,10 +50,9 @@ const activeGenerationOptions = computed<GenerationControlOptions | null>(() =>
return null return null
}) })
const generationValidationMessage = computed(() => { const qualityValidationMessage = computed(() => {
const options = activeGenerationOptions.value const options = activeGenerationOptions.value
if (!options) return '' if (!options) return ''
if (options.generationModelId === '') return '请选择数据生成模型'
if (options.qualityFilterEnabled && !options.filterLowQuality && !options.filterShortContent) { if (options.qualityFilterEnabled && !options.filterLowQuality && !options.filterShortContent) {
return '开启质量筛选后,请至少选择一项筛选规则' return '开启质量筛选后,请至少选择一项筛选规则'
} }
@@ -112,8 +109,8 @@ async function validate() {
return false return false
} }
} }
if (generationValidationMessage.value) { if (qualityValidationMessage.value) {
ElMessage.error(generationValidationMessage.value) ElMessage.error(qualityValidationMessage.value)
return false return false
} }
return true return true
@@ -210,9 +207,8 @@ defineExpose({ validate })
<StructuredOptionsPanel <StructuredOptionsPanel
v-if="processType === 'structured'" v-if="processType === 'structured'"
:options="structuredOptions" :options="structuredOptions"
:generation-models="generationModels"
:validation-attempted="validationAttempted" :validation-attempted="validationAttempted"
:generation-validation-message="generationValidationMessage" :quality-validation-message="qualityValidationMessage"
@update:options="emit('update:structuredOptions', $event)" @update:options="emit('update:structuredOptions', $event)"
/> />
@@ -220,9 +216,8 @@ defineExpose({ validate })
v-if="processType === 'unstructured'" v-if="processType === 'unstructured'"
ref="unstructuredOptionsPanelRef" ref="unstructuredOptionsPanelRef"
:options="unstructuredOptions" :options="unstructuredOptions"
:generation-models="generationModels"
:validation-attempted="validationAttempted" :validation-attempted="validationAttempted"
:generation-validation-message="generationValidationMessage" :quality-validation-message="qualityValidationMessage"
:chunk-validation-message="chunkValidationMessage" :chunk-validation-message="chunkValidationMessage"
@update:options="emit('update:unstructuredOptions', $event)" @update:options="emit('update:unstructuredOptions', $event)"
/> />

View File

@@ -1,6 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref, watch } from 'vue' import { computed, ref, watch } from 'vue'
import type { ModelItem } from '@/types'
import type { import type {
ChunkMethod, ChunkMethod,
GenerationControlOptions, GenerationControlOptions,
@@ -12,9 +11,8 @@ import GenerationOptionsPanel from './GenerationOptionsPanel.vue'
const props = defineProps<{ const props = defineProps<{
options: UnstructuredProcessOptions options: UnstructuredProcessOptions
generationModels: ModelItem[]
validationAttempted: boolean validationAttempted: boolean
generationValidationMessage: string qualityValidationMessage: string
chunkValidationMessage: string chunkValidationMessage: string
}>() }>()
@@ -269,9 +267,8 @@ defineExpose({ revealValidation })
<div class="generation-option-list"> <div class="generation-option-list">
<GenerationOptionsPanel <GenerationOptionsPanel
:options="options" :options="options"
:models="generationModels"
section="quality" section="quality"
:validation-message="validationAttempted ? generationValidationMessage : ''" :validation-message="validationAttempted ? qualityValidationMessage : ''"
@update:options="updateGenerationOptions" @update:options="updateGenerationOptions"
/> />
@@ -301,23 +298,6 @@ defineExpose({ revealValidation })
</div> </div>
</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> </template>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@@ -37,7 +37,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
max-width: 860px; max-width: 1040px;
margin: 0 auto; margin: 0 auto;
padding: 0 20px; padding: 0 20px;
} }
@@ -45,11 +45,7 @@
.step-item { .step-item {
display: flex; display: flex;
align-items: center; align-items: center;
flex: 1; flex: none;
&:first-child {
flex: 0;
}
} }
.step-connector { .step-connector {
@@ -60,14 +56,14 @@
transition: background-color 0.3s; transition: background-color 0.3s;
} }
.step-item.is-completed .step-connector, .step-connector.is-active {
.step-item.is-active .step-connector {
background-color: #5146e5; background-color: #5146e5;
} }
.step-node { .step-node {
display: flex; display: flex;
align-items: center; align-items: center;
flex-shrink: 0;
gap: 10px; gap: 10px;
} }
@@ -149,6 +145,30 @@
min-width: 160px; min-width: 160px;
} }
@media (max-width: 1100px) {
.custom-wizard-steps {
padding: 0;
}
.step-connector {
margin: 0 8px;
}
.step-node {
gap: 7px;
}
.step-title {
display: none;
}
.step-item.is-active .step-title {
display: block;
font-size: 12px;
white-space: nowrap;
}
}
@media (max-width: 760px) { @media (max-width: 760px) {
.wizard-main { .wizard-main {
padding: 24px 20px; padding: 24px 20px;
@@ -168,9 +188,7 @@
.step-title { .step-title {
max-width: 72px; max-width: 72px;
font-size: 12px;
line-height: 1.35; line-height: 1.35;
white-space: normal;
} }
.wizard-footer { .wizard-footer {

View File

@@ -1,6 +1,6 @@
export type ProcessType = 'structured' | 'unstructured' | 'external' export type ProcessType = 'structured' | 'unstructured' | 'external'
export type StepId = 'create' | 'upload' | 'preview' | 'generate' | 'results' export type StepId = 'create' | 'model' | 'upload' | 'preview' | 'generate' | 'results'
export type PreprocessOption = export type PreprocessOption =
| 'clean_invalid' | 'clean_invalid'