topfans/backend/gateway/service/minimax_client.go
2026-06-03 22:19:22 +08:00

132 lines
3.4 KiB
Go

package service
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
"go.uber.org/zap"
"github.com/topfans/backend/pkg/logger"
)
// MinimaxClient MiniMax 图像生成 HTTP 客户端
type MinimaxClient struct {
APIURL string
APIKey string
HTTPClient *http.Client
}
// NewMinimaxClient 创建 MiniMax 客户端
func NewMinimaxClient(apiURL, apiKey string) *MinimaxClient {
return &MinimaxClient{
APIURL: apiURL,
APIKey: apiKey,
HTTPClient: &http.Client{
Timeout: 60 * time.Second,
},
}
}
// SubjectReference 人像参考(图生图)
type SubjectReference struct {
Type string `json:"type"` // "character"
ImageFile string `json:"image_file"` // URL 或 data: URL
}
// ImageGenReq 文生图/图生图请求
type ImageGenReq struct {
Model string `json:"model"`
Prompt string `json:"prompt"`
AspectRatio string `json:"aspect_ratio"`
N int `json:"n"`
ResponseFormat string `json:"response_format"`
SubjectReference []SubjectReference `json:"subject_reference,omitempty"`
}
// ImageGenResp 文生图响应
type ImageGenResp struct {
ID string `json:"id"`
Data struct {
ImageURLs []string `json:"image_urls"`
} `json:"data"`
BaseResp struct {
StatusCode int `json:"status_code"`
StatusMsg string `json:"status_msg"`
} `json:"base_resp"`
}
// GenerateImage 文生图(无参考图)
func (c *MinimaxClient) GenerateImage(ctx context.Context, prompt string) (string, error) {
return c.GenerateImageWithSubject(ctx, prompt, "")
}
// GenerateImageWithSubject 图生图 — 将人物抠图作为 subject_reference 传给 MiniMax
// 这样 AI 生成的背景图会自然地融合人物主体
func (c *MinimaxClient) GenerateImageWithSubject(ctx context.Context, prompt, subjectImageURL string) (string, error) {
req := ImageGenReq{
Model: "image-01",
Prompt: prompt,
AspectRatio: "3:4",
N: 1,
ResponseFormat: "url",
}
if subjectImageURL != "" {
req.SubjectReference = []SubjectReference{
{Type: "character", ImageFile: subjectImageURL},
}
}
body, err := json.Marshal(req)
if err != nil {
return "", fmt.Errorf("marshal minimax request: %w", err)
}
httpReq, err := http.NewRequestWithContext(ctx, "POST", c.APIURL, bytes.NewReader(body))
if err != nil {
return "", fmt.Errorf("create minimax request: %w", err)
}
httpReq.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.APIKey))
httpReq.Header.Set("Content-Type", "application/json")
resp, err := c.HTTPClient.Do(httpReq)
if err != nil {
return "", fmt.Errorf("minimax http: %w", err)
}
defer resp.Body.Close()
respBody, _ := io.ReadAll(resp.Body)
var genResp ImageGenResp
if err := json.Unmarshal(respBody, &genResp); err != nil {
return "", fmt.Errorf("parse minimax response: %w, body=%s", err, string(respBody))
}
if genResp.BaseResp.StatusCode != 0 {
return "", fmt.Errorf("minimax error: code=%d msg=%s", genResp.BaseResp.StatusCode, genResp.BaseResp.StatusMsg)
}
if len(genResp.Data.ImageURLs) == 0 {
return "", fmt.Errorf("minimax returned no images")
}
logger.Logger.Info("MiniMax generated image",
zap.String("prompt_preview", prompt[:minLen(60, len(prompt))]),
zap.Bool("with_subject", subjectImageURL != ""),
)
return genResp.Data.ImageURLs[0], nil
}
func minLen(a, b int) int {
if a < b {
return a
}
return b
}