feat(ui): OpenAI OAuth 账号支持批量 RT 输入创建
新增通过手动输入 Refresh Token 创建 OpenAI OAuth 账号功能, 参考 Anthropic sessionKey 批量创建方式: - useOpenAIOAuth 添加 validateRefreshToken 方法 - accounts.ts 添加 refreshOpenAIToken API - AuthInputMethod 类型新增 refresh_token 选项 - 支持多行输入 RT(每行一个)批量创建账号 - 账号名称自动累加后缀 #1, #2 等 - UI 显示 RT 数量徽章和批量创建提示 - 添加中英文 i18n 翻译
This commit is contained in:
@@ -398,6 +398,26 @@ export async function getAntigravityDefaultModelMapping(): Promise<Record<string
|
|||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh OpenAI token using refresh token
|
||||||
|
* @param refreshToken - The refresh token
|
||||||
|
* @param proxyId - Optional proxy ID
|
||||||
|
* @returns Token information including access_token, email, etc.
|
||||||
|
*/
|
||||||
|
export async function refreshOpenAIToken(
|
||||||
|
refreshToken: string,
|
||||||
|
proxyId?: number | null
|
||||||
|
): Promise<Record<string, unknown>> {
|
||||||
|
const payload: { refresh_token: string; proxy_id?: number } = {
|
||||||
|
refresh_token: refreshToken
|
||||||
|
}
|
||||||
|
if (proxyId) {
|
||||||
|
payload.proxy_id = proxyId
|
||||||
|
}
|
||||||
|
const { data } = await apiClient.post<Record<string, unknown>>('/admin/openai/refresh-token', payload)
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
export const accountsAPI = {
|
export const accountsAPI = {
|
||||||
list,
|
list,
|
||||||
getById,
|
getById,
|
||||||
@@ -418,6 +438,7 @@ export const accountsAPI = {
|
|||||||
getAvailableModels,
|
getAvailableModels,
|
||||||
generateAuthUrl,
|
generateAuthUrl,
|
||||||
exchangeCode,
|
exchangeCode,
|
||||||
|
refreshOpenAIToken,
|
||||||
batchCreate,
|
batchCreate,
|
||||||
batchUpdateCredentials,
|
batchUpdateCredentials,
|
||||||
bulkUpdate,
|
bulkUpdate,
|
||||||
|
|||||||
@@ -1650,10 +1650,12 @@
|
|||||||
:show-proxy-warning="form.platform !== 'openai' && !!form.proxy_id"
|
:show-proxy-warning="form.platform !== 'openai' && !!form.proxy_id"
|
||||||
:allow-multiple="form.platform === 'anthropic'"
|
:allow-multiple="form.platform === 'anthropic'"
|
||||||
:show-cookie-option="form.platform === 'anthropic'"
|
:show-cookie-option="form.platform === 'anthropic'"
|
||||||
|
:show-refresh-token-option="form.platform === 'openai'"
|
||||||
:platform="form.platform"
|
:platform="form.platform"
|
||||||
:show-project-id="geminiOAuthType === 'code_assist'"
|
:show-project-id="geminiOAuthType === 'code_assist'"
|
||||||
@generate-url="handleGenerateUrl"
|
@generate-url="handleGenerateUrl"
|
||||||
@cookie-auth="handleCookieAuth"
|
@cookie-auth="handleCookieAuth"
|
||||||
|
@validate-refresh-token="handleOpenAIValidateRT"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -2010,6 +2012,7 @@ interface OAuthFlowExposed {
|
|||||||
oauthState: string
|
oauthState: string
|
||||||
projectId: string
|
projectId: string
|
||||||
sessionKey: string
|
sessionKey: string
|
||||||
|
refreshToken: string
|
||||||
inputMethod: AuthInputMethod
|
inputMethod: AuthInputMethod
|
||||||
reset: () => void
|
reset: () => void
|
||||||
}
|
}
|
||||||
@@ -2861,6 +2864,95 @@ const handleOpenAIExchange = async (authCode: string) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OpenAI 手动 RT 批量验证和创建
|
||||||
|
const handleOpenAIValidateRT = async (refreshTokenInput: string) => {
|
||||||
|
if (!refreshTokenInput.trim()) return
|
||||||
|
|
||||||
|
// Parse multiple refresh tokens (one per line)
|
||||||
|
const refreshTokens = refreshTokenInput
|
||||||
|
.split('\n')
|
||||||
|
.map((rt) => rt.trim())
|
||||||
|
.filter((rt) => rt)
|
||||||
|
|
||||||
|
if (refreshTokens.length === 0) {
|
||||||
|
openaiOAuth.error.value = t('admin.accounts.oauth.openai.pleaseEnterRefreshToken')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
openaiOAuth.loading.value = true
|
||||||
|
openaiOAuth.error.value = ''
|
||||||
|
|
||||||
|
let successCount = 0
|
||||||
|
let failedCount = 0
|
||||||
|
const errors: string[] = []
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (let i = 0; i < refreshTokens.length; i++) {
|
||||||
|
try {
|
||||||
|
const tokenInfo = await openaiOAuth.validateRefreshToken(
|
||||||
|
refreshTokens[i],
|
||||||
|
form.proxy_id
|
||||||
|
)
|
||||||
|
if (!tokenInfo) {
|
||||||
|
failedCount++
|
||||||
|
errors.push(`#${i + 1}: ${openaiOAuth.error.value || 'Validation failed'}`)
|
||||||
|
openaiOAuth.error.value = ''
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const credentials = openaiOAuth.buildCredentials(tokenInfo)
|
||||||
|
const extra = openaiOAuth.buildExtraInfo(tokenInfo)
|
||||||
|
|
||||||
|
// Generate account name with index for batch
|
||||||
|
const accountName = refreshTokens.length > 1 ? `${form.name} #${i + 1}` : form.name
|
||||||
|
|
||||||
|
await adminAPI.accounts.create({
|
||||||
|
name: accountName,
|
||||||
|
notes: form.notes,
|
||||||
|
platform: 'openai',
|
||||||
|
type: 'oauth',
|
||||||
|
credentials,
|
||||||
|
extra,
|
||||||
|
proxy_id: form.proxy_id,
|
||||||
|
concurrency: form.concurrency,
|
||||||
|
priority: form.priority,
|
||||||
|
rate_multiplier: form.rate_multiplier,
|
||||||
|
group_ids: form.group_ids,
|
||||||
|
expires_at: form.expires_at,
|
||||||
|
auto_pause_on_expired: autoPauseOnExpired.value
|
||||||
|
})
|
||||||
|
successCount++
|
||||||
|
} catch (error: any) {
|
||||||
|
failedCount++
|
||||||
|
const errMsg = error.response?.data?.detail || error.message || 'Unknown error'
|
||||||
|
errors.push(`#${i + 1}: ${errMsg}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show results
|
||||||
|
if (successCount > 0 && failedCount === 0) {
|
||||||
|
appStore.showSuccess(
|
||||||
|
refreshTokens.length > 1
|
||||||
|
? t('admin.accounts.oauth.batchSuccess', { count: successCount })
|
||||||
|
: t('admin.accounts.accountCreated')
|
||||||
|
)
|
||||||
|
emit('created')
|
||||||
|
handleClose()
|
||||||
|
} else if (successCount > 0 && failedCount > 0) {
|
||||||
|
appStore.showWarning(
|
||||||
|
t('admin.accounts.oauth.batchPartialSuccess', { success: successCount, failed: failedCount })
|
||||||
|
)
|
||||||
|
openaiOAuth.error.value = errors.join('\n')
|
||||||
|
emit('created')
|
||||||
|
} else {
|
||||||
|
openaiOAuth.error.value = errors.join('\n')
|
||||||
|
appStore.showError(t('admin.accounts.oauth.batchFailed'))
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
openaiOAuth.loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Gemini OAuth 授权码兑换
|
// Gemini OAuth 授权码兑换
|
||||||
const handleGeminiExchange = async (authCode: string) => {
|
const handleGeminiExchange = async (authCode: string) => {
|
||||||
if (!authCode.trim() || !geminiOAuth.sessionId.value) return
|
if (!authCode.trim() || !geminiOAuth.sessionId.value) return
|
||||||
|
|||||||
@@ -10,11 +10,11 @@
|
|||||||
<h4 class="mb-3 font-semibold text-blue-900 dark:text-blue-200">{{ oauthTitle }}</h4>
|
<h4 class="mb-3 font-semibold text-blue-900 dark:text-blue-200">{{ oauthTitle }}</h4>
|
||||||
|
|
||||||
<!-- Auth Method Selection -->
|
<!-- Auth Method Selection -->
|
||||||
<div v-if="showCookieOption" class="mb-4">
|
<div v-if="showMethodSelection" class="mb-4">
|
||||||
<label class="mb-2 block text-sm font-medium text-blue-800 dark:text-blue-300">
|
<label class="mb-2 block text-sm font-medium text-blue-800 dark:text-blue-300">
|
||||||
{{ methodLabel }}
|
{{ methodLabel }}
|
||||||
</label>
|
</label>
|
||||||
<div class="flex gap-4">
|
<div class="flex flex-wrap gap-4">
|
||||||
<label class="flex cursor-pointer items-center gap-2">
|
<label class="flex cursor-pointer items-center gap-2">
|
||||||
<input
|
<input
|
||||||
v-model="inputMethod"
|
v-model="inputMethod"
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
t('admin.accounts.oauth.manualAuth')
|
t('admin.accounts.oauth.manualAuth')
|
||||||
}}</span>
|
}}</span>
|
||||||
</label>
|
</label>
|
||||||
<label class="flex cursor-pointer items-center gap-2">
|
<label v-if="showCookieOption" class="flex cursor-pointer items-center gap-2">
|
||||||
<input
|
<input
|
||||||
v-model="inputMethod"
|
v-model="inputMethod"
|
||||||
type="radio"
|
type="radio"
|
||||||
@@ -37,6 +37,101 @@
|
|||||||
t('admin.accounts.oauth.cookieAutoAuth')
|
t('admin.accounts.oauth.cookieAutoAuth')
|
||||||
}}</span>
|
}}</span>
|
||||||
</label>
|
</label>
|
||||||
|
<label v-if="showRefreshTokenOption" class="flex cursor-pointer items-center gap-2">
|
||||||
|
<input
|
||||||
|
v-model="inputMethod"
|
||||||
|
type="radio"
|
||||||
|
value="refresh_token"
|
||||||
|
class="text-blue-600 focus:ring-blue-500"
|
||||||
|
/>
|
||||||
|
<span class="text-sm text-blue-900 dark:text-blue-200">{{
|
||||||
|
t('admin.accounts.oauth.openai.refreshTokenAuth')
|
||||||
|
}}</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Refresh Token Input (OpenAI only) -->
|
||||||
|
<div v-if="inputMethod === 'refresh_token'" class="space-y-4">
|
||||||
|
<div
|
||||||
|
class="rounded-lg border border-blue-300 bg-white/80 p-4 dark:border-blue-600 dark:bg-gray-800/80"
|
||||||
|
>
|
||||||
|
<p class="mb-3 text-sm text-blue-700 dark:text-blue-300">
|
||||||
|
{{ t('admin.accounts.oauth.openai.refreshTokenDesc') }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- Refresh Token Input -->
|
||||||
|
<div class="mb-4">
|
||||||
|
<label
|
||||||
|
class="mb-2 flex items-center gap-2 text-sm font-semibold text-gray-700 dark:text-gray-300"
|
||||||
|
>
|
||||||
|
<Icon name="key" size="sm" class="text-blue-500" />
|
||||||
|
Refresh Token
|
||||||
|
<span
|
||||||
|
v-if="parsedRefreshTokenCount > 1"
|
||||||
|
class="rounded-full bg-blue-500 px-2 py-0.5 text-xs text-white"
|
||||||
|
>
|
||||||
|
{{ t('admin.accounts.oauth.keysCount', { count: parsedRefreshTokenCount }) }}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
v-model="refreshTokenInput"
|
||||||
|
rows="3"
|
||||||
|
class="input w-full resize-y font-mono text-sm"
|
||||||
|
:placeholder="t('admin.accounts.oauth.openai.refreshTokenPlaceholder')"
|
||||||
|
></textarea>
|
||||||
|
<p
|
||||||
|
v-if="parsedRefreshTokenCount > 1"
|
||||||
|
class="mt-1 text-xs text-blue-600 dark:text-blue-400"
|
||||||
|
>
|
||||||
|
{{ t('admin.accounts.oauth.batchCreateAccounts', { count: parsedRefreshTokenCount }) }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Error Message -->
|
||||||
|
<div
|
||||||
|
v-if="error"
|
||||||
|
class="mb-4 rounded-lg border border-red-200 bg-red-50 p-3 dark:border-red-700 dark:bg-red-900/30"
|
||||||
|
>
|
||||||
|
<p class="whitespace-pre-line text-sm text-red-600 dark:text-red-400">
|
||||||
|
{{ error }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Validate Button -->
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-primary w-full"
|
||||||
|
:disabled="loading || !refreshTokenInput.trim()"
|
||||||
|
@click="handleValidateRefreshToken"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
v-if="loading"
|
||||||
|
class="-ml-1 mr-2 h-4 w-4 animate-spin"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<circle
|
||||||
|
class="opacity-25"
|
||||||
|
cx="12"
|
||||||
|
cy="12"
|
||||||
|
r="10"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="4"
|
||||||
|
></circle>
|
||||||
|
<path
|
||||||
|
class="opacity-75"
|
||||||
|
fill="currentColor"
|
||||||
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
<Icon v-else name="sparkles" size="sm" class="mr-2" />
|
||||||
|
{{
|
||||||
|
loading
|
||||||
|
? t('admin.accounts.oauth.openai.validating')
|
||||||
|
: t('admin.accounts.oauth.openai.validateAndCreate')
|
||||||
|
}}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -173,7 +268,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Manual Authorization Flow -->
|
<!-- Manual Authorization Flow -->
|
||||||
<div v-else class="space-y-4">
|
<div v-if="inputMethod === 'manual'" class="space-y-4">
|
||||||
<p class="mb-4 text-sm text-blue-800 dark:text-blue-300">
|
<p class="mb-4 text-sm text-blue-800 dark:text-blue-300">
|
||||||
{{ oauthFollowSteps }}
|
{{ oauthFollowSteps }}
|
||||||
</p>
|
</p>
|
||||||
@@ -428,6 +523,7 @@ interface Props {
|
|||||||
allowMultiple?: boolean
|
allowMultiple?: boolean
|
||||||
methodLabel?: string
|
methodLabel?: string
|
||||||
showCookieOption?: boolean // Whether to show cookie auto-auth option
|
showCookieOption?: boolean // Whether to show cookie auto-auth option
|
||||||
|
showRefreshTokenOption?: boolean // Whether to show refresh token input option (OpenAI only)
|
||||||
platform?: 'anthropic' | 'openai' | 'gemini' | 'antigravity' // Platform type for different UI/text
|
platform?: 'anthropic' | 'openai' | 'gemini' | 'antigravity' // Platform type for different UI/text
|
||||||
showProjectId?: boolean // New prop to control project ID visibility
|
showProjectId?: boolean // New prop to control project ID visibility
|
||||||
}
|
}
|
||||||
@@ -442,6 +538,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
allowMultiple: false,
|
allowMultiple: false,
|
||||||
methodLabel: 'Authorization Method',
|
methodLabel: 'Authorization Method',
|
||||||
showCookieOption: true,
|
showCookieOption: true,
|
||||||
|
showRefreshTokenOption: false,
|
||||||
platform: 'anthropic',
|
platform: 'anthropic',
|
||||||
showProjectId: true
|
showProjectId: true
|
||||||
})
|
})
|
||||||
@@ -450,6 +547,7 @@ const emit = defineEmits<{
|
|||||||
'generate-url': []
|
'generate-url': []
|
||||||
'exchange-code': [code: string]
|
'exchange-code': [code: string]
|
||||||
'cookie-auth': [sessionKey: string]
|
'cookie-auth': [sessionKey: string]
|
||||||
|
'validate-refresh-token': [refreshToken: string]
|
||||||
'update:inputMethod': [method: AuthInputMethod]
|
'update:inputMethod': [method: AuthInputMethod]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
@@ -487,10 +585,14 @@ const oauthImportantNotice = computed(() => {
|
|||||||
const inputMethod = ref<AuthInputMethod>(props.showCookieOption ? 'manual' : 'manual')
|
const inputMethod = ref<AuthInputMethod>(props.showCookieOption ? 'manual' : 'manual')
|
||||||
const authCodeInput = ref('')
|
const authCodeInput = ref('')
|
||||||
const sessionKeyInput = ref('')
|
const sessionKeyInput = ref('')
|
||||||
|
const refreshTokenInput = ref('')
|
||||||
const showHelpDialog = ref(false)
|
const showHelpDialog = ref(false)
|
||||||
const oauthState = ref('')
|
const oauthState = ref('')
|
||||||
const projectId = ref('')
|
const projectId = ref('')
|
||||||
|
|
||||||
|
// Computed: show method selection when either cookie or refresh token option is enabled
|
||||||
|
const showMethodSelection = computed(() => props.showCookieOption || props.showRefreshTokenOption)
|
||||||
|
|
||||||
// Clipboard
|
// Clipboard
|
||||||
const { copied, copyToClipboard } = useClipboard()
|
const { copied, copyToClipboard } = useClipboard()
|
||||||
|
|
||||||
@@ -502,6 +604,14 @@ const parsedKeyCount = computed(() => {
|
|||||||
.filter((k) => k).length
|
.filter((k) => k).length
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Computed: count of refresh tokens entered
|
||||||
|
const parsedRefreshTokenCount = computed(() => {
|
||||||
|
return refreshTokenInput.value
|
||||||
|
.split('\n')
|
||||||
|
.map((rt) => rt.trim())
|
||||||
|
.filter((rt) => rt).length
|
||||||
|
})
|
||||||
|
|
||||||
// Watchers
|
// Watchers
|
||||||
watch(inputMethod, (newVal) => {
|
watch(inputMethod, (newVal) => {
|
||||||
emit('update:inputMethod', newVal)
|
emit('update:inputMethod', newVal)
|
||||||
@@ -563,18 +673,26 @@ const handleCookieAuth = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleValidateRefreshToken = () => {
|
||||||
|
if (refreshTokenInput.value.trim()) {
|
||||||
|
emit('validate-refresh-token', refreshTokenInput.value.trim())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Expose methods and state
|
// Expose methods and state
|
||||||
defineExpose({
|
defineExpose({
|
||||||
authCode: authCodeInput,
|
authCode: authCodeInput,
|
||||||
oauthState,
|
oauthState,
|
||||||
projectId,
|
projectId,
|
||||||
sessionKey: sessionKeyInput,
|
sessionKey: sessionKeyInput,
|
||||||
|
refreshToken: refreshTokenInput,
|
||||||
inputMethod,
|
inputMethod,
|
||||||
reset: () => {
|
reset: () => {
|
||||||
authCodeInput.value = ''
|
authCodeInput.value = ''
|
||||||
oauthState.value = ''
|
oauthState.value = ''
|
||||||
projectId.value = ''
|
projectId.value = ''
|
||||||
sessionKeyInput.value = ''
|
sessionKeyInput.value = ''
|
||||||
|
refreshTokenInput.value = ''
|
||||||
inputMethod.value = 'manual'
|
inputMethod.value = 'manual'
|
||||||
showHelpDialog.value = false
|
showHelpDialog.value = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useAppStore } from '@/stores/app'
|
|||||||
import { adminAPI } from '@/api/admin'
|
import { adminAPI } from '@/api/admin'
|
||||||
|
|
||||||
export type AddMethod = 'oauth' | 'setup-token'
|
export type AddMethod = 'oauth' | 'setup-token'
|
||||||
export type AuthInputMethod = 'manual' | 'cookie'
|
export type AuthInputMethod = 'manual' | 'cookie' | 'refresh_token'
|
||||||
|
|
||||||
export interface OAuthState {
|
export interface OAuthState {
|
||||||
authUrl: string
|
authUrl: string
|
||||||
|
|||||||
@@ -105,6 +105,32 @@ export function useOpenAIOAuth() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate refresh token and get full token info
|
||||||
|
const validateRefreshToken = async (
|
||||||
|
refreshToken: string,
|
||||||
|
proxyId?: number | null
|
||||||
|
): Promise<OpenAITokenInfo | null> => {
|
||||||
|
if (!refreshToken.trim()) {
|
||||||
|
error.value = 'Missing refresh token'
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = true
|
||||||
|
error.value = ''
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Use dedicated refresh-token endpoint
|
||||||
|
const tokenInfo = await adminAPI.accounts.refreshOpenAIToken(refreshToken.trim(), proxyId)
|
||||||
|
return tokenInfo as OpenAITokenInfo
|
||||||
|
} catch (err: any) {
|
||||||
|
error.value = err.response?.data?.detail || 'Failed to validate refresh token'
|
||||||
|
appStore.showError(error.value)
|
||||||
|
return null
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Build credentials for OpenAI OAuth account
|
// Build credentials for OpenAI OAuth account
|
||||||
const buildCredentials = (tokenInfo: OpenAITokenInfo): Record<string, unknown> => {
|
const buildCredentials = (tokenInfo: OpenAITokenInfo): Record<string, unknown> => {
|
||||||
const creds: Record<string, unknown> = {
|
const creds: Record<string, unknown> = {
|
||||||
@@ -152,6 +178,7 @@ export function useOpenAIOAuth() {
|
|||||||
resetState,
|
resetState,
|
||||||
generateAuthUrl,
|
generateAuthUrl,
|
||||||
exchangeAuthCode,
|
exchangeAuthCode,
|
||||||
|
validateRefreshToken,
|
||||||
buildCredentials,
|
buildCredentials,
|
||||||
buildExtraInfo
|
buildExtraInfo
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1662,6 +1662,9 @@ export default {
|
|||||||
cookieAuthFailed: 'Cookie authorization failed',
|
cookieAuthFailed: 'Cookie authorization failed',
|
||||||
keyAuthFailed: 'Key {index}: {error}',
|
keyAuthFailed: 'Key {index}: {error}',
|
||||||
successCreated: 'Successfully created {count} account(s)',
|
successCreated: 'Successfully created {count} account(s)',
|
||||||
|
batchSuccess: 'Successfully created {count} account(s)',
|
||||||
|
batchPartialSuccess: 'Partial success: {success} succeeded, {failed} failed',
|
||||||
|
batchFailed: 'Batch creation failed',
|
||||||
// OpenAI specific
|
// OpenAI specific
|
||||||
openai: {
|
openai: {
|
||||||
title: 'OpenAI Account Authorization',
|
title: 'OpenAI Account Authorization',
|
||||||
@@ -1680,7 +1683,14 @@ export default {
|
|||||||
authCodePlaceholder:
|
authCodePlaceholder:
|
||||||
'Option 1: Copy the complete URL\n(http://localhost:xxx/auth/callback?code=...)\nOption 2: Copy only the code parameter value',
|
'Option 1: Copy the complete URL\n(http://localhost:xxx/auth/callback?code=...)\nOption 2: Copy only the code parameter value',
|
||||||
authCodeHint:
|
authCodeHint:
|
||||||
'You can copy the entire URL or just the code parameter value, the system will auto-detect'
|
'You can copy the entire URL or just the code parameter value, the system will auto-detect',
|
||||||
|
// Refresh Token auth
|
||||||
|
refreshTokenAuth: 'Manual RT Input',
|
||||||
|
refreshTokenDesc: 'Enter your existing OpenAI Refresh Token(s). Supports batch input (one per line). The system will automatically validate and create accounts.',
|
||||||
|
refreshTokenPlaceholder: 'Paste your OpenAI Refresh Token...\nSupports multiple, one per line',
|
||||||
|
validating: 'Validating...',
|
||||||
|
validateAndCreate: 'Validate & Create Account',
|
||||||
|
pleaseEnterRefreshToken: 'Please enter Refresh Token'
|
||||||
},
|
},
|
||||||
// Gemini specific
|
// Gemini specific
|
||||||
gemini: {
|
gemini: {
|
||||||
|
|||||||
@@ -1804,6 +1804,9 @@ export default {
|
|||||||
cookieAuthFailed: 'Cookie 授权失败',
|
cookieAuthFailed: 'Cookie 授权失败',
|
||||||
keyAuthFailed: '密钥 {index}: {error}',
|
keyAuthFailed: '密钥 {index}: {error}',
|
||||||
successCreated: '成功创建 {count} 个账号',
|
successCreated: '成功创建 {count} 个账号',
|
||||||
|
batchSuccess: '成功创建 {count} 个账号',
|
||||||
|
batchPartialSuccess: '部分成功:{success} 个成功,{failed} 个失败',
|
||||||
|
batchFailed: '批量创建失败',
|
||||||
// OpenAI specific
|
// OpenAI specific
|
||||||
openai: {
|
openai: {
|
||||||
title: 'OpenAI 账户授权',
|
title: 'OpenAI 账户授权',
|
||||||
@@ -1820,7 +1823,14 @@ export default {
|
|||||||
authCode: '授权链接或 Code',
|
authCode: '授权链接或 Code',
|
||||||
authCodePlaceholder:
|
authCodePlaceholder:
|
||||||
'方式1:复制完整的链接\n(http://localhost:xxx/auth/callback?code=...)\n方式2:仅复制 code 参数的值',
|
'方式1:复制完整的链接\n(http://localhost:xxx/auth/callback?code=...)\n方式2:仅复制 code 参数的值',
|
||||||
authCodeHint: '您可以直接复制整个链接或仅复制 code 参数值,系统会自动识别'
|
authCodeHint: '您可以直接复制整个链接或仅复制 code 参数值,系统会自动识别',
|
||||||
|
// Refresh Token auth
|
||||||
|
refreshTokenAuth: '手动输入 RT',
|
||||||
|
refreshTokenDesc: '输入您已有的 OpenAI Refresh Token,支持批量输入(每行一个),系统将自动验证并创建账号。',
|
||||||
|
refreshTokenPlaceholder: '粘贴您的 OpenAI Refresh Token...\n支持多个,每行一个',
|
||||||
|
validating: '验证中...',
|
||||||
|
validateAndCreate: '验证并创建账号',
|
||||||
|
pleaseEnterRefreshToken: '请输入 Refresh Token'
|
||||||
},
|
},
|
||||||
// Gemini specific
|
// Gemini specific
|
||||||
gemini: {
|
gemini: {
|
||||||
|
|||||||
Reference in New Issue
Block a user