Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 

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