feat: add Grok (grok.com) video provider; runway nano-banana image; unify runway/grok pool policy

- grok provider: imagine text/image-to-video (media.post.create → conversations/new),
  GetGrokCreditsConfig credit query (remaining = 100 - used) + weekly reset, spoofed
  x-statsig-id (no Cloudflare clearance needed), /api/auth/session email lookup,
  6 reference images, 10 concurrent jobs/account, no token refresh (dead = dead)
- runway: nano-banana-2 image flow (Nano Banana 2); drop pre-deduct + post-success
  reconcile; out-of-credits/403 → dead (no revive); 10-ratio support
- imagine: drop post-success credit reconcile (consistent with krea)
- account gate: per-account N-concurrency (grok=10, others=1)
- admin: provider health lists all 7 providers; frontend import auto-detects Grok SSO
- docs: README (CN/EN) updated to 7 providers + Grok

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-28 04:01:06 +08:00
co-authored by Claude Opus 4.8
parent 8633addf64
commit 17cd289dfd
16 changed files with 1442 additions and 166 deletions
@@ -102,6 +102,36 @@ func (h *ProviderAdminHandler) ImportRunwayToken(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"ok": true, "id": item.ID, "status": item.Status, "pending": item.Status == "pending"})
}
func (h *ProviderAdminHandler) ImportGrokToken(c *gin.Context) {
var body struct {
AccessToken string `json:"access_token"`
Value string `json:"value"`
SSO string `json:"sso"`
Name string `json:"name"`
ID string `json:"id"`
}
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"detail": "invalid request body"})
return
}
token := body.AccessToken
for _, v := range []string{body.Value, body.SSO} {
if token == "" {
token = v
}
}
name := body.Name
if name == "" {
name = body.ID
}
item, err := h.tokens.ImportGrokToken(c.Request.Context(), token, name)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"ok": true, "id": item.ID, "status": item.Status, "pending": item.Status == "pending"})
}
func (h *ProviderAdminHandler) ImportKreaCookie(c *gin.Context) {
var body struct {
Cookie string `json:"cookie"`
+1
View File
@@ -108,6 +108,7 @@ func New(cfg *config.Config, auth *service.AuthService, handlers Handlers) *gin.
authed.POST("/tokens/import-leonardo-cookie", handlers.ProviderAdmin.ImportLeonardoCookie)
authed.POST("/tokens/import-krea-cookie", handlers.ProviderAdmin.ImportKreaCookie)
authed.POST("/tokens/import-imagine-token", handlers.ProviderAdmin.ImportImagineToken)
authed.POST("/tokens/import-grok-token", handlers.ProviderAdmin.ImportGrokToken)
authed.POST("/tokens/delete-bulk", handlers.ProviderAdmin.TokenDeleteBulk)
authed.PATCH("/tokens/:pool/:id", handlers.ProviderAdmin.TokenUpdate)
authed.DELETE("/tokens/:pool/:id", handlers.ProviderAdmin.TokenDelete)