feat: 数据处理向导新增模型配置步骤
StepId 新增 model 步骤,DataProcessCreateView 增加模型选择与生成参数编排,各选项面板与样式适配新步骤流程,回归脚本补充断言。
This commit is contained in:
@@ -5,6 +5,7 @@ import { ElMessage, type UploadFile } from 'element-plus'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import AppConfirmDialog from '@/components/AppConfirmDialog.vue'
|
||||
import TaskSetupStep from './create/TaskSetupStep.vue'
|
||||
import ModelSelectionStep from './create/ModelSelectionStep.vue'
|
||||
import SourceUploadStep from './create/SourceUploadStep.vue'
|
||||
import PreviewCompareStep from './create/PreviewCompareStep.vue'
|
||||
import GenerationStep from './create/GenerationStep.vue'
|
||||
@@ -22,6 +23,7 @@ import { useDataProcessGeneration } from './create/useDataProcessGeneration'
|
||||
import { useModelsStore } from '@/stores/models'
|
||||
import type {
|
||||
ExternalDataSource,
|
||||
GenerationControlOptions,
|
||||
PreviewItem,
|
||||
ProcessType,
|
||||
StepId,
|
||||
@@ -35,15 +37,17 @@ const modelsStore = useModelsStore()
|
||||
const { list: modelList } = storeToRefs(modelsStore)
|
||||
const generationModels = computed(() => modelList.value.filter((model) => model.type === 'LLM'))
|
||||
const taskSetupRef = ref<InstanceType<typeof TaskSetupStep>>()
|
||||
const modelSelectionRef = ref<InstanceType<typeof ModelSelectionStep>>()
|
||||
const confirmDialogRef = ref<InstanceType<typeof AppConfirmDialog>>()
|
||||
const PREVIEW_MODEL_VERSION = 'document-chunk-v2'
|
||||
|
||||
const WIZARD_STEPS = [
|
||||
{ id: 'create', title: '创建任务', desc: '填写任务信息与处理配置' },
|
||||
{ id: 'model', title: '大模型选择', desc: '选择生成模型并设置输出要求' },
|
||||
{ id: 'upload', title: '上传文件', desc: '上传或接入待处理的源数据' },
|
||||
{ id: 'preview', title: '数据预览', desc: '核对源文件与预览内容' },
|
||||
{ id: 'generate', title: '开始生成', desc: '确认摘要并启动处理' },
|
||||
{ id: 'results', title: '结果编辑与保存', desc: '检查、修改并保存结果' },
|
||||
{ id: 'results', title: '编辑与保存', desc: '检查、修改并保存结果' },
|
||||
] as const satisfies ReadonlyArray<{ id: StepId; title: string; desc: string }>
|
||||
const currentStep = ref(0)
|
||||
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 structuredOptions = ref<StructuredProcessOptions>(createDefaultStructuredOptions())
|
||||
const unstructuredOptions = ref<UnstructuredProcessOptions>(createDefaultUnstructuredOptions())
|
||||
const modelSelectionOptions = computed<GenerationControlOptions>(() => (
|
||||
processType.value === 'unstructured' ? unstructuredOptions.value : structuredOptions.value
|
||||
))
|
||||
const uploadedFiles = ref<UploadedDataFile[]>([])
|
||||
|
||||
const externalSource = reactive<ExternalDataSource>({
|
||||
@@ -119,7 +126,8 @@ const previewFiles = computed(() => uploadedFiles.value.map((file) => {
|
||||
}
|
||||
}))
|
||||
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 === 'preview') return '确认预览并继续'
|
||||
if (currentStepId.value === 'results') return '保存任务'
|
||||
@@ -144,6 +152,14 @@ function goToStep(stepId: StepId) {
|
||||
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({
|
||||
currentStepId,
|
||||
task,
|
||||
@@ -393,6 +409,12 @@ function resetSourceDataForProcessTypeChange() {
|
||||
async function nextFromCreate() {
|
||||
const valid = await taskSetupRef.value?.validate()
|
||||
if (!valid) return
|
||||
goToStep('model')
|
||||
}
|
||||
|
||||
async function nextFromModel() {
|
||||
const valid = await modelSelectionRef.value?.validate()
|
||||
if (!valid) return
|
||||
goToStep('upload')
|
||||
}
|
||||
|
||||
@@ -504,6 +526,10 @@ async function handlePrimaryAction() {
|
||||
await nextFromCreate()
|
||||
return
|
||||
}
|
||||
if (currentStepId.value === 'model') {
|
||||
await nextFromModel()
|
||||
return
|
||||
}
|
||||
if (currentStepId.value === 'upload') {
|
||||
nextFromUpload()
|
||||
return
|
||||
@@ -596,27 +622,31 @@ onMounted(() => {
|
||||
<div class="wizard-main-inner">
|
||||
<div class="wizard-steps-container">
|
||||
<div class="custom-wizard-steps">
|
||||
<div
|
||||
v-for="(step, index) in WIZARD_STEPS"
|
||||
:key="step.id"
|
||||
class="step-item"
|
||||
:class="{
|
||||
'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">
|
||||
<div class="step-icon">
|
||||
<i v-if="currentStep > index" class="fa fa-check" />
|
||||
<span v-else>{{ index + 1 }}</span>
|
||||
</div>
|
||||
<div class="step-text">
|
||||
<div class="step-title">{{ step.title }}</div>
|
||||
<template v-for="(step, index) in WIZARD_STEPS" :key="step.id">
|
||||
<div
|
||||
v-if="index !== 0"
|
||||
class="step-connector"
|
||||
:class="{ 'is-active': currentStep >= index }"
|
||||
></div>
|
||||
<div
|
||||
class="step-item"
|
||||
:class="{
|
||||
'is-active': currentStep === index,
|
||||
'is-completed': currentStep > index
|
||||
}"
|
||||
:aria-current="currentStep === index ? 'step' : undefined"
|
||||
>
|
||||
<div class="step-node">
|
||||
<div class="step-icon">
|
||||
<i v-if="currentStep > index" class="fa fa-check" />
|
||||
<span v-else>{{ index + 1 }}</span>
|
||||
</div>
|
||||
<div class="step-text">
|
||||
<div class="step-title">{{ step.title }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -629,7 +659,14 @@ onMounted(() => {
|
||||
v-model:process-type="processType"
|
||||
v-model:structured-options="structuredOptions"
|
||||
v-model:unstructured-options="unstructuredOptions"
|
||||
:generation-models="generationModels"
|
||||
/>
|
||||
|
||||
<ModelSelectionStep
|
||||
v-else-if="currentStepId === 'model'"
|
||||
ref="modelSelectionRef"
|
||||
:options="modelSelectionOptions"
|
||||
:models="generationModels"
|
||||
@update:options="updateModelSelectionOptions"
|
||||
/>
|
||||
|
||||
<SourceUploadStep
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { GenerationControlOptions } from './types'
|
||||
|
||||
const props = defineProps<{
|
||||
options: GenerationControlOptions
|
||||
models: ModelItem[]
|
||||
models?: ModelItem[]
|
||||
section: 'model' | 'quality'
|
||||
validationMessage?: string
|
||||
}>()
|
||||
@@ -59,7 +59,7 @@ function sectionValidationMessage() {
|
||||
@update:model-value="updateField('generationModelId', $event)"
|
||||
>
|
||||
<el-option
|
||||
v-for="model in models"
|
||||
v-for="model in models || []"
|
||||
:key="model.id"
|
||||
:label="model.name"
|
||||
:value="model.id"
|
||||
@@ -234,7 +234,22 @@ function sectionValidationMessage() {
|
||||
.model-config-group {
|
||||
display: flex;
|
||||
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 {
|
||||
|
||||
@@ -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>
|
||||
@@ -1,5 +1,4 @@
|
||||
<script setup lang="ts">
|
||||
import type { ModelItem } from '@/types'
|
||||
import type {
|
||||
GenerationControlOptions,
|
||||
PreprocessOption,
|
||||
@@ -10,9 +9,8 @@ import GenerationOptionsPanel from './GenerationOptionsPanel.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
options: StructuredProcessOptions
|
||||
generationModels: ModelItem[]
|
||||
validationAttempted: boolean
|
||||
generationValidationMessage: string
|
||||
qualityValidationMessage: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -89,9 +87,8 @@ function updatePreprocessOptions(value: Array<string | number | boolean>) {
|
||||
<div class="generation-option-list">
|
||||
<GenerationOptionsPanel
|
||||
:options="options"
|
||||
:models="generationModels"
|
||||
section="quality"
|
||||
:validation-message="validationAttempted ? generationValidationMessage : ''"
|
||||
:validation-message="validationAttempted ? qualityValidationMessage : ''"
|
||||
@update:options="updateGenerationOptions"
|
||||
/>
|
||||
|
||||
@@ -116,23 +113,6 @@ function updatePreprocessOptions(value: Array<string | number | boolean>) {
|
||||
</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>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
|
||||
import type { ModelItem } from '@/types'
|
||||
import type {
|
||||
GenerationControlOptions,
|
||||
ProcessType,
|
||||
@@ -17,7 +16,6 @@ const props = defineProps<{
|
||||
processType: ProcessType
|
||||
structuredOptions: StructuredProcessOptions
|
||||
unstructuredOptions: UnstructuredProcessOptions
|
||||
generationModels: ModelItem[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -52,10 +50,9 @@ const activeGenerationOptions = computed<GenerationControlOptions | null>(() =>
|
||||
return null
|
||||
})
|
||||
|
||||
const generationValidationMessage = computed(() => {
|
||||
const qualityValidationMessage = computed(() => {
|
||||
const options = activeGenerationOptions.value
|
||||
if (!options) return ''
|
||||
if (options.generationModelId === '') return '请选择数据生成模型'
|
||||
if (options.qualityFilterEnabled && !options.filterLowQuality && !options.filterShortContent) {
|
||||
return '开启质量筛选后,请至少选择一项筛选规则'
|
||||
}
|
||||
@@ -112,8 +109,8 @@ async function validate() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if (generationValidationMessage.value) {
|
||||
ElMessage.error(generationValidationMessage.value)
|
||||
if (qualityValidationMessage.value) {
|
||||
ElMessage.error(qualityValidationMessage.value)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@@ -210,9 +207,8 @@ defineExpose({ validate })
|
||||
<StructuredOptionsPanel
|
||||
v-if="processType === 'structured'"
|
||||
:options="structuredOptions"
|
||||
:generation-models="generationModels"
|
||||
:validation-attempted="validationAttempted"
|
||||
:generation-validation-message="generationValidationMessage"
|
||||
:quality-validation-message="qualityValidationMessage"
|
||||
@update:options="emit('update:structuredOptions', $event)"
|
||||
/>
|
||||
|
||||
@@ -220,9 +216,8 @@ defineExpose({ validate })
|
||||
v-if="processType === 'unstructured'"
|
||||
ref="unstructuredOptionsPanelRef"
|
||||
:options="unstructuredOptions"
|
||||
:generation-models="generationModels"
|
||||
:validation-attempted="validationAttempted"
|
||||
:generation-validation-message="generationValidationMessage"
|
||||
:quality-validation-message="qualityValidationMessage"
|
||||
:chunk-validation-message="chunkValidationMessage"
|
||||
@update:options="emit('update:unstructuredOptions', $event)"
|
||||
/>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import type { ModelItem } from '@/types'
|
||||
import type {
|
||||
ChunkMethod,
|
||||
GenerationControlOptions,
|
||||
@@ -12,9 +11,8 @@ import GenerationOptionsPanel from './GenerationOptionsPanel.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
options: UnstructuredProcessOptions
|
||||
generationModels: ModelItem[]
|
||||
validationAttempted: boolean
|
||||
generationValidationMessage: string
|
||||
qualityValidationMessage: string
|
||||
chunkValidationMessage: string
|
||||
}>()
|
||||
|
||||
@@ -269,9 +267,8 @@ defineExpose({ revealValidation })
|
||||
<div class="generation-option-list">
|
||||
<GenerationOptionsPanel
|
||||
:options="options"
|
||||
:models="generationModels"
|
||||
section="quality"
|
||||
:validation-message="validationAttempted ? generationValidationMessage : ''"
|
||||
:validation-message="validationAttempted ? qualityValidationMessage : ''"
|
||||
@update:options="updateGenerationOptions"
|
||||
/>
|
||||
|
||||
@@ -301,23 +298,6 @@ defineExpose({ revealValidation })
|
||||
</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>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
max-width: 860px;
|
||||
max-width: 1040px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
@@ -45,11 +45,7 @@
|
||||
.step-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
|
||||
&:first-child {
|
||||
flex: 0;
|
||||
}
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.step-connector {
|
||||
@@ -60,14 +56,14 @@
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.step-item.is-completed .step-connector,
|
||||
.step-item.is-active .step-connector {
|
||||
.step-connector.is-active {
|
||||
background-color: #5146e5;
|
||||
}
|
||||
|
||||
.step-node {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
@@ -149,6 +145,30 @@
|
||||
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) {
|
||||
.wizard-main {
|
||||
padding: 24px 20px;
|
||||
@@ -168,9 +188,7 @@
|
||||
|
||||
.step-title {
|
||||
max-width: 72px;
|
||||
font-size: 12px;
|
||||
line-height: 1.35;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.wizard-footer {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
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 =
|
||||
| 'clean_invalid'
|
||||
|
||||
Reference in New Issue
Block a user