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ů.
 
 
 

380 řádky
12 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. // CreateChannelPricing 创建或更新渠道定价
  59. func CreateChannelPricing(c *gin.Context) {
  60. var req CreateChannelPricingRequest
  61. if err := c.ShouldBindJSON(&req); err != nil {
  62. common.ApiError(c, err)
  63. return
  64. }
  65. if req.CacheRatio < 0 || req.CacheCreationRatio < 0 || req.ImageRatio < 0 || req.AudioRatio < 0 || req.AudioCompletionRatio < 0 {
  66. common.ApiErrorMsg(c, "ratio values must be >= 0")
  67. return
  68. }
  69. // 检查是否已存在
  70. existing, _ := model.GetChannelPricing(req.ModelName, req.ChannelId)
  71. if existing != nil {
  72. // 更新
  73. existing.QuotaType = req.QuotaType
  74. existing.ModelRatio = req.ModelRatio
  75. existing.CompletionRatio = req.CompletionRatio
  76. existing.ModelPrice = req.ModelPrice
  77. existing.TagIds = req.TagIds
  78. existing.CacheRatio = req.CacheRatio
  79. existing.CacheCreationRatio = req.CacheCreationRatio
  80. existing.ImageRatio = req.ImageRatio
  81. existing.AudioRatio = req.AudioRatio
  82. existing.AudioCompletionRatio = req.AudioCompletionRatio
  83. if err := existing.Update(); err != nil {
  84. common.ApiError(c, err)
  85. return
  86. }
  87. common.SysLog(fmt.Sprintf("[ChannelPricing] updated: id=%d model=%s channel=%d", existing.Id, existing.ModelName, existing.ChannelId))
  88. common.ApiSuccess(c, existing)
  89. return
  90. }
  91. // 创建
  92. cp := &model.ChannelPricing{
  93. ModelName: req.ModelName,
  94. ChannelId: req.ChannelId,
  95. QuotaType: req.QuotaType,
  96. ModelRatio: req.ModelRatio,
  97. CompletionRatio: req.CompletionRatio,
  98. ModelPrice: req.ModelPrice,
  99. TagIds: req.TagIds,
  100. CacheRatio: req.CacheRatio,
  101. CacheCreationRatio: req.CacheCreationRatio,
  102. ImageRatio: req.ImageRatio,
  103. AudioRatio: req.AudioRatio,
  104. AudioCompletionRatio: req.AudioCompletionRatio,
  105. }
  106. if err := cp.Insert(); err != nil {
  107. common.ApiError(c, err)
  108. return
  109. }
  110. 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",
  111. req.ModelName, req.ChannelId, req.QuotaType, req.ModelRatio, req.CompletionRatio, req.ModelPrice,
  112. req.CacheRatio, req.CacheCreationRatio, req.ImageRatio, req.AudioRatio, req.AudioCompletionRatio))
  113. common.ApiSuccess(c, cp)
  114. }
  115. // BatchCreateChannelPricingRequest 批量创建请求
  116. type BatchCreateChannelPricingRequest struct {
  117. Items []*CreateChannelPricingRequest `json:"items" binding:"required"`
  118. }
  119. // BatchCreateChannelPricing 批量创建或更新渠道定价
  120. func BatchCreateChannelPricing(c *gin.Context) {
  121. var req BatchCreateChannelPricingRequest
  122. if err := c.ShouldBindJSON(&req); err != nil {
  123. common.ApiError(c, err)
  124. return
  125. }
  126. pricings := make([]*model.ChannelPricing, 0, len(req.Items))
  127. for _, item := range req.Items {
  128. pricings = append(pricings, &model.ChannelPricing{
  129. ModelName: item.ModelName,
  130. ChannelId: item.ChannelId,
  131. QuotaType: item.QuotaType,
  132. ModelRatio: item.ModelRatio,
  133. CompletionRatio: item.CompletionRatio,
  134. ModelPrice: item.ModelPrice,
  135. TagIds: item.TagIds,
  136. CacheRatio: item.CacheRatio,
  137. CacheCreationRatio: item.CacheCreationRatio,
  138. ImageRatio: item.ImageRatio,
  139. AudioRatio: item.AudioRatio,
  140. AudioCompletionRatio: item.AudioCompletionRatio,
  141. })
  142. }
  143. // 使用事务逐个处理(GORM 的批量 upsert 在不同数据库表现不一致)
  144. for _, cp := range pricings {
  145. existing, _ := model.GetChannelPricing(cp.ModelName, cp.ChannelId)
  146. if existing != nil {
  147. existing.QuotaType = cp.QuotaType
  148. existing.ModelRatio = cp.ModelRatio
  149. existing.CompletionRatio = cp.CompletionRatio
  150. existing.ModelPrice = cp.ModelPrice
  151. existing.TagIds = cp.TagIds
  152. existing.CacheRatio = cp.CacheRatio
  153. existing.CacheCreationRatio = cp.CacheCreationRatio
  154. existing.ImageRatio = cp.ImageRatio
  155. existing.AudioRatio = cp.AudioRatio
  156. existing.AudioCompletionRatio = cp.AudioCompletionRatio
  157. if err := existing.Update(); err != nil {
  158. common.ApiError(c, err)
  159. return
  160. }
  161. } else {
  162. if err := cp.Insert(); err != nil {
  163. common.ApiError(c, err)
  164. return
  165. }
  166. }
  167. }
  168. common.ApiSuccess(c, gin.H{"affected": len(pricings)})
  169. }
  170. // DeleteChannelPricing 删除渠道定价
  171. func DeleteChannelPricing(c *gin.Context) {
  172. idStr := c.Param("id")
  173. id, err := strconv.Atoi(idStr)
  174. if err != nil {
  175. common.ApiError(c, err)
  176. return
  177. }
  178. cp := &model.ChannelPricing{Id: id}
  179. if err := cp.Delete(); err != nil {
  180. common.ApiError(c, err)
  181. return
  182. }
  183. common.SysLog(fmt.Sprintf("[ChannelPricing] deleted: id=%d", id))
  184. common.ApiSuccess(c, nil)
  185. }
  186. // CopyGlobalPricingRequest 复制全局定价请求
  187. type CopyGlobalPricingRequest struct {
  188. Overwrite bool `json:"overwrite"` // 是否覆盖已存在的渠道定价
  189. }
  190. // CopyGlobalPricing 复制全局定价到指定渠道
  191. // 从 ratio_setting 读取全局定价信息,复制到 channel_pricing 表
  192. func CopyGlobalPricing(c *gin.Context) {
  193. channelIdStr := c.Param("channel_id")
  194. channelId, err := strconv.Atoi(channelIdStr)
  195. if err != nil {
  196. common.ApiError(c, err)
  197. return
  198. }
  199. var req CopyGlobalPricingRequest
  200. c.ShouldBindJSON(&req)
  201. // 获取该渠道支持的所有模型(使用 GetAbilitiesByChannelId)
  202. abilities, err := model.GetAbilitiesByChannelId(channelId)
  203. if err != nil {
  204. common.ApiError(c, err)
  205. return
  206. }
  207. imported := 0
  208. for _, ability := range abilities {
  209. // 检查是否已存在
  210. existing, _ := model.GetChannelPricing(ability.Model, channelId)
  211. if existing != nil && !req.Overwrite {
  212. continue
  213. }
  214. // 获取全局扩展比率
  215. globalCacheRatio, hasCacheRatio := ratio_setting.GetCacheRatio(ability.Model)
  216. if !hasCacheRatio {
  217. globalCacheRatio = 0
  218. }
  219. globalCacheCreationRatio, hasCacheCreationRatio := ratio_setting.GetCreateCacheRatio(ability.Model)
  220. if !hasCacheCreationRatio {
  221. globalCacheCreationRatio = 0
  222. }
  223. globalImageRatio, hasImageRatio := ratio_setting.GetImageRatio(ability.Model)
  224. if !hasImageRatio {
  225. globalImageRatio = 0
  226. }
  227. globalAudioRatio := ratio_setting.GetAudioRatio(ability.Model)
  228. globalAudioCompletionRatio := ratio_setting.GetAudioCompletionRatio(ability.Model)
  229. // 确定定价类型
  230. var quotaType int
  231. var ratio, completionRatio, price float64
  232. // 优先检查是否有按次计费的价格
  233. modelPrice, hasPrice := ratio_setting.GetModelPrice(ability.Model, false)
  234. if hasPrice {
  235. quotaType = model.QuotaTypeByCall
  236. price = modelPrice
  237. } else {
  238. // 使用按量计费
  239. quotaType = model.QuotaTypeByTokens
  240. modelRatio, hasRatio, _ := ratio_setting.GetModelRatio(ability.Model)
  241. if hasRatio {
  242. ratio = modelRatio
  243. }
  244. completionRatio = ratio_setting.GetCompletionRatio(ability.Model)
  245. }
  246. if existing != nil {
  247. existing.QuotaType = quotaType
  248. existing.ModelRatio = ratio
  249. existing.CompletionRatio = completionRatio
  250. existing.ModelPrice = price
  251. existing.CacheRatio = globalCacheRatio
  252. existing.CacheCreationRatio = globalCacheCreationRatio
  253. existing.ImageRatio = globalImageRatio
  254. existing.AudioRatio = globalAudioRatio
  255. existing.AudioCompletionRatio = globalAudioCompletionRatio
  256. if err := existing.Update(); err == nil {
  257. imported++
  258. }
  259. } else {
  260. cp := &model.ChannelPricing{
  261. ModelName: ability.Model,
  262. ChannelId: channelId,
  263. QuotaType: quotaType,
  264. ModelRatio: ratio,
  265. CompletionRatio: completionRatio,
  266. ModelPrice: price,
  267. CacheRatio: globalCacheRatio,
  268. CacheCreationRatio: globalCacheCreationRatio,
  269. ImageRatio: globalImageRatio,
  270. AudioRatio: globalAudioRatio,
  271. AudioCompletionRatio: globalAudioCompletionRatio,
  272. }
  273. if err := cp.Insert(); err == nil {
  274. imported++
  275. }
  276. }
  277. }
  278. common.SysLog(fmt.Sprintf("[ChannelPricing] copyGlobalPricing: channel=%d imported=%d/%d", channelId, imported, len(abilities)))
  279. common.ApiSuccess(c, gin.H{
  280. "total": len(abilities),
  281. "imported": imported,
  282. })
  283. }
  284. // ChannelPricingWithTags 带标签详情的渠道定价响应
  285. type ChannelPricingWithTags struct {
  286. *model.ChannelPricing
  287. Tags []*model.PricingTag `json:"tags"`
  288. }
  289. // GetChannelPricingByModelWithChannelInfo 获取指定模型的渠道定价(带渠道信息,所有用户可访问)
  290. func GetChannelPricingByModelWithChannelInfo(c *gin.Context) {
  291. // 使用通配符路由时,参数包含前导斜杠,需要去除
  292. modelName := c.Param("name")
  293. if modelName == "" {
  294. common.ApiErrorMsg(c, "model name is required")
  295. return
  296. }
  297. // 去除前导斜杠(路由是 /channel-pricing/model/*name,name 会是 "/deepseek-ai/xxx")
  298. modelName = strings.TrimPrefix(modelName, "/")
  299. list, err := model.GetChannelPricingByModelWithChannelInfo(modelName)
  300. if err != nil {
  301. common.ApiError(c, err)
  302. return
  303. }
  304. common.ApiSuccess(c, list)
  305. }
  306. // GetChannelPricingWithTags 获取渠道定价(带标签详情)
  307. func GetChannelPricingWithTags(c *gin.Context) {
  308. page, _ := strconv.Atoi(c.DefaultQuery("p", "1"))
  309. pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
  310. offset := (page - 1) * pageSize
  311. list, total, err := model.GetAllChannelPricing(offset, pageSize)
  312. if err != nil {
  313. common.ApiError(c, err)
  314. return
  315. }
  316. // 获取所有标签
  317. tags, _ := model.GetAllPricingTags()
  318. tagMap := make(map[int]*model.PricingTag)
  319. for _, tag := range tags {
  320. tagMap[tag.Id] = tag
  321. }
  322. // 为每个定价填充标签详情
  323. result := make([]*ChannelPricingWithTags, 0, len(list))
  324. for _, cp := range list {
  325. item := &ChannelPricingWithTags{
  326. ChannelPricing: cp,
  327. Tags: model.ParseTagIds(cp.TagIds, tagMap),
  328. }
  329. result = append(result, item)
  330. }
  331. common.ApiSuccess(c, gin.H{
  332. "page": page,
  333. "page_size": pageSize,
  334. "total": total,
  335. "items": result,
  336. })
  337. }