Compare commits

..

2 Commits

Author SHA1 Message Date
7379488839 fix: 修复按钮状态恢复、Music 任务内容显示与比例标签为空,新增 platform_code 参数
- taskPolling: isSubGerenate=true 提前至 modelId 校验前,确保错误时按钮恢复;请求体新增 platform_code
- dialogBox: generateData 集成平台 getGenerateDataExtras 扩展字段
- Music 平台: 新增 getGenerateDataExtras() 输出 pureMusic/mode,供列表展示和再生成使用
- set.vue: 硬编码比例标签改为 secondTagText 计算属性,Music 显示纯音乐/有歌词,Painting/Video 从 modelParams 提取比例
2026-06-15 17:10:18 +08:00
4d76899488 chore: 清理 env 废弃变量、移除旧模型配置,补充 Music 平台文档与大小写归一化修复
- .env 文件移除未使用的 VITE_API_PAY_* 变量,更新生产环境 URL
- 删除 5 个已废弃的 model-configs JSON 文件
- CLAUDE.md 新增 Music 平台架构说明与计费类型映射
- getPlatformCode / getChargeType 增加输入大小写归一化
2026-06-15 16:55:34 +08:00
13 changed files with 159 additions and 324 deletions

View File

@ -5,10 +5,6 @@ VITE_BASE = '/'
VITE_API_PREFIX = '/api'
VITE_API_BASE_URL = 'http://test.xueai.art/newapi/api' # http://huanda.xueai.art http://106.54.11.219/api 43.248.131.153:8003
# 支付服务
VITE_API_PAY_PREFIX = '/pay'
VITE_API_PAY_TARGET = 'http://test.xueai.art' # http://43.248.133.202 test.xueai.art
# 任务处理模块
VITE_API_WORKFLOW_UPLOAD = 'http://test.xueai.art/AIGC/Temp/uploadImage' # https://sxwz.xueai.art/workflow https://designtools.xueai.art/workflow
VITE_API_TASK_PREFIX = '/suanli'

View File

@ -6,21 +6,15 @@ VITE_BUILD_MOCK = false
# 主服务
VITE_API_PREFIX = '/api'
VITE_API_BASE_URL = 'https://sxwz.xueai.art/api'
# 支付服务
VITE_API_PAY_PREFIX = '/pay'
VITE_API_PAY_TARGET = 'https://sxwz.xueai.art' # http://43.248.133.202
VITE_API_BASE_URL = 'https://sxwz.xueai.art/newapi/api'
# 任务处理模块
VITE_API_WORKFLOW_UPLOAD = 'https://designtools.xueai.art/workflow/file/upload' # https://sxwz.xueai.art/workflow https://designtools.xueai.art/workflow
VITE_API_WORKFLOW_UPLOAD = 'https://resources.xueai.art/AIGC/Temp/uploadImage' # https://sxwz.xueai.art/workflow https://designtools.xueai.art/workflow
VITE_API_TASK_PREFIX = '/suanli'
VITE_API_TASK_TARGET = 'http://test.xueai.art'
# 模型资源
VITE_API_MODEL_RESOURCE = 'https://resources.xueai.art/AIGC'
VITE_API_TASK_TARGET = 'https://sxwz.xueai.art'
# 是否开启KKFileView
FILE_OPEN_PREVIEW = false
# KKFileView服务器地址
# FILE_VIEW_SERVER_URL = 'http://192.168.122.209:8012'
# FILE_VIEW_SERVER_URL = 'http://192.168.122.209:8012'.

View File

