Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.
 
 
 

527 rader
15 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.GetBoundKlingAssetChannelForModel(c.GetInt("id"), group, relayInfo.OriginModelName)
  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. AllowedChannelTypes: service.VideoAssetChannelTypesForFamily(service.VideoAssetFamilyKling),
  157. })
  158. if err != nil {
  159. c.JSON(http.StatusServiceUnavailable, gin.H{"code": 503, "message": err.Error()})
  160. return
  161. }
  162. if err := persistKlingAipingProxyBindingIfNeeded(c.GetInt("id"), group, route.BillingModel, channel); err != nil {
  163. c.JSON(http.StatusServiceUnavailable, gin.H{"code": 503, "message": err.Error()})
  164. return
  165. }
  166. }
  167. if setupErr := middleware.SetupContextForSelectedChannel(c, channel, route.BillingModel); setupErr != nil {
  168. c.JSON(setupErr.StatusCode, gin.H{"code": setupErr.GetErrorCode(), "message": setupErr.Error()})
  169. return
  170. }
  171. resp, err := doKlingAipingProxyRequest(c, route, channel)
  172. if err != nil {
  173. c.JSON(http.StatusBadGateway, gin.H{"code": 502, "message": err.Error()})
  174. return
  175. }
  176. defer resp.Body.Close()
  177. copyProxyResponse(c, resp)
  178. }
  179. func resolveKlingAipingBoundChannelForProxy(c *gin.Context, group, billingModel string) *model.Channel {
  180. group = strings.TrimSpace(group)
  181. if group == "" || group == "auto" || strings.TrimSpace(billingModel) == "" {
  182. return nil
  183. }
  184. channel, err := service.GetBoundKlingAssetChannelForModel(c.GetInt("id"), group, billingModel)
  185. if err != nil {
  186. logger.LogError(c, fmt.Sprintf("resolve kling aiping bound channel (proxy) failed: %v", err))
  187. return nil
  188. }
  189. if channel == nil || !service.IsUsableKlingAssetChannel(channel, group) {
  190. return nil
  191. }
  192. if !model.IsChannelEnabledForGroupModel(group, billingModel, channel.Id) {
  193. return nil
  194. }
  195. return channel
  196. }
  197. func persistKlingAipingProxyBindingIfNeeded(userId int, group, billingModel string, channel *model.Channel) error {
  198. group = strings.TrimSpace(group)
  199. billingModel = strings.TrimSpace(billingModel)
  200. if group == "" || group == "auto" || billingModel == "" || channel == nil {
  201. return nil
  202. }
  203. if !service.IsUsableVideoAssetChannelForFamily(channel, group, billingModel, service.VideoAssetFamilyKling) {
  204. return nil
  205. }
  206. return service.BindVideoAssetChannel(userId, group, channel, service.VideoAssetFamilyKling)
  207. }
  208. func readJSONPayload(c *gin.Context) (map[string]any, error) {
  209. body, err := common.GetBodyStorage(c)
  210. if err != nil {
  211. return nil, err
  212. }
  213. data, err := body.Bytes()
  214. if err != nil {
  215. return nil, err
  216. }
  217. _, _ = body.Seek(0, io.SeekStart)
  218. c.Request.Body = io.NopCloser(body)
  219. payload := map[string]any{}
  220. if strings.TrimSpace(string(data)) == "" {
  221. return payload, nil
  222. }
  223. if err := common.Unmarshal(data, &payload); err != nil {
  224. return nil, err
  225. }
  226. return payload, nil
  227. }
  228. func resolveKlingAipingModel(payload map[string]any, route klingaiping.Route) string {
  229. if modelName := stringFromMap(payload, "model_name"); modelName != "" {
  230. return modelName
  231. }
  232. if modelName := stringFromMap(payload, "model"); modelName != "" {
  233. return modelName
  234. }
  235. if route.BillingModel != "" {
  236. return route.BillingModel
  237. }
  238. return "kling-v3"
  239. }
  240. func stringFromMap(payload map[string]any, key string) string {
  241. if value, ok := payload[key].(string); ok {
  242. return strings.TrimSpace(value)
  243. }
  244. return ""
  245. }
  246. func getKlingAipingUserTask(c *gin.Context, taskID string, action string) (*model.Task, bool) {
  247. task, exist, err := model.GetByTaskId(c.GetInt("id"), taskID)
  248. if err != nil {
  249. c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": err.Error()})
  250. return nil, false
  251. }
  252. if !exist || task.Platform != constant.TaskPlatform(strconv.Itoa(constant.ChannelTypeKlingAiping)) || task.Action != action {
  253. c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "task not found"})
  254. return nil, false
  255. }
  256. return task, true
  257. }
  258. func refreshKlingAipingTaskIfNeeded(task *model.Task) {
  259. if task.Status == model.TaskStatusSuccess || task.Status == model.TaskStatusFailure {
  260. return
  261. }
  262. channelModel, err := model.GetChannelById(task.ChannelId, true)
  263. if err != nil || channelModel == nil {
  264. return
  265. }
  266. adaptor := relay.GetTaskAdaptor(task.Platform)
  267. if adaptor == nil {
  268. return
  269. }
  270. resp, err := adaptor.FetchTask(channelModel.GetBaseURL(), channelModel.Key, map[string]any{
  271. "task_id": task.GetUpstreamTaskID(),
  272. "action": task.Action,
  273. }, channelModel.GetSetting().Proxy)
  274. if err != nil || resp == nil {
  275. return
  276. }
  277. defer resp.Body.Close()
  278. body, err := io.ReadAll(resp.Body)
  279. if err != nil || resp.StatusCode < 200 || resp.StatusCode >= 300 {
  280. return
  281. }
  282. taskInfo, err := adaptor.ParseTaskResult(body)
  283. if err != nil || taskInfo == nil {
  284. return
  285. }
  286. snap := task.Snapshot()
  287. task.Data = body
  288. if taskInfo.Status != "" {
  289. task.Status = model.TaskStatus(taskInfo.Status)
  290. }
  291. if taskInfo.Progress != "" {
  292. task.Progress = taskInfo.Progress
  293. }
  294. if taskInfo.Url != "" {
  295. task.PrivateData.ResultURL = taskInfo.Url
  296. }
  297. if !snap.Equal(task.Snapshot()) {
  298. _, _ = task.UpdateWithStatus(snap.Status)
  299. }
  300. }
  301. func buildKlingAipingTaskPayload(task *model.Task) map[string]any {
  302. payload := map[string]any{
  303. "code": 0,
  304. "message": "success",
  305. "data": taskDataObject(task),
  306. }
  307. return payload
  308. }
  309. func taskDataObject(task *model.Task) map[string]any {
  310. payload := map[string]any{}
  311. _ = common.Unmarshal(task.Data, &payload)
  312. delete(payload, "aiping_id")
  313. data, _ := payload["data"].(map[string]any)
  314. if data == nil {
  315. data = map[string]any{}
  316. }
  317. data["task_id"] = task.TaskID
  318. if _, ok := data["task_status"]; !ok {
  319. data["task_status"] = mapKlingAipingTaskStatus(task.Status)
  320. }
  321. if _, ok := data["task_status_msg"]; !ok {
  322. data["task_status_msg"] = task.FailReason
  323. }
  324. if _, ok := data["created_at"]; !ok && task.CreatedAt != 0 {
  325. data["created_at"] = task.CreatedAt
  326. }
  327. if _, ok := data["updated_at"]; !ok && task.UpdatedAt != 0 {
  328. data["updated_at"] = task.UpdatedAt
  329. }
  330. ensureKlingAipingWatermarkURL(data)
  331. return data
  332. }
  333. func mapKlingAipingTaskStatus(status model.TaskStatus) string {
  334. switch status {
  335. case model.TaskStatusSubmitted, model.TaskStatusQueued:
  336. return "submitted"
  337. case model.TaskStatusInProgress:
  338. return "processing"
  339. case model.TaskStatusSuccess:
  340. return "succeed"
  341. case model.TaskStatusFailure:
  342. return "failed"
  343. default:
  344. return "processing"
  345. }
  346. }
  347. func ensureKlingAipingWatermarkURL(data map[string]any) {
  348. taskResult, _ := data["task_result"].(map[string]any)
  349. if taskResult == nil {
  350. return
  351. }
  352. videos, _ := taskResult["videos"].([]any)
  353. for _, videoAny := range videos {
  354. video, _ := videoAny.(map[string]any)
  355. if video == nil {
  356. continue
  357. }
  358. if _, ok := video["watermark_url"]; !ok {
  359. video["watermark_url"] = ""
  360. }
  361. }
  362. }
  363. func parseKlingAipingPage(c *gin.Context) (int, int, error) {
  364. pageNum := parseIntDefault(c.Query("pageNum"), 1)
  365. pageSize := parseIntDefault(c.Query("pageSize"), 30)
  366. if pageNum < 1 || pageNum > 1000 {
  367. return 0, 0, fmt.Errorf("pageNum must be in [1, 1000]")
  368. }
  369. if pageSize < 1 || pageSize > 500 {
  370. return 0, 0, fmt.Errorf("pageSize must be in [1, 500]")
  371. }
  372. return pageNum, pageSize, nil
  373. }
  374. func parseIntDefault(raw string, fallback int) int {
  375. if strings.TrimSpace(raw) == "" {
  376. return fallback
  377. }
  378. v, err := strconv.Atoi(raw)
  379. if err != nil {
  380. return -1
  381. }
  382. return v
  383. }
  384. func doKlingAipingProxyRequest(c *gin.Context, route klingaiping.Route, channelModel *model.Channel) (*http.Response, error) {
  385. baseURL := strings.TrimRight(channelModel.GetBaseURL(), "/")
  386. upstreamPath := strings.Replace(route.UpstreamPath, ":id", c.Param("id"), 1)
  387. url := baseURL + upstreamPath
  388. if c.Request.URL.RawQuery != "" {
  389. url += "?" + c.Request.URL.RawQuery
  390. }
  391. var body io.Reader
  392. if c.Request.Method != http.MethodGet {
  393. data, err := proxyBodyBytes(c, route)
  394. if err != nil {
  395. return nil, err
  396. }
  397. body = bytes.NewReader(data)
  398. }
  399. req, err := http.NewRequest(c.Request.Method, url, body)
  400. if err != nil {
  401. return nil, err
  402. }
  403. req.Header.Set("Accept", "application/json")
  404. req.Header.Set("Content-Type", "application/json")
  405. key := common.GetContextKeyString(c, constant.ContextKeyChannelKey)
  406. if key == "" {
  407. key = channelModel.Key
  408. }
  409. req.Header.Set("Authorization", "Bearer "+key)
  410. client, err := service.GetHttpClientWithProxy(channelModel.GetSetting().Proxy)
  411. if err != nil {
  412. return nil, err
  413. }
  414. return client.Do(req)
  415. }
  416. func proxyBodyBytes(c *gin.Context, route klingaiping.Route) ([]byte, error) {
  417. storage, err := common.GetBodyStorage(c)
  418. if err != nil {
  419. return nil, err
  420. }
  421. return storage.Bytes()
  422. }
  423. func copyProxyResponse(c *gin.Context, resp *http.Response) {
  424. if resp.StatusCode >= http.StatusBadRequest {
  425. copyNormalizedProxyError(c, resp)
  426. return
  427. }
  428. for key, values := range resp.Header {
  429. for _, value := range values {
  430. c.Writer.Header().Add(key, value)
  431. }
  432. }
  433. c.Status(resp.StatusCode)
  434. _, _ = io.Copy(c.Writer, resp.Body)
  435. }
  436. func copyNormalizedProxyError(c *gin.Context, resp *http.Response) {
  437. body, _ := io.ReadAll(resp.Body)
  438. message := strings.TrimSpace(string(body))
  439. payload := map[string]any{}
  440. if len(body) > 0 && common.Unmarshal(body, &payload) == nil {
  441. if msg := stringFromMap(payload, "message"); msg != "" {
  442. message = msg
  443. } else if msg := stringFromMap(payload, "msg"); msg != "" {
  444. message = msg
  445. } else if detail, ok := payload["detail"]; ok {
  446. if detailMap, ok := detail.(map[string]any); ok {
  447. if msg := stringFromMap(detailMap, "message"); msg != "" {
  448. message = msg
  449. } else if msg := stringFromMap(detailMap, "msg"); msg != "" {
  450. message = msg
  451. } else {
  452. message = fmt.Sprint(detail)
  453. }
  454. } else {
  455. message = fmt.Sprint(detail)
  456. }
  457. }
  458. delete(payload, "msg")
  459. } else {
  460. payload = map[string]any{}
  461. }
  462. if message == "" {
  463. message = resp.Status
  464. }
  465. if _, ok := payload["code"]; !ok {
  466. payload["code"] = resp.StatusCode
  467. }
  468. payload["message"] = message
  469. payload["request_id"] = c.GetString(common.RequestIdKey)
  470. c.JSON(resp.StatusCode, payload)
  471. }
  472. func normalizeKlingAipingTaskError(taskErr *dto.TaskError) {
  473. if taskErr == nil || strings.TrimSpace(taskErr.Message) == "" {
  474. return
  475. }
  476. payload := map[string]any{}
  477. if common.Unmarshal([]byte(taskErr.Message), &payload) != nil {
  478. return
  479. }
  480. if msg := stringFromMap(payload, "message"); msg != "" {
  481. taskErr.Message = msg
  482. return
  483. }
  484. if msg := stringFromMap(payload, "msg"); msg != "" {
  485. taskErr.Message = msg
  486. return
  487. }
  488. if detail, ok := payload["detail"].(map[string]any); ok {
  489. if msg := stringFromMap(detail, "message"); msg != "" {
  490. taskErr.Message = msg
  491. }
  492. }
  493. }