feat(data-process): 接入重新生成配置流程
This commit is contained in:
@@ -22,6 +22,7 @@ assert.match(routerSource, /path:\s*['"]data-process\/:id['"]/, '缺少数据处
|
||||
assert.match(routerSource, /name:\s*['"]data-process-detail['"]/, '数据处理详情路由缺少名称')
|
||||
assert.match(routerSource, /DataProcessDetailView\.vue/, '数据处理详情路由未加载详情页面')
|
||||
assert.match(routerSource, /title:\s*['"]数据处理详情['"]/, '数据处理详情路由标题不正确')
|
||||
assert.match(routerSource, /path:\s*['"]data-process\/:id\/regenerate['"][\s\S]*?name:\s*['"]data-process-regenerate['"][\s\S]*?DataProcessCreateView\.vue/, '重新生成路由未复用创建向导')
|
||||
|
||||
assert.match(listSource, /name:\s*['"]data-process-detail['"]/, '列表详情按钮未使用详情命名路由')
|
||||
assert.match(listSource, /params:\s*\{\s*id:\s*taskId\s*\}/, '列表详情按钮未传递任务 ID')
|
||||
@@ -62,7 +63,11 @@ assert.match(detailSource, /\.output-dataset-links[\s\S]*?\.el-button[\s\S]*?wid
|
||||
assert.match(detailSource, /\.el-button\s*>\s*span[\s\S]*?width:\s*100%[\s\S]*?display:\s*flex/, '输出数据集按钮内容没有撑满统一宽度')
|
||||
assert.match(detailSource, /\.el-button i[\s\S]*?margin-left:\s*auto/, '输出数据集跳转图标没有统一右对齐')
|
||||
assert.match(detailSource, /将发布三个独立数据集/, '发布说明仍未明确生成三个独立数据集')
|
||||
assert.match(detailSource, /outputDatasetBaseName\.value/, '重新同步时未使用单一基础名称')
|
||||
assert.match(detailSource, /function startRegeneration\(\)[\s\S]*?name: 'data-process-regenerate'[\s\S]*?params: \{ id: taskId\.value \}/, '重新生成按钮没有携带原任务 ID 进入命名路由')
|
||||
assert.match(detailSource, /const canRegenerate = computed\(\(\) => \{[\s\S]*?status === 'pending'[\s\S]*?status === 'failed'[\s\S]*?status === 'stopped'[\s\S]*?status === 'completed'[\s\S]*?outputDatasetId\.value/, '详情页没有覆盖可恢复的重新生成任务状态')
|
||||
assert.match(detailSource, /v-if="canRegenerate"[\s\S]*?@click="startRegeneration"[\s\S]*?重新生成/, '可恢复任务没有收敛为单一重新生成入口')
|
||||
assert.match(detailSource, /v-if="detail\.status === 'completed' && !outputDatasetId"[\s\S]*?@click="openPublishDialog"[\s\S]*?发布为三个数据集/, '未发布完成任务没有保留首次发布入口')
|
||||
assert.doesNotMatch(detailSource, /查看三个数据集|重新同步数据集|resyncPublishedDataset/, '已发布任务仍保留旧查看或同步按钮')
|
||||
assert.doesNotMatch(detailSource, /label="数据集类型"/, '发布三个数据集时不应再选择单一数据集类型')
|
||||
assert.match(detailSource, /无法加载数据处理任务/, '未知任务或加载失败缺少明确错误状态')
|
||||
assert.match(detailSource, /结果明细加载失败/, '结果加载失败缺少明确错误状态')
|
||||
|
||||
@@ -14,24 +14,28 @@ const apiPath = path.resolve(scriptDir, '../src/api/modules/dataProcess.ts')
|
||||
const contractTypesPath = path.resolve(scriptDir, '../src/types/dataProcess.ts')
|
||||
const requestPath = path.resolve(scriptDir, '../src/api/request.ts')
|
||||
const authStorePath = path.resolve(scriptDir, '../src/stores/auth.ts')
|
||||
const modelsStorePath = path.resolve(scriptDir, '../src/stores/models.ts')
|
||||
const routerPath = path.resolve(scriptDir, '../src/router/index.ts')
|
||||
const sessionActivityPath = path.resolve(scriptDir, '../src/utils/sessionActivity.ts')
|
||||
const sourceUploadWorkerPath = path.join(createDir, 'useDataProcessSourceUpload.ts')
|
||||
const regenerationPath = path.join(createDir, 'useDataProcessRegeneration.ts')
|
||||
const viewSource = await readFile(viewPath, 'utf8')
|
||||
const layoutSource = await readFile(layoutPath, 'utf8')
|
||||
const [stateSource, generationSource, previewBuildSource, sourceUploadWorkerSource, viewStyleSource, apiSource, contractTypesSource] = await Promise.all([
|
||||
const [stateSource, generationSource, previewBuildSource, sourceUploadWorkerSource, regenerationSource, viewStyleSource, apiSource, contractTypesSource] = await Promise.all([
|
||||
readFile(path.join(createDir, 'dataProcessCreateState.ts'), 'utf8'),
|
||||
readFile(path.join(createDir, 'useDataProcessGeneration.ts'), 'utf8'),
|
||||
readFile(path.join(createDir, 'useDataProcessPreviewBuild.ts'), 'utf8'),
|
||||
readFile(sourceUploadWorkerPath, 'utf8'),
|
||||
readFile(regenerationPath, 'utf8'),
|
||||
readFile(path.join(createDir, 'data-process-create.scss'), 'utf8'),
|
||||
readFile(apiPath, 'utf8'),
|
||||
readFile(contractTypesPath, 'utf8'),
|
||||
])
|
||||
const implementationSource = [viewSource, stateSource, generationSource, previewBuildSource, sourceUploadWorkerSource].join('\n')
|
||||
const [requestSource, authStoreSource, routerSource, sessionActivitySource] = await Promise.all([
|
||||
const implementationSource = [viewSource, stateSource, generationSource, previewBuildSource, sourceUploadWorkerSource, regenerationSource].join('\n')
|
||||
const [requestSource, authStoreSource, modelsStoreSource, routerSource, sessionActivitySource] = await Promise.all([
|
||||
readFile(requestPath, 'utf8'),
|
||||
readFile(authStorePath, 'utf8'),
|
||||
readFile(modelsStorePath, 'utf8'),
|
||||
readFile(routerPath, 'utf8'),
|
||||
readFile(sessionActivityPath, 'utf8'),
|
||||
])
|
||||
@@ -60,6 +64,9 @@ assert.match(viewSource, /import AppConfirmDialog from '@\/components\/AppConfir
|
||||
assert.match(viewSource, /<AppConfirmDialog/, '创建页模板缺少公共确认弹窗实例')
|
||||
assert.match(viewSource, /onBeforeRouteLeave\(async \(\) =>/, '路由离开确认没有改为异步公共弹窗流程')
|
||||
assert.doesNotMatch(viewSource, /window\.confirm|ElMessageBox/, '创建页仍在使用系统或 Element Plus 确认框')
|
||||
assert.match(routerSource, /path:\s*'data-process\/:id\/regenerate'[\s\S]*?name:\s*'data-process-regenerate'[\s\S]*?DataProcessCreateView\.vue/, '重新生成路由没有复用创建向导')
|
||||
assert.match(regenerationSource, /route\.name === 'data-process-regenerate'[\s\S]*?route\.params\.id/, '创建向导没有从路由参数识别重新生成来源任务')
|
||||
assert.match(viewSource, /const currentStep = ref\(0\)/, '重新生成必须从向导第一步开始')
|
||||
|
||||
assert.match(viewSource, /const WIZARD_STEPS = \[/, '向导步骤尚未改为固定常量')
|
||||
for (const title of ['创建任务', '大模型选择', '上传文件', '数据预览', '开始生成', '结果编辑与保存']) {
|
||||
@@ -257,6 +264,12 @@ assert.doesNotMatch(nextFromCreateSource, /uploadedFiles|buildDataProcessPreview
|
||||
assert.match(nextFromModelSource, /modelSelectionRef\.value\?\.validate\(\)/, '大模型选择步骤继续前没有校验模型配置')
|
||||
assert.match(nextFromModelSource, /createDataProcessTask\(taskPayload\(\)\)/, '大模型选择完成后没有通过真实 API 创建任务')
|
||||
assert.match(nextFromModelSource, /goToStep\('upload'\)/, '大模型选择完成后没有进入上传文件')
|
||||
assert.match(nextFromModelSource, /await modelsStore\.load\(true\)/, '提交任务前没有等待最新模型列表')
|
||||
assert.ok(nextFromModelSource.indexOf('await modelsStore.load(true)') < nextFromModelSource.indexOf('prepareRegeneration(taskPayload())'), '重新生成可能在模型列表校验前修改服务端')
|
||||
assert.ok(nextFromModelSource.indexOf('await modelsStore.load(true)') < nextFromModelSource.indexOf('updateDataProcessTask'), '普通任务可能在模型列表校验前修改服务端')
|
||||
assert.ok(nextFromModelSource.includes('模型列表加载失败,未提交任何修改'), '模型列表加载失败没有明确阻断提交')
|
||||
assert.ok(nextFromModelSource.includes('原任务使用的模型已删除'), '原任务模型已删除没有要求重新选择')
|
||||
assert.match(modelsStoreSource, /catch \{[\s\S]*?list\.value = \[\][\s\S]*?loaded\.value = false/, '模型列表请求失败后 loaded 状态仍可能错误保留')
|
||||
assert.match(nextFromUploadSource, /uploadedFiles\.value\.length === 0/, '上传步骤继续前没有校验源数据')
|
||||
assert.match(
|
||||
nextFromUploadSource,
|
||||
@@ -383,6 +396,7 @@ assert.doesNotMatch(generationSource, /createResults\(/, '生成 composable 仍
|
||||
|
||||
for (const apiName of [
|
||||
'createDataProcessTask',
|
||||
'regenerateDataProcessTask',
|
||||
'uploadDataProcessSourceFiles',
|
||||
'buildDataProcessPreview',
|
||||
'getDataProcessPreview',
|
||||
@@ -428,6 +442,9 @@ assert.match(sourceUploadWorkerSource, /pending\.status = 'ready'[\s\S]*?pending
|
||||
assert.match(viewSource, /failedUploads[\s\S]*?hasUnfinishedUploads[\s\S]*?buildPreviewsByFile/, '上传失败或未完成时没有阻断切分')
|
||||
assert.doesNotMatch(viewSource, /file\.sourceFileId \|\| file\.uid/, '切分或删除仍可能把本地临时 UID 当成后端文件 ID')
|
||||
assert.match(contractTypesSource, /expected_updated_at\?: string/, '编辑契约缺少乐观并发版本字段')
|
||||
assert.match(contractTypesSource, /export interface DataProcessRegeneratePayload extends DataProcessTaskCreatePayload[\s\S]*?expected_updated_at: string/, '重新生成 payload 的原任务并发版本必须为必填')
|
||||
assert.match(contractTypesSource, /export interface DataProcessRegenerateResult[\s\S]*?preview_invalidated: boolean[\s\S]*?published_outputs_preserved: boolean/, '重新生成响应缺少预览失效或已发布数据保留状态')
|
||||
assert.match(apiSource, /regenerateDataProcessTask[\s\S]*?\/regenerate`/, '重新生成 API 路径未接入')
|
||||
|
||||
for (const field of [
|
||||
'generationModelId',
|
||||
@@ -510,6 +527,10 @@ for (const removedField of ['contextScope', 'generationTypes', 'skipUnanswerable
|
||||
}
|
||||
|
||||
assert.match(taskSetupSource, /v-if="processType === 'unstructured'"/, '非结构化配置必须仅在非结构化数据类型下显示')
|
||||
assert.match(taskSetupSource, /processTypeLocked\?: boolean/, '任务配置缺少处理类型锁定状态')
|
||||
assert.match(taskSetupSource, /:disabled="processTypeLocked"/, '重新生成时处理类型选项没有禁用')
|
||||
assert.ok(taskSetupSource.includes('重新生成沿用原任务处理类型,不可修改'), '处理类型锁定缺少明确说明')
|
||||
assert.match(viewSource, /:process-type-locked="isRegeneration"/, '创建向导没有向第一步传递重新生成锁定状态')
|
||||
assert.ok(unstructuredOptionsSource.includes('智能预处理'), '简化后缺少智能预处理总开关')
|
||||
assert.ok(unstructuredOptionsSource.includes('敏感信息脱敏'), '简化后缺少脱敏开关')
|
||||
const expectedSmartPreprocessOptions = [
|
||||
@@ -668,14 +689,51 @@ assert.ok(
|
||||
const taskPayloadStart = viewSource.indexOf('function taskPayload()')
|
||||
const taskPayloadEnd = viewSource.indexOf('function externalPayload()', taskPayloadStart)
|
||||
const taskPayloadSource = viewSource.slice(taskPayloadStart, taskPayloadEnd)
|
||||
for (const marker of ['name: task.name.trim()', 'description: task.description.trim()', 'process_type: processType.value', 'config: toBackendConfig()']) {
|
||||
for (const marker of ['name: task.name.trim()', 'description: task.description.trim()', 'process_type: originalProcessType.value || processType.value', 'config: toBackendConfig()']) {
|
||||
assert.ok(taskPayloadSource.includes(marker), `任务创建 payload 缺少:${marker}`)
|
||||
}
|
||||
assert.match(stateSource, /Object\.prototype\.hasOwnProperty\.call\(config, key\)/, '配置反向映射没有区分缺失值与 false/0')
|
||||
assert.match(stateSource, /Number\.isFinite\(value\) \? value : fallback/, '配置反向映射没有保留合法数字 0')
|
||||
assert.match(stateSource, /createStructuredOptionsFromConfig/, '结构化配置缺少后端到表单的反向映射')
|
||||
assert.match(stateSource, /createUnstructuredOptionsFromConfig/, '非结构化配置缺少后端到表单的反向映射')
|
||||
assert.match(stateSource, /configValue<unknown>\(config, 'preprocess_options', \[\]\)/, '历史任务缺少预处理配置时必须按后端空列表语义回填')
|
||||
for (const [field, fallback] of [
|
||||
['preserve_tables', 'preserveTables'],
|
||||
['preserve_code_blocks', 'preserveCodeBlocks'],
|
||||
['preserve_lists', 'preserveLists'],
|
||||
]) {
|
||||
assert.match(
|
||||
stateSource,
|
||||
new RegExp(`configValue\\(\\s*config,\\s*'${field}',[\\s\\S]*?defaults\\.${fallback}`),
|
||||
`历史任务缺少 ${field} 时必须沿用表单默认值,同时保留显式 false`,
|
||||
)
|
||||
}
|
||||
assert.match(regenerationSource, /getDataProcessTask\(sourceTaskId\.value\)/, '重新生成没有加载原任务')
|
||||
assert.match(regenerationSource, /while \(true\)[\s\S]*?getDataProcessSourceContent[\s\S]*?has_more/, '重新生成没有分页加载完整源正文')
|
||||
assert.match(regenerationSource, /getDataProcessPreview\(taskId, \{ page: 1, page_size: 500 \}\)[\s\S]*?for \(let page = 2; page <= pages;/, '重新生成没有分页加载全部现有切片')
|
||||
assert.match(viewSource, /if \(hydrating\.value\) return/, '任务水合期间仍可能触发重置副作用')
|
||||
assert.match(regenerationSource, /currentSignature !== originalPreviewConfigSignature\.value[\s\S]*?currentSignature === confirmedPreviewConfigSignature\.value/, '切分变更确认没有按原签名和已确认签名去重')
|
||||
assert.match(regenerationSource, /if \(!bindings\.previewItems\.value\.length\) return true/, '没有现有切片时仍会弹出删除切片警告')
|
||||
assert.ok(regenerationSource.includes('删除现有切片和生成结果;已发布三数据集暂时保留'), '切分变更警告没有说明完整影响范围')
|
||||
assert.match(regenerationSource, /regenerateDataProcessTask\(sourceTaskId\.value,[\s\S]*?expected_updated_at: originalTaskUpdatedAt\.value/, '第二步没有携带最新乐观并发版本重新生成任务')
|
||||
assert.match(regenerationSource, /originalTaskUpdatedAt\.value = regenerated\.task\.updated_at/, '重新生成成功后没有更新下一次提交的并发版本')
|
||||
assert.match(regenerationSource, /if \(regenerationPrepared\.value\) \{[\s\S]*?getDataProcessTask\(sourceTaskId\.value\)[\s\S]*?originalTaskUpdatedAt\.value = latestTask\.updated_at/, '服务端中间操作后再次提交没有刷新任务并发版本')
|
||||
assert.match(regenerationSource, /regenerationPrepared\.value = true/, '重新生成提交成功后没有记录服务端已变更状态')
|
||||
assert.match(regenerationSource, /hydrateWorkspace\(regeneratedTask, !regenerated\.preview_invalidated\)/, '重新生成没有按 preview_invalidated 决定保留或清空切片')
|
||||
assert.match(regenerationSource, /任务已进入重新生成状态,但工作区恢复失败/, '重新生成已提交但水合失败时缺少可恢复错误状态')
|
||||
assert.match(regenerationSource, /return chunks\.join\(''\)/, '分页恢复源正文时不应额外插入换行')
|
||||
assert.doesNotMatch(regenerationSource, /binaryDocument[\s\S]*?mapDataProcessSourceFile\(file, ''\)/, '二进制源正文加载失败时不能静默降级为空内容')
|
||||
assert.match(nextFromModelSource, /if \(isRegeneration\.value\) \{[\s\S]*?prepareRegeneration\(taskPayload\(\)\)/, '重新生成每次从模型步骤继续时没有调用专用接口')
|
||||
assert.doesNotMatch(nextFromModelSource, /isRegeneration\.value && !taskId\.value/, '重新生成提交一次后可能错误转为普通任务更新')
|
||||
assert.ok(viewSource.includes('已提交到服务端的修改不会因离开页面而撤销'), '离开提示没有说明已提交服务端的修改不可撤销')
|
||||
assert.match(viewSource, /regenerationPrepared\.value \? '离开页面' : '放弃修改'/, '服务端已提交后离开按钮仍使用误导性的放弃修改文案')
|
||||
assert.match(viewSource, /v-if="initializationError"[\s\S]*?@click="loadRegenerationSource"[\s\S]*?重试加载原任务/, '原任务初始化失败后缺少安全重试入口')
|
||||
assert.match(regenerationSource, /previewStatus = preservePreviews && count > 0 \? 'success' : 'waiting'/, '保留切片时没有跳过重切,或切片失效后未回到等待状态')
|
||||
|
||||
const previewOptionsStart = viewSource.indexOf('function previewAffectingOptions()')
|
||||
const previewOptionsEnd = viewSource.indexOf('function generationAffectingOptions()', previewOptionsStart)
|
||||
const previewOptionsStart = stateSource.indexOf('export function previewAffectingOptionsFor(')
|
||||
const previewOptionsEnd = stateSource.indexOf('export function generationAffectingOptionsFor(', previewOptionsStart)
|
||||
assert.ok(previewOptionsStart >= 0 && previewOptionsEnd > previewOptionsStart, '缺少预览影响配置签名函数')
|
||||
const previewOptionsSource = viewSource.slice(previewOptionsStart, previewOptionsEnd)
|
||||
const previewOptionsSource = stateSource.slice(previewOptionsStart, previewOptionsEnd)
|
||||
for (const field of [
|
||||
'preprocessOptions',
|
||||
'chunkMethod',
|
||||
@@ -693,10 +751,10 @@ for (const field of ['semanticEnrichment', 'qaPairsPerChunk', 'datasetSplit']) {
|
||||
assert.ok(!previewOptionsSource.includes(field), `生成字段 ${field} 不应导致预览重建并丢失编辑`)
|
||||
}
|
||||
|
||||
const generationOptionsStart = viewSource.indexOf('function generationAffectingOptions()')
|
||||
const generationOptionsEnd = viewSource.indexOf('const generationOptionsSignature', generationOptionsStart)
|
||||
const generationOptionsStart = stateSource.indexOf('export function generationAffectingOptionsFor(')
|
||||
const generationOptionsEnd = stateSource.length
|
||||
assert.ok(generationOptionsStart >= 0 && generationOptionsEnd > generationOptionsStart, '缺少生成影响配置签名函数')
|
||||
const generationOptionsSource = viewSource.slice(generationOptionsStart, generationOptionsEnd)
|
||||
const generationOptionsSource = stateSource.slice(generationOptionsStart, generationOptionsEnd)
|
||||
for (const field of [
|
||||
'semanticEnrichment',
|
||||
'qaPairsPerChunk',
|
||||
|
||||
Reference in New Issue
Block a user