@ -18,9 +18,9 @@ Vue 3 (Composition API) + Vite 7 + Pinia + Vue Router + Element Plus + `vue-elem
## 架构概览
AI 绘画/视频生成前端操作平台,通过 HTTP 接口对接算力调度后端suanli提交生成任务并轮询结果。
AI 绘画/视频/音乐生成前端操作平台,通过 HTTP 接口对接算力调度后端suanli提交生成任务并轮询结果。
**核心架构Platform Descriptor 模式。** Painting 和 Video 是两个独立的平台包通过统一的注册表动态加载。dialogBox 是通用编排壳,不包含任何平台特定逻辑。
**核心架构Platform Descriptor 模式。** Painting、Video 和 Music 是三个独立的平台包通过统一的注册表动态加载。dialogBox 是通用编排壳,不包含任何平台特定逻辑。
### 关键目录
@ -54,6 +54,15 @@ src/
│ ├── pattern.vue
│ ├── proportion.vue
│ └── time.vue
│ └── music/ # Music 平台
│ ├── index.js # defineMusicPlatform()
│ ├── modelSelector.vue
│ ├── imageUploader.vue
│ └── controls/
│ ├── modeSelector.vue # 模式选择(常用/专业/Remix/编辑)
│ ├── pureMusicGroup.vue # 纯音乐开关 + 歌词输入弹窗
│ ├── lyricsInput.vue # 专业模式歌词输入
│ └── timeControl.vue # 时长滑块(常用模式)
├── stores/ # Pinia 状态管理
│ ├── user.js # 用户认证、信息(含 sessionIdpinia persist 持久化 token
│ ├── display.js # 生成历史列表、UI 状态(滚动、画布等)
@ -234,6 +243,36 @@ Video 的 `getDefaultModel()` 返回 `''`,不硬编码 UUID模型列表加
}
```
### Music 平台特有行为
Music 平台与 Painting/Video 的关键差异:
- **模式驱动控件显隐**`mode` ref常用模式/专业模式/Remix模式/编辑模式)决定大部分控件的 `show()`。切换模式时 `visibleControls` 自动更新。
- **纯音乐/歌词互斥**:常用模式下 `pureMusicGroup` 控件内含纯音乐开关 + 歌词输入弹窗。开关开启时清空歌词;关闭时弹出歌词输入框。`buildTaskBody` 中 `pureMusic` 为 true 时 `lyrics` 为空字符串。
- **参考音频**`imageUploader` 用于上传音频文件(非图片),仅在专业模式下显示(`showImageUploader()` → `mode === '专业模式'`),且为必填(`isImageRequired()` → true
- **数量限制**:专业模式下 `quantity` 强制为 1`props()` 中做 `Math.min(mode === '专业模式' ? 1 : maxQty)`)。
- **Music 独有控件**
| 控件 | `name` | `beforeModel` | 作用 |
|------|--------|---------------|------|
| `ModeSelector` | `modeSelector` | `true`(在模型选择器前) | 常用/专业/Remix/编辑 四选一 |
| `PureMusicGroup` | `pureMusicGroup` | `false` | 纯音乐开关 + 歌词输入(仅常用模式显示) |
| `LyricsInput` | `lyricsInput` | `false` | 歌词输入(仅专业模式显示) |
| `TimeControl` | `timeControl` | `false` | 时长滑块 min~max仅常用模式显示 |
- **`handledUis`**Music ParamGroup 过滤用):`['textarea', 'proportion', 'imageUpload', 'hidden', 'quantity']`,额外按名称排除 `['mode', 'pureMusic', 'lyrics', 'duration', 'quantity']`
### 计费类型映射(`getChargeType`
`taskPolling.js` 中的 `getChargeType(chargeType)` 将平台类型映射为计费数字:
| 平台 | 计费码 |
|------|--------|
| Painting | 1 |
| Video | 4 |
| Music | 5 |
| 其他 | 2 |
### 组件规范
**禁止在项目组件中使用外部 UI 库**Element Plus 等),图标除外。自定义组件使用项目自研的 `Select`、`Popover` 或纯 CSS 实现。`SwitchControl` 即为一例——纯 CSS 滑动开关,不依赖 `el-switch`
@ -358,6 +397,7 @@ FILE_OPEN_PREVIEW = true # 是否开启 KKFileView 预览
|------|----------|
| Painting | `ai_painting_talk` |
| Video | `ai_video_talk` |
| Music | `ai_music_talk` |
映射函数 `getPlatformCode()` 位于 `utils/modelApi.js`

View File

