mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-09-24 16:05:44 +08:00
fix: convert grok image edit uploads
This commit is contained in:
@@ -292,6 +292,11 @@ func (s *OpenAIGatewayService) ForwardGrokMedia(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
body, contentType, err = prepareGrokMediaForwardBody(endpoint, body, contentType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var bodyReader io.Reader
|
||||
if endpoint.RequiresRequestBody() {
|
||||
bodyReader = bytes.NewReader(body)
|
||||
@@ -356,6 +361,69 @@ func (s *OpenAIGatewayService) ForwardGrokMedia(
|
||||
}, nil
|
||||
}
|
||||
|
||||
func prepareGrokMediaForwardBody(endpoint GrokMediaEndpoint, body []byte, contentType string) ([]byte, string, error) {
|
||||
if endpoint != GrokMediaEndpointImagesEdits || gjson.ValidBytes(body) {
|
||||
return body, contentType, nil
|
||||
}
|
||||
mediaType, _, err := mime.ParseMediaType(strings.TrimSpace(contentType))
|
||||
if err != nil || !strings.EqualFold(mediaType, "multipart/form-data") {
|
||||
return body, contentType, nil
|
||||
}
|
||||
|
||||
info := ParseGrokMediaRequest(contentType, body)
|
||||
payload := make(map[string]any)
|
||||
if info.Model != "" {
|
||||
payload["model"] = info.Model
|
||||
}
|
||||
if info.Prompt != "" {
|
||||
payload["prompt"] = info.Prompt
|
||||
}
|
||||
if info.N > 1 {
|
||||
payload["n"] = info.N
|
||||
}
|
||||
if info.Size != "" {
|
||||
payload["size"] = info.Size
|
||||
}
|
||||
|
||||
images := make([]map[string]string, 0, len(info.InputImageURLs)+len(info.Uploads))
|
||||
for _, imageURL := range info.InputImageURLs {
|
||||
if imageURL = strings.TrimSpace(imageURL); imageURL != "" {
|
||||
images = append(images, map[string]string{"image_url": imageURL})
|
||||
}
|
||||
}
|
||||
for _, upload := range info.Uploads {
|
||||
dataURL, err := openAIImageUploadToDataURL(upload)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
images = append(images, map[string]string{"image_url": dataURL})
|
||||
}
|
||||
if len(images) > 0 {
|
||||
payload["image"] = images[0]
|
||||
if len(images) > 1 {
|
||||
payload["images"] = images
|
||||
}
|
||||
}
|
||||
|
||||
maskImageURL := strings.TrimSpace(info.MaskImageURL)
|
||||
if info.MaskUpload != nil {
|
||||
dataURL, err := openAIImageUploadToDataURL(*info.MaskUpload)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
maskImageURL = dataURL
|
||||
}
|
||||
if maskImageURL != "" {
|
||||
payload["mask"] = map[string]string{"image_url": maskImageURL}
|
||||
}
|
||||
|
||||
out, err := marshalOpenAIUpstreamJSON(payload)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return out, "application/json", nil
|
||||
}
|
||||
|
||||
type grokMediaUsageMetadata struct {
|
||||
ResponseID string
|
||||
Usage OpenAIUsage
|
||||
|
||||
@@ -229,6 +229,58 @@ func TestForwardGrokMediaImagesGenerationPassthrough(t *testing.T) {
|
||||
require.Equal(t, ImageBillingSize2K, result.ImageSize)
|
||||
}
|
||||
|
||||
func TestForwardGrokMediaImagesEditMultipartConvertsToJSON(t *testing.T) {
|
||||
t.Setenv(xai.EnvAllowUnsafeURLOverrides, "true")
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
var buf bytes.Buffer
|
||||
writer := multipart.NewWriter(&buf)
|
||||
require.NoError(t, writer.WriteField("model", "grok-imagine-edit"))
|
||||
require.NoError(t, writer.WriteField("prompt", "edit this private image"))
|
||||
partHeader := textproto.MIMEHeader{}
|
||||
partHeader.Set("Content-Disposition", `form-data; name="image"; filename="input.png"`)
|
||||
partHeader.Set("Content-Type", "image/png")
|
||||
part, err := writer.CreatePart(partHeader)
|
||||
require.NoError(t, err)
|
||||
_, err = part.Write([]byte{0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, writer.Close())
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(recorder)
|
||||
c.Request = httptest.NewRequest(http.MethodPost, "/v1/images/edits", bytes.NewReader(buf.Bytes()))
|
||||
c.Request.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
|
||||
account := &Account{
|
||||
ID: 62,
|
||||
Name: "grok",
|
||||
Platform: PlatformGrok,
|
||||
Type: AccountTypeAPIKey,
|
||||
Concurrency: 1,
|
||||
Credentials: map[string]any{
|
||||
"api_key": "api-key",
|
||||
"base_url": "https://xai.test/v1",
|
||||
},
|
||||
}
|
||||
upstream := &httpUpstreamRecorder{resp: &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Header: http.Header{
|
||||
"Content-Type": []string{"application/json"},
|
||||
},
|
||||
Body: io.NopCloser(strings.NewReader(`{"data":[]}`)),
|
||||
}}
|
||||
svc := &OpenAIGatewayService{httpUpstream: upstream}
|
||||
|
||||
_, err = svc.ForwardGrokMedia(context.Background(), c, account, GrokMediaEndpointImagesEdits, "", buf.Bytes(), writer.FormDataContentType())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "https://xai.test/v1/images/edits", upstream.lastReq.URL.String())
|
||||
require.Equal(t, "application/json", upstream.lastReq.Header.Get("Content-Type"))
|
||||
require.True(t, json.Valid(upstream.lastBody))
|
||||
require.Equal(t, "grok-imagine-edit", gjson.GetBytes(upstream.lastBody, "model").String())
|
||||
require.Equal(t, "edit this private image", gjson.GetBytes(upstream.lastBody, "prompt").String())
|
||||
require.True(t, strings.HasPrefix(gjson.GetBytes(upstream.lastBody, "image.image_url").String(), "data:image/png;base64,"))
|
||||
}
|
||||
|
||||
func TestForwardGrokMediaVideoGenerationReturnsUsageAndResponseID(t *testing.T) {
|
||||
t.Setenv(xai.EnvAllowUnsafeURLOverrides, "true")
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
Reference in New Issue
Block a user