Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 

392 lignes
11 KiB

  1. package aiping
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io"
  6. "net/http"
  7. "strings"
  8. "time"
  9. "github.com/QuantumNous/new-api/common"
  10. "github.com/QuantumNous/new-api/dto"
  11. "github.com/QuantumNous/new-api/model"
  12. "github.com/QuantumNous/new-api/relay/channel"
  13. taskcommon "github.com/QuantumNous/new-api/relay/channel/task/taskcommon"
  14. relaycommon "github.com/QuantumNous/new-api/relay/common"
  15. "github.com/QuantumNous/new-api/service"
  16. "github.com/gin-gonic/gin"
  17. "github.com/pkg/errors"
  18. "github.com/shopspring/decimal"
  19. )
  20. type responsePayload struct {
  21. Code int `json:"code"`
  22. Message string `json:"message"`
  23. RequestID string `json:"request_id"`
  24. AipingID string `json:"aiping_id"`
  25. Data struct {
  26. TaskID string `json:"task_id"`
  27. TaskStatus string `json:"task_status"`
  28. TaskStatusMsg string `json:"task_status_msg"`
  29. TaskInfo any `json:"task_info,omitempty"`
  30. TaskResult struct {
  31. Videos []struct {
  32. ID string `json:"id"`
  33. URL string `json:"url"`
  34. WatermarkURL string `json:"watermark_url,omitempty"`
  35. Duration string `json:"duration"`
  36. } `json:"videos"`
  37. } `json:"task_result"`
  38. FinalUnitDeduction any `json:"final_unit_deduction"`
  39. CreatedAt int64 `json:"created_at"`
  40. UpdatedAt int64 `json:"updated_at"`
  41. } `json:"data"`
  42. }
  43. type TaskAdaptor struct {
  44. taskcommon.BaseBilling
  45. apiKey string
  46. baseURL string
  47. }
  48. func (a *TaskAdaptor) Init(info *relaycommon.RelayInfo) {
  49. a.apiKey = info.ApiKey
  50. a.baseURL = strings.TrimRight(info.ChannelBaseUrl, "/")
  51. }
  52. func (a *TaskAdaptor) ValidateRequestAndSetAction(c *gin.Context, info *relaycommon.RelayInfo) *dto.TaskError {
  53. req, err := relaycommon.GetTaskRequest(c)
  54. if err != nil {
  55. return service.TaskErrorWrapperLocal(fmt.Errorf("task request not prepared"), "invalid_request", http.StatusBadRequest)
  56. }
  57. modelName := resolveRequestModelName(req, info)
  58. if isOmniModel(modelName) && (info.Action == ActionText2Video || info.Action == ActionImage2Video) {
  59. return service.TaskErrorWrapperLocal(
  60. fmt.Errorf("model %s must use /v1/videos/omni-video", modelName),
  61. "invalid_model_route",
  62. http.StatusUnprocessableEntity,
  63. )
  64. }
  65. return nil
  66. }
  67. func (a *TaskAdaptor) BuildRequestURL(info *relaycommon.RelayInfo) (string, error) {
  68. route, ok := FindRouteByAction(info.Action)
  69. if !ok {
  70. return "", fmt.Errorf("unsupported kling aiping action: %s", info.Action)
  71. }
  72. return a.baseURL + route.UpstreamPath, nil
  73. }
  74. func (a *TaskAdaptor) BuildRequestHeader(c *gin.Context, req *http.Request, info *relaycommon.RelayInfo) error {
  75. req.Header.Set("Content-Type", "application/json")
  76. req.Header.Set("Accept", "application/json")
  77. req.Header.Set("Authorization", "Bearer "+a.apiKey)
  78. return nil
  79. }
  80. func (a *TaskAdaptor) BuildRequestBody(c *gin.Context, info *relaycommon.RelayInfo) (io.Reader, error) {
  81. req, err := relaycommon.GetTaskRequest(c)
  82. if err != nil {
  83. return nil, err
  84. }
  85. body := cloneMap(req.Metadata)
  86. normalizeRequestBody(body, info)
  87. data, err := common.Marshal(body)
  88. if err != nil {
  89. return nil, err
  90. }
  91. return bytes.NewReader(data), nil
  92. }
  93. func (a *TaskAdaptor) DoRequest(c *gin.Context, info *relaycommon.RelayInfo, requestBody io.Reader) (*http.Response, error) {
  94. return channel.DoTaskApiRequest(a, c, info, requestBody)
  95. }
  96. func (a *TaskAdaptor) DoResponse(c *gin.Context, resp *http.Response, info *relaycommon.RelayInfo) (string, []byte, *dto.TaskError) {
  97. responseBody, err := io.ReadAll(resp.Body)
  98. if err != nil {
  99. return "", nil, service.TaskErrorWrapper(err, "read_response_body_failed", http.StatusInternalServerError)
  100. }
  101. _ = resp.Body.Close()
  102. var parsed responsePayload
  103. if err := common.Unmarshal(responseBody, &parsed); err != nil {
  104. return "", nil, service.TaskErrorWrapper(errors.Wrapf(err, "body: %s", responseBody), "unmarshal_response_body_failed", http.StatusInternalServerError)
  105. }
  106. if parsed.Code != 0 {
  107. msg := parsed.Message
  108. if msg == "" {
  109. msg = "upstream task failed"
  110. }
  111. return "", nil, service.TaskErrorWrapper(fmt.Errorf("%s", msg), "task_failed", http.StatusBadRequest)
  112. }
  113. upstreamTaskID := parsed.Data.TaskID
  114. if strings.TrimSpace(upstreamTaskID) == "" {
  115. return "", nil, service.TaskErrorWrapper(fmt.Errorf("task_id is empty"), "invalid_response", http.StatusInternalServerError)
  116. }
  117. clientPayload := map[string]any{}
  118. if err := common.Unmarshal(responseBody, &clientPayload); err != nil {
  119. return "", nil, service.TaskErrorWrapper(err, "unmarshal_response_body_failed", http.StatusInternalServerError)
  120. }
  121. sanitizeNativeTaskPayload(clientPayload, info.PublicTaskID)
  122. c.JSON(http.StatusOK, clientPayload)
  123. return upstreamTaskID, responseBody, nil
  124. }
  125. func (a *TaskAdaptor) FetchTask(baseURL, key string, body map[string]any, proxy string) (*http.Response, error) {
  126. taskID, _ := body["task_id"].(string)
  127. if strings.TrimSpace(taskID) == "" {
  128. taskID, _ = body["upstream_task_id"].(string)
  129. }
  130. action, _ := body["action"].(string)
  131. if strings.TrimSpace(taskID) == "" {
  132. return nil, fmt.Errorf("invalid task_id")
  133. }
  134. route, ok := FindFetchRouteByAction(action)
  135. if !ok {
  136. return nil, fmt.Errorf("unsupported kling aiping action: %s", action)
  137. }
  138. url := strings.TrimRight(baseURL, "/") + strings.Replace(route.UpstreamPath, ":task_id", taskID, 1)
  139. req, err := http.NewRequest(http.MethodGet, url, nil)
  140. if err != nil {
  141. return nil, err
  142. }
  143. req.Header.Set("Accept", "application/json")
  144. req.Header.Set("Authorization", "Bearer "+key)
  145. client, err := service.GetHttpClientWithProxy(proxy)
  146. if err != nil {
  147. return nil, fmt.Errorf("new proxy http client failed: %w", err)
  148. }
  149. return client.Do(req)
  150. }
  151. func (a *TaskAdaptor) ParseTaskResult(respBody []byte) (*relaycommon.TaskInfo, error) {
  152. var parsed responsePayload
  153. if err := common.Unmarshal(respBody, &parsed); err != nil {
  154. return nil, errors.Wrap(err, "unmarshal kling aiping task data failed")
  155. }
  156. info := &relaycommon.TaskInfo{
  157. Code: parsed.Code,
  158. TaskID: parsed.Data.TaskID,
  159. Reason: parsed.Data.TaskStatusMsg,
  160. }
  161. switch parsed.Data.TaskStatus {
  162. case "submitted":
  163. info.Status = model.TaskStatusSubmitted
  164. info.Progress = taskcommon.ProgressSubmitted
  165. case "processing":
  166. info.Status = model.TaskStatusInProgress
  167. info.Progress = taskcommon.ProgressInProgress
  168. case "succeed":
  169. info.Status = model.TaskStatusSuccess
  170. info.Progress = taskcommon.ProgressComplete
  171. if tokens := finalUnitDeductionTokens(parsed.Data.FinalUnitDeduction); tokens > 0 {
  172. info.CompletionTokens = tokens
  173. info.TotalTokens = tokens
  174. }
  175. case "failed":
  176. info.Status = model.TaskStatusFailure
  177. info.Progress = taskcommon.ProgressComplete
  178. default:
  179. info.Status = model.TaskStatusInProgress
  180. info.Progress = taskcommon.ProgressInProgress
  181. }
  182. if len(parsed.Data.TaskResult.Videos) > 0 {
  183. info.Url = parsed.Data.TaskResult.Videos[0].URL
  184. }
  185. return info, nil
  186. }
  187. func finalUnitDeductionTokens(value any) int {
  188. var d decimal.Decimal
  189. var err error
  190. switch v := value.(type) {
  191. case string:
  192. if strings.TrimSpace(v) == "" {
  193. return 0
  194. }
  195. d, err = decimal.NewFromString(strings.TrimSpace(v))
  196. case float64:
  197. d = decimal.NewFromFloat(v)
  198. case int:
  199. d = decimal.NewFromInt(int64(v))
  200. case int64:
  201. d = decimal.NewFromInt(v)
  202. default:
  203. return 0
  204. }
  205. if err != nil || !d.IsPositive() {
  206. return 0
  207. }
  208. return int(d.Mul(decimal.NewFromInt(1000000)).IntPart())
  209. }
  210. func (a *TaskAdaptor) GetModelList() []string {
  211. return Models()
  212. }
  213. func (a *TaskAdaptor) GetChannelName() string {
  214. return "kling-aiping"
  215. }
  216. func normalizeRequestBody(body map[string]any, info *relaycommon.RelayInfo) {
  217. delete(body, "action_control")
  218. delete(body, "uid")
  219. delete(body, "create_at")
  220. delete(body, "_standard_model")
  221. modelName := stringValue(body["model_name"])
  222. if modelName == "" {
  223. modelName = stringValue(body["model"])
  224. }
  225. if info != nil && info.IsModelMapped && info.UpstreamModelName != "" {
  226. modelName = info.UpstreamModelName
  227. }
  228. if modelName != "" {
  229. body["model_name"] = canonicalAipingModelName(modelName)
  230. }
  231. delete(body, "model")
  232. duration := body["duration"]
  233. if duration == nil {
  234. duration = body["seconds"]
  235. }
  236. if duration != nil {
  237. body["duration"] = fmt.Sprint(duration)
  238. }
  239. delete(body, "seconds")
  240. if imageList, ok := body["reference_images"]; ok {
  241. if _, exists := body["image_list"]; !exists {
  242. body["image_list"] = imageList
  243. }
  244. delete(body, "reference_images")
  245. }
  246. if imageList, ok := normalizeImageList(body["image_list"]); ok {
  247. body["image_list"] = imageList
  248. }
  249. if body["watermark_info"] == nil {
  250. return
  251. }
  252. }
  253. func normalizeImageList(v any) ([]map[string]any, bool) {
  254. items, ok := v.([]any)
  255. if !ok {
  256. return nil, false
  257. }
  258. out := make([]map[string]any, 0, len(items))
  259. for _, item := range items {
  260. switch val := item.(type) {
  261. case string:
  262. out = append(out, map[string]any{"image": val})
  263. case map[string]any:
  264. image := firstString(val, "image", "image_url", "url", "base64")
  265. if image == "" {
  266. out = append(out, val)
  267. continue
  268. }
  269. out = append(out, map[string]any{"image": image})
  270. default:
  271. out = append(out, map[string]any{"image": fmt.Sprint(val)})
  272. }
  273. }
  274. return out, true
  275. }
  276. func sanitizeNativeTaskPayload(payload map[string]any, publicTaskID string) {
  277. delete(payload, "aiping_id")
  278. data, _ := payload["data"].(map[string]any)
  279. if data == nil {
  280. return
  281. }
  282. data["task_id"] = publicTaskID
  283. ensureWatermarkURL(data)
  284. if _, ok := data["created_at"]; !ok {
  285. data["created_at"] = time.Now().UnixMilli()
  286. }
  287. if _, ok := data["updated_at"]; !ok {
  288. data["updated_at"] = data["created_at"]
  289. }
  290. }
  291. func ensureWatermarkURL(data map[string]any) {
  292. taskResult, _ := data["task_result"].(map[string]any)
  293. if taskResult == nil {
  294. return
  295. }
  296. videos, _ := taskResult["videos"].([]any)
  297. for _, videoAny := range videos {
  298. video, _ := videoAny.(map[string]any)
  299. if video == nil {
  300. continue
  301. }
  302. if _, ok := video["watermark_url"]; !ok {
  303. video["watermark_url"] = ""
  304. }
  305. }
  306. }
  307. func cloneMap(in map[string]any) map[string]any {
  308. out := make(map[string]any, len(in))
  309. for k, v := range in {
  310. out[k] = v
  311. }
  312. return out
  313. }
  314. func stringValue(v any) string {
  315. if s, ok := v.(string); ok {
  316. return strings.TrimSpace(s)
  317. }
  318. return ""
  319. }
  320. func canonicalAipingModelName(modelName string) string {
  321. trimmed := strings.TrimSpace(modelName)
  322. switch strings.ToLower(trimmed) {
  323. case "kling-v1":
  324. return "Kling-V1"
  325. case "kling-v1-6":
  326. return "Kling-V1.6"
  327. case "kling-v2-6":
  328. return "Kling-V2.6"
  329. case "kling-v3":
  330. return "Kling-V3"
  331. case "kling-video-o1":
  332. return "Kling-Video-O1"
  333. case "kling-v3-omni":
  334. return "Kling-V3-Omni"
  335. default:
  336. return trimmed
  337. }
  338. }
  339. func firstString(m map[string]any, keys ...string) string {
  340. for _, key := range keys {
  341. if s := stringValue(m[key]); s != "" {
  342. return s
  343. }
  344. }
  345. return ""
  346. }
  347. func resolveRequestModelName(req relaycommon.TaskSubmitReq, info *relaycommon.RelayInfo) string {
  348. if info != nil && info.ChannelMeta != nil && info.IsModelMapped && info.UpstreamModelName != "" {
  349. return strings.TrimSpace(info.UpstreamModelName)
  350. }
  351. if modelName := firstString(req.Metadata, "model_name", "model"); modelName != "" {
  352. return modelName
  353. }
  354. return strings.TrimSpace(req.Model)
  355. }
  356. func isOmniModel(modelName string) bool {
  357. normalized := strings.ToLower(strings.TrimSpace(modelName))
  358. return normalized == "kling-video-o1" || normalized == "kling-v3-omni"
  359. }