You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

485 lines
14 KiB

  1. package controller
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io"
  6. "net/http"
  7. "strconv"
  8. "strings"
  9. "github.com/QuantumNous/new-api/common"
  10. "github.com/QuantumNous/new-api/constant"
  11. "github.com/QuantumNous/new-api/dto"
  12. "github.com/QuantumNous/new-api/logger"
  13. "github.com/QuantumNous/new-api/middleware"
  14. "github.com/QuantumNous/new-api/model"
  15. "github.com/QuantumNous/new-api/relay"
  16. klingaiping "github.com/QuantumNous/new-api/relay/channel/task/kling/aiping"
  17. relaycommon "github.com/QuantumNous/new-api/relay/common"
  18. "github.com/QuantumNous/new-api/service"
  19. "github.com/QuantumNous/new-api/types"
  20. "github.com/gin-gonic/gin"
  21. )
  22. func KlingAipingNativeTaskSubmit(c *gin.Context) {
  23. route, ok := klingaiping.FindRoute(c.Request.Method, c.FullPath(), klingaiping.RouteKindSubmit)
  24. if !ok {
  25. c.JSON(http.StatusNotFound, gin.H{"code": 404, "message": "route not found"})
  26. return
  27. }
  28. relayInfo, err := relaycommon.GenRelayInfo(c, types.RelayFormatTask, nil, nil)
  29. if err != nil {
  30. c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": err.Error()})
  31. return
  32. }
  33. payload, err := readJSONPayload(c)
  34. if err != nil {
  35. c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": err.Error()})
  36. return
  37. }
  38. configureKlingAipingTaskRelayInfo(c, relayInfo, route, payload)
  39. if bound := resolveKlingAipingBoundChannel(c, relayInfo); bound != nil {
  40. relayInfo.LockedChannel = bound
  41. }
  42. relayTaskWithInfo(c, relayInfo)
  43. }
  44. func resolveKlingAipingBoundChannel(c *gin.Context, relayInfo *relaycommon.RelayInfo) *model.Channel {
  45. group := concreteTaskVideoBindingGroup(c, relayInfo.TokenGroup)
  46. if group == "" {
  47. return nil
  48. }
  49. channel, err := service.ResolveKlingAssetChannel(c.GetInt("id"), group)
  50. if err != nil {
  51. logger.LogError(c, fmt.Sprintf("resolve kling aiping bound channel failed: %v", err))
  52. return nil
  53. }
  54. if channel == nil || !service.IsUsableKlingAssetChannel(channel, group) {
  55. return nil
  56. }
  57. if !model.IsChannelEnabledForGroupModel(group, relayInfo.OriginModelName, channel.Id) {
  58. return nil
  59. }
  60. return channel
  61. }
  62. func configureKlingAipingTaskRelayInfo(c *gin.Context, relayInfo *relaycommon.RelayInfo, route klingaiping.Route, payload map[string]any) {
  63. // Native Kling routes bypass Distribute(), so force relayTaskWithInfo to
  64. // select a channel instead of reading a preselected one from context.
  65. if relayInfo.ChannelMeta == nil {
  66. relayInfo.ChannelMeta = &relaycommon.ChannelMeta{}
  67. }
  68. modelName := resolveKlingAipingModel(payload, route)
  69. relayInfo.OriginModelName = modelName
  70. relayInfo.Action = route.Action
  71. relaycommon.StoreTaskRequest(c, relayInfo, route.Action, relaycommon.TaskSubmitReq{
  72. Model: modelName,
  73. Prompt: stringFromMap(payload, "prompt"),
  74. Seconds: fmt.Sprint(payload["seconds"]),
  75. Metadata: payload,
  76. })
  77. }
  78. func KlingAipingNativeTaskFetch(c *gin.Context) {
  79. route, ok := klingaiping.FindRoute(c.Request.Method, c.FullPath(), klingaiping.RouteKindFetch)
  80. if !ok {
  81. c.JSON(http.StatusNotFound, gin.H{"code": 404, "message": "route not found"})
  82. return
  83. }
  84. task, ok := getKlingAipingUserTask(c, c.Param("task_id"), route.Action)
  85. if !ok {
  86. return
  87. }
  88. refreshKlingAipingTaskIfNeeded(task)
  89. c.JSON(http.StatusOK, buildKlingAipingTaskPayload(task))
  90. }
  91. func KlingAipingNativeTaskList(c *gin.Context) {
  92. route, ok := klingaiping.FindRoute(c.Request.Method, c.FullPath(), klingaiping.RouteKindList)
  93. if !ok {
  94. c.JSON(http.StatusNotFound, gin.H{"code": 404, "message": "route not found"})
  95. return
  96. }
  97. pageNum, pageSize, err := parseKlingAipingPage(c)
  98. if err != nil {
  99. c.JSON(http.StatusUnprocessableEntity, gin.H{"code": 422, "message": err.Error()})
  100. return
  101. }
  102. tasks := model.TaskGetAllUserTask(c.GetInt("id"), (pageNum-1)*pageSize, pageSize, model.SyncTaskQueryParams{
  103. Platform: constant.TaskPlatform(strconv.Itoa(constant.ChannelTypeKlingAiping)),
  104. Action: route.Action,
  105. })
  106. data := make([]any, 0, len(tasks))
  107. for _, task := range tasks {
  108. data = append(data, taskDataObject(task))
  109. }
  110. c.JSON(http.StatusOK, gin.H{
  111. "code": 0,
  112. "message": "success",
  113. "request_id": c.GetString(common.RequestIdKey),
  114. "data": data,
  115. })
  116. }
  117. func KlingAipingNativeProxy(c *gin.Context) {
  118. route, ok := klingaiping.FindRoute(c.Request.Method, c.FullPath(), klingaiping.RouteKindProxy)
  119. if !ok {
  120. c.JSON(http.StatusNotFound, gin.H{"code": 404, "message": "route not found"})
  121. return
  122. }
  123. group := common.GetContextKeyString(c, constant.ContextKeyUsingGroup)
  124. channel := resolveKlingAipingBoundChannelForProxy(c, group, route.BillingModel)
  125. if channel == nil {
  126. var err error
  127. channel, _, err = service.CacheGetRandomSatisfiedChannel(&service.RetryParam{
  128. Ctx: c,
  129. TokenGroup: group,
  130. ModelName: route.BillingModel,
  131. Retry: common.GetPointer(0),
  132. RequiredChannelType: constant.ChannelTypeKlingAiping,
  133. })
  134. if err != nil {
  135. c.JSON(http.StatusServiceUnavailable, gin.H{"code": 503, "message": err.Error()})
  136. return
  137. }
  138. }
  139. if setupErr := middleware.SetupContextForSelectedChannel(c, channel, route.BillingModel); setupErr != nil {
  140. c.JSON(setupErr.StatusCode, gin.H{"code": setupErr.GetErrorCode(), "message": setupErr.Error()})
  141. return
  142. }
  143. resp, err := doKlingAipingProxyRequest(c, route, channel)
  144. if err != nil {
  145. c.JSON(http.StatusBadGateway, gin.H{"code": 502, "message": err.Error()})
  146. return
  147. }
  148. defer resp.Body.Close()
  149. copyProxyResponse(c, resp)
  150. }
  151. func resolveKlingAipingBoundChannelForProxy(c *gin.Context, group, billingModel string) *model.Channel {
  152. group = strings.TrimSpace(group)
  153. if group == "" || group == "auto" || strings.TrimSpace(billingModel) == "" {
  154. return nil
  155. }
  156. channel, err := service.ResolveKlingAssetChannel(c.GetInt("id"), group)
  157. if err != nil {
  158. logger.LogError(c, fmt.Sprintf("resolve kling aiping bound channel (proxy) failed: %v", err))
  159. return nil
  160. }
  161. if channel == nil || !service.IsUsableKlingAssetChannel(channel, group) {
  162. return nil
  163. }
  164. if !model.IsChannelEnabledForGroupModel(group, billingModel, channel.Id) {
  165. return nil
  166. }
  167. return channel
  168. }
  169. func readJSONPayload(c *gin.Context) (map[string]any, error) {
  170. body, err := common.GetBodyStorage(c)
  171. if err != nil {
  172. return nil, err
  173. }
  174. data, err := body.Bytes()
  175. if err != nil {
  176. return nil, err
  177. }
  178. _, _ = body.Seek(0, io.SeekStart)
  179. c.Request.Body = io.NopCloser(body)
  180. payload := map[string]any{}
  181. if strings.TrimSpace(string(data)) == "" {
  182. return payload, nil
  183. }
  184. if err := common.Unmarshal(data, &payload); err != nil {
  185. return nil, err
  186. }
  187. return payload, nil
  188. }
  189. func resolveKlingAipingModel(payload map[string]any, route klingaiping.Route) string {
  190. if modelName := stringFromMap(payload, "model_name"); modelName != "" {
  191. return modelName
  192. }
  193. if modelName := stringFromMap(payload, "model"); modelName != "" {
  194. return modelName
  195. }
  196. if route.BillingModel != "" {
  197. return route.BillingModel
  198. }
  199. return "kling-v3"
  200. }
  201. func stringFromMap(payload map[string]any, key string) string {
  202. if value, ok := payload[key].(string); ok {
  203. return strings.TrimSpace(value)
  204. }
  205. return ""
  206. }
  207. func getKlingAipingUserTask(c *gin.Context, taskID string, action string) (*model.Task, bool) {
  208. task, exist, err := model.GetByTaskId(c.GetInt("id"), taskID)
  209. if err != nil {
  210. c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": err.Error()})
  211. return nil, false
  212. }
  213. if !exist || task.Platform != constant.TaskPlatform(strconv.Itoa(constant.ChannelTypeKlingAiping)) || task.Action != action {
  214. c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "task not found"})
  215. return nil, false
  216. }
  217. return task, true
  218. }
  219. func refreshKlingAipingTaskIfNeeded(task *model.Task) {
  220. if task.Status == model.TaskStatusSuccess || task.Status == model.TaskStatusFailure {
  221. return
  222. }
  223. channelModel, err := model.GetChannelById(task.ChannelId, true)
  224. if err != nil || channelModel == nil {
  225. return
  226. }
  227. adaptor := relay.GetTaskAdaptor(task.Platform)
  228. if adaptor == nil {
  229. return
  230. }
  231. resp, err := adaptor.FetchTask(channelModel.GetBaseURL(), channelModel.Key, map[string]any{
  232. "task_id": task.GetUpstreamTaskID(),
  233. "action": task.Action,
  234. }, channelModel.GetSetting().Proxy)
  235. if err != nil || resp == nil {
  236. return
  237. }
  238. defer resp.Body.Close()
  239. body, err := io.ReadAll(resp.Body)
  240. if err != nil || resp.StatusCode < 200 || resp.StatusCode >= 300 {
  241. return
  242. }
  243. taskInfo, err := adaptor.ParseTaskResult(body)
  244. if err != nil || taskInfo == nil {
  245. return
  246. }
  247. snap := task.Snapshot()
  248. task.Data = body
  249. if taskInfo.Status != "" {
  250. task.Status = model.TaskStatus(taskInfo.Status)
  251. }
  252. if taskInfo.Progress != "" {
  253. task.Progress = taskInfo.Progress
  254. }
  255. if taskInfo.Url != "" {
  256. task.PrivateData.ResultURL = taskInfo.Url
  257. }
  258. if !snap.Equal(task.Snapshot()) {
  259. _, _ = task.UpdateWithStatus(snap.Status)
  260. }
  261. }
  262. func buildKlingAipingTaskPayload(task *model.Task) map[string]any {
  263. payload := map[string]any{
  264. "code": 0,
  265. "message": "success",
  266. "data": taskDataObject(task),
  267. }
  268. return payload
  269. }
  270. func taskDataObject(task *model.Task) map[string]any {
  271. payload := map[string]any{}
  272. _ = common.Unmarshal(task.Data, &payload)
  273. delete(payload, "aiping_id")
  274. data, _ := payload["data"].(map[string]any)
  275. if data == nil {
  276. data = map[string]any{}
  277. }
  278. data["task_id"] = task.TaskID
  279. if _, ok := data["task_status"]; !ok {
  280. data["task_status"] = mapKlingAipingTaskStatus(task.Status)
  281. }
  282. if _, ok := data["task_status_msg"]; !ok {
  283. data["task_status_msg"] = task.FailReason
  284. }
  285. if _, ok := data["created_at"]; !ok && task.CreatedAt != 0 {
  286. data["created_at"] = task.CreatedAt
  287. }
  288. if _, ok := data["updated_at"]; !ok && task.UpdatedAt != 0 {
  289. data["updated_at"] = task.UpdatedAt
  290. }
  291. ensureKlingAipingWatermarkURL(data)
  292. return data
  293. }
  294. func mapKlingAipingTaskStatus(status model.TaskStatus) string {
  295. switch status {
  296. case model.TaskStatusSubmitted, model.TaskStatusQueued:
  297. return "submitted"
  298. case model.TaskStatusInProgress:
  299. return "processing"
  300. case model.TaskStatusSuccess:
  301. return "succeed"
  302. case model.TaskStatusFailure:
  303. return "failed"
  304. default:
  305. return "processing"
  306. }
  307. }
  308. func ensureKlingAipingWatermarkURL(data map[string]any) {
  309. taskResult, _ := data["task_result"].(map[string]any)
  310. if taskResult == nil {
  311. return
  312. }
  313. videos, _ := taskResult["videos"].([]any)
  314. for _, videoAny := range videos {
  315. video, _ := videoAny.(map[string]any)
  316. if video == nil {
  317. continue
  318. }
  319. if _, ok := video["watermark_url"]; !ok {
  320. video["watermark_url"] = ""
  321. }
  322. }
  323. }
  324. func parseKlingAipingPage(c *gin.Context) (int, int, error) {
  325. pageNum := parseIntDefault(c.Query("pageNum"), 1)
  326. pageSize := parseIntDefault(c.Query("pageSize"), 30)
  327. if pageNum < 1 || pageNum > 1000 {
  328. return 0, 0, fmt.Errorf("pageNum must be in [1, 1000]")
  329. }
  330. if pageSize < 1 || pageSize > 500 {
  331. return 0, 0, fmt.Errorf("pageSize must be in [1, 500]")
  332. }
  333. return pageNum, pageSize, nil
  334. }
  335. func parseIntDefault(raw string, fallback int) int {
  336. if strings.TrimSpace(raw) == "" {
  337. return fallback
  338. }
  339. v, err := strconv.Atoi(raw)
  340. if err != nil {
  341. return -1
  342. }
  343. return v
  344. }
  345. func doKlingAipingProxyRequest(c *gin.Context, route klingaiping.Route, channelModel *model.Channel) (*http.Response, error) {
  346. baseURL := strings.TrimRight(channelModel.GetBaseURL(), "/")
  347. upstreamPath := strings.Replace(route.UpstreamPath, ":id", c.Param("id"), 1)
  348. url := baseURL + upstreamPath
  349. if c.Request.URL.RawQuery != "" {
  350. url += "?" + c.Request.URL.RawQuery
  351. }
  352. var body io.Reader
  353. if c.Request.Method != http.MethodGet {
  354. data, err := proxyBodyBytes(c, route)
  355. if err != nil {
  356. return nil, err
  357. }
  358. body = bytes.NewReader(data)
  359. }
  360. req, err := http.NewRequest(c.Request.Method, url, body)
  361. if err != nil {
  362. return nil, err
  363. }
  364. req.Header.Set("Accept", "application/json")
  365. req.Header.Set("Content-Type", "application/json")
  366. key := common.GetContextKeyString(c, constant.ContextKeyChannelKey)
  367. if key == "" {
  368. key = channelModel.Key
  369. }
  370. req.Header.Set("Authorization", "Bearer "+key)
  371. client, err := service.GetHttpClientWithProxy(channelModel.GetSetting().Proxy)
  372. if err != nil {
  373. return nil, err
  374. }
  375. return client.Do(req)
  376. }
  377. func proxyBodyBytes(c *gin.Context, route klingaiping.Route) ([]byte, error) {
  378. storage, err := common.GetBodyStorage(c)
  379. if err != nil {
  380. return nil, err
  381. }
  382. return storage.Bytes()
  383. }
  384. func copyProxyResponse(c *gin.Context, resp *http.Response) {
  385. if resp.StatusCode >= http.StatusBadRequest {
  386. copyNormalizedProxyError(c, resp)
  387. return
  388. }
  389. for key, values := range resp.Header {
  390. for _, value := range values {
  391. c.Writer.Header().Add(key, value)
  392. }
  393. }
  394. c.Status(resp.StatusCode)
  395. _, _ = io.Copy(c.Writer, resp.Body)
  396. }
  397. func copyNormalizedProxyError(c *gin.Context, resp *http.Response) {
  398. body, _ := io.ReadAll(resp.Body)
  399. message := strings.TrimSpace(string(body))
  400. payload := map[string]any{}
  401. if len(body) > 0 && common.Unmarshal(body, &payload) == nil {
  402. if msg := stringFromMap(payload, "message"); msg != "" {
  403. message = msg
  404. } else if msg := stringFromMap(payload, "msg"); msg != "" {
  405. message = msg
  406. } else if detail, ok := payload["detail"]; ok {
  407. if detailMap, ok := detail.(map[string]any); ok {
  408. if msg := stringFromMap(detailMap, "message"); msg != "" {
  409. message = msg
  410. } else if msg := stringFromMap(detailMap, "msg"); msg != "" {
  411. message = msg
  412. } else {
  413. message = fmt.Sprint(detail)
  414. }
  415. } else {
  416. message = fmt.Sprint(detail)
  417. }
  418. }
  419. delete(payload, "msg")
  420. } else {
  421. payload = map[string]any{}
  422. }
  423. if message == "" {
  424. message = resp.Status
  425. }
  426. if _, ok := payload["code"]; !ok {
  427. payload["code"] = resp.StatusCode
  428. }
  429. payload["message"] = message
  430. payload["request_id"] = c.GetString(common.RequestIdKey)
  431. c.JSON(resp.StatusCode, payload)
  432. }
  433. func normalizeKlingAipingTaskError(taskErr *dto.TaskError) {
  434. if taskErr == nil || strings.TrimSpace(taskErr.Message) == "" {
  435. return
  436. }
  437. payload := map[string]any{}
  438. if common.Unmarshal([]byte(taskErr.Message), &payload) != nil {
  439. return
  440. }
  441. if msg := stringFromMap(payload, "message"); msg != "" {
  442. taskErr.Message = msg
  443. return
  444. }
  445. if msg := stringFromMap(payload, "msg"); msg != "" {
  446. taskErr.Message = msg
  447. return
  448. }
  449. if detail, ok := payload["detail"].(map[string]any); ok {
  450. if msg := stringFromMap(detail, "message"); msg != "" {
  451. taskErr.Message = msg
  452. }
  453. }
  454. }