refactor: 删除旧模型配置文件
- 删除 src/platforms/painting/models/(9 个硬编码 JS) - 删除 src/utils/modelConfig.js(Video 旧远程 JSON 加载) 配置已全部迁移至后端 API。
This commit is contained in:
parent
2cd3f8fad6
commit
5c24de354b
@ -2,7 +2,12 @@
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(git add *)",
|
||||
"Bash(git commit *)"
|
||||
"Bash(git commit *)",
|
||||
"Bash(git -C \"D:/WebUI/Kexue/操作平台/AI_Painting_V2.0\" log --oneline --all -- src/views/home/display/index.vue)",
|
||||
"Bash(git -C \"D:/WebUI/Kexue/操作平台/AI_Painting_V2.0\" log --oneline --all -- src/stores/display.js)",
|
||||
"Bash(git -C \"D:/WebUI/Kexue/操作平台/AI_Painting_V2.0\" log --oneline --all -- src/components/dialogBox/index.vue)",
|
||||
"Bash(git -C \"D:/WebUI/Kexue/操作平台/AI_Painting_V2.0\" log --all --oneline --follow -p -- src/stores/display.js)",
|
||||
"Bash(git -C \"D:/WebUI/Kexue/操作平台/AI_Painting_V2.0\" log --all --oneline -p -- src/components/dialogBox/index.vue)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
1
components.d.ts
vendored
1
components.d.ts
vendored
@ -12,6 +12,7 @@ export {}
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
Canvas: typeof import('./src/components/canvas/index.vue')['default']
|
||||
copy: typeof import('./src/components/virtual-scroller/VirtualScroller copy.vue')['default']
|
||||
DialogBox: typeof import('./src/components/dialogBox/index.vue')['default']
|
||||
ElButton: typeof import('element-plus/es')['ElButton']
|
||||
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
|
||||
|
||||
735
docs/superpowers/plans/2026-06-09-模型参数后端化-前端适配.md
Normal file
735
docs/superpowers/plans/2026-06-09-模型参数后端化-前端适配.md
Normal file
@ -0,0 +1,735 @@
|
||||
# 模型参数后端化 — 前端适配实现计划
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** 将 Painting/Video 平台的模型参数配置从硬编码迁移至后端 API 获取
|
||||
|
||||
**Architecture:** 新建 modelConfigHelper.js 共享工具函数,在 modelApi.js 加缓存层,Painting/Video 双平台统一走 API + params 驱动。方案 A 最小改动。
|
||||
|
||||
**Tech Stack:** Vue 3 Composition API + Vite 7 + Pinia + Axios
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 新建 `src/utils/modelConfigHelper.js`
|
||||
|
||||
**Files:**
|
||||
- Create: `src/utils/modelConfigHelper.js`
|
||||
|
||||
- [ ] **Step 1: 写入完整文件**
|
||||
|
||||
```js
|
||||
// 模型配置共享工具函数
|
||||
// 供 Painting / Video descriptor 使用
|
||||
|
||||
/**
|
||||
* 检测 dimension 配置模式
|
||||
* @param {Object|null} config - 模型配置对象
|
||||
* @returns {Object|null}
|
||||
* - combined: { type: 'combined', config: dimension子对象, paramName: string }
|
||||
* - split: { type: 'split', wParam: 宽度参数, hParam: 高度参数 }
|
||||
* - null: 无 dimension 参数
|
||||
*/
|
||||
export 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
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查 showWhen 条件是否满足
|
||||
* @param {Object} param - 参数定义(可能含 showWhen)
|
||||
* @param {Object} paramValues - 当前所有参数值
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function checkShowWhen(param, paramValues) {
|
||||
if (!param.showWhen) return true
|
||||
return Object.entries(param.showWhen).every(([key, expected]) => {
|
||||
return paramValues[key] === expected
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 API 返回的 config 同步到响应式 state
|
||||
*
|
||||
* state 对象需包含以下属性(均为 ref 或 reactive):
|
||||
* modelConfig, paramValues, proportion, resolution, quantity, quality,
|
||||
* customWidth, customHight, dimWidth, dimHeight, promptPlaceholder
|
||||
*/
|
||||
export function syncDefaults(config, state) {
|
||||
const {
|
||||
modelConfig, paramValues, proportion, resolution, quantity, quality,
|
||||
customWidth, customHight, dimWidth, dimHeight, promptPlaceholder,
|
||||
} = state
|
||||
|
||||
modelConfig.value = config
|
||||
if (!config) return
|
||||
|
||||
// 1. dimension.separator → 生成 parse/format(在遍历 params 之前完成)
|
||||
config.params.forEach(p => {
|
||||
if (p.ui === 'dimension' && p.dimension?.separator && !p.dimension.parse) {
|
||||
const sep = p.dimension.separator
|
||||
p.dimension.parse = (val) => {
|
||||
const parts = (val || '').split(sep)
|
||||
return { width: parseInt(parts[0]) || 0, height: parseInt(parts[1]) || 0 }
|
||||
}
|
||||
p.dimension.format = (w, h) => `${w}${sep}${h}`
|
||||
}
|
||||
})
|
||||
|
||||
// 2. 初始化 paramValues(已存在的 key 保留,避免切换模型时丢失值)
|
||||
config.params.forEach(p => {
|
||||
if (!(p.name in paramValues)) {
|
||||
paramValues[p.name] = p.default ?? (p.name === 'outputFormat' ? 'png' : '')
|
||||
}
|
||||
})
|
||||
|
||||
// 3. 同步专用 ref
|
||||
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'
|
||||
|
||||
// 4. dimension 初始化
|
||||
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
|
||||
}
|
||||
|
||||
// 5. promptPlaceholder 同步
|
||||
if (config.promptPlaceholder) {
|
||||
promptPlaceholder.value = config.promptPlaceholder
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将专用 ref 的当前值回写到 paramValues
|
||||
* (在 buildTaskBody 之前调用)
|
||||
*/
|
||||
export function syncParamValues(config, state) {
|
||||
const {
|
||||
paramValues, proportion, resolution, quantity,
|
||||
customWidth, customHight, dimWidth, dimHeight, quality,
|
||||
} = state
|
||||
|
||||
const ratioParam = config?.params?.find(p => p.ui === 'proportion')
|
||||
if (ratioParam) paramValues[ratioParam.name] = proportion.value
|
||||
|
||||
const resParam = config?.params?.find(p => p.ui === 'resolution')
|
||||
if (resParam) paramValues[resParam.name] = resolution.value
|
||||
|
||||
const qtyParam = config?.params?.find(p => p.ui === 'quantity')
|
||||
if (qtyParam) paramValues[qtyParam.name] = quantity.value
|
||||
|
||||
if (config?.params?.find(p => p.name === 'customWidth')) {
|
||||
paramValues.customWidth = customWidth.value
|
||||
}
|
||||
if (config?.params?.find(p => p.name === 'customHight')) {
|
||||
paramValues.customHight = customHight.value
|
||||
}
|
||||
if (config?.params?.find(p => p.name === 'quality')) {
|
||||
paramValues.quality = quality.value
|
||||
}
|
||||
|
||||
const dc = getDimConfig(config)
|
||||
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)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 验证语法**
|
||||
|
||||
Run: `npx eslint src/utils/modelConfigHelper.js --fix`
|
||||
Expected: 无错误
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add src/utils/modelConfigHelper.js
|
||||
git commit -m "feat: 新增 modelConfigHelper 共享工具函数
|
||||
|
||||
提取 getDimConfig / checkShowWhen / syncDefaults / syncParamValues,
|
||||
供 Painting 和 Video 平台共用。syncDefaults 新增 dimension.separator
|
||||
解析和 promptPlaceholder 同步能力。"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: 新增 API 函数
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/apis/display/index.js`
|
||||
|
||||
- [ ] **Step 1: 在文件末尾追加两个 API 函数**
|
||||
|
||||
```js
|
||||
// 批量获取模型配置(POST /suanli/v1/models/configs)
|
||||
export function requestModelConfigsBatch(modelIds) {
|
||||
return service.post('/suanli/v1/models/configs', { modelIds })
|
||||
}
|
||||
|
||||
// 单条查询模型配置(GET /suanli/v1/models/:modelId/config)
|
||||
export function requestModelConfig(modelId) {
|
||||
return service.get(`/suanli/v1/models/${modelId}/config`)
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 验证语法**
|
||||
|
||||
Run: `npx eslint src/apis/display/index.js --fix`
|
||||
Expected: 无错误
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add src/apis/display/index.js
|
||||
git commit -m "feat: 新增模型配置 API(批量 + 单条)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: 添加缓存层
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/utils/modelApi.js`
|
||||
|
||||
- [ ] **Step 1: 在文件末尾追加缓存相关函数**
|
||||
|
||||
在 `clearPlatformModelCache` 函数之前插入以下代码:
|
||||
|
||||
```js
|
||||
// ==================== 模型配置缓存 ====================
|
||||
|
||||
const CONFIG_CACHE_PREFIX = 'model_config_'
|
||||
const CONFIG_CACHE_TTL = 60 * 1000 // 60 秒
|
||||
const pendingConfigRequests = new Map()
|
||||
|
||||
// 导入 API 函数(在文件顶部添加)
|
||||
// import { requestModelConfigsBatch, requestModelConfig } from '@/apis/display/index.js'
|
||||
|
||||
/**
|
||||
* 批量预加载模型配置到缓存
|
||||
* @param {string[]} modelIds - 模型 UUID 列表
|
||||
*/
|
||||
export async function preloadModelConfigs(modelIds) {
|
||||
if (!modelIds.length) return
|
||||
const result = await requestModelConfigsBatch(modelIds)
|
||||
const data = result?.data || {}
|
||||
const now = Date.now()
|
||||
modelIds.forEach(id => {
|
||||
const config = data[id]
|
||||
if (config) {
|
||||
const cacheEntry = { config, timestamp: now }
|
||||
try {
|
||||
localStorage.setItem(CONFIG_CACHE_PREFIX + id, JSON.stringify(cacheEntry))
|
||||
} catch { /* localStorage 满时静默失败 */ }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单个模型配置(优先读缓存,未命中调 API)
|
||||
* @param {string} modelId - 模型 UUID
|
||||
* @returns {Promise<Object|null>} 模型配置对象
|
||||
*/
|
||||
export async function getModelConfig(modelId) {
|
||||
if (!modelId) return null
|
||||
|
||||
// 1. 读缓存
|
||||
try {
|
||||
const cached = localStorage.getItem(CONFIG_CACHE_PREFIX + modelId)
|
||||
if (cached) {
|
||||
const { config, timestamp } = JSON.parse(cached)
|
||||
if (Date.now() - timestamp < CONFIG_CACHE_TTL) {
|
||||
return config
|
||||
}
|
||||
}
|
||||
} catch { /* 缓存解析失败,走 API */ }
|
||||
|
||||
// 2. 并发去重
|
||||
if (pendingConfigRequests.has(modelId)) {
|
||||
return pendingConfigRequests.get(modelId)
|
||||
}
|
||||
|
||||
// 3. 调单条 API
|
||||
const promise = (async () => {
|
||||
try {
|
||||
const result = await requestModelConfig(modelId)
|
||||
const config = result?.data
|
||||
if (config) {
|
||||
const cacheEntry = { config, timestamp: Date.now() }
|
||||
try {
|
||||
localStorage.setItem(CONFIG_CACHE_PREFIX + modelId, JSON.stringify(cacheEntry))
|
||||
} catch { /* 静默 */ }
|
||||
}
|
||||
return config || null
|
||||
} catch {
|
||||
return null
|
||||
} finally {
|
||||
pendingConfigRequests.delete(modelId)
|
||||
}
|
||||
})()
|
||||
|
||||
pendingConfigRequests.set(modelId, promise)
|
||||
return promise
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 在文件顶部追加 import**
|
||||
|
||||
在 `import { fetchPlatformModels as _fetchPlatformModels } from '@/apis/display/index.js'` 所在行(或其附近),改为同时导入新 API 函数。如果是按需导入,在已有 import 语句中加入 `requestModelConfigsBatch, requestModelConfig`:
|
||||
|
||||
```js
|
||||
import { fetchPlatformModels as _fetchPlatformModels, requestModelConfigsBatch, requestModelConfig } from '@/apis/display/index.js'
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 验证语法**
|
||||
|
||||
Run: `npx eslint src/utils/modelApi.js --fix`
|
||||
Expected: 无错误
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add src/utils/modelApi.js
|
||||
git commit -m "feat: 新增模型配置缓存层(60s TTL + 并发去重)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: Painting 平台接入 API
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/platforms/painting/index.js`
|
||||
|
||||
- [ ] **Step 1: 替换 import**
|
||||
|
||||
将:
|
||||
```js
|
||||
import { fetchPlatformModels, getPlatformCode } from '@/utils/modelApi'
|
||||
import { getModelConfig } from './models/index.js'
|
||||
```
|
||||
|
||||
替换为:
|
||||
```js
|
||||
import { fetchPlatformModels, getPlatformCode, getModelId, getModelConfig, preloadModelConfigs } from '@/utils/modelApi'
|
||||
import { getDimConfig, checkShowWhen, syncDefaults as _syncDefaults, syncParamValues as _syncParamValues } from '@/utils/modelConfigHelper.js'
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 新增 paintingState + 删除 getDimConfig**
|
||||
|
||||
删除 painting/index.js 中第 12-20 行的 `getDimConfig` 函数(已在 helper 中)。
|
||||
|
||||
- [ ] **Step 3: 替换 `syncDefaults` 函数**
|
||||
|
||||
将第 46-77 行的 `syncDefaults` 函数替换为对 helper 的调用:
|
||||
|
||||
```js
|
||||
function syncDefaults(config) {
|
||||
syncDefaults_internal(config, paintingState)
|
||||
}
|
||||
```
|
||||
|
||||
实际上,直接用 helper 版本替换原来的内部函数。由于 helper 的 `syncDefaults` 接受 `(config, state)`,在 descriptor 内部创建一个包装:
|
||||
|
||||
删除原 `syncDefaults` 函数(第 46-77 行),改为:
|
||||
|
||||
```js
|
||||
// state 对象供 helper 函数使用
|
||||
const paintingState = {
|
||||
modelConfig, paramValues, proportion, resolution, quantity, quality,
|
||||
customWidth, customHight, dimWidth, dimHeight, promptPlaceholder,
|
||||
}
|
||||
|
||||
function syncDefaults(config) {
|
||||
_syncDefaults(config, paintingState)
|
||||
}
|
||||
```
|
||||
|
||||
同时将 helper 的 `syncDefaults` 以别名导入(避免与本地函数重名):
|
||||
|
||||
```js
|
||||
import { getDimConfig, checkShowWhen, syncDefaults as _syncDefaults, syncParamValues as _syncParamValues } from '@/utils/modelConfigHelper.js'
|
||||
```
|
||||
|
||||
- [ ] **Step 4: 替换 `syncParamValues` 函数**
|
||||
|
||||
将第 79-102 行的 `syncParamValues` 函数替换为:
|
||||
|
||||
```js
|
||||
function syncParamValues() {
|
||||
_syncParamValues(modelConfig.value, paintingState)
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 5: 替换 `loadConfig` 函数**
|
||||
|
||||
将第 194-198 行的:
|
||||
```js
|
||||
async loadConfig(modelName, _modelType) {
|
||||
const config = getModelConfig(modelName)
|
||||
syncDefaults(config)
|
||||
return config
|
||||
},
|
||||
```
|
||||
|
||||
替换为:
|
||||
```js
|
||||
async loadConfig(modelName, _modelType) {
|
||||
const modelId = await getModelId('Painting', modelName)
|
||||
if (!modelId) return null
|
||||
const config = await getModelConfig(modelId)
|
||||
syncDefaults(config)
|
||||
return config
|
||||
},
|
||||
```
|
||||
|
||||
- [ ] **Step 6: 替换 `loadModels` 函数,加入批量预加载**
|
||||
|
||||
将第 189-192 行的:
|
||||
```js
|
||||
async loadModels() {
|
||||
const code = getPlatformCode('Painting')
|
||||
return fetchPlatformModels(code)
|
||||
},
|
||||
```
|
||||
|
||||
替换为:
|
||||
```js
|
||||
async loadModels() {
|
||||
const code = getPlatformCode('Painting')
|
||||
const models = await fetchPlatformModels(code)
|
||||
if (models?.length) {
|
||||
const modelIds = models.map(m => m.id)
|
||||
await preloadModelConfigs(modelIds)
|
||||
}
|
||||
return models
|
||||
},
|
||||
```
|
||||
|
||||
- [ ] **Step 7: 在 controls 的 `show()` 中加入 showWhen 判断**
|
||||
|
||||
dimension control 的 `show`(第 133 行)改为:
|
||||
|
||||
```js
|
||||
show: (config) => {
|
||||
const hasDim = config?.params?.find(p =>
|
||||
(p.ui === 'dimension' || p.ui === 'dimensionWidth') && checkShowWhen(p, paramValues)
|
||||
)
|
||||
return !!hasDim
|
||||
},
|
||||
```
|
||||
|
||||
- [ ] **Step 8: 验证语法**
|
||||
|
||||
Run: `npx eslint src/platforms/painting/index.js --fix`
|
||||
Expected: 无错误
|
||||
|
||||
- [ ] **Step 9: Commit**
|
||||
|
||||
```bash
|
||||
git add src/platforms/painting/index.js
|
||||
git commit -m "feat: Painting 平台接入模型配置 API
|
||||
|
||||
- loadModels 增加批量预加载模型配置
|
||||
- loadConfig 改为 API 获取(替代硬编码 getModelConfig)
|
||||
- syncDefaults/syncParamValues/getDimConfig 迁移至 helper
|
||||
- controls show() 加入 showWhen 条件判断"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 5: Video 平台接入 API
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/platforms/video/index.js`
|
||||
|
||||
- [ ] **Step 1: 替换 import**
|
||||
|
||||
将:
|
||||
```js
|
||||
import { fetchModelConfig } from '@/utils/modelConfig'
|
||||
import { fetchPlatformModels, getPlatformCode } from '@/utils/modelApi'
|
||||
```
|
||||
|
||||
替换为:
|
||||
```js
|
||||
import { fetchPlatformModels, getPlatformCode, getModelId, getModelConfig, preloadModelConfigs } from '@/utils/modelApi'
|
||||
import { getDimConfig, checkShowWhen, syncDefaults as _syncDefaults, syncParamValues as _syncParamValues } from '@/utils/modelConfigHelper.js'
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 增加 params 驱动的 state**
|
||||
|
||||
在 `defineVideoPlatform` 函数内部,原有 state ref 定义之后(第 33-35 行之后),追加:
|
||||
|
||||
```js
|
||||
// params 驱动(与 Painting 统一)
|
||||
const paramValues = reactive({})
|
||||
const modelConfig = ref(null)
|
||||
const quality = ref('medium')
|
||||
const customWidth = ref(1024)
|
||||
const customHight = ref(1024)
|
||||
const dimWidth = ref(1024)
|
||||
const dimHeight = ref(1024)
|
||||
const quantity = ref(1)
|
||||
|
||||
const paintingCompatState = {
|
||||
modelConfig, paramValues, proportion, resolution, quantity, quality,
|
||||
customWidth, customHight, dimWidth, dimHeight, promptPlaceholder,
|
||||
}
|
||||
|
||||
function syncDefaults(config) {
|
||||
_syncDefaults(config, paintingCompatState)
|
||||
}
|
||||
|
||||
function syncParamValues() {
|
||||
_syncParamValues(modelConfig.value, paintingCompatState)
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 替换 `loadInternalConfig` 和 `loadConfig`**
|
||||
|
||||
删除 `loadInternalConfig` 函数(第 45-67 行)。
|
||||
|
||||
将 `loadConfig`(第 122-124 行)替换为:
|
||||
|
||||
```js
|
||||
async loadConfig(modelName, modelTypeVal) {
|
||||
const modelId = await getModelId('Video', modelName)
|
||||
if (!modelId) return null
|
||||
const config = await getModelConfig(modelId)
|
||||
syncDefaults(config)
|
||||
return config
|
||||
},
|
||||
```
|
||||
|
||||
- [ ] **Step 4: 替换 `loadModels` 加入批量预加载**
|
||||
|
||||
将第 117-120 行替换为:
|
||||
|
||||
```js
|
||||
async loadModels() {
|
||||
const code = getPlatformCode('Video')
|
||||
const models = await fetchPlatformModels(code)
|
||||
if (models?.length) {
|
||||
const modelIds = models.map(m => m.id)
|
||||
await preloadModelConfigs(modelIds)
|
||||
}
|
||||
return models
|
||||
},
|
||||
```
|
||||
|
||||
- [ ] **Step 5: 替换 `showImageUploader` 和 `isImageRequired`**
|
||||
|
||||
将第 141-143 行的:
|
||||
```js
|
||||
showImageUploader() {
|
||||
return modelType.value !== 'text'
|
||||
},
|
||||
```
|
||||
|
||||
替换为:
|
||||
```js
|
||||
showImageUploader() {
|
||||
return modelConfig.value?.inputType === 'image' || modelConfig.value?.inputType === 'both'
|
||||
},
|
||||
```
|
||||
|
||||
将第 149-151 行的:
|
||||
```js
|
||||
isImageRequired() {
|
||||
return modelType.value !== 'text'
|
||||
},
|
||||
```
|
||||
|
||||
替换为:
|
||||
```js
|
||||
isImageRequired() {
|
||||
return !!(modelConfig.value?.params?.find(p => p.ui === 'imageUpload'))
|
||||
},
|
||||
```
|
||||
|
||||
- [ ] **Step 6: 替换 `imageUploadLimit`**
|
||||
|
||||
将第 145-147 行替换为:
|
||||
|
||||
```js
|
||||
imageUploadLimit() {
|
||||
if (!modelConfig.value) return 4
|
||||
const imageParam = modelConfig.value.params.find(p => p.ui === 'imageUpload')
|
||||
return imageParam?.maxCount || modelConfig.value.maxImages || 4
|
||||
},
|
||||
```
|
||||
|
||||
- [ ] **Step 7: 替换 `buildTaskBody`**
|
||||
|
||||
将第 153-162 行替换为:
|
||||
|
||||
```js
|
||||
buildTaskBody(shared) {
|
||||
syncParamValues()
|
||||
const modelParams = { ...paramValues }
|
||||
if (shared.prompt.value) modelParams.prompt = shared.prompt.value
|
||||
return modelParams
|
||||
},
|
||||
```
|
||||
|
||||
- [ ] **Step 8: 替换 `modelDisplayConfig` → `modelConfig`**
|
||||
|
||||
在 platform 对象中,将 `modelDisplayConfig` 替换为 `modelConfig`(第 114 行附近)。
|
||||
|
||||
在 `fillFromResult` 中保持不变(不涉及 config 字段)。
|
||||
|
||||
- [ ] **Step 9: 验证语法**
|
||||
|
||||
Run: `npx eslint src/platforms/video/index.js --fix`
|
||||
Expected: 无错误
|
||||
|
||||
- [ ] **Step 10: Commit**
|
||||
|
||||
```bash
|
||||
git add src/platforms/video/index.js
|
||||
git commit -m "feat: Video 平台接入模型配置 API
|
||||
|
||||
- loadModels 增加批量预加载
|
||||
- loadConfig 改为 API 获取(替代 modelConfig.js)
|
||||
- buildTaskBody 改为 params 驱动
|
||||
- showImageUploader/isImageRequired 改为 inputType 驱动
|
||||
- modelDisplayConfig 统一为 modelConfig"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 6: 移除 createTask 依赖
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/utils/taskPolling.js`
|
||||
- Delete: `src/utils/createTask.js`
|
||||
|
||||
- [ ] **Step 1: 确认 createTask 的引用点**
|
||||
|
||||
`taskPolling.js:4` import + `taskPolling.js:93-94` 调用。`createTask(data)` 只返回 `data.body`(纯透传)。
|
||||
|
||||
- [ ] **Step 2: 修改 taskPolling.js**
|
||||
|
||||
删除第 4 行的 import:
|
||||
```js
|
||||
import { createTask } from '@/utils/createTask'
|
||||
```
|
||||
|
||||
将第 93-94 行的:
|
||||
```js
|
||||
// 通过 createTask 获取 body 内容(RunningHub workflow payload)
|
||||
const body = await createTask(data)
|
||||
```
|
||||
|
||||
替换为:
|
||||
```js
|
||||
const body = data.body
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 删除 createTask.js**
|
||||
|
||||
```bash
|
||||
rm src/utils/createTask.js
|
||||
```
|
||||
|
||||
- [ ] **Step 4: 验证语法**
|
||||
|
||||
Run: `npx eslint src/utils/taskPolling.js --fix`
|
||||
Expected: 无错误
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add src/utils/taskPolling.js
|
||||
git rm src/utils/createTask.js
|
||||
git commit -m "refactor: 移除 createTask 透传层,taskPolling 直接读 data.body"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 7: 删除旧配置文件
|
||||
|
||||
**Files:**
|
||||
- Delete: `src/platforms/painting/models/`(整个目录,9 个文件)
|
||||
- Delete: `src/utils/modelConfig.js`
|
||||
|
||||
- [ ] **Step 1: 确认无其他引用**
|
||||
|
||||
`src/platforms/painting/models/index.js` — 仅被 painting/index.js 引用(已在 Task 4 移除 import)
|
||||
`src/utils/modelConfig.js` — 仅被 video/index.js 引用(已在 Task 5 移除 import)
|
||||
|
||||
- [ ] **Step 2: 删除文件**
|
||||
|
||||
```bash
|
||||
rm -rf src/platforms/painting/models/
|
||||
rm src/utils/modelConfig.js
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "refactor: 删除 Painting 硬编码模型配置和 Video 旧 config 加载
|
||||
|
||||
- 删除 src/platforms/painting/models/(9 个硬编码 JS)
|
||||
- 删除 src/utils/modelConfig.js(Video 旧远程 JSON 加载)
|
||||
配置已全部迁移至后端 API。"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 8: 验证
|
||||
|
||||
- [ ] **Step 1: ESLint 全量检查**
|
||||
|
||||
Run: `npx eslint src/ --fix`
|
||||
Expected: 无错误
|
||||
|
||||
- [ ] **Step 2: 启动开发服务器**
|
||||
|
||||
Run: `pnpm dev`
|
||||
Expected: Vite 启动成功,无编译错误
|
||||
|
||||
- [ ] **Step 3: 功能冒烟**
|
||||
|
||||
在浏览器中验证:
|
||||
- Painting 平台模型列表正常加载
|
||||
- 切换模型后控件正常渲染(参数来自 API)
|
||||
- 生成任务提交正常
|
||||
- Video 平台(暂无模型)不报错
|
||||
|
||||
- [ ] **Step 4: Commit(如有 lint 修复)**
|
||||
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "chore: ESLint 修复"
|
||||
```
|
||||
147
docs/superpowers/specs/2026-06-09-模型参数后端化-前端适配-design.md
Normal file
147
docs/superpowers/specs/2026-06-09-模型参数后端化-前端适配-design.md
Normal file
@ -0,0 +1,147 @@
|
||||
# 模型参数后端化 — 前端适配设计
|
||||
|
||||
## 概述
|
||||
|
||||
将 Painting/Video 平台的模型参数配置从**前端代码硬编码**迁移至**后端 API 获取**,实现新增模型或修改参数无需前端发版。
|
||||
|
||||
方案 A:最小改动,只替换配置来源,保持现有架构不变。
|
||||
|
||||
## 一、数据流
|
||||
|
||||
```
|
||||
页面加载 → platform.loadModels()
|
||||
→ fetchPlatformModels(code) // 已有
|
||||
→ 提取所有 modelId
|
||||
→ POST /suanli/v1/models/configs // 新增,批量获取
|
||||
→ 返回 { "uuid1": { config }, ... }
|
||||
→ 逐条写入 60s localStorage 缓存
|
||||
|
||||
用户切换模型 → platform.loadConfig(modelName)
|
||||
→ getModelId(type, modelName) // 已有
|
||||
→ getModelConfig(modelId) // 新增,优先缓存 → fallback 单条 API
|
||||
→ syncDefaults(config)
|
||||
→ modelConfig.value = config
|
||||
→ 遍历 params 初始化 paramValues + 专用 ref
|
||||
→ dimension.separator → 生成 parse/format
|
||||
→ promptPlaceholder 同步
|
||||
→ visibleControls 更新(含 showWhen 条件判断)
|
||||
→ 用户填写参数 → buildTaskBody() → 扁平 modelParams → POST 创建任务
|
||||
```
|
||||
|
||||
## 二、API 层
|
||||
|
||||
### 新增 API 函数(`src/apis/display/index.js`)
|
||||
|
||||
```js
|
||||
// 批量获取模型配置
|
||||
export function requestModelConfigsBatch(modelIds) {
|
||||
return service.post('/suanli/v1/models/configs', { modelIds })
|
||||
}
|
||||
|
||||
// 单条查询(缓存未命中 fallback)
|
||||
export function requestModelConfig(modelId) {
|
||||
return service.get(`/suanli/v1/models/${modelId}/config`)
|
||||
}
|
||||
```
|
||||
|
||||
### 缓存层(`src/utils/modelApi.js`)
|
||||
|
||||
新增 `getModelConfig(modelId)`:
|
||||
|
||||
- 优先读 localStorage(key: `model_config_{modelId}`,TTL 60 秒)
|
||||
- 未命中调 `requestModelConfig()` + 写入缓存
|
||||
- `pendingRequests` Map 并发去重
|
||||
|
||||
新增 `preloadModelConfigs(modelIds)`:
|
||||
|
||||
- 调用 `requestModelConfigsBatch(modelIds)`
|
||||
- 逐条写入 localStorage 缓存
|
||||
|
||||
## 三、共享工具函数
|
||||
|
||||
新建 `src/utils/modelConfigHelper.js`:
|
||||
|
||||
| 导出函数 | 说明 |
|
||||
|---------|------|
|
||||
| `syncDefaults(config, state)` | params → paramValues + 专用 ref(proportion/resolution/quantity/dimension/quality) |
|
||||
| `syncParamValues(config, state)` | 专用 ref 回写到 paramValues |
|
||||
| `getDimConfig(config)` | 检测 combined/split 模式,返回 dimension 配置 |
|
||||
| `checkShowWhen(param, paramValues)` | 检查 showWhen 条件是否满足 |
|
||||
|
||||
### `syncDefaults` 增强
|
||||
|
||||
1. dimension.separator → 生成 `parse/format` 函数(替代硬编码的 JS 函数)
|
||||
2. `config.promptPlaceholder` → 同步到 `promptPlaceholder.value`
|
||||
3. customWidth/customHight 继续通过 `p.name` 查找(保持现有硬编码兼容)
|
||||
|
||||
## 四、Painting 平台改造
|
||||
|
||||
`src/platforms/painting/index.js`:
|
||||
|
||||
- `loadConfig()` 改为 `getModelId()` + `getModelConfig()` API 调用
|
||||
- `syncDefaults`/`syncParamValues`/`getDimConfig` 改为从 helper 导入
|
||||
- 移除 `import { getModelConfig } from './models/index.js'`
|
||||
- controls 的 `show()` 加入 `checkShowWhen` 判断
|
||||
|
||||
## 五、Video 平台改造
|
||||
|
||||
`src/platforms/video/index.js`:
|
||||
|
||||
| 项目 | 当前 | 改造后 |
|
||||
|------|------|--------|
|
||||
| 配置来源 | `modelConfig.js` → 远程 JSON | 统一 API |
|
||||
| 配置存储 | `modelDisplayConfig` | 统一 `modelConfig` |
|
||||
| 数据结构 | `config.display.*` | 统一 `params[]` 数组 |
|
||||
| `loadConfig` | `loadInternalConfig` | 改为 API + `syncDefaults` |
|
||||
| `buildTaskBody` | 硬编码 5 字段 | 改为 params 驱动(与 Painting 一致) |
|
||||
| `showImageUploader` | `modelType !== 'text'` | 改为 `config.inputType` 驱动 |
|
||||
| controls | pattern/proportion/time | 改为按 `params[]` 的 `ui` 驱动 |
|
||||
|
||||
Video 现有的 pattern/time 控件对应的 ui 值暂未定义,保留占位,等后端配置数据就绪后再适配。
|
||||
|
||||
## 六、showWhen 条件显示
|
||||
|
||||
`checkShowWhen(param, paramValues)` 检查 param 的 `showWhen` 字段:
|
||||
|
||||
```js
|
||||
// 例如 { aspectRatio: 'custom' } → 仅在 paramValues.aspectRatio === 'custom' 时显示
|
||||
showWhen 为空 → 总是显示
|
||||
showWhen 存在 → 所有 key-value 匹配才显示
|
||||
```
|
||||
|
||||
controls 的 `show()` 中调用,因为直接读取 `paramValues[key]`(reactive),Vue computed 自动追踪依赖。
|
||||
|
||||
## 七、文件清理
|
||||
|
||||
| 路径 | 操作 |
|
||||
|------|------|
|
||||
| `src/utils/modelConfigHelper.js` | **新建** |
|
||||
| `src/platforms/painting/models/`(9 文件) | **删除** |
|
||||
| `src/utils/modelConfig.js` | **删除** |
|
||||
| `src/utils/createTask.js` | **删除** |
|
||||
|
||||
删除前需确认 `createTask.js` 无其他文件 import。
|
||||
|
||||
## 八、API 验证发现
|
||||
|
||||
已用 token 实测 Painting 全部 8 个模型,结论:
|
||||
|
||||
| 发现 | 结论 |
|
||||
|------|------|
|
||||
| 单条 API | `GET /suanli/v1/models/:id/config` → `data` 直接返回 config |
|
||||
| 批量 API | `POST /suanli/v1/models/configs` → `data` 为 `{ modelId: config, ... }`,不存在的 ID 返回空 `{}` |
|
||||
| `dimension.separator` | 字符串 `"*"`,需前端 `syncDefaults` 生成 parse/format |
|
||||
| `dimensionWidth/Height` 的 `min/max` | 在 param 根层,与硬编码结构一致,`getDimConfig` 直接兼容 |
|
||||
| `imageUpload` 的 `maxCount` | 在 param 根层,`imageUploadLimit()` 直接兼容 |
|
||||
| `number` 类型的 `min/max` | 在 param 根层,与 dimensionWidth 一致 |
|
||||
| customWidth/customHight | `ui: 'number'`,`showWhen: {"aspectRatio": "custom"}` |
|
||||
| hidden 参数 | 不返回 `options` 字段(与硬编码不同),不影响功能 |
|
||||
| `type` 字段 | API 不返回,前端 `syncDefaults` 不依赖,无影响 |
|
||||
| Video 平台 | 模型列表为空,改造后无 fallback 将不可用 |
|
||||
|
||||
## 九、不改动的部分
|
||||
|
||||
- `src/components/dialogBox/index.vue`:通过 `platform.loadConfig()` 多态调用,改动仅在 descriptor 内部
|
||||
- `src/utils/modelApi.js`:`getModelId()`/`fetchPlatformModels()` 保持不变,新增两个函数
|
||||
- controls 组件(proportion.vue / dimension.vue / quality.vue / quantity.vue):UI 不变
|
||||
- `buildTaskBody()` / `fillFromResult()`:逻辑不变
|
||||
@ -1,282 +0,0 @@
|
||||
# 模型参数后端化方案
|
||||
|
||||
## 一、当前架构(平台重构后)
|
||||
|
||||
平台重构已采用 **Platform Descriptor 模式**,Painting 和 Video 统一走扁平 `modelParams`:
|
||||
|
||||
```
|
||||
src/platforms/
|
||||
├── registry.js # 注册表:registerPlatform() + createPlatform()
|
||||
├── painting/
|
||||
│ ├── index.js # Painting descriptor(controls, state, loadConfig 等)
|
||||
│ ├── modelSelector.vue
|
||||
│ ├── imageUploader.vue
|
||||
│ ├── models/ # 模型参数 schema(本地 JS 文件,待后端化)
|
||||
│ │ ├── index.js # getModelConfig(modelName) → 查找本地 config
|
||||
│ │ └── flux-2.js # 单个模型的 params 定义
|
||||
│ └── controls/
|
||||
│ ├── proportion.vue
|
||||
│ ├── dimension.vue
|
||||
│ ├── quality.vue
|
||||
│ └── quantity.vue
|
||||
└── video/
|
||||
├── index.js # Video descriptor(仍用 fetchModelConfig 拉远程 JSON)
|
||||
├── modelSelector.vue
|
||||
├── imageUploader.vue
|
||||
└── controls/
|
||||
├── pattern.vue
|
||||
├── proportion.vue
|
||||
└── time.vue
|
||||
```
|
||||
|
||||
**核心数据流(两个平台统一):**
|
||||
|
||||
1. 用户选择模型 → `dialogBox` 调用 `platform.loadConfig(modelName, modelType)`
|
||||
2. `loadConfig` 获取参数 schema → 驱动 `controls` 数组渲染对应 UI 控件
|
||||
3. 用户点击发送 → `platform.buildTaskBody({ prompt, referenceImages })` 返回扁平 `modelParams`
|
||||
4. `createTask.js` 透传 `data.body`(不再做任何转换)
|
||||
5. POST `/suanli/v1/tasks`,携带 `X-Session-Id` header
|
||||
|
||||
**当前参数配置来源(待统一):**
|
||||
|
||||
| 平台 | 参数配置来源 | 位置 |
|
||||
|------|-------------|------|
|
||||
| Painting | 本地 JS 文件(硬编码) | `src/platforms/painting/models/*.js` |
|
||||
| Video | 远程静态 JSON(每日 localStorage 缓存) | `fetchModelConfig()` → `resources.xueai.art/AIGC/static/public/Platform/Video/workflows/...` |
|
||||
|
||||
两个平台的 `buildTaskBody()` 均已返回扁平 `modelParams`,不再构造 `{ workflowId, nodeInfoList }` 格式。
|
||||
|
||||
---
|
||||
|
||||
## 二、目标架构
|
||||
|
||||
将模型参数配置从**前端代码**迁移到**后端 API**,实现:
|
||||
|
||||
- 新增模型/修改参数无需前端发版
|
||||
- Painting 和 Video 使用统一的 API 获取配置
|
||||
- 前端只保留 UI 控件库,参数 schema 完全由后端驱动
|
||||
|
||||
```
|
||||
后端 API
|
||||
├── GET /api/v1/platforms/:code/models # 模型列表(已有)
|
||||
│ └── 响应: [{ id, display_name, tags, config? }]
|
||||
├── GET /api/v1/models/:id/config # 模型参数配置(新增)
|
||||
│ └── 响应: { params: [...], inputType, maxImages, ... }
|
||||
└── POST /api/v1/tasks # 创建任务(已有)
|
||||
└── 请求体: 扁平 modelParams(与 RunningHub API 格式对齐)
|
||||
```
|
||||
|
||||
**改造后的数据流:**
|
||||
|
||||
```
|
||||
用户选择模型
|
||||
→ platform.loadConfig(modelName)
|
||||
→ GET /api/v1/models/:id/config
|
||||
→ 返回 { params: [{ name, ui, default, options, ... }] }
|
||||
→ 前端根据 ui 字段渲染对应控件
|
||||
→ 用户填写参数
|
||||
→ buildTaskBody() 返回扁平 { aspectRatio, resolution, ... }
|
||||
→ POST /api/v1/tasks
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、模型配置 API 格式
|
||||
|
||||
### 3.1 请求
|
||||
|
||||
```
|
||||
GET /api/v1/models/:modelId/config
|
||||
```
|
||||
|
||||
`modelId` 来自模型列表接口返回的 `id` 字段(UUID)。
|
||||
|
||||
### 3.2 响应格式
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"inputType": "text",
|
||||
"maxImages": 4,
|
||||
"promptPlaceholder": "描述你想生成的画面和动作。",
|
||||
"params": [
|
||||
{
|
||||
"name": "prompt",
|
||||
"ui": "textarea",
|
||||
"label": "提示词",
|
||||
"required": true,
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "aspectRatio",
|
||||
"ui": "proportion",
|
||||
"label": "比例",
|
||||
"default": "1:1",
|
||||
"options": ["1:1", "3:4", "4:3", "9:16", "16:9", "custom"]
|
||||
},
|
||||
{
|
||||
"name": "resolution",
|
||||
"ui": "resolution",
|
||||
"label": "分辨率",
|
||||
"default": "2k",
|
||||
"options": ["1k", "2k", "4k"]
|
||||
},
|
||||
{
|
||||
"name": "size",
|
||||
"ui": "dimension",
|
||||
"label": "尺寸",
|
||||
"default": "1024*1024",
|
||||
"dimension": {
|
||||
"separator": "*",
|
||||
"width": { "min": 256, "max": 6197 },
|
||||
"height": { "min": 256, "max": 4096 }
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "quality",
|
||||
"ui": "select",
|
||||
"label": "画质",
|
||||
"default": "medium",
|
||||
"options": ["low", "medium", "high"]
|
||||
},
|
||||
{
|
||||
"name": "quantity",
|
||||
"ui": "quantity",
|
||||
"label": "生成数量",
|
||||
"default": 1,
|
||||
"options": [1, 2, 3, 4]
|
||||
},
|
||||
{
|
||||
"name": "imageUrl",
|
||||
"ui": "imageUpload",
|
||||
"label": "参考图",
|
||||
"maxCount": 4
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 `ui` 字段与前端控件映射
|
||||
|
||||
| `ui` 值 | 前端控件 | 说明 |
|
||||
|---------|---------|------|
|
||||
| `textarea` | Sender 内置 textarea | 提示词输入框,不需要额外渲染控件 |
|
||||
| `proportion` | `PaintingProportion` / `VideoProportion` | 比例选择 Popover,`options` 含 `custom` 时允许自定义宽高 |
|
||||
| `resolution` | `proportion` 控件内部 | 分辨率子选项,与 proportion 共用 Popover |
|
||||
| `dimension` | `DimensionInput` | **组合模式**:单字段 `"W*H"` 格式,通过 `dimension.parse/format` 序列化 |
|
||||
| `dimensionWidth` + `dimensionHeight` | `DimensionInput` | **拆分模式**:两个独立字段,共享同一个 Popover 和比例锁 |
|
||||
| `select` | `Select` | 通用下拉选择(如 quality) |
|
||||
| `quantity` | `Quantity` | 生成数量,上限由 `options` 最大值派生 |
|
||||
| `imageUpload` | `ImageUploader` | 参考图上传,`maxCount` 控制数量上限 |
|
||||
| `hidden` | 无 | 静默写入默认值,不渲染控件 |
|
||||
|
||||
> **关于 `dimension.separator`:** 当前前端 Painting 配置中 `parse/format` 为 JS 函数(`s.split('*')` / `` `${w}*${h}` ``),后端返回 JSON 无法携带函数。因此 `dimension` 类型的配置需包含 `separator` 字段,前端据此在运行时生成等价的 parse/format 逻辑,无需硬编码分隔符。拆分模式(`dimensionWidth` + `dimensionHeight`)无此字段。
|
||||
|
||||
### 3.4 条件显示
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "customWidth",
|
||||
"ui": "dimensionWidth",
|
||||
"showWhen": { "aspectRatio": "custom" }
|
||||
}
|
||||
```
|
||||
|
||||
`showWhen` 字段使参数仅在指定条件满足时显示。当前支持的条件:`aspectRatio` 值为 `custom`。
|
||||
|
||||
---
|
||||
|
||||
## 四、迁移步骤
|
||||
|
||||
### 阶段一:后端实现模型配置 API
|
||||
|
||||
- [ ] 设计并实现 `GET /api/v1/models/:id/config` 接口
|
||||
- [ ] 将 Painting 的 9 个模型配置(`src/platforms/painting/models/*.js`)迁移到数据库/配置文件
|
||||
- [ ] 将 Video 的 workflow 配置(`resources.xueai.art/AIGC/static/public/Platform/Video/workflows/...`)迁移到同一接口
|
||||
- [ ] 响应格式对齐 3.2 节的 schema
|
||||
|
||||
### 阶段二:前端适配
|
||||
|
||||
- [ ] `src/platforms/painting/models/` 目录删除,`getModelConfig()` 改为调用 API
|
||||
- [ ] `src/utils/modelConfig.js`(Video 旧架构遗留)删除,Video descriptor 改为调用同一 API
|
||||
- [ ] `src/utils/createTask.js`(当前仅透传)删除,`dialogBox` 直接使用 `buildTaskBody()` 返回值
|
||||
- [ ] Painting descriptor 的 `loadConfig()` 改为 `fetch` API + 本地缓存(30s TTL,与模型列表一致)
|
||||
|
||||
### 阶段三:清理
|
||||
|
||||
- [ ] 删除 `src/utils/modelConfig.js`
|
||||
- [ ] 删除 `src/utils/createTask.js`
|
||||
- [ ] 确认前端不再包含任何硬编码的模型参数
|
||||
|
||||
---
|
||||
|
||||
## 五、与 RunningHub API 的关系
|
||||
|
||||
后端创建任务的请求体格式应**直接对齐 RunningHub 标准模型 API**,前端 `buildTaskBody()` 返回的扁平 `modelParams` 即是 API body:
|
||||
|
||||
```json
|
||||
{
|
||||
"prompt": "...",
|
||||
"resolution": "720p",
|
||||
"aspectRatio": "16:9",
|
||||
"duration": 5
|
||||
}
|
||||
```
|
||||
|
||||
不再需要中间层转换(旧架构的 `src/config/runninghub/` 已删除)。
|
||||
|
||||
---
|
||||
|
||||
## 附录:RunningHub API 参考
|
||||
|
||||
以下为 RunningHub 标准模型 API 的原始文档,作为后端任务创建接口的设计参考。
|
||||
|
||||
### A.1 提交任务
|
||||
|
||||
```curl
|
||||
curl --location --request POST 'https://www.runninghub.cn/openapi/v2/rhart-video/ltx-2.3/text-to-video' \
|
||||
--header "Content-Type: application/json" \
|
||||
--header "Authorization: Bearer ${RUNNINGHUB_API_KEY}" \
|
||||
--data-raw '{
|
||||
"prompt": "...",
|
||||
"resolution": "720p",
|
||||
"aspectRatio": "16:9",
|
||||
"duration": 5
|
||||
}'
|
||||
```
|
||||
|
||||
| 参数 | 类型 | 必填/可选 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `prompt` | String | 必填 | 提示词 |
|
||||
| `resolution` | String | 必填 | 枚举值: [1080p, 720p, 480p] |
|
||||
| `aspectRatio` | String | 必填 | 枚举值: [16:9, 9:16] |
|
||||
| `duration` | Int | 必填 | 输入范围值: 5 - 15 |
|
||||
|
||||
### A.2 查询结果
|
||||
|
||||
```curl
|
||||
curl --location --request POST 'https://www.runninghub.cn/openapi/v2/query' \
|
||||
--header "Content-Type: application/json" \
|
||||
--header "Authorization: Bearer ${RUNNINGHUB_API_KEY}" \
|
||||
--data-raw '{"taskId": "${RUNNINGHUB_TASKID}"}'
|
||||
```
|
||||
|
||||
响应字段:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `taskId` | String | 任务 ID |
|
||||
| `status` | String | QUEUED / RUNNING / SUCCESS / FAILED |
|
||||
| `results` | List | 生成结果列表 |
|
||||
| `results[].url` | String | 结果 URL(24 小时有效) |
|
||||
| `results[].nodeId` | String | 工作流节点 ID |
|
||||
| `results[].outputType` | String | 文件扩展名 (png, mp4, txt) |
|
||||
| `results[].text` | String | 纯文本输出内容 |
|
||||
|
||||
### A.3 文件上传
|
||||
|
||||
**上传接口:** `POST https://www.runninghub.cn/openapi/v2/media/upload/binary`
|
||||
|
||||
支持 `imageUrls`(公共 URL)、Base64 Data URI、RH 上传接口三种方式传入图片。
|
||||
@ -1,51 +0,0 @@
|
||||
// Flux 2 Dev — 文生图
|
||||
export default {
|
||||
name: 'Flux 2',
|
||||
tag: '文生图',
|
||||
inputType: 'text',
|
||||
params: [
|
||||
{
|
||||
name: 'prompt',
|
||||
label: '提示词',
|
||||
type: 'string',
|
||||
required: true,
|
||||
ui: 'textarea',
|
||||
},
|
||||
{
|
||||
name: 'aspectRatio',
|
||||
label: '比例',
|
||||
type: 'select',
|
||||
default: '1:1',
|
||||
options: ['1:1', '3:4', '4:3', '9:16', '16:9', '2:3', '3:2', 'custom'],
|
||||
ui: 'proportion',
|
||||
},
|
||||
{
|
||||
name: 'customWidth',
|
||||
label: '宽度',
|
||||
type: 'number',
|
||||
default: 1024,
|
||||
min: 256,
|
||||
max: 1536,
|
||||
ui: 'number',
|
||||
showWhen: { aspectRatio: 'custom' },
|
||||
},
|
||||
{
|
||||
name: 'customHight',
|
||||
label: '高度',
|
||||
type: 'number',
|
||||
default: 1024,
|
||||
min: 256,
|
||||
max: 1536,
|
||||
ui: 'number',
|
||||
showWhen: { aspectRatio: 'custom' },
|
||||
},
|
||||
{
|
||||
name: 'outputFormat',
|
||||
label: '输出格式',
|
||||
type: 'string',
|
||||
default: 'png',
|
||||
options: ['png', 'jpeg', 'webp(lossless)', 'webp(lossy)'],
|
||||
ui: 'hidden',
|
||||
},
|
||||
],
|
||||
}
|
||||
@ -1,48 +0,0 @@
|
||||
// GPT-Image-2 I2I — 图片编辑
|
||||
export default {
|
||||
name: 'GPT-Image-2 I2I',
|
||||
tag: '图片编辑',
|
||||
inputType: 'image',
|
||||
maxImages: 10,
|
||||
params: [
|
||||
{
|
||||
name: 'imageUrls',
|
||||
label: '参考图片',
|
||||
type: 'image',
|
||||
required: true,
|
||||
ui: 'imageUpload',
|
||||
maxCount: 10,
|
||||
},
|
||||
{
|
||||
name: 'prompt',
|
||||
label: '编辑指令',
|
||||
type: 'string',
|
||||
required: true,
|
||||
ui: 'textarea',
|
||||
},
|
||||
{
|
||||
name: 'aspectRatio',
|
||||
label: '比例',
|
||||
type: 'select',
|
||||
default: '1:1',
|
||||
options: ['1:1', '1:2', '2:1', '1:3', '3:1', '2:3', '3:2', '3:4', '4:3', '4:5', '5:4', '9:16', '21:9', '9:21', '16:9'],
|
||||
ui: 'proportion',
|
||||
},
|
||||
{
|
||||
name: 'resolution',
|
||||
label: '分辨率',
|
||||
type: 'select',
|
||||
default: '2k',
|
||||
options: ['1k', '2k', '4k'],
|
||||
ui: 'resolution',
|
||||
},
|
||||
{
|
||||
name: 'quality',
|
||||
label: '画质',
|
||||
type: 'select',
|
||||
default: 'medium',
|
||||
options: ['low', 'medium', 'high'],
|
||||
ui: 'select',
|
||||
},
|
||||
],
|
||||
}
|
||||
@ -1,39 +0,0 @@
|
||||
// GPT-Image-2 — 文生图
|
||||
export default {
|
||||
name: 'GPT-Image-2',
|
||||
tag: '文生图',
|
||||
inputType: 'text',
|
||||
params: [
|
||||
{
|
||||
name: 'prompt',
|
||||
label: '提示词',
|
||||
type: 'string',
|
||||
required: true,
|
||||
ui: 'textarea',
|
||||
},
|
||||
{
|
||||
name: 'aspectRatio',
|
||||
label: '比例',
|
||||
type: 'select',
|
||||
default: '1:1',
|
||||
options: ['1:1', '1:2', '2:1', '1:3', '3:1', '2:3', '3:2', '3:4', '4:3', '4:5', '5:4', '9:16', '21:9', '9:21', '16:9'],
|
||||
ui: 'proportion',
|
||||
},
|
||||
{
|
||||
name: 'resolution',
|
||||
label: '分辨率',
|
||||
type: 'select',
|
||||
default: '2k',
|
||||
options: ['1k', '2k', '4k'],
|
||||
ui: 'resolution',
|
||||
},
|
||||
{
|
||||
name: 'quality',
|
||||
label: '画质',
|
||||
type: 'select',
|
||||
default: 'medium',
|
||||
options: ['low', 'medium', 'high'],
|
||||
ui: 'select',
|
||||
},
|
||||
],
|
||||
}
|
||||
@ -1,45 +0,0 @@
|
||||
// 模型配置注册表 — 按模型名称查找参数 schema
|
||||
import flux from './flux.js'
|
||||
import zImage from './z-image.js'
|
||||
import jimeng from './jimeng.js'
|
||||
import qwen from './qwen.js'
|
||||
import gptImage from './gpt-image.js'
|
||||
import nanoPro from './nano-pro.js'
|
||||
import qwenEdit from './qwen-edit.js'
|
||||
import gptImageI2i from './gpt-image-i2i.js'
|
||||
|
||||
const configs = {
|
||||
'Flux 2': flux,
|
||||
'Z-Image Turbo': zImage,
|
||||
'即梦4.6': jimeng,
|
||||
'通义万相2.0': qwen,
|
||||
'GPT-Image-2': gptImage,
|
||||
'Nano Pro': nanoPro,
|
||||
'通义万相2.0 Pro': qwenEdit,
|
||||
'GPT-Image-2 I2i': gptImageI2i,
|
||||
}
|
||||
|
||||
// API display_name → config key 映射(API 返回的 display_name 可能与 config 的 name 不同)
|
||||
const displayNameMap = {
|
||||
'flux': 'Flux 2',
|
||||
'Z-image': 'Z-Image Turbo',
|
||||
'Jimeng4.6': '即梦4.6',
|
||||
'QwenImage2.0': '通义万相2.0',
|
||||
'GPT-image-2': 'GPT-Image-2',
|
||||
'Banana-Pro': 'Nano Pro',
|
||||
'QwenImage2.0-Pro': '通义万相2.0 Pro',
|
||||
'GPT-Image-2': 'GPT-Image-2 I2I',
|
||||
}
|
||||
|
||||
/** 根据模型名称获取参数配置,支持 API display_name 和 config key 两种方式查找 */
|
||||
export function getModelConfig(modelName) {
|
||||
if (configs[modelName]) return configs[modelName]
|
||||
const mappedKey = displayNameMap[modelName]
|
||||
if (mappedKey && configs[mappedKey]) return configs[mappedKey]
|
||||
return null
|
||||
}
|
||||
|
||||
/** 获取所有模型配置 */
|
||||
export function getAllModelConfigs() {
|
||||
return configs
|
||||
}
|
||||
@ -1,40 +0,0 @@
|
||||
// 即梦 4.6 — 文生图(直接指定宽高像素)
|
||||
export default {
|
||||
name: '即梦4.6',
|
||||
tag: '文生图',
|
||||
inputType: 'text',
|
||||
params: [
|
||||
{
|
||||
name: 'prompt',
|
||||
label: '提示词',
|
||||
type: 'string',
|
||||
required: true,
|
||||
ui: 'textarea',
|
||||
},
|
||||
{
|
||||
name: 'width',
|
||||
label: '宽度',
|
||||
type: 'number',
|
||||
default: 1024,
|
||||
min: 900,
|
||||
max: 6197,
|
||||
ui: 'dimensionWidth',
|
||||
},
|
||||
{
|
||||
name: 'height',
|
||||
label: '高度',
|
||||
type: 'number',
|
||||
default: 1024,
|
||||
min: 768,
|
||||
max: 4096,
|
||||
ui: 'dimensionHeight',
|
||||
},
|
||||
{
|
||||
name: 'forceSingle',
|
||||
label: '强制单张',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
ui: 'hidden',
|
||||
},
|
||||
],
|
||||
}
|
||||
@ -1,40 +0,0 @@
|
||||
// Nano Pro — 图片编辑
|
||||
export default {
|
||||
name: 'Nano Pro',
|
||||
tag: '图片编辑',
|
||||
inputType: 'image',
|
||||
maxImages: 10,
|
||||
params: [
|
||||
{
|
||||
name: 'imageUrls',
|
||||
label: '参考图片',
|
||||
type: 'image',
|
||||
required: true,
|
||||
ui: 'imageUpload',
|
||||
maxCount: 10,
|
||||
},
|
||||
{
|
||||
name: 'prompt',
|
||||
label: '编辑指令',
|
||||
type: 'string',
|
||||
required: true,
|
||||
ui: 'textarea',
|
||||
},
|
||||
{
|
||||
name: 'aspectRatio',
|
||||
label: '比例',
|
||||
type: 'select',
|
||||
default: '1:1',
|
||||
options: ['1:1', '3:2', '2:3', '3:4', '4:3', '4:5', '5:4', '9:16', '16:9', '21:9'],
|
||||
ui: 'proportion',
|
||||
},
|
||||
{
|
||||
name: 'resolution',
|
||||
label: '分辨率',
|
||||
type: 'select',
|
||||
default: '2k',
|
||||
options: ['1k', '2k', '4k'],
|
||||
ui: 'resolution',
|
||||
},
|
||||
],
|
||||
}
|
||||
@ -1,48 +0,0 @@
|
||||
// 通义万相 2.0 Pro — 图片编辑
|
||||
export default {
|
||||
name: '通义万相2.0 Pro',
|
||||
tag: '图片编辑',
|
||||
inputType: 'image',
|
||||
maxImages: 3,
|
||||
params: [
|
||||
{
|
||||
name: 'imageUrls',
|
||||
label: '参考图片',
|
||||
type: 'image',
|
||||
required: true,
|
||||
ui: 'imageUpload',
|
||||
maxCount: 3,
|
||||
},
|
||||
{
|
||||
name: 'prompt',
|
||||
label: '提示词',
|
||||
type: 'string',
|
||||
default: '',
|
||||
ui: 'textarea',
|
||||
},
|
||||
{
|
||||
name: 'size',
|
||||
label: '尺寸',
|
||||
type: 'string',
|
||||
default: '1024*1024',
|
||||
ui: 'dimension',
|
||||
dimension: {
|
||||
parse: (val) => {
|
||||
const parts = (val || '1024*1024').split('*')
|
||||
return { width: parseInt(parts[0]) || 1024, height: parseInt(parts[1]) || 1024 }
|
||||
},
|
||||
format: (w, h) => `${w}*${h}`,
|
||||
width: { min: 512, max: 2048 },
|
||||
height: { min: 512, max: 2048 },
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'imageNum',
|
||||
label: '生成张数',
|
||||
type: 'select',
|
||||
default: 1,
|
||||
options: [1, 2, 3, 4, 5, 6],
|
||||
ui: 'quantity',
|
||||
},
|
||||
],
|
||||
}
|
||||
@ -1,40 +0,0 @@
|
||||
// 通义万相 2.0 — 文生图
|
||||
export default {
|
||||
name: '通义万相2.0',
|
||||
tag: '文生图',
|
||||
inputType: 'text',
|
||||
maxImages: 6,
|
||||
params: [
|
||||
{
|
||||
name: 'prompt',
|
||||
label: '提示词',
|
||||
type: 'string',
|
||||
required: true,
|
||||
ui: 'textarea',
|
||||
},
|
||||
{
|
||||
name: 'size',
|
||||
label: '尺寸',
|
||||
type: 'string',
|
||||
default: '1024*1024',
|
||||
ui: 'dimension',
|
||||
dimension: {
|
||||
parse: (val) => {
|
||||
const parts = (val || '1024*1024').split('*')
|
||||
return { width: parseInt(parts[0]) || 1024, height: parseInt(parts[1]) || 1024 }
|
||||
},
|
||||
format: (w, h) => `${w}*${h}`,
|
||||
width: { min: 512, max: 2048 },
|
||||
height: { min: 512, max: 2048 },
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'imageNum',
|
||||
label: '生成张数',
|
||||
type: 'select',
|
||||
default: 1,
|
||||
options: [1, 2, 3, 4, 5, 6],
|
||||
ui: 'quantity',
|
||||
},
|
||||
],
|
||||
}
|
||||
@ -1,31 +0,0 @@
|
||||
// Z-Image Turbo — 文生图
|
||||
export default {
|
||||
name: 'Z-Image Turbo',
|
||||
tag: '文生图',
|
||||
inputType: 'text',
|
||||
params: [
|
||||
{
|
||||
name: 'prompt',
|
||||
label: '提示词',
|
||||
type: 'string',
|
||||
required: true,
|
||||
ui: 'textarea',
|
||||
},
|
||||
{
|
||||
name: 'aspectRatio',
|
||||
label: '比例',
|
||||
type: 'select',
|
||||
default: '1:1',
|
||||
options: ['1:1', '3:4', '4:3', '9:16', '16:9', '2:3', '3:2'],
|
||||
ui: 'proportion',
|
||||
},
|
||||
{
|
||||
name: 'outputFormat',
|
||||
label: '输出格式',
|
||||
type: 'string',
|
||||
default: 'png',
|
||||
options: ['png', 'jpeg', 'webp(lossless)', 'webp(lossy)'],
|
||||
ui: 'hidden',
|
||||
},
|
||||
],
|
||||
}
|
||||
@ -1,95 +0,0 @@
|
||||
const STORAGE_PREFIX = 'model_config_'
|
||||
|
||||
function getTodayDateString() {
|
||||
const today = new Date()
|
||||
return `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`
|
||||
}
|
||||
|
||||
function getStorageKey(modelName, modelType) {
|
||||
return `${STORAGE_PREFIX}${modelType}_${modelName}`
|
||||
}
|
||||
|
||||
function getConfigFromStorage(modelName, modelType) {
|
||||
try {
|
||||
const key = getStorageKey(modelName, modelType)
|
||||
const stored = localStorage.getItem(key)
|
||||
|
||||
if (!stored) {
|
||||
return null
|
||||
}
|
||||
|
||||
const data = JSON.parse(stored)
|
||||
const todayStr = getTodayDateString()
|
||||
|
||||
if (data.storageDate !== todayStr) {
|
||||
localStorage.removeItem(key)
|
||||
return null
|
||||
}
|
||||
|
||||
return data.config
|
||||
} catch (error) {
|
||||
console.error('从localStorage读取配置失败:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function saveConfigToStorage(modelName, modelType, config) {
|
||||
try {
|
||||
const key = getStorageKey(modelName, modelType)
|
||||
const data = {
|
||||
config,
|
||||
storageDate: getTodayDateString(),
|
||||
timestamp: Date.now()
|
||||
}
|
||||
localStorage.setItem(key, JSON.stringify(data))
|
||||
} catch (error) {
|
||||
console.error('保存配置到localStorage失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchModelConfig(type, modelName, modelType) {
|
||||
const cachedConfig = getConfigFromStorage(modelName, modelType)
|
||||
|
||||
if (cachedConfig) {
|
||||
console.log(`从缓存加载模型配置: ${modelName}`)
|
||||
return cachedConfig
|
||||
}
|
||||
|
||||
try {
|
||||
const url = `${import.meta.env.VITE_API_MODEL_RESOURCE}/static/public/Platform/${type}/workflows/${modelType}/${modelName}.json`
|
||||
console.log(`从远程获取模型配置: ${url}`)
|
||||
|
||||
const response = await fetch(url)
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`)
|
||||
}
|
||||
|
||||
const config = await response.json()
|
||||
|
||||
saveConfigToStorage(modelName, modelType, config)
|
||||
|
||||
return config
|
||||
} catch (error) {
|
||||
console.error('获取模型配置失败:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
export function clearModelConfigCache(modelName, modelType) {
|
||||
const key = getStorageKey(modelName, modelType)
|
||||
localStorage.removeItem(key)
|
||||
}
|
||||
|
||||
export function clearAllModelConfigCache() {
|
||||
const keysToRemove = []
|
||||
|
||||
for (let i = 0; i < localStorage.length; i++) {
|
||||
const key = localStorage.key(i)
|
||||
if (key && key.startsWith(STORAGE_PREFIX)) {
|
||||
keysToRemove.push(key)
|
||||
}
|
||||
}
|
||||
|
||||
keysToRemove.forEach(key => localStorage.removeItem(key))
|
||||
}
|
||||
137
平台参数管理-上线说明.md
Normal file
137
平台参数管理-上线说明.md
Normal file
@ -0,0 +1,137 @@
|
||||
# 平台参数管理 — 功能上线说明
|
||||
|
||||
## 概述
|
||||
|
||||
绘画/视频平台的模型参数配置已从**前端代码硬编码**迁移至**管理后台数据库管理**。新增模型或修改参数无需前端发版,在管理后台配置即可生效。
|
||||
|
||||
## 变更内容
|
||||
|
||||
### 1. 新增数据库表
|
||||
|
||||
`suanli.model_param_configs` — 模型参数配置表
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `model_id` | CHAR(36) | 关联模型 UUID |
|
||||
| `platform_id` | CHAR(36) | 关联平台 UUID(如 ai_painting_talk / ai_video_talk) |
|
||||
| `input_type` | VARCHAR(32) | text(文生图)/ image(图片编辑) |
|
||||
| `max_images` | INT | 最大上传图片数,0=不支持 |
|
||||
| `prompt_placeholder` | VARCHAR(500) | 提示词输入框占位文本 |
|
||||
| `params` | JSON | 参数列表,每项含 name/ui/label/default/options 等 |
|
||||
|
||||
唯一约束:`(model_id, platform_id)` — 同一模型在同一平台下仅一份配置。
|
||||
|
||||
### 2. 新增后端接口
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| GET | `/suanli/v1/admin/model-params` | 列表(分页,支持 `?platform_id=` 筛选) |
|
||||
| GET | `/suanli/v1/admin/model-params/:id` | 详情 |
|
||||
| POST | `/suanli/v1/admin/model-params` | 新建 |
|
||||
| PUT | `/suanli/v1/admin/model-params/:id` | 更新 |
|
||||
| DELETE | `/suanli/v1/admin/model-params/:id` | 删除 |
|
||||
| GET | `/suanli/v1/models/:modelId/config` | **公共 API**,供客户端拉取参数 schema |
|
||||
|
||||
公共 API 响应示例(含各 ui 类型的完整结构):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"inputType": "text",
|
||||
"maxImages": 0,
|
||||
"promptPlaceholder": "描述你想生成的画面。",
|
||||
"params": [
|
||||
{ "name": "prompt", "ui": "textarea", "label": "提示词", "required": true, "default": "" },
|
||||
{ "name": "aspectRatio", "ui": "proportion", "label": "比例", "default": "1:1", "options": ["1:1","16:9","9:16","custom"] },
|
||||
{ "name": "resolution", "ui": "resolution", "label": "分辨率", "default": "2k", "options": ["1k","2k","4k"] },
|
||||
{
|
||||
"name": "size", "ui": "dimension", "label": "尺寸", "default": "1024*1024",
|
||||
"dimension": {
|
||||
"separator": "*",
|
||||
"width": { "min": 512, "max": 2048 },
|
||||
"height": { "min": 512, "max": 2048 }
|
||||
}
|
||||
},
|
||||
{ "name": "quality", "ui": "select", "label": "画质", "default": "medium", "options": ["low","medium","high"] },
|
||||
{ "name": "imageNum", "ui": "quantity", "label": "生成张数", "default": 1, "options": [1, 2, 3, 4, 5, 6] },
|
||||
{ "name": "imageUrls", "ui": "imageUpload", "label": "参考图", "required": true, "maxCount": 10 }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 管理后台页面
|
||||
|
||||
路径:侧边栏 → **平台参数管理** → **模型参数配置**
|
||||
|
||||
- 进入页面后需先选择平台,才会显示该平台下已配置的模型参数
|
||||
- 新建时:选择平台 → 手动填入模型 UUID(从模型管理页复制)→ 配置参数 JSON → 保存
|
||||
- params 字段提供 JSON 编辑器和格式化按钮
|
||||
|
||||
## params 字段说明
|
||||
|
||||
`ui` 值与前端控件映射:
|
||||
|
||||
| ui | 控件 | 必需字段 | 说明 |
|
||||
|----|------|---------|------|
|
||||
| `textarea` | 文本输入框 | name, default | 提示词 |
|
||||
| `proportion` | 比例选择器 | options | `options` 含 `custom` 时允许自定义宽高 |
|
||||
| `resolution` | 分辨率选择器 | options | 与 proportion 共用弹窗 |
|
||||
| `dimension` | 组合尺寸 | `dimension: { separator, width: {min,max}, height: {min,max} }` | 单字段 `"W*H"` 格式,前端根据 separator 生成 parse/format |
|
||||
| `dimensionWidth` / `dimensionHeight` | 分离尺寸 | min, max | 两个独立字段,共享比例锁。必须传 min/max 作为输入校验边界 |
|
||||
| `select` | 下拉选择 | options | 通用 |
|
||||
| `quantity` | 生成数量 | options(**数字数组**) | `options` 必须为数字类型 `[1,2,3]`,前端 `Math.max()` 计算上限 |
|
||||
| `imageUpload` | 图片上传 | maxCount | 控制上传数量上限。顶层 `maxImages` 仅作 fallback |
|
||||
| `number` | 数字输入 | min, max | 自定义宽高(如 Flux 的 customWidth/customHight),支持 `showWhen` 条件显示 |
|
||||
| `hidden` | 不渲染 | default | 静默写入默认值 |
|
||||
|
||||
**`inputType` 枚举值**:`text`(文生图)/ `image`(图生图/图片编辑)/ `both`(同时支持,可传图也可不传)
|
||||
|
||||
**条件显示**:`"showWhen": { "aspectRatio": "custom" }` 使参数仅在比例为自定义时显示。
|
||||
|
||||
**字段类型速查**:
|
||||
|
||||
| 字段 | 类型 | 适用 ui | 前端读取位置 |
|
||||
|------|------|---------|-------------|
|
||||
| `name` | string | 全部 | 各处以 `p.name` 查找 |
|
||||
| `ui` | string | 全部 | `getDimConfig`、controls `show()` |
|
||||
| `default` | any | 全部 | `syncDefaults` |
|
||||
| `options` | array | proportion/resolution/select/quantity | 控件 props、`Math.max` |
|
||||
| `min` / `max` | number | dimensionWidth/dimensionHeight/number | `getDimConfig` 校验边界 |
|
||||
| `maxCount` | number | imageUpload | `imageUploadLimit` |
|
||||
| `dimension.separator` | string | dimension | `syncDefaults` 解析 W*H |
|
||||
| `dimension.width.min/max` | number | dimension | `getDimConfig` 宽度校验 |
|
||||
| `dimension.height.min/max` | number | dimension | `getDimConfig` 高度校验 |
|
||||
| `showWhen` | object | 任意 | 条件显示(需前端改造) |
|
||||
|
||||
## 已配置数据
|
||||
|
||||
`ai_painting_talk`(AI绘画new)平台下 8 个模型已完成参数配置:
|
||||
|
||||
| 模型 | 输入类型 | 参数数 |
|
||||
|------|---------|--------|
|
||||
| Flux 2 | text | 5 |
|
||||
| Z-Image Turbo | text | 3 |
|
||||
| 即梦4.6 | text | 4 |
|
||||
| 通义万相2.0 | text | 3 |
|
||||
| GPT-Image-2 | text | 4 |
|
||||
| Nano Pro | image | 4 |
|
||||
| 通义万相2.0 Pro | image | 4 |
|
||||
| GPT-Image-2 I2I | image | 5 |
|
||||
|
||||
`ai_video_talk`(AI视频new)暂未配置。
|
||||
|
||||
## 客户端接入
|
||||
|
||||
AI_Painting_V2.0 前端可调用 `GET /suanli/v1/models/{modelId}/config` 获取参数配置,替代当前的本地 JS 硬编码和远程 JSON 拉取。接入后删除:
|
||||
|
||||
- `src/platforms/painting/models/` 目录
|
||||
- `src/utils/modelConfig.js`
|
||||
- `src/utils/createTask.js`
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 参数配置**不同步到转发层**,仅用于驱动前端 UI 渲染,不影响任务执行
|
||||
- 模型 ID 为 UUID 格式,需从「模型管理」页面复制
|
||||
- 直接操作数据库(手动 SQL 插入/更新)不会触发任何副作用,安全
|
||||
Loading…
Reference in New Issue
Block a user