feat(grok): 视频改走 Grok Console 并新增图片模型

- grok 视频/图片统一走 console.x.ai:sso 换 DPoP 短期 token,绕开 grok.com 的 statsig 反爬

- 新增 grok-image:不带参考图走 /v1/images/generations,带图自动走 quality 上游 + /v1/images/edits

- grok 额度改本地写死 图5/视频2,成功各扣一次,两份归零直接判死,无恢复时间

- 视频渲染预算统一 20 分钟(adobe/runway/grok/custom)

- 删除参考图人脸打码(facemask + onnxruntime 依赖 + 前台开关)
This commit is contained in:
2026-08-08 11:15:42 +08:00
parent 554dea6252
commit ebcf3c5779
16 changed files with 956 additions and 446 deletions
+52
View File
@@ -124,6 +124,58 @@ func (r *TokenRepository) ReserveQuota(ctx context.Context, pool, id string, amo
return allowed, deducted, err
}
// Grok accounts carry a forced local quota instead of an upstream balance:
// Console 没有额度接口,所以导入时写死 图 5 / 视频 2,用一次扣一次,两个都归零直接判死。
const (
GrokImageQuotaKey = "grok_image_remaining"
GrokVideoQuotaKey = "grok_video_remaining"
GrokImageQuota = 5
GrokVideoQuota = 2
)
// ConsumeGrokQuota deducts one unit from a grok account's local per-kind quota
// under a row lock. Zeroed kinds are flagged (image_limited / video_limited) so
// scheduling skips them; once both are zero the account is dead (no reset time —
// 用完就废).
func (r *TokenRepository) ConsumeGrokQuota(ctx context.Context, id, kind string) error {
return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
var item model.TokenAccount
if e := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
First(&item, "pool = ? AND id = ?", "grok", id).Error; e != nil {
return e
}
images, known := metaInt(item.Meta, GrokImageQuotaKey)
if !known {
images = GrokImageQuota
}
videos, known := metaInt(item.Meta, GrokVideoQuotaKey)
if !known {
videos = GrokVideoQuota
}
if kind == "video" {
videos = max(0, videos-1)
} else {
images = max(0, images-1)
}
meta := cloneMeta(item.Meta)
meta[GrokImageQuotaKey] = images
meta[GrokVideoQuotaKey] = videos
patch := map[string]any{
"meta": meta,
"image_limited": images <= 0,
"video_limited": videos <= 0,
"updated_at": time.Now(),
}
if images <= 0 && videos <= 0 {
patch["status"] = "disabled"
patch["dead"] = true
}
return tx.Model(&model.TokenAccount{}).
Where("pool = ? AND id = ?", "grok", id).
Updates(patch).Error
})
}
// RefundQuota atomically adds `amount` back to cached_quota_remaining (releasing a
// hold from a reservation whose render then failed). No-op if the balance is
// unknown. Row-locked like ReserveQuota.