Skip to content

Instantly share code, notes, and snippets.

@ParvinEyvazov
Created April 24, 2025 16:38
Show Gist options
  • Save ParvinEyvazov/f929719759538782b094ce4a50ba163a to your computer and use it in GitHub Desktop.
Save ParvinEyvazov/f929719759538782b094ce4a50ba163a to your computer and use it in GitHub Desktop.
OpenAI's new gpt-image-1 model implementation in Golang
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