refactor: 数据处理向导拆分源数据上传步骤

将任务设置中的文件上传与外来数据源拉取抽离为独立 SourceUploadStep 组件,TaskSetupStep 聚焦任务信息与处理配置,CreateView 同步接入新步骤与草稿同步,回归脚本补充上传步骤断言。
This commit is contained in:
caoxiaozhu
2026-07-12 15:39:43 +08:00
parent fecaba040b
commit c0f5f4a30a
5 changed files with 1057 additions and 750 deletions

View File

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

View File

@@ -0,0 +1,565 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import type { UploadFile } from 'element-plus'
import type { ExternalDataSource, ProcessType } from './types'
interface UploadedSourceFile {
uid: string | number
name: string
size: number
count: number
}
const props = defineProps<{
processType: ProcessType
uploadedFiles: UploadedSourceFile[]
externalSource: ExternalDataSource
externalPulling: boolean
externalConnected: boolean
}>()
const emit = defineEmits<{
'update:externalSource': [value: ExternalDataSource]
'file-change': [file: UploadFile]
'remove-file': [uid: string | number]
'use-sample': []
'test-connection': []
'pull-data': []
}>()
const DATA_SOURCE_TYPES = [
{ value: 'mysql', label: 'MySQL' },
{ value: 'postgresql', label: 'PostgreSQL' },
{ value: 'mongodb', label: 'MongoDB' },
{ value: 'api', label: 'REST API' },
]
const AUTH_MODES = [
{ value: 'none', label: '免鉴权' },
{ value: 'basic', label: '账号密码' },
{ value: 'token', label: 'Token' },
]
const FILE_PAGE_SIZE = 10
const currentFilePage = ref(1)
const isExternal = computed(() => props.processType === 'external')
const uploadAccept = computed(() => props.processType === 'unstructured'
? '.txt,.md,.pdf,.docx,.doc,.json,.jsonl'
: '.json,.jsonl,.csv,.xlsx,.xls')
const pagedUploadedFiles = computed(() => {
const start = (currentFilePage.value - 1) * FILE_PAGE_SIZE
return props.uploadedFiles.slice(start, start + FILE_PAGE_SIZE)
})
watch(() => props.uploadedFiles.length, (newLength, oldLength) => {
const totalPages = Math.max(1, Math.ceil(newLength / FILE_PAGE_SIZE))
if (newLength > oldLength) {
currentFilePage.value = totalPages
return
}
currentFilePage.value = Math.min(currentFilePage.value, totalPages)
})
watch(() => props.processType, () => {
currentFilePage.value = 1
})
function updateExternalField<K extends keyof ExternalDataSource>(field: K, value: ExternalDataSource[K]) {
emit('update:externalSource', { ...props.externalSource, [field]: value })
}
function formatSize(size: number) {
if (!size) return '0 KB'
if (size >= 1024 * 1024) return `${(size / 1024 / 1024).toFixed(1)} MB`
return `${(size / 1024).toFixed(1)} KB`
}
</script>
<template>
<section class="source-upload-step" aria-labelledby="source-upload-title">
<div v-if="isExternal" class="form-section external-section">
<div class="section-title-row">
<div>
<h3 id="source-upload-title">数据源配置</h3>
<p>配置并验证外部数据源拉取成功后可在下一步预览数据内容</p>
</div>
</div>
<div class="external-form">
<el-form label-position="top" class="external-grid">
<el-form-item label="数据源类型">
<el-select
:model-value="externalSource.type"
placeholder="请选择数据源类型"
aria-label="数据源类型"
@update:model-value="updateExternalField('type', $event)"
>
<el-option
v-for="item in DATA_SOURCE_TYPES"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="地址 / URL">
<el-input
:model-value="externalSource.url"
placeholder="例如mysql://host:3306/db 或 https://api.example.com/data"
aria-label="数据源地址或 URL"
@update:model-value="updateExternalField('url', $event)"
/>
</el-form-item>
<el-form-item label="鉴权方式">
<el-select
:model-value="externalSource.authMode"
aria-label="鉴权方式"
@update:model-value="updateExternalField('authMode', $event)"
>
<el-option
v-for="item in AUTH_MODES"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item v-if="externalSource.authMode === 'basic'" label="账号">
<el-input
:model-value="externalSource.username"
autocomplete="username"
placeholder="请输入账号"
aria-label="数据源账号"
@update:model-value="updateExternalField('username', $event)"
/>
</el-form-item>
<el-form-item v-if="externalSource.authMode === 'basic'" label="密码">
<el-input
:model-value="externalSource.password"
type="password"
show-password
autocomplete="current-password"
placeholder="请输入密码"
aria-label="数据源密码"
@update:model-value="updateExternalField('password', $event)"
/>
</el-form-item>
<el-form-item v-if="externalSource.authMode === 'token'" label="Token">
<el-input
:model-value="externalSource.token"
type="password"
show-password
autocomplete="off"
placeholder="请输入访问 Token"
aria-label="数据源访问 Token"
@update:model-value="updateExternalField('token', $event)"
/>
</el-form-item>
<el-form-item label="拉取条数">
<el-input-number
:model-value="externalSource.limit"
:min="1"
:max="100000"
:step="100"
controls-position="right"
aria-label="数据拉取条数"
@update:model-value="updateExternalField('limit', Number($event) || 0)"
/>
</el-form-item>
</el-form>
<div class="external-actions">
<el-button
:loading="externalPulling && !externalConnected"
:disabled="externalPulling"
plain
@click="emit('test-connection')"
>
测试连接
</el-button>
<el-button
type="primary"
:loading="externalPulling"
:disabled="externalPulling"
@click="emit('pull-data')"
>
拉取数据
</el-button>
<span v-if="externalConnected" class="external-status is-connected" role="status">
<i class="fa fa-check-circle" aria-hidden="true" /> 连接正常
</span>
</div>
<section v-if="uploadedFiles.length" class="uploaded-file-list" aria-label="已拉取数据列表">
<div class="uploaded-file-list-header">
<span>已拉取 {{ uploadedFiles.length }} 个数据集</span>
</div>
<div class="uploaded-file-items">
<div v-for="file in pagedUploadedFiles" :key="file.uid" class="uploaded-file">
<span class="file-icon"><i class="fa fa-cloud-download" aria-hidden="true" /></span>
<div class="file-main">
<strong :title="file.name">{{ file.name }}</strong>
<span>
{{ formatSize(file.size) }}
<template v-if="file.count"> · {{ file.count.toLocaleString() }} </template>
</span>
</div>
<span class="file-status"><i class="fa fa-check-circle" aria-hidden="true" /> 拉取成功</span>
<el-button
link
type="danger"
:aria-label="`删除数据集 ${file.name}`"
@click="emit('remove-file', file.uid)"
>
删除
</el-button>
</div>
</div>
<el-pagination
v-if="uploadedFiles.length > FILE_PAGE_SIZE"
v-model:current-page="currentFilePage"
:page-size="FILE_PAGE_SIZE"
:total="uploadedFiles.length"
:pager-count="5"
small
background
layout="prev, pager, next"
class="uploaded-file-pagination"
aria-label="已拉取数据分页"
/>
</section>
</div>
</div>
<div v-else class="form-section upload-section">
<div class="section-title-row">
<div>
<h3 id="source-upload-title">源数据上传</h3>
<p>上传后可在下一步检查内容和切分效果支持同时添加多个文件</p>
</div>
<el-button v-if="uploadedFiles.length === 0" link type="primary" @click="emit('use-sample')">
使用示例数据
</el-button>
</div>
<el-upload
v-if="uploadedFiles.length === 0"
drag
multiple
:accept="uploadAccept"
:auto-upload="false"
:show-file-list="false"
:on-change="(file: UploadFile) => emit('file-change', file)"
aria-label="选择或拖拽源数据文件"
>
<i class="fa fa-cloud-upload upload-icon" aria-hidden="true" />
<div class="el-upload__text">拖拽文件到此处,或<em>点击选择文件</em></div>
<template #tip>
<div class="el-upload__tip">
{{ processType === 'unstructured'
? '支持 TXT、Markdown、PDF、Word、JSON、JSONL单文件不超过 200MB'
: '支持 JSON、JSONL、CSV、Excel单文件不超过 200MB' }}
</div>
</template>
</el-upload>
<section v-else class="uploaded-file-list" aria-label="已上传文件列表">
<div class="uploaded-file-list-header">
<span>已添加 {{ uploadedFiles.length }} 个文件</span>
<div class="continue-upload">
<el-upload
multiple
:accept="uploadAccept"
:auto-upload="false"
:show-file-list="false"
:on-change="(file: UploadFile) => emit('file-change', file)"
aria-label="继续添加源数据文件"
>
<el-button size="small" type="primary">继续上传</el-button>
</el-upload>
</div>
</div>
<div class="uploaded-file-items">
<div v-for="file in pagedUploadedFiles" :key="file.uid" class="uploaded-file">
<span class="file-icon"><i class="fa fa-file-text-o" aria-hidden="true" /></span>
<div class="file-main">
<strong :title="file.name">{{ file.name }}</strong>
<span>
{{ formatSize(file.size) }}
<template v-if="file.count"> · {{ file.count.toLocaleString() }} 条</template>
</span>
</div>
<span class="file-status"><i class="fa fa-check-circle" aria-hidden="true" /> 校验通过</span>
<el-button
link
type="danger"
:aria-label="`删除文件 ${file.name}`"
@click="emit('remove-file', file.uid)"
>
删除
</el-button>
</div>
</div>
<el-pagination
v-if="uploadedFiles.length > FILE_PAGE_SIZE"
v-model:current-page="currentFilePage"
:page-size="FILE_PAGE_SIZE"
:total="uploadedFiles.length"
:pager-count="5"
small
background
layout="prev, pager, next"
class="uploaded-file-pagination"
aria-label="已上传文件分页"
/>
</section>
</div>
</section>
</template>
<style scoped lang="scss">
.source-upload-step {
width: 100%;
}
.form-section {
padding: 0;
h3 {
margin: 0 0 5px;
color: #2f3747;
font-size: 15px;
font-weight: 650;
}
}
.section-title-row {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 16px;
p {
margin: 0;
color: #8a93a3;
font-size: 12px;
line-height: 1.6;
}
}
.external-section {
.external-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 18px 20px;
margin-top: 16px;
}
:deep(.el-form-item) {
margin-bottom: 0;
}
:deep(.el-select),
:deep(.el-input),
:deep(.el-input-number) {
width: 100%;
}
}
.external-actions {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 12px;
margin-top: 22px;
}
.external-status {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 12px;
&.is-connected {
color: #2ca66a;
}
}
.upload-section :deep(.el-upload) {
width: 100%;
margin-top: 16px;
}
.upload-section :deep(.el-upload-dragger) {
width: 100%;
min-height: 154px;
padding: 32px 20px;
background: #fbfcfe;
border-color: #dfe3ea;
transition: border-color 0.18s ease, background-color 0.18s ease;
&:hover,
&:focus-visible {
background: #fafaff;
border-color: #8b82f4;
}
&:focus-visible {
outline: 2px solid #5b50f2;
outline-offset: 2px;
}
}
.upload-icon {
margin-bottom: 12px;
color: #5b50f2;
font-size: 30px;
}
.uploaded-file-list {
margin-top: 20px;
overflow: hidden;
background: #fff;
border: 1px solid #dfe3ea;
border-radius: 8px;
}
.uploaded-file-list-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 10px 14px;
color: #5f6878;
font-size: 12px;
background: #fbfcfe;
border-bottom: 1px solid #edf0f5;
}
.continue-upload {
flex: 0 0 auto;
}
.continue-upload :deep(.el-upload) {
width: auto;
margin-top: 0;
}
.uploaded-file-pagination {
display: flex;
justify-content: flex-end;
padding: 10px 14px;
border-top: 1px solid #edf0f5;
}
.uploaded-file {
display: flex;
align-items: center;
gap: 10px;
min-height: 48px;
padding: 8px 14px;
border-bottom: 1px solid #edf0f5;
&:last-child {
border-bottom: 0;
}
}
.file-icon {
display: inline-flex;
align-items: center;
justify-content: center;
flex: 0 0 auto;
width: 28px;
height: 28px;
color: #5b50f2;
font-size: 14px;
background: #f0efff;
border-radius: 7px;
}
.file-main {
display: flex;
flex: 1;
flex-direction: column;
gap: 5px;
min-width: 0;
strong {
overflow: hidden;
color: #273142;
font-size: 14px;
text-overflow: ellipsis;
white-space: nowrap;
}
span {
color: #8a93a3;
font-size: 12px;
}
}
.file-status {
color: #2ca66a;
font-size: 12px;
}
.uploaded-file :deep(.el-button) {
flex: 0 0 auto;
}
@media (max-width: 900px) {
.external-section .external-grid {
grid-template-columns: minmax(0, 1fr);
}
.uploaded-file {
gap: 8px;
padding: 8px 10px;
}
.file-status {
flex: 0 1 auto;
line-height: 1.4;
white-space: normal;
}
.uploaded-file-pagination {
justify-content: center;
}
}
@media (max-width: 560px) {
.section-title-row {
align-items: flex-start;
flex-direction: column;
}
.uploaded-file-list-header {
align-items: flex-start;
flex-direction: column;
}
.uploaded-file {
align-items: flex-start;
flex-wrap: wrap;
}
.file-main {
min-width: calc(100% - 40px);
}
.file-status {
margin-left: 38px;
}
}
@media (prefers-reduced-motion: reduce) {
.upload-section :deep(.el-upload-dragger) {
transition: none;
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
export type ProcessType = 'structured' | 'unstructured' | 'external'
export type StepId = 'create' | 'preview' | 'generate' | 'results'
export type StepId = 'create' | 'upload' | 'preview' | 'generate' | 'results'
export type PreprocessOption =
| 'clean_invalid'
@@ -34,15 +34,6 @@ export type UnstructuredPreprocessOption =
export type ChunkMethod = 'semantic' | 'heading' | 'fixed' | 'custom'
export type GenerationContextScope = 'current' | 'adjacent' | 'section'
export type QuestionGenerationType =
| 'factual'
| 'concept'
| 'procedure'
| 'reasoning'
| 'comprehensive'
export interface UnstructuredProcessOptions {
preprocessOptions: UnstructuredPreprocessOption[]
chunkMethod: ChunkMethod
@@ -55,9 +46,6 @@ export interface UnstructuredProcessOptions {
preserveLists: boolean
semanticEnrichment: boolean
qaPairsPerChunk: number
contextScope: GenerationContextScope
generationTypes: QuestionGenerationType[]
skipUnanswerable: boolean
datasetSplit: DatasetSplitOptions
}