179 lines
5.3 KiB
Go
179 lines
5.3 KiB
Go
package controller
|
|
|
|
import (
|
|
"net/http"
|
|
"sort"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/QuantumNous/new-api/common"
|
|
"github.com/QuantumNous/new-api/model"
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// SupplierModelUsageItem 供应商模型使用统计项。
|
|
type SupplierModelUsageItem struct {
|
|
ModelName string `json:"model_name"`
|
|
Requests int `json:"requests"`
|
|
Tokens int `json:"tokens"`
|
|
Quota int `json:"quota"`
|
|
}
|
|
|
|
// loadSupplierDashboardAccount 加载供应商对接人(申请人)在平台上的剩余额度与历史累计已用额度,与使用日志中的额度/花费字段同源。
|
|
func loadSupplierDashboardAccount(c *gin.Context, adminSupplierID int) (quota int, usedQuota int) {
|
|
if c.GetInt("role") >= common.RoleAdminUser {
|
|
if adminSupplierID <= 0 {
|
|
return 0, 0
|
|
}
|
|
app, err := model.GetSupplierByID(adminSupplierID)
|
|
if err != nil || app == nil || app.ApplicantUserID <= 0 {
|
|
return 0, 0
|
|
}
|
|
u, err := model.GetUserById(app.ApplicantUserID, false)
|
|
if err != nil {
|
|
return 0, 0
|
|
}
|
|
return u.Quota, u.UsedQuota
|
|
}
|
|
u, err := model.GetUserById(c.GetInt("id"), false)
|
|
if err != nil {
|
|
return 0, 0
|
|
}
|
|
return u.Quota, u.UsedQuota
|
|
}
|
|
|
|
// parseSupplierDashboardTimeRange 解析供应商看板时间范围:请求参数 start_timestamp、end_timestamp 为 Unix 秒;未传或非法时默认最近 24 小时。
|
|
func parseSupplierDashboardTimeRange(c *gin.Context) (int64, int64) {
|
|
startTimestamp, _ := strconv.ParseInt(c.Query("start_timestamp"), 10, 64)
|
|
endTimestamp, _ := strconv.ParseInt(c.Query("end_timestamp"), 10, 64)
|
|
if endTimestamp <= 0 {
|
|
endTimestamp = time.Now().Unix()
|
|
}
|
|
if startTimestamp <= 0 || startTimestamp >= endTimestamp {
|
|
startTimestamp = endTimestamp - 24*3600
|
|
}
|
|
return startTimestamp, endTimestamp
|
|
}
|
|
|
|
// toSortedModelSlice 将模型集合转换为稳定排序切片。
|
|
func toSortedModelSlice(modelsMap map[string]struct{}) []string {
|
|
modelNames := make([]string, 0, len(modelsMap))
|
|
for modelName := range modelsMap {
|
|
modelNames = append(modelNames, modelName)
|
|
}
|
|
sort.Strings(modelNames)
|
|
return modelNames
|
|
}
|
|
|
|
// GetSupplierDashboardData 返回供应商数据看板(供应商看自己,管理员看全部供应商模型)。
|
|
func GetSupplierDashboardData(c *gin.Context) {
|
|
startTimestamp, endTimestamp := parseSupplierDashboardTimeRange(c)
|
|
adminSupplierID, _ := strconv.Atoi(c.Query("supplier_id"))
|
|
accountQuota, accountUsedQuota := loadSupplierDashboardAccount(c, adminSupplierID)
|
|
|
|
var (
|
|
modelNamesMap map[string]struct{}
|
|
err error
|
|
)
|
|
|
|
// 管理员默认查看全部供应商模型;当传 supplier_id 时查看指定供应商。
|
|
if c.GetInt("role") >= common.RoleAdminUser {
|
|
supplierID := adminSupplierID
|
|
if supplierID > 0 {
|
|
modelNamesMap, err = collectSupplierOwnedModelNamesBySupplierID(supplierID)
|
|
} else {
|
|
modelNamesMap, err = collectAllSupplierOwnedModelNames()
|
|
}
|
|
} else {
|
|
modelNamesMap, err = collectSupplierOwnedModelNames(c.GetInt("id"))
|
|
}
|
|
if err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
|
|
modelNames := toSortedModelSlice(modelNamesMap)
|
|
quotaData, err := model.GetQuotaDataByModelNames(startTimestamp, endTimestamp, modelNames)
|
|
if err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
stat, err := model.SumUsedQuotaByModelNames(startTimestamp, endTimestamp, modelNames)
|
|
if err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
|
|
usageMap := make(map[string]*SupplierModelUsageItem)
|
|
totalRequests := 0
|
|
totalTokens := 0
|
|
totalQuota := 0
|
|
|
|
for _, item := range quotaData {
|
|
if item == nil {
|
|
continue
|
|
}
|
|
totalRequests += item.Count
|
|
totalTokens += item.TokenUsed
|
|
totalQuota += item.Quota
|
|
|
|
usageItem, ok := usageMap[item.ModelName]
|
|
if !ok {
|
|
usageItem = &SupplierModelUsageItem{
|
|
ModelName: item.ModelName,
|
|
}
|
|
usageMap[item.ModelName] = usageItem
|
|
}
|
|
usageItem.Requests += item.Count
|
|
usageItem.Tokens += item.TokenUsed
|
|
usageItem.Quota += item.Quota
|
|
}
|
|
|
|
modelUsageStats := make([]*SupplierModelUsageItem, 0, len(usageMap))
|
|
for _, usageItem := range usageMap {
|
|
modelUsageStats = append(modelUsageStats, usageItem)
|
|
}
|
|
sort.Slice(modelUsageStats, func(i, j int) bool {
|
|
return modelUsageStats[i].Quota > modelUsageStats[j].Quota
|
|
})
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"message": "",
|
|
"data": gin.H{
|
|
"start_timestamp": startTimestamp,
|
|
"end_timestamp": endTimestamp,
|
|
"usage_time_range": gin.H{
|
|
"start_timestamp": startTimestamp,
|
|
"end_timestamp": endTimestamp,
|
|
"bucket": "hour",
|
|
},
|
|
"account": gin.H{
|
|
"quota": accountQuota,
|
|
"used_quota": accountUsedQuota,
|
|
},
|
|
"model_names": modelNames,
|
|
"quota_data": quotaData,
|
|
"model_usage_stats": modelUsageStats,
|
|
"resource_consumption": gin.H{
|
|
"total_requests": totalRequests,
|
|
"total_tokens": totalTokens,
|
|
"total_quota": totalQuota,
|
|
},
|
|
"performance_metrics": gin.H{
|
|
"rpm": stat.Rpm,
|
|
"tpm": stat.Tpm,
|
|
},
|
|
"model_data_analysis": gin.H{
|
|
// provided_model_count: 供应商配置过的模型总数(无请求也计入)。
|
|
"provided_model_count": len(modelNames),
|
|
// active_model_count: 时间范围内有调用数据的模型数。
|
|
"active_model_count": len(modelUsageStats),
|
|
// model_count 保留为兼容字段,语义等同 provided_model_count。
|
|
"model_count": len(modelNames),
|
|
"top_models": modelUsageStats,
|
|
},
|
|
},
|
|
})
|
|
}
|