release: prepare v0.1.134
This commit is contained in:
@@ -446,6 +446,26 @@ export async function getAvailableModels(id: number): Promise<ClaudeModel[]> {
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Kiro models from the upstream Kiro ListAvailableModels API using the account proxy.
|
||||
* @param id - Account ID
|
||||
* @returns List of upstream Kiro models
|
||||
*/
|
||||
export async function getKiroUpstreamModels(id: number): Promise<ClaudeModel[]> {
|
||||
const { data } = await apiClient.get<ClaudeModel[]>(`/admin/accounts/${id}/kiro/upstream-models`)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Get OpenAI models from the upstream /v1/models API using the account proxy.
|
||||
* @param id - Account ID
|
||||
* @returns List of upstream OpenAI models
|
||||
*/
|
||||
export async function getOpenAIUpstreamModels(id: number): Promise<ClaudeModel[]> {
|
||||
const { data } = await apiClient.get<ClaudeModel[]>(`/admin/accounts/${id}/openai/upstream-models`)
|
||||
return data
|
||||
}
|
||||
|
||||
export interface CRSPreviewAccount {
|
||||
crs_account_id: string
|
||||
kind: string
|
||||
@@ -667,6 +687,8 @@ export const accountsAPI = {
|
||||
resetTempUnschedulable,
|
||||
setSchedulable,
|
||||
getAvailableModels,
|
||||
getOpenAIUpstreamModels,
|
||||
getKiroUpstreamModels,
|
||||
generateAuthUrl,
|
||||
exchangeCode,
|
||||
refreshOpenAIToken,
|
||||
|
||||
@@ -75,6 +75,7 @@ export async function refreshToken(payload: {
|
||||
export async function importToken(payload: {
|
||||
token_json: string
|
||||
device_registration_json?: string
|
||||
proxy_id?: number
|
||||
}): Promise<KiroTokenInfo> {
|
||||
const { data } = await apiClient.post<KiroTokenInfo>('/admin/kiro/oauth/import-token', payload)
|
||||
return data
|
||||
|
||||
@@ -3217,7 +3217,7 @@
|
||||
<div v-if="isKiroImportMode" class="space-y-4 rounded-lg border border-amber-200 bg-amber-50 p-4 dark:border-amber-700 dark:bg-amber-900/20">
|
||||
<div>
|
||||
<label class="input-label">{{ t('admin.accounts.oauth.kiro.tokenJsonLabel') }}</label>
|
||||
<textarea v-model="kiroTokenJson" rows="8" class="input font-mono text-xs" placeholder='{"accessToken":"...","refreshToken":"..."}'></textarea>
|
||||
<textarea v-model="kiroTokenJson" rows="8" class="input font-mono text-xs" placeholder='{"refreshToken":"...","provider":"Google"}'></textarea>
|
||||
<p class="input-hint">{{ t('admin.accounts.oauth.kiro.tokenJsonHint') }}</p>
|
||||
</div>
|
||||
<div>
|
||||
@@ -5811,7 +5811,8 @@ const handleKiroImport = async () => {
|
||||
|
||||
const tokenInfo = await kiroOAuth.importToken(
|
||||
kiroTokenJson.value,
|
||||
kiroDeviceRegistrationJson.value || undefined
|
||||
kiroDeviceRegistrationJson.value || undefined,
|
||||
form.proxy_id
|
||||
)
|
||||
if (!tokenInfo) return
|
||||
|
||||
|
||||
@@ -74,7 +74,18 @@
|
||||
</div>
|
||||
|
||||
<div v-if="account.platform === 'kiro'" class="border-t border-gray-200 pt-4 dark:border-dark-600">
|
||||
<label class="input-label">{{ t('admin.accounts.modelRestriction') }}</label>
|
||||
<div class="mb-2 flex flex-wrap items-center justify-between gap-2">
|
||||
<label class="input-label mb-0">{{ t('admin.accounts.modelRestriction') }}</label>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-secondary btn-sm"
|
||||
:disabled="kiroModelListLoading"
|
||||
@click="handleFetchKiroModelMappings"
|
||||
>
|
||||
<Icon name="refresh" size="sm" :class="kiroModelListLoading ? 'animate-spin' : ''" />
|
||||
{{ kiroModelListLoading ? t('common.loading') : t('admin.accounts.fetchModelList') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="mb-3 rounded-lg bg-purple-50 p-3 dark:bg-purple-900/20">
|
||||
<p class="text-xs text-purple-700 dark:text-purple-400">
|
||||
@@ -160,7 +171,19 @@
|
||||
|
||||
<!-- Model Restriction Section (不适用于 Antigravity / Kiro) -->
|
||||
<div v-else-if="account.platform !== 'antigravity'" class="border-t border-gray-200 pt-4 dark:border-dark-600">
|
||||
<label class="input-label">{{ t('admin.accounts.modelRestriction') }}</label>
|
||||
<div class="mb-2 flex flex-wrap items-center justify-between gap-2">
|
||||
<label class="input-label mb-0">{{ t('admin.accounts.modelRestriction') }}</label>
|
||||
<button
|
||||
v-if="account.platform === 'openai'"
|
||||
type="button"
|
||||
class="btn btn-secondary btn-sm"
|
||||
:disabled="openAIModelListLoading || isOpenAIModelRestrictionDisabled"
|
||||
@click="handleFetchOpenAIModels"
|
||||
>
|
||||
<Icon name="refresh" size="sm" :class="openAIModelListLoading ? 'animate-spin' : ''" />
|
||||
{{ openAIModelListLoading ? t('common.loading') : t('admin.accounts.fetchUpstreamModelList') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="isOpenAIModelRestrictionDisabled"
|
||||
@@ -501,7 +524,29 @@
|
||||
v-if="(account.platform === 'openai' || account.platform === 'kiro') && account.type === 'oauth'"
|
||||
class="border-t border-gray-200 pt-4 dark:border-dark-600"
|
||||
>
|
||||
<label class="input-label">{{ t('admin.accounts.modelRestriction') }}</label>
|
||||
<div class="mb-2 flex flex-wrap items-center justify-between gap-2">
|
||||
<label class="input-label mb-0">{{ t('admin.accounts.modelRestriction') }}</label>
|
||||
<button
|
||||
v-if="account.platform === 'openai'"
|
||||
type="button"
|
||||
class="btn btn-secondary btn-sm"
|
||||
:disabled="openAIModelListLoading || isOpenAIModelRestrictionDisabled"
|
||||
@click="handleFetchOpenAIModels"
|
||||
>
|
||||
<Icon name="refresh" size="sm" :class="openAIModelListLoading ? 'animate-spin' : ''" />
|
||||
{{ openAIModelListLoading ? t('common.loading') : t('admin.accounts.fetchUpstreamModelList') }}
|
||||
</button>
|
||||
<button
|
||||
v-if="account.platform === 'kiro'"
|
||||
type="button"
|
||||
class="btn btn-secondary btn-sm"
|
||||
:disabled="kiroModelListLoading"
|
||||
@click="handleFetchKiroModelMappings"
|
||||
>
|
||||
<Icon name="refresh" size="sm" :class="kiroModelListLoading ? 'animate-spin' : ''" />
|
||||
{{ kiroModelListLoading ? t('common.loading') : t('admin.accounts.fetchModelList') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="isOpenAIModelRestrictionDisabled"
|
||||
@@ -2442,6 +2487,8 @@ const modelMappings = ref<ModelMapping[]>([])
|
||||
const openAICompactModelMappings = ref<ModelMapping[]>([])
|
||||
const modelRestrictionMode = ref<'whitelist' | 'mapping'>('whitelist')
|
||||
const allowedModels = ref<string[]>([])
|
||||
const openAIModelListLoading = ref(false)
|
||||
const kiroModelListLoading = ref(false)
|
||||
const DEFAULT_POOL_MODE_RETRY_COUNT = 3
|
||||
const MAX_POOL_MODE_RETRY_COUNT = 10
|
||||
const poolModeEnabled = ref(false)
|
||||
@@ -2478,6 +2525,75 @@ const loadDefaultKiroModelMappings = () => {
|
||||
})
|
||||
}
|
||||
|
||||
const extractModelID = (model: unknown): string => {
|
||||
if (typeof model === 'string') return model.trim()
|
||||
if (!model || typeof model !== 'object') return ''
|
||||
|
||||
const record = model as Record<string, unknown>
|
||||
const rawID = record.modelId ?? record.model_id ?? record.id ?? record.name ?? record.model
|
||||
return typeof rawID === 'string' ? rawID.trim() : ''
|
||||
}
|
||||
|
||||
const handleFetchKiroModelMappings = async () => {
|
||||
if (!props.account || props.account.platform !== 'kiro') return
|
||||
|
||||
kiroModelListLoading.value = true
|
||||
try {
|
||||
const models = await adminAPI.accounts.getKiroUpstreamModels(props.account.id)
|
||||
const modelIDs = Array.from(
|
||||
new Set(
|
||||
models
|
||||
.map(extractModelID)
|
||||
.filter((id) => id.length > 0)
|
||||
)
|
||||
)
|
||||
|
||||
if (modelIDs.length === 0) {
|
||||
appStore.showError(t('admin.accounts.noModelsFetched'))
|
||||
return
|
||||
}
|
||||
|
||||
modelRestrictionMode.value = 'mapping'
|
||||
modelMappings.value = modelIDs.map((model) => ({ from: model, to: model }))
|
||||
allowedModels.value = []
|
||||
appStore.showSuccess(t('admin.accounts.modelListApplied', { count: modelIDs.length }))
|
||||
} catch (error: any) {
|
||||
appStore.showError(error.response?.data?.message || error.response?.data?.detail || t('admin.accounts.failedToFetchModelList'))
|
||||
} finally {
|
||||
kiroModelListLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleFetchOpenAIModels = async () => {
|
||||
if (!props.account || props.account.platform !== 'openai') return
|
||||
|
||||
openAIModelListLoading.value = true
|
||||
try {
|
||||
const models = await adminAPI.accounts.getOpenAIUpstreamModels(props.account.id)
|
||||
const modelIDs = Array.from(
|
||||
new Set(
|
||||
models
|
||||
.map(extractModelID)
|
||||
.filter((id) => id.length > 0)
|
||||
)
|
||||
)
|
||||
|
||||
if (modelIDs.length === 0) {
|
||||
appStore.showError(t('admin.accounts.noModelsFetched'))
|
||||
return
|
||||
}
|
||||
|
||||
modelRestrictionMode.value = 'whitelist'
|
||||
allowedModels.value = modelIDs
|
||||
modelMappings.value = []
|
||||
appStore.showSuccess(t('admin.accounts.modelListApplied', { count: modelIDs.length }))
|
||||
} catch (error: any) {
|
||||
appStore.showError(error.response?.data?.message || error.response?.data?.detail || t('admin.accounts.failedToFetchModelList'))
|
||||
} finally {
|
||||
openAIModelListLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const showMixedChannelWarning = ref(false)
|
||||
const mixedChannelWarningDetails = ref<{ groupName: string; currentPlatform: string; otherPlatform: string } | null>(
|
||||
null
|
||||
|
||||
@@ -775,7 +775,8 @@ const handleKiroImport = async () => {
|
||||
|
||||
const tokenInfo = await kiroOAuth.importToken(
|
||||
kiroTokenJson.value,
|
||||
kiroDeviceRegistrationJson.value || undefined
|
||||
kiroDeviceRegistrationJson.value || undefined,
|
||||
props.account.proxy_id
|
||||
)
|
||||
if (!tokenInfo) return
|
||||
|
||||
|
||||
@@ -141,14 +141,16 @@ export function useKiroOAuth() {
|
||||
|
||||
const importToken = async (
|
||||
tokenJSON: string,
|
||||
deviceRegistrationJSON?: string
|
||||
deviceRegistrationJSON?: string,
|
||||
proxyId?: number | null
|
||||
): Promise<KiroTokenInfo | null> => {
|
||||
loading.value = true
|
||||
error.value = ''
|
||||
try {
|
||||
return await adminAPI.kiro.importToken({
|
||||
token_json: tokenJSON,
|
||||
device_registration_json: deviceRegistrationJSON
|
||||
device_registration_json: deviceRegistrationJSON,
|
||||
proxy_id: proxyId || undefined
|
||||
})
|
||||
} catch (err: any) {
|
||||
error.value = err.response?.data?.detail || t('admin.accounts.oauth.authFailed')
|
||||
|
||||
@@ -1028,7 +1028,7 @@ export default {
|
||||
columns: {
|
||||
email: 'Email',
|
||||
username: 'Username',
|
||||
rebate: 'Rebate',
|
||||
rebate: 'Total Earnings',
|
||||
joinedAt: 'Joined At'
|
||||
}
|
||||
},
|
||||
@@ -3234,6 +3234,11 @@ export default {
|
||||
requestModel: 'Request model',
|
||||
actualModel: 'Actual model',
|
||||
addMapping: 'Add Mapping',
|
||||
fetchModelList: 'Fetch Models',
|
||||
fetchUpstreamModelList: 'Fetch Upstream Models',
|
||||
modelListApplied: 'Replaced with {count} model(s)',
|
||||
noModelsFetched: 'No available models were returned',
|
||||
failedToFetchModelList: 'Failed to fetch model list',
|
||||
mappingExists: 'Mapping for {model} already exists',
|
||||
wildcardOnlyAtEnd: 'Wildcard * can only be at the end',
|
||||
targetNoWildcard: 'Target model cannot contain wildcard *',
|
||||
@@ -3643,7 +3648,7 @@ export default {
|
||||
regionLabel: 'Region',
|
||||
regionPlaceholder: 'us-east-1',
|
||||
tokenJsonLabel: 'Kiro Token JSON',
|
||||
tokenJsonHint: 'Sign in through Kiro IDE first, then paste the contents of `~/.aws/sso/cache/kiro-auth-token.json` here.',
|
||||
tokenJsonHint: 'Supports full Kiro token JSON, or a refresh-token-only payload with refreshToken + provider. The server will refresh it into usable credentials.',
|
||||
deviceRegistrationLabel: 'Device Registration JSON',
|
||||
deviceRegistrationHint: 'Optional. Only needed when the token file does not include full client details and only has `clientIdHash`.',
|
||||
importAndUpdate: 'Import and Update'
|
||||
|
||||
@@ -1032,7 +1032,7 @@ export default {
|
||||
columns: {
|
||||
email: '邮箱',
|
||||
username: '用户名',
|
||||
rebate: '返利明细',
|
||||
rebate: '累计收益',
|
||||
joinedAt: '注册时间'
|
||||
}
|
||||
},
|
||||
@@ -3391,6 +3391,11 @@ export default {
|
||||
requestModel: '请求模型',
|
||||
actualModel: '实际模型',
|
||||
addMapping: '添加映射',
|
||||
fetchModelList: '获取模型列表',
|
||||
fetchUpstreamModelList: '从上游获取模型',
|
||||
modelListApplied: '已覆盖为 {count} 个模型',
|
||||
noModelsFetched: '未获取到可用模型',
|
||||
failedToFetchModelList: '获取模型列表失败',
|
||||
mappingExists: '模型 {model} 的映射已存在',
|
||||
wildcardOnlyAtEnd: '通配符 * 只能放在末尾',
|
||||
targetNoWildcard: '目标模型不能包含通配符 *',
|
||||
@@ -3787,7 +3792,7 @@ export default {
|
||||
regionLabel: 'Region',
|
||||
regionPlaceholder: 'us-east-1',
|
||||
tokenJsonLabel: 'Kiro Token JSON',
|
||||
tokenJsonHint: '先在 Kiro IDE 完成登录,再粘贴 `~/.aws/sso/cache/kiro-auth-token.json` 的内容。',
|
||||
tokenJsonHint: '支持完整 Kiro token JSON,也支持仅粘贴 refreshToken + provider 格式,系统会自动刷新为可用凭据。',
|
||||
deviceRegistrationLabel: 'Device Registration JSON',
|
||||
deviceRegistrationHint: '可选。只有 token 文件里缺少完整客户端信息、只剩 `clientIdHash` 时才需要补充。',
|
||||
importAndUpdate: '导入并更新'
|
||||
|
||||
Reference in New Issue
Block a user