diff --git a/backend/internal/service/tokens.go b/backend/internal/service/tokens.go index e396b1e..162a784 100644 --- a/backend/internal/service/tokens.go +++ b/backend/internal/service/tokens.go @@ -862,26 +862,12 @@ func (s *TokenService) ImportGrokToken(ctx context.Context, ssoToken, tokenID st return nil, errors.New("not a grok sso token") } sid := grok.SessionIDFromToken(ssoToken) - // Resolve the real account email up front (GET /api/auth/session) for dedup + - // display — the sso session_id rotates per login, so email is the stable id. - email := "" - if s.grok != nil { - s.applyProxy(ctx) - // Best-effort email resolution. A dead/blocked probe must NOT block import: - // land the account and let the async checkPendingGrok disable it if the - // session is truly dead. Empty email just falls back to the session id. - if e, _, ferr := s.grok.FetchSession(ctx, ssoToken); ferr == nil { - email = e - } - } - idKey := email - if idKey == "" { - idKey = sid - } - // Identity is (pool, email): reuse the row for this account, else mint. - if existing, _ := s.tokens.GetByPoolEmail(ctx, "grok", idKey); existing != nil { - tokenID = existing.ID - } else if idKey != "" || tokenID == "" { + // Fully async, no dedup: every import mints a fresh row (a passed-in tokenID is + // an explicit edit → update). We do NOT look up an existing account by + // email/session_id, and we leave account_email empty — email, quota and + // recovery time are all filled off-thread by checkPendingGrok, which also + // disables the account if the sso session is dead. + if strings.TrimSpace(tokenID) == "" { tokenID = newTokenID("grok") } meta := datatypes.JSONMap{"pending_check": true} @@ -900,11 +886,6 @@ func (s *TokenService) ImportGrokToken(ctx context.Context, ssoToken, tokenID st return nil, err } } - if idKey != "" { - if updated, uerr := s.tokens.Update(ctx, "grok", tokenID, map[string]any{"account_email": idKey}); uerr == nil { - item = updated - } - } go s.checkPendingGrok(tokenID, ssoToken) return item, nil } diff --git a/frontend/src/components/ImportModal.vue b/frontend/src/components/ImportModal.vue index 05bb19b..24ed8c8 100644 --- a/frontend/src/components/ImportModal.vue +++ b/frontend/src/components/ImportModal.vue @@ -45,7 +45,6 @@ async function doSmartImport() { const errs = [] for (let i = 0; i < items.length; i++) { const it = items[i] - setStatus(`正在导入 ${i + 1}/${items.length} (${it.type})…`) try { const r = it.type === 'openai' ? await api('/tokens/import-chatgpt-token', jsonBody('POST', { access_token: it.value })) @@ -77,9 +76,10 @@ async function doSmartImport() { // Quota isn't checked here — the server probes each token off-thread and the // account list flips pending → active/dead on its own. if (fail === 0) { - setStatus(`✓ 导入 ${ok} 项 · 正在后台检测额度…`) + // Close immediately on success — the account lands in the table right away + // and the server backfills quota/email off-thread (pending → active/dead). emit('imported') - setTimeout(() => emit('close'), 1000) + emit('close') } else { setStatus(`成功 ${ok} · 失败 ${fail} · ${errs.slice(0, 3).join(' | ')}`, true) emit('imported')