mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-03-30 02:27:11 +00:00
feat: 从 OpenAI JWT 提取 chatgpt_plan_type 并在前端展示
OAuth 授权和 token 刷新时从 id_token 的 OpenAI auth claim 中 提取 chatgpt_plan_type(plus/team/pro/free),存入 credentials, 账号管理页面 PlatformTypeBadge 显示订阅类型。
This commit is contained in:
@@ -268,6 +268,7 @@ type IDTokenClaims struct {
|
|||||||
type OpenAIAuthClaims struct {
|
type OpenAIAuthClaims struct {
|
||||||
ChatGPTAccountID string `json:"chatgpt_account_id"`
|
ChatGPTAccountID string `json:"chatgpt_account_id"`
|
||||||
ChatGPTUserID string `json:"chatgpt_user_id"`
|
ChatGPTUserID string `json:"chatgpt_user_id"`
|
||||||
|
ChatGPTPlanType string `json:"chatgpt_plan_type"`
|
||||||
UserID string `json:"user_id"`
|
UserID string `json:"user_id"`
|
||||||
Organizations []OrganizationClaim `json:"organizations"`
|
Organizations []OrganizationClaim `json:"organizations"`
|
||||||
}
|
}
|
||||||
@@ -375,6 +376,7 @@ type UserInfo struct {
|
|||||||
Email string
|
Email string
|
||||||
ChatGPTAccountID string
|
ChatGPTAccountID string
|
||||||
ChatGPTUserID string
|
ChatGPTUserID string
|
||||||
|
PlanType string
|
||||||
UserID string
|
UserID string
|
||||||
OrganizationID string
|
OrganizationID string
|
||||||
Organizations []OrganizationClaim
|
Organizations []OrganizationClaim
|
||||||
@@ -389,6 +391,7 @@ func (c *IDTokenClaims) GetUserInfo() *UserInfo {
|
|||||||
if c.OpenAIAuth != nil {
|
if c.OpenAIAuth != nil {
|
||||||
info.ChatGPTAccountID = c.OpenAIAuth.ChatGPTAccountID
|
info.ChatGPTAccountID = c.OpenAIAuth.ChatGPTAccountID
|
||||||
info.ChatGPTUserID = c.OpenAIAuth.ChatGPTUserID
|
info.ChatGPTUserID = c.OpenAIAuth.ChatGPTUserID
|
||||||
|
info.PlanType = c.OpenAIAuth.ChatGPTPlanType
|
||||||
info.UserID = c.OpenAIAuth.UserID
|
info.UserID = c.OpenAIAuth.UserID
|
||||||
info.Organizations = c.OpenAIAuth.Organizations
|
info.Organizations = c.OpenAIAuth.Organizations
|
||||||
|
|
||||||
|
|||||||
@@ -130,6 +130,7 @@ type OpenAITokenInfo struct {
|
|||||||
ChatGPTAccountID string `json:"chatgpt_account_id,omitempty"`
|
ChatGPTAccountID string `json:"chatgpt_account_id,omitempty"`
|
||||||
ChatGPTUserID string `json:"chatgpt_user_id,omitempty"`
|
ChatGPTUserID string `json:"chatgpt_user_id,omitempty"`
|
||||||
OrganizationID string `json:"organization_id,omitempty"`
|
OrganizationID string `json:"organization_id,omitempty"`
|
||||||
|
PlanType string `json:"plan_type,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExchangeCode exchanges authorization code for tokens
|
// ExchangeCode exchanges authorization code for tokens
|
||||||
@@ -202,6 +203,7 @@ func (s *OpenAIOAuthService) ExchangeCode(ctx context.Context, input *OpenAIExch
|
|||||||
tokenInfo.ChatGPTAccountID = userInfo.ChatGPTAccountID
|
tokenInfo.ChatGPTAccountID = userInfo.ChatGPTAccountID
|
||||||
tokenInfo.ChatGPTUserID = userInfo.ChatGPTUserID
|
tokenInfo.ChatGPTUserID = userInfo.ChatGPTUserID
|
||||||
tokenInfo.OrganizationID = userInfo.OrganizationID
|
tokenInfo.OrganizationID = userInfo.OrganizationID
|
||||||
|
tokenInfo.PlanType = userInfo.PlanType
|
||||||
}
|
}
|
||||||
|
|
||||||
return tokenInfo, nil
|
return tokenInfo, nil
|
||||||
@@ -246,6 +248,7 @@ func (s *OpenAIOAuthService) RefreshTokenWithClientID(ctx context.Context, refre
|
|||||||
tokenInfo.ChatGPTAccountID = userInfo.ChatGPTAccountID
|
tokenInfo.ChatGPTAccountID = userInfo.ChatGPTAccountID
|
||||||
tokenInfo.ChatGPTUserID = userInfo.ChatGPTUserID
|
tokenInfo.ChatGPTUserID = userInfo.ChatGPTUserID
|
||||||
tokenInfo.OrganizationID = userInfo.OrganizationID
|
tokenInfo.OrganizationID = userInfo.OrganizationID
|
||||||
|
tokenInfo.PlanType = userInfo.PlanType
|
||||||
}
|
}
|
||||||
|
|
||||||
return tokenInfo, nil
|
return tokenInfo, nil
|
||||||
@@ -510,6 +513,9 @@ func (s *OpenAIOAuthService) BuildAccountCredentials(tokenInfo *OpenAITokenInfo)
|
|||||||
if tokenInfo.OrganizationID != "" {
|
if tokenInfo.OrganizationID != "" {
|
||||||
creds["organization_id"] = tokenInfo.OrganizationID
|
creds["organization_id"] = tokenInfo.OrganizationID
|
||||||
}
|
}
|
||||||
|
if tokenInfo.PlanType != "" {
|
||||||
|
creds["plan_type"] = tokenInfo.PlanType
|
||||||
|
}
|
||||||
if strings.TrimSpace(tokenInfo.ClientID) != "" {
|
if strings.TrimSpace(tokenInfo.ClientID) != "" {
|
||||||
creds["client_id"] = strings.TrimSpace(tokenInfo.ClientID)
|
creds["client_id"] = strings.TrimSpace(tokenInfo.ClientID)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,10 @@
|
|||||||
<Icon v-else name="key" size="xs" />
|
<Icon v-else name="key" size="xs" />
|
||||||
<span>{{ typeLabel }}</span>
|
<span>{{ typeLabel }}</span>
|
||||||
</span>
|
</span>
|
||||||
|
<!-- Plan type part (optional) -->
|
||||||
|
<span v-if="planLabel" :class="['inline-flex items-center gap-1 px-1.5 py-1 border-l border-white/20', typeClass]">
|
||||||
|
<span>{{ planLabel }}</span>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -40,6 +44,7 @@ import Icon from '@/components/icons/Icon.vue'
|
|||||||
interface Props {
|
interface Props {
|
||||||
platform: AccountPlatform
|
platform: AccountPlatform
|
||||||
type: AccountType
|
type: AccountType
|
||||||
|
planType?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<Props>()
|
const props = defineProps<Props>()
|
||||||
@@ -65,6 +70,24 @@ const typeLabel = computed(() => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const planLabel = computed(() => {
|
||||||
|
if (!props.planType) return ''
|
||||||
|
const lower = props.planType.toLowerCase()
|
||||||
|
switch (lower) {
|
||||||
|
case 'plus':
|
||||||
|
return 'Plus'
|
||||||
|
case 'team':
|
||||||
|
return 'Team'
|
||||||
|
case 'chatgptpro':
|
||||||
|
case 'pro':
|
||||||
|
return 'Pro'
|
||||||
|
case 'free':
|
||||||
|
return 'Free'
|
||||||
|
default:
|
||||||
|
return props.planType
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const platformClass = computed(() => {
|
const platformClass = computed(() => {
|
||||||
if (props.platform === 'anthropic') {
|
if (props.platform === 'anthropic') {
|
||||||
return 'bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400'
|
return 'bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400'
|
||||||
|
|||||||
@@ -171,7 +171,7 @@
|
|||||||
<span v-else class="text-sm text-gray-400 dark:text-dark-500">-</span>
|
<span v-else class="text-sm text-gray-400 dark:text-dark-500">-</span>
|
||||||
</template>
|
</template>
|
||||||
<template #cell-platform_type="{ row }">
|
<template #cell-platform_type="{ row }">
|
||||||
<PlatformTypeBadge :platform="row.platform" :type="row.type" />
|
<PlatformTypeBadge :platform="row.platform" :type="row.type" :plan-type="row.credentials?.plan_type" />
|
||||||
</template>
|
</template>
|
||||||
<template #cell-capacity="{ row }">
|
<template #cell-capacity="{ row }">
|
||||||
<AccountCapacityCell :account="row" />
|
<AccountCapacityCell :account="row" />
|
||||||
|
|||||||
Reference in New Issue
Block a user