feat(grok): 对齐 free-usage/empty 失败分类与 sticky/ReAuth 体验

从 grokcli 吸收 body-first 失败分类(free-usage/empty/billing),
接到现有 temp_unsched 与 failover,保留 content-policy 与 pool_mode。
Grok 粘连/缓存在无显式 session 时可用 previous_response_id;
ReAuth 预填 email---- 并默认密码 tab(密码不落库)。
This commit is contained in:
IanShaw027
2026-08-07 13:47:09 +08:00
parent 9bc99e8b6e
commit e12e0dc1a6
10 changed files with 806 additions and 4 deletions
@@ -924,6 +924,11 @@ interface Props {
showEmailPasswordOption?: boolean
showManualOption?: boolean
initialInputMethod?: AuthInputMethod
/**
* Prefill for Grok email----password reauth. Password is never stored;
* pass only the email (or "email----") so the operator types the password.
*/
initialEmailPassword?: string
platform?: AccountPlatform // Platform type for different UI/text
showProjectId?: boolean // New prop to control project ID visibility
}
@@ -949,6 +954,7 @@ const props = withDefaults(defineProps<Props>(), {
showEmailPasswordOption: false,
showManualOption: true,
initialInputMethod: 'manual',
initialEmailPassword: '',
platform: 'anthropic',
showProjectId: true
})
@@ -1010,7 +1016,7 @@ const sessionTokenInput = ref('')
const codexSessionInput = ref('')
const codexPATInput = ref('')
const ssoCookieInput = ref('')
const emailPasswordInput = ref('')
const emailPasswordInput = ref(props.initialEmailPassword || '')
const showHelpDialog = ref(false)
const oauthState = ref('')
const projectId = ref('')
@@ -1085,6 +1091,16 @@ watch(() => props.initialInputMethod, (newVal) => {
inputMethod.value = newVal
})
watch(
() => props.initialEmailPassword,
(newVal) => {
// Only prefill when the field is empty so we never overwrite operator input.
if (newVal && !emailPasswordInput.value.trim()) {
emailPasswordInput.value = newVal
}
}
)
watch(inputMethod, (newVal) => {
emit('update:inputMethod', newVal)
})
@@ -137,6 +137,8 @@
:method-label="t('admin.accounts.inputMethod')"
:platform="isOpenAI ? 'openai' : isGemini ? 'gemini' : isAntigravity ? 'antigravity' : isGrok ? 'grok' : 'anthropic'"
:show-project-id="isGemini && geminiOAuthType === 'code_assist'"
:initial-input-method="grokInitialInputMethod"
:initial-email-password="grokPrefillEmailPassword"
@generate-url="handleGenerateUrl"
@cookie-auth="handleCookieAuth"
@validate-refresh-token="handleGrokValidateRefreshToken"
@@ -255,6 +257,40 @@ const isAnthropic = computed(() => props.account?.platform === 'anthropic')
const isAntigravity = computed(() => props.account?.platform === 'antigravity')
const isGrok = computed(() => props.account?.platform === 'grok')
/** Stored Grok email for reauth prefill (password is never stored). */
const grokAccountEmail = computed(() => {
if (!isGrok.value || !props.account) return ''
const creds = (props.account.credentials || {}) as Record<string, unknown>
const email = typeof creds.email === 'string' ? creds.email.trim() : ''
return email
})
/**
* Prefill "email----" so the operator only types the password.
* Empty when no email is known (full email----password required).
*/
const grokPrefillEmailPassword = computed(() => {
const email = grokAccountEmail.value
return email ? `${email}----` : ''
})
/**
* Grok reauth default tab:
* - password first when we know the email (common reauth path)
* - refresh_token when email unknown but RT may still work
* - email_password otherwise
*/
const grokInitialInputMethod = computed<AuthInputMethod>(() => {
if (!isGrok.value) return 'manual'
if (grokAccountEmail.value) return 'email_password'
const creds = (props.account?.credentials || {}) as Record<string, unknown>
const hasRT =
(typeof creds.refresh_token === 'string' && creds.refresh_token.trim() !== '') ||
(typeof creds.has_refresh_token === 'boolean' && creds.has_refresh_token)
if (hasRT) return 'refresh_token'
return 'email_password'
})
// Computed - current OAuth state based on platform
const currentAuthUrl = computed(() => {
if (isOpenAILike.value) return openaiOAuth.authUrl.value
@@ -32,4 +32,13 @@ describe('ReAuthAccountModal Grok re-auth paths', () => {
expect(source).toContain("method === 'email_password'")
expect(source).toContain("method === 'refresh_token'")
})
it('prefills email---- and defaults to password method when email is known', () => {
expect(source).toContain('grokPrefillEmailPassword')
expect(source).toContain('grokInitialInputMethod')
expect(source).toContain(':initial-email-password="grokPrefillEmailPassword"')
expect(source).toContain(':initial-input-method="grokInitialInputMethod"')
expect(source).toContain('email----')
expect(source).toContain("return 'email_password'")
})
})