refactor: 数据处理向导拆分组合式函数与子面板
提取 useDataProcessDraft、useDataProcessGeneration 与 dataProcessCreateState 管理向导状态,新增 StructuredOptionsPanel、UnstructuredOptionsPanel、DatasetSplitEditor 子面板组件,样式抽离为独立 scss,DataProcessCreateView 与 TaskSetupStep 大幅瘦身,回归脚本适配。
This commit is contained in:
@@ -9,17 +9,25 @@ import SourceUploadStep from './create/SourceUploadStep.vue'
|
||||
import PreviewCompareStep from './create/PreviewCompareStep.vue'
|
||||
import GenerationStep from './create/GenerationStep.vue'
|
||||
import ResultEditorStep from './create/ResultEditorStep.vue'
|
||||
import { buildPreviewItems, createResults, DEFAULT_SOURCE_TEXT } from './create/previewModel'
|
||||
import { buildPreviewItems, DEFAULT_SOURCE_TEXT } from './create/previewModel'
|
||||
import {
|
||||
createDefaultStructuredOptions,
|
||||
createDefaultUnstructuredOptions,
|
||||
} from './create/dataProcessCreateState'
|
||||
import {
|
||||
DATA_PROCESS_DRAFT_STORAGE_KEY,
|
||||
useDataProcessDraft,
|
||||
} from './create/useDataProcessDraft'
|
||||
import { useDataProcessGeneration } from './create/useDataProcessGeneration'
|
||||
import { useModelsStore } from '@/stores/models'
|
||||
import type {
|
||||
ExternalDataSource,
|
||||
GenerationState,
|
||||
PreviewItem,
|
||||
ProcessType,
|
||||
ResultItem,
|
||||
StepId,
|
||||
StructuredProcessOptions,
|
||||
UnstructuredProcessOptions,
|
||||
UploadedDataFile,
|
||||
} from './create/types'
|
||||
|
||||
const router = useRouter()
|
||||
@@ -28,10 +36,7 @@ const { list: modelList } = storeToRefs(modelsStore)
|
||||
const generationModels = computed(() => modelList.value.filter((model) => model.type === 'LLM'))
|
||||
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 = 5
|
||||
const PREVIEW_MODEL_VERSION = 'document-chunk-v2'
|
||||
const DEFAULT_GENERATION_PROMPT = '你是一名专业的数据生成助手。请根据输入内容生成准确、完整、可直接用于模型训练的问答数据。仅输出符合目标格式的内容,答案应事实清晰、语言自然,不要添加分析过程、说明或无关内容。'
|
||||
|
||||
const WIZARD_STEPS = [
|
||||
{ id: 'create', title: '创建任务', desc: '填写任务信息与处理配置' },
|
||||
@@ -40,65 +45,12 @@ const WIZARD_STEPS = [
|
||||
{ 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')
|
||||
const task = reactive({ name: '', description: '' })
|
||||
const processType = ref<ProcessType>('unstructured')
|
||||
const structuredOptions = ref<StructuredProcessOptions>({
|
||||
preprocessOptions: ['clean_invalid', 'detect_structure', 'deduplicate', 'normalize_format'],
|
||||
semanticEnrichment: false,
|
||||
qaPairsPerRow: 1,
|
||||
datasetSplit: { train: 80, validation: 10, test: 10 },
|
||||
generationModelId: '',
|
||||
generationPrompt: DEFAULT_GENERATION_PROMPT,
|
||||
temperature: 0.7,
|
||||
maxTokens: 1024,
|
||||
jsonMode: false,
|
||||
qualityFilterEnabled: false,
|
||||
filterLowQuality: true,
|
||||
filterShortContent: true,
|
||||
minOutputLength: 20,
|
||||
})
|
||||
const unstructuredOptions = ref<UnstructuredProcessOptions>({
|
||||
preprocessOptions: [
|
||||
'clean_invalid_content',
|
||||
'detect_document_structure',
|
||||
'merge_short_content',
|
||||
'filter_low_quality',
|
||||
'deduplicate_content',
|
||||
'preserve_context',
|
||||
],
|
||||
chunkMethod: 'semantic',
|
||||
chunkSize: 800,
|
||||
chunkOverlap: 100,
|
||||
minChunkSize: 100,
|
||||
customDelimiter: '',
|
||||
preserveTables: true,
|
||||
preserveCodeBlocks: true,
|
||||
preserveLists: true,
|
||||
semanticEnrichment: false,
|
||||
qaPairsPerChunk: 1,
|
||||
datasetSplit: { train: 80, validation: 10, test: 10 },
|
||||
generationModelId: '',
|
||||
generationPrompt: DEFAULT_GENERATION_PROMPT,
|
||||
temperature: 0.7,
|
||||
maxTokens: 1024,
|
||||
jsonMode: false,
|
||||
qualityFilterEnabled: false,
|
||||
filterLowQuality: true,
|
||||
filterShortContent: true,
|
||||
minOutputLength: 20,
|
||||
})
|
||||
interface UploadedDataFile {
|
||||
uid: number | string
|
||||
name: string
|
||||
size: number
|
||||
count: number
|
||||
content: string
|
||||
}
|
||||
|
||||
const structuredOptions = ref<StructuredProcessOptions>(createDefaultStructuredOptions())
|
||||
const unstructuredOptions = ref<UnstructuredProcessOptions>(createDefaultUnstructuredOptions())
|
||||
const uploadedFiles = ref<UploadedDataFile[]>([])
|
||||
|
||||
const externalSource = reactive<ExternalDataSource>({
|
||||
@@ -112,35 +64,53 @@ const externalSource = reactive<ExternalDataSource>({
|
||||
})
|
||||
const externalPulling = ref(false)
|
||||
const externalConnected = ref(false)
|
||||
let connectionTimer: ReturnType<typeof setTimeout> | null = null
|
||||
let pullTimer: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
const fileName = computed(() => uploadedFiles.value.map(f => f.name).join(', '))
|
||||
const fileSize = computed(() => uploadedFiles.value.reduce((sum, f) => sum + f.size, 0))
|
||||
const fileCount = computed(() => uploadedFiles.value.reduce((sum, f) => sum + f.count, 0))
|
||||
const previewSignature = ref('')
|
||||
const previewItems = ref<PreviewItem[]>([])
|
||||
const selectedPreviewFileId = ref<string | null>(null)
|
||||
const selectedPreviewId = ref<string | null>(null)
|
||||
const selectedPreviewIdsByFile = ref<Record<string, string>>({})
|
||||
const results = ref<ResultItem[]>([])
|
||||
const selectedResultId = ref<string | null>(null)
|
||||
const dirty = ref(false)
|
||||
const restoringDraft = ref(false)
|
||||
let allowLeave = false
|
||||
|
||||
const generation = reactive<GenerationState>({
|
||||
status: 'idle',
|
||||
progress: 0,
|
||||
message: '确认摘要后即可开始生成,过程中可查看实时进度。',
|
||||
const {
|
||||
generation,
|
||||
results,
|
||||
selectedResultId,
|
||||
resetDownstream,
|
||||
restoreResult,
|
||||
startGeneration,
|
||||
stopGeneration,
|
||||
stopGenerationTimer,
|
||||
updateResultField,
|
||||
validateResults,
|
||||
} = useDataProcessGeneration({
|
||||
previewItems,
|
||||
processType,
|
||||
structuredOptions,
|
||||
unstructuredOptions,
|
||||
dirty,
|
||||
})
|
||||
|
||||
let generationTimer: ReturnType<typeof setInterval> | null = null
|
||||
|
||||
const modifiedPreviewCount = computed(() => previewItems.value.filter((item) => item.status !== 'original').length)
|
||||
const previewItemsByFile = computed(() => {
|
||||
const grouped = new Map<string, PreviewItem[]>()
|
||||
for (const item of previewItems.value) {
|
||||
const items = grouped.get(item.sourceFileId) || []
|
||||
items.push(item)
|
||||
grouped.set(item.sourceFileId, items)
|
||||
}
|
||||
return grouped
|
||||
})
|
||||
const activePreviewFile = computed(() => uploadedFiles.value.find((file) => String(file.uid) === selectedPreviewFileId.value))
|
||||
const activePreviewItems = computed(() => previewItems.value.filter((item) => item.sourceFileId === selectedPreviewFileId.value))
|
||||
const activePreviewItems = computed(() => previewItemsByFile.value.get(selectedPreviewFileId.value || '') || [])
|
||||
const activeSourceText = computed(() => activePreviewFile.value?.content ?? '')
|
||||
const previewFiles = computed(() => uploadedFiles.value.map((file) => {
|
||||
const items = previewItems.value.filter((item) => item.sourceFileId === String(file.uid))
|
||||
const items = previewItemsByFile.value.get(String(file.uid)) || []
|
||||
return {
|
||||
id: String(file.uid),
|
||||
name: file.name,
|
||||
@@ -169,44 +139,22 @@ 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: {
|
||||
...structuredOptions.value,
|
||||
preprocessOptions: [...structuredOptions.value.preprocessOptions],
|
||||
datasetSplit: { ...structuredOptions.value.datasetSplit },
|
||||
},
|
||||
unstructuredOptions: {
|
||||
...unstructuredOptions.value,
|
||||
preprocessOptions: [...unstructuredOptions.value.preprocessOptions],
|
||||
datasetSplit: { ...unstructuredOptions.value.datasetSplit },
|
||||
},
|
||||
uploadedFiles: uploadedFiles.value,
|
||||
externalSource: { ...externalSource },
|
||||
previewSignature: previewSignature.value,
|
||||
previewItems: previewItems.value,
|
||||
selectedPreviewFileId: selectedPreviewFileId.value,
|
||||
selectedPreviewId: selectedPreviewId.value,
|
||||
selectedPreviewIdsByFile: selectedPreviewIdsByFile.value,
|
||||
results: results.value,
|
||||
selectedResultId: selectedResultId.value,
|
||||
generation: { ...generation },
|
||||
}
|
||||
}
|
||||
const { persistDraft, restoreDraft } = useDataProcessDraft({
|
||||
currentStepId,
|
||||
task,
|
||||
processType,
|
||||
structuredOptions,
|
||||
unstructuredOptions,
|
||||
externalSource,
|
||||
restoringDraft,
|
||||
dirty,
|
||||
goToStep,
|
||||
})
|
||||
|
||||
function previewAffectingOptions() {
|
||||
if (processType.value === 'structured') {
|
||||
@@ -291,191 +239,11 @@ const generationOptionsSignature = computed(() => JSON.stringify(generationAffec
|
||||
|
||||
function buildPreviewSignature() {
|
||||
const filesSignature = uploadedFiles.value
|
||||
.map((file) => `${file.uid}:${file.content}`)
|
||||
.map((file) => `${file.uid}:${file.name}:${file.size}:${file.count}`)
|
||||
.join('|')
|
||||
return `${PREVIEW_MODEL_VERSION}:${processType.value}:${JSON.stringify(previewAffectingOptions())}:${filesSignature}`
|
||||
}
|
||||
|
||||
function persistDraft() {
|
||||
if (restoringDraft.value) return
|
||||
try {
|
||||
localStorage.setItem(DRAFT_STORAGE_KEY, JSON.stringify(draftSnapshot()))
|
||||
} catch {
|
||||
ElMessage.warning('草稿保存失败,请检查浏览器存储空间')
|
||||
}
|
||||
}
|
||||
|
||||
type DraftSnapshot = Partial<ReturnType<typeof draftSnapshot>> & {
|
||||
fileName?: string
|
||||
fileSize?: number
|
||||
fileCount?: number
|
||||
sourceText?: string
|
||||
}
|
||||
|
||||
function restoreDraft() {
|
||||
try {
|
||||
const raw = localStorage.getItem(DRAFT_STORAGE_KEY)
|
||||
if (!raw) return
|
||||
const snapshot = JSON.parse(raw) as DraftSnapshot
|
||||
if (!snapshot?.uploadedFiles && (!snapshot?.fileName || !snapshot?.sourceText)) return
|
||||
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
|
||||
goToStep(requiresPreviewMigration ? 'create' : restoredStepId)
|
||||
task.name = snapshot.task?.name || ''
|
||||
task.description = snapshot.task?.description || ''
|
||||
processType.value = snapshot.processType === 'structured' || snapshot.processType === 'external'
|
||||
? snapshot.processType
|
||||
: 'unstructured'
|
||||
if (snapshot.structuredOptions) {
|
||||
structuredOptions.value = {
|
||||
...structuredOptions.value,
|
||||
...snapshot.structuredOptions,
|
||||
preprocessOptions: Array.isArray(snapshot.structuredOptions.preprocessOptions)
|
||||
? snapshot.structuredOptions.preprocessOptions
|
||||
: structuredOptions.value.preprocessOptions,
|
||||
datasetSplit: {
|
||||
...structuredOptions.value.datasetSplit,
|
||||
...snapshot.structuredOptions.datasetSplit,
|
||||
},
|
||||
}
|
||||
}
|
||||
if (snapshot.unstructuredOptions) {
|
||||
const restoredUnstructuredOptions = snapshot.unstructuredOptions
|
||||
const restoredDatasetSplit = restoredUnstructuredOptions.datasetSplit
|
||||
unstructuredOptions.value = {
|
||||
...unstructuredOptions.value,
|
||||
preprocessOptions: Array.isArray(restoredUnstructuredOptions.preprocessOptions)
|
||||
? restoredUnstructuredOptions.preprocessOptions
|
||||
: unstructuredOptions.value.preprocessOptions,
|
||||
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,
|
||||
generationModelId: restoredUnstructuredOptions.generationModelId ?? unstructuredOptions.value.generationModelId,
|
||||
generationPrompt: typeof restoredUnstructuredOptions.generationPrompt === 'string'
|
||||
? restoredUnstructuredOptions.generationPrompt
|
||||
: unstructuredOptions.value.generationPrompt,
|
||||
temperature: Number.isFinite(restoredUnstructuredOptions.temperature)
|
||||
? restoredUnstructuredOptions.temperature
|
||||
: unstructuredOptions.value.temperature,
|
||||
maxTokens: Number.isFinite(restoredUnstructuredOptions.maxTokens)
|
||||
? restoredUnstructuredOptions.maxTokens
|
||||
: unstructuredOptions.value.maxTokens,
|
||||
jsonMode: typeof restoredUnstructuredOptions.jsonMode === 'boolean'
|
||||
? restoredUnstructuredOptions.jsonMode
|
||||
: unstructuredOptions.value.jsonMode,
|
||||
qualityFilterEnabled: typeof restoredUnstructuredOptions.qualityFilterEnabled === 'boolean'
|
||||
? restoredUnstructuredOptions.qualityFilterEnabled
|
||||
: unstructuredOptions.value.qualityFilterEnabled,
|
||||
filterLowQuality: typeof restoredUnstructuredOptions.filterLowQuality === 'boolean'
|
||||
? restoredUnstructuredOptions.filterLowQuality
|
||||
: unstructuredOptions.value.filterLowQuality,
|
||||
filterShortContent: typeof restoredUnstructuredOptions.filterShortContent === 'boolean'
|
||||
? restoredUnstructuredOptions.filterShortContent
|
||||
: unstructuredOptions.value.filterShortContent,
|
||||
minOutputLength: Number.isFinite(restoredUnstructuredOptions.minOutputLength)
|
||||
? restoredUnstructuredOptions.minOutputLength
|
||||
: unstructuredOptions.value.minOutputLength,
|
||||
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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (snapshot.uploadedFiles) {
|
||||
uploadedFiles.value = Array.isArray(snapshot.uploadedFiles) ? snapshot.uploadedFiles : []
|
||||
} else if (snapshot.fileName && snapshot.sourceText) {
|
||||
// Migrate old draft
|
||||
uploadedFiles.value = [{
|
||||
uid: 'migrated-draft',
|
||||
name: snapshot.fileName,
|
||||
size: Number(snapshot.fileSize) || 0,
|
||||
count: Number(snapshot.fileCount) || 0,
|
||||
content: snapshot.sourceText
|
||||
}]
|
||||
}
|
||||
|
||||
previewSignature.value = requiresPreviewMigration ? '' : snapshot.previewSignature || ''
|
||||
if (snapshot.externalSource) {
|
||||
Object.assign(externalSource, snapshot.externalSource)
|
||||
}
|
||||
const defaultSourceFileId = String(uploadedFiles.value[0]?.uid ?? '')
|
||||
previewItems.value = !requiresPreviewMigration && Array.isArray(snapshot.previewItems)
|
||||
? snapshot.previewItems.map((item) => ({ ...item, sourceFileId: item.sourceFileId ?? defaultSourceFileId }))
|
||||
: []
|
||||
selectedPreviewFileId.value = snapshot.selectedPreviewFileId || defaultSourceFileId || null
|
||||
selectedPreviewIdsByFile.value = requiresPreviewMigration ? {} : snapshot.selectedPreviewIdsByFile || {}
|
||||
selectedPreviewId.value = requiresPreviewMigration
|
||||
? null
|
||||
: snapshot.selectedPreviewId
|
||||
|| selectedPreviewIdsByFile.value[selectedPreviewFileId.value ?? '']
|
||||
|| activePreviewItems.value[0]?.id
|
||||
|| null
|
||||
results.value = !requiresPreviewMigration && Array.isArray(snapshot.results) ? snapshot.results : []
|
||||
selectedResultId.value = requiresPreviewMigration ? null : snapshot.selectedResultId || results.value[0]?.id || null
|
||||
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
|
||||
? '已恢复任务信息,切分规则已更新,请重新生成预览'
|
||||
: '已恢复上次保存的草稿')
|
||||
} catch {
|
||||
localStorage.removeItem(DRAFT_STORAGE_KEY)
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
[() => task.name, () => task.description, processType, structuredOptions, unstructuredOptions, externalSource],
|
||||
() => {
|
||||
@@ -496,9 +264,7 @@ watch(generationOptionsSignature, (currentSignature, previousSignature) => {
|
||||
})
|
||||
|
||||
watch(
|
||||
[currentStep, task, processType, structuredOptions, unstructuredOptions, uploadedFiles, externalSource, previewSignature,
|
||||
previewItems, selectedPreviewFileId, selectedPreviewId, selectedPreviewIdsByFile,
|
||||
results, selectedResultId, generation],
|
||||
[task, processType, structuredOptions, unstructuredOptions, externalSource],
|
||||
persistDraft,
|
||||
{ deep: true },
|
||||
)
|
||||
@@ -566,8 +332,10 @@ function handleTestConnection() {
|
||||
ElMessage.warning('请先填写数据源地址')
|
||||
return
|
||||
}
|
||||
if (connectionTimer) clearTimeout(connectionTimer)
|
||||
externalPulling.value = true
|
||||
setTimeout(() => {
|
||||
connectionTimer = setTimeout(() => {
|
||||
connectionTimer = null
|
||||
externalPulling.value = false
|
||||
externalConnected.value = true
|
||||
ElMessage.success('数据源连接测试成功')
|
||||
@@ -579,8 +347,10 @@ function handlePullData() {
|
||||
ElMessage.warning('请先填写数据源地址')
|
||||
return
|
||||
}
|
||||
if (pullTimer) clearTimeout(pullTimer)
|
||||
externalPulling.value = true
|
||||
setTimeout(() => {
|
||||
pullTimer = setTimeout(() => {
|
||||
pullTimer = null
|
||||
externalPulling.value = false
|
||||
externalConnected.value = true
|
||||
const typeName = externalSource.type.toUpperCase()
|
||||
@@ -620,15 +390,6 @@ function resetSourceDataForProcessTypeChange() {
|
||||
resetDownstream()
|
||||
}
|
||||
|
||||
function resetDownstream() {
|
||||
stopGenerationTimer()
|
||||
generation.status = 'idle'
|
||||
generation.progress = 0
|
||||
generation.message = '确认摘要后即可开始生成,过程中可查看实时进度。'
|
||||
results.value = []
|
||||
selectedResultId.value = null
|
||||
}
|
||||
|
||||
async function nextFromCreate() {
|
||||
const valid = await taskSetupRef.value?.validate()
|
||||
if (!valid) return
|
||||
@@ -738,81 +499,6 @@ async function removePreviewItem(id: string) {
|
||||
dirty.value = true
|
||||
}
|
||||
|
||||
function stopGenerationTimer() {
|
||||
if (generationTimer) clearInterval(generationTimer)
|
||||
generationTimer = null
|
||||
}
|
||||
|
||||
function startGeneration() {
|
||||
stopGenerationTimer()
|
||||
generation.status = 'running'
|
||||
generation.progress = 0
|
||||
generation.message = '正在应用预览修改并生成标准化结果,请稍候。'
|
||||
|
||||
generationTimer = setInterval(() => {
|
||||
generation.progress = Math.min(100, generation.progress + 8)
|
||||
if (generation.progress < 100) return
|
||||
|
||||
stopGenerationTimer()
|
||||
generation.status = 'success'
|
||||
results.value = createResults(
|
||||
previewItems.value,
|
||||
processType.value === 'structured'
|
||||
? structuredOptions.value
|
||||
: processType.value === 'unstructured'
|
||||
? unstructuredOptions.value
|
||||
: undefined,
|
||||
)
|
||||
generation.message = `已完成 ${results.value.length.toLocaleString()} 条数据处理,可进入结果页检查。`
|
||||
selectedResultId.value = results.value[0]?.id ?? null
|
||||
dirty.value = true
|
||||
ElMessage.success('数据处理完成')
|
||||
}, 180)
|
||||
}
|
||||
|
||||
function stopGeneration() {
|
||||
stopGenerationTimer()
|
||||
generation.status = 'failed'
|
||||
generation.message = '任务已停止,预览修改仍然保留,可以重新生成。'
|
||||
}
|
||||
|
||||
function updateResultField(id: string, field: 'instruction' | 'input' | 'output', value: string) {
|
||||
const item = results.value.find((entry) => entry.id === id)
|
||||
if (!item) return
|
||||
item[field] = value
|
||||
const valid = item.instruction.trim() && item.output.trim()
|
||||
item.error = valid ? undefined : 'Instruction 和 Output 不能为空'
|
||||
const changed = item.instruction !== item.originalInstruction
|
||||
|| item.input !== item.originalInput
|
||||
|| item.output !== item.originalOutput
|
||||
item.status = item.error ? 'invalid' : changed ? 'modified' : 'valid'
|
||||
dirty.value = true
|
||||
}
|
||||
|
||||
function restoreResult(id: string) {
|
||||
const item = results.value.find((entry) => entry.id === id)
|
||||
if (!item) return
|
||||
item.instruction = item.originalInstruction
|
||||
item.input = item.originalInput
|
||||
item.output = item.originalOutput
|
||||
item.error = undefined
|
||||
item.status = 'valid'
|
||||
dirty.value = true
|
||||
}
|
||||
|
||||
function validateResults() {
|
||||
let firstInvalidId: string | null = null
|
||||
for (const item of results.value) {
|
||||
if (!item.instruction.trim() || !item.output.trim()) {
|
||||
item.error = 'Instruction 和 Output 不能为空'
|
||||
item.status = 'invalid'
|
||||
firstInvalidId ??= item.id
|
||||
}
|
||||
}
|
||||
if (firstInvalidId) selectedResultId.value = firstInvalidId
|
||||
return firstInvalidId == null
|
||||
}
|
||||
|
||||
async function handlePrimaryAction() {
|
||||
if (currentStepId.value === 'create') {
|
||||
await nextFromCreate()
|
||||
@@ -856,7 +542,7 @@ async function saveTask() {
|
||||
return
|
||||
}
|
||||
dirty.value = false
|
||||
localStorage.removeItem(DRAFT_STORAGE_KEY)
|
||||
localStorage.removeItem(DATA_PROCESS_DRAFT_STORAGE_KEY)
|
||||
allowLeave = true
|
||||
ElMessage.success('数据处理任务已保存')
|
||||
await router.push('/data-process')
|
||||
@@ -893,7 +579,11 @@ onBeforeRouteLeave(async () => {
|
||||
return Boolean(confirmed)
|
||||
})
|
||||
|
||||
onBeforeUnmount(stopGenerationTimer)
|
||||
onBeforeUnmount(() => {
|
||||
stopGenerationTimer()
|
||||
if (connectionTimer) clearTimeout(connectionTimer)
|
||||
if (pullTimer) clearTimeout(pullTimer)
|
||||
})
|
||||
onMounted(() => {
|
||||
restoreDraft()
|
||||
modelsStore.load()
|
||||
@@ -1023,198 +713,4 @@ onMounted(() => {
|
||||
<AppConfirmDialog ref="confirmDialogRef" />
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.create-wizard-layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
border: 1px solid #eef0f5;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.wizard-main {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 32px;
|
||||
|
||||
/* 自定义滚动条 */
|
||||
&::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #cbd5e1;
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.wizard-main-inner {
|
||||
min-height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.wizard-steps-container {
|
||||
margin-bottom: 32px;
|
||||
padding-bottom: 24px;
|
||||
border-bottom: 1px dashed #e2e8f0;
|
||||
}
|
||||
|
||||
.custom-wizard-steps {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
max-width: 860px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.step-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
|
||||
&:first-child {
|
||||
flex: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.step-connector {
|
||||
flex: 1;
|
||||
height: 2px;
|
||||
background-color: #e2e8f0;
|
||||
margin: 0 16px;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.step-item.is-completed .step-connector,
|
||||
.step-item.is-active .step-connector {
|
||||
background-color: #5146e5;
|
||||
}
|
||||
|
||||
.step-node {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.step-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid #cbd5e1;
|
||||
background-color: #fff;
|
||||
color: #64748b;
|
||||
font-size: 13px;
|
||||
font-weight: 650;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.step-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #64748b;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* State: Active */
|
||||
.step-item.is-active {
|
||||
.step-icon {
|
||||
border-color: #5146e5;
|
||||
background-color: #eef2ff;
|
||||
color: #5146e5;
|
||||
}
|
||||
.step-title {
|
||||
color: #1e293b;
|
||||
}
|
||||
}
|
||||
|
||||
/* State: Completed */
|
||||
.step-item.is-completed {
|
||||
.step-icon {
|
||||
background-color: #5146e5;
|
||||
border-color: #5146e5;
|
||||
color: #fff;
|
||||
}
|
||||
.step-title {
|
||||
color: #1e293b;
|
||||
}
|
||||
}
|
||||
|
||||
.wizard-content {
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.wizard-footer {
|
||||
flex-shrink: 0;
|
||||
height: 64px;
|
||||
background: #ffffff;
|
||||
border-top: 1px solid #e2e8f0;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto 1fr;
|
||||
align-items: center;
|
||||
padding: 0 32px;
|
||||
box-shadow: 0 -4px 6px -1px rgba(0, 0, 0, 0.02);
|
||||
|
||||
.footer-left {
|
||||
justify-self: start;
|
||||
}
|
||||
|
||||
.footer-center {
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.footer-right {
|
||||
justify-self: end;
|
||||
}
|
||||
}
|
||||
|
||||
.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>
|
||||
<style scoped lang="scss" src="./create/data-process-create.scss"></style>
|
||||
|
||||
Reference in New Issue
Block a user