1、后台积分比例需要对图片增加和列表增加传入图片价格,倍率、基础积分、每张积分
2、后台列表视频倍率、视频基础积分、视频每秒积分放在单独的传入视频列里 3、列表图片的对应放在单独的传入图片列里 4、/credits/credit-ratios接口对应返回图片添加的规则 5、/credits/ratios接口对应返回图片添加的规则 6、同步修改前台AI创作的/conversation的具体最终图片积分计算逻辑
This commit is contained in:
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+36
-36
@@ -1,37 +1,37 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
|
||||
<title>后台管理</title>
|
||||
<script>
|
||||
(function() {
|
||||
var cached = localStorage.getItem('siteInfo');
|
||||
if (cached) {
|
||||
try {
|
||||
var info = JSON.parse(cached);
|
||||
if (info.siteName) {
|
||||
document.title = info.siteName + ' - 管理后台';
|
||||
}
|
||||
if (info.siteLogo) {
|
||||
var link = document.querySelector('link[rel="icon"]');
|
||||
if (link) {
|
||||
link.href = info.siteLogo;
|
||||
link.type = 'image/png';
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<script type="module" crossorigin src="/assets/index-cvtLwU6k.js"></script>
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
|
||||
<title>后台管理</title>
|
||||
<script>
|
||||
(function() {
|
||||
var cached = localStorage.getItem('siteInfo');
|
||||
if (cached) {
|
||||
try {
|
||||
var info = JSON.parse(cached);
|
||||
if (info.siteName) {
|
||||
document.title = info.siteName + ' - 管理后台';
|
||||
}
|
||||
if (info.siteLogo) {
|
||||
var link = document.querySelector('link[rel="icon"]');
|
||||
if (link) {
|
||||
link.href = info.siteLogo;
|
||||
link.type = 'image/png';
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<script type="module" crossorigin src="/assets/index-Bq0Cmo9c.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-D7ShJUt4.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>␍
|
||||
</body>
|
||||
</html>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -25,6 +25,9 @@ interface CreditRatio {
|
||||
inputVideoRatio: number;
|
||||
inputVideoBaseCredits: number;
|
||||
inputVideoPerSecondCredits: number;
|
||||
inputImageRatio: number;
|
||||
inputImageBaseCredits: number;
|
||||
inputImagePerImageCredits: number;
|
||||
}
|
||||
|
||||
interface CreditRatioFormValues {
|
||||
@@ -37,6 +40,9 @@ interface CreditRatioFormValues {
|
||||
inputVideoRatio?: number;
|
||||
inputVideoBaseCredits?: number;
|
||||
inputVideoPerSecondCredits?: number;
|
||||
inputImageRatio?: number;
|
||||
inputImageBaseCredits?: number;
|
||||
inputImagePerImageCredits?: number;
|
||||
}
|
||||
|
||||
const DEFAULT_IMAGE_SIZES = ['2K', '4K'];
|
||||
@@ -136,6 +142,9 @@ const AdminCreditRatios: React.FC = () => {
|
||||
payload.input_video_base_credits = values.inputVideoBaseCredits ?? 0;
|
||||
payload.input_video_per_second_credits = values.inputVideoPerSecondCredits ?? 0;
|
||||
}
|
||||
payload.input_image_ratio = values.inputImageRatio ?? 1.0;
|
||||
payload.input_image_base_credits = values.inputImageBaseCredits ?? 0;
|
||||
payload.input_image_per_image_credits = values.inputImagePerImageCredits ?? 0;
|
||||
if (modal.ratio) {
|
||||
await saveCreditRatio({ id: modal.ratio.id, ...payload });
|
||||
message.success('已更新');
|
||||
@@ -188,6 +197,9 @@ const AdminCreditRatios: React.FC = () => {
|
||||
inputVideoRatio: ratio.inputVideoRatio,
|
||||
inputVideoBaseCredits: ratio.inputVideoBaseCredits,
|
||||
inputVideoPerSecondCredits: ratio.inputVideoPerSecondCredits,
|
||||
inputImageRatio: ratio.inputImageRatio,
|
||||
inputImageBaseCredits: ratio.inputImageBaseCredits,
|
||||
inputImagePerImageCredits: ratio.inputImagePerImageCredits,
|
||||
});
|
||||
} else {
|
||||
form.resetFields();
|
||||
@@ -199,6 +211,9 @@ const AdminCreditRatios: React.FC = () => {
|
||||
inputVideoRatio: 1.0,
|
||||
inputVideoBaseCredits: 0,
|
||||
inputVideoPerSecondCredits: 0.5,
|
||||
inputImageRatio: 1.0,
|
||||
inputImageBaseCredits: 0,
|
||||
inputImagePerImageCredits: 0.5,
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -238,31 +253,60 @@ const AdminCreditRatios: React.FC = () => {
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '视频倍率', dataIndex: 'inputVideoRatio', width: 100,
|
||||
render: (v: number, r: CreditRatio) => (
|
||||
<Typography.Text>{r.genType === 'image' ? '-' : (
|
||||
<span style={{ color: v >= 2 ? '#ef4444' : v >= 1.5 ? '#f59e0b' : '#10b981' }}>x{v}</span>
|
||||
)}</Typography.Text>
|
||||
),
|
||||
title: '传入视频',
|
||||
children: [
|
||||
{
|
||||
title: '倍率', dataIndex: 'inputVideoRatio', width: 90,
|
||||
render: (v: number, r: CreditRatio) => (
|
||||
<Typography.Text>{r.genType === 'image' ? '-' : (
|
||||
<span style={{ color: v >= 2 ? '#ef4444' : v >= 1.5 ? '#f59e0b' : '#10b981' }}>x{v}</span>
|
||||
)}</Typography.Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '基础积分', dataIndex: 'inputVideoBaseCredits', width: 100,
|
||||
render: (v: number, r: CreditRatio) => (
|
||||
<Typography.Text>{r.genType === 'image' ? '-' : `${v} 积分`}</Typography.Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '每秒积分', dataIndex: 'inputVideoPerSecondCredits', width: 100,
|
||||
render: (v: number, r: CreditRatio) => (
|
||||
<Typography.Text>{r.genType === 'image' ? '-' : `${v} 积分/秒`}</Typography.Text>
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '视频基础积分', dataIndex: 'inputVideoBaseCredits', width: 110,
|
||||
render: (v: number, r: CreditRatio) => (
|
||||
<Typography.Text>{r.genType === 'image' ? '-' : `${v} 积分`}</Typography.Text>
|
||||
),
|
||||
title: '传入图片',
|
||||
children: [
|
||||
{
|
||||
title: '倍率', dataIndex: 'inputImageRatio', width: 90,
|
||||
render: (v: number) => (
|
||||
<span style={{ color: v >= 2 ? '#ef4444' : v >= 1.5 ? '#f59e0b' : '#10b981' }}>x{v}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '基础积分', dataIndex: 'inputImageBaseCredits', width: 100,
|
||||
render: (v: number) => <Typography.Text>{v} 积分</Typography.Text>,
|
||||
},
|
||||
{
|
||||
title: '每张积分', dataIndex: 'inputImagePerImageCredits', width: 100,
|
||||
render: (v: number) => <Typography.Text>{v} 积分/张</Typography.Text>,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '视频每秒积分', dataIndex: 'inputVideoPerSecondCredits', width: 110,
|
||||
render: (v: number, r: CreditRatio) => (
|
||||
<Typography.Text>{r.genType === 'image' ? '-' : `${v} 积分/秒`}</Typography.Text>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '示例计算(视频15秒,上传视频15秒)', key: 'example', width: 140,
|
||||
title: '示例计算', key: 'example', width: 160,
|
||||
render: (_: any, r: CreditRatio) => {
|
||||
let total: number;
|
||||
if (r.genType === 'image') {
|
||||
total = Math.round(r.baseCredits * r.ratio);
|
||||
if (r.inputImageRatio && r.inputImagePerImageCredits) {
|
||||
total += Math.round(
|
||||
(r.inputImageBaseCredits + r.inputImagePerImageCredits * 3) * r.inputImageRatio
|
||||
);
|
||||
}
|
||||
} else {
|
||||
total = Math.round((r.baseCredits + r.perSecondCredits * 15) * r.ratio);
|
||||
if (r.inputVideoRatio && r.inputVideoPerSecondCredits) {
|
||||
@@ -270,6 +314,11 @@ const AdminCreditRatios: React.FC = () => {
|
||||
(r.inputVideoBaseCredits + r.inputVideoPerSecondCredits * 15) * r.inputVideoRatio
|
||||
);
|
||||
}
|
||||
if (r.inputImageRatio && r.inputImagePerImageCredits) {
|
||||
total += Math.round(
|
||||
(r.inputImageBaseCredits + r.inputImagePerImageCredits * 3) * r.inputImageRatio
|
||||
);
|
||||
}
|
||||
}
|
||||
return <Typography.Text strong style={{ color: '#6366f1' }}>{total} 积分</Typography.Text>;
|
||||
},
|
||||
@@ -346,7 +395,7 @@ const AdminCreditRatios: React.FC = () => {
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
pagination={false}
|
||||
scroll={{ x: 1180 }}
|
||||
scroll={{ x: 1500 }}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
@@ -421,6 +470,29 @@ const AdminCreditRatios: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div style={{
|
||||
marginTop: 12,
|
||||
padding: '12px 16px',
|
||||
backgroundColor: '#f0fff4',
|
||||
borderRadius: 8,
|
||||
border: '1px solid #c6f6d5',
|
||||
}}>
|
||||
<Typography.Text strong style={{ display: 'block', marginBottom: 8, color: '#059669' }}>
|
||||
传入图片价格
|
||||
</Typography.Text>
|
||||
<div style={{ display: 'flex', gap: 16 }}>
|
||||
<Form.Item name="inputImageRatio" label="倍率" style={{ flex: 1, marginBottom: 0 }} rules={[{ required: true, message: '请输入传入图片倍率' }]}>
|
||||
<InputNumber min={0} max={10} step={0.1} style={{ width: '100%' }} size="large" />
|
||||
</Form.Item>
|
||||
<Form.Item name="inputImageBaseCredits" label="基础积分" style={{ flex: 1, marginBottom: 0 }} rules={[{ required: true, message: '请输入传入图片基础积分' }]}>
|
||||
<InputNumber min={0} max={500} style={{ width: '100%' }} size="large" />
|
||||
</Form.Item>
|
||||
<Form.Item name="inputImagePerImageCredits" label="每张积分" style={{ flex: 1, marginBottom: 0 }} rules={[{ required: true, message: '请输入传入图片每张积分' }]}>
|
||||
<InputNumber min={0} max={50} style={{ width: '100%' }} size="large" />
|
||||
</Form.Item>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
</Modal>
|
||||
</div>
|
||||
|
||||
@@ -1383,6 +1383,7 @@ async def create_credit_ratio(
|
||||
ensure_ascii=False,
|
||||
),
|
||||
)
|
||||
await db.commit()
|
||||
return ratio
|
||||
|
||||
|
||||
@@ -1422,6 +1423,7 @@ async def update_credit_ratio(
|
||||
ensure_ascii=False,
|
||||
),
|
||||
)
|
||||
await db.commit()
|
||||
return ratio
|
||||
|
||||
|
||||
@@ -1455,6 +1457,7 @@ async def delete_credit_ratio(
|
||||
ensure_ascii=False,
|
||||
),
|
||||
)
|
||||
await db.commit()
|
||||
return {"message": "ok"}
|
||||
|
||||
|
||||
|
||||
@@ -25,3 +25,6 @@ class CreditRatio(Base, TimestampMixin):
|
||||
input_video_ratio: Mapped[float] = mapped_column(Float, default=1.0)
|
||||
input_video_base_credits: Mapped[float] = mapped_column(Float, default=0.0)
|
||||
input_video_per_second_credits: Mapped[float] = mapped_column(Float, default=0.5)
|
||||
input_image_ratio: Mapped[float] = mapped_column(Float, default=1.0)
|
||||
input_image_base_credits: Mapped[float] = mapped_column(Float, default=0.0)
|
||||
input_image_per_image_credits: Mapped[float] = mapped_column(Float, default=0.5)
|
||||
|
||||
@@ -66,6 +66,7 @@ async def calc_video_credits(
|
||||
resolution: str,
|
||||
engine_id: str | None = None,
|
||||
input_video_duration: float | None = None,
|
||||
input_image_count: int | None = None,
|
||||
) -> float:
|
||||
"""Calculate video credits using CreditRatio table, with fallback to hardcoded.
|
||||
|
||||
@@ -75,6 +76,7 @@ async def calc_video_credits(
|
||||
3. 原硬编码默认算法。
|
||||
|
||||
input_video_duration: 用户上传的参考视频总时长(秒),不为空时额外计费
|
||||
input_image_count: 用户上传的参考图片数量,不为空时额外计费
|
||||
"""
|
||||
if not engine_id:
|
||||
video_engines_result = await db.execute(
|
||||
@@ -97,6 +99,11 @@ async def calc_video_credits(
|
||||
ratio.input_video_base_credits + ratio.input_video_per_second_credits * input_video_duration
|
||||
) * ratio.input_video_ratio
|
||||
base_cost += input_video_cost
|
||||
if input_image_count and input_image_count > 0:
|
||||
input_image_cost = (
|
||||
ratio.input_image_base_credits + ratio.input_image_per_image_credits * input_image_count
|
||||
) * ratio.input_image_ratio
|
||||
base_cost += input_image_cost
|
||||
return round(base_cost, 2)
|
||||
|
||||
base = 60.0
|
||||
@@ -105,6 +112,8 @@ async def calc_video_credits(
|
||||
total = (base + duration_cost) * multiplier
|
||||
if input_video_duration and input_video_duration > 0:
|
||||
total += input_video_duration * 0.5 * multiplier
|
||||
if input_image_count and input_image_count > 0:
|
||||
total += input_image_count * 0.5 * multiplier
|
||||
return round(total, 2)
|
||||
|
||||
|
||||
@@ -120,6 +129,7 @@ async def calc_image_credits(
|
||||
db: AsyncSession,
|
||||
image_size: str,
|
||||
engine_id: str | None = None,
|
||||
input_image_count: int | None = None,
|
||||
) -> float:
|
||||
"""Calculate image credits using CreditRatio table, with fallback to hardcoded.
|
||||
|
||||
@@ -127,6 +137,8 @@ async def calc_image_credits(
|
||||
1. gen_type=image + engine_id + image_size 精确规则;
|
||||
2. gen_type=image + image_size 下 base_credits/per_second_credits 最高规则;
|
||||
3. 原硬编码默认算法。
|
||||
|
||||
input_image_count: 用户上传的参考图片数量,不为空时额外计费
|
||||
"""
|
||||
# 如果engine_id为空,默认查询权重最高的图片引擎积分规则
|
||||
if not engine_id:
|
||||
@@ -144,12 +156,21 @@ async def calc_image_credits(
|
||||
engine_id=engine_id,
|
||||
)
|
||||
if ratio:
|
||||
return round(ratio.base_credits * ratio.ratio, 2)
|
||||
base_cost = ratio.base_credits * ratio.ratio
|
||||
if input_image_count and input_image_count > 0:
|
||||
input_image_cost = (
|
||||
ratio.input_image_base_credits + ratio.input_image_per_image_credits * input_image_count
|
||||
) * ratio.input_image_ratio
|
||||
base_cost += input_image_cost
|
||||
return round(base_cost, 2)
|
||||
|
||||
# Fallback
|
||||
multiplier = {"4K": 2.0, "2K": 1.0}.get(image_size, 1.0)
|
||||
base_cost = 4.0
|
||||
return round(base_cost * multiplier, 2)
|
||||
total = base_cost * multiplier
|
||||
if input_image_count and input_image_count > 0:
|
||||
total += input_image_count * 0.5 * multiplier
|
||||
return round(total, 2)
|
||||
|
||||
|
||||
async def _get_existing_credit_record_by_biz_key(
|
||||
|
||||
+3
-3
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -28,7 +28,7 @@
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<script type="module" crossorigin src="/assets/index-CYMIldpb.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-CT6M5c-S.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-JhRVnnL-.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -386,6 +386,9 @@ const AIChatPage: React.FC = () => {
|
||||
inputVideoRatio: 1,
|
||||
inputVideoBaseCredits: 0,
|
||||
inputVideoPerSecondCredits: 0.5,
|
||||
inputImageRatio: 1,
|
||||
inputImageBaseCredits: 0,
|
||||
inputImagePerImageCredits: 0.5,
|
||||
};
|
||||
if (videoResolution === '1080p') {
|
||||
config.ratio = 2;
|
||||
@@ -401,6 +404,9 @@ const AIChatPage: React.FC = () => {
|
||||
perSecondCredits: 0.1,
|
||||
baseCredits: 4,
|
||||
ratio: 1,
|
||||
inputImageRatio: 1,
|
||||
inputImageBaseCredits: 0,
|
||||
inputImagePerImageCredits: 0.5,
|
||||
};
|
||||
if (selectedResolution === '2K') {
|
||||
config.ratio = 1;
|
||||
@@ -422,10 +428,27 @@ const AIChatPage: React.FC = () => {
|
||||
const inputVideoCost = ((config.inputVideoBaseCredits || 0) + (config.inputVideoPerSecondCredits || 0) * inputVideoDuration) * (config.inputVideoRatio || 1);
|
||||
total += inputVideoCost;
|
||||
}
|
||||
// 传入图片积分
|
||||
const inputImageCount = currentMedia
|
||||
.filter((m) => m.type === 'image')
|
||||
.length;
|
||||
if (inputImageCount > 0) {
|
||||
const inputImageCost = ((config.inputImageBaseCredits || 0) + (config.inputImagePerImageCredits || 0) * inputImageCount) * (config.inputImageRatio || 1);
|
||||
total += inputImageCost;
|
||||
}
|
||||
return Number(total.toFixed(2));
|
||||
} else {
|
||||
// 图片:baseCredits × ratio
|
||||
return Number((config.baseCredits * config.ratio).toFixed(2));
|
||||
let total = config.baseCredits * config.ratio;
|
||||
// 传入图片积分
|
||||
const inputImageCount = currentMedia
|
||||
.filter((m) => m.type === 'image')
|
||||
.length;
|
||||
if (inputImageCount > 0) {
|
||||
const inputImageCost = ((config.inputImageBaseCredits || 0) + (config.inputImagePerImageCredits || 0) * inputImageCount) * (config.inputImageRatio || 1);
|
||||
total += inputImageCost;
|
||||
}
|
||||
return Number(total.toFixed(2));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user