126 lines
3.5 KiB
Go
126 lines
3.5 KiB
Go
package service
|
||
|
||
import (
|
||
"bytes"
|
||
"context"
|
||
"encoding/json"
|
||
"fmt"
|
||
"io"
|
||
"net/http"
|
||
"os"
|
||
"time"
|
||
|
||
dto "github.com/topfans/backend/gateway/dto"
|
||
)
|
||
|
||
// ImageGenerationResponse 图生图响应
|
||
type ImageGenerationResponse struct {
|
||
Images []string `json:"images"`
|
||
}
|
||
|
||
// MinimaxService MiniMax API 转发服务
|
||
type MinimaxService interface {
|
||
GenerateImage(ctx context.Context, req *dto.ImageGenerationRequest) (*ImageGenerationResponse, error)
|
||
}
|
||
|
||
type minimaxService struct {
|
||
client *http.Client
|
||
}
|
||
|
||
// NewMinimaxService 创建 MiniMax 服务
|
||
func NewMinimaxService() MinimaxService {
|
||
return &minimaxService{
|
||
client: &http.Client{Timeout: 320 * time.Second},
|
||
}
|
||
}
|
||
|
||
// GenerateImage 调用 MiniMax 图生图 API(同步调用)
|
||
func (s *minimaxService) GenerateImage(ctx context.Context, req *dto.ImageGenerationRequest) (*ImageGenerationResponse, error) {
|
||
apiURL := os.Getenv("MINIMAX_API_URL")
|
||
apiKey := os.Getenv("MINIMAX_API_KEY")
|
||
|
||
payload := map[string]interface{}{
|
||
"model": req.Model,
|
||
"prompt": req.Prompt,
|
||
"aspect_ratio": req.AspectRatio,
|
||
"subject_reference": req.SubjectReference,
|
||
"n": req.N,
|
||
"response_format": "base64",
|
||
}
|
||
|
||
jsonData, err := json.Marshal(payload)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to marshal request: %w", err)
|
||
}
|
||
|
||
httpReq, err := http.NewRequestWithContext(ctx, "POST", apiURL, bytes.NewBuffer(jsonData))
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||
}
|
||
httpReq.Header.Set("Authorization", "Bearer "+apiKey)
|
||
httpReq.Header.Set("Content-Type", "application/json")
|
||
|
||
resp, err := s.client.Do(httpReq)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to call MiniMax API: %w", err)
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
body, err := io.ReadAll(resp.Body)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to read response body: %w", err)
|
||
}
|
||
|
||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||
return nil, fmt.Errorf("MiniMax API returned status %d: %s", resp.StatusCode, string(body))
|
||
}
|
||
|
||
var rawResp map[string]interface{}
|
||
if err := json.Unmarshal(body, &rawResp); err != nil {
|
||
return nil, fmt.Errorf("failed to parse response: %w", err)
|
||
}
|
||
|
||
var images []string
|
||
|
||
// MiniMax API 响应格式:
|
||
// {"id": "...", "data": {"image_base64": ["..."]}, "base_resp": {"status_code": 0, "status_msg": "success"}}
|
||
data, ok := rawResp["data"].(map[string]interface{})
|
||
if !ok {
|
||
return nil, fmt.Errorf("invalid MiniMax response format: missing data field: %s", string(body))
|
||
}
|
||
|
||
// 检查 base_resp 状态码
|
||
if baseResp, ok := rawResp["base_resp"].(map[string]interface{}); ok {
|
||
if statusCode, ok := baseResp["status_code"].(float64); ok && statusCode != 0 {
|
||
statusMsg, _ := baseResp["status_msg"].(string)
|
||
return nil, fmt.Errorf("MiniMax API error: code=%d, msg=%s", int(statusCode), statusMsg)
|
||
}
|
||
}
|
||
|
||
// 优先使用 base64 格式(当 response_format=base64 时)
|
||
if base64Imgs, ok := data["image_base64"].([]interface{}); ok {
|
||
for _, img := range base64Imgs {
|
||
if base64Str, ok := img.(string); ok && base64Str != "" {
|
||
images = append(images, "data:image/jpeg;base64,"+base64Str)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 回退使用 image_urls 格式
|
||
if len(images) == 0 {
|
||
if urlImgs, ok := data["image_urls"].([]interface{}); ok {
|
||
for _, img := range urlImgs {
|
||
if urlStr, ok := img.(string); ok && urlStr != "" {
|
||
images = append(images, urlStr)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if len(images) == 0 {
|
||
return nil, fmt.Errorf("no images found in MiniMax response: %s", string(body))
|
||
}
|
||
|
||
return &ImageGenerationResponse{Images: images}, nil
|
||
}
|