feat: 二次元前端改版 + 后端账号plan探测/调度修复
This commit is contained in:
@@ -709,17 +709,44 @@ func (s *TokenService) checkPendingAdobe(tokenID, cookie string) {
|
||||
_, _ = s.tokens.Update(ctx, "adobe", tokenID, seed)
|
||||
|
||||
quotaMeta := map[string]any{}
|
||||
planKnown := false
|
||||
if cb, e := s.adobe.FetchCreditsBalance(ctx, result.AccessToken); e == nil {
|
||||
if ra := strings.TrimSpace(stringValue(cb["available_until"])); ra != "" {
|
||||
_, _ = s.tokens.Update(ctx, "adobe", tokenID, map[string]any{"cached_quota_reset_after": ra})
|
||||
}
|
||||
quotaMeta["cached_quota_at"] = int(time.Now().Unix())
|
||||
planCap := strings.ToLower(strings.TrimSpace(stringValue(cb["plan"])))
|
||||
quotaMeta["plan"] = planCap
|
||||
isVIP := planCap != "" && planCap != "free"
|
||||
planKnown = planCap != ""
|
||||
|
||||
if rem, ok := cb["remaining"].(int); ok {
|
||||
quotaMeta["cached_quota_remaining"] = rem
|
||||
if rem <= 4000 {
|
||||
if isVIP && rem > 0 && rem <= 4000 {
|
||||
quotaMeta["is_sub_account"] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Set concurrency + limits based on plan
|
||||
concurrency := 1
|
||||
imageLimit := true
|
||||
videoLimit := true
|
||||
if isVIP {
|
||||
concurrency = 5
|
||||
imageLimit = false
|
||||
videoLimit = false
|
||||
if ra := strings.TrimSpace(stringValue(cb["available_until"])); ra != "" {
|
||||
_, _ = s.tokens.Update(ctx, "adobe", tokenID, map[string]any{"cached_quota_reset_after": ra})
|
||||
}
|
||||
}
|
||||
patch := map[string]any{
|
||||
"concurrency": concurrency,
|
||||
"image_limited": imageLimit,
|
||||
"video_limited": videoLimit,
|
||||
"meta": quotaMeta,
|
||||
}
|
||||
if planKnown {
|
||||
patch["status"] = "active"
|
||||
patch["dead"] = false
|
||||
}
|
||||
_, _ = s.tokens.Update(ctx, "adobe", tokenID, patch)
|
||||
}
|
||||
if prof, e := s.adobe.FetchAccountProfile(ctx, result.AccessToken); e == nil {
|
||||
p := map[string]any{}
|
||||
@@ -734,6 +761,13 @@ func (s *TokenService) checkPendingAdobe(tokenID, cookie string) {
|
||||
}
|
||||
}
|
||||
|
||||
// 探测不到会员身份(credits 接口失败或没返回 plan)的号既不算普号也不算会员号,
|
||||
// 留在池里只会在需要会员的请求上撞 403 —— 直接置死号,等重新探测到 plan 再启用。
|
||||
if !planKnown {
|
||||
log.Printf("token import: adobe %s plan unknown, marking dead", tokenID)
|
||||
s.finishPending(ctx, "adobe", tokenID, "disabled", true, quotaMeta)
|
||||
return
|
||||
}
|
||||
s.finishPending(ctx, "adobe", tokenID, "active", false, quotaMeta)
|
||||
_, _ = s.refresh.Update(ctx, tokenID, map[string]any{
|
||||
"last_attempt_at": time.Now(),
|
||||
@@ -1274,9 +1308,21 @@ func (s *TokenService) Quota(ctx context.Context, pool, id string) (map[string]a
|
||||
patch := map[string]any{}
|
||||
meta := cloneJSONMap(item.Meta)
|
||||
meta["cached_quota_at"] = int(time.Now().Unix())
|
||||
plan := strings.ToLower(strings.TrimSpace(stringValue(data["plan"])))
|
||||
if plan != "" {
|
||||
meta["plan"] = plan
|
||||
}
|
||||
if remaining, ok := data["remaining"].(int); ok {
|
||||
meta["cached_quota_remaining"] = remaining
|
||||
meta["is_sub_account"] = remaining <= 4000
|
||||
// is_sub_account 仅在导入时写入,刷新配额时不覆盖
|
||||
if _, hasFlag := meta["is_sub_account"]; !hasFlag && plan != "free" {
|
||||
meta["is_sub_account"] = remaining > 0 && remaining <= 4000
|
||||
}
|
||||
}
|
||||
// 子号必然是会员号:plan 查回 free 说明这个号没有会员,不能再挂子号标记
|
||||
// (否则同一行会同时显示 子号 和 普号)。
|
||||
if plan == "free" {
|
||||
meta["is_sub_account"] = false
|
||||
}
|
||||
if used, ok := data["used"].(int); ok {
|
||||
meta["cached_quota_used"] = used
|
||||
@@ -1285,6 +1331,12 @@ func (s *TokenService) Quota(ctx context.Context, pool, id string) (map[string]a
|
||||
meta["cached_quota_total"] = total
|
||||
}
|
||||
patch["meta"] = meta
|
||||
// 探测不到会员身份(新旧 plan 都为空)的号既不算普号也不算会员号,
|
||||
// 留在池里只会在需要会员的请求上撞 403 —— 直接置死号。
|
||||
if strings.TrimSpace(stringValue(meta["plan"])) == "" {
|
||||
patch["status"] = "disabled"
|
||||
patch["dead"] = true
|
||||
}
|
||||
if resetAfter := strings.TrimSpace(stringValue(data["available_until"])); resetAfter != "" {
|
||||
patch["cached_quota_reset_after"] = resetAfter
|
||||
item.CachedQuotaResetAfter = resetAfter
|
||||
@@ -1294,6 +1346,7 @@ func (s *TokenService) Quota(ctx context.Context, pool, id string) (map[string]a
|
||||
item = updated
|
||||
}
|
||||
}
|
||||
subAccount, _ := jsonMapBool(meta, "is_sub_account")
|
||||
return map[string]any{
|
||||
"supported": true,
|
||||
"remaining": data["remaining"],
|
||||
@@ -1304,6 +1357,9 @@ func (s *TokenService) Quota(ctx context.Context, pool, id string) (map[string]a
|
||||
"unchanged": false,
|
||||
"unknown": boolValueWithDefault(data["unknown"], false),
|
||||
"error": data["error"],
|
||||
// 会员身份跟着额度一起返回,前端查一次额度就能刷新 子号/母号/普号 徽章。
|
||||
"plan": emptyToNil(strings.ToLower(strings.TrimSpace(stringValue(meta["plan"])))),
|
||||
"sub_account": subAccount,
|
||||
}, nil
|
||||
}
|
||||
if poolToType(item.Pool) == "krea" && s.krea != nil {
|
||||
@@ -1648,6 +1704,7 @@ func accountRow(item model.TokenAccount, inFlight int64) map[string]any {
|
||||
quotaAt, _ := jsonMapInt(item.Meta, "cached_quota_at")
|
||||
pending, _ := jsonMapBool(item.Meta, "pending_check")
|
||||
subAccount, _ := jsonMapBool(item.Meta, "is_sub_account")
|
||||
plan, _ := item.Meta["plan"]
|
||||
// OpenAI email lives in the token's JWT (nested profile claim). Decode it at
|
||||
// render time like the Python reference (_account_row) so accounts imported
|
||||
// before the email was persisted still show a name; fall back to the cached
|
||||
@@ -1686,6 +1743,7 @@ func accountRow(item model.TokenAccount, inFlight int64) map[string]any {
|
||||
"video_limited": item.VideoLimited,
|
||||
"pending": pending,
|
||||
"sub_account": subAccount,
|
||||
"plan": emptyToNil(strings.ToLower(strings.TrimSpace(stringValue(plan)))),
|
||||
"quota_supported": hasQuota,
|
||||
"needs_reset_fetch": typeLabel == "adobe" && item.Status == "active" && strings.TrimSpace(item.CachedQuotaResetAfter) == "",
|
||||
"weight": item.Weight,
|
||||
|
||||
Reference in New Issue
Block a user