Created
April 24, 2025 16:38
-
-
Save ParvinEyvazov/f929719759538782b094ce4a50ba163a to your computer and use it in GitHub Desktop.
OpenAI's new gpt-image-1 model implementation in Golang
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package api | |
import ( | |
"bytes" | |
"encoding/json" | |
"fmt" | |
"io" | |
"mime/multipart" | |
"net/http" | |
"net/textproto" | |
"github.com/gin-gonic/gin" | |
) | |
// ImageEditRequest represents the request body for image edit operations | |
type ImageEditRequest struct { | |
Images []string `json:"images"` // URLs to the images | |
Prompt string `json:"prompt"` // Prompt describing the desired edit | |
} | |
// OpenAIImageResponse represents the response from OpenAI image generation APIs | |
type OpenAIImageResponse struct { | |
Created int `json:"created"` | |
Data []struct { | |
URL string `json:"url,omitempty"` | |
B64JSON string `json:"b64_json,omitempty"` | |
RevisedPrompt string `json:"revised_prompt,omitempty"` | |
} `json:"data"` | |
Usage struct { | |
TotalTokens int `json:"total_tokens"` | |
InputTokens int `json:"input_tokens"` | |
OutputTokens int `json:"output_tokens"` | |
InputTokensDetails struct { | |
TextTokens int `json:"text_tokens"` | |
ImageTokens int `json:"image_tokens"` | |
} `json:"input_tokens_details"` | |
} `json:"usage"` | |
} | |
// GenerateActionFigureImage takes image URLs and a prompt, sends a request to OpenAI's image edits API, | |
// and returns the generated image as base64 data | |
func (server *Server) GenerateActionFigureImage(ctx *gin.Context) (string, error) { | |
var req ImageEditRequest | |
if err := ctx.ShouldBindJSON(&req); err != nil { | |
return "", fmt.Errorf("invalid request body: %v", err) | |
} | |
if len(req.Images) == 0 { | |
return "", fmt.Errorf("at least one image URL is required") | |
} | |
if req.Prompt == "" { | |
return "", fmt.Errorf("prompt is required") | |
} | |
apiKey := server.config.OpenAIAPIKey | |
if apiKey == "" { | |
return "", fmt.Errorf("OpenAI API key not configured") | |
} | |
// Create the multipart form data directly in memory | |
var reqBody bytes.Buffer | |
writer := multipart.NewWriter(&reqBody) | |
// Process each image URL | |
for i, imageURL := range req.Images { | |
fmt.Printf("Processing image from URL: %s\n", imageURL) | |
// Download the image | |
resp, err := http.Get(imageURL) | |
if err != nil { | |
return "", fmt.Errorf("failed to download image: %v", err) | |
} | |
defer resp.Body.Close() | |
if resp.StatusCode != http.StatusOK { | |
return "", fmt.Errorf("failed to download image, status code: %d", resp.StatusCode) | |
} | |
// Read image data | |
imageData, err := io.ReadAll(resp.Body) | |
if err != nil { | |
return "", fmt.Errorf("failed to read image data: %v", err) | |
} | |
// For gpt-image-1, we use image[] format | |
fieldName := fmt.Sprintf("image[%d]", i) | |
// Create part with explicit Content-Type header | |
h := make(textproto.MIMEHeader) | |
h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="image%d.jpeg"`, fieldName, i)) | |
// Explicitly set Content-Type to image/jpeg as in the Node.js workaround | |
h.Set("Content-Type", "image/jpeg") | |
part, err := writer.CreatePart(h) | |
if err != nil { | |
return "", fmt.Errorf("failed to create form part: %v", err) | |
} | |
if _, err := part.Write(imageData); err != nil { | |
return "", fmt.Errorf("failed to write image data: %v", err) | |
} | |
fmt.Printf("Added image %d with explicit Content-Type: image/jpeg\n", i) | |
} | |
// Add other fields | |
writer.WriteField("prompt", req.Prompt) | |
writer.WriteField("model", "gpt-image-1") | |
writer.WriteField("n", "1") | |
writer.WriteField("size", "1024x1024") | |
writer.WriteField("quality", "high") | |
// Close the writer | |
if err := writer.Close(); err != nil { | |
return "", fmt.Errorf("failed to close multipart writer: %v", err) | |
} | |
// Create the HTTP request | |
request, err := http.NewRequest("POST", "https://api.openai.com/v1/images/edits", &reqBody) | |
if err != nil { | |
return "", fmt.Errorf("failed to create HTTP request: %v", err) | |
} | |
// Set the headers | |
request.Header.Set("Content-Type", writer.FormDataContentType()) | |
request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", apiKey)) | |
// Send the request | |
client := &http.Client{} | |
response, err := client.Do(request) | |
if err != nil { | |
return "", fmt.Errorf("failed to send HTTP request: %v", err) | |
} | |
defer response.Body.Close() | |
// Read and parse the response | |
respBody, err := io.ReadAll(response.Body) | |
if err != nil { | |
return "", fmt.Errorf("failed to read response body: %v", err) | |
} | |
// Log full response for debugging | |
fmt.Println("Response status:", response.Status) | |
fmt.Println("Response body:", string(respBody)) | |
// Check for error status | |
if response.StatusCode != http.StatusOK { | |
var errorResp struct { | |
Error struct { | |
Message string `json:"message"` | |
Type string `json:"type"` | |
} `json:"error"` | |
} | |
if err := json.Unmarshal(respBody, &errorResp); err == nil && errorResp.Error.Message != "" { | |
return "", fmt.Errorf("API error: %s", errorResp.Error.Message) | |
} | |
return "", fmt.Errorf("API error: status %d", response.StatusCode) | |
} | |
// Parse response | |
var openAIResp OpenAIImageResponse | |
if err := json.Unmarshal(respBody, &openAIResp); err != nil { | |
return "", fmt.Errorf("failed to parse response: %v", err) | |
} | |
if len(openAIResp.Data) == 0 { | |
return "", fmt.Errorf("no image generated") | |
} | |
// gpt-image-1 always returns base64-encoded images | |
return openAIResp.Data[0].B64JSON, nil | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment