mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-04-04 09:15:55 +00:00
Compare commits
10 Commits
v0.9.26
...
v0.9.27-pa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f6e7a2344b | ||
|
|
3257723a55 | ||
|
|
b19b2d62df | ||
|
|
f9c8624f2c | ||
|
|
6c8253156b | ||
|
|
a66b314f5b | ||
|
|
e29ff0060d | ||
|
|
d4a2c2ab54 | ||
|
|
ded463ee57 | ||
|
|
e337936227 |
@@ -169,7 +169,7 @@ type ImageResponse struct {
|
||||
Extra any `json:"extra,omitempty"`
|
||||
}
|
||||
type ImageData struct {
|
||||
Url string `json:"url,omitempty"`
|
||||
B64Json string `json:"b64_json,omitempty"`
|
||||
RevisedPrompt string `json:"revised_prompt,omitempty"`
|
||||
Url string `json:"url"`
|
||||
B64Json string `json:"b64_json"`
|
||||
RevisedPrompt string `json:"revised_prompt"`
|
||||
}
|
||||
|
||||
@@ -27,8 +27,6 @@ import (
|
||||
func SetupApiRequestHeader(info *common.RelayInfo, c *gin.Context, req *http.Header) {
|
||||
if info.RelayMode == constant.RelayModeAudioTranscription || info.RelayMode == constant.RelayModeAudioTranslation {
|
||||
// multipart/form-data
|
||||
} else if info.RelayMode == constant.RelayModeImagesEdits {
|
||||
// multipart/form-data
|
||||
} else if info.RelayMode == constant.RelayModeRealtime {
|
||||
// websocket
|
||||
} else {
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
package gemini
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/QuantumNous/new-api/dto"
|
||||
@@ -57,139 +55,9 @@ func (a *Adaptor) ConvertAudioRequest(c *gin.Context, info *relaycommon.RelayInf
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
|
||||
type ImageConfig struct {
|
||||
AspectRatio string `json:"aspectRatio,omitempty"`
|
||||
ImageSize string `json:"imageSize,omitempty"`
|
||||
}
|
||||
|
||||
type SizeMapping struct {
|
||||
AspectRatio string
|
||||
ImageSize string
|
||||
}
|
||||
|
||||
type QualityMapping struct {
|
||||
Standard string
|
||||
HD string
|
||||
High string
|
||||
FourK string
|
||||
Auto string
|
||||
}
|
||||
|
||||
func getImageSizeMapping() QualityMapping {
|
||||
return QualityMapping{
|
||||
Standard: "1K",
|
||||
HD: "2K",
|
||||
High: "2K",
|
||||
FourK: "4K",
|
||||
Auto: "1K",
|
||||
}
|
||||
}
|
||||
|
||||
func getSizeMappings() map[string]SizeMapping {
|
||||
return map[string]SizeMapping{
|
||||
// Gemini 2.5 Flash Image - default 1K resolutions
|
||||
"1024x1024": {AspectRatio: "1:1", ImageSize: ""},
|
||||
"832x1248": {AspectRatio: "2:3", ImageSize: ""},
|
||||
"1248x832": {AspectRatio: "3:2", ImageSize: ""},
|
||||
"864x1184": {AspectRatio: "3:4", ImageSize: ""},
|
||||
"1184x864": {AspectRatio: "4:3", ImageSize: ""},
|
||||
"896x1152": {AspectRatio: "4:5", ImageSize: ""},
|
||||
"1152x896": {AspectRatio: "5:4", ImageSize: ""},
|
||||
"768x1344": {AspectRatio: "9:16", ImageSize: ""},
|
||||
"1344x768": {AspectRatio: "16:9", ImageSize: ""},
|
||||
"1536x672": {AspectRatio: "21:9", ImageSize: ""},
|
||||
|
||||
// Gemini 3 Pro Image Preview resolutions
|
||||
"1536x1024": {AspectRatio: "3:2", ImageSize: ""},
|
||||
"1024x1536": {AspectRatio: "2:3", ImageSize: ""},
|
||||
"1024x1792": {AspectRatio: "9:16", ImageSize: ""},
|
||||
"1792x1024": {AspectRatio: "16:9", ImageSize: ""},
|
||||
"2048x2048": {AspectRatio: "1:1", ImageSize: "2K"},
|
||||
"4096x4096": {AspectRatio: "1:1", ImageSize: "4K"},
|
||||
}
|
||||
}
|
||||
|
||||
func processSizeParameters(size, quality string) ImageConfig {
|
||||
config := ImageConfig{} // 默认为空值
|
||||
|
||||
if size != "" {
|
||||
if strings.Contains(size, ":") {
|
||||
config.AspectRatio = size // 直接设置,不与默认值比较
|
||||
} else {
|
||||
if mapping, exists := getSizeMappings()[size]; exists {
|
||||
if mapping.AspectRatio != "" {
|
||||
config.AspectRatio = mapping.AspectRatio
|
||||
}
|
||||
if mapping.ImageSize != "" {
|
||||
config.ImageSize = mapping.ImageSize
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if quality != "" {
|
||||
qualityMapping := getImageSizeMapping()
|
||||
switch strings.ToLower(strings.TrimSpace(quality)) {
|
||||
case "hd", "high":
|
||||
config.ImageSize = qualityMapping.HD
|
||||
case "4k":
|
||||
config.ImageSize = qualityMapping.FourK
|
||||
case "standard", "medium", "low", "auto", "1k":
|
||||
config.ImageSize = qualityMapping.Standard
|
||||
}
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
func (a *Adaptor) ConvertImageRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.ImageRequest) (any, error) {
|
||||
if model_setting.IsGeminiModelSupportImagine(info.UpstreamModelName) {
|
||||
var content any
|
||||
if base64Data, err := relaycommon.GetImageBase64sFromForm(c); err == nil {
|
||||
content = []any{
|
||||
dto.MediaContent{
|
||||
Type: dto.ContentTypeText,
|
||||
Text: request.Prompt,
|
||||
},
|
||||
dto.MediaContent{
|
||||
Type: dto.ContentTypeFile,
|
||||
File: &dto.MessageFile{
|
||||
FileData: base64Data.String(),
|
||||
},
|
||||
},
|
||||
}
|
||||
} else {
|
||||
content = request.Prompt
|
||||
}
|
||||
|
||||
chatRequest := dto.GeneralOpenAIRequest{
|
||||
Model: request.Model,
|
||||
Messages: []dto.Message{
|
||||
{Role: "user", Content: content},
|
||||
},
|
||||
N: int(request.N),
|
||||
}
|
||||
|
||||
config := processSizeParameters(strings.TrimSpace(request.Size), request.Quality)
|
||||
|
||||
// 兼容 nano-banana 传quality[imageSize]会报错 An internal error has occurred. Please retry or report in https://developers.generativeai.google/guide/troubleshooting
|
||||
if slices.Contains([]string{"nano-banana", "gemini-2.5-flash-image"}, info.UpstreamModelName) {
|
||||
config.ImageSize = ""
|
||||
}
|
||||
|
||||
googleGenerationConfig := map[string]interface{}{
|
||||
"responseModalities": []string{"TEXT", "IMAGE"},
|
||||
"imageConfig": config,
|
||||
}
|
||||
|
||||
extraBody := map[string]interface{}{
|
||||
"google": map[string]interface{}{
|
||||
"generationConfig": googleGenerationConfig,
|
||||
},
|
||||
}
|
||||
chatRequest.ExtraBody, _ = json.Marshal(extraBody)
|
||||
|
||||
return a.ConvertOpenAIRequest(c, info, &chatRequest)
|
||||
if !strings.HasPrefix(info.UpstreamModelName, "imagen") {
|
||||
return nil, errors.New("not supported model for image generation")
|
||||
}
|
||||
|
||||
// convert size to aspect ratio but allow user to specify aspect ratio
|
||||
@@ -199,8 +67,17 @@ func (a *Adaptor) ConvertImageRequest(c *gin.Context, info *relaycommon.RelayInf
|
||||
if strings.Contains(size, ":") {
|
||||
aspectRatio = size
|
||||
} else {
|
||||
if mapping, exists := getSizeMappings()[size]; exists && mapping.AspectRatio != "" {
|
||||
aspectRatio = mapping.AspectRatio
|
||||
switch size {
|
||||
case "256x256", "512x512", "1024x1024":
|
||||
aspectRatio = "1:1"
|
||||
case "1536x1024":
|
||||
aspectRatio = "3:2"
|
||||
case "1024x1536":
|
||||
aspectRatio = "2:3"
|
||||
case "1024x1792":
|
||||
aspectRatio = "9:16"
|
||||
case "1792x1024":
|
||||
aspectRatio = "16:9"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -381,10 +258,6 @@ func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, info *relaycom
|
||||
return GeminiImageHandler(c, info, resp)
|
||||
}
|
||||
|
||||
if model_setting.IsGeminiModelSupportImagine(info.UpstreamModelName) {
|
||||
return ChatImageHandler(c, info, resp)
|
||||
}
|
||||
|
||||
// check if the model is an embedding model
|
||||
if strings.HasPrefix(info.UpstreamModelName, "text-embedding") ||
|
||||
strings.HasPrefix(info.UpstreamModelName, "embedding") ||
|
||||
|
||||
@@ -183,7 +183,7 @@ func ThinkingAdaptor(geminiRequest *dto.GeminiChatRequest, info *relaycommon.Rel
|
||||
}
|
||||
|
||||
// Setting safety to the lowest possible values since Gemini is already powerless enough
|
||||
func CovertOpenAI2Gemini(c *gin.Context, textRequest dto.GeneralOpenAIRequest, info *relaycommon.RelayInfo, base64Data ...*relaycommon.Base64Data) (*dto.GeminiChatRequest, error) {
|
||||
func CovertOpenAI2Gemini(c *gin.Context, textRequest dto.GeneralOpenAIRequest, info *relaycommon.RelayInfo) (*dto.GeminiChatRequest, error) {
|
||||
|
||||
geminiRequest := dto.GeminiChatRequest{
|
||||
Contents: make([]dto.GeminiChatContent, 0, len(textRequest.Messages)),
|
||||
@@ -239,16 +239,6 @@ func CovertOpenAI2Gemini(c *gin.Context, textRequest dto.GeneralOpenAIRequest, i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if generationConfig, ok := googleBody["generationConfig"].(map[string]any); ok {
|
||||
generationConfigBytes, err := json.Marshal(generationConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal generationConfig: %w", err)
|
||||
}
|
||||
if err := json.Unmarshal(generationConfigBytes, &geminiRequest.GenerationConfig); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal generationConfig: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -464,11 +454,10 @@ func CovertOpenAI2Gemini(c *gin.Context, textRequest dto.GeneralOpenAIRequest, i
|
||||
})
|
||||
}
|
||||
} else if part.Type == dto.ContentTypeFile {
|
||||
file := part.GetFile()
|
||||
if file.FileId != "" {
|
||||
if part.GetFile().FileId != "" {
|
||||
return nil, fmt.Errorf("only base64 file is supported in gemini")
|
||||
}
|
||||
format, base64String, err := service.DecodeBase64FileData(file.FileData)
|
||||
format, base64String, err := service.DecodeBase64FileData(part.GetFile().FileData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decode base64 file data failed: %s", err.Error())
|
||||
}
|
||||
@@ -1275,70 +1264,3 @@ func GeminiImageHandler(c *gin.Context, info *relaycommon.RelayInfo, resp *http.
|
||||
|
||||
return usage, nil
|
||||
}
|
||||
|
||||
func convertToOaiImageResponse(geminiResponse *dto.GeminiChatResponse) (*dto.ImageResponse, error) {
|
||||
openAIResponse := &dto.ImageResponse{
|
||||
Created: common.GetTimestamp(),
|
||||
Data: make([]dto.ImageData, 0),
|
||||
}
|
||||
|
||||
// extract images from candidates' inlineData
|
||||
for _, candidate := range geminiResponse.Candidates {
|
||||
for _, part := range candidate.Content.Parts {
|
||||
if part.InlineData != nil && strings.HasPrefix(part.InlineData.MimeType, "image") {
|
||||
openAIResponse.Data = append(openAIResponse.Data, dto.ImageData{
|
||||
B64Json: part.InlineData.Data,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(openAIResponse.Data) == 0 {
|
||||
return nil, errors.New("no images found in response")
|
||||
}
|
||||
|
||||
return openAIResponse, nil
|
||||
}
|
||||
|
||||
func ChatImageHandler(c *gin.Context, info *relaycommon.RelayInfo, resp *http.Response) (*dto.Usage, *types.NewAPIError) {
|
||||
responseBody, readErr := io.ReadAll(resp.Body)
|
||||
if readErr != nil {
|
||||
return nil, types.NewOpenAIError(readErr, types.ErrorCodeBadResponseBody, http.StatusInternalServerError)
|
||||
}
|
||||
service.CloseResponseBodyGracefully(resp)
|
||||
|
||||
if common.DebugEnabled {
|
||||
println("ChatImageHandler response:", string(responseBody))
|
||||
}
|
||||
|
||||
var geminiResponse dto.GeminiChatResponse
|
||||
if jsonErr := common.Unmarshal(responseBody, &geminiResponse); jsonErr != nil {
|
||||
return nil, types.NewOpenAIError(jsonErr, types.ErrorCodeBadResponseBody, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
if len(geminiResponse.Candidates) == 0 {
|
||||
return nil, types.NewOpenAIError(errors.New("no images generated"), types.ErrorCodeBadResponseBody, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
openAIResponse, err := convertToOaiImageResponse(&geminiResponse)
|
||||
if err != nil {
|
||||
return nil, types.NewOpenAIError(err, types.ErrorCodeBadResponseBody, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
jsonResponse, jsonErr := json.Marshal(openAIResponse)
|
||||
if jsonErr != nil {
|
||||
return nil, types.NewError(jsonErr, types.ErrorCodeBadResponseBody)
|
||||
}
|
||||
|
||||
c.Writer.Header().Set("Content-Type", "application/json")
|
||||
c.Writer.WriteHeader(resp.StatusCode)
|
||||
_, _ = c.Writer.Write(jsonResponse)
|
||||
|
||||
usage := &dto.Usage{
|
||||
PromptTokens: geminiResponse.UsageMetadata.PromptTokenCount,
|
||||
CompletionTokens: geminiResponse.UsageMetadata.CandidatesTokenCount,
|
||||
TotalTokens: geminiResponse.UsageMetadata.TotalTokenCount,
|
||||
}
|
||||
|
||||
return usage, nil
|
||||
}
|
||||
|
||||
@@ -24,9 +24,13 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// VideoGenerationConfig represents the video generation configuration
|
||||
// ============================
|
||||
// Request / Response structures
|
||||
// ============================
|
||||
|
||||
// GeminiVideoGenerationConfig represents the video generation configuration
|
||||
// Based on: https://ai.google.dev/gemini-api/docs/video
|
||||
type VideoGenerationConfig struct {
|
||||
type GeminiVideoGenerationConfig struct {
|
||||
AspectRatio string `json:"aspectRatio,omitempty"` // "16:9" or "9:16"
|
||||
DurationSeconds float64 `json:"durationSeconds,omitempty"` // 4, 6, or 8 (as number)
|
||||
NegativePrompt string `json:"negativePrompt,omitempty"` // unwanted elements
|
||||
@@ -34,21 +38,15 @@ type VideoGenerationConfig struct {
|
||||
Resolution string `json:"resolution,omitempty"` // video resolution
|
||||
}
|
||||
|
||||
type Image struct {
|
||||
BytesBase64Encoded string `json:"bytesBase64Encoded,omitempty"`
|
||||
MimeType string `json:"mimeType,omitempty"`
|
||||
// GeminiVideoRequest represents a single video generation instance
|
||||
type GeminiVideoRequest struct {
|
||||
Prompt string `json:"prompt"`
|
||||
}
|
||||
|
||||
type VideoRequest struct {
|
||||
Prompt string `json:"prompt"`
|
||||
Image *Image `json:"image,omitempty"`
|
||||
LastFrame *Image `json:"lastFrame,omitempty"`
|
||||
}
|
||||
|
||||
// VideoPayload represents the complete video generation request payload
|
||||
type VideoPayload struct {
|
||||
Instances []VideoRequest `json:"instances"`
|
||||
Parameters VideoGenerationConfig `json:"parameters,omitempty"`
|
||||
// GeminiVideoPayload represents the complete video generation request payload
|
||||
type GeminiVideoPayload struct {
|
||||
Instances []GeminiVideoRequest `json:"instances"`
|
||||
Parameters GeminiVideoGenerationConfig `json:"parameters,omitempty"`
|
||||
}
|
||||
|
||||
type submitResponse struct {
|
||||
@@ -77,8 +75,6 @@ type operationResponse struct {
|
||||
URI string `json:"uri"`
|
||||
} `json:"video"`
|
||||
} `json:"generatedSamples"`
|
||||
RaiMediaFilteredCount int `json:"raiMediaFilteredCount"`
|
||||
RaiMediaFilteredReasons []string `json:"raiMediaFilteredReasons"`
|
||||
} `json:"generateVideoResponse"`
|
||||
} `json:"response"`
|
||||
Error struct {
|
||||
@@ -104,7 +100,8 @@ func (a *TaskAdaptor) Init(info *relaycommon.RelayInfo) {
|
||||
|
||||
// ValidateRequestAndSetAction parses body, validates fields and sets default action.
|
||||
func (a *TaskAdaptor) ValidateRequestAndSetAction(c *gin.Context, info *relaycommon.RelayInfo) (taskErr *dto.TaskError) {
|
||||
return relaycommon.ValidateBasicTaskRequest(c, info, constant.TaskActionGenerate)
|
||||
// Use the standard validation method for TaskSubmitReq
|
||||
return relaycommon.ValidateBasicTaskRequest(c, info, constant.TaskActionTextGenerate)
|
||||
}
|
||||
|
||||
// BuildRequestURL constructs the upstream URL.
|
||||
@@ -140,21 +137,13 @@ func (a *TaskAdaptor) BuildRequestBody(c *gin.Context, info *relaycommon.RelayIn
|
||||
}
|
||||
|
||||
// Create structured video generation request
|
||||
body := VideoPayload{
|
||||
Instances: []VideoRequest{
|
||||
body := GeminiVideoPayload{
|
||||
Instances: []GeminiVideoRequest{
|
||||
{Prompt: req.Prompt},
|
||||
},
|
||||
Parameters: VideoGenerationConfig{},
|
||||
Parameters: GeminiVideoGenerationConfig{},
|
||||
}
|
||||
|
||||
if len(req.Images) > 0 {
|
||||
body.Instances[0].Image = a.convertImage(req.Images[0])
|
||||
}
|
||||
if len(req.Images) > 1 {
|
||||
body.Instances[0].LastFrame = a.convertImage(req.Images[1])
|
||||
}
|
||||
|
||||
// Parse metadata for additional configuration
|
||||
metadata := req.Metadata
|
||||
medaBytes, err := json.Marshal(metadata)
|
||||
if err != nil {
|
||||
@@ -258,19 +247,20 @@ func (a *TaskAdaptor) ParseTaskResult(respBody []byte) (*relaycommon.TaskInfo, e
|
||||
return ti, nil
|
||||
}
|
||||
|
||||
if len(op.Response.GenerateVideoResponse.GeneratedSamples) == 0 {
|
||||
ti.Status = model.TaskStatusFailure
|
||||
ti.Reason = fmt.Sprintf("no generated video url found: %s", strings.Join(op.Response.GenerateVideoResponse.RaiMediaFilteredReasons, "; "))
|
||||
} else {
|
||||
if uri := op.Response.GenerateVideoResponse.GeneratedSamples[0].Video.URI; uri != "" {
|
||||
ti.RemoteUrl = uri
|
||||
}
|
||||
ti.Status = model.TaskStatusSuccess
|
||||
}
|
||||
ti.Status = model.TaskStatusSuccess
|
||||
ti.Progress = "100%"
|
||||
|
||||
taskID := encodeLocalTaskID(op.Name)
|
||||
ti.TaskID = taskID
|
||||
ti.Url = fmt.Sprintf("%s/v1/videos/%s/content", system_setting.ServerAddress, taskID)
|
||||
|
||||
// Extract URL from generateVideoResponse if available
|
||||
if len(op.Response.GenerateVideoResponse.GeneratedSamples) > 0 {
|
||||
if uri := op.Response.GenerateVideoResponse.GeneratedSamples[0].Video.URI; uri != "" {
|
||||
ti.RemoteUrl = uri
|
||||
}
|
||||
}
|
||||
|
||||
return ti, nil
|
||||
}
|
||||
|
||||
@@ -299,30 +289,6 @@ func (a *TaskAdaptor) ConvertToOpenAIVideo(task *model.Task) ([]byte, error) {
|
||||
return common.Marshal(video)
|
||||
}
|
||||
|
||||
func (a *TaskAdaptor) convertImage(imageStr string) *Image {
|
||||
if strings.TrimSpace(imageStr) == "" {
|
||||
return nil
|
||||
}
|
||||
img := &Image{
|
||||
MimeType: "image/png",
|
||||
BytesBase64Encoded: imageStr,
|
||||
}
|
||||
if strings.HasPrefix(imageStr, "data:image/") {
|
||||
parts := strings.Split(imageStr, ";base64,")
|
||||
if len(parts) == 2 {
|
||||
img.MimeType = strings.TrimPrefix(parts[0], "data:")
|
||||
img.BytesBase64Encoded = parts[1]
|
||||
}
|
||||
} else if strings.HasPrefix(imageStr, "http") {
|
||||
mimeType, data, err := service.GetImageFromUrl(imageStr)
|
||||
if err == nil {
|
||||
img.MimeType = mimeType
|
||||
img.BytesBase64Encoded = data
|
||||
}
|
||||
}
|
||||
return img
|
||||
}
|
||||
|
||||
// ============================
|
||||
// helpers
|
||||
// ============================
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -229,54 +226,3 @@ func ValidateBasicTaskRequest(c *gin.Context, info *RelayInfo, action string) *d
|
||||
storeTaskRequest(c, info, action, req)
|
||||
return nil
|
||||
}
|
||||
func GetImagesBase64sFromForm(c *gin.Context) ([]*Base64Data, error) {
|
||||
return GetBase64sFromForm(c, "image")
|
||||
}
|
||||
func GetImageBase64sFromForm(c *gin.Context) (*Base64Data, error) {
|
||||
base64s, err := GetImagesBase64sFromForm(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return base64s[0], nil
|
||||
}
|
||||
|
||||
type Base64Data struct {
|
||||
MimeType string
|
||||
Data string
|
||||
}
|
||||
|
||||
func (m Base64Data) String() string {
|
||||
return fmt.Sprintf("data:%s;base64,%s", m.MimeType, m.Data)
|
||||
}
|
||||
func GetBase64sFromForm(c *gin.Context, fieldName string) ([]*Base64Data, error) {
|
||||
mf := c.Request.MultipartForm
|
||||
if mf == nil {
|
||||
if _, err := c.MultipartForm(); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse image edit form request: %w", err)
|
||||
}
|
||||
mf = c.Request.MultipartForm
|
||||
}
|
||||
imageFiles, exists := mf.File[fieldName]
|
||||
if !exists || len(imageFiles) == 0 {
|
||||
return nil, errors.New("field " + fieldName + " is not found or empty")
|
||||
}
|
||||
var imageBase64s []*Base64Data
|
||||
for _, file := range imageFiles {
|
||||
image, err := file.Open()
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to open image file")
|
||||
}
|
||||
defer image.Close()
|
||||
imageData, err := io.ReadAll(image)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to read image file")
|
||||
}
|
||||
mimeType := http.DetectContentType(imageData)
|
||||
base64Data := base64.StdEncoding.EncodeToString(imageData)
|
||||
imageBase64s = append(imageBase64s, &Base64Data{
|
||||
MimeType: mimeType,
|
||||
Data: base64Data,
|
||||
})
|
||||
}
|
||||
return imageBase64s, nil
|
||||
}
|
||||
|
||||
@@ -141,7 +141,6 @@ func GetAndValidOpenAIImageRequest(c *gin.Context, relayMode int) (*dto.ImageReq
|
||||
imageRequest.N = uint(common.String2Int(formData.Get("n")))
|
||||
imageRequest.Quality = formData.Get("quality")
|
||||
imageRequest.Size = formData.Get("size")
|
||||
imageRequest.ResponseFormat = formData.Get("response_format")
|
||||
if imageValue := formData.Get("image"); imageValue != "" {
|
||||
imageRequest.Image, _ = json.Marshal(imageValue)
|
||||
}
|
||||
|
||||
@@ -26,10 +26,6 @@ var defaultGeminiSettings = GeminiSettings{
|
||||
SupportedImagineModels: []string{
|
||||
"gemini-2.0-flash-exp-image-generation",
|
||||
"gemini-2.0-flash-exp",
|
||||
"gemini-3-pro-image-preview",
|
||||
"gemini-2.5-flash-image",
|
||||
"nano-banana",
|
||||
"nano-banana-pro",
|
||||
},
|
||||
ThinkingAdapterEnabled: false,
|
||||
ThinkingAdapterBudgetTokensPercentage: 0.6,
|
||||
|
||||
Reference in New Issue
Block a user