mirror of
https://github.com/yangjian102621/geekai.git
synced 2026-08-25 00:27:13 +00:00
9ccff4efbc
- 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
228 lines
6.7 KiB
Go
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
|
|
}
|