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

267 行
6.9 KiB

  1. package controller
  2. import (
  3. "fmt"
  4. "math"
  5. "sort"
  6. "strings"
  7. "github.com/QuantumNous/new-api/common"
  8. "github.com/QuantumNous/new-api/model"
  9. "github.com/QuantumNous/new-api/service"
  10. "github.com/QuantumNous/new-api/setting/ratio_setting"
  11. "github.com/QuantumNous/new-api/types"
  12. "github.com/gin-gonic/gin"
  13. )
  14. // resolveCurrentUser 从 gin.Context 解析当前登录用户信息。
  15. // 未登录或查询失败时 loggedIn=false,groupRatio=1.0。
  16. func resolveCurrentUser(c *gin.Context) (loggedIn bool, userId int, userGroup string, groupRatio float64) {
  17. groupRatio = 1.0
  18. raw, exists := c.Get("id")
  19. if !exists {
  20. return
  21. }
  22. userId = raw.(int)
  23. user, err := model.GetUserCache(userId)
  24. if err != nil {
  25. return
  26. }
  27. loggedIn = true
  28. userGroup = user.Group
  29. groupRatio = service.GetUserGroupRatio(userGroup, userGroup)
  30. return
  31. }
  32. func filterPricingByUsableGroups(pricing []model.Pricing, usableGroup map[string]string) []model.Pricing {
  33. if len(pricing) == 0 {
  34. return pricing
  35. }
  36. if len(usableGroup) == 0 {
  37. return []model.Pricing{}
  38. }
  39. filtered := make([]model.Pricing, 0, len(pricing))
  40. for _, item := range pricing {
  41. if common.StringsContains(item.EnableGroup, "all") {
  42. filtered = append(filtered, item)
  43. continue
  44. }
  45. for _, group := range item.EnableGroup {
  46. if _, ok := usableGroup[group]; ok {
  47. filtered = append(filtered, item)
  48. break
  49. }
  50. }
  51. }
  52. return filtered
  53. }
  54. func GetPricing(c *gin.Context) {
  55. pricing := model.GetPricing()
  56. userId, exists := c.Get("id")
  57. usableGroup := map[string]string{}
  58. groupRatio := map[string]float64{}
  59. for s, f := range ratio_setting.GetGroupRatioCopy() {
  60. groupRatio[s] = f
  61. }
  62. var group string
  63. if exists {
  64. user, err := model.GetUserCache(userId.(int))
  65. if err == nil {
  66. group = user.Group
  67. for g := range groupRatio {
  68. ratio, ok := ratio_setting.GetGroupGroupRatio(group, g)
  69. if ok {
  70. groupRatio[g] = ratio
  71. }
  72. }
  73. }
  74. }
  75. usableGroup = service.GetUserUsableGroups(group)
  76. pricing = filterPricingByUsableGroups(pricing, usableGroup)
  77. // check groupRatio contains usableGroup
  78. for group := range ratio_setting.GetGroupRatioCopy() {
  79. if _, ok := usableGroup[group]; !ok {
  80. delete(groupRatio, group)
  81. }
  82. }
  83. c.JSON(200, gin.H{
  84. "success": true,
  85. "data": pricing,
  86. "vendors": model.GetVendors(),
  87. "group_ratio": groupRatio,
  88. "usable_group": usableGroup,
  89. "supported_endpoint": model.GetSupportedEndpointMap(),
  90. "auto_groups": service.GetUserAutoGroup(group),
  91. "_": "a42d372ccf0b5dd13ecf71203521f9d2",
  92. })
  93. }
  94. // GetUserPricing 获取用户对指定模型的价格信息(原价 vs 用户价)
  95. func GetUserPricing(c *gin.Context) {
  96. modelName := c.Param("model")
  97. // *model 通配符返回值带前导 /,需要去掉
  98. modelName = strings.TrimPrefix(modelName, "/")
  99. if modelName == "" {
  100. common.ApiErrorMsg(c, "模型名不能为空")
  101. return
  102. }
  103. pricingData := model.GetPricingByModel(modelName)
  104. if pricingData == nil {
  105. common.ApiErrorMsg(c, "未找到该模型的定价信息")
  106. return
  107. }
  108. loggedIn, _, userGroup, groupRatio := resolveCurrentUser(c)
  109. if !loggedIn {
  110. respondOriginalPrice(c, pricingData)
  111. return
  112. }
  113. totalRatio := groupRatio
  114. savingsPercent := int(math.Round((1 - totalRatio) * 100))
  115. result := gin.H{
  116. "success": true,
  117. "model_name": pricingData.ModelName,
  118. "quota_type": pricingData.QuotaType,
  119. "group": userGroup,
  120. "group_ratio": groupRatio,
  121. "savings_percent": savingsPercent,
  122. "logged_in": true,
  123. }
  124. if pricingData.PricingConfig != nil {
  125. result["pricing_config"] = pricingData.PricingConfig
  126. }
  127. if pricingData.QuotaType == model.QuotaTypeByTokens {
  128. if pricingData.PricingConfig != nil {
  129. originalPrice := representativeMatrixPrice(pricingData.PricingConfig, pricingData.ModelPrice)
  130. result["original_price"] = originalPrice
  131. result["user_price"] = originalPrice * totalRatio
  132. }
  133. originalInput := pricingData.ModelRatio * 2
  134. originalOutput := pricingData.ModelRatio * pricingData.CompletionRatio * 2
  135. result["original_input"] = originalInput
  136. result["original_output"] = originalOutput
  137. result["user_input"] = originalInput * totalRatio
  138. result["user_output"] = originalOutput * totalRatio
  139. } else {
  140. result["original_price"] = pricingData.ModelPrice
  141. result["user_price"] = pricingData.ModelPrice * totalRatio
  142. }
  143. if savingsPercent > 0 {
  144. result["discount"] = formatDiscount(totalRatio)
  145. }
  146. c.JSON(200, result)
  147. }
  148. // respondOriginalPrice 未登录或用户查询失败时,只返回原价
  149. func respondOriginalPrice(c *gin.Context, pricingData *model.Pricing) {
  150. result := gin.H{
  151. "success": true,
  152. "model_name": pricingData.ModelName,
  153. "quota_type": pricingData.QuotaType,
  154. "logged_in": false,
  155. }
  156. if pricingData.QuotaType == model.QuotaTypeByTokens {
  157. if pricingData.PricingConfig != nil {
  158. result["pricing_config"] = pricingData.PricingConfig
  159. result["original_price"] = representativeMatrixPrice(pricingData.PricingConfig, pricingData.ModelPrice)
  160. }
  161. result["original_input"] = pricingData.ModelRatio * 2
  162. result["original_output"] = pricingData.ModelRatio * pricingData.CompletionRatio * 2
  163. } else {
  164. if pricingData.PricingConfig != nil {
  165. result["pricing_config"] = pricingData.PricingConfig
  166. }
  167. result["original_price"] = pricingData.ModelPrice
  168. }
  169. c.JSON(200, result)
  170. }
  171. func representativeMatrixPrice(cfg *types.PricingConfig, fallback float64) float64 {
  172. if cfg == nil || len(cfg.Table) == 0 {
  173. return fallback
  174. }
  175. prices := make([]float64, 0, len(cfg.Table))
  176. for _, row := range cfg.Table {
  177. price, ok := pricingRowFloat(row["price"])
  178. if ok {
  179. prices = append(prices, price)
  180. }
  181. }
  182. if len(prices) == 0 {
  183. return fallback
  184. }
  185. sort.Float64s(prices)
  186. mid := len(prices) / 2
  187. if len(prices)%2 == 1 {
  188. return prices[mid]
  189. }
  190. return (prices[mid-1] + prices[mid]) / 2
  191. }
  192. func pricingRowFloat(value any) (float64, bool) {
  193. switch n := value.(type) {
  194. case float64:
  195. return n, true
  196. case float32:
  197. return float64(n), true
  198. case int:
  199. return float64(n), true
  200. case int64:
  201. return float64(n), true
  202. case int32:
  203. return float64(n), true
  204. default:
  205. return 0, false
  206. }
  207. }
  208. // formatDiscount 将倍率转换为中文折扣格式
  209. func formatDiscount(ratio float64) string {
  210. if ratio <= 0 {
  211. return "免费"
  212. }
  213. rawDiscount := math.Round(ratio*100) / 10
  214. if rawDiscount >= 10 {
  215. return ""
  216. }
  217. if rawDiscount == math.Trunc(rawDiscount) {
  218. return fmt.Sprintf("%.0f折", rawDiscount)
  219. }
  220. return fmt.Sprintf("%.1f折", rawDiscount)
  221. }
  222. func ResetModelRatio(c *gin.Context) {
  223. defaultStr := ratio_setting.DefaultModelRatio2JSONString()
  224. err := model.UpdateOption("ModelRatio", defaultStr)
  225. if err != nil {
  226. c.JSON(200, gin.H{
  227. "success": false,
  228. "message": err.Error(),
  229. })
  230. return
  231. }
  232. err = ratio_setting.UpdateModelRatioByJSONString(defaultStr)
  233. if err != nil {
  234. c.JSON(200, gin.H{
  235. "success": false,
  236. "message": err.Error(),
  237. })
  238. return
  239. }
  240. c.JSON(200, gin.H{
  241. "success": true,
  242. "message": "重置模型倍率成功",
  243. })
  244. }