Files
geekai/api/service/video/adapters/minimax_adapter.go
T
RockYang 9ccff4efbc feat(release): migrate GeekAI v4.3.0 to open source
- Sync backend and frontend from GeekAI Plus v4.3.0

- Remove commercial License flows and update open-source deployment defaults

- Preserve Docker Compose deployment and bump image tags to v4.3.0

BREAKING CHANGE: commercial License configuration and related endpoints are removed
2026-08-11 14:51:20 +08:00

228 lines
6.7 KiB
Go

package adapters
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// * Copyright 2023 The Geek-AI Authors. All rights reserved.
// * Use of this source code is governed by a Apache-2.0 license
// * that can be found in the LICENSE file.
// * @Author yangjian102621@163.com
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
import (
"encoding/json"
"fmt"
"geekai/core/types"
"io"
"strings"
"time"
"github.com/imroc/req/v3"
"gorm.io/gorm"
)
// MiniMaxAdapter MiniMax 视频生成适配器
type MiniMaxAdapter struct {
db *gorm.DB
httpClient *req.Client
}
// NewMiniMaxAdapter 创建 MiniMax 适配器
func NewMiniMaxAdapter(db *gorm.DB) *MiniMaxAdapter {
return &MiniMaxAdapter{
db: db,
httpClient: req.C().SetTimeout(time.Minute * 3),
}
}
// GetProvider 获取服务提供商名称
func (a *MiniMaxAdapter) GetProvider() string {
return "minimax"
}
// MiniMaxCreateRequest MiniMax 创建任务请求
type MiniMaxCreateRequest struct {
Model string `json:"model"`
Prompt string `json:"prompt"`
Duration int `json:"duration,omitempty"`
Resolution string `json:"resolution,omitempty"`
FirstFrameImage string `json:"first_frame_image,omitempty"`
LastFrameImage string `json:"last_frame_image,omitempty"`
PromptOptimizer bool `json:"prompt_optimizer,omitempty"`
}
// MiniMaxCreateResponse MiniMax 创建任务响应
type MiniMaxCreateResponse struct {
TaskId string `json:"task_id"`
BaseResp struct {
StatusCode int `json:"status_code"`
StatusMsg string `json:"status_msg"`
} `json:"base_resp"`
}
// MiniMaxFile MiniMax 文件信息
type MiniMaxFile struct {
Bytes int `json:"bytes"`
CreatedAt int64 `json:"created_at"`
DownloadURL string `json:"download_url"`
FileId int64 `json:"file_id"`
Filename string `json:"filename"`
Purpose string `json:"purpose"`
}
// MiniMaxQueryResponse MiniMax 查询任务响应
type MiniMaxQueryResponse struct {
TaskId string `json:"task_id"`
Status string `json:"status"`
FileId string `json:"file_id,omitempty"` // 顶层 file_id 可能是字符串
File *MiniMaxFile `json:"file,omitempty"` // file 对象包含详细信息
VideoWidth int `json:"video_width,omitempty"`
VideoHeight int `json:"video_height,omitempty"`
VideoURL string `json:"video_url,omitempty"`
Prompt string `json:"prompt,omitempty"`
ErrMsg string `json:"err_msg,omitempty"`
StatusMsg string `json:"status_msg,omitempty"`
BaseResp struct {
StatusCode int `json:"status_code"`
StatusMsg string `json:"status_msg"`
} `json:"base_resp"`
}
// CreateTask 创建视频生成任务
func (a *MiniMaxAdapter) CreateTask(task types.VideoTask, videoConfig *types.VideoConfig) (CreateTaskResponse, error) {
// 解析任务参数
paramsMap, ok := task.Params.(map[string]interface{})
if !ok {
return CreateTaskResponse{}, fmt.Errorf("invalid params type for MiniMax video task")
}
// 构建请求参数
reqBody := MiniMaxCreateRequest{
Prompt: task.Prompt,
}
// 从 params 中提取参数
if model, ok := paramsMap["model"].(string); ok {
reqBody.Model = model
}
if duration, ok := paramsMap["duration"].(float64); ok {
reqBody.Duration = int(duration)
} else if duration, ok := paramsMap["duration"].(int); ok {
reqBody.Duration = duration
}
if resolution, ok := paramsMap["resolution"].(string); ok {
reqBody.Resolution = resolution
}
if firstFrameImage, ok := paramsMap["first_frame_image"].(string); ok {
reqBody.FirstFrameImage = firstFrameImage
}
if lastFrameImage, ok := paramsMap["last_frame_image"].(string); ok {
reqBody.LastFrameImage = lastFrameImage
}
if promptOptimizer, ok := paramsMap["prompt_optimizer"].(bool); ok {
reqBody.PromptOptimizer = promptOptimizer
} else {
reqBody.PromptOptimizer = true // 默认值
}
// 发送请求
apiURL := fmt.Sprintf("%s/minimax/v1/video_generation", videoConfig.ApiURL)
r, err := a.httpClient.R().
SetHeader("Authorization", "Bearer "+videoConfig.ApiKey).
SetHeader("Content-Type", "application/json").
SetBody(reqBody).
Post(apiURL)
if err != nil {
return CreateTaskResponse{}, fmt.Errorf("请求 API 出错:%v", err)
}
if r.StatusCode != 200 && r.StatusCode != 201 {
body, _ := io.ReadAll(r.Body)
return CreateTaskResponse{}, fmt.Errorf("请求 API 出错:%d, %s", r.StatusCode, string(body))
}
body, _ := io.ReadAll(r.Body)
var res MiniMaxCreateResponse
err = json.Unmarshal(body, &res)
if err != nil {
return CreateTaskResponse{}, fmt.Errorf("解析API数据失败:%v, %s", err, string(body))
}
if res.BaseResp.StatusCode != 0 {
return CreateTaskResponse{}, fmt.Errorf("API 返回错误:%s", res.BaseResp.StatusMsg)
}
return CreateTaskResponse{
TaskId: res.TaskId,
Channel: videoConfig.ApiURL,
Prompt: task.Prompt,
State: types.VideoStatusPending,
CreatedAt: time.Now().Format(time.RFC3339),
}, nil
}
// QueryTask 查询任务状态
func (a *MiniMaxAdapter) QueryTask(taskId string, channel string, videoConfig *types.VideoConfig) (QueryTaskResponse, error) {
// MiniMax 查询接口
apiURL := fmt.Sprintf("%s/minimax/v1/query/video_generation?task_id=%s", videoConfig.ApiURL, taskId)
r, err := a.httpClient.R().
SetHeader("Authorization", "Bearer "+videoConfig.ApiKey).
Get(apiURL)
if err != nil {
return QueryTaskResponse{}, fmt.Errorf("请求 API 失败:%v", err)
}
defer r.Body.Close()
if r.StatusCode != 200 {
body, _ := io.ReadAll(r.Body)
return QueryTaskResponse{}, fmt.Errorf("API 返回失败:%d, %s", r.StatusCode, string(body))
}
body, _ := io.ReadAll(r.Body)
var res MiniMaxQueryResponse
err = json.Unmarshal(body, &res)
if err != nil {
return QueryTaskResponse{}, fmt.Errorf("解析API数据失败:%v, %s", err, string(body))
}
if res.BaseResp.StatusCode != 0 {
return QueryTaskResponse{}, fmt.Errorf("API 返回错误:%s", res.BaseResp.StatusMsg)
}
// 转换状态(处理大小写)
state := strings.ToLower(res.Status)
switch state {
case "completed", "succeed", "success":
state = types.VideoStatusSuccess
case "in_progress", "running":
state = types.VideoStatusInProgress
case "failed":
state = types.VideoStatusFailed
default:
state = types.VideoStatusPending
}
// 获取视频URL,优先从 file.download_url 获取
videoURL := res.VideoURL
if res.File != nil && res.File.DownloadURL != "" {
videoURL = res.File.DownloadURL
}
// 构建响应
response := QueryTaskResponse{
TaskId: res.TaskId,
Status: state,
VideoURL: videoURL,
Prompt: res.Prompt,
ErrMsg: res.ErrMsg,
StatusMsg: res.StatusMsg,
}
// 如果有原始数据,转换为 JSON 字符串
if len(body) > 0 {
response.Output = string(body)
}
return response, nil
}