Merge branch 'pr/support-New-API-proxy-kling' of github.com:feitianbubu/new-api into feitianbubu-pr/support-New-API-proxy-kling

This commit is contained in:
creamlike1024
2025-07-21 23:03:53 +08:00
6 changed files with 66 additions and 43 deletions

View File

@@ -114,3 +114,23 @@ type KlingImage2VideoRequest struct {
CallbackURL string `json:"callback_url,omitempty" example:"https://your.domain/callback"` CallbackURL string `json:"callback_url,omitempty" example:"https://your.domain/callback"`
ExternalTaskId string `json:"external_task_id,omitempty" example:"custom-task-002"` ExternalTaskId string `json:"external_task_id,omitempty" example:"custom-task-002"`
} }
// KlingImage2videoTaskId godoc
// @Summary 可灵任务查询--图生视频
// @Description Query the status and result of a Kling video generation task by task ID
// @Tags Origin
// @Accept json
// @Produce json
// @Param task_id path string true "Task ID"
// @Router /kling/v1/videos/image2video/{task_id} [get]
func KlingImage2videoTaskId(c *gin.Context) {}
// KlingText2videoTaskId godoc
// @Summary 可灵任务查询--文生视频
// @Description Query the status and result of a Kling text-to-video generation task by task ID
// @Tags Origin
// @Accept json
// @Produce json
// @Param task_id path string true "Task ID"
// @Router /kling/v1/videos/text2video/{task_id} [get]
func KlingText2videoTaskId(c *gin.Context) {}

View File

