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.
 
 
 

231 rader
6.0 KiB

  1. package common
  2. import (
  3. "fmt"
  4. "net/http"
  5. "strconv"
  6. "strings"
  7. "github.com/QuantumNous/new-api/common"
  8. "github.com/QuantumNous/new-api/constant"
  9. "github.com/QuantumNous/new-api/dto"
  10. "github.com/gin-gonic/gin"
  11. "github.com/samber/lo"
  12. )
  13. type HasPrompt interface {
  14. GetPrompt() string
  15. }
  16. type HasImage interface {
  17. HasImage() bool
  18. }
  19. func GetFullRequestURL(baseURL string, requestURL string, channelType int) string {
  20. fullRequestURL := fmt.Sprintf("%s%s", baseURL, requestURL)
  21. if strings.HasPrefix(baseURL, "https://gateway.ai.cloudflare.com") {
  22. switch channelType {
  23. case constant.ChannelTypeOpenAI:
  24. fullRequestURL = fmt.Sprintf("%s%s", baseURL, strings.TrimPrefix(requestURL, "/v1"))
  25. case constant.ChannelTypeAzure:
  26. fullRequestURL = fmt.Sprintf("%s%s", baseURL, strings.TrimPrefix(requestURL, "/openai/deployments"))
  27. }
  28. }
  29. return fullRequestURL
  30. }
  31. func GetAPIVersion(c *gin.Context) string {
  32. query := c.Request.URL.Query()
  33. apiVersion := query.Get("api-version")
  34. if apiVersion == "" {
  35. apiVersion = c.GetString("api_version")
  36. }
  37. return apiVersion
  38. }
  39. func createTaskError(err error, code string, statusCode int, localError bool) *dto.TaskError {
  40. return &dto.TaskError{
  41. Code: code,
  42. Message: err.Error(),
  43. StatusCode: statusCode,
  44. LocalError: localError,
  45. Error: err,
  46. }
  47. }
  48. func storeTaskRequest(c *gin.Context, info *RelayInfo, action string, requestObj TaskSubmitReq) {
  49. info.Action = action
  50. c.Set("task_request", requestObj)
  51. }
  52. func StoreTaskRequest(c *gin.Context, info *RelayInfo, action string, requestObj TaskSubmitReq) {
  53. if info.TaskRelayInfo == nil {
  54. info.TaskRelayInfo = &TaskRelayInfo{}
  55. }
  56. storeTaskRequest(c, info, action, requestObj)
  57. }
  58. func GetTaskRequest(c *gin.Context) (TaskSubmitReq, error) {
  59. v, exists := c.Get("task_request")
  60. if !exists {
  61. return TaskSubmitReq{}, fmt.Errorf("request not found in context")
  62. }
  63. req, ok := v.(TaskSubmitReq)
  64. if !ok {
  65. return TaskSubmitReq{}, fmt.Errorf("invalid task request type")
  66. }
  67. return req, nil
  68. }
  69. func validatePrompt(prompt string) *dto.TaskError {
  70. if strings.TrimSpace(prompt) == "" {
  71. return createTaskError(fmt.Errorf("prompt is required"), "invalid_request", http.StatusBadRequest, true)
  72. }
  73. return nil
  74. }
  75. func validateMultipartTaskRequest(c *gin.Context, info *RelayInfo, action string) (TaskSubmitReq, error) {
  76. var req TaskSubmitReq
  77. if _, err := c.MultipartForm(); err != nil {
  78. return req, err
  79. }
  80. formData := c.Request.PostForm
  81. req = TaskSubmitReq{
  82. Prompt: formData.Get("prompt"),
  83. Model: formData.Get("model"),
  84. Mode: formData.Get("mode"),
  85. Image: formData.Get("image"),
  86. Size: formData.Get("size"),
  87. Metadata: make(map[string]interface{}),
  88. }
  89. if durationStr := formData.Get("seconds"); durationStr != "" {
  90. if duration, err := strconv.Atoi(durationStr); err == nil {
  91. req.Duration = duration
  92. }
  93. }
  94. if images := formData["images"]; len(images) > 0 {
  95. req.Images = images
  96. }
  97. for key, values := range formData {
  98. if len(values) > 0 && !isKnownTaskField(key) {
  99. if intVal, err := strconv.Atoi(values[0]); err == nil {
  100. req.Metadata[key] = intVal
  101. } else if floatVal, err := strconv.ParseFloat(values[0], 64); err == nil {
  102. req.Metadata[key] = floatVal
  103. } else {
  104. req.Metadata[key] = values[0]
  105. }
  106. }
  107. }
  108. return req, nil
  109. }
  110. func ValidateMultipartDirect(c *gin.Context, info *RelayInfo) *dto.TaskError {
  111. var prompt string
  112. var model string
  113. var seconds int
  114. var size string
  115. var hasInputReference bool
  116. var req TaskSubmitReq
  117. if err := common.UnmarshalBodyReusable(c, &req); err != nil {
  118. return createTaskError(err, "invalid_json", http.StatusBadRequest, true)
  119. }
  120. prompt = req.Prompt
  121. model = req.Model
  122. size = req.Size
  123. seconds, _ = strconv.Atoi(req.Seconds)
  124. if seconds == 0 {
  125. seconds = req.Duration
  126. }
  127. if req.InputReference != "" {
  128. req.Images = []string{req.InputReference}
  129. }
  130. if strings.TrimSpace(req.Model) == "" {
  131. return createTaskError(fmt.Errorf("model field is required"), "missing_model", http.StatusBadRequest, true)
  132. }
  133. if req.HasImage() {
  134. hasInputReference = true
  135. }
  136. if taskErr := validatePrompt(prompt); taskErr != nil {
  137. return taskErr
  138. }
  139. action := constant.TaskActionTextGenerate
  140. if hasInputReference {
  141. action = constant.TaskActionGenerate
  142. }
  143. if strings.HasPrefix(model, "sora-2") {
  144. if size == "" {
  145. size = "720x1280"
  146. }
  147. if seconds <= 0 {
  148. seconds = 4
  149. }
  150. if model == "sora-2" && !lo.Contains([]string{"720x1280", "1280x720"}, size) {
  151. return createTaskError(fmt.Errorf("sora-2 size is invalid"), "invalid_size", http.StatusBadRequest, true)
  152. }
  153. if model == "sora-2-pro" && !lo.Contains([]string{"720x1280", "1280x720", "1792x1024", "1024x1792"}, size) {
  154. return createTaskError(fmt.Errorf("sora-2 size is invalid"), "invalid_size", http.StatusBadRequest, true)
  155. }
  156. // OtherRatios 已移到 Sora adaptor 的 EstimateBilling 中设置
  157. }
  158. storeTaskRequest(c, info, action, req)
  159. return nil
  160. }
  161. func isKnownTaskField(field string) bool {
  162. knownFields := map[string]bool{
  163. "prompt": true,
  164. "model": true,
  165. "mode": true,
  166. "image": true,
  167. "images": true,
  168. "size": true,
  169. "duration": true,
  170. "input_reference": true, // Sora 特有字段
  171. }
  172. return knownFields[field]
  173. }
  174. func ValidateBasicTaskRequest(c *gin.Context, info *RelayInfo, action string) *dto.TaskError {
  175. var err error
  176. contentType := c.GetHeader("Content-Type")
  177. var req TaskSubmitReq
  178. if strings.HasPrefix(contentType, "multipart/form-data") {
  179. req, err = validateMultipartTaskRequest(c, info, action)
  180. if err != nil {
  181. return createTaskError(err, "invalid_multipart_form", http.StatusBadRequest, true)
  182. }
  183. } else if err := common.UnmarshalBodyReusable(c, &req); err != nil {
  184. return createTaskError(err, "invalid_request", http.StatusBadRequest, true)
  185. }
  186. if taskErr := validatePrompt(req.Prompt); taskErr != nil {
  187. return taskErr
  188. }
  189. if len(req.Images) == 0 && strings.TrimSpace(req.Image) != "" {
  190. // 兼容单图上传
  191. req.Images = []string{req.Image}
  192. }
  193. storeTaskRequest(c, info, action, req)
  194. return nil
  195. }