feat: add OpenAI image generation controls
This commit is contained in:
@@ -666,6 +666,40 @@
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 mb-3">
|
||||
{{ t("admin.groups.imagePricing.description") }}
|
||||
</p>
|
||||
<div class="mb-4 grid grid-cols-1 gap-3 md:grid-cols-2">
|
||||
<label class="flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300">
|
||||
<input
|
||||
v-model="createForm.allow_image_generation"
|
||||
type="checkbox"
|
||||
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
{{ t("admin.groups.imagePricing.allowImageGeneration") }}
|
||||
</label>
|
||||
<label class="flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300">
|
||||
<input
|
||||
v-model="createForm.image_rate_independent"
|
||||
type="checkbox"
|
||||
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
{{ t("admin.groups.imagePricing.independentMultiplier") }}
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
v-if="createForm.image_rate_independent"
|
||||
class="mb-4"
|
||||
>
|
||||
<label class="input-label">{{
|
||||
t("admin.groups.imagePricing.imageMultiplier")
|
||||
}}</label>
|
||||
<input
|
||||
v-model.number="createForm.image_rate_multiplier"
|
||||
type="number"
|
||||
step="0.0001"
|
||||
min="0"
|
||||
class="input"
|
||||
placeholder="1"
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 gap-3">
|
||||
<div>
|
||||
<label class="input-label">1K ($)</label>
|
||||
@@ -701,6 +735,22 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p class="mt-3 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t("admin.groups.imagePricing.modeHint") }}
|
||||
</p>
|
||||
<div class="mt-2 rounded-lg bg-gray-50 p-3 text-xs text-gray-700 dark:bg-gray-800 dark:text-gray-300">
|
||||
<div class="mb-1 font-medium">
|
||||
{{ t("admin.groups.imagePricing.finalPricePreview") }}
|
||||
</div>
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
<div
|
||||
v-for="item in createImageFinalPricePreview"
|
||||
:key="item.label"
|
||||
>
|
||||
{{ item.label }}: {{ item.value }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 支持的模型系列(仅 antigravity 平台) -->
|
||||
@@ -1801,6 +1851,40 @@
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 mb-3">
|
||||
{{ t("admin.groups.imagePricing.description") }}
|
||||
</p>
|
||||
<div class="mb-4 grid grid-cols-1 gap-3 md:grid-cols-2">
|
||||
<label class="flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300">
|
||||
<input
|
||||
v-model="editForm.allow_image_generation"
|
||||
type="checkbox"
|
||||
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
{{ t("admin.groups.imagePricing.allowImageGeneration") }}
|
||||
</label>
|
||||
<label class="flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300">
|
||||
<input
|
||||
v-model="editForm.image_rate_independent"
|
||||
type="checkbox"
|
||||
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
{{ t("admin.groups.imagePricing.independentMultiplier") }}
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
v-if="editForm.image_rate_independent"
|
||||
class="mb-4"
|
||||
>
|
||||
<label class="input-label">{{
|
||||
t("admin.groups.imagePricing.imageMultiplier")
|
||||
}}</label>
|
||||
<input
|
||||
v-model.number="editForm.image_rate_multiplier"
|
||||
type="number"
|
||||
step="0.0001"
|
||||
min="0"
|
||||
class="input"
|
||||
placeholder="1"
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 gap-3">
|
||||
<div>
|
||||
<label class="input-label">1K ($)</label>
|
||||
@@ -1836,6 +1920,22 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p class="mt-3 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t("admin.groups.imagePricing.modeHint") }}
|
||||
</p>
|
||||
<div class="mt-2 rounded-lg bg-gray-50 p-3 text-xs text-gray-700 dark:bg-gray-800 dark:text-gray-300">
|
||||
<div class="mb-1 font-medium">
|
||||
{{ t("admin.groups.imagePricing.finalPricePreview") }}
|
||||
</div>
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
<div
|
||||
v-for="item in editImageFinalPricePreview"
|
||||
:key="item.label"
|
||||
>
|
||||
{{ item.label }}: {{ item.value }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 支持的模型系列(仅 antigravity 平台) -->
|
||||
@@ -3009,7 +3109,10 @@ const createForm = reactive({
|
||||
daily_limit_usd: null as number | null,
|
||||
weekly_limit_usd: null as number | null,
|
||||
monthly_limit_usd: null as number | null,
|
||||
// 图片生成计费配置(仅 antigravity 平台使用)
|
||||
// 图片生成计费配置
|
||||
allow_image_generation: false,
|
||||
image_rate_independent: false,
|
||||
image_rate_multiplier: 1,
|
||||
image_price_1k: null as number | null,
|
||||
image_price_2k: null as number | null,
|
||||
image_price_4k: null as number | null,
|
||||
@@ -3291,7 +3394,10 @@ const editForm = reactive({
|
||||
daily_limit_usd: null as number | null,
|
||||
weekly_limit_usd: null as number | null,
|
||||
monthly_limit_usd: null as number | null,
|
||||
// 图片生成计费配置(仅 antigravity 平台使用)
|
||||
// 图片生成计费配置
|
||||
allow_image_generation: false,
|
||||
image_rate_independent: false,
|
||||
image_rate_multiplier: 1,
|
||||
image_price_1k: null as number | null,
|
||||
image_price_2k: null as number | null,
|
||||
image_price_4k: null as number | null,
|
||||
@@ -3321,6 +3427,62 @@ const editForm = reactive({
|
||||
rpm_limit: 0 as number,
|
||||
});
|
||||
|
||||
type ImagePricingFormState = {
|
||||
rate_multiplier: number;
|
||||
image_rate_independent: boolean;
|
||||
image_rate_multiplier: number;
|
||||
image_price_1k: number | string | null;
|
||||
image_price_2k: number | string | null;
|
||||
image_price_4k: number | string | null;
|
||||
};
|
||||
|
||||
const imagePricingTiers = [
|
||||
{ key: "image_price_1k", label: "1K" },
|
||||
{ key: "image_price_2k", label: "2K" },
|
||||
{ key: "image_price_4k", label: "4K" },
|
||||
] as const;
|
||||
|
||||
const normalizePreviewNumber = (value: number | string | null | undefined, fallback = 0) => {
|
||||
if (value === null || value === undefined || value === "") {
|
||||
return fallback;
|
||||
}
|
||||
const parsed = Number(value);
|
||||
return Number.isFinite(parsed) ? parsed : fallback;
|
||||
};
|
||||
|
||||
const formatImagePricePreview = (value: number | string | null | undefined) => {
|
||||
if (value === null || value === undefined || value === "") {
|
||||
return t("admin.groups.imagePricing.notConfigured");
|
||||
}
|
||||
const price = Number(value);
|
||||
if (!Number.isFinite(price) || price < 0) {
|
||||
return t("admin.groups.imagePricing.notConfigured");
|
||||
}
|
||||
return `$${price.toFixed(6).replace(/0+$/, "").replace(/\.$/, "")}`;
|
||||
};
|
||||
|
||||
const buildImageFinalPricePreview = (form: ImagePricingFormState) => {
|
||||
const multiplier = form.image_rate_independent
|
||||
? normalizePreviewNumber(form.image_rate_multiplier, 1)
|
||||
: normalizePreviewNumber(form.rate_multiplier, 1);
|
||||
return imagePricingTiers.map((tier) => {
|
||||
const basePrice = normalizePreviewNumber(form[tier.key]);
|
||||
return {
|
||||
label: tier.label,
|
||||
value: basePrice > 0
|
||||
? formatImagePricePreview(basePrice * multiplier)
|
||||
: t("admin.groups.imagePricing.notConfigured"),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const createImageFinalPricePreview = computed(() =>
|
||||
buildImageFinalPricePreview(createForm),
|
||||
);
|
||||
const editImageFinalPricePreview = computed(() =>
|
||||
buildImageFinalPricePreview(editForm),
|
||||
);
|
||||
|
||||
// 根据分组类型返回不同的删除确认消息
|
||||
const deleteConfirmMessage = computed(() => {
|
||||
if (!deletingGroup.value) {
|
||||
@@ -3479,6 +3641,9 @@ const closeCreateModal = () => {
|
||||
createForm.daily_limit_usd = null;
|
||||
createForm.weekly_limit_usd = null;
|
||||
createForm.monthly_limit_usd = null;
|
||||
createForm.allow_image_generation = false;
|
||||
createForm.image_rate_independent = false;
|
||||
createForm.image_rate_multiplier = 1;
|
||||
createForm.image_price_1k = null;
|
||||
createForm.image_price_2k = null;
|
||||
createForm.image_price_4k = null;
|
||||
@@ -3513,6 +3678,16 @@ const normalizeOptionalLimit = (
|
||||
return Number.isFinite(value) && value > 0 ? value : null;
|
||||
};
|
||||
|
||||
const normalizeImageRateMultiplier = (
|
||||
value: number | string | null | undefined,
|
||||
): number => {
|
||||
if (value === null || value === undefined || value === "") {
|
||||
return 1;
|
||||
}
|
||||
const parsed = Number(value);
|
||||
return Number.isFinite(parsed) && parsed >= 0 ? parsed : 1;
|
||||
};
|
||||
|
||||
const handleCreateGroup = async () => {
|
||||
if (!createForm.name.trim()) {
|
||||
appStore.showError(t("admin.groups.nameRequired"));
|
||||
@@ -3551,6 +3726,9 @@ const handleCreateGroup = async () => {
|
||||
requestData.daily_limit_usd = emptyToNull(requestData.daily_limit_usd);
|
||||
requestData.weekly_limit_usd = emptyToNull(requestData.weekly_limit_usd);
|
||||
requestData.monthly_limit_usd = emptyToNull(requestData.monthly_limit_usd);
|
||||
requestData.image_rate_multiplier = normalizeImageRateMultiplier(
|
||||
requestData.image_rate_multiplier,
|
||||
);
|
||||
await adminAPI.groups.create(requestData);
|
||||
appStore.showSuccess(t("admin.groups.groupCreated"));
|
||||
closeCreateModal();
|
||||
@@ -3582,6 +3760,9 @@ const handleEdit = async (group: AdminGroup) => {
|
||||
editForm.daily_limit_usd = group.daily_limit_usd;
|
||||
editForm.weekly_limit_usd = group.weekly_limit_usd;
|
||||
editForm.monthly_limit_usd = group.monthly_limit_usd;
|
||||
editForm.allow_image_generation = group.allow_image_generation ?? false;
|
||||
editForm.image_rate_independent = group.image_rate_independent ?? false;
|
||||
editForm.image_rate_multiplier = group.image_rate_multiplier ?? 1;
|
||||
editForm.image_price_1k = group.image_price_1k;
|
||||
editForm.image_price_2k = group.image_price_2k;
|
||||
editForm.image_price_4k = group.image_price_4k;
|
||||
@@ -3676,6 +3857,9 @@ const handleUpdateGroup = async () => {
|
||||
payload.daily_limit_usd = emptyToNull(payload.daily_limit_usd);
|
||||
payload.weekly_limit_usd = emptyToNull(payload.weekly_limit_usd);
|
||||
payload.monthly_limit_usd = emptyToNull(payload.monthly_limit_usd);
|
||||
payload.image_rate_multiplier = normalizeImageRateMultiplier(
|
||||
payload.image_rate_multiplier,
|
||||
);
|
||||
await adminAPI.groups.update(editingGroup.value.id, payload);
|
||||
appStore.showSuccess(t("admin.groups.groupUpdated"));
|
||||
closeEditModal();
|
||||
|
||||
Reference in New Issue
Block a user