@@ -2,13 +2,16 @@ package controller
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"io" "io"
"one-api/common" "one-api/common"
"one-api/constant" "one-api/constant"
"one-api/dto"
"one-api/model" "one-api/model"
"one-api/relay" "one-api/relay"
"one-api/relay/channel" "one-api/relay/channel"
relaycommon "one-api/relay/common"
"time" "time"
) )
@@ -77,13 +80,21 @@ func updateVideoSingleTask(ctx context.Context, adaptor channel.TaskAdaptor, cha
return fmt.Errorf("readAll failed for task %s: %w", taskId, err) return fmt.Errorf("readAll failed for task %s: %w", taskId, err)
} }
taskResult, err := adaptor.ParseTaskResult(responseBody) taskResult := &relaycommon.TaskInfo{}
if err != nil { // try parse as New API response format
var responseItems dto.TaskResponse[model.Task]
if err = json.Unmarshal(responseBody, &responseItems); err == nil {
t := responseItems.Data
taskResult.TaskID = t.TaskID
taskResult.Status = string(t.Status)
taskResult.Url = t.FailReason
taskResult.Progress = t.Progress
taskResult.Reason = t.FailReason
} else if taskResult, err = adaptor.ParseTaskResult(responseBody); err != nil {
return fmt.Errorf("parseTaskResult failed for task %s: %w", taskId, err) return fmt.Errorf("parseTaskResult failed for task %s: %w", taskId, err)
} else {
task.Data = responseBody
} }
//if taskResult.Code != 0 {
// return fmt.Errorf("video task fetch failed for task %s", taskId)
//}
now := time.Now().Unix() now := time.Now().Unix()
if taskResult.Status == "" { if taskResult.Status == "" {
@@ -128,8 +139,6 @@ func updateVideoSingleTask(ctx context.Context, adaptor channel.TaskAdaptor, cha
if taskResult.Progress != "" { if taskResult.Progress != "" {
task.Progress = taskResult.Progress task.Progress = taskResult.Progress
} }
task.Data = responseBody
if err := task.Update(); err != nil { if err := task.Update(); err != nil {
common.SysError("UpdateVideoTask task error: " + err.Error()) common.SysError("UpdateVideoTask task error: " + err.Error())
} }

View File

@@ -50,6 +50,7 @@ type requestPayload struct {
type responsePayload struct { type responsePayload struct {
Code int `json:"code"` Code int `json:"code"`
Message string `json:"message"` Message string `json:"message"`
TaskId string `json:"task_id"`
RequestId string `json:"request_id"` RequestId string `json:"request_id"`
Data struct { Data struct {
TaskId string `json:"task_id"` TaskId string `json:"task_id"`
@@ -73,21 +74,16 @@ type responsePayload struct {
type TaskAdaptor struct { type TaskAdaptor struct {
ChannelType int ChannelType int
accessKey string apiKey string
secretKey string
baseURL string baseURL string
} }
func (a *TaskAdaptor) Init(info *relaycommon.TaskRelayInfo) { func (a *TaskAdaptor) Init(info *relaycommon.TaskRelayInfo) {
a.ChannelType = info.ChannelType a.ChannelType = info.ChannelType
a.baseURL = info.BaseUrl a.baseURL = info.BaseUrl
a.apiKey = info.ApiKey
// apiKey format: "access_key|secret_key" // apiKey format: "access_key|secret_key"
keyParts := strings.Split(info.ApiKey, "|")
if len(keyParts) == 2 {
a.accessKey = strings.TrimSpace(keyParts[0])
a.secretKey = strings.TrimSpace(keyParts[1])
}
} }
// ValidateRequestAndSetAction parses body, validates fields and sets default action. // ValidateRequestAndSetAction parses body, validates fields and sets default action.
@@ -166,27 +162,19 @@ func (a *TaskAdaptor) DoResponse(c *gin.Context, resp *http.Response, info *rela
return return
} }
// Attempt Kling response parse first.
var kResp responsePayload var kResp responsePayload
if err := json.Unmarshal(responseBody, &kResp); err == nil && kResp.Code == 0 { err = json.Unmarshal(responseBody, &kResp)
c.JSON(http.StatusOK, gin.H{"task_id": kResp.Data.TaskId}) if err != nil {
return kResp.Data.TaskId, responseBody, nil taskErr = service.TaskErrorWrapper(err, "unmarshal_response_failed", http.StatusInternalServerError)
}
// Fallback generic task response.
var generic dto.TaskResponse[string]
if err := json.Unmarshal(responseBody, &generic); err != nil {
taskErr = service.TaskErrorWrapper(errors.Wrapf(err, "body: %s", responseBody), "unmarshal_response_body_failed", http.StatusInternalServerError)
return return
} }
if kResp.Code != 0 {
if !generic.IsSuccess() { taskErr = service.TaskErrorWrapperLocal(fmt.Errorf(kResp.Message), "task_failed", http.StatusBadRequest)
taskErr = service.TaskErrorWrapper(fmt.Errorf(generic.Message), generic.Code, http.StatusInternalServerError)
return return
} }
kResp.TaskId = kResp.Data.TaskId
c.JSON(http.StatusOK, gin.H{"task_id": generic.Data}) c.JSON(http.StatusOK, kResp)
return generic.Data, responseBody, nil return kResp.Data.TaskId, responseBody, nil
} }
// FetchTask fetch task status // FetchTask fetch task status
@@ -288,21 +276,25 @@ func defaultInt(v int, def int) int {
// ============================ // ============================
func (a *TaskAdaptor) createJWTToken() (string, error) { func (a *TaskAdaptor) createJWTToken() (string, error) {
return a.createJWTTokenWithKeys(a.accessKey, a.secretKey) return a.createJWTTokenWithKey(a.apiKey)
} }
//func (a *TaskAdaptor) createJWTTokenWithKey(apiKey string) (string, error) {
// parts := strings.Split(apiKey, "|")
// if len(parts) != 2 {
// return "", fmt.Errorf("invalid API key format, expected 'access_key,secret_key'")
// }
// return a.createJWTTokenWithKey(strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]))
//}
func (a *TaskAdaptor) createJWTTokenWithKey(apiKey string) (string, error) { func (a *TaskAdaptor) createJWTTokenWithKey(apiKey string) (string, error) {
parts := strings.Split(apiKey, "|")
if len(parts) != 2 {
return "", fmt.Errorf("invalid API key format, expected 'access_key,secret_key'")
}
return a.createJWTTokenWithKeys(strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]))
}
func (a *TaskAdaptor) createJWTTokenWithKeys(accessKey, secretKey string) (string, error) { keyParts := strings.Split(apiKey, "|")
if accessKey == "" || secretKey == "" { accessKey := strings.TrimSpace(keyParts[0])
return "", fmt.Errorf("access key and secret key are required") if len(keyParts) == 1 {
return accessKey, nil
} }
secretKey := strings.TrimSpace(keyParts[1])
now := time.Now().Unix() now := time.Now().Unix()
claims := jwt.MapClaims{ claims := jwt.MapClaims{
"iss": accessKey, "iss": accessKey,
@@ -315,12 +307,12 @@ func (a *TaskAdaptor) createJWTTokenWithKeys(accessKey, secretKey string) (strin
} }
func (a *TaskAdaptor) ParseTaskResult(respBody []byte) (*relaycommon.TaskInfo, error) { func (a *TaskAdaptor) ParseTaskResult(respBody []byte) (*relaycommon.TaskInfo, error) {
taskInfo := &relaycommon.TaskInfo{}
resPayload := responsePayload{} resPayload := responsePayload{}
err := json.Unmarshal(respBody, &resPayload) err := json.Unmarshal(respBody, &resPayload)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "failed to unmarshal response body") return nil, errors.Wrap(err, "failed to unmarshal response body")
} }
taskInfo := &relaycommon.TaskInfo{}
taskInfo.Code = resPayload.Code taskInfo.Code = resPayload.Code
taskInfo.TaskID = resPayload.Data.TaskId taskInfo.TaskID = resPayload.Data.TaskId
taskInfo.Reason = resPayload.Message taskInfo.Reason = resPayload.Message

View File

@@ -150,7 +150,7 @@ func Path2RelayKling(method, path string) int {
relayMode := RelayModeUnknown relayMode := RelayModeUnknown
if method == http.MethodPost && strings.HasSuffix(path, "/video/generations") { if method == http.MethodPost && strings.HasSuffix(path, "/video/generations") {
relayMode = RelayModeKlingSubmit relayMode = RelayModeKlingSubmit
} else if method == http.MethodGet && strings.Contains(path, "/video/generations/") { } else if method == http.MethodGet && (strings.Contains(path, "/video/generations")) {
relayMode = RelayModeKlingFetchByID relayMode = RelayModeKlingFetchByID
} }
return relayMode return relayMode

View File

@@ -20,5 +20,7 @@ func SetVideoRouter(router *gin.Engine) {
{ {
klingV1Router.POST("/videos/text2video", controller.RelayTask) klingV1Router.POST("/videos/text2video", controller.RelayTask)
klingV1Router.POST("/videos/image2video", controller.RelayTask) klingV1Router.POST("/videos/image2video", controller.RelayTask)
klingV1Router.GET("/videos/text2video/:task_id", controller.RelayTask)
klingV1Router.GET("/videos/image2video/:task_id", controller.RelayTask)
} }
} }

View File

@@ -86,7 +86,7 @@ function type2secretPrompt(type) {
case 33: case 33:
return '按照如下格式输入Ak|Sk|Region'; return '按照如下格式输入Ak|Sk|Region';
case 50: case 50:
return '按照如下格式输入: AccessKey|SecretKey'; return '按照如下格式输入: AccessKey|SecretKey, 如果上游是New API则直接输ApiKey';
case 51: case 51:
return '按照如下格式输入: Access Key ID|Secret Access Key'; return '按照如下格式输入: Access Key ID|Secret Access Key';
default: default: