mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-04 16:37:46 +08:00
feat(billing): add image output pricing and usage tracking
This commit is contained in:
@@ -234,6 +234,9 @@
|
||||
<template v-if="perRequestCost > 0">
|
||||
+ 按次费用 <span class="font-medium">${{ perRequestCost.toFixed(6) }}</span>
|
||||
</template>
|
||||
<template v-if="imageOutputCostTotal > 0">
|
||||
+ 图片输出费用 <span class="font-medium">${{ imageOutputCostTotal.toFixed(6) }}</span>
|
||||
</template>
|
||||
<template v-if="videoCostTotal > 0">
|
||||
+ {{ detail.video_billing?.task_type === 'image' ? '图像' : detail.video_billing?.task_type === 'audio' ? '音频' : '视频' }}费用 <span class="font-medium">${{ videoCostTotal.toFixed(6) }}</span>
|
||||
</template>
|
||||
@@ -385,7 +388,56 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========== 4. 视频/图像/音频计费(独立隔离,与Token计费风格一致) ========== -->
|
||||
<!-- ========== 4. 图片输出计费 ========== -->
|
||||
<div
|
||||
v-if="hasImageBillingDetail"
|
||||
class="rounded-lg p-3 space-y-2 bg-primary/5 border border-primary/30 mb-3"
|
||||
>
|
||||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-1 sm:gap-2 text-xs">
|
||||
<div class="flex items-center gap-2 flex-wrap">
|
||||
<span class="font-medium text-primary">图片输出</span>
|
||||
<Badge
|
||||
variant="outline"
|
||||
class="text-[10px] px-1.5 py-0 h-4"
|
||||
>
|
||||
{{ imageOutputBillingLabel }}
|
||||
</Badge>
|
||||
<span
|
||||
v-if="imageOutputMatrixEnabled && imagePriceKey"
|
||||
class="text-muted-foreground font-mono"
|
||||
>{{ imagePriceKey }}</span>
|
||||
<span
|
||||
v-else-if="imageOutputSize || imageOutputQuality"
|
||||
class="text-muted-foreground font-mono"
|
||||
>{{ [imageOutputSize, imageOutputQuality].filter(Boolean).join(' / ') }}</span>
|
||||
</div>
|
||||
<div class="text-muted-foreground flex items-center gap-2 flex-wrap">
|
||||
<span
|
||||
v-if="imageOutputPricePerImage !== null"
|
||||
class="font-mono"
|
||||
>{{ formatNumber(imageOutputCount) }} 张 × ${{ imageOutputPricePerImage.toFixed(6) }}/张 = ${{ imageOutputCostTotal.toFixed(6) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center">
|
||||
<div class="flex items-center flex-1">
|
||||
<span class="text-xs text-muted-foreground w-[56px]">数量</span>
|
||||
<span class="text-sm font-semibold font-mono flex-1 text-center">{{ formatNumber(imageOutputCount) }}</span>
|
||||
<span class="text-xs font-mono">${{ imageOutputCostTotal.toFixed(6) }}</span>
|
||||
</div>
|
||||
<Separator
|
||||
orientation="vertical"
|
||||
class="h-4 mx-4"
|
||||
/>
|
||||
<div class="flex items-center flex-1">
|
||||
<span class="text-xs text-muted-foreground w-[56px]">格式</span>
|
||||
<span class="text-sm font-semibold font-mono flex-1 text-center">{{ imageOutputFormat || '-' }}</span>
|
||||
<span class="text-xs font-mono text-muted-foreground">{{ imageOutputBillingLabel }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========== 5. 视频/图像/音频计费(独立隔离,与Token计费风格一致) ========== -->
|
||||
<div
|
||||
v-if="detail.video_billing"
|
||||
class="rounded-lg p-3 space-y-2 bg-primary/5 border border-primary/30"
|
||||
@@ -901,6 +953,11 @@ function getNestedNumber(record: JsonRecord | null, ...path: string[]): number |
|
||||
return toNumber(getNestedValue(record, ...path))
|
||||
}
|
||||
|
||||
function getNestedString(record: JsonRecord | null, ...path: string[]): string | null {
|
||||
const value = getNestedValue(record, ...path)
|
||||
return typeof value === 'string' && value.trim() ? value.trim() : null
|
||||
}
|
||||
|
||||
function normalizeCacheTtlPricing(value: unknown): CacheTTLPriceEntry[] {
|
||||
if (!Array.isArray(value)) return []
|
||||
return value
|
||||
@@ -1036,6 +1093,10 @@ const billingResolvedVariables = computed<JsonRecord | null>(() =>
|
||||
asRecord(billingSnapshot.value?.resolved_variables),
|
||||
)
|
||||
|
||||
const billingResolvedDimensions = computed<JsonRecord | null>(() =>
|
||||
asRecord(billingSnapshot.value?.resolved_dimensions),
|
||||
)
|
||||
|
||||
const billingCostBreakdown = computed<JsonRecord | null>(() =>
|
||||
asRecord(billingSnapshot.value?.cost_breakdown),
|
||||
)
|
||||
@@ -1377,6 +1438,74 @@ const effectiveRequestCost = computed(() => {
|
||||
return 0
|
||||
})
|
||||
|
||||
const effectiveImageOutputCost = computed(() =>
|
||||
getNestedNumber(billingCostBreakdown.value, 'image_output_cost')
|
||||
?? toNumber(detail.value?.image_output_cost)
|
||||
?? 0,
|
||||
)
|
||||
|
||||
const imageOutputCostTotal = computed(() => effectiveImageOutputCost.value)
|
||||
|
||||
const imageOutputPricePerImage = computed(() =>
|
||||
getNestedNumber(billingResolvedVariables.value, 'image_output_price_per_image'),
|
||||
)
|
||||
|
||||
const imageOutputCount = computed(() =>
|
||||
getNestedNumber(billingResolvedDimensions.value, 'image_count')
|
||||
?? getNestedNumber(traceRequestMetadata.value, 'billing_dimensions', 'image_count')
|
||||
?? getNestedNumber(traceRequestMetadata.value, 'dimensions', 'image_count')
|
||||
?? 0,
|
||||
)
|
||||
|
||||
const imageOutputSize = computed(() =>
|
||||
getNestedString(billingResolvedDimensions.value, 'image_size')
|
||||
?? getNestedString(traceRequestMetadata.value, 'billing_dimensions', 'image_size')
|
||||
?? getNestedString(traceRequestMetadata.value, 'dimensions', 'image_size'),
|
||||
)
|
||||
|
||||
const imageOutputQuality = computed(() =>
|
||||
getNestedString(billingResolvedDimensions.value, 'image_quality')
|
||||
?? getNestedString(traceRequestMetadata.value, 'billing_dimensions', 'image_quality')
|
||||
?? getNestedString(traceRequestMetadata.value, 'dimensions', 'image_quality'),
|
||||
)
|
||||
|
||||
const imageOutputFormat = computed(() =>
|
||||
getNestedString(billingResolvedDimensions.value, 'image_output_format')
|
||||
?? getNestedString(traceRequestMetadata.value, 'billing_dimensions', 'image_output_format')
|
||||
?? getNestedString(traceRequestMetadata.value, 'dimensions', 'image_output_format'),
|
||||
)
|
||||
|
||||
const imagePriceKey = computed(() => {
|
||||
const snapshotKey = getNestedString(billingResolvedDimensions.value, 'image_price_key')
|
||||
if (snapshotKey) return snapshotKey
|
||||
const fallbackKey = [imageOutputSize.value, imageOutputQuality.value].filter(Boolean).join(':')
|
||||
return fallbackKey || null
|
||||
})
|
||||
|
||||
const imageOutputPricingMode = computed(() =>
|
||||
getNestedString(billingResolvedDimensions.value, 'image_output_pricing_mode'),
|
||||
)
|
||||
|
||||
const imageOutputPricingEnabled = computed(() =>
|
||||
getNestedValue(billingResolvedDimensions.value, 'image_output_pricing_enabled') === true
|
||||
|| imageOutputPricingMode.value === 'matrix'
|
||||
|| imageOutputPricingMode.value === 'per_image'
|
||||
|| imageOutputCostTotal.value > 0,
|
||||
)
|
||||
|
||||
const imageOutputMatrixEnabled = computed(() =>
|
||||
getNestedValue(billingResolvedDimensions.value, 'image_output_matrix_enabled') === true
|
||||
|| imageOutputPricingMode.value === 'matrix',
|
||||
)
|
||||
|
||||
const imageOutputBillingLabel = computed(() =>
|
||||
imageOutputMatrixEnabled.value ? '矩阵计费' : '默认计费',
|
||||
)
|
||||
|
||||
const hasImageBillingDetail = computed(() =>
|
||||
imageOutputPricingEnabled.value && (imageOutputCount.value > 0 || imageOutputCostTotal.value > 0),
|
||||
)
|
||||
|
||||
const fallbackCacheTtlPricing = computed<CacheTTLPriceEntry[]>(() => {
|
||||
const tierPricing = normalizeCacheTtlPricing(billingTierInfo.value?.cache_ttl_pricing)
|
||||
if (tierPricing.length > 0) return tierPricing
|
||||
|
||||
Reference in New Issue
Block a user