Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.
 
 
 

328 řádky
9.8 KiB

  1. package controller
  2. import (
  3. "fmt"
  4. "strconv"
  5. "strings"
  6. "github.com/QuantumNous/new-api/common"
  7. "github.com/QuantumNous/new-api/model"
  8. "github.com/QuantumNous/new-api/setting/ratio_setting"
  9. "github.com/gin-gonic/gin"
  10. )
  11. // GetAllChannelPricing 获取所有渠道定价(分页)
  12. func GetAllChannelPricing(c *gin.Context) {
  13. page, _ := strconv.Atoi(c.DefaultQuery("p", "1"))
  14. pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
  15. offset := (page - 1) * pageSize
  16. list, total, err := model.GetAllChannelPricing(offset, pageSize)
  17. if err != nil {
  18. common.ApiError(c, err)
  19. return
  20. }
  21. common.ApiSuccess(c, gin.H{
  22. "page": page,
  23. "page_size": pageSize,
  24. "total": total,
  25. "items": list,
  26. })
  27. }
  28. // GetChannelPricingByModel 获取指定模型的所有渠道定价
  29. func GetChannelPricingByModel(c *gin.Context) {
  30. modelName := c.Param("name")
  31. if modelName == "" {
  32. common.ApiErrorMsg(c, "model name is required")
  33. return
  34. }
  35. list, err := model.GetChannelPricingByModel(modelName)
  36. if err != nil {
  37. common.ApiError(c, err)
  38. return
  39. }
  40. common.ApiSuccess(c, list)
  41. }
  42. // CreateChannelPricingRequest 创建渠道定价请求
  43. type CreateChannelPricingRequest struct {
  44. Id int `json:"id"`
  45. ModelName string `json:"model_name" binding:"required"`
  46. ChannelId int `json:"channel_id" binding:"required"`
  47. QuotaType int `json:"quota_type"`
  48. ModelRatio float64 `json:"model_ratio"`
  49. CompletionRatio float64 `json:"completion_ratio"`
  50. ModelPrice float64 `json:"model_price"`
  51. TagIds string `json:"tag_ids"`
  52. CacheRatio float64 `json:"cache_ratio"`
  53. CacheCreationRatio float64 `json:"cache_creation_ratio"`
  54. ImageRatio float64 `json:"image_ratio"`
  55. AudioRatio float64 `json:"audio_ratio"`
  56. AudioCompletionRatio float64 `json:"audio_completion_ratio"`
  57. }
  58. // applyRequest 将请求字段应用到 ChannelPricing
  59. func applyRequestFields(cp *model.ChannelPricing, req *CreateChannelPricingRequest) {
  60. cp.ApplyFields(req.QuotaType, req.ModelRatio, req.CompletionRatio, req.ModelPrice, req.TagIds,
  61. req.CacheRatio, req.CacheCreationRatio, req.ImageRatio, req.AudioRatio, req.AudioCompletionRatio)
  62. }
  63. // CreateChannelPricing 创建或更新渠道定价
  64. func CreateChannelPricing(c *gin.Context) {
  65. var req CreateChannelPricingRequest
  66. if err := c.ShouldBindJSON(&req); err != nil {
  67. common.ApiError(c, err)
  68. return
  69. }
  70. if req.CacheRatio < 0 || req.CacheCreationRatio < 0 || req.ImageRatio < 0 || req.AudioRatio < 0 || req.AudioCompletionRatio < 0 {
  71. common.ApiErrorMsg(c, "ratio values must be >= 0")
  72. return
  73. }
  74. // 检查是否已存在
  75. existing, _ := model.GetChannelPricing(req.ModelName, req.ChannelId)
  76. if existing != nil {
  77. applyRequestFields(existing, &req)
  78. if err := existing.Update(); err != nil {
  79. common.ApiError(c, err)
  80. return
  81. }
  82. common.SysLog(fmt.Sprintf("[ChannelPricing] updated: id=%d model=%s channel=%d", existing.Id, existing.ModelName, existing.ChannelId))
  83. common.ApiSuccess(c, existing)
  84. return
  85. }
  86. // 创建
  87. cp := &model.ChannelPricing{
  88. ModelName: req.ModelName,
  89. ChannelId: req.ChannelId,
  90. }
  91. applyRequestFields(cp, &req)
  92. if err := cp.Insert(); err != nil {
  93. common.ApiError(c, err)
  94. return
  95. }
  96. common.SysLog(fmt.Sprintf("[ChannelPricing] created: model=%s channel=%d quotaType=%d modelRatio=%.4f completionRatio=%.4f modelPrice=%.4f cacheRatio=%.4f cacheCreationRatio=%.4f imageRatio=%.4f audioRatio=%.4f audioCompletionRatio=%.4f",
  97. req.ModelName, req.ChannelId, req.QuotaType, req.ModelRatio, req.CompletionRatio, req.ModelPrice,
  98. req.CacheRatio, req.CacheCreationRatio, req.ImageRatio, req.AudioRatio, req.AudioCompletionRatio))
  99. common.ApiSuccess(c, cp)
  100. }
  101. // BatchCreateChannelPricingRequest 批量创建请求
  102. type BatchCreateChannelPricingRequest struct {
  103. Items []*CreateChannelPricingRequest `json:"items" binding:"required"`
  104. }
  105. // BatchCreateChannelPricing 批量创建或更新渠道定价
  106. func BatchCreateChannelPricing(c *gin.Context) {
  107. var req BatchCreateChannelPricingRequest
  108. if err := c.ShouldBindJSON(&req); err != nil {
  109. common.ApiError(c, err)
  110. return
  111. }
  112. pricings := make([]*model.ChannelPricing, 0, len(req.Items))
  113. for _, item := range req.Items {
  114. cp := &model.ChannelPricing{
  115. ModelName: item.ModelName,
  116. ChannelId: item.ChannelId,
  117. }
  118. applyRequestFields(cp, item)
  119. pricings = append(pricings, cp)
  120. }
  121. // 使用事务逐个处理(GORM 的批量 upsert 在不同数据库表现不一致)
  122. for _, cp := range pricings {
  123. existing, _ := model.GetChannelPricing(cp.ModelName, cp.ChannelId)
  124. if existing != nil {
  125. existing.ApplyFields(cp.QuotaType, cp.ModelRatio, cp.CompletionRatio, cp.ModelPrice, cp.TagIds,
  126. cp.CacheRatio, cp.CacheCreationRatio, cp.ImageRatio, cp.AudioRatio, cp.AudioCompletionRatio)
  127. if err := existing.Update(); err != nil {
  128. common.ApiError(c, err)
  129. return
  130. }
  131. } else {
  132. if err := cp.Insert(); err != nil {
  133. common.ApiError(c, err)
  134. return
  135. }
  136. }
  137. }
  138. common.ApiSuccess(c, gin.H{"affected": len(pricings)})
  139. }
  140. // DeleteChannelPricing 删除渠道定价
  141. func DeleteChannelPricing(c *gin.Context) {
  142. idStr := c.Param("id")
  143. id, err := strconv.Atoi(idStr)
  144. if err != nil {
  145. common.ApiError(c, err)
  146. return
  147. }
  148. cp := &model.ChannelPricing{Id: id}
  149. if err := cp.Delete(); err != nil {
  150. common.ApiError(c, err)
  151. return
  152. }
  153. common.SysLog(fmt.Sprintf("[ChannelPricing] deleted: id=%d", id))
  154. common.ApiSuccess(c, nil)
  155. }
  156. // CopyGlobalPricingRequest 复制全局定价请求
  157. type CopyGlobalPricingRequest struct {
  158. Overwrite bool `json:"overwrite"` // 是否覆盖已存在的渠道定价
  159. }
  160. // CopyGlobalPricing 复制全局定价到指定渠道
  161. // 从 ratio_setting 读取全局定价信息,复制到 channel_pricing 表
  162. func CopyGlobalPricing(c *gin.Context) {
  163. channelIdStr := c.Param("channel_id")
  164. channelId, err := strconv.Atoi(channelIdStr)
  165. if err != nil {
  166. common.ApiError(c, err)
  167. return
  168. }
  169. var req CopyGlobalPricingRequest
  170. c.ShouldBindJSON(&req)
  171. // 获取该渠道支持的所有模型(使用 GetAbilitiesByChannelId)
  172. abilities, err := model.GetAbilitiesByChannelId(channelId)
  173. if err != nil {
  174. common.ApiError(c, err)
  175. return
  176. }
  177. imported := 0
  178. for _, ability := range abilities {
  179. // 检查是否已存在
  180. existing, _ := model.GetChannelPricing(ability.Model, channelId)
  181. if existing != nil && !req.Overwrite {
  182. continue
  183. }
  184. // 获取全局扩展比率
  185. globalCacheRatio, _ := ratio_setting.GetCacheRatio(ability.Model)
  186. globalCacheCreationRatio, _ := ratio_setting.GetCreateCacheRatio(ability.Model)
  187. globalImageRatio, _ := ratio_setting.GetImageRatio(ability.Model)
  188. globalAudioRatio := ratio_setting.GetAudioRatio(ability.Model)
  189. globalAudioCompletionRatio := ratio_setting.GetAudioCompletionRatio(ability.Model)
  190. // 确定定价类型
  191. var quotaType int
  192. var ratio, completionRatio, price float64
  193. // 优先检查是否有按次计费的价格
  194. modelPrice, hasPrice := ratio_setting.GetModelPrice(ability.Model, false)
  195. if hasPrice {
  196. quotaType = model.QuotaTypeByCall
  197. price = modelPrice
  198. } else {
  199. // 使用按量计费
  200. quotaType = model.QuotaTypeByTokens
  201. modelRatio, hasRatio, _ := ratio_setting.GetModelRatio(ability.Model)
  202. if hasRatio {
  203. ratio = modelRatio
  204. }
  205. completionRatio = ratio_setting.GetCompletionRatio(ability.Model)
  206. }
  207. if existing != nil {
  208. existing.ApplyFields(quotaType, ratio, completionRatio, price, "",
  209. globalCacheRatio, globalCacheCreationRatio, globalImageRatio, globalAudioRatio, globalAudioCompletionRatio)
  210. if err := existing.Update(); err == nil {
  211. imported++
  212. }
  213. } else {
  214. cp := &model.ChannelPricing{
  215. ModelName: ability.Model,
  216. ChannelId: channelId,
  217. }
  218. cp.ApplyFields(quotaType, ratio, completionRatio, price, "",
  219. globalCacheRatio, globalCacheCreationRatio, globalImageRatio, globalAudioRatio, globalAudioCompletionRatio)
  220. if err := cp.Insert(); err == nil {
  221. imported++
  222. }
  223. }
  224. }
  225. common.SysLog(fmt.Sprintf("[ChannelPricing] copyGlobalPricing: channel=%d imported=%d/%d", channelId, imported, len(abilities)))
  226. common.ApiSuccess(c, gin.H{
  227. "total": len(abilities),
  228. "imported": imported,
  229. })
  230. }
  231. // ChannelPricingWithTags 带标签详情的渠道定价响应
  232. type ChannelPricingWithTags struct {
  233. *model.ChannelPricing
  234. Tags []*model.PricingTag `json:"tags"`
  235. }
  236. // GetChannelPricingByModelWithChannelInfo 获取指定模型的渠道定价(带渠道信息,所有用户可访问)
  237. func GetChannelPricingByModelWithChannelInfo(c *gin.Context) {
  238. // 使用通配符路由时,参数包含前导斜杠,需要去除
  239. modelName := c.Param("name")
  240. if modelName == "" {
  241. common.ApiErrorMsg(c, "model name is required")
  242. return
  243. }
  244. // 去除前导斜杠(路由是 /channel-pricing/model/*name,name 会是 "/deepseek-ai/xxx")
  245. modelName = strings.TrimPrefix(modelName, "/")
  246. list, err := model.GetChannelPricingByModelWithChannelInfo(modelName)
  247. if err != nil {
  248. common.ApiError(c, err)
  249. return
  250. }
  251. common.ApiSuccess(c, list)
  252. }
  253. // GetChannelPricingWithTags 获取渠道定价(带标签详情)
  254. func GetChannelPricingWithTags(c *gin.Context) {
  255. page, _ := strconv.Atoi(c.DefaultQuery("p", "1"))
  256. pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
  257. offset := (page - 1) * pageSize
  258. list, total, err := model.GetAllChannelPricing(offset, pageSize)
  259. if err != nil {
  260. common.ApiError(c, err)
  261. return
  262. }
  263. // 获取所有标签
  264. tags, _ := model.GetAllPricingTags()
  265. tagMap := make(map[int]*model.PricingTag)
  266. for _, tag := range tags {
  267. tagMap[tag.Id] = tag
  268. }
  269. // 为每个定价填充标签详情
  270. result := make([]*ChannelPricingWithTags, 0, len(list))
  271. for _, cp := range list {
  272. item := &ChannelPricingWithTags{
  273. ChannelPricing: cp,
  274. Tags: model.ParseTagIds(cp.TagIds, tagMap),
  275. }
  276. result = append(result, item)
  277. }
  278. common.ApiSuccess(c, gin.H{
  279. "page": page,
  280. "page_size": pageSize,
  281. "total": total,
  282. "items": result,
  283. })
  284. }