mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-09-24 16:05:44 +08:00
feat: 添加账号时支持同步上游模型
新增 POST /admin/accounts/models/sync-upstream-preview 端点, 允许在创建账号流程中直接使用凭证同步上游支持的模型, 无需先保存账号获取 ID。 - 后端: 新增 SyncUpstreamModelsPreview handler - 前端: 新增 syncUpstreamModelsPreview API 函数 - 前端: ModelWhitelistSelector 支持 syncCredentials prop - 前端: CreateAccountModal 传递凭证给 ModelWhitelistSelector
This commit is contained in:
@@ -487,6 +487,23 @@ export async function syncUpstreamModels(id: number): Promise<SyncUpstreamModels
|
||||
return data
|
||||
}
|
||||
|
||||
export interface SyncUpstreamPreviewParams {
|
||||
platform: string
|
||||
type: string
|
||||
base_url?: string
|
||||
api_key: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Preview upstream models without a saved account (create-flow)
|
||||
* @param params - Connection credentials
|
||||
* @returns List of model IDs returned by the upstream
|
||||
*/
|
||||
export async function syncUpstreamModelsPreview(params: SyncUpstreamPreviewParams): Promise<SyncUpstreamModelsResult> {
|
||||
const { data } = await apiClient.post<SyncUpstreamModelsResult>('/admin/accounts/models/sync-upstream-preview', params)
|
||||
return data
|
||||
}
|
||||
|
||||
export interface CRSPreviewAccount {
|
||||
crs_account_id: string
|
||||
kind: string
|
||||
@@ -703,6 +720,7 @@ export const accountsAPI = {
|
||||
setSchedulable,
|
||||
getAvailableModels,
|
||||
syncUpstreamModels,
|
||||
syncUpstreamModelsPreview,
|
||||
generateAuthUrl,
|
||||
exchangeCode,
|
||||
refreshOpenAIToken,
|
||||
|
||||
@@ -1124,7 +1124,7 @@
|
||||
|
||||
<!-- Whitelist Mode -->
|
||||
<div v-if="modelRestrictionMode === 'whitelist'">
|
||||
<ModelWhitelistSelector v-model="allowedModels" :platform="form.platform" />
|
||||
<ModelWhitelistSelector v-model="allowedModels" :platform="form.platform" :sync-credentials="syncPreviewCredentials" />
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.accounts.selectedModels', { count: allowedModels.length }) }}
|
||||
<span v-if="allowedModels.length === 0">{{
|
||||
@@ -1562,7 +1562,7 @@
|
||||
|
||||
<!-- Whitelist Mode -->
|
||||
<div v-if="modelRestrictionMode === 'whitelist'">
|
||||
<ModelWhitelistSelector v-model="allowedModels" platform="anthropic" />
|
||||
<ModelWhitelistSelector v-model="allowedModels" platform="anthropic" :sync-credentials="syncPreviewCredentials" />
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.accounts.selectedModels', { count: allowedModels.length }) }}
|
||||
<span v-if="allowedModels.length === 0">{{ t('admin.accounts.supportsAllModels') }}</span>
|
||||
@@ -1813,7 +1813,7 @@
|
||||
|
||||
<!-- Whitelist Mode -->
|
||||
<div v-if="modelRestrictionMode === 'whitelist'">
|
||||
<ModelWhitelistSelector v-model="allowedModels" :platform="form.platform" />
|
||||
<ModelWhitelistSelector v-model="allowedModels" :platform="form.platform" :sync-credentials="syncPreviewCredentials" />
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.accounts.selectedModels', { count: allowedModels.length }) }}
|
||||
<span v-if="allowedModels.length === 0">{{
|
||||
@@ -3361,6 +3361,17 @@ const accountCategory = ref<'oauth-based' | 'apikey' | 'bedrock' | 'service_acco
|
||||
const addMethod = ref<AddMethod>('oauth') // For oauth-based: 'oauth' or 'setup-token'
|
||||
const apiKeyBaseUrl = ref('https://api.anthropic.com')
|
||||
const apiKeyValue = ref('')
|
||||
|
||||
const syncPreviewCredentials = computed(() => {
|
||||
if (!apiKeyValue.value) return undefined
|
||||
return {
|
||||
platform: form.platform,
|
||||
type: form.type,
|
||||
base_url: apiKeyBaseUrl.value || undefined,
|
||||
api_key: apiKeyValue.value
|
||||
}
|
||||
})
|
||||
|
||||
const editQuotaLimit = ref<number | null>(null)
|
||||
const editQuotaDailyLimit = ref<number | null>(null)
|
||||
const editQuotaWeeklyLimit = ref<number | null>(null)
|
||||
|
||||
@@ -133,6 +133,7 @@ import { ref, computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { accountsAPI } from '@/api/admin/accounts'
|
||||
import type { SyncUpstreamPreviewParams } from '@/api/admin/accounts'
|
||||
import ModelIcon from '@/components/common/ModelIcon.vue'
|
||||
import Icon from '@/components/icons/Icon.vue'
|
||||
import { allModels, getModelsByPlatform } from '@/composables/useModelWhitelist'
|
||||
@@ -144,6 +145,12 @@ const props = defineProps<{
|
||||
platform?: string
|
||||
platforms?: string[]
|
||||
accountId?: number
|
||||
syncCredentials?: {
|
||||
platform: string
|
||||
type: string
|
||||
base_url?: string
|
||||
api_key: string
|
||||
}
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -176,9 +183,14 @@ const normalizedPlatforms = computed(() => {
|
||||
|
||||
const upstreamSyncPlatforms = new Set(['anthropic', 'openai', 'gemini', 'antigravity'])
|
||||
const canSyncUpstream = computed(() => {
|
||||
if (!props.accountId) return false
|
||||
if (normalizedPlatforms.value.length === 0) return true
|
||||
return normalizedPlatforms.value.some(platform => upstreamSyncPlatforms.has(platform.toLowerCase()))
|
||||
if (props.accountId) {
|
||||
if (normalizedPlatforms.value.length === 0) return true
|
||||
return normalizedPlatforms.value.some(platform => upstreamSyncPlatforms.has(platform.toLowerCase()))
|
||||
}
|
||||
if (props.syncCredentials) {
|
||||
return upstreamSyncPlatforms.has(props.syncCredentials.platform.toLowerCase())
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
const availableOptions = computed(() => {
|
||||
@@ -249,11 +261,20 @@ const fillRelated = () => {
|
||||
}
|
||||
|
||||
const syncUpstreamModels = async () => {
|
||||
if (!props.accountId || isSyncingUpstream.value) return
|
||||
if (isSyncingUpstream.value) return
|
||||
if (!props.accountId && !props.syncCredentials) return
|
||||
|
||||
isSyncingUpstream.value = true
|
||||
try {
|
||||
const result = await accountsAPI.syncUpstreamModels(props.accountId)
|
||||
let result
|
||||
if (props.accountId) {
|
||||
result = await accountsAPI.syncUpstreamModels(props.accountId)
|
||||
} else if (props.syncCredentials) {
|
||||
result = await accountsAPI.syncUpstreamModelsPreview(props.syncCredentials as SyncUpstreamPreviewParams)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
const upstreamModels = result.models.map(model => model.trim()).filter(Boolean)
|
||||
if (upstreamModels.length === 0) {
|
||||
appStore.showInfo(t('admin.accounts.syncUpstreamModelsEmpty'))
|
||||
|
||||
Reference in New Issue
Block a user