Merge pull request #2582 from wucm667/feat/channel-pricing-sync-models

feat(channels): 模型定价支持一键同步最新模型
This commit is contained in:
Wesley Liddick
2026-05-20 08:43:10 +08:00
committed by GitHub
10 changed files with 252 additions and 12 deletions
+15 -1
View File
@@ -164,5 +164,19 @@ export async function getModelDefaultPricing(model: string): Promise<ModelDefaul
return data
}
const channelsAPI = { list, getById, create, update, remove, getModelDefaultPricing }
export interface SyncPricingModelsResult {
models: string[]
}
/**
* Fetch the latest model names from the LiteLLM pricing catalog for the given platform
*/
export async function syncPricingModels(platform: string): Promise<SyncPricingModelsResult> {
const { data } = await apiClient.get<SyncPricingModelsResult>('/admin/channels/pricing/sync-models', {
params: { platform }
})
return data
}
const channelsAPI = { list, getById, create, update, remove, getModelDefaultPricing, syncPricingModels }
export default channelsAPI
+9 -4
View File
@@ -2370,10 +2370,15 @@ export default {
searchAccountPlaceholder: 'Search accounts...',
ruleAccountsHint: 'Leave empty to match all accounts',
ruleModelPricing: 'Model Pricing',
noGroupsInChannel: 'No groups selected in platform tabs above',
unnamed: 'Unnamed'
}
},
noGroupsInChannel: 'No groups selected in platform tabs above',
unnamed: 'Unnamed',
syncLatestModels: 'Sync Latest Models',
syncingModels: 'Syncing...',
syncModelsSuccess: 'Synced {count} new model(s)',
syncModelsAlreadyUpToDate: 'Models already up to date',
syncModelsError: 'Failed to sync models'
}
},
riskControl: {
title: 'Risk Control',
+6 -1
View File
@@ -2448,7 +2448,12 @@ export default {
ruleAccountsHint: '留空表示匹配所有账号',
ruleModelPricing: '模型定价',
noGroupsInChannel: '上方平台标签页中未选择分组',
unnamed: '未命名'
unnamed: '未命名',
syncLatestModels: '同步最新模型',
syncingModels: '同步中...',
syncModelsSuccess: '已同步 {count} 个新模型',
syncModelsAlreadyUpToDate: '模型列表已是最新',
syncModelsError: '同步模型失败'
}
},
+51 -3
View File
@@ -406,9 +406,19 @@
<div>
<div class="mb-1 flex items-center justify-between">
<label class="input-label text-xs mb-0">{{ t('admin.channels.form.modelPricing', 'Model Pricing') }}</label>
<button type="button" @click="addPricingEntry(sIdx)" class="text-xs text-primary-600 hover:text-primary-700">
+ {{ t('common.add', 'Add') }}
</button>
<div class="flex items-center gap-2">
<button
type="button"
@click="syncLatestModels(sIdx)"
:disabled="syncingPlatform === section.platform"
class="text-xs text-gray-500 hover:text-primary-600 disabled:opacity-50"
>
{{ syncingPlatform === section.platform ? t('admin.channels.form.syncingModels') : t('admin.channels.form.syncLatestModels') }}
</button>
<button type="button" @click="addPricingEntry(sIdx)" class="text-xs text-primary-600 hover:text-primary-700">
+ {{ t('common.add', 'Add') }}
</button>
</div>
</div>
<div
v-if="section.model_pricing.length === 0"
@@ -834,6 +844,44 @@ function addPricingEntry(sectionIdx: number) {
})
}
const syncingPlatform = ref<string | null>(null)
async function syncLatestModels(sectionIdx: number) {
const platform = form.platforms[sectionIdx].platform
if (syncingPlatform.value) return
syncingPlatform.value = platform
try {
const result = await adminAPI.channels.syncPricingModels(platform)
// Collect all model names already present in this platform's pricing entries
const existingModels = new Set<string>()
for (const entry of form.platforms[sectionIdx].model_pricing) {
for (const m of entry.models) existingModels.add(m)
}
const newModels = result.models.filter(m => !existingModels.has(m))
if (newModels.length === 0) {
appStore.showSuccess(t('admin.channels.form.syncModelsAlreadyUpToDate'))
return
}
// Add new models as a single new pricing entry (user fills in prices)
form.platforms[sectionIdx].model_pricing.push({
models: newModels,
billing_mode: 'token',
input_price: null,
output_price: null,
cache_write_price: null,
cache_read_price: null,
image_output_price: null,
per_request_price: null,
intervals: []
})
appStore.showSuccess(t('admin.channels.form.syncModelsSuccess', { count: newModels.length }))
} catch (error) {
appStore.showError(extractApiErrorMessage(error, t('admin.channels.form.syncModelsError')))
} finally {
syncingPlatform.value = null
}
}
function updatePricingEntry(sectionIdx: number, idx: number, updated: PricingFormEntry) {
form.platforms[sectionIdx].model_pricing.splice(idx, 1, updated)
}