feat(model):添加对阿里万象和可灵模型的支持

This commit is contained in:
qian committed 2026-05-20 00:24:25 +08:00
1 parent 5084406687
commit d7ef88fa6e
5 files changed
+604 -55

No files matched your search

+3
View File
@@ -9,6 +9,7 @@ const (
TaskPlatformSuno TaskPlatform = "suno"
TaskPlatformMidjourney TaskPlatform = "midjourney"
TaskPlatformVolcengine TaskPlatform = "volcengine"
TaskPlatformAli TaskPlatform = "ali"
)
// TaskAction 异步任务动作类型
@@ -81,6 +82,8 @@ func ProviderTypeToTaskPlatform(p ProviderType) (TaskPlatform, bool) {
return TaskPlatformMidjourney, true
case ProviderVolcengine:
return TaskPlatformVolcengine, true
case ProviderAli:
return TaskPlatformAli, true
default:
return "", false
}
+1
View File
@@ -14,6 +14,7 @@ import (
"github.com/qianfree/team-api/relay/common"
"github.com/qianfree/team-api/relay/constant"
"github.com/qianfree/team-api/relay/taskchannel"
_ "github.com/qianfree/team-api/relay/taskchannel/ali"
_ "github.com/qianfree/team-api/relay/taskchannel/kling"
"github.com/qianfree/team-api/relay/taskchannel/midjourney"
_ "github.com/qianfree/team-api/relay/taskchannel/sora"
+351
View File
@@ -0,0 +1,351 @@
package ali
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"github.com/qianfree/team-api/relay/common"
"github.com/qianfree/team-api/relay/constant"
"github.com/qianfree/team-api/relay/taskchannel"
)
func init() {
taskchannel.Register(constant.ProviderAli, func() common.TaskAdaptor {
return &AliVideoAdaptor{}
})
}
// ==================== 请求/响应结构体 ====================
// dashScopeVideoRequest DashScope 视频生成请求
type dashScopeVideoRequest struct {
Model string `json:"model"`
Input dashScopeVideoInput `json:"input"`
Parameters *dashScopeVideoParams `json:"parameters,omitempty"`
}
type dashScopeVideoInput struct {
Prompt string `json:"prompt"`
NegativePrompt string `json:"negative_prompt,omitempty"`
AudioURL string `json:"audio_url,omitempty"`
}
type dashScopeVideoParams struct {
Resolution string `json:"resolution,omitempty"`
Ratio string `json:"ratio,omitempty"`
Size string `json:"size,omitempty"`
Duration *int `json:"duration,omitempty"`
PromptExtend *bool `json:"prompt_extend,omitempty"`
Watermark *bool `json:"watermark,omitempty"`
Seed *int `json:"seed,omitempty"`
}
// dashScopeSubmitResponse DashScope 异步提交响应
type dashScopeSubmitResponse struct {
Output struct {
TaskID string `json:"task_id"`
TaskStatus string `json:"task_status"`
Code string `json:"code"`
Message string `json:"message"`
} `json:"output"`
RequestID string `json:"request_id"`
Code string `json:"code"`
Message string `json:"message"`
}
// dashScopeTaskResponse DashScope 异步任务查询响应
type dashScopeTaskResponse struct {
Output struct {
TaskID string `json:"task_id"`
TaskStatus string `json:"task_status"`
VideoURL string `json:"video_url"`
Code string `json:"code"`
Message string `json:"message"`
} `json:"output"`
Usage struct {
Duration float64 `json:"duration"`
VideoCount int `json:"video_count"`
OutputVideoDuration int `json:"output_video_duration"`
SR int `json:"SR"`
Ratio string `json:"ratio"`
} `json:"usage"`
RequestID string `json:"request_id"`
}
// ==================== Adaptor 实现 ====================
type AliVideoAdaptor struct {
info *common.RelayInfo
}
func (a *AliVideoAdaptor) Init(info *common.RelayInfo) {
a.info = info
}
func (a *AliVideoAdaptor) ValidateRequest(_ context.Context, _ *common.RelayInfo, body []byte) *common.TaskError {
var req map[string]any
if err := json.Unmarshal(body, &req); err != nil {
return &common.TaskError{StatusCode: http.StatusBadRequest, Message: "invalid request body"}
}
if _, ok := req["model"]; !ok {
return &common.TaskError{StatusCode: http.StatusBadRequest, Message: "model is required"}
}
return nil
}
func (a *AliVideoAdaptor) EstimateBilling(_ context.Context, _ *common.RelayInfo, body []byte) map[string]float64 {
ratios := map[string]float64{"base": 1.0}
var req map[string]any
if json.Unmarshal(body, &req) != nil {
return ratios
}
// duration 影响计费,传递给计费引擎
if metadata, ok := req["metadata"].(map[string]any); ok {
if v, ok := metadata["duration"].(float64); ok && v > 0 {
ratios["duration"] = v
}
}
if seconds, ok := req["seconds"].(string); ok {
if d, err := parseInt(seconds); err == nil && d > 0 {
ratios["duration"] = float64(d)
}
}
return ratios
}
func (a *AliVideoAdaptor) AdjustBillingOnSubmit(_ *common.RelayInfo, _ []byte) map[string]float64 {
return nil
}
func (a *AliVideoAdaptor) BuildRequestURL(info *common.RelayInfo) (string, error) {
baseURL := strings.TrimRight(info.ChannelMeta.BaseURL, "/")
return baseURL + "/api/v1/services/aigc/video-generation/video-synthesis", nil
}
func (a *AliVideoAdaptor) BuildRequestHeader(header http.Header, info *common.RelayInfo) error {
header.Set("Authorization", "Bearer "+info.ChannelMeta.ApiKey)
header.Set("Content-Type", "application/json")
header.Set("Accept", "application/json")
header.Set("X-DashScope-Async", "enable")
return nil
}
func (a *AliVideoAdaptor) BuildRequestBody(_ context.Context, info *common.RelayInfo, body []byte) (io.Reader, error) {
var req map[string]any
if err := json.Unmarshal(body, &req); err != nil {
return strings.NewReader(string(body)), nil
}
dsReq := dashScopeVideoRequest{
Input: dashScopeVideoInput{},
}
// 模型名
if info.ChannelMeta.IsModelMapped && info.ChannelMeta.UpstreamModelName != "" {
dsReq.Model = info.ChannelMeta.UpstreamModelName
} else if m, ok := req["model"].(string); ok {
dsReq.Model = m
}
// prompt → input.prompt
if v, ok := req["prompt"].(string); ok {
dsReq.Input.Prompt = v
}
// 从 metadata 提取参数
params := &dashScopeVideoParams{}
hasParams := false
if metadata, ok := req["metadata"].(map[string]any); ok {
if v, ok := metadata["negative_prompt"].(string); ok {
dsReq.Input.NegativePrompt = v
}
if v, ok := metadata["audio_url"].(string); ok {
dsReq.Input.AudioURL = v
}
// wan2.7+ 使用 resolution + ratio
if v, ok := metadata["resolution"].(string); ok {
params.Resolution = v
hasParams = true
}
if v, ok := metadata["ratio"].(string); ok {
params.Ratio = v
hasParams = true
}
// wan2.6 及以下使用 size
if v, ok := metadata["size"].(string); ok {
params.Size = strings.ReplaceAll(v, "x", "*")
hasParams = true
}
if v, ok := metadata["duration"].(float64); ok && v > 0 {
d := int(v)
params.Duration = &d
hasParams = true
}
if v, ok := metadata["prompt_extend"].(bool); ok {
params.PromptExtend = &v
hasParams = true
}
if v, ok := metadata["watermark"].(bool); ok {
params.Watermark = &v
hasParams = true
}
if v, ok := metadata["seed"].(float64); ok {
s := int(v)
params.Seed = &s
hasParams = true
}
}
// seconds 字段映射到 duration
if seconds, ok := req["seconds"].(string); ok {
if d, err := parseInt(seconds); err == nil && d > 0 {
params.Duration = &d
hasParams = true
}
}
if hasParams {
dsReq.Parameters = params
}
data, err := json.Marshal(dsReq)
if err != nil {
return nil, fmt.Errorf("marshal dashscope video request: %w", err)
}
return strings.NewReader(string(data)), nil
}
func (a *AliVideoAdaptor) DoRequest(_ context.Context, info *common.RelayInfo, requestBody io.Reader) (*http.Response, error) {
url, err := a.BuildRequestURL(info)
if err != nil {
return nil, err
}
req, err := http.NewRequest(http.MethodPost, url, requestBody)
if err != nil {
return nil, fmt.Errorf("create request: %w", err)
}
if err := a.BuildRequestHeader(req.Header, info); err != nil {
return nil, fmt.Errorf("setup header: %w", err)
}
client := &http.Client{Timeout: 120 * 1e9}
return client.Do(req)
}
func (a *AliVideoAdaptor) DoResponse(_ context.Context, resp *http.Response, _ *common.RelayInfo) (string, []byte, *common.TaskError) {
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", nil, &common.TaskError{StatusCode: http.StatusInternalServerError, Message: "read response failed"}
}
// DashScope 错误时可能在顶层返回 code + message
var errResp dashScopeSubmitResponse
if json.Unmarshal(body, &errResp) == nil && errResp.Code != "" {
return "", body, &common.TaskError{
StatusCode: resp.StatusCode,
Message: errResp.Message,
ErrCode: errResp.Code,
}
}
if resp.StatusCode != http.StatusOK {
return "", body, &common.TaskError{
StatusCode: resp.StatusCode,
Message: string(body),
}
}
var result dashScopeSubmitResponse
if err := json.Unmarshal(body, &result); err != nil {
return "", body, &common.TaskError{StatusCode: http.StatusInternalServerError, Message: "parse response failed"}
}
if result.Output.TaskID == "" {
// DashScope 有时在 200 响应中返回错误
if result.Output.Code != "" {
return "", body, &common.TaskError{
StatusCode: resp.StatusCode,
Message: result.Output.Message,
ErrCode: result.Output.Code,
}
}
return "", body, &common.TaskError{StatusCode: http.StatusInternalServerError, Message: "upstream returned empty task id"}
}
return result.Output.TaskID, body, nil
}
func (a *AliVideoAdaptor) FetchTask(baseURL, apiKey string, taskData []byte) (*http.Response, error) {
var data struct {
TaskID string `json:"task_id"`
}
if err := json.Unmarshal(taskData, &data); err != nil {
return nil, fmt.Errorf("ali: invalid task data: %w", err)
}
url := fmt.Sprintf("%s/api/v1/tasks/%s", strings.TrimRight(baseURL, "/"), data.TaskID)
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
client := &http.Client{Timeout: 30 * 1e9}
return client.Do(req)
}
func (a *AliVideoAdaptor) ParseTaskResult(body []byte) (*common.TaskInfo, error) {
var resp dashScopeTaskResponse
if err := json.Unmarshal(body, &resp); err != nil {
return nil, fmt.Errorf("ali: parse task result: %w", err)
}
info := &common.TaskInfo{Data: body}
switch resp.Output.TaskStatus {
case "PENDING":
info.Status = common.TaskStatusSubmitted
info.Progress = "10%"
case "RUNNING":
info.Status = common.TaskStatusInProgress
info.Progress = "50%"
case "SUCCEEDED":
info.Status = common.TaskStatusSuccess
info.Progress = "100%"
info.ResultURL = resp.Output.VideoURL
case "FAILED":
info.Status = common.TaskStatusFailure
info.FailReason = resp.Output.Message
case "CANCELED", "UNKNOWN":
info.Status = common.TaskStatusFailure
info.FailReason = fmt.Sprintf("task status: %s", resp.Output.TaskStatus)
default:
info.Status = common.TaskStatusSubmitted
info.Progress = "10%"
}
return info, nil
}
func (a *AliVideoAdaptor) GetModelList() []string {
return ModelList
}
func (a *AliVideoAdaptor) GetChannelName() string {
return channelName
}
// ==================== 辅助函数 ====================
func parseInt(s string) (int, error) {
var n int
_, err := fmt.Sscanf(s, "%d", &n)
return n, err
}
+14
View File
@@ -0,0 +1,14 @@
package ali
// ModelList 阿里云 DashScope 支持视频生成的模型列表
var ModelList = []string{
"wan2.7-t2v-2026-04-25",
"wan2.6-t2v",
"wan2.6-t2v-us",
"wan2.5-t2v-preview",
"wan2.2-t2v-plus",
"wanx2.1-t2v-turbo",
"wanx2.1-t2v-plus",
}
const channelName = "AliVideo"
+235 -55
View File
@@ -15,33 +15,118 @@ import (
const channelName = "Kling"
// ModelList 可灵视频生成模型列表
var ModelList = []string{
"kling-v1",
"kling-v1-5",
"kling-v1-6",
"kling-v2-master",
"kling-v2-1-master",
"kling-v2-5-turbo",
"kling-v2-6",
"kling-v3",
"kling-video-o1",
}
func init() {
taskchannel.Register(constant.ProviderKling, func() common.TaskAdaptor {
return &KlingAdaptor{}
})
}
// ==================== 请求/响应结构体 ====================
// klingRequest 可灵视频生成请求(文生视频 + 图生视频共用)
type klingRequest struct {
ModelName string `json:"model_name"`
Prompt string `json:"prompt,omitempty"`
NegativePrompt string `json:"negative_prompt,omitempty"`
Mode string `json:"mode,omitempty"`
Duration string `json:"duration,omitempty"`
AspectRatio string `json:"aspect_ratio,omitempty"`
CfgScale *float64 `json:"cfg_scale,omitempty"`
Image string `json:"image,omitempty"`
ImageTail string `json:"image_tail,omitempty"`
Sound string `json:"sound,omitempty"`
CameraControl map[string]any `json:"camera_control,omitempty"`
}
// klingSubmitResponse 可灵任务提交响应
type klingSubmitResponse struct {
Code int `json:"code"`
Message string `json:"message"`
Data struct {
TaskID string `json:"task_id"`
TaskStatus string `json:"task_status"`
} `json:"data"`
RequestID string `json:"request_id"`
}
// klingTaskResponse 可灵任务查询响应
type klingTaskResponse 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"`
TaskResult struct {
Videos []struct {
ID string `json:"id"`
URL string `json:"url"`
Duration string `json:"duration"`
} `json:"videos"`
} `json:"task_result"`
} `json:"data"`
RequestID string `json:"request_id"`
}
// ==================== Adaptor 实现 ====================
type KlingAdaptor struct {
info *common.RelayInfo
info *common.RelayInfo
taskType string // text2video / image2video / omni-video
}
func (a *KlingAdaptor) Init(info *common.RelayInfo) {
a.info = info
}
func (a *KlingAdaptor) ValidateRequest(_ context.Context, _ *common.RelayInfo, _ []byte) *common.TaskError {
func (a *KlingAdaptor) ValidateRequest(_ context.Context, _ *common.RelayInfo, body []byte) *common.TaskError {
var req map[string]any
if err := json.Unmarshal(body, &req); err != nil {
return &common.TaskError{StatusCode: http.StatusBadRequest, Message: "invalid request body"}
}
if _, ok := req["model"]; !ok {
return &common.TaskError{StatusCode: http.StatusBadRequest, Message: "model is required"}
}
return nil
}
func (a *KlingAdaptor) EstimateBilling(_ context.Context, _ *common.RelayInfo, body []byte) map[string]float64 {
ratios := map[string]float64{"base": 1.0}
var req map[string]any
if json.Unmarshal(body, &req) == nil {
// 高清模式加价
if mode, ok := req["mode"]; ok && fmt.Sprintf("%v", mode) == "hq" {
ratios["quality"] = 2.0
if json.Unmarshal(body, &req) != nil {
return ratios
}
// 高清模式加价
if mode, ok := req["mode"].(string); ok && mode == "pro" {
ratios["quality"] = 3.5
}
// 时长加价:10s = 2x
if metadata, ok := req["metadata"].(map[string]any); ok {
if v, ok := metadata["duration"].(float64); ok && v >= 10 {
ratios["duration_multiplier"] = 2.0
}
}
if seconds, ok := req["seconds"].(string); ok {
if d, err := parseInt(seconds); err == nil && d >= 10 {
ratios["duration_multiplier"] = 2.0
}
}
return ratios
}
@@ -49,20 +134,47 @@ func (a *KlingAdaptor) AdjustBillingOnSubmit(_ *common.RelayInfo, _ []byte) map[
return nil
}
// determineTaskType 根据请求内容判断任务类型
func determineTaskType(req map[string]any) string {
// Omni-Video 模型
if modelName, ok := req["model"].(string); ok {
if strings.HasPrefix(modelName, "kling-video-o") || strings.HasPrefix(modelName, "kling-v3") {
return "omni-video"
}
}
// 有图片输入则 image2video
if metadata, ok := req["metadata"].(map[string]any); ok {
if _, hasImg := metadata["image"]; hasImg {
return "image2video"
}
}
if _, hasImg := req["images"].([]any); hasImg {
return "image2video"
}
return "text2video"
}
func (a *KlingAdaptor) BuildRequestURL(info *common.RelayInfo) (string, error) {
baseURL := strings.TrimRight(info.ChannelMeta.BaseURL, "/")
// 根据是否有 image 参数决定 text2video 或 image2video
// 默认使用 text2video
// 根据 model 名判断路径(URL 在 BuildRequestBody 阶段确定 taskType,此处用 model 前置判断)
modelName := info.ChannelMeta.UpstreamModelName
if modelName == "" {
modelName = info.OriginModelName
}
if strings.HasPrefix(modelName, "kling-video-o") || strings.HasPrefix(modelName, "kling-v3") {
return fmt.Sprintf("%s/v1/videos/omni-video", baseURL), nil
}
// 默认 text2video,image2video 由 BuildRequestBody 设置 task_type 后 FetchTask 使用
return fmt.Sprintf("%s/v1/videos/text2video", baseURL), nil
}
func (a *KlingAdaptor) BuildRequestHeader(header http.Header, info *common.RelayInfo) error {
apiKey := info.ChannelMeta.ApiKey
// 如果是 sk- 开头的 key,直接用 Bearer token
if strings.HasPrefix(apiKey, "sk-") {
header.Set("Authorization", "Bearer "+apiKey)
} else {
// 否则生成 JWT token(accessKey|secretKey 格式)
token := buildKlingJWT(apiKey)
header.Set("Authorization", "Bearer "+token)
}
@@ -76,34 +188,107 @@ func (a *KlingAdaptor) BuildRequestBody(_ context.Context, info *common.RelayInf
return strings.NewReader(string(body)), nil
}
if info.ChannelMeta.UpstreamModelName != "" {
req["model_name"] = info.ChannelMeta.UpstreamModelName
req["model"] = info.ChannelMeta.UpstreamModelName
taskType := determineTaskType(req)
kReq := klingRequest{}
// 模型名
if info.ChannelMeta.IsModelMapped && info.ChannelMeta.UpstreamModelName != "" {
kReq.ModelName = info.ChannelMeta.UpstreamModelName
} else if m, ok := req["model"].(string); ok {
kReq.ModelName = m
}
// 如果没有指定 mode,默认 std
if _, ok := req["mode"]; !ok {
req["mode"] = "std"
}
// 如果没有指定 duration,默认 5
if _, ok := req["duration"]; !ok {
req["duration"] = "5"
// prompt
if v, ok := req["prompt"].(string); ok {
kReq.Prompt = v
}
data, _ := json.Marshal(req)
// 从 metadata 提取参数
if metadata, ok := req["metadata"].(map[string]any); ok {
if v, ok := metadata["negative_prompt"].(string); ok {
kReq.NegativePrompt = v
}
if v, ok := metadata["mode"].(string); ok {
kReq.Mode = v
}
if v, ok := metadata["duration"].(string); ok {
kReq.Duration = v
} else if v, ok := metadata["duration"].(float64); ok {
kReq.Duration = fmt.Sprintf("%d", int(v))
}
if v, ok := metadata["aspect_ratio"].(string); ok {
kReq.AspectRatio = v
}
if v, ok := metadata["cfg_scale"].(float64); ok {
kReq.CfgScale = &v
}
if v, ok := metadata["image"].(string); ok {
kReq.Image = v
}
if v, ok := metadata["image_tail"].(string); ok {
kReq.ImageTail = v
}
if v, ok := metadata["sound"].(string); ok {
kReq.Sound = v
}
if v, ok := metadata["camera_control"].(map[string]any); ok {
kReq.CameraControl = v
}
}
// seconds 字段映射到 duration
if seconds, ok := req["seconds"].(string); ok {
kReq.Duration = seconds
}
// images 数组 → 取第一张作为 image
if images, ok := req["images"].([]any); ok && len(images) > 0 {
if url, ok := images[0].(string); ok && kReq.Image == "" {
kReq.Image = url
}
}
// 默认值
if kReq.Mode == "" {
kReq.Mode = "std"
}
if kReq.Duration == "" {
kReq.Duration = "5"
}
// 记录 taskType 供 DoRequest 使用正确的 URL
if taskType == "image2video" || taskType == "omni-video" {
a.taskType = taskType
}
data, err := json.Marshal(kReq)
if err != nil {
return nil, fmt.Errorf("marshal kling request: %w", err)
}
return strings.NewReader(string(data)), nil
}
func (a *KlingAdaptor) DoRequest(_ context.Context, info *common.RelayInfo, requestBody io.Reader) (*http.Response, error) {
url, err := a.BuildRequestURL(info)
if err != nil {
return nil, err
baseURL := strings.TrimRight(info.ChannelMeta.BaseURL, "/")
// 根据 taskType 决定 URL
var reqURL string
switch a.taskType {
case "image2video":
reqURL = fmt.Sprintf("%s/v1/videos/image2video", baseURL)
case "omni-video":
reqURL = fmt.Sprintf("%s/v1/videos/omni-video", baseURL)
default:
reqURL = fmt.Sprintf("%s/v1/videos/text2video", baseURL)
}
req, err := http.NewRequest("POST", url, requestBody)
req, err := http.NewRequest(http.MethodPost, reqURL, requestBody)
if err != nil {
return nil, err
return nil, fmt.Errorf("create request: %w", err)
}
if err := a.BuildRequestHeader(req.Header, info); err != nil {
return nil, fmt.Errorf("setup header: %w", err)
}
a.BuildRequestHeader(req.Header, info)
client := &http.Client{Timeout: 120 * 1e9}
return client.Do(req)
}
@@ -111,19 +296,14 @@ func (a *KlingAdaptor) DoRequest(_ context.Context, info *common.RelayInfo, requ
func (a *KlingAdaptor) DoResponse(_ context.Context, resp *http.Response, _ *common.RelayInfo) (string, []byte, *common.TaskError) {
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", nil, &common.TaskError{StatusCode: 500, Message: "read response failed"}
return "", nil, &common.TaskError{StatusCode: http.StatusInternalServerError, Message: "read response failed"}
}
var result struct {
Code int `json:"code"`
Message string `json:"message"`
Data struct {
TaskID string `json:"task_id"`
} `json:"data"`
}
var result klingSubmitResponse
if err := json.Unmarshal(body, &result); err != nil {
return "", body, &common.TaskError{StatusCode: 500, Message: "parse response failed"}
return "", body, &common.TaskError{StatusCode: http.StatusInternalServerError, Message: "parse response failed"}
}
if result.Code != 0 {
return "", body, &common.TaskError{
StatusCode: resp.StatusCode,
@@ -131,16 +311,18 @@ func (a *KlingAdaptor) DoResponse(_ context.Context, resp *http.Response, _ *com
ErrCode: fmt.Sprintf("%d", result.Code),
}
}
if result.Data.TaskID == "" {
return "", body, &common.TaskError{StatusCode: 500, Message: "upstream returned empty task id"}
return "", body, &common.TaskError{StatusCode: http.StatusInternalServerError, Message: "upstream returned empty task id"}
}
return result.Data.TaskID, body, nil
}
func (a *KlingAdaptor) FetchTask(baseURL, apiKey string, taskData []byte) (*http.Response, error) {
var data struct {
TaskID string `json:"task_id"`
TaskType string `json:"task_type"` // text2video 或 image2video
TaskType string `json:"task_type"`
}
if err := json.Unmarshal(taskData, &data); err != nil {
return nil, fmt.Errorf("kling: invalid task data: %w", err)
@@ -150,9 +332,10 @@ func (a *KlingAdaptor) FetchTask(baseURL, apiKey string, taskData []byte) (*http
if taskType == "" {
taskType = "text2video"
}
url := fmt.Sprintf("%s/v1/videos/%s/%s", strings.TrimRight(baseURL, "/"), taskType, data.TaskID)
req, err := http.NewRequest("GET", url, nil)
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return nil, err
}
@@ -162,27 +345,14 @@ func (a *KlingAdaptor) FetchTask(baseURL, apiKey string, taskData []byte) (*http
} else {
req.Header.Set("Authorization", "Bearer "+buildKlingJWT(apiKey))
}
req.Header.Set("Content-Type", "application/json")
client := &http.Client{Timeout: 30 * 1e9}
return client.Do(req)
}
func (a *KlingAdaptor) ParseTaskResult(body []byte) (*common.TaskInfo, error) {
var resp struct {
Code int `json:"code"`
Message string `json:"message"`
Data struct {
TaskStatus string `json:"task_status"`
TaskStatusMsg string `json:"task_status_msg"`
TaskResult struct {
Videos []struct {
ID string `json:"id"`
URL string `json:"url"`
Duration string `json:"duration"`
} `json:"videos"`
} `json:"task_result"`
} `json:"data"`
}
var resp klingTaskResponse
if err := json.Unmarshal(body, &resp); err != nil {
return nil, fmt.Errorf("kling: parse task result: %w", err)
}
@@ -198,6 +368,7 @@ func (a *KlingAdaptor) ParseTaskResult(body []byte) (*common.TaskInfo, error) {
switch resp.Data.TaskStatus {
case "submitted":
info.Status = common.TaskStatusSubmitted
info.Progress = "10%"
case "processing":
info.Status = common.TaskStatusInProgress
info.Progress = "50%"
@@ -212,15 +383,24 @@ func (a *KlingAdaptor) ParseTaskResult(body []byte) (*common.TaskInfo, error) {
info.FailReason = resp.Data.TaskStatusMsg
default:
info.Status = common.TaskStatusSubmitted
info.Progress = "10%"
}
return info, nil
}
func (a *KlingAdaptor) GetModelList() []string {
return []string{"kling-v1", "kling-v1-6", "kling-v2-master"}
return ModelList
}
func (a *KlingAdaptor) GetChannelName() string {
return channelName
}
// ==================== 辅助函数 ====================
func parseInt(s string) (int, error) {
var n int
_, err := fmt.Sscanf(s, "%d", &n)
return n, err
}