refactor: dialogBox 重构为通用平台编排壳,委托所有平台特定逻辑
This commit is contained in:
parent
615afbc211
commit
ec81dce28a
@ -1,91 +1,60 @@
|
||||
<template>
|
||||
<Transition name="slide-up">
|
||||
<div class="input-container" :class="{ generate : !props.isGenerate }" @click="handleContainerClick">
|
||||
<div v-if="!props.isGenerate && props.type === 'Painting'" class="title">AI绘画2026</div>
|
||||
<div v-if="!props.isGenerate && props.type === 'Video'" class="title">AI视频2026</div>
|
||||
<div v-if="!props.isGenerate" class="title">{{ platform.label }}</div>
|
||||
|
||||
<div class="sender-top">
|
||||
<div v-if="useDisplay.Sender_variant === 'default'" class="scroll-to-bottom-text" @click.stop="handleScrollToBottom">回到底部<img src="@/assets/dialog/ArrowDown.svg"></div>
|
||||
<div v-if="useDisplay.Sender_variant === 'default'" class="scroll-to-bottom-text" @click.stop="handleScrollToBottom">
|
||||
回到底部<img src="@/assets/dialog/ArrowDown.svg">
|
||||
</div>
|
||||
|
||||
<div v-show="showImageUploader" class="upload-img-container">
|
||||
<div v-show="showUploader" class="upload-img-container">
|
||||
<div class="reference-diagram">
|
||||
<ImageUploader
|
||||
v-if="props.type === 'Painting'"
|
||||
<component
|
||||
v-if="platform.ImageUploader"
|
||||
:is="platform.ImageUploader"
|
||||
ref="referenceDiagramRef"
|
||||
v-model="referenceImages"
|
||||
:limit="imageUploadLimit"
|
||||
@open-canvas="handleOpenCanvas"
|
||||
/>
|
||||
<VideoImageUploader
|
||||
v-else-if="props.type === 'Video'"
|
||||
ref="referenceDiagramRef"
|
||||
v-model="referenceImages"
|
||||
:model-type="modelType"
|
||||
:images-count="modelDisplayConfig?.display?.images || 1"
|
||||
v-bind="uploaderBindings"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Sender :key="useDisplay.Sender_variant" v-model="prompt" :variant="useDisplay.Sender_variant" :placeholder="promptPlaceholder" :submit-btn-disabled="isgerenate.value" :auto-size="autoSizeConfig">
|
||||
<template #prefix>
|
||||
<div v-if="useDisplay.Sender_variant !== 'default' && props.type === 'Painting'" class="prefix-self-wrap">
|
||||
<paintingModel v-model="model" v-model:typeValue="modelType" />
|
||||
<paintingProportion
|
||||
v-if="showProportion"
|
||||
v-model="proportion"
|
||||
v-model:resolution="resolution"
|
||||
v-model:width="customWidth"
|
||||
v-model:height="customHight"
|
||||
:proportion-options="paintingProportionOpts"
|
||||
:resolution-options="paintingResolutionOpts"
|
||||
:allow-custom="hasCustomSize"
|
||||
<Sender
|
||||
v-model="prompt"
|
||||
:variant="useDisplay.Sender_variant"
|
||||
:placeholder="platform.promptPlaceholder.value"
|
||||
:submit-btn-disabled="isgerenate"
|
||||
:auto-size="autoSizeConfig"
|
||||
>
|
||||
<template v-if="useDisplay.Sender_variant !== 'default'" #prefix>
|
||||
<div class="prefix-self-wrap">
|
||||
<component
|
||||
:is="platform.ModelSelector"
|
||||
:modelValue="platform.model.value"
|
||||
@update:modelValue="platform.model.value = $event"
|
||||
:typeValue="platform.modelType.value"
|
||||
@update:typeValue="platform.modelType.value = $event"
|
||||
v-bind="(platform.modelSelectorProps && platform.modelSelectorProps()) || {}"
|
||||
/>
|
||||
<DimensionInput
|
||||
v-if="showDimension"
|
||||
v-model:width="dimWidth"
|
||||
v-model:height="dimHeight"
|
||||
:min-w="dimMinW"
|
||||
:max-w="dimMaxW"
|
||||
:min-h="dimMinH"
|
||||
:max-h="dimMaxH"
|
||||
/>
|
||||
<Select
|
||||
v-if="showQuality"
|
||||
v-model="qualityValue"
|
||||
:options="qualityOpts"
|
||||
width="auto"
|
||||
class="quality-select"
|
||||
>
|
||||
<template #prefix>
|
||||
<span class="quality-label">画质</span>
|
||||
</template>
|
||||
</Select>
|
||||
<Quantity v-if="showQuantity" v-model="quantity" :max="quantityMax" />
|
||||
</div>
|
||||
|
||||
<div v-if="useDisplay.Sender_variant !== 'default' && props.type === 'Video'" class="prefix-self-wrap">
|
||||
<Pattern v-model="videoPattern" />
|
||||
<videoModel v-model="model" v-model:typeValue="modelType" :video-pattern="videoPattern" />
|
||||
|
||||
<videoProportion
|
||||
v-model="proportion"
|
||||
v-model:resolution="resolution"
|
||||
:proportion-options="proportionOptions"
|
||||
:resolution-options="resolutionOptions"
|
||||
/>
|
||||
<Time v-model="duration" :options="durationOptions" />
|
||||
<template v-for="ctrl in visibleControls" :key="ctrl.name">
|
||||
<component
|
||||
:is="ctrl.component"
|
||||
v-bind="ctrl.props(getCurrentConfig())"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #action-list>
|
||||
<div style="display: flex; align-items: center; gap: 8px; height: 100%;">
|
||||
<el-button v-if="isgerenate" round color="#626aef">
|
||||
<i-ep-loading style="animation: spin 1s linear infinite;" />
|
||||
<i-ep-loading style="animation: spin 1s linear infinite;" />
|
||||
</el-button>
|
||||
<div v-else class="gerenate" :class="{ isprompt: prompt }" @click="handleStart">
|
||||
<img v-if="!prompt" src="@/assets/dialog/darkArrow.svg" alt="" />
|
||||
<img v-else src="@/assets/dialog/writerArrow.svg" alt="" />
|
||||
<img v-if="!prompt" src="@/assets/dialog/darkArrow.svg" alt="">
|
||||
<img v-else src="@/assets/dialog/writerArrow.svg" alt="">
|
||||
<div v-show="useDisplay.Sender_variant !== 'default'">发送</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -97,415 +66,117 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import videoProportion from './proportion/video.vue'
|
||||
import paintingModel from './model/painting.vue'
|
||||
import videoModel from './model/video.vue'
|
||||
import Pattern from './pattern/index.vue'
|
||||
import ImageUploader from './imageUploader/index.vue'
|
||||
import VideoImageUploader from './videoImageUploader/index.vue'
|
||||
import Time from './Time/index.vue'
|
||||
import paintingProportion from './proportion/painting.vue'
|
||||
import Quantity from './quantity/index.vue'
|
||||
import DimensionInput from './dimension/index.vue'
|
||||
import Select from '@/components/Select/index.vue'
|
||||
import { Sender } from 'vue-element-plus-x'
|
||||
import { useDisplayStore } from '@/stores'
|
||||
import { generate } from '@/utils/taskPolling'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { getModelId, fetchPlatformModels, getPlatformCode } from '@/utils/modelApi'
|
||||
import { fetchModelConfig } from '@/utils/modelConfig'
|
||||
import { getModelConfig } from '@/config/models/index.js'
|
||||
import { createPlatform } from '@/platforms/registry.js'
|
||||
import { getModelId } from '@/utils/modelApi'
|
||||
|
||||
// 确保平台包被加载(触发自注册)
|
||||
import '@/platforms/painting/index.js'
|
||||
import '@/platforms/video/index.js'
|
||||
|
||||
const props = defineProps({
|
||||
isGenerate: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
generate: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'Painting'
|
||||
}
|
||||
isGenerate: { type: Boolean, default: false },
|
||||
generate: { type: Boolean, default: false },
|
||||
type: { type: String, default: 'Painting' },
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
const useDisplay = useDisplayStore()
|
||||
const isgerenate = ref(false)
|
||||
|
||||
const model = ref() // 模型
|
||||
const modelType = ref('text')
|
||||
|
||||
// 当前模型配置
|
||||
const modelConfig = computed(() => {
|
||||
return props.type === 'Painting' ? getModelConfig(model.value) : null
|
||||
})
|
||||
|
||||
// 模型参数值
|
||||
const paramValues = reactive({})
|
||||
|
||||
const showImageUploader = computed(() => {
|
||||
if (props.type === 'Video') return modelType.value !== 'text'
|
||||
return modelType.value !== 'text' || modelConfig.value?.inputType === 'image' || modelConfig.value?.inputType === 'both'
|
||||
})
|
||||
|
||||
// 模型是否有数量参数(imageNum),或生成类模型显示张数选择
|
||||
const showQuantity = computed(() => {
|
||||
if (props.type !== 'Painting') return false
|
||||
return !!modelConfig.value?.params?.find(p => p.ui === 'quantity')
|
||||
})
|
||||
|
||||
// 模型是否使用 proportion 组件(aspectRatio 参数)
|
||||
const showProportion = computed(() => {
|
||||
return !!modelConfig.value?.params?.find(p => p.ui === 'proportion')
|
||||
})
|
||||
|
||||
// 模型是否支持自定义尺寸(aspectRatio 选项含 'custom')
|
||||
const hasCustomSize = computed(() => {
|
||||
const ratioParam = modelConfig.value?.params?.find(p => p.ui === 'proportion')
|
||||
return ratioParam?.options?.includes('custom') || false
|
||||
})
|
||||
|
||||
// 尺寸输入(DimensionInput)相关
|
||||
const dimWidth = ref(1024)
|
||||
const dimHeight = ref(1024)
|
||||
const qualityValue = ref('medium')
|
||||
|
||||
const showDimension = computed(() => {
|
||||
return !!modelConfig.value?.params?.find(p => p.ui === 'dimension' || p.ui === 'dimensionWidth')
|
||||
})
|
||||
|
||||
const dimConfig = computed(() => {
|
||||
if (!modelConfig.value) return null
|
||||
const dimParam = modelConfig.value.params.find(p => p.ui === 'dimension')
|
||||
if (dimParam) return { type: 'combined', config: dimParam.dimension, paramName: dimParam.name }
|
||||
const wParam = modelConfig.value.params.find(p => p.ui === 'dimensionWidth')
|
||||
const hParam = modelConfig.value.params.find(p => p.ui === 'dimensionHeight')
|
||||
if (wParam && hParam) return { type: 'split', wParam, hParam }
|
||||
return null
|
||||
})
|
||||
|
||||
const dimMinW = computed(() => dimConfig.value?.config?.width?.min || dimConfig.value?.wParam?.min || 256)
|
||||
const dimMaxW = computed(() => dimConfig.value?.config?.width?.max || dimConfig.value?.wParam?.max || 6197)
|
||||
const dimMinH = computed(() => dimConfig.value?.config?.height?.min || dimConfig.value?.hParam?.min || 256)
|
||||
const dimMaxH = computed(() => dimConfig.value?.config?.height?.max || dimConfig.value?.hParam?.max || 4096)
|
||||
|
||||
const showQuality = computed(() => {
|
||||
return !!modelConfig.value?.params?.find(p => p.name === 'quality')
|
||||
})
|
||||
|
||||
const qualityOpts = computed(() => {
|
||||
const q = modelConfig.value?.params?.find(p => p.name === 'quality')
|
||||
if (q?.options) return q.options.map(o => ({ value: o, label: o }))
|
||||
return []
|
||||
})
|
||||
|
||||
// 从模型配置派生比例选项,回退到默认值
|
||||
const paintingProportionOpts = computed(() => {
|
||||
const ratioParam = modelConfig.value?.params?.find(p => p.ui === 'proportion')
|
||||
if (ratioParam?.options) {
|
||||
return ratioParam.options
|
||||
.filter(o => o !== 'custom')
|
||||
.map(o => ({ value: o, label: o }))
|
||||
}
|
||||
return proportionOptions.value
|
||||
})
|
||||
|
||||
// 从模型配置派生分辨率选项(仅模型有 resolution 参数时显示)
|
||||
const paintingResolutionOpts = computed(() => {
|
||||
const resParam = modelConfig.value?.params?.find(p => p.ui === 'resolution')
|
||||
if (resParam?.options) {
|
||||
return resParam.options.map(o => ({ value: o, label: o.toUpperCase() }))
|
||||
}
|
||||
return []
|
||||
})
|
||||
|
||||
const imageUploadLimit = computed(() => {
|
||||
if (!modelConfig.value) return 4
|
||||
const imageParam = modelConfig.value.params.find(p => p.ui === 'imageUpload')
|
||||
return imageParam?.maxCount || modelConfig.value.maxImages || 4
|
||||
})
|
||||
|
||||
const promptPlaceholder = ref('描述你想生成的画面和动作。') // 提示词占位符
|
||||
|
||||
const prompt = ref('') // 提示词
|
||||
const proportion = ref('16:9') // 比例(Video 用)
|
||||
const resolution = ref('1k') // 分辨率(Video 用)
|
||||
const prompt = ref('')
|
||||
const referenceImages = ref([])
|
||||
|
||||
// 绘画
|
||||
const quantity = ref(1) // 生成数量
|
||||
const customWidth = ref(1024) // 自定义宽度
|
||||
const customHight = ref(1024) // 自定义高度
|
||||
const platform = computed(() => createPlatform(props.type))
|
||||
|
||||
const quantityMax = computed(() => {
|
||||
const qtyParam = modelConfig.value?.params?.find(p => p.ui === 'quantity')
|
||||
if (qtyParam?.options?.length) return Math.max(...qtyParam.options)
|
||||
return 4
|
||||
const getCurrentConfig = () => {
|
||||
return platform.value.modelConfig?.value ?? platform.value.modelDisplayConfig?.value ?? null
|
||||
}
|
||||
|
||||
const visibleControls = computed(() => {
|
||||
const config = getCurrentConfig()
|
||||
return platform.value.controls.filter(c => c.show(config))
|
||||
})
|
||||
|
||||
// 同步模型默认值到 paramValues 和 UI refs
|
||||
watch(modelConfig, (config) => {
|
||||
if (!config) return
|
||||
config.params.forEach(p => {
|
||||
if (!(p.name in paramValues)) {
|
||||
if (p.name === 'outputFormat') {
|
||||
paramValues[p.name] = 'png'
|
||||
} else {
|
||||
paramValues[p.name] = p.default ?? ''
|
||||
}
|
||||
}
|
||||
})
|
||||
// 同步默认值到 UI 控件
|
||||
const ratioParam = config.params.find(p => p.ui === 'proportion')
|
||||
if (ratioParam) proportion.value = ratioParam.default || '1:1'
|
||||
const resParam = config.params.find(p => p.ui === 'resolution')
|
||||
if (resParam) resolution.value = resParam.default || '2k'
|
||||
const qtyParam = config.params.find(p => p.ui === 'quantity')
|
||||
if (qtyParam) quantity.value = qtyParam.default || 1
|
||||
const cwParam = config.params.find(p => p.name === 'customWidth')
|
||||
if (cwParam) customWidth.value = cwParam.default || 1024
|
||||
const chParam = config.params.find(p => p.name === 'customHight')
|
||||
if (chParam) customHight.value = chParam.default || 1024
|
||||
// 同步 dimension 和 quality 默认值
|
||||
const qualityParam = config.params.find(p => p.name === 'quality')
|
||||
if (qualityParam) qualityValue.value = qualityParam.default || 'medium'
|
||||
const dc = dimConfig.value
|
||||
if (dc?.type === 'split') {
|
||||
dimWidth.value = dc.wParam.default || 1024
|
||||
dimHeight.value = dc.hParam.default || 1024
|
||||
} else if (dc?.type === 'combined') {
|
||||
const raw = paramValues[dc.paramName] || config.params.find(p => p.name === dc.paramName)?.default || ''
|
||||
const parsed = dc.config.parse(raw)
|
||||
dimWidth.value = parsed.width
|
||||
dimHeight.value = parsed.height
|
||||
const showUploader = computed(() => {
|
||||
return platform.value.showImageUploader()
|
||||
})
|
||||
|
||||
const uploaderBindings = computed(() => {
|
||||
const p = platform.value
|
||||
if (p.id === 'painting') {
|
||||
return { limit: p.imageUploadLimit() }
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
// 反向同步:UI refs → paramValues
|
||||
watch(proportion, (val) => {
|
||||
const p = modelConfig.value?.params?.find(param => param.ui === 'proportion')
|
||||
if (p) paramValues[p.name] = val
|
||||
})
|
||||
watch(resolution, (val) => {
|
||||
const p = modelConfig.value?.params?.find(param => param.ui === 'resolution')
|
||||
if (p) paramValues[p.name] = val
|
||||
})
|
||||
watch(quantity, (val) => {
|
||||
const p = modelConfig.value?.params?.find(param => param.ui === 'quantity')
|
||||
if (p) paramValues[p.name] = val
|
||||
})
|
||||
watch(customWidth, (val) => {
|
||||
if (modelConfig.value?.params?.find(p => p.name === 'customWidth')) {
|
||||
paramValues.customWidth = val
|
||||
if (p.id === 'video') {
|
||||
return { modelType: p.modelType.value, imagesCount: p.imageUploadLimit() }
|
||||
}
|
||||
return {}
|
||||
})
|
||||
watch(customHight, (val) => {
|
||||
if (modelConfig.value?.params?.find(p => p.name === 'customHight')) {
|
||||
paramValues.customHight = val
|
||||
}
|
||||
})
|
||||
watch([dimWidth, dimHeight], ([w, h]) => {
|
||||
const dc = dimConfig.value
|
||||
if (!dc) return
|
||||
if (dc.type === 'split') {
|
||||
paramValues[dc.wParam.name] = w
|
||||
paramValues[dc.hParam.name] = h
|
||||
} else if (dc.type === 'combined') {
|
||||
paramValues[dc.paramName] = dc.config.format(w, h)
|
||||
}
|
||||
})
|
||||
watch(qualityValue, (val) => {
|
||||
if (modelConfig.value?.params?.find(p => p.name === 'quality')) {
|
||||
paramValues.quality = val
|
||||
}
|
||||
})
|
||||
|
||||
// 同步参考图片到 paramValues
|
||||
watch(referenceImages, (imgs) => {
|
||||
const imageParam = modelConfig.value?.params?.find(p => p.ui === 'imageUpload')
|
||||
if (imageParam) {
|
||||
paramValues[imageParam.name] = imgs.map(img => img.url)
|
||||
}
|
||||
}, { deep: true })
|
||||
|
||||
// 视频
|
||||
const duration = ref(5) // 时间
|
||||
const videoPattern = ref('文生视频') // 视频模式下,默认值为'文生视频'
|
||||
|
||||
const resolutionOptions = ref([
|
||||
{ value: '1k', label: '标清 1K' },
|
||||
{ value: '2k', label: '高清 2K' },
|
||||
{ value: '4k', label: '超清 4K' },
|
||||
])
|
||||
const proportionOptions = ref([
|
||||
{ value: '智能', label: '智能' },
|
||||
{ value: '21:9', label: '21:9' },
|
||||
{ value: '16:9', label: '16:9' },
|
||||
{ value: '4:3', label: '4:3' },
|
||||
{ value: '1:1', label: '1:1' },
|
||||
{ value: '3:4', label: '3:4' },
|
||||
{ value: '9:16', label: '9:16' },
|
||||
])
|
||||
const durationOptions = ref([])
|
||||
|
||||
const isInitialized = ref(false)
|
||||
|
||||
const autoSizeConfig = computed(() => {
|
||||
if (useDisplay.Sender_variant !== 'default') {
|
||||
return { minRows: 5, maxRows: 9 }
|
||||
} else {
|
||||
return { minRows: 1, maxRows: 1 }
|
||||
}
|
||||
return { minRows: 1, maxRows: 1 }
|
||||
})
|
||||
|
||||
const modelDisplayConfig = ref(null)
|
||||
|
||||
// Video: 从远程加载 workflow 配置(保留旧逻辑)
|
||||
const loadVideoModelConfig = async (modelName, currentModelType) => {
|
||||
try {
|
||||
const config = await fetchModelConfig(props.type, modelName, currentModelType)
|
||||
modelDisplayConfig.value = config
|
||||
|
||||
if (config.display) {
|
||||
const display = config.display
|
||||
if (display.promptPlaceholder) {
|
||||
promptPlaceholder.value = display.promptPlaceholder.default || '描述你想生成的画面和动作。'
|
||||
}
|
||||
if (display.prompt && !isInitialized.value) {
|
||||
prompt.value = display.prompt.default || ''
|
||||
}
|
||||
if (display.resolution) {
|
||||
resolution.value = display.resolution.default || '1k'
|
||||
resolutionOptions.value = display.resolution.options || []
|
||||
}
|
||||
if (display.proportion) {
|
||||
proportion.value = display.proportion.default || '16:9'
|
||||
proportionOptions.value = display.proportion.options || []
|
||||
}
|
||||
if (display.duration) {
|
||||
duration.value = display.duration.default || 5
|
||||
durationOptions.value = display.duration.options || []
|
||||
}
|
||||
}
|
||||
isInitialized.value = true
|
||||
} catch (error) {
|
||||
console.error('加载视频模型配置失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const handleStart = async () => {
|
||||
const currentType = props.type
|
||||
let currentModelType = modelType.value
|
||||
const p = platform.value
|
||||
|
||||
if(model.value === 'Seedance 2.0') {
|
||||
if (props.type === 'Video' && p.model.value === 'Seedance 2.0') {
|
||||
ElMessage.primary('敬请期待 Seedance 2.0')
|
||||
return
|
||||
}
|
||||
|
||||
if (!props.isGenerate) {
|
||||
router.push({ name: 'home', query: { loading: false, Generate: true, type: currentType } })
|
||||
router.push({ name: 'home', query: { loading: false, Generate: true, type: props.type } })
|
||||
}
|
||||
if (!prompt.value) {
|
||||
// eslint-disable-next-line no-undef
|
||||
ElMessage.error('请输入提示词')
|
||||
return
|
||||
}
|
||||
if (showImageUploader.value && !referenceImages.value.length){
|
||||
if (showUploader.value && p.isImageRequired() && !referenceImages.value.length) {
|
||||
ElMessage.warning('请上传图片')
|
||||
return
|
||||
}
|
||||
|
||||
isgerenate.value = true
|
||||
console.log('生成开始', isgerenate.value)
|
||||
const imgs = []
|
||||
referenceImages.value.forEach((img, index) => {
|
||||
imgs.push({ name: `image_${index + 1}`, url: img.url })
|
||||
})
|
||||
|
||||
// 构建模型参数(Painting 用)
|
||||
const modelParams = { ...paramValues }
|
||||
if (prompt.value) modelParams.prompt = prompt.value
|
||||
const modelId = await getModelId(props.type, p.model.value)
|
||||
|
||||
// 所有平台统一返回扁平 modelParams
|
||||
const body = await p.buildTaskBody({ prompt, referenceImages })
|
||||
|
||||
const generateData = {
|
||||
model: model.value,
|
||||
modelType: currentModelType,
|
||||
model: p.model.value,
|
||||
modelType: p.modelType.value,
|
||||
prompt: prompt.value,
|
||||
proportion: proportion.value,
|
||||
referenceImages: referenceImages.value,
|
||||
quantity: quantity.value,
|
||||
resolution: resolution.value,
|
||||
customWidth: customWidth.value,
|
||||
customHight: customHight.value,
|
||||
duration: duration.value,
|
||||
videoPattern: videoPattern.value,
|
||||
modelParams,
|
||||
modelParams: body,
|
||||
}
|
||||
|
||||
const modelId = await getModelId(currentType, model.value)
|
||||
|
||||
// Painting 用新架构扁平参数,Video 保留旧 params 数组
|
||||
const isPainting = currentType === 'Painting'
|
||||
const data = {
|
||||
type: currentType,
|
||||
modelType: currentModelType,
|
||||
AIGC: currentType,
|
||||
platform: 'runninghub',
|
||||
modelName: model.value,
|
||||
type: props.type,
|
||||
modelType: p.modelType.value,
|
||||
modelName: p.model.value,
|
||||
modelId: modelId || '',
|
||||
modelParams: isPainting ? modelParams : {},
|
||||
params: isPainting ? [] : [
|
||||
{ name: 'prompt', data: prompt.value },
|
||||
{ name: 'quantity', data: quantity.value },
|
||||
{ name: 'proportion', data: proportion.value },
|
||||
{ name: 'resolution', data: resolution.value },
|
||||
{ name: 'duration', data: duration.value },
|
||||
],
|
||||
imgs,
|
||||
request: JSON.stringify(generateData)
|
||||
body,
|
||||
request: JSON.stringify(generateData),
|
||||
}
|
||||
|
||||
await generate(data, generateData)
|
||||
console.log('生成中', isgerenate.value)
|
||||
}
|
||||
|
||||
const fillParamsFromResult = (resultData) => {
|
||||
if (!resultData) return
|
||||
|
||||
if (resultData.model !== undefined) model.value = resultData.model
|
||||
if (resultData.modelType !== undefined) modelType.value = resultData.modelType
|
||||
platform.value.fillFromResult(resultData)
|
||||
if (resultData.prompt !== undefined) prompt.value = resultData.prompt
|
||||
if (resultData.proportion !== undefined) proportion.value = resultData.proportion
|
||||
if (resultData.referenceImages !== undefined) referenceImages.value = resultData.referenceImages
|
||||
if (resultData.quantity !== undefined) quantity.value = resultData.quantity
|
||||
if (resultData.resolution !== undefined) resolution.value = resultData.resolution
|
||||
if (resultData.customWidth !== undefined) customWidth.value = resultData.customWidth
|
||||
if (resultData.customHight !== undefined) customHight.value = resultData.customHight
|
||||
if (resultData.duration !== undefined) duration.value = resultData.duration
|
||||
if (resultData.videoPattern !== undefined) videoPattern.value = resultData.videoPattern
|
||||
if (resultData.modelParams !== undefined) Object.assign(paramValues, resultData.modelParams)
|
||||
// 从恢复的 modelParams 同步 dimension/quality UI refs
|
||||
nextTick(() => {
|
||||
const dc = dimConfig.value
|
||||
if (dc?.type === 'split') {
|
||||
if (paramValues[dc.wParam.name] !== undefined) dimWidth.value = paramValues[dc.wParam.name]
|
||||
if (paramValues[dc.hParam.name] !== undefined) dimHeight.value = paramValues[dc.hParam.name]
|
||||
} else if (dc?.type === 'combined') {
|
||||
if (paramValues[dc.paramName]) {
|
||||
const parsed = dc.config.parse(paramValues[dc.paramName])
|
||||
dimWidth.value = parsed.width
|
||||
dimHeight.value = parsed.height
|
||||
}
|
||||
}
|
||||
if (paramValues.quality !== undefined) qualityValue.value = paramValues.quality
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
fillParamsFromResult,
|
||||
handleStart
|
||||
})
|
||||
defineExpose({ fillParamsFromResult, handleStart })
|
||||
|
||||
const handleContainerClick = () => {
|
||||
if (useDisplay.Sender_variant === 'default') {
|
||||
@ -514,46 +185,33 @@ const handleContainerClick = () => {
|
||||
}
|
||||
|
||||
const handleScrollToBottom = () => {
|
||||
console.log('点击回到底部按钮')
|
||||
useDisplay.scrollToBottom()
|
||||
}
|
||||
|
||||
const handleOpenCanvas = (data) => {
|
||||
useDisplay.openCanvas(data)
|
||||
}
|
||||
watch(() => useDisplay.isSubGerenate, (v) => { isgerenate.value = v }, { immediate: true })
|
||||
|
||||
watch(() => useDisplay.isSubGerenate, (newValue) => {
|
||||
console.log('生成状态', newValue)
|
||||
isgerenate.value = newValue
|
||||
}, { immediate: true })
|
||||
|
||||
watch([() => model.value, () => modelType.value], async ([newModel, newModelType]) => {
|
||||
console.log('模型或类型改变:', newModel, newModelType)
|
||||
if (!newModel) return
|
||||
if (props.type !== 'Painting') {
|
||||
await loadVideoModelConfig(newModel, newModelType)
|
||||
}
|
||||
})
|
||||
|
||||
// 预加载平台模型列表,避免首次点击"发送"时才请求接口
|
||||
const prefetchModels = () => {
|
||||
const code = getPlatformCode(props.type)
|
||||
fetchPlatformModels(code)
|
||||
}
|
||||
// 模型变更 → 加载配置
|
||||
watch(
|
||||
[() => platform.value.model.value, () => platform.value.modelType.value],
|
||||
async ([newModel, newModelType]) => {
|
||||
if (!newModel) return
|
||||
if (platform.value.id === 'video') {
|
||||
await platform.value.loadConfig(newModel, newModelType)
|
||||
} else {
|
||||
platform.value.loadConfig(newModel)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
// 平台切换 → 设置默认模型 + 预加载模型列表
|
||||
watch(() => props.type, (newType) => {
|
||||
if (newType === 'Video') {
|
||||
model.value = 'LTX2.0'
|
||||
} else {
|
||||
model.value = 'flux'
|
||||
}
|
||||
prefetchModels()
|
||||
const p = createPlatform(newType)
|
||||
p.model.value = p.getDefaultModel()
|
||||
p.loadModels()
|
||||
}, { immediate: true })
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
/* 输入区域 */
|
||||
.input-container {
|
||||
width: 50%;
|
||||
max-width: 880px;
|
||||
@ -591,20 +249,14 @@ watch(() => props.type, (newType) => {
|
||||
justify-content: center;
|
||||
gap: 5px;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
&:hover {
|
||||
background-color: #F0F1F2;
|
||||
}
|
||||
&:active { transform: scale(0.95); }
|
||||
&:hover { background-color: #F0F1F2; }
|
||||
}
|
||||
|
||||
// 上传图片
|
||||
.upload-img-container{
|
||||
.upload-img-container {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 80%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: start;
|
||||
@ -620,35 +272,31 @@ watch(() => props.type, (newType) => {
|
||||
}
|
||||
}
|
||||
|
||||
.generate{
|
||||
.generate {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
// gap: 40px;
|
||||
position: relative;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
|
||||
:deep(.el-sender){
|
||||
:deep(.el-sender) {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
.prefix-self-wrap{
|
||||
|
||||
.prefix-self-wrap {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 5px;
|
||||
|
||||
img{
|
||||
height: 50px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
img { height: 50px; border-radius: 4px; }
|
||||
}
|
||||
.title{
|
||||
|
||||
.title {
|
||||
background-color: #FFF;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
@ -659,27 +307,28 @@ watch(() => props.type, (newType) => {
|
||||
line-height: normal;
|
||||
margin-bottom: 106px;
|
||||
}
|
||||
:deep(.el-sender){
|
||||
|
||||
:deep(.el-sender) {
|
||||
background-color: #F5F6F7;
|
||||
border: none;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
:deep(.el-sender:focus-within){
|
||||
:deep(.el-sender:focus-within) {
|
||||
box-shadow: 0 0 15px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
:deep(.el-popover.el-popper){
|
||||
:deep(.el-popover.el-popper) {
|
||||
border-radius: 20px;
|
||||
}
|
||||
// 时间选择器
|
||||
.select{
|
||||
|
||||
.select {
|
||||
background: #ffffff;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.10);
|
||||
}
|
||||
// 视频效果选择器
|
||||
.upload-btn{
|
||||
|
||||
.upload-btn {
|
||||
display: flex;
|
||||
height: 40px;
|
||||
padding: 0 15px;
|
||||
@ -692,10 +341,9 @@ watch(() => props.type, (newType) => {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
}
|
||||
.upload-btn:hover{
|
||||
background: #E5E7EB;
|
||||
}
|
||||
/* 圆形按钮 */
|
||||
|
||||
.upload-btn:hover { background: #E5E7EB; }
|
||||
|
||||
.circle-btn {
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
@ -711,18 +359,10 @@ watch(() => props.type, (newType) => {
|
||||
transition: all 0.3s ease;
|
||||
color: rgb(0, 0, 0);
|
||||
font-size: 20px;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.1);
|
||||
// box-shadow: 0 6px 16px rgba(98, 106, 239, 0.6);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
&:hover { transform: scale(1.1); }
|
||||
&:active { transform: scale(0.95); }
|
||||
}
|
||||
|
||||
/* 过渡动画 */
|
||||
.slide-up-enter-active,
|
||||
.slide-up-leave-active {
|
||||
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
@ -738,7 +378,7 @@ watch(() => props.type, (newType) => {
|
||||
transform: translate(-50%, 100%);
|
||||
}
|
||||
|
||||
.gerenate{
|
||||
.gerenate {
|
||||
display: inline-flex;
|
||||
height: 40px;
|
||||
padding: 0 20px;
|
||||
@ -748,7 +388,6 @@ watch(() => props.type, (newType) => {
|
||||
border-radius: 10px;
|
||||
background: rgba(0, 15, 51, 0.10);
|
||||
cursor: pointer;
|
||||
|
||||
color: #000F33;
|
||||
text-align: center;
|
||||
font-family: "Microsoft YaHei";
|
||||
@ -757,35 +396,9 @@ watch(() => props.type, (newType) => {
|
||||
font-weight: 700;
|
||||
line-height: normal;
|
||||
}
|
||||
.isprompt{
|
||||
|
||||
.isprompt {
|
||||
color: #ffffff;
|
||||
background-color: #000F33;
|
||||
}
|
||||
// .gerenate:hover{
|
||||
// background: rgba(0, 15, 51, 0.20);
|
||||
// }
|
||||
// 画质选择器
|
||||
.quality-select {
|
||||
:deep(.select-header) {
|
||||
height: 40px;
|
||||
padding: 0 15px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid #E8E9EB;
|
||||
background: #f5f6f7;
|
||||
|
||||
&:hover {
|
||||
background: #e9eaeb;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.select-text) {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.quality-label {
|
||||
font-family: "Microsoft YaHei";
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,22 +1,5 @@
|
||||
import outPlatform from '@/config/index'
|
||||
|
||||
// 构造任务 body
|
||||
// 所有平台 descriptor 的 buildTaskBody() 已直接返回扁平 modelParams,
|
||||
// 此文件仅做透传,后续可直接移除
|
||||
export async function createTask(data) {
|
||||
// Painting 使用新架构:直接使用动态模型参数
|
||||
if (data.type === 'Painting') {
|
||||
return data.modelParams || {}
|
||||
}
|
||||
|
||||
// Video 继续使用旧 workflow 适配器
|
||||
const payload = await outPlatform[data.platform].Playload(data)
|
||||
return payload
|
||||
}
|
||||
|
||||
// 获取结果
|
||||
export async function getTask(result) {
|
||||
if (result.code === 0 && result.msg === 'success' && Array.isArray(result.data) && result.data.length > 0) {
|
||||
const urls = result.data.map(item => item.fileUrl)
|
||||
return { type: true, urls: urls }
|
||||
}
|
||||
return { type: false, message: result.data.exception_message || '生成失败' }
|
||||
return data.body
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user