feat(leonardo): 接入视频通道并新增 seedance-2.0/minimax-h3 模型
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"mime/multipart"
|
||||
"strings"
|
||||
@@ -51,24 +52,25 @@ const mUploadImage = `mutation UploadImage($uploadImageInput: UploadImageInput!)
|
||||
// uploadInitImage uploads a reference (init) image for image-to-image: it asks
|
||||
// Leonardo for a presigned S3 POST, uploads the bytes, and returns the upload id
|
||||
// to reference in the Generate request's image_reference guidance.
|
||||
func (c *Client) uploadInitImage(ctx context.Context, accessToken string, img []byte) (string, error) {
|
||||
func (c *Client) uploadInitImage(ctx context.Context, cookie string, img []byte) (string, error) {
|
||||
return c.uploadAsset(ctx, cookie, "png", img)
|
||||
}
|
||||
|
||||
// uploadAsset uploads one reference asset (extension png / mp3 / mp4 …) through
|
||||
// the same UploadImage presigned-S3 flow images use, and returns its upload id.
|
||||
func (c *Client) uploadAsset(ctx context.Context, cookie, extension string, asset []byte) (string, error) {
|
||||
extension = strings.TrimPrefix(strings.ToLower(strings.TrimSpace(extension)), ".")
|
||||
if extension == "" {
|
||||
extension = "png"
|
||||
}
|
||||
payload, _ := json.Marshal(map[string]any{
|
||||
"operationName": "UploadImage",
|
||||
"query": mUploadImage,
|
||||
"variables": map[string]any{"uploadImageInput": map[string]any{"uploadType": "INIT", "extension": "png"}},
|
||||
"variables": map[string]any{"uploadImageInput": map[string]any{"uploadType": "INIT", "extension": extension}},
|
||||
})
|
||||
body, status, err := c.graphqlP(ctx, accessToken, payload, false)
|
||||
body, err := c.callGraphQL(ctx, cookie, payload, false, "upload-init")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("%w: upload-init: %s", ErrTemporaryUpstream, err.Error())
|
||||
}
|
||||
if status == 401 || status == 403 {
|
||||
return "", ErrAuth
|
||||
}
|
||||
if status != 200 {
|
||||
return "", fmt.Errorf("%w: upload-init http %d: %s", ErrTemporaryUpstream, status, clip(body, 160))
|
||||
}
|
||||
if e := graphqlError(body); e != nil {
|
||||
return "", e
|
||||
return "", err
|
||||
}
|
||||
var ur struct {
|
||||
Data struct {
|
||||
@@ -97,11 +99,11 @@ func (c *Client) uploadInitImage(ctx context.Context, accessToken string, img []
|
||||
for k, v := range fields {
|
||||
_ = w.WriteField(k, v)
|
||||
}
|
||||
fw, err := w.CreateFormFile("file", "image.png")
|
||||
fw, err := w.CreateFormFile("file", "asset."+extension)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if _, err := fw.Write(img); err != nil {
|
||||
if _, err := fw.Write(asset); err != nil {
|
||||
return "", err
|
||||
}
|
||||
_ = w.Close()
|
||||
@@ -154,7 +156,7 @@ func (c *Client) GenerateImage(ctx context.Context, cookie, model, prompt string
|
||||
if len(img) == 0 {
|
||||
continue
|
||||
}
|
||||
uploadID, upErr := c.uploadInitImage(ctx, sess.AccessToken, img)
|
||||
uploadID, upErr := c.uploadInitImage(ctx, cookie, img)
|
||||
if upErr != nil {
|
||||
return nil, nil, upErr
|
||||
}
|
||||
@@ -192,18 +194,9 @@ func (c *Client) GenerateImage(ctx context.Context, cookie, model, prompt string
|
||||
},
|
||||
}
|
||||
payload, _ := json.Marshal(genReq)
|
||||
body, status, err := c.graphql(ctx, sess.AccessToken, payload)
|
||||
body, err := c.callGraphQL(ctx, cookie, payload, true, "generate")
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("%w: %s", ErrTemporaryUpstream, err.Error())
|
||||
}
|
||||
if status == 401 || status == 403 {
|
||||
return nil, nil, ErrAuth
|
||||
}
|
||||
if status != 200 {
|
||||
return nil, nil, fmt.Errorf("%w: generate http %d: %s", ErrTemporaryUpstream, status, clip(body, 200))
|
||||
}
|
||||
if e := graphqlError(body); e != nil {
|
||||
return nil, nil, e
|
||||
return nil, nil, err
|
||||
}
|
||||
var genResp struct {
|
||||
Data struct {
|
||||
@@ -221,7 +214,7 @@ func (c *Client) GenerateImage(ctx context.Context, cookie, model, prompt string
|
||||
}
|
||||
|
||||
// 2. poll until COMPLETE, then read the image url.
|
||||
imageURL, err := c.pollImage(ctx, sess.AccessToken, genID)
|
||||
imageURL, err := c.pollImage(ctx, cookie, genID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -244,7 +237,7 @@ func (c *Client) GenerateImage(ctx context.Context, cookie, model, prompt string
|
||||
|
||||
// pollImage polls one generation until it reports COMPLETE (returning the first
|
||||
// image url) or FAILED (error). Honors ctx cancellation / deadline.
|
||||
func (c *Client) pollImage(ctx context.Context, accessToken, genID string) (string, error) {
|
||||
func (c *Client) pollImage(ctx context.Context, cookie, genID string) (string, error) {
|
||||
payload, _ := json.Marshal(map[string]any{
|
||||
"operationName": "GenerationImages",
|
||||
"query": qGenerationImages,
|
||||
@@ -264,14 +257,12 @@ func (c *Client) pollImage(ctx context.Context, accessToken, genID string) (stri
|
||||
}
|
||||
|
||||
for {
|
||||
body, status, err := c.graphqlP(ctx, accessToken, payload, false)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("%w: poll: %s", ErrTemporaryUpstream, err.Error())
|
||||
body, err := c.callGraphQL(ctx, cookie, payload, false, "poll")
|
||||
if errors.Is(err, ErrAuth) {
|
||||
return "", err
|
||||
}
|
||||
if status == 401 || status == 403 {
|
||||
return "", ErrAuth
|
||||
}
|
||||
if status == 200 {
|
||||
// 其它错误(含上游临时抖动)不中断轮询,等 deadline 再判超时。
|
||||
if err == nil {
|
||||
var pr struct {
|
||||
Data struct {
|
||||
Generations []struct {
|
||||
|
||||
Reference in New Issue
Block a user