mirror of
https://github.com/yangjian102621/geekai.git
synced 2026-08-21 23:07: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
268 lines
7.8 KiB
Go
268 lines
7.8 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 (
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"geekai/core/types"
|
|
"io"
|
|
"net/http"
|
|
"time"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// KelingAdapter 可灵视频生成适配器
|
|
type KelingAdapter struct {
|
|
db *gorm.DB
|
|
}
|
|
|
|
// NewKelingAdapter 创建可灵适配器
|
|
func NewKelingAdapter(db *gorm.DB) *KelingAdapter {
|
|
return &KelingAdapter{
|
|
db: db,
|
|
}
|
|
}
|
|
|
|
// GetProvider 获取服务提供商名称
|
|
func (a *KelingAdapter) GetProvider() string {
|
|
return "keling"
|
|
}
|
|
|
|
// KelingCreateRequest 可灵创建任务请求
|
|
type KelingCreateRequest struct {
|
|
ModelName string `json:"model_name"`
|
|
Prompt string `json:"prompt"`
|
|
NegativePrompt string `json:"negative_prompt,omitempty"`
|
|
CfgScale float64 `json:"cfg_scale,omitempty"`
|
|
Mode string `json:"mode,omitempty"`
|
|
AspectRatio string `json:"aspect_ratio,omitempty"`
|
|
Duration string `json:"duration,omitempty"`
|
|
Sound bool `json:"sound,omitempty"`
|
|
Image string `json:"image,omitempty"`
|
|
ImageTail string `json:"image_tail,omitempty"`
|
|
}
|
|
|
|
// KelingCreateResponse 可灵创建任务响应
|
|
type KelingCreateResponse struct {
|
|
Code int `json:"code"`
|
|
Message string `json:"message"`
|
|
RequestID string `json:"request_id"`
|
|
Data struct {
|
|
TaskID string `json:"task_id"`
|
|
TaskStatus string `json:"task_status"`
|
|
CreatedAt int64 `json:"created_at"`
|
|
UpdatedAt int64 `json:"updated_at"`
|
|
} `json:"data"`
|
|
}
|
|
|
|
// KelingQueryResponse 可灵查询任务响应
|
|
type KelingQueryResponse struct {
|
|
Code int `json:"code"`
|
|
Message string `json:"message"`
|
|
Data struct {
|
|
TaskID string `json:"task_id"`
|
|
TaskStatus string `json:"task_status"`
|
|
TaskStatusMsg string `json:"task_status_msg"`
|
|
CreatedAt int64 `json:"created_at"`
|
|
UpdatedAt int64 `json:"updated_at"`
|
|
TaskResult struct {
|
|
Images []struct {
|
|
Index int `json:"index"`
|
|
URL string `json:"url"`
|
|
} `json:"images,omitempty"`
|
|
Videos []struct {
|
|
ID string `json:"id"`
|
|
URL string `json:"url"`
|
|
Duration string `json:"duration"`
|
|
} `json:"videos,omitempty"`
|
|
} `json:"task_result"`
|
|
} `json:"data"`
|
|
}
|
|
|
|
// CreateTask 创建视频生成任务
|
|
func (a *KelingAdapter) CreateTask(task types.VideoTask, videoConfig *types.VideoConfig) (CreateTaskResponse, error) {
|
|
// 解析任务参数
|
|
paramsMap, ok := task.Params.(map[string]interface{})
|
|
if !ok {
|
|
return CreateTaskResponse{}, errors.New("invalid params type for KeLing video task")
|
|
}
|
|
|
|
// 构建请求参数
|
|
payload := KelingCreateRequest{
|
|
Prompt: task.Prompt,
|
|
}
|
|
|
|
// 从 params 中提取参数
|
|
if modelName, ok := paramsMap["model_name"].(string); ok {
|
|
payload.ModelName = modelName
|
|
}
|
|
if prompt, ok := paramsMap["prompt"].(string); ok {
|
|
payload.Prompt = prompt
|
|
}
|
|
if negativePrompt, ok := paramsMap["negative_prompt"].(string); ok {
|
|
payload.NegativePrompt = negativePrompt
|
|
}
|
|
if cfgScale, ok := paramsMap["cfg_scale"].(float64); ok {
|
|
payload.CfgScale = cfgScale
|
|
}
|
|
if mode, ok := paramsMap["mode"].(string); ok {
|
|
payload.Mode = mode
|
|
}
|
|
if aspectRatio, ok := paramsMap["aspect_ratio"].(string); ok {
|
|
payload.AspectRatio = aspectRatio
|
|
}
|
|
if duration, ok := paramsMap["duration"].(string); ok {
|
|
payload.Duration = duration
|
|
}
|
|
|
|
if sound, ok := paramsMap["sound"].(bool); ok {
|
|
payload.Sound = sound
|
|
}
|
|
|
|
// 处理图生视频
|
|
taskType, ok := paramsMap["task_type"].(string)
|
|
if ok && taskType == "image2video" {
|
|
if image, ok := paramsMap["image"].(string); ok {
|
|
payload.Image = image
|
|
}
|
|
if imageTail, ok := paramsMap["image_tail"].(string); ok {
|
|
payload.ImageTail = imageTail
|
|
}
|
|
}
|
|
|
|
jsonPayload, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return CreateTaskResponse{}, fmt.Errorf("failed to marshal payload: %v", err)
|
|
}
|
|
logger.Debugf("KelingCreateRequest: %+v", string(jsonPayload))
|
|
|
|
// 发送请求
|
|
url := fmt.Sprintf("%s/kling/v1/videos/%s", videoConfig.ApiURL, taskType)
|
|
req, err := http.NewRequest("POST", url, bytes.NewReader(jsonPayload))
|
|
if err != nil {
|
|
return CreateTaskResponse{}, fmt.Errorf("failed to create request: %v", err)
|
|
}
|
|
|
|
req.Header.Set("Authorization", "Bearer "+videoConfig.ApiKey)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
// 发送请求
|
|
client := &http.Client{Timeout: time.Duration(30) * time.Second}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return CreateTaskResponse{}, fmt.Errorf("failed to send request: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
// 处理响应
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return CreateTaskResponse{}, fmt.Errorf("failed to read response: %v", err)
|
|
}
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return CreateTaskResponse{}, fmt.Errorf("API error (status %d): %s", resp.StatusCode, string(body))
|
|
}
|
|
|
|
var apiResponse KelingCreateResponse
|
|
if err := json.Unmarshal(body, &apiResponse); err != nil {
|
|
return CreateTaskResponse{}, fmt.Errorf("failed to parse response: %v", err)
|
|
}
|
|
|
|
if apiResponse.Code != 0 {
|
|
return CreateTaskResponse{}, fmt.Errorf("API error: %s", apiResponse.Message)
|
|
}
|
|
|
|
return CreateTaskResponse{
|
|
TaskId: apiResponse.Data.TaskID,
|
|
Channel: videoConfig.ApiURL,
|
|
Prompt: task.Prompt,
|
|
State: types.VideoStatusPending,
|
|
CreatedAt: time.Now().Format(time.RFC3339),
|
|
}, nil
|
|
}
|
|
|
|
// QueryTask 查询任务状态
|
|
func (a *KelingAdapter) QueryTask(taskId string, channel string, videoConfig *types.VideoConfig) (QueryTaskResponse, error) {
|
|
// 从 taskId 中提取 action(可灵的 taskId 格式可能包含 action 信息)
|
|
// 这里需要从任务信息中获取 task_type,暂时使用 text2video 作为默认值
|
|
action := "text2video"
|
|
|
|
// 尝试从 channel 或其他地方获取 action,这里简化处理
|
|
// 实际应该从任务信息中获取
|
|
|
|
url := fmt.Sprintf("%s/kling/v1/videos/%s/%s", videoConfig.ApiURL, action, taskId)
|
|
req, err := http.NewRequest("GET", url, nil)
|
|
if err != nil {
|
|
return QueryTaskResponse{}, fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
|
|
req.Header.Set("Authorization", "Bearer "+videoConfig.ApiKey)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
client := &http.Client{Timeout: time.Duration(30) * time.Second}
|
|
res, err := client.Do(req)
|
|
if err != nil {
|
|
return QueryTaskResponse{}, fmt.Errorf("failed to execute request: %w", err)
|
|
}
|
|
defer res.Body.Close()
|
|
|
|
if res.StatusCode != http.StatusOK {
|
|
body, _ := io.ReadAll(res.Body)
|
|
return QueryTaskResponse{}, fmt.Errorf("unexpected status code: %d, %s", res.StatusCode, string(body))
|
|
}
|
|
|
|
body, err := io.ReadAll(res.Body)
|
|
if err != nil {
|
|
return QueryTaskResponse{}, fmt.Errorf("failed to read response body: %w", err)
|
|
}
|
|
|
|
var response KelingQueryResponse
|
|
if err := json.Unmarshal(body, &response); err != nil {
|
|
return QueryTaskResponse{}, fmt.Errorf("failed to unmarshal response: %w", err)
|
|
}
|
|
|
|
if response.Code != 0 {
|
|
return QueryTaskResponse{}, fmt.Errorf("API error: %s", response.Message)
|
|
}
|
|
|
|
// 转换状态
|
|
state := response.Data.TaskStatus
|
|
status := state
|
|
switch state {
|
|
case "in_progress", "processing":
|
|
status = types.VideoStatusInProgress
|
|
case "completed", "succeed", "success":
|
|
status = types.VideoStatusSuccess
|
|
case "failed":
|
|
status = types.VideoStatusFailed
|
|
default:
|
|
status = types.VideoStatusPending
|
|
}
|
|
|
|
// 构建响应
|
|
result := QueryTaskResponse{
|
|
TaskId: response.Data.TaskID,
|
|
Status: status,
|
|
ErrMsg: response.Data.TaskStatusMsg,
|
|
StatusMsg: response.Data.TaskStatusMsg,
|
|
Output: string(body),
|
|
}
|
|
|
|
// 提取视频URL
|
|
if len(response.Data.TaskResult.Videos) > 0 {
|
|
result.VideoURL = response.Data.TaskResult.Videos[0].URL
|
|
}
|
|
|
|
return result, nil
|
|
}
|