Files
new-api/controller/model_pricing_config.go
CaIon 883eeb87d8 feat(plugins): warn about task models a plugin cannot charge
A plugin change can leave a task model without a usable price: no price
is set, the price uses usage the plugin no longer provides, or the
plugin no longer offers the model. The gateway now lists these models.
The list follows the plugins currently serving, so a gateway update
that changes a built-in plugin shows up too.

- Uploading, installing from the marketplace, activating or enabling a
  plugin, or deleting its active version, lists the models it cannot
  charge in the upload dialog or in the notice after the action.
- Model pricing marks each of them "Price needs attention" with the
  reason, and a "Price status" filter lists them. A plugin model that
  was never priced now appears in the table too. The badge replaces
  "Task pricing not configured".
- The sidebar shows how many models need attention on System Settings,
  Billing & Payment and Model Pricing, for super administrators.
- GET /api/option/model_pricing/issues returns the list. It is root
  only; access tokens need the option:read scope.
- Prices kept for a switched-off plugin are not reported. They apply
  again when the plugin is switched back on.
2026-10-30 17:34:37 +08:00

91 lines
2.6 KiB
Go

package controller
import (
"errors"
"net/http"
"github.com/QuantumNous/new-api/common"
"github.com/QuantumNous/new-api/model"
"github.com/gin-gonic/gin"
)
func GetModelPricingConfig(c *gin.Context) {
snapshot, err := model.GetModelPricingSnapshot(c.QueryArray("model"))
if err != nil {
common.ApiError(c, err)
return
}
common.ApiSuccess(c, snapshot)
}
func GetModelPricingIssues(c *gin.Context) {
issues, err := model.GetModelPricingIssues("")
if err != nil {
common.ApiError(c, err)
return
}
common.ApiSuccess(c, issues)
}
func PreviewModelPricingConversion(c *gin.Context) {
var request struct {
ModelName string `json:"model_name"`
Pricing model.PricingValues `json:"pricing"`
}
if err := common.DecodeJson(c.Request.Body, &request); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": err.Error()})
return
}
preview, err := model.PreviewModelPricingConversion(request.ModelName, request.Pricing)
if err != nil {
common.ApiError(c, err)
return
}
common.ApiSuccess(c, preview)
}
func PreviewModelPricing(c *gin.Context) {
var request struct {
ModelName string `json:"model_name"`
Pricing model.PricingValues `json:"pricing"`
}
if err := common.DecodeJson(c.Request.Body, &request); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": err.Error()})
return
}
preview, err := model.PreviewModelPricing(request.ModelName, request.Pricing)
if err != nil {
common.ApiError(c, err)
return
}
common.ApiSuccess(c, model.ModelPricingDescription{
Effective: preview,
CacheWriteMode: model.ResolveCacheWriteMode(request.ModelName, request.Pricing),
BillingDetails: model.ResolveLegacyBillingDetails(request.ModelName, preview, request.Pricing),
})
}
func UpdateModelPricingConfig(c *gin.Context) {
var request struct {
Changes []model.ModelPricingChange `json:"changes"`
}
if err := common.DecodeJson(c.Request.Body, &request); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": err.Error()})
return
}
if err := model.UpdateModelPricing(request.Changes); err != nil {
status := http.StatusBadRequest
if errors.Is(err, model.ErrModelPricingConflict) {
status = http.StatusConflict
}
c.JSON(status, gin.H{"success": false, "message": err.Error()})
return
}
names := make([]string, 0, len(request.Changes))
for _, change := range request.Changes {
names = append(names, change.ModelName)
}
recordManageAudit(c, "model.pricing.update", map[string]any{"models": names})
common.ApiSuccess(c, gin.H{"updated_models": names})
}