feat(data-process): 接入重新生成配置流程
This commit is contained in:
@@ -0,0 +1,232 @@
|
||||
import { computed, nextTick, ref, type Reactive, type Ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import {
|
||||
getDataProcessPreview,
|
||||
getDataProcessSourceContent,
|
||||
getDataProcessTask,
|
||||
regenerateDataProcessTask,
|
||||
} from '@/api/modules/dataProcess'
|
||||
import type {
|
||||
DataProcessPreviewItem,
|
||||
DataProcessRegeneratePayload,
|
||||
DataProcessTask,
|
||||
} from '@/types/dataProcess'
|
||||
import {
|
||||
createStructuredOptionsFromConfig,
|
||||
createUnstructuredOptionsFromConfig,
|
||||
} from './dataProcessCreateState'
|
||||
import { mapDataProcessSourceFile } from './useDataProcessSourceUpload'
|
||||
import type {
|
||||
PreviewItem,
|
||||
ProcessType,
|
||||
StructuredProcessOptions,
|
||||
UnstructuredProcessOptions,
|
||||
UploadedDataFile,
|
||||
} from './types'
|
||||
|
||||
interface ConfirmOptions {
|
||||
title: string
|
||||
message: string
|
||||
confirmText: string
|
||||
cancelText: string
|
||||
tone: 'warning'
|
||||
}
|
||||
|
||||
interface RegenerationBindings {
|
||||
task: Reactive<{ name: string; description: string }>
|
||||
processType: Ref<ProcessType>
|
||||
structuredOptions: Ref<StructuredProcessOptions>
|
||||
unstructuredOptions: Ref<UnstructuredProcessOptions>
|
||||
uploadedFiles: Ref<UploadedDataFile[]>
|
||||
previewItems: Ref<PreviewItem[]>
|
||||
selectedPreviewFileId: Ref<string | null>
|
||||
selectedPreviewId: Ref<string | null>
|
||||
selectedPreviewIdsByFile: Ref<Record<string, string>>
|
||||
previewSignature: Ref<string>
|
||||
dirty: Ref<boolean>
|
||||
buildPreviewConfigSignature: () => string
|
||||
buildPreviewSignature: () => string
|
||||
mapPreviewItem: (item: DataProcessPreviewItem) => PreviewItem
|
||||
resetDownstream: () => void
|
||||
}
|
||||
|
||||
async function loadSourceContent(taskId: string, fileId: string | number) {
|
||||
const chunks: string[] = []
|
||||
let startLine = 1
|
||||
while (true) {
|
||||
const source = await getDataProcessSourceContent(taskId, fileId, {
|
||||
start_line: startLine,
|
||||
line_count: 10_000,
|
||||
})
|
||||
chunks.push(source.content || '')
|
||||
if (!source.has_more) break
|
||||
const nextLine = Number(source.end_line || startLine) + 1
|
||||
if (nextLine <= startLine) break
|
||||
startLine = nextLine
|
||||
}
|
||||
// source_content_lines 已保留原始换行;分页之间直接拼接,避免凭空增加空行并破坏偏移。
|
||||
return chunks.join('')
|
||||
}
|
||||
|
||||
async function loadAllPreviews(taskId: string, mapPreviewItem: RegenerationBindings['mapPreviewItem']) {
|
||||
const first = await getDataProcessPreview(taskId, { page: 1, page_size: 500 })
|
||||
const items = [...first.items]
|
||||
const pages = Math.ceil(first.total / first.page_size)
|
||||
for (let page = 2; page <= pages; page += 1) {
|
||||
const next = await getDataProcessPreview(taskId, { page, page_size: 500 })
|
||||
items.push(...next.items)
|
||||
}
|
||||
return items.map(mapPreviewItem)
|
||||
}
|
||||
|
||||
export function useDataProcessRegeneration(bindings: RegenerationBindings) {
|
||||
const route = useRoute()
|
||||
const sourceTaskId = computed(() => (
|
||||
route.name === 'data-process-regenerate' ? String(route.params.id || '') : ''
|
||||
))
|
||||
const isRegeneration = computed(() => Boolean(sourceTaskId.value))
|
||||
const originalProcessType = ref<ProcessType | null>(null)
|
||||
const originalTaskUpdatedAt = ref('')
|
||||
const regenerationPrepared = ref(false)
|
||||
const originalPreviewConfigSignature = ref('')
|
||||
const confirmedPreviewConfigSignature = ref('')
|
||||
const hydrating = ref(false)
|
||||
const initializationError = ref('')
|
||||
|
||||
async function hydrateWorkspace(task: DataProcessTask, preservePreviews: boolean) {
|
||||
const taskId = String(task.id)
|
||||
bindings.uploadedFiles.value = await Promise.all((task.source_files || []).map(async (file) => (
|
||||
mapDataProcessSourceFile(file, await loadSourceContent(taskId, file.id))
|
||||
)))
|
||||
bindings.previewItems.value = preservePreviews
|
||||
? await loadAllPreviews(taskId, bindings.mapPreviewItem)
|
||||
: []
|
||||
|
||||
const configSignature = bindings.buildPreviewConfigSignature()
|
||||
const previewCounts = new Map<string, number>()
|
||||
for (const item of bindings.previewItems.value) {
|
||||
previewCounts.set(item.sourceFileId, (previewCounts.get(item.sourceFileId) || 0) + 1)
|
||||
}
|
||||
for (const file of bindings.uploadedFiles.value) {
|
||||
const count = previewCounts.get(String(file.sourceFileId)) || 0
|
||||
file.previewCount = count
|
||||
file.previewStatus = preservePreviews && count > 0 ? 'success' : 'waiting'
|
||||
file.previewProgress = preservePreviews && count > 0 ? 100 : 0
|
||||
file.previewConfigSignature = preservePreviews && count > 0 ? configSignature : undefined
|
||||
}
|
||||
|
||||
bindings.selectedPreviewFileId.value = String(bindings.uploadedFiles.value[0]?.uid ?? '') || null
|
||||
bindings.selectedPreviewId.value = bindings.selectedPreviewFileId.value
|
||||
? bindings.previewItems.value.find((item) => (
|
||||
item.sourceFileId === bindings.selectedPreviewFileId.value
|
||||
))?.id ?? null
|
||||
: null
|
||||
bindings.selectedPreviewIdsByFile.value = (
|
||||
bindings.selectedPreviewFileId.value && bindings.selectedPreviewId.value
|
||||
) ? { [bindings.selectedPreviewFileId.value]: bindings.selectedPreviewId.value } : {}
|
||||
bindings.previewSignature.value = preservePreviews && bindings.previewItems.value.length
|
||||
? bindings.buildPreviewSignature()
|
||||
: ''
|
||||
bindings.resetDownstream()
|
||||
}
|
||||
|
||||
async function loadSource() {
|
||||
if (!sourceTaskId.value) return
|
||||
hydrating.value = true
|
||||
initializationError.value = ''
|
||||
try {
|
||||
const sourceTask = await getDataProcessTask(sourceTaskId.value)
|
||||
const sourceType = sourceTask.process_type as ProcessType
|
||||
originalProcessType.value = sourceType
|
||||
originalTaskUpdatedAt.value = sourceTask.updated_at
|
||||
bindings.task.name = sourceTask.name
|
||||
bindings.task.description = sourceTask.description || ''
|
||||
bindings.processType.value = sourceType
|
||||
const config = sourceTask.config || {}
|
||||
bindings.structuredOptions.value = createStructuredOptionsFromConfig(config)
|
||||
bindings.unstructuredOptions.value = createUnstructuredOptionsFromConfig(config)
|
||||
originalPreviewConfigSignature.value = bindings.buildPreviewConfigSignature()
|
||||
confirmedPreviewConfigSignature.value = ''
|
||||
await hydrateWorkspace(sourceTask, true)
|
||||
await nextTick()
|
||||
bindings.dirty.value = false
|
||||
} catch (error) {
|
||||
initializationError.value = error instanceof Error
|
||||
? error.message
|
||||
: '原数据处理任务加载失败,请返回详情页后重试'
|
||||
} finally {
|
||||
hydrating.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmPreviewConfigChange(
|
||||
openConfirm: (options: ConfirmOptions) => Promise<boolean | undefined>,
|
||||
) {
|
||||
if (!isRegeneration.value) return true
|
||||
if (!bindings.previewItems.value.length) return true
|
||||
const currentSignature = bindings.buildPreviewConfigSignature()
|
||||
const changed = currentSignature !== originalPreviewConfigSignature.value
|
||||
if (!changed || currentSignature === confirmedPreviewConfigSignature.value) return true
|
||||
const confirmed = await openConfirm({
|
||||
title: '确认修改切分配置?',
|
||||
message: '修改预处理或切分配置将删除现有切片和生成结果;已发布三数据集暂时保留,直到重新发布后才会更新。',
|
||||
confirmText: '确认并继续',
|
||||
cancelText: '返回检查',
|
||||
tone: 'warning',
|
||||
})
|
||||
if (confirmed) confirmedPreviewConfigSignature.value = currentSignature
|
||||
return Boolean(confirmed)
|
||||
}
|
||||
|
||||
async function prepareRegeneration(
|
||||
payload: Omit<DataProcessRegeneratePayload, 'expected_updated_at'>,
|
||||
) {
|
||||
// 第一次 prepare 后,上传、切分、编辑预览和生成都会推进任务版本。
|
||||
// 再次提交前读取同一任务的最新版本,仍由后端事务处理读取后的并发竞争。
|
||||
if (regenerationPrepared.value) {
|
||||
const latestTask = await getDataProcessTask(sourceTaskId.value)
|
||||
originalTaskUpdatedAt.value = latestTask.updated_at || originalTaskUpdatedAt.value
|
||||
}
|
||||
const regenerated = await regenerateDataProcessTask(sourceTaskId.value, {
|
||||
...payload,
|
||||
expected_updated_at: originalTaskUpdatedAt.value,
|
||||
})
|
||||
originalTaskUpdatedAt.value = regenerated.task.updated_at || originalTaskUpdatedAt.value
|
||||
originalPreviewConfigSignature.value = bindings.buildPreviewConfigSignature()
|
||||
confirmedPreviewConfigSignature.value = ''
|
||||
regenerationPrepared.value = true
|
||||
bindings.dirty.value = true
|
||||
try {
|
||||
const regeneratedTask = regenerated.task.source_files
|
||||
? regenerated.task
|
||||
: await getDataProcessTask(regenerated.task.id)
|
||||
originalTaskUpdatedAt.value = regeneratedTask.updated_at || originalTaskUpdatedAt.value
|
||||
hydrating.value = true
|
||||
try {
|
||||
await hydrateWorkspace(regeneratedTask, !regenerated.preview_invalidated)
|
||||
} finally {
|
||||
hydrating.value = false
|
||||
}
|
||||
} catch (error) {
|
||||
initializationError.value = error instanceof Error
|
||||
? `任务已进入重新生成状态,但工作区恢复失败:${error.message}`
|
||||
: '任务已进入重新生成状态,但工作区恢复失败,请重试加载原任务'
|
||||
throw error
|
||||
}
|
||||
return regenerated
|
||||
}
|
||||
|
||||
return {
|
||||
sourceTaskId,
|
||||
isRegeneration,
|
||||
originalProcessType,
|
||||
originalTaskUpdatedAt,
|
||||
regenerationPrepared,
|
||||
hydrating,
|
||||
initializationError,
|
||||
hydrateWorkspace,
|
||||
loadSource,
|
||||
confirmPreviewConfigChange,
|
||||
prepareRegeneration,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user