mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-29 08:11:32 +00:00
Merge pull request #2582 from wucm667/feat/channel-pricing-sync-models
feat(channels): 模型定价支持一键同步最新模型
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -2448,7 +2448,12 @@ export default {
|
||||
ruleAccountsHint: '留空表示匹配所有账号',
|
||||
ruleModelPricing: '模型定价',
|
||||
noGroupsInChannel: '上方平台标签页中未选择分组',
|
||||
unnamed: '未命名'
|
||||
unnamed: '未命名',
|
||||
syncLatestModels: '同步最新模型',
|
||||
syncingModels: '同步中...',
|
||||
syncModelsSuccess: '已同步 {count} 个新模型',
|
||||
syncModelsAlreadyUpToDate: '模型列表已是最新',
|
||||
syncModelsError: '同步模型失败'
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user