@ -1,39 +0,0 @@
{
"modelName": "海螺 02-fast 图生视频",
"modelDescription": "RunningHub MiniMax 海螺 02-fast 图生视频模型,基于参考图生成快节奏电影感动画",
"endpoint": "/minimax/hailuo-02/fast",
"params": [
{
"name": "prompt",
"ui": "textarea",
"label": "提示词",
"required": false
},
{
"name": "enablePromptExpansion",
"ui": "switch",
"label": "提示词扩展",
"default": true,
"required": true
},
{
"name": "imageUrl",
"ui": "imageUpload",
"label": "参考图片",
"maxCount": 1,
"required": true,
"maxSizeMB": 10
},
{
"name": "duration",
"ui": "select",
"label": "时长(秒)",
"default": "6",
"options": ["6", "10"],
"required": true
}
],
"promptPlaceholder": "描述基于原图的动画效果(可选)。",
"inputType": "image",
"maxImages": 1
}

View File

@ -1,47 +0,0 @@
{
"modelName": "LTX-2.3 图生视频",
"modelDescription": "RunningHub LTX-2.3 图生视频模型,基于图片生成视频",
"endpoint": "/rhart-video/ltx-2.3/image-to-video",
"params": [
{
"name": "imageUrl",
"ui": "imageUpload",
"label": "参考图片",
"maxCount": 1,
"required": true
},
{
"name": "prompt",
"ui": "textarea",
"label": "提示词",
"required": true
},
{
"name": "aspectRatio",
"ui": "proportion",
"label": "画面比例",
"default": "16:9",
"options": ["9:16", "16:9"],
"required": true
},
{
"name": "resolution",
"ui": "resolution",
"label": "分辨率",
"default": "720p",
"options": ["480p", "720p", "1080p"],
"required": true
},
{
"name": "duration",
"ui": "select",
"label": "时长(秒)",
"default": 5,
"options": [5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
"required": true
}
],
"promptPlaceholder": "描述图片和期望的视频运动效果。",
"inputType": "image",
"maxImages": 1
}

View File

@ -1,40 +0,0 @@
{
"modelName": "LTX-2.3 文生视频",
"modelDescription": "RunningHub LTX-2.3 文生视频模型Text-to-Video基于文本生成视频",
"endpoint": "/rhart-video/ltx-2.3/text-to-video",
"params": [
{
"name": "prompt",
"ui": "textarea",
"label": "提示词",
"required": true
},
{
"name": "resolution",
"ui": "select",
"label": "分辨率",
"default": "720p",
"options": ["1080p", "720p", "480p"],
"required": true
},
{
"name": "aspectRatio",
"ui": "proportion",
"label": "画面比例",
"default": "16:9",
"options": ["16:9", "9:16"],
"required": true
},
{
"name": "duration",
"ui": "number",
"label": "时长(秒)",
"default": 5,
"min": 5,
"max": 15,
"required": true
}
],
"promptPlaceholder": "描述你想生成的画面和动作。",
"inputType": "text"
}

View File

@ -1,64 +0,0 @@
{
"modelName": "Vidu 首尾帧生视频 q3-turbo",
"modelDescription": "RunningHub Vidu 首尾帧生视频 q3-turbo 模型,通过首尾帧图片驱动视频生成,支持音视频直出",
"endpoint": "/vidu/start-end-to-video-q3-turbo",
"params": [
{
"name": "prompt",
"ui": "textarea",
"label": "提示词",
"required": true,
"maxLength": 4000
},
{
"name": "firstImageUrl",
"ui": "imageUpload",
"label": "首帧图片",
"maxCount": 1,
"required": true,
"maxSizeMB": 50
},
{
"name": "lastImageUrl",
"ui": "imageUpload",
"label": "尾帧图片",
"maxCount": 1,
"required": true,
"maxSizeMB": 50
},
{
"name": "duration",
"ui": "select",
"label": "时长(秒)",
"default": "5",
"options": ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16"],
"required": true
},
{
"name": "resolution",
"ui": "resolution",
"label": "分辨率",
"default": "720p",
"options": ["540p", "720p", "1080p"],
"required": true
},
{
"name": "movementAmplitude",
"ui": "select",
"label": "运动幅度",
"default": "auto",
"options": ["auto", "small", "medium", "large"],
"required": true
},
{
"name": "audio",
"ui": "switch",
"label": "音频直出",
"default": true,
"required": true
}
],
"promptPlaceholder": "描述首尾帧之间的动作补全。文本长度限制 1-4000。",
"inputType": "image",
"maxImages": 2
}

View File

@ -1,55 +0,0 @@
{
"modelName": "Vidu 文生视频 q3-turbo",
"modelDescription": "RunningHub Vidu 文生视频 q3-turbo 模型,支持音视频直出",
"endpoint": "/vidu/text-to-video-q3-turbo",
"params": [
{
"name": "prompt",
"ui": "textarea",
"label": "提示词",
"required": true,
"maxLength": 4000
},
{
"name": "style",
"ui": "select",
"label": "风格",
"default": "general",
"options": ["general", "anime"],
"required": true
},
{
"name": "aspectRatio",
"ui": "proportion",
"label": "画面比例",
"default": "16:9",
"options": ["4:3", "3:4", "16:9", "9:16", "1:1"],
"required": true
},
{
"name": "resolution",
"ui": "resolution",
"label": "分辨率",
"default": "720p",
"options": ["540p", "720p", "1080p"],
"required": true
},
{
"name": "duration",
"ui": "select",
"label": "时长(秒)",
"default": "5",
"options": ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16"],
"required": true
},
{
"name": "audio",
"ui": "switch",
"label": "音频直出",
"default": true,
"required": true
}
],
"promptPlaceholder": "描述你想生成的视频画面和音频。文本长度限制 1-4000。",
"inputType": "text"
}

View File

@ -157,7 +157,8 @@ const handleStart = async () => {
modelType: p.modelType.value,
prompt: prompt.value,
referenceImages: referenceImages.value,
modelParams: body
modelParams: body,
...(p.getGenerateDataExtras?.() || {})
}
const data = {

View File

@ -1,16 +1,15 @@
import { markRaw, reactive, ref, computed } from 'vue'
import { registerPlatform } from '@/platforms/registry.js'
import { fetchPlatformModels, getPlatformCode, getModelConfig, getModelId, preloadModelConfigs } from '@/utils/modelApi'
import { syncParamValues, checkShowWhen } from '@/utils/modelConfigHelper.js'
import ModelSelector from './modelSelector.vue'
import ImageUploader from './imageUploader.vue'
import ModeSelector from './controls/modeSelector.vue'
import PureMusicGroup from './controls/pureMusicGroup.vue'
import LyricsInput from './controls/lyricsInput.vue'
import TimeControl from './controls/timeControl.vue'
import { computed, markRaw, reactive, ref } from 'vue'
import ParamGroup from '@/components/ParamGroup/index.vue'
import Select from '@/components/Select/index.vue'
import { ElMessage } from 'element-plus'
import { registerPlatform } from '@/platforms/registry.js'
import { fetchPlatformModels, getModelConfig, getModelId, getPlatformCode, preloadModelConfigs } from '@/utils/modelApi'
import { checkShowWhen } from '@/utils/modelConfigHelper.js'
import LyricsInput from './controls/lyricsInput.vue'
import ModeSelector from './controls/modeSelector.vue'
import PureMusicGroup from './controls/pureMusicGroup.vue'
import TimeControl from './controls/timeControl.vue'
import ImageUploader from './imageUploader.vue'
import ModelSelector from './modelSelector.vue'
// 由专用控件处理的 ui 类型
const handledUis = ['textarea', 'proportion', 'imageUpload', 'hidden', 'quantity']
@ -37,11 +36,11 @@ export function defineMusicPlatform() {
async function loadModels() {
models.value = await fetchPlatformModels(code.value)
if (!model.value && models.value.length) {
const first = models.value.find(m => !m.disabled)
const first = models.value.find((m) => !m.disabled)
if (first) model.value = first.id
}
if (models.value.length) {
const ids = models.value.map(m => m.id)
const ids = models.value.map((m) => m.id)
preloadModelConfigs(ids)
}
}
@ -65,22 +64,22 @@ export function defineMusicPlatform() {
})
// 同步专用 ref
const modeParam = config.params.find(p => p.name === 'mode' || p.ui === 'mode')
const modeParam = config.params.find((p) => p.name === 'mode' || p.ui === 'mode')
if (modeParam) mode.value = modeParam.default || '常用模式'
const qtyParam = config.params.find(p => p.ui === 'quantity')
const qtyParam = config.params.find((p) => p.ui === 'quantity')
if (qtyParam) quantity.value = qtyParam.default || 1
const durParam = config.params.find(p => p.name === 'duration')
const durParam = config.params.find((p) => p.name === 'duration')
if (durParam) duration.value = durParam.default || 'Auto'
const lyricsParam = config.params.find(p => p.name === 'lyrics')
const lyricsParam = config.params.find((p) => p.name === 'lyrics')
if (lyricsParam) lyrics.value = lyricsParam.default || ''
const seedParam = config.params.find(p => p.name === 'randomSeed' || p.name === 'seed')
const seedParam = config.params.find((p) => p.name === 'randomSeed' || p.name === 'seed')
if (seedParam) randomSeed.value = seedParam.default || ''
const pmParam = config.params.find(p => p.name === 'pureMusic')
const pmParam = config.params.find((p) => p.name === 'pureMusic')
if (pmParam) pureMusic.value = pmParam.default !== undefined ? pmParam.default : true
if (config.promptPlaceholder) {
@ -93,7 +92,7 @@ export function defineMusicPlatform() {
function imageUploadLimit() {
if (!modelConfig.value) return 0
return modelConfig.value.params
.filter(p => p.ui === 'imageUpload')
.filter((p) => p.ui === 'imageUpload')
.reduce((sum, p) => sum + (p.maxCount || 1), 0)
}
@ -118,12 +117,12 @@ export function defineMusicPlatform() {
function buildTaskBody({ prompt, referenceImages }) {
syncMusicParamValues()
// 将 prompt 写入 paramValues如果 config 中有 prompt 参数)
const promptParam = modelConfig.value?.params?.find(p => p.ui === 'textarea')
const promptParam = modelConfig.value?.params?.find((p) => p.ui === 'textarea')
if (promptParam) paramValues[promptParam.name] = prompt
// 将参考音频映射到 imageUpload 参数
if (modelConfig.value) {
const imageUploadParams = modelConfig.value.params.filter(p => p.ui === 'imageUpload')
const imageUploadParams = modelConfig.value.params.filter((p) => p.ui === 'imageUpload')
imageUploadParams.forEach((p, i) => {
if (referenceAudio.value[i]) {
paramValues[p.name] = referenceAudio.value[i].url
@ -133,26 +132,34 @@ export function defineMusicPlatform() {
return { ...paramValues }
}
// 附加平台专属字段到 generateData用于任务列表展示和再次生成/重新编辑
function getGenerateDataExtras() {
return {
pureMusic: pureMusic.value,
mode: mode.value
}
}
function syncMusicParamValues() {
if (!modelConfig.value) return
const config = modelConfig.value
const qtyParam = config.params.find(p => p.ui === 'quantity')
const qtyParam = config.params.find((p) => p.ui === 'quantity')
if (qtyParam) paramValues[qtyParam.name] = quantity.value
const durParam = config.params.find(p => p.name === 'duration')
const durParam = config.params.find((p) => p.name === 'duration')
if (durParam) paramValues[durParam.name] = duration.value
const lyricsParam = config.params.find(p => p.name === 'lyrics')
const lyricsParam = config.params.find((p) => p.name === 'lyrics')
if (lyricsParam) paramValues[lyricsParam.name] = lyrics.value
const seedParam = config.params.find(p => p.name === 'randomSeed' || p.name === 'seed')
const seedParam = config.params.find((p) => p.name === 'randomSeed' || p.name === 'seed')
if (seedParam) paramValues[seedParam.name] = randomSeed.value
const pmParam = config.params.find(p => p.name === 'pureMusic')
const pmParam = config.params.find((p) => p.name === 'pureMusic')
if (pmParam) paramValues[pmParam.name] = pureMusic.value
const modeParam = config.params.find(p => p.name === 'mode' || p.ui === 'mode')
const modeParam = config.params.find((p) => p.name === 'mode' || p.ui === 'mode')
if (modeParam) paramValues[modeParam.name] = mode.value
}
@ -171,13 +178,13 @@ export function defineMusicPlatform() {
name: 'modeSelector',
component: markRaw(ModeSelector),
beforeModel: true,
show: (config) => !!config?.params?.find(p => p.name === 'mode' || p.ui === 'mode'),
show: (config) => !!config?.params?.find((p) => p.name === 'mode' || p.ui === 'mode'),
props: (config) => {
const modeParam = config?.params?.find(p => p.name === 'mode' || p.ui === 'mode')
const modeParam = config?.params?.find((p) => p.name === 'mode' || p.ui === 'mode')
return {
modelValue: mode.value,
'modelValue': mode.value,
'onUpdate:modelValue': (v) => { mode.value = v },
options: modeParam?.options || []
'options': modeParam?.options || []
}
}
},
@ -185,11 +192,11 @@ export function defineMusicPlatform() {
name: 'pureMusicGroup',
component: markRaw(PureMusicGroup),
beforeModel: false,
show: (config) => mode.value === '常用模式' && !!config?.params?.find(p => p.name === 'pureMusic'),
show: (config) => mode.value === '常用模式' && !!config?.params?.find((p) => p.name === 'pureMusic'),
props: (config) => ({
modelValue: pureMusic.value,
'modelValue': pureMusic.value,
'onUpdate:modelValue': (v) => { pureMusic.value = v },
lyrics: lyrics.value,
'lyrics': lyrics.value,
'onUpdate:lyrics': (v) => { lyrics.value = v }
})
},
@ -197,9 +204,9 @@ export function defineMusicPlatform() {
name: 'lyricsInput',
component: markRaw(LyricsInput),
beforeModel: false,
show: (config) => mode.value === '专业模式' && !!config?.params?.find(p => p.name === 'lyrics'),
show: (config) => mode.value === '专业模式' && !!config?.params?.find((p) => p.name === 'lyrics'),
props: (config) => ({
modelValue: lyrics.value,
'modelValue': lyrics.value,
'onUpdate:modelValue': (v) => { lyrics.value = v }
})
},
@ -207,14 +214,14 @@ export function defineMusicPlatform() {
name: 'timeControl',
component: markRaw(TimeControl),
beforeModel: false,
show: (config) => mode.value === '常用模式' && !!config?.params?.find(p => p.name === 'duration'),
show: (config) => mode.value === '常用模式' && !!config?.params?.find((p) => p.name === 'duration'),
props: (config) => {
const durParam = config?.params?.find(p => p.name === 'duration')
const durParam = config?.params?.find((p) => p.name === 'duration')
return {
modelValue: duration.value,
'modelValue': duration.value,
'onUpdate:modelValue': (v) => { duration.value = v },
min: durParam?.min || 10,
max: durParam?.max || 240
'min': durParam?.min || 10,
'max': durParam?.max || 240
}
}
},
@ -222,15 +229,15 @@ export function defineMusicPlatform() {
name: 'quantity',
component: markRaw(Select),
beforeModel: false,
show: (config) => !!config?.params?.find(p => p.ui === 'quantity'),
show: (config) => !!config?.params?.find((p) => p.ui === 'quantity'),
props: (config) => {
const qtyParam = config?.params?.find(p => p.ui === 'quantity')
const qtyParam = config?.params?.find((p) => p.ui === 'quantity')
const maxQty = Math.max(...(qtyParam?.options || [1]))
const limited = mode.value === '专业模式' ? 1 : maxQty
return {
modelValue: quantity.value,
'modelValue': quantity.value,
'onUpdate:modelValue': (v) => { quantity.value = v },
options: Array.from({ length: limited }, (_, i) => ({ value: i + 1, label: `${i + 1}` }))
'options': Array.from({ length: limited }, (_, i) => ({ value: i + 1, label: `${i + 1}` }))
}
}
},
@ -263,15 +270,36 @@ export function defineMusicPlatform() {
controls,
ImageUploader: markRaw(ImageUploader),
state: {
model, modelType, mode, modelConfig, paramValues,
promptPlaceholder, referenceAudio, models,
quantity, duration, lyrics, randomSeed, pureMusic
model,
modelType,
mode,
modelConfig,
paramValues,
promptPlaceholder,
referenceAudio,
models,
quantity,
duration,
lyrics,
randomSeed,
pureMusic
},
model, modelType, mode, modelConfig, promptPlaceholder,
loadModels, loadConfig, getDefaultModel,
imageUploadLimit, validateBeforeSubmit,
getUploaderBindings, showImageUploader, isImageRequired,
buildTaskBody, fillFromResult
model,
modelType,
mode,
modelConfig,
promptPlaceholder,
loadModels,
loadConfig,
getDefaultModel,
imageUploadLimit,
validateBeforeSubmit,
getUploaderBindings,
showImageUploader,
isImageRequired,
buildTaskBody,
fillFromResult,
getGenerateDataExtras
}
}

View File

@ -39,7 +39,8 @@ function saveToCache(code, models) {
// 类型 → 平台编码映射
export function getPlatformCode(type) {
switch (type) {
const normalized = type.charAt(0).toUpperCase() + type.slice(1).toLowerCase()
switch (normalized) {
case 'Painting':
return 'ai_painting_talk'
case 'Video':

View File

@ -2,10 +2,12 @@ import { ElNotification } from 'element-plus'
import { h } from 'vue'
import { requestCreateTask, requestTaskStatus } from '@/apis/display'
import { useDisplayStore, useUserStore } from '@/stores'
import { getPlatformCode } from '@/utils/modelApi'
import { userError } from '@/utils/tokenError'
export function getChargeType(chargeType) {
switch (chargeType) {
const normalized = chargeType.charAt(0).toUpperCase() + chargeType.slice(1).toLowerCase()
switch (normalized) {
case 'Painting':
return 1
case 'Video':
@ -66,17 +68,18 @@ export async function generate(data, generateData) {
let taskId = null
let pollInterval = null
useDisplay.isSubGerenate = true
if (!data.modelId) {
ElNotification({
title: '生成失败',
message: h('i', { style: 'color: teal' }, '未找到模型ID请联系管理员配置'),
type: 'error'
})
useDisplay.isSubGerenate = false
return
}
useDisplay.isSubGerenate = true
// 从登录态获取 sessionId
const sessionId = useUserStore().userInfo.sessionId
if (!sessionId) {
@ -96,7 +99,8 @@ export async function generate(data, generateData) {
const requestBody = {
model_id: data.modelId,
body,
request: data.request
request: data.request,
platform_code: getPlatformCode(data.type)
}
// POST 创建任务

View File

@ -10,13 +10,13 @@
</span>
<div :style="{ visibility: !isHovering && !showExternalGenerateData ? 'visible' : 'hidden' }" class="generate-data internal">
<div class="detailed-data first-detailed-data">{{ props.item.generateData.model }}</div>
<div class="detailed-data">{{ props.item.generateData.proportion }}</div>
<div class="detailed-data">{{ secondTagText }}</div>
</div>
</div>
</div>
<div v-show="!isHovering && showExternalGenerateData" class="generate-data external">
<div class="detailed-data first-detailed-data">{{ props.item.generateData.model }}</div>
<div class="detailed-data">{{ props.item.generateData.proportion }}</div>
<div class="detailed-data">{{ secondTagText }}</div>
</div>
</div>
@ -212,6 +212,22 @@ const generateStatusText = computed(() => {
return ''
})
//
const secondTagText = computed(() => {
const gd = props.item.generateData
if (props.item.type === 'Music') {
if (gd.pureMusic === true) return '纯音乐'
if (gd.pureMusic === false) return '有歌词'
return ''
}
// Painting/Video modelParams
const params = gd.modelParams || {}
const key = Object.keys(params).find((k) =>
['aspectRatio', 'ratio', 'proportion'].includes(k)
)
return key ? params[key] : ''
})
const AIbrush = (file, index) => {
emit('open-canvas', {
mainImage: { url: file, index: index + 1 },