選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。
 
 
 

237 行
7.4 KiB

  1. package service
  2. import (
  3. "errors"
  4. "sort"
  5. "strings"
  6. "github.com/QuantumNous/new-api/common"
  7. "github.com/QuantumNous/new-api/constant"
  8. "github.com/QuantumNous/new-api/model"
  9. "gorm.io/gorm"
  10. )
  11. type VideoAssetFamily string
  12. const (
  13. VideoAssetFamilySeedance VideoAssetFamily = "seedance"
  14. VideoAssetFamilyKling VideoAssetFamily = "kling"
  15. )
  16. func VideoAssetFamilies() []VideoAssetFamily {
  17. return []VideoAssetFamily{VideoAssetFamilySeedance, VideoAssetFamilyKling}
  18. }
  19. func VideoAssetChannelTypesForFamily(family VideoAssetFamily) []int {
  20. switch family {
  21. case VideoAssetFamilySeedance:
  22. // DoubaoVideo (official Volcengine Ark) joins the family so a user's
  23. // asset upload channel and video task channel stay the same; assets
  24. // are referenced by asset:// ids passed through to the upstream.
  25. return []int{
  26. constant.ChannelTypeDoubaoVideo,
  27. constant.ChannelTypeDoubaoVideoCompatibleAiping,
  28. constant.ChannelTypeDoubaoVideoCompatibleTianyiYun,
  29. constant.ChannelTypeChinaMobileSeedance,
  30. }
  31. case VideoAssetFamilyKling:
  32. return []int{
  33. constant.ChannelTypeKlingAiping,
  34. }
  35. default:
  36. return nil
  37. }
  38. }
  39. func VideoAssetFamilyForChannelType(channelType int) (VideoAssetFamily, bool) {
  40. for _, family := range []VideoAssetFamily{VideoAssetFamilySeedance, VideoAssetFamilyKling} {
  41. if channelTypeAllowed(channelType, VideoAssetChannelTypesForFamily(family)) {
  42. return family, true
  43. }
  44. }
  45. return "", false
  46. }
  47. func GetBoundVideoAssetChannelForModel(userId int, tokenGroup string, modelName string, family VideoAssetFamily) (*model.Channel, error) {
  48. bindings, err := model.GetUserAssetChannelsByTypes(userId, VideoAssetChannelTypesForFamily(family), tokenGroup)
  49. if err != nil {
  50. return nil, err
  51. }
  52. for _, binding := range bindings {
  53. channel, err := model.CacheGetChannel(binding.ChannelId)
  54. if err != nil {
  55. if errors.Is(err, gorm.ErrRecordNotFound) || common.MemoryCacheEnabled {
  56. continue
  57. }
  58. return nil, err
  59. }
  60. if IsUsableVideoAssetChannelForFamily(channel, tokenGroup, modelName, family) {
  61. return channel, nil
  62. }
  63. }
  64. return nil, nil
  65. }
  66. func ResolveVideoAssetChannelForModel(userId int, tokenGroup string, modelName string, family VideoAssetFamily) (*model.Channel, error) {
  67. channel, err := GetBoundVideoAssetChannelForModel(userId, tokenGroup, modelName, family)
  68. if err != nil {
  69. return nil, err
  70. }
  71. if channel != nil {
  72. return channel, nil
  73. }
  74. channel, err = autoMatchVideoAssetChannelForModel(tokenGroup, modelName, family)
  75. if err != nil {
  76. return nil, err
  77. }
  78. if channel == nil {
  79. return nil, nil
  80. }
  81. if err = BindVideoAssetChannel(userId, tokenGroup, channel, family); err != nil {
  82. return nil, err
  83. }
  84. return channel, nil
  85. }
  86. func BindVideoAssetChannel(userId int, tokenGroup string, channel *model.Channel, family VideoAssetFamily) error {
  87. if channel == nil {
  88. return nil
  89. }
  90. channelTypes := VideoAssetChannelTypesForFamily(family)
  91. if len(channelTypes) == 0 || !channelTypeAllowed(channel.Type, channelTypes) {
  92. return nil
  93. }
  94. otherTypes := make([]int, 0, len(channelTypes))
  95. for _, channelType := range channelTypes {
  96. if channelType != channel.Type {
  97. otherTypes = append(otherTypes, channelType)
  98. }
  99. }
  100. return model.DB.Transaction(func(tx *gorm.DB) error {
  101. if err := model.DeleteUserAssetChannelsByTypesWithTx(tx, userId, otherTypes, tokenGroup); err != nil {
  102. return err
  103. }
  104. return model.BindUserAssetChannelWithTx(tx, userId, channel.Type, tokenGroup, channel.Id)
  105. })
  106. }
  107. func ClearVideoAssetChannelBinding(userId int, tokenGroup string, family VideoAssetFamily) error {
  108. channelTypes := VideoAssetChannelTypesForFamily(family)
  109. if len(channelTypes) == 0 {
  110. return nil
  111. }
  112. return model.DB.Transaction(func(tx *gorm.DB) error {
  113. return model.DeleteUserAssetChannelsByTypesWithTx(tx, userId, channelTypes, tokenGroup)
  114. })
  115. }
  116. func GetVideoAssetChannelCandidates(tokenGroup string, family VideoAssetFamily) ([]*model.Channel, error) {
  117. channelTypes := VideoAssetChannelTypesForFamily(family)
  118. if len(channelTypes) == 0 {
  119. return nil, nil
  120. }
  121. candidates := make(map[int]*model.Channel)
  122. for _, channelType := range channelTypes {
  123. for startIdx := 0; ; startIdx += DoubaoAssetChannelPageSize {
  124. channels, err := model.GetChannelsByType(startIdx, DoubaoAssetChannelPageSize, true, channelType)
  125. if err != nil {
  126. return nil, err
  127. }
  128. for _, candidate := range channels {
  129. if candidate == nil || candidate.Status != common.ChannelStatusEnabled || !MatchDoubaoAssetGroup(candidate.GetGroups(), tokenGroup) {
  130. continue
  131. }
  132. channel, err := model.GetChannelById(candidate.Id, true)
  133. if err != nil {
  134. return nil, err
  135. }
  136. if channel.Status == common.ChannelStatusEnabled && strings.TrimSpace(channel.Key) != "" && MatchDoubaoAssetGroup(channel.GetGroups(), tokenGroup) {
  137. channel.Key = ""
  138. channel.Keys = nil
  139. candidates[channel.Id] = channel
  140. }
  141. }
  142. if len(channels) < DoubaoAssetChannelPageSize {
  143. break
  144. }
  145. }
  146. }
  147. result := make([]*model.Channel, 0, len(candidates))
  148. for _, channel := range candidates {
  149. result = append(result, channel)
  150. }
  151. sort.Slice(result, func(i, j int) bool { return result[i].Id < result[j].Id })
  152. return result, nil
  153. }
  154. func HasVideoAssetChannelForGroupModel(tokenGroup string, modelName string, family VideoAssetFamily) bool {
  155. tokenGroup = strings.TrimSpace(tokenGroup)
  156. modelName = strings.TrimSpace(modelName)
  157. if tokenGroup == "" || modelName == "" {
  158. return false
  159. }
  160. channel, err := autoMatchVideoAssetChannelForModel(tokenGroup, modelName, family)
  161. return err == nil && channel != nil
  162. }
  163. func autoMatchVideoAssetChannelForModel(tokenGroup string, modelName string, family VideoAssetFamily) (*model.Channel, error) {
  164. for _, channelType := range VideoAssetChannelTypesForFamily(family) {
  165. for startIdx := 0; ; startIdx += DoubaoAssetChannelPageSize {
  166. candidates, err := model.GetChannelsByType(startIdx, DoubaoAssetChannelPageSize, true, channelType)
  167. if err != nil {
  168. return nil, err
  169. }
  170. for _, candidate := range candidates {
  171. if candidate == nil || !MatchDoubaoAssetGroup(candidate.GetGroups(), tokenGroup) {
  172. continue
  173. }
  174. channel, err := model.CacheGetChannel(candidate.Id)
  175. if err != nil {
  176. if errors.Is(err, gorm.ErrRecordNotFound) || common.MemoryCacheEnabled {
  177. continue
  178. }
  179. return nil, err
  180. }
  181. if IsUsableVideoAssetChannelForFamily(channel, tokenGroup, modelName, family) {
  182. return channel, nil
  183. }
  184. }
  185. if len(candidates) < DoubaoAssetChannelPageSize {
  186. break
  187. }
  188. }
  189. }
  190. return nil, nil
  191. }
  192. func IsUsableVideoAssetChannelForFamily(channel *model.Channel, tokenGroup string, modelName string, family VideoAssetFamily) bool {
  193. if channel == nil {
  194. return false
  195. }
  196. channelTypes := VideoAssetChannelTypesForFamily(family)
  197. if len(channelTypes) == 0 || !channelTypeAllowed(channel.Type, channelTypes) {
  198. return false
  199. }
  200. if channel.Status != common.ChannelStatusEnabled {
  201. return false
  202. }
  203. if strings.TrimSpace(channel.Key) == "" || len(channel.GetKeys()) == 0 {
  204. return false
  205. }
  206. if !MatchDoubaoAssetGroup(channel.GetGroups(), tokenGroup) {
  207. return false
  208. }
  209. // Official Volcengine channels additionally need the asset AK/SK
  210. // credential before they can serve the asset family; without it they are
  211. // skipped so binding/auto-match falls through to compatible channels.
  212. if !assetCredentialConfigured(channel) {
  213. return false
  214. }
  215. modelName = strings.TrimSpace(modelName)
  216. if modelName == "" {
  217. return true
  218. }
  219. return model.IsChannelEnabledForGroupModel(tokenGroup, modelName, channel.Id)
  220. }