mirror of
https://github.com/yangjian102621/geekai.git
synced 2026-05-10 19:54:25 +08:00
optimize jimeng power config
This commit is contained in:
@@ -8,15 +8,7 @@ type JimengConfig struct {
|
||||
// 火山引擎大模型专用的验证方式
|
||||
ApiKey string `json:"api_key"`
|
||||
// 算力配置
|
||||
Power JimengPower `json:"power"`
|
||||
}
|
||||
|
||||
// JimengPower 即梦AI算力配置
|
||||
type JimengPower struct {
|
||||
Image int `json:"image"` // 图片生成算力,单位:积分/张
|
||||
Video int `json:"video"` // 视频生成算力,单位:积分/秒
|
||||
VirtualHuman int `json:"virtual_human"` // 数字人视频生成算力,单位:积分/秒
|
||||
ActionTransfer int `json:"action_transfer"` // 视频动作迁移算力,单位:积分/秒
|
||||
Powers map[string]int `json:"powers"`
|
||||
}
|
||||
|
||||
// JMTaskStatus 任务状态
|
||||
|
||||
@@ -231,21 +231,15 @@ func (h *AdminJimengHandler) UpdateConfig(c *gin.Context) {
|
||||
}
|
||||
|
||||
// 验证算力配置
|
||||
if req.Power.Image <= 0 {
|
||||
resp.ERROR(c, "图片生成算力必须大于0")
|
||||
if len(req.Powers) == 0 {
|
||||
resp.ERROR(c, "请至少配置一个模型的积分")
|
||||
return
|
||||
}
|
||||
if req.Power.Video <= 0 {
|
||||
resp.ERROR(c, "视频生成算力必须大于0")
|
||||
for key, val := range req.Powers {
|
||||
if val <= 0 {
|
||||
resp.ERROR(c, fmt.Sprintf("模型 %s 的积分必须大于0", key))
|
||||
return
|
||||
}
|
||||
if req.Power.VirtualHuman <= 0 {
|
||||
resp.ERROR(c, "数字人生成算力必须大于0")
|
||||
return
|
||||
}
|
||||
if req.Power.ActionTransfer <= 0 {
|
||||
resp.ERROR(c, "视频动作迁移算力必须大于0")
|
||||
return
|
||||
}
|
||||
|
||||
// 保存配置
|
||||
|
||||
@@ -39,12 +39,12 @@ func NewJimengHandler(app *core.AppServer, jimengService *jimeng.Service, db *go
|
||||
// RegisterRoutes 注册路由,新增统一任务接口
|
||||
func (h *JimengHandler) RegisterRoutes() {
|
||||
group := h.App.Engine.Group("/api/jimeng/")
|
||||
group.GET("power-config", h.GetPowerConfig)
|
||||
|
||||
// 需要用户授权的接口
|
||||
group.Use(middleware.UserAuthMiddleware(h.App.Config.Session.SecretKey, h.App.Redis))
|
||||
{
|
||||
group.POST("task", h.CreateTask)
|
||||
group.GET("power-config", h.GetPowerConfig)
|
||||
group.POST("jobs", h.Jobs)
|
||||
group.GET("remove", h.Remove)
|
||||
group.GET("retry", h.Retry)
|
||||
@@ -125,15 +125,31 @@ func (h *JimengHandler) CreateTask(c *gin.Context) {
|
||||
|
||||
func (h *JimengHandler) getTaskRemark(req types.JimengTaskRequest, jobId uint) string {
|
||||
remark := fmt.Sprintf("即梦任务%s,任务ID:%d", req.ReqKey, jobId)
|
||||
perUnit, ok := h.App.SysConfig.Jimeng.Powers[req.ReqKey]
|
||||
if !ok || perUnit <= 0 {
|
||||
return remark // Fallback if power not found or invalid
|
||||
}
|
||||
switch req.TaskType {
|
||||
case types.JMTaskTypeImage:
|
||||
remark = fmt.Sprintf("即梦图片生成,任务ID:%d,%d积分/张", jobId, h.App.SysConfig.Jimeng.Power.Image)
|
||||
remark = fmt.Sprintf("即梦图片生成,任务ID:%d,%d积分/张", jobId, perUnit)
|
||||
case types.JMTaskTypeVideo:
|
||||
remark = fmt.Sprintf("即梦视频生成,任务ID:%d,%d积分/秒, %d秒", jobId, h.App.SysConfig.Jimeng.Power.Video, req.Power/h.App.SysConfig.Jimeng.Power.Video)
|
||||
seconds := 0
|
||||
if perUnit > 0 {
|
||||
seconds = req.Power / perUnit
|
||||
}
|
||||
remark = fmt.Sprintf("即梦视频生成,任务ID:%d,%d积分/秒, %d秒", jobId, perUnit, seconds)
|
||||
case types.JMTaskTypeVirtualHuman:
|
||||
remark = fmt.Sprintf("即梦数字人视频生成,任务ID:%d,%d积分/秒, %d秒", jobId, h.App.SysConfig.Jimeng.Power.VirtualHuman, req.Power/h.App.SysConfig.Jimeng.Power.VirtualHuman)
|
||||
seconds := 0
|
||||
if perUnit > 0 {
|
||||
seconds = req.Power / perUnit
|
||||
}
|
||||
remark = fmt.Sprintf("即梦数字人视频生成,任务ID:%d,%d积分/秒, %d秒", jobId, perUnit, seconds)
|
||||
case types.JMTaskTypeActionTransfer:
|
||||
remark = fmt.Sprintf("即梦视频动作迁移,任务ID:%d,%d积分/秒, %d秒", jobId, h.App.SysConfig.Jimeng.Power.ActionTransfer, req.Power/h.App.SysConfig.Jimeng.Power.ActionTransfer)
|
||||
seconds := 0
|
||||
if perUnit > 0 {
|
||||
seconds = req.Power / perUnit
|
||||
}
|
||||
remark = fmt.Sprintf("即梦视频动作迁移,任务ID:%d,%d积分/秒, %d秒", jobId, perUnit, seconds)
|
||||
}
|
||||
return remark
|
||||
}
|
||||
@@ -299,20 +315,22 @@ func (h *JimengHandler) Retry(c *gin.Context) {
|
||||
resp.SUCCESS(c, gin.H{"message": "重试任务已提交"})
|
||||
}
|
||||
|
||||
// getPowerFromConfig 从配置中获取指定类型的算力消耗
|
||||
func (h *JimengHandler) getTaskPower(req types.JimengTaskRequest) (int, error) {
|
||||
logger.Debugf("getTaskPower req: %+v", req)
|
||||
config := h.App.SysConfig.Jimeng
|
||||
basePower, ok := config.Powers[req.ReqKey]
|
||||
if !ok || basePower <= 0 {
|
||||
return 0, errors.New("未配置模型积分或配置不合法")
|
||||
}
|
||||
switch req.TaskType {
|
||||
case types.JMTaskTypeImage:
|
||||
return config.Power.Image, nil
|
||||
return basePower, nil
|
||||
case types.JMTaskTypeVideo:
|
||||
if req.Duration == 0 {
|
||||
return 0, errors.New("视频时长不能为0")
|
||||
}
|
||||
return config.Power.Video * req.Duration, nil
|
||||
return basePower * req.Duration, nil
|
||||
case types.JMTaskTypeVirtualHuman:
|
||||
// TODO 计算音频时长
|
||||
if req.AudioURL == "" {
|
||||
return 0, errors.New("音频URL不能为空")
|
||||
}
|
||||
@@ -320,9 +338,12 @@ func (h *JimengHandler) getTaskPower(req types.JimengTaskRequest) (int, error) {
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return config.Power.VirtualHuman * int(audioDuration.Seconds()), nil
|
||||
seconds := int(audioDuration.Seconds())
|
||||
if seconds <= 0 {
|
||||
return 0, errors.New("音频时长无效")
|
||||
}
|
||||
return basePower * seconds, nil
|
||||
case types.JMTaskTypeActionTransfer:
|
||||
// TODO 计算视频时长
|
||||
if req.VideoURL == "" {
|
||||
return 0, errors.New("视频URL不能为空")
|
||||
}
|
||||
@@ -330,7 +351,11 @@ func (h *JimengHandler) getTaskPower(req types.JimengTaskRequest) (int, error) {
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return config.Power.ActionTransfer * int(videoDuration.Seconds()), nil
|
||||
seconds := int(videoDuration.Seconds())
|
||||
if seconds <= 0 {
|
||||
return 0, errors.New("视频时长无效")
|
||||
}
|
||||
return basePower * seconds, nil
|
||||
default:
|
||||
return 0, errors.New("任务类型不支持")
|
||||
}
|
||||
@@ -340,9 +365,6 @@ func (h *JimengHandler) getTaskPower(req types.JimengTaskRequest) (int, error) {
|
||||
func (h *JimengHandler) GetPowerConfig(c *gin.Context) {
|
||||
config := h.App.SysConfig.Jimeng
|
||||
resp.SUCCESS(c, gin.H{
|
||||
"image": config.Power.Image,
|
||||
"video": config.Power.Video,
|
||||
"virtual_human": config.Power.VirtualHuman,
|
||||
"action_transfer": config.Power.ActionTransfer,
|
||||
"powers": config.Powers,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -341,6 +341,7 @@
|
||||
// 提示词指南样式
|
||||
.prompt-guide {
|
||||
margin: 12px 0 16px;
|
||||
background-color: var(--el-fill-color-blank);
|
||||
|
||||
.guide-title {
|
||||
display: flex;
|
||||
|
||||
@@ -36,7 +36,7 @@ export const useJimengStore = defineStore('jimeng', () => {
|
||||
const shareStore = useSharedStore()
|
||||
|
||||
// 积分消耗配置
|
||||
const powerConfig = reactive({})
|
||||
const powerConfig = reactive({ powers: {} })
|
||||
const currentPowerCost = ref('0积分')
|
||||
|
||||
// 功能配置
|
||||
@@ -83,12 +83,10 @@ export const useJimengStore = defineStore('jimeng', () => {
|
||||
// 获取状态类型
|
||||
const getTaskType = (type) => {
|
||||
const typeMap = {
|
||||
text_to_image: 'primary',
|
||||
image_to_image: 'primary',
|
||||
image_edit: 'primary',
|
||||
image_effects: 'primary',
|
||||
text_to_video: 'success',
|
||||
image_to_video: 'success',
|
||||
image: 'info',
|
||||
video: 'primary',
|
||||
virtual_human: 'success',
|
||||
action_transfer: 'warning',
|
||||
}
|
||||
return typeMap[type] || 'primary'
|
||||
}
|
||||
@@ -124,7 +122,7 @@ export const useJimengStore = defineStore('jimeng', () => {
|
||||
}
|
||||
|
||||
total.value = data.total || 0
|
||||
if (data.items.length < pageSize.value) {
|
||||
if (!data.items || data.items.length < pageSize.value) {
|
||||
isOver.value = true
|
||||
}
|
||||
if (pageNum === 1) {
|
||||
@@ -150,7 +148,7 @@ export const useJimengStore = defineStore('jimeng', () => {
|
||||
page_size: 20,
|
||||
})
|
||||
const data = response.data
|
||||
if (data.items.length === 0) {
|
||||
if (!data.items || data.items.length === 0) {
|
||||
stopPolling()
|
||||
return
|
||||
}
|
||||
@@ -184,7 +182,6 @@ export const useJimengStore = defineStore('jimeng', () => {
|
||||
shareStore.setShowLoginDialog(true)
|
||||
return
|
||||
}
|
||||
console.log(formData.value)
|
||||
for (const key in requiredKeys.value) {
|
||||
if (!formData.value[key]) {
|
||||
showMessageError('缺少参数:' + requiredKeys.value[key].label)
|
||||
@@ -284,20 +281,32 @@ export const useJimengStore = defineStore('jimeng', () => {
|
||||
}
|
||||
|
||||
const setFunctionPowers = () => {
|
||||
if (activeFunction.value === 'image') {
|
||||
currentPowerCost.value = `${powerConfig.image}积分/张`
|
||||
} else {
|
||||
currentPowerCost.value = `${powerConfig.video}积分/秒`
|
||||
nextTick(() => {
|
||||
const key = formData.value.req_key
|
||||
const perUnit = key ? powerConfig.powers[key] : 0
|
||||
if (!perUnit) {
|
||||
currentPowerCost.value = '未配置积分'
|
||||
return
|
||||
}
|
||||
currentPowerCost.value =
|
||||
activeFunction.value === 'image' ? `${perUnit}积分/张` : `${perUnit}积分/秒`
|
||||
})
|
||||
}
|
||||
|
||||
watch(
|
||||
() => formData.value,
|
||||
() => {
|
||||
setFunctionPowers()
|
||||
}
|
||||
)
|
||||
|
||||
// 初始化方法
|
||||
const init = async () => {
|
||||
try {
|
||||
// 获取积分消耗配置
|
||||
const powerRes = await httpGet('/api/jimeng/power-config')
|
||||
if (powerRes.data) {
|
||||
Object.assign(powerConfig, powerRes.data)
|
||||
powerConfig.powers = powerRes.data.powers || {}
|
||||
setFunctionPowers()
|
||||
}
|
||||
const user = await checkSession()
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 提示词编写指南(可折叠) -->
|
||||
<div class="prompt-guide">
|
||||
<div class="prompt-guide pl-2">
|
||||
<el-collapse v-model="guideActive">
|
||||
<el-collapse-item name="guide">
|
||||
<template #title>
|
||||
|
||||
@@ -82,59 +82,44 @@
|
||||
<el-divider />
|
||||
<!-- 算力配置分组 -->
|
||||
<div class="mb-3">
|
||||
<h3 class="heading-3 mb-3">算力配置</h3>
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<div class="text-gray-500 text-sm">
|
||||
生成图片消耗的积分,包括:文生图、图生图、图片编辑、图片特效,<el-tag type="primary"
|
||||
>单位:积分/张</el-tag
|
||||
<h3 class="heading-3 mb-3">任务积分配置</h3>
|
||||
<Alert type="info" class="mb-3">
|
||||
<div class="text-gray-500">
|
||||
图片类模型统一都是 0.2 元一张,假如你100积分售价1元,建议设置:20积分/张。
|
||||
</div>
|
||||
<div class="text-gray-500">
|
||||
视频/数字人/动作迁移单位:积分/秒,但是不同的模型的价格不一样,建议去火山方舟控制台查看,根据价格设置积分。
|
||||
</div>
|
||||
</Alert>
|
||||
|
||||
<div v-for="func in functions" :key="func.key" class="mb-4">
|
||||
<h4 class="mb-2 text-base font-bold flex items-center gap-2">
|
||||
<i class="iconfont" :class="func.icon"></i>
|
||||
{{ func.name }}
|
||||
<el-tag size="small" type="info">{{ getUnit(func.key) }}</el-tag>
|
||||
</h4>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||
<div
|
||||
v-for="model in params[func.key]"
|
||||
:key="model.key"
|
||||
class="p-3 rounded-md border border-gray-100"
|
||||
>
|
||||
<div class="text-sm mb-2">
|
||||
<div class="font-bold">{{ model.name }}</div>
|
||||
<div class="text-gray-500 line-clamp-2" :title="model.label">
|
||||
{{ model.label }}
|
||||
</div>
|
||||
</template>
|
||||
<el-input-number
|
||||
v-model="jimengConfig.power.image"
|
||||
:min="1"
|
||||
placeholder="请输入图片生成算力消耗"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<div class="text-gray-500 text-sm">
|
||||
生成视频消耗的积分,包括:文生视频、图生视频,<el-tag type="primary"
|
||||
>单位:积分/秒</el-tag
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
<el-input-number
|
||||
v-model="jimengConfig.power.video"
|
||||
v-model="jimengConfig.powers[model.key]"
|
||||
:min="1"
|
||||
placeholder="请输入视频生成算力消耗"
|
||||
:placeholder="`对应模型:${model.key}(${getUnit(func.key)})`"
|
||||
class="w-full"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<div class="text-gray-500 text-sm">
|
||||
生成数字人视频消耗的积分,<el-tag type="primary">单位:积分/秒</el-tag>
|
||||
<div class="text-xs text-gray-400 mt-1">对应模型:{{ model.key }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<el-input-number
|
||||
v-model="jimengConfig.power.virtual_human"
|
||||
:min="1"
|
||||
placeholder="请输入数字人视频生成算力消耗"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<template #label>
|
||||
<div class="text-gray-500 text-sm">
|
||||
生成视频动作迁移消耗的积分,<el-tag type="primary">单位:积分/秒</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
<el-input-number
|
||||
v-model="jimengConfig.power.action_transfer"
|
||||
:min="1"
|
||||
placeholder="请输入视频动作迁移算力消耗"
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div style="padding: 10px">
|
||||
<el-form-item>
|
||||
@@ -149,6 +134,7 @@
|
||||
|
||||
<script setup>
|
||||
import Alert from '@/components/ui/Alert.vue'
|
||||
import { JimengFunctions, JimengParams } from '@/store/data/jimeng_params'
|
||||
import { httpGet, httpPost } from '@/utils/http'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { onMounted, ref } from 'vue'
|
||||
@@ -156,20 +142,15 @@ import { onMounted, ref } from 'vue'
|
||||
const jimengConfig = ref({
|
||||
access_key: '',
|
||||
secret_key: '',
|
||||
power: {
|
||||
text_to_image: 10,
|
||||
image_to_image: 15,
|
||||
image_edit: 20,
|
||||
image_effects: 25,
|
||||
text_to_video: 30,
|
||||
image_to_video: 35,
|
||||
},
|
||||
api_key: '',
|
||||
powers: {},
|
||||
})
|
||||
|
||||
const loading = ref(true)
|
||||
const saving = ref(false)
|
||||
const testing = ref(false)
|
||||
const configFormRef = ref()
|
||||
const functions = JimengFunctions
|
||||
const params = JimengParams
|
||||
|
||||
// 表单验证规则
|
||||
const rules = {
|
||||
@@ -177,6 +158,8 @@ const rules = {
|
||||
secret_key: [{ required: true, message: '请输入SecretKey', trigger: 'blur' }],
|
||||
}
|
||||
|
||||
const getUnit = (funcKey) => (funcKey === 'image' ? '积分/张' : '积分/秒')
|
||||
|
||||
onMounted(() => {
|
||||
loadConfig()
|
||||
})
|
||||
@@ -185,7 +168,9 @@ onMounted(() => {
|
||||
const loadConfig = async () => {
|
||||
try {
|
||||
const res = await httpGet('/api/admin/config/get?key=jimeng')
|
||||
jimengConfig.value = res.data
|
||||
const cfg = res.data || {}
|
||||
cfg.powers = cfg.powers || {}
|
||||
jimengConfig.value = cfg
|
||||
} catch (e) {
|
||||
ElMessage.error('加载配置失败: ' + e.message)
|
||||
} finally {
|
||||
@@ -214,14 +199,8 @@ const resetConfig = () => {
|
||||
jimengConfig.value = {
|
||||
access_key: '',
|
||||
secret_key: '',
|
||||
power: {
|
||||
text_to_image: 10,
|
||||
image_to_image: 15,
|
||||
image_edit: 20,
|
||||
image_effects: 25,
|
||||
text_to_video: 30,
|
||||
image_to_video: 35,
|
||||
},
|
||||
api_key: '',
|
||||
powers: {},
|
||||
}
|
||||
ElMessage.info('配置已重置')
|
||||
}
|
||||
@@ -237,7 +216,7 @@ const resetConfig = () => {
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 800px;
|
||||
max-width: 1000px;
|
||||
}
|
||||
|
||||
.heading-3 {
|
||||
|
||||
Reference in New Issue
Block a user