From 2afb4e7857e009bbfc42ff54d61e533ff98caefb Mon Sep 17 00:00:00 2001 From: chiyi Date: Fri, 10 Jul 2026 11:19:51 +0800 Subject: [PATCH] =?UTF-8?q?nano-banana-2=20=E5=8D=87=E7=BA=A7=E4=B8=BA=20n?= =?UTF-8?q?ano-banana-pro=20(workflow=5Fgemini=5Fimage=20/=20gemini-3-pro-?= =?UTF-8?q?image-preview,=20=E6=94=AF=E6=8C=81=202K/4K)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../internal/http/handler/user_generation.go | 8 ++--- backend/internal/provider/runway/image.go | 33 +++++++++---------- backend/internal/service/v1.go | 20 +++++------ 3 files changed, 29 insertions(+), 32 deletions(-) diff --git a/backend/internal/http/handler/user_generation.go b/backend/internal/http/handler/user_generation.go index 4d6bc42..63de11d 100644 --- a/backend/internal/http/handler/user_generation.go +++ b/backend/internal/http/handler/user_generation.go @@ -453,7 +453,7 @@ func (h *UserGenerationHandler) catalogEntries(c *gin.Context) ([]gin.H, error) "description": "Adobe Flux Kontext Max", }, { - "id": "nano-banana-2", + "id": "nano-banana-pro", "provider": "runway", "type": "image", "ratios": []string{"1:1", "1:4", "1:8", "2:3", "3:2", "3:4", "4:1", "4:3", "4:5", "5:4", "8:1", "9:16", "16:9", "21:9"}, @@ -461,7 +461,7 @@ func (h *UserGenerationHandler) catalogEntries(c *gin.Context) ([]gin.H, error) "image_to_image": true, "max_reference_images": 6, "reference_mode": "asset", - "description": "Runway Nano Banana 2 (图/参考图)", + "description": "Runway Nano Banana Pro (图/参考图)", }, { "id": "gemini-veo31", @@ -614,12 +614,12 @@ func (h *UserGenerationHandler) publicModels() ([]gin.H, error) { "stub": false, }, { - "id": "nano-banana-2", + "id": "nano-banana-pro", "provider": "runway", "kind": "image", "ratios": []string{"1:1", "1:4", "1:8", "2:3", "3:2", "3:4", "4:1", "4:3", "4:5", "5:4", "8:1", "9:16", "16:9", "21:9"}, "resolutions": []string{"1K", "2K", "4K"}, - "description": "Runway Nano Banana 2", + "description": "Runway Nano Banana Pro", "stub": false, }, { diff --git a/backend/internal/provider/runway/image.go b/backend/internal/provider/runway/image.go index 32e27db..3179f0b 100644 --- a/backend/internal/provider/runway/image.go +++ b/backend/internal/provider/runway/image.go @@ -13,14 +13,14 @@ import ( "github.com/google/uuid" ) -// GenerateImage runs the Runway "Nano Banana 2" (gemini_3_1_flash_image) -// text/image-to-image pipeline: upload each reference image (DATASET + -// DATASET_PREVIEW → dataset) to obtain its {assetId, url}, create a gemini image -// task and poll it to completion, then download the rendered PNG. teamID is the -// workspace id; if empty it's derived from the token. aspectRatio is passed -// through as-is (e.g. "16:9"); imageSize is the "1K"/"2K"/"4K" tier. refs may be -// empty (pure text-to-image). -func (c *Client) GenerateImage(ctx context.Context, token, teamID, prompt, aspectRatio, imageSize string, refs [][]byte) ([]byte, map[string]any, error) { +// GenerateImage runs the Runway "Nano Banana Pro" (workflow_gemini_image / +// gemini-3-pro-image-preview) text/image-to-image pipeline: upload each +// reference image (DATASET + DATASET_PREVIEW → dataset) to obtain its +// {assetId, url}, create a gemini image task and poll it to completion, then +// download the rendered PNG. teamID is the workspace id; if empty it's derived +// from the token. imageSize is the "1K"/"2K"/"4K" tier (Pro honors 2K/4K). +// refs may be empty (pure text-to-image). +func (c *Client) GenerateImage(ctx context.Context, token, teamID, prompt, imageSize string, refs [][]byte) ([]byte, map[string]any, error) { token = strings.TrimSpace(strings.TrimPrefix(token, "Bearer ")) if token == "" { return nil, nil, ErrAuth @@ -31,9 +31,6 @@ func (c *Client) GenerateImage(ctx context.Context, token, teamID, prompt, aspec if teamID == "" { return nil, nil, errors.New("runway: no team id") } - if strings.TrimSpace(aspectRatio) == "" { - aspectRatio = "16:9" - } if strings.TrimSpace(imageSize) == "" { imageSize = "1K" } @@ -66,7 +63,7 @@ func (c *Client) GenerateImage(ctx context.Context, token, teamID, prompt, aspec }) } - taskID, err := c.createImageTask(ctx, submitClient, token, teamID, prompt, aspectRatio, imageSize, refImages) + taskID, err := c.createImageTask(ctx, submitClient, token, teamID, prompt, imageSize, refImages) if err != nil { return nil, nil, err } @@ -111,15 +108,15 @@ func (c *Client) uploadReference(ctx context.Context, client tlsclient.HttpClien return assetID, refURL, nil } -// createImageTask creates a gemini_3_1_flash_image task and returns its id. -func (c *Client) createImageTask(ctx context.Context, client tlsclient.HttpClient, token, teamID, prompt, aspectRatio, imageSize string, refImages []map[string]any) (string, error) { +// createImageTask creates a workflow_gemini_image (Nano Banana Pro) task and +// returns its id. +func (c *Client) createImageTask(ctx context.Context, client tlsclient.HttpClient, token, teamID, prompt, imageSize string, refImages []map[string]any) (string, error) { opts := map[string]any{ - "name": "Nano Banana 2 - " + prompt, + "name": "Nano Banana Pro - " + prompt, "text_prompt": prompt, - "aspect_ratio": aspectRatio, "num_images": 1, "image_size": imageSize, - "model": "gemini-3.1-flash-image-preview", + "model": "gemini-3-pro-image-preview", "exploreMode": false, "creationSource": "tool-mode", } @@ -127,7 +124,7 @@ func (c *Client) createImageTask(ctx context.Context, client tlsclient.HttpClien opts["reference_images"] = refImages } res, err := c.submitTask(ctx, client, token, teamID, map[string]any{ - "taskType": "gemini_3_1_flash_image", + "taskType": "workflow_gemini_image", "options": opts, "asTeamId": jsonNumberOrString(teamID), "sessionId": uuid.NewString(), diff --git a/backend/internal/service/v1.go b/backend/internal/service/v1.go index 86fa57a..70eee2d 100644 --- a/backend/internal/service/v1.go +++ b/backend/internal/service/v1.go @@ -555,7 +555,7 @@ func (s *V1Service) prepareImageExecution(ctx context.Context, principal *APIPri } imageBytes = b case "runway": - b, execErr := s.generateRunwayImage(genCtx, eventID, modelItem, in, aspectRatio, resolution) + b, execErr := s.generateRunwayImage(genCtx, eventID, modelItem, in, resolution) if execErr != nil { _ = s.refundIfNeeded(ctx, principal, eventID, price) _ = s.events.UpdateStatus(ctx, eventID, "failed", execErr.Error(), 0) @@ -2137,14 +2137,14 @@ func (s *V1Service) generateGrokVideo(ctx context.Context, eventID string, model return nil, "", lastErr } -// generateRunwayImage runs the Runway "Nano Banana 2" (gemini_3_1_flash_image) -// image pipeline across the runway pool. Unlike the video path it does NOT -// pre-deduct credits: it simply round-robins the pool and generates. Per ops -// decision an out-of-credits account is treated like a dead 401 — marked -// dead (status=disabled) and skipped — because Runway credits don't refill -// daily, so a "quota" mark (which the maintenance loop would revive) is wrong. -// Reference images (up to the model's max) are uploaded per attempt. -func (s *V1Service) generateRunwayImage(ctx context.Context, eventID string, modelItem *model.ModelConfig, in V1ImageRequest, aspectRatio, resolution string) ([]byte, error) { +// generateRunwayImage runs the Runway "Nano Banana Pro" (workflow_gemini_image / +// gemini-3-pro-image-preview) image pipeline across the runway pool. Unlike the +// video path it does NOT pre-deduct credits: it simply round-robins the pool and +// generates. Per ops decision an out-of-credits account is treated like a dead +// 401 — marked dead (status=disabled) and skipped — because Runway credits don't +// refill daily, so a "quota" mark (which the maintenance loop would revive) is +// wrong. Reference images (up to the model's max) are uploaded per attempt. +func (s *V1Service) generateRunwayImage(ctx context.Context, eventID string, modelItem *model.ModelConfig, in V1ImageRequest, resolution string) ([]byte, error) { if s.runway == nil { return nil, errors.New("runway client not configured") } @@ -2203,7 +2203,7 @@ func (s *V1Service) generateRunwayImage(ctx context.Context, eventID string, mod if token.Meta != nil { teamID = strings.TrimSpace(stringValue(token.Meta["team_id"])) } - d, _, genErr := s.runway.GenerateImage(ctx, token.Value, teamID, in.Prompt, aspectRatio, imageSize, refs) + d, _, genErr := s.runway.GenerateImage(ctx, token.Value, teamID, in.Prompt, imageSize, refs) if genErr == nil { _, _ = s.tokens.Update(ctx, "runway", token.ID, map[string]any{ "last_used_at": time.Now(),