diff --git a/src/platforms/painting/controls/dimension.vue b/src/platforms/painting/controls/dimension.vue
new file mode 100644
index 0000000..1a70538
--- /dev/null
+++ b/src/platforms/painting/controls/dimension.vue
@@ -0,0 +1,247 @@
+
+
+
+
+
+

+
{{ displayText }}
+
+
+
+
+
+
+
+
diff --git a/src/platforms/painting/controls/proportion.vue b/src/platforms/painting/controls/proportion.vue
new file mode 100644
index 0000000..e86235f
--- /dev/null
+++ b/src/platforms/painting/controls/proportion.vue
@@ -0,0 +1,465 @@
+
+
+
+
+
+

+
{{ proportion }}
+
+
+
+
+
+
+
+
diff --git a/src/platforms/painting/controls/quality.vue b/src/platforms/painting/controls/quality.vue
new file mode 100644
index 0000000..d5351ff
--- /dev/null
+++ b/src/platforms/painting/controls/quality.vue
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
diff --git a/src/platforms/painting/controls/quantity.vue b/src/platforms/painting/controls/quantity.vue
new file mode 100644
index 0000000..319a032
--- /dev/null
+++ b/src/platforms/painting/controls/quantity.vue
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
diff --git a/src/platforms/painting/imageUploader.vue b/src/platforms/painting/imageUploader.vue
new file mode 100644
index 0000000..120d2bb
--- /dev/null
+++ b/src/platforms/painting/imageUploader.vue
@@ -0,0 +1,312 @@
+
+
+
+
+
![上传的图片]()
+
{{ index + 1 }}
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/platforms/painting/index.js b/src/platforms/painting/index.js
new file mode 100644
index 0000000..57afbfc
--- /dev/null
+++ b/src/platforms/painting/index.js
@@ -0,0 +1,254 @@
+import { ref, reactive, computed, markRaw } from 'vue'
+import { fetchPlatformModels, getPlatformCode } from '@/utils/modelApi'
+import { getModelConfig } from '@/config/models/index.js'
+import PaintingModelSelector from './modelSelector.vue'
+import PaintingProportion from './controls/proportion.vue'
+import DimensionInput from './controls/dimension.vue'
+import QualitySelect from './controls/quality.vue'
+import Quantity from './controls/quantity.vue'
+import ImageUploader from './imageUploader.vue'
+import { registerPlatform } from '../registry.js'
+
+function getDimConfig(config) {
+ if (!config) return null
+ const dimParam = config.params.find(p => p.ui === 'dimension')
+ if (dimParam) return { type: 'combined', config: dimParam.dimension, paramName: dimParam.name }
+ const wParam = config.params.find(p => p.ui === 'dimensionWidth')
+ const hParam = config.params.find(p => p.ui === 'dimensionHeight')
+ if (wParam && hParam) return { type: 'split', wParam, hParam }
+ return null
+}
+
+export function definePaintingPlatform() {
+ const model = ref('Flux 2')
+ const modelType = ref('text')
+ const proportion = ref('1:1')
+ const resolution = ref('2k')
+ const customWidth = ref(1024)
+ const customHight = ref(1024)
+ const dimWidth = ref(1024)
+ const dimHeight = ref(1024)
+ const quantity = ref(1)
+ const quality = ref('medium')
+ const modelConfig = ref(null)
+ const promptPlaceholder = ref('描述你想生成的画面和动作。')
+ const paramValues = reactive({})
+
+ const state = {
+ model, modelType,
+ proportion, resolution,
+ customWidth, customHight,
+ dimWidth, dimHeight,
+ quantity, quality,
+ paramValues, modelConfig,
+ }
+
+ function syncDefaults(config) {
+ modelConfig.value = config
+ if (!config) return
+ config.params.forEach(p => {
+ if (!(p.name in paramValues)) {
+ paramValues[p.name] = p.default ?? (p.name === 'outputFormat' ? 'png' : '')
+ }
+ })
+ 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
+ const qualityParam = config.params.find(p => p.name === 'quality')
+ if (qualityParam) quality.value = qualityParam.default || 'medium'
+ const dc = getDimConfig(config)
+ if (dc?.type === 'split') {
+ dimWidth.value = dc.wParam.default || 1024
+ dimHeight.value = dc.hParam.default || 1024
+ } else if (dc?.type === 'combined') {
+ const dimParam = config.params.find(p => p.name === dc.paramName)
+ const raw = dimParam?.default || ''
+ const parsed = dc.config.parse(raw)
+ dimWidth.value = parsed.width
+ dimHeight.value = parsed.height
+ }
+ }
+
+ function syncParamValues() {
+ const ratioParam = modelConfig.value?.params?.find(p => p.ui === 'proportion')
+ if (ratioParam) paramValues[ratioParam.name] = proportion.value
+ const resParam = modelConfig.value?.params?.find(p => p.ui === 'resolution')
+ if (resParam) paramValues[resParam.name] = resolution.value
+ const qtyParam = modelConfig.value?.params?.find(p => p.ui === 'quantity')
+ if (qtyParam) paramValues[qtyParam.name] = quantity.value
+ if (modelConfig.value?.params?.find(p => p.name === 'customWidth')) {
+ paramValues.customWidth = customWidth.value
+ }
+ if (modelConfig.value?.params?.find(p => p.name === 'customHight')) {
+ paramValues.customHight = customHight.value
+ }
+ if (modelConfig.value?.params?.find(p => p.name === 'quality')) {
+ paramValues.quality = quality.value
+ }
+ const dc = getDimConfig(modelConfig.value)
+ if (dc?.type === 'split') {
+ paramValues[dc.wParam.name] = dimWidth.value
+ paramValues[dc.hParam.name] = dimHeight.value
+ } else if (dc?.type === 'combined') {
+ paramValues[dc.paramName] = dc.config.format(dimWidth.value, dimHeight.value)
+ }
+ }
+
+ const controls = [
+ {
+ name: 'proportion',
+ component: markRaw(PaintingProportion),
+ show: (config) => !!config?.params?.find(p => p.ui === 'proportion'),
+ props: (config) => {
+ const ratioParam = config?.params?.find(p => p.ui === 'proportion')
+ const resParam = config?.params?.find(p => p.ui === 'resolution')
+ return {
+ modelValue: proportion.value,
+ 'onUpdate:modelValue': (v) => { proportion.value = v },
+ resolution: resolution.value,
+ 'onUpdate:resolution': (v) => { resolution.value = v },
+ width: customWidth.value,
+ 'onUpdate:width': (v) => { customWidth.value = v },
+ height: customHight.value,
+ 'onUpdate:height': (v) => { customHight.value = v },
+ proportionOptions: ratioParam?.options
+ ?.filter(o => o !== 'custom')
+ .map(o => ({ value: o, label: o })) || [],
+ resolutionOptions: resParam?.options
+ ?.map(o => ({ value: o, label: o.toUpperCase() })) || [],
+ allowCustom: ratioParam?.options?.includes('custom') || false,
+ }
+ },
+ },
+ {
+ name: 'dimension',
+ component: markRaw(DimensionInput),
+ show: (config) => !!config?.params?.find(p => p.ui === 'dimension' || p.ui === 'dimensionWidth'),
+ props: (config) => {
+ const dc = getDimConfig(config)
+ return {
+ width: dimWidth.value,
+ 'onUpdate:width': (v) => { dimWidth.value = v },
+ height: dimHeight.value,
+ 'onUpdate:height': (v) => { dimHeight.value = v },
+ minW: dc?.config?.width?.min || dc?.wParam?.min || 256,
+ maxW: dc?.config?.width?.max || dc?.wParam?.max || 6197,
+ minH: dc?.config?.height?.min || dc?.hParam?.min || 256,
+ maxH: dc?.config?.height?.max || dc?.hParam?.max || 4096,
+ }
+ },
+ },
+ {
+ name: 'quality',
+ component: markRaw(QualitySelect),
+ show: (config) => !!config?.params?.find(p => p.name === 'quality'),
+ props: (config) => {
+ const q = config?.params?.find(p => p.name === 'quality')
+ return {
+ modelValue: quality.value,
+ 'onUpdate:modelValue': (v) => { quality.value = v },
+ options: q?.options?.map(o => ({ value: o, label: o })) || [],
+ }
+ },
+ },
+ {
+ name: 'quantity',
+ component: markRaw(Quantity),
+ show: (config) => !!config?.params?.find(p => p.ui === 'quantity'),
+ props: (config) => {
+ const qtyParam = config?.params?.find(p => p.ui === 'quantity')
+ return {
+ modelValue: quantity.value,
+ 'onUpdate:modelValue': (v) => { quantity.value = v },
+ max: qtyParam?.options?.length ? Math.max(...qtyParam.options) : 4,
+ }
+ },
+ },
+ ]
+
+ const platform = {
+ id: 'painting',
+ label: 'AI绘画2026',
+ ModelSelector: markRaw(PaintingModelSelector),
+ modelSelectorProps: null,
+ controls,
+ ImageUploader: markRaw(ImageUploader),
+ state,
+ model,
+ modelType,
+ modelConfig,
+ promptPlaceholder,
+
+ async loadModels() {
+ const code = getPlatformCode('Painting')
+ return fetchPlatformModels(code)
+ },
+
+ async loadConfig(modelName) {
+ const config = getModelConfig(modelName)
+ syncDefaults(config)
+ return config
+ },
+
+ getDefaultModel() {
+ return 'Flux 2'
+ },
+
+ showImageUploader() {
+ if (modelType.value !== 'text') return true
+ return modelConfig.value?.inputType === 'image' || modelConfig.value?.inputType === 'both'
+ },
+
+ imageUploadLimit() {
+ if (!modelConfig.value) return 4
+ const imageParam = modelConfig.value.params.find(p => p.ui === 'imageUpload')
+ return imageParam?.maxCount || modelConfig.value.maxImages || 4
+ },
+
+ isImageRequired() {
+ return !!(modelConfig.value?.params?.find(p => p.ui === 'imageUpload'))
+ },
+
+ buildTaskBody(shared) {
+ syncParamValues()
+ const modelParams = { ...paramValues }
+ if (shared.prompt.value) modelParams.prompt = shared.prompt.value
+ return modelParams
+ },
+
+ fillFromResult(resultData) {
+ if (resultData.model !== undefined) model.value = resultData.model
+ if (resultData.modelType !== undefined) modelType.value = resultData.modelType
+ if (resultData.proportion !== undefined) proportion.value = resultData.proportion
+ 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.quantity !== undefined) quantity.value = resultData.quantity
+ if (resultData.modelParams !== undefined) Object.assign(paramValues, resultData.modelParams)
+ const dc = getDimConfig(modelConfig.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) quality.value = paramValues.quality
+ },
+ }
+
+ return platform
+}
+
+// 自注册
+registerPlatform('Painting', definePaintingPlatform)
diff --git a/src/platforms/painting/modelSelector.vue b/src/platforms/painting/modelSelector.vue
new file mode 100644
index 0000000..ddb0b77
--- /dev/null
+++ b/src/platforms/painting/modelSelector.vue
@@ -0,0 +1,168 @@
+
+
+
+
+
+
+