mirror of
				https://github.com/linux-do/new-api.git
				synced 2025-11-04 05:13:41 +08:00 
			
		
		
		
	feat: able to query logs now (close #144)
This commit is contained in:
		@@ -13,7 +13,11 @@ func GetAllLogs(c *gin.Context) {
 | 
			
		||||
		p = 0
 | 
			
		||||
	}
 | 
			
		||||
	logType, _ := strconv.Atoi(c.Query("type"))
 | 
			
		||||
	logs, err := model.GetAllLogs(logType, p*common.ItemsPerPage, common.ItemsPerPage)
 | 
			
		||||
	startTimestamp, _ := strconv.ParseInt(c.Query("start_timestamp"), 10, 64)
 | 
			
		||||
	endTimestamp, _ := strconv.ParseInt(c.Query("end_timestamp"), 10, 64)
 | 
			
		||||
	username := c.Query("username")
 | 
			
		||||
	modelName := c.Query("model_name")
 | 
			
		||||
	logs, err := model.GetAllLogs(logType, startTimestamp, endTimestamp, modelName, username, p*common.ItemsPerPage, common.ItemsPerPage)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		c.JSON(200, gin.H{
 | 
			
		||||
			"success": false,
 | 
			
		||||
@@ -35,7 +39,11 @@ func GetUserLogs(c *gin.Context) {
 | 
			
		||||
	}
 | 
			
		||||
	userId := c.GetInt("id")
 | 
			
		||||
	logType, _ := strconv.Atoi(c.Query("type"))
 | 
			
		||||
	logs, err := model.GetUserLogs(userId, logType, p*common.ItemsPerPage, common.ItemsPerPage)
 | 
			
		||||
	startTimestamp, _ := strconv.ParseInt(c.Query("start_timestamp"), 10, 64)
 | 
			
		||||
	endTimestamp, _ := strconv.ParseInt(c.Query("end_timestamp"), 10, 64)
 | 
			
		||||
	tokenName := c.Query("token_name")
 | 
			
		||||
	modelName := c.Query("model_name")
 | 
			
		||||
	logs, err := model.GetUserLogs(userId, logType, startTimestamp, endTimestamp, modelName, tokenName, p*common.ItemsPerPage, common.ItemsPerPage)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		c.JSON(200, gin.H{
 | 
			
		||||
			"success": false,
 | 
			
		||||
@@ -84,3 +92,40 @@ func SearchUserLogs(c *gin.Context) {
 | 
			
		||||
		"data":    logs,
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetLogsStat(c *gin.Context) {
 | 
			
		||||
	logType, _ := strconv.Atoi(c.Query("type"))
 | 
			
		||||
	startTimestamp, _ := strconv.ParseInt(c.Query("start_timestamp"), 10, 64)
 | 
			
		||||
	endTimestamp, _ := strconv.ParseInt(c.Query("end_timestamp"), 10, 64)
 | 
			
		||||
	username := c.Query("username")
 | 
			
		||||
	modelName := c.Query("model_name")
 | 
			
		||||
	quotaNum := model.SumUsedQuota(logType, startTimestamp, endTimestamp, modelName, username, "")
 | 
			
		||||
	//tokenNum := model.SumUsedToken(logType, startTimestamp, endTimestamp, modelName, username, "")
 | 
			
		||||
	c.JSON(200, gin.H{
 | 
			
		||||
		"success": true,
 | 
			
		||||
		"message": "",
 | 
			
		||||
		"data": gin.H{
 | 
			
		||||
			"quota": quotaNum,
 | 
			
		||||
			//"token": tokenNum,
 | 
			
		||||
		},
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetLogsSelfStat(c *gin.Context) {
 | 
			
		||||
	username := c.GetString("username")
 | 
			
		||||
	logType, _ := strconv.Atoi(c.Query("type"))
 | 
			
		||||
	startTimestamp, _ := strconv.ParseInt(c.Query("start_timestamp"), 10, 64)
 | 
			
		||||
	endTimestamp, _ := strconv.ParseInt(c.Query("end_timestamp"), 10, 64)
 | 
			
		||||
	tokenName := c.Query("token_name")
 | 
			
		||||
	modelName := c.Query("model_name")
 | 
			
		||||
	quotaNum := model.SumUsedQuota(logType, startTimestamp, endTimestamp, modelName, username, tokenName)
 | 
			
		||||
	//tokenNum := model.SumUsedToken(logType, startTimestamp, endTimestamp, modelName, username, tokenName)
 | 
			
		||||
	c.JSON(200, gin.H{
 | 
			
		||||
		"success": true,
 | 
			
		||||
		"message": "",
 | 
			
		||||
		"data": gin.H{
 | 
			
		||||
			"quota": quotaNum,
 | 
			
		||||
			//"token": tokenNum,
 | 
			
		||||
		},
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -58,6 +58,7 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	var promptTokens int
 | 
			
		||||
	var completionTokens int
 | 
			
		||||
	switch relayMode {
 | 
			
		||||
	case RelayModeChatCompletions:
 | 
			
		||||
		promptTokens = countTokenMessages(textRequest.Messages, textRequest.Model)
 | 
			
		||||
@@ -128,11 +129,12 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode {
 | 
			
		||||
				completionRatio = 2
 | 
			
		||||
			}
 | 
			
		||||
			if isStream {
 | 
			
		||||
				responseTokens := countTokenText(streamResponseText, textRequest.Model)
 | 
			
		||||
				quota = promptTokens + int(float64(responseTokens)*completionRatio)
 | 
			
		||||
				completionTokens = countTokenText(streamResponseText, textRequest.Model)
 | 
			
		||||
			} else {
 | 
			
		||||
				quota = textResponse.Usage.PromptTokens + int(float64(textResponse.Usage.CompletionTokens)*completionRatio)
 | 
			
		||||
				promptTokens = textResponse.Usage.PromptTokens
 | 
			
		||||
				completionTokens = textResponse.Usage.CompletionTokens
 | 
			
		||||
			}
 | 
			
		||||
			quota = promptTokens + int(float64(completionTokens)*completionRatio)
 | 
			
		||||
			quota = int(float64(quota) * ratio)
 | 
			
		||||
			if ratio != 0 && quota <= 0 {
 | 
			
		||||
				quota = 1
 | 
			
		||||
@@ -143,7 +145,8 @@ func relayTextHelper(c *gin.Context, relayMode int) *OpenAIErrorWithStatusCode {
 | 
			
		||||
				common.SysError("error consuming token remain quota: " + err.Error())
 | 
			
		||||
			}
 | 
			
		||||
			tokenName := c.GetString("token_name")
 | 
			
		||||
			model.RecordLog(userId, model.LogTypeConsume, fmt.Sprintf("通过令牌「%s」使用模型 %s 消耗 %s(模型倍率 %.2f,分组倍率 %.2f)", tokenName, textRequest.Model, common.LogQuota(quota), modelRatio, groupRatio))
 | 
			
		||||
			logContent := fmt.Sprintf("模型倍率 %.2f,分组倍率 %.2f", modelRatio, groupRatio)
 | 
			
		||||
			model.RecordConsumeLog(userId, promptTokens, completionTokens, textRequest.Model, tokenName, quota, logContent)
 | 
			
		||||
			model.UpdateUserUsedQuotaAndRequestCount(userId, quota)
 | 
			
		||||
			channelId := c.GetInt("channel_id")
 | 
			
		||||
			model.UpdateChannelUsedQuota(channelId, quota)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										18
									
								
								i18n/en.json
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								i18n/en.json
									
									
									
									
									
								
							@@ -441,5 +441,19 @@
 | 
			
		||||
  "此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改": "This item is read-only. Users need to bind through the relevant binding button on the personal settings page, and cannot be modified directly",
 | 
			
		||||
  "已绑定的微信账户": "WeChat Account Bound",
 | 
			
		||||
  "已绑定的邮箱账户": "Email Account Bound",
 | 
			
		||||
  "用户信息更新成功!": "User information updated successfully!"
 | 
			
		||||
}
 | 
			
		||||
  "用户信息更新成功!": "User information updated successfully!",
 | 
			
		||||
  "模型倍率 %.2f,分组倍率 %.2f": "model rate %.2f, group rate %.2f",
 | 
			
		||||
  "使用明细(总消耗额度:{renderQuota(stat.quota)})": "Usage Details (Total Consumption Quota: {renderQuota(stat.quota)})",
 | 
			
		||||
  "用户名称": "User Name",
 | 
			
		||||
  "令牌名称": "Token Name",
 | 
			
		||||
  "留空则查询全部用户": "Leave blank to query all users",
 | 
			
		||||
  "留空则查询全部令牌": "Leave blank to query all tokens",
 | 
			
		||||
  "模型名称": "Model Name",
 | 
			
		||||
  "留空则查询全部模型": "Leave blank to query all models",
 | 
			
		||||
  "起始时间": "Start Time",
 | 
			
		||||
  "结束时间": "End Time",
 | 
			
		||||
  "查询": "Query",
 | 
			
		||||
  "提示令牌": "Prompt Token",
 | 
			
		||||
  "补全令牌": "Completion Token",
 | 
			
		||||
  "消耗额度": "Used Quota"
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										109
									
								
								model/log.go
									
									
									
									
									
								
							
							
						
						
									
										109
									
								
								model/log.go
									
									
									
									
									
								
							@@ -6,11 +6,17 @@ import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Log struct {
 | 
			
		||||
	Id        int    `json:"id"`
 | 
			
		||||
	UserId    int    `json:"user_id" gorm:"index"`
 | 
			
		||||
	CreatedAt int64  `json:"created_at" gorm:"bigint"`
 | 
			
		||||
	Type      int    `json:"type" gorm:"index"`
 | 
			
		||||
	Content   string `json:"content"`
 | 
			
		||||
	Id               int    `json:"id"`
 | 
			
		||||
	UserId           int    `json:"user_id"`
 | 
			
		||||
	CreatedAt        int64  `json:"created_at" gorm:"bigint;index"`
 | 
			
		||||
	Type             int    `json:"type" gorm:"index"`
 | 
			
		||||
	Content          string `json:"content"`
 | 
			
		||||
	Username         string `json:"username" gorm:"index;default:''"`
 | 
			
		||||
	TokenName        string `json:"token_name" gorm:"index;default:''"`
 | 
			
		||||
	ModelName        string `json:"model_name" gorm:"index;default:''"`
 | 
			
		||||
	Quota            int    `json:"quota" gorm:"default:0"`
 | 
			
		||||
	PromptTokens     int    `json:"prompt_tokens" gorm:"default:0"`
 | 
			
		||||
	CompletionTokens int    `json:"completion_tokens" gorm:"default:0"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
@@ -27,6 +33,7 @@ func RecordLog(userId int, logType int, content string) {
 | 
			
		||||
	}
 | 
			
		||||
	log := &Log{
 | 
			
		||||
		UserId:    userId,
 | 
			
		||||
		Username:  GetUsernameById(userId),
 | 
			
		||||
		CreatedAt: common.GetTimestamp(),
 | 
			
		||||
		Type:      logType,
 | 
			
		||||
		Content:   content,
 | 
			
		||||
@@ -37,24 +44,70 @@ func RecordLog(userId int, logType int, content string) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetAllLogs(logType int, startIdx int, num int) (logs []*Log, err error) {
 | 
			
		||||
func RecordConsumeLog(userId int, promptTokens int, completionTokens int, modelName string, tokenName string, quota int, content string) {
 | 
			
		||||
	if !common.LogConsumeEnabled {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	log := &Log{
 | 
			
		||||
		UserId:           userId,
 | 
			
		||||
		Username:         GetUsernameById(userId),
 | 
			
		||||
		CreatedAt:        common.GetTimestamp(),
 | 
			
		||||
		Type:             LogTypeConsume,
 | 
			
		||||
		Content:          content,
 | 
			
		||||
		PromptTokens:     promptTokens,
 | 
			
		||||
		CompletionTokens: completionTokens,
 | 
			
		||||
		TokenName:        tokenName,
 | 
			
		||||
		ModelName:        modelName,
 | 
			
		||||
		Quota:            quota,
 | 
			
		||||
	}
 | 
			
		||||
	err := DB.Create(log).Error
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		common.SysError("failed to record log: " + err.Error())
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetAllLogs(logType int, startTimestamp int64, endTimestamp int64, modelName string, username string, startIdx int, num int) (logs []*Log, err error) {
 | 
			
		||||
	var tx *gorm.DB
 | 
			
		||||
	if logType == LogTypeUnknown {
 | 
			
		||||
		tx = DB
 | 
			
		||||
	} else {
 | 
			
		||||
		tx = DB.Where("type = ?", logType)
 | 
			
		||||
	}
 | 
			
		||||
	if modelName != "" {
 | 
			
		||||
		tx = tx.Where("model_name = ?", modelName)
 | 
			
		||||
	}
 | 
			
		||||
	if username != "" {
 | 
			
		||||
		tx = tx.Where("username = ?", username)
 | 
			
		||||
	}
 | 
			
		||||
	if startTimestamp != 0 {
 | 
			
		||||
		tx = tx.Where("created_at >= ?", startTimestamp)
 | 
			
		||||
	}
 | 
			
		||||
	if endTimestamp != 0 {
 | 
			
		||||
		tx = tx.Where("created_at <= ?", endTimestamp)
 | 
			
		||||
	}
 | 
			
		||||
	err = tx.Order("id desc").Limit(num).Offset(startIdx).Find(&logs).Error
 | 
			
		||||
	return logs, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetUserLogs(userId int, logType int, startIdx int, num int) (logs []*Log, err error) {
 | 
			
		||||
func GetUserLogs(userId int, logType int, startTimestamp int64, endTimestamp int64, modelName string, tokenName string, startIdx int, num int) (logs []*Log, err error) {
 | 
			
		||||
	var tx *gorm.DB
 | 
			
		||||
	if logType == LogTypeUnknown {
 | 
			
		||||
		tx = DB.Where("user_id = ?", userId)
 | 
			
		||||
	} else {
 | 
			
		||||
		tx = DB.Where("user_id = ? and type = ?", userId, logType)
 | 
			
		||||
	}
 | 
			
		||||
	if modelName != "" {
 | 
			
		||||
		tx = tx.Where("model_name = ?", modelName)
 | 
			
		||||
	}
 | 
			
		||||
	if tokenName != "" {
 | 
			
		||||
		tx = tx.Where("token_name = ?", tokenName)
 | 
			
		||||
	}
 | 
			
		||||
	if startTimestamp != 0 {
 | 
			
		||||
		tx = tx.Where("created_at >= ?", startTimestamp)
 | 
			
		||||
	}
 | 
			
		||||
	if endTimestamp != 0 {
 | 
			
		||||
		tx = tx.Where("created_at <= ?", endTimestamp)
 | 
			
		||||
	}
 | 
			
		||||
	err = tx.Order("id desc").Limit(num).Offset(startIdx).Omit("id").Find(&logs).Error
 | 
			
		||||
	return logs, err
 | 
			
		||||
}
 | 
			
		||||
@@ -68,3 +121,45 @@ func SearchUserLogs(userId int, keyword string) (logs []*Log, err error) {
 | 
			
		||||
	err = DB.Where("user_id = ? and type = ?", userId, keyword).Order("id desc").Limit(common.MaxRecentItems).Omit("id").Find(&logs).Error
 | 
			
		||||
	return logs, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func SumUsedQuota(logType int, startTimestamp int64, endTimestamp int64, modelName string, username string, tokenName string) (quota int) {
 | 
			
		||||
	tx := DB.Table("logs").Select("sum(quota)")
 | 
			
		||||
	if username != "" {
 | 
			
		||||
		tx = tx.Where("username = ?", username)
 | 
			
		||||
	}
 | 
			
		||||
	if tokenName != "" {
 | 
			
		||||
		tx = tx.Where("token_name = ?", tokenName)
 | 
			
		||||
	}
 | 
			
		||||
	if startTimestamp != 0 {
 | 
			
		||||
		tx = tx.Where("created_at >= ?", startTimestamp)
 | 
			
		||||
	}
 | 
			
		||||
	if endTimestamp != 0 {
 | 
			
		||||
		tx = tx.Where("created_at <= ?", endTimestamp)
 | 
			
		||||
	}
 | 
			
		||||
	if modelName != "" {
 | 
			
		||||
		tx = tx.Where("model_name = ?", modelName)
 | 
			
		||||
	}
 | 
			
		||||
	tx.Where("type = ?", LogTypeConsume).Scan("a)
 | 
			
		||||
	return quota
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func SumUsedToken(logType int, startTimestamp int64, endTimestamp int64, modelName string, username string, tokenName string) (token int) {
 | 
			
		||||
	tx := DB.Table("logs").Select("sum(prompt_tokens) + sum(completion_tokens)")
 | 
			
		||||
	if username != "" {
 | 
			
		||||
		tx = tx.Where("username = ?", username)
 | 
			
		||||
	}
 | 
			
		||||
	if tokenName != "" {
 | 
			
		||||
		tx = tx.Where("token_name = ?", tokenName)
 | 
			
		||||
	}
 | 
			
		||||
	if startTimestamp != 0 {
 | 
			
		||||
		tx = tx.Where("created_at >= ?", startTimestamp)
 | 
			
		||||
	}
 | 
			
		||||
	if endTimestamp != 0 {
 | 
			
		||||
		tx = tx.Where("created_at <= ?", endTimestamp)
 | 
			
		||||
	}
 | 
			
		||||
	if modelName != "" {
 | 
			
		||||
		tx = tx.Where("model_name = ?", modelName)
 | 
			
		||||
	}
 | 
			
		||||
	tx.Where("type = ?", LogTypeConsume).Scan(&token)
 | 
			
		||||
	return token
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -303,3 +303,8 @@ func UpdateUserUsedQuotaAndRequestCount(id int, quota int) {
 | 
			
		||||
		common.SysError("failed to update user used quota and request count: " + err.Error())
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetUsernameById(id int) (username string) {
 | 
			
		||||
	DB.Model(&User{}).Where("id = ?", id).Select("username").Find(&username)
 | 
			
		||||
	return username
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -96,6 +96,8 @@ func SetApiRouter(router *gin.Engine) {
 | 
			
		||||
		}
 | 
			
		||||
		logRoute := apiRouter.Group("/log")
 | 
			
		||||
		logRoute.GET("/", middleware.AdminAuth(), controller.GetAllLogs)
 | 
			
		||||
		logRoute.GET("/stat", middleware.AdminAuth(), controller.GetLogsStat)
 | 
			
		||||
		logRoute.GET("/self/stat", middleware.UserAuth(), controller.GetLogsSelfStat)
 | 
			
		||||
		logRoute.GET("/search", middleware.AdminAuth(), controller.SearchAllLogs)
 | 
			
		||||
		logRoute.GET("/self", middleware.UserAuth(), controller.GetUserLogs)
 | 
			
		||||
		logRoute.GET("/self/search", middleware.UserAuth(), controller.SearchUserLogs)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,9 @@
 | 
			
		||||
import React, { useEffect, useState } from 'react';
 | 
			
		||||
import { Button, Label, Pagination, Select, Table } from 'semantic-ui-react';
 | 
			
		||||
import { Button, Form, Header, Label, Pagination, Segment, Select, Table } from 'semantic-ui-react';
 | 
			
		||||
import { API, isAdmin, showError, timestamp2string } from '../helpers';
 | 
			
		||||
 | 
			
		||||
import { ITEMS_PER_PAGE } from '../constants';
 | 
			
		||||
import { renderQuota } from '../helpers/render';
 | 
			
		||||
 | 
			
		||||
function renderTimestamp(timestamp) {
 | 
			
		||||
  return (
 | 
			
		||||
@@ -14,7 +15,7 @@ function renderTimestamp(timestamp) {
 | 
			
		||||
 | 
			
		||||
const MODE_OPTIONS = [
 | 
			
		||||
  { key: 'all', text: '全部用户', value: 'all' },
 | 
			
		||||
  { key: 'self', text: '当前用户', value: 'self' },
 | 
			
		||||
  { key: 'self', text: '当前用户', value: 'self' }
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const LOG_OPTIONS = [
 | 
			
		||||
@@ -47,13 +48,57 @@ const LogsTable = () => {
 | 
			
		||||
  const [searchKeyword, setSearchKeyword] = useState('');
 | 
			
		||||
  const [searching, setSearching] = useState(false);
 | 
			
		||||
  const [logType, setLogType] = useState(0);
 | 
			
		||||
  const [mode, setMode] = useState('self'); // all, self
 | 
			
		||||
  const showModePanel = isAdmin();
 | 
			
		||||
  const isAdminUser = isAdmin();
 | 
			
		||||
  let now = new Date();
 | 
			
		||||
  const [inputs, setInputs] = useState({
 | 
			
		||||
    name: '',
 | 
			
		||||
    model_name: '',
 | 
			
		||||
    start_timestamp: timestamp2string(0),
 | 
			
		||||
    end_timestamp: timestamp2string(now.getTime() / 1000 + 3600)
 | 
			
		||||
  });
 | 
			
		||||
  const { name, model_name, start_timestamp, end_timestamp } = inputs;
 | 
			
		||||
 | 
			
		||||
  const [stat, setStat] = useState({
 | 
			
		||||
    quota: 0,
 | 
			
		||||
    token: 0
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const handleInputChange = (e, { name, value }) => {
 | 
			
		||||
    setInputs((inputs) => ({ ...inputs, [name]: value }));
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const getLogSelfStat = async () => {
 | 
			
		||||
    let localStartTimestamp = Date.parse(start_timestamp) / 1000;
 | 
			
		||||
    let localEndTimestamp = Date.parse(end_timestamp) / 1000;
 | 
			
		||||
    let res = await API.get(`/api/log/self/stat?type=${logType}&token_name=${name}&model_name=${model_name}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}`);
 | 
			
		||||
    const { success, message, data } = res.data;
 | 
			
		||||
    if (success) {
 | 
			
		||||
      setStat(data);
 | 
			
		||||
    } else {
 | 
			
		||||
      showError(message);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const getLogStat = async () => {
 | 
			
		||||
    let localStartTimestamp = Date.parse(start_timestamp) / 1000;
 | 
			
		||||
    let localEndTimestamp = Date.parse(end_timestamp) / 1000;
 | 
			
		||||
    let res = await API.get(`/api/log/stat?type=${logType}&username=${name}&model_name=${model_name}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}`);
 | 
			
		||||
    const { success, message, data } = res.data;
 | 
			
		||||
    if (success) {
 | 
			
		||||
      setStat(data);
 | 
			
		||||
    } else {
 | 
			
		||||
      showError(message);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const loadLogs = async (startIdx) => {
 | 
			
		||||
    let url = `/api/log/self/?p=${startIdx}&type=${logType}`;
 | 
			
		||||
    if (mode === 'all') {
 | 
			
		||||
      url = `/api/log/?p=${startIdx}&type=${logType}`;
 | 
			
		||||
    let url = '';
 | 
			
		||||
    let localStartTimestamp = Date.parse(start_timestamp) / 1000;
 | 
			
		||||
    let localEndTimestamp = Date.parse(end_timestamp) / 1000;
 | 
			
		||||
    if (isAdminUser) {
 | 
			
		||||
      url = `/api/log/?p=${startIdx}&type=${logType}&username=${name}&model_name=${model_name}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}`;
 | 
			
		||||
    } else {
 | 
			
		||||
      url = `/api/log/self/?p=${startIdx}&type=${logType}&token_name=${name}&model_name=${model_name}&start_timestamp=${localStartTimestamp}&end_timestamp=${localEndTimestamp}`;
 | 
			
		||||
    }
 | 
			
		||||
    const res = await API.get(url);
 | 
			
		||||
    const { success, message, data } = res.data;
 | 
			
		||||
@@ -84,19 +129,16 @@ const LogsTable = () => {
 | 
			
		||||
  const refresh = async () => {
 | 
			
		||||
    setLoading(true);
 | 
			
		||||
    await loadLogs(0);
 | 
			
		||||
    if (isAdminUser) {
 | 
			
		||||
      getLogStat().then();
 | 
			
		||||
    } else {
 | 
			
		||||
      getLogSelfStat().then();
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    loadLogs(0)
 | 
			
		||||
      .then()
 | 
			
		||||
      .catch((reason) => {
 | 
			
		||||
        showError(reason);
 | 
			
		||||
      });
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    refresh().then();
 | 
			
		||||
  }, [mode, logType]);
 | 
			
		||||
  }, [logType]);
 | 
			
		||||
 | 
			
		||||
  const searchLogs = async () => {
 | 
			
		||||
    if (searchKeyword === '') {
 | 
			
		||||
@@ -137,118 +179,161 @@ const LogsTable = () => {
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
      <Table basic>
 | 
			
		||||
        <Table.Header>
 | 
			
		||||
          <Table.Row>
 | 
			
		||||
            <Table.HeaderCell
 | 
			
		||||
              style={{ cursor: 'pointer' }}
 | 
			
		||||
              onClick={() => {
 | 
			
		||||
                sortLog('created_time');
 | 
			
		||||
              }}
 | 
			
		||||
              width={3}
 | 
			
		||||
            >
 | 
			
		||||
              时间
 | 
			
		||||
            </Table.HeaderCell>
 | 
			
		||||
            {
 | 
			
		||||
              showModePanel && (
 | 
			
		||||
                <Table.HeaderCell
 | 
			
		||||
                  style={{ cursor: 'pointer' }}
 | 
			
		||||
                  onClick={() => {
 | 
			
		||||
                    sortLog('user_id');
 | 
			
		||||
                  }}
 | 
			
		||||
                  width={1}
 | 
			
		||||
                >
 | 
			
		||||
                  用户
 | 
			
		||||
                </Table.HeaderCell>
 | 
			
		||||
              )
 | 
			
		||||
            }
 | 
			
		||||
            <Table.HeaderCell
 | 
			
		||||
              style={{ cursor: 'pointer' }}
 | 
			
		||||
              onClick={() => {
 | 
			
		||||
                sortLog('type');
 | 
			
		||||
              }}
 | 
			
		||||
              width={2}
 | 
			
		||||
            >
 | 
			
		||||
              类型
 | 
			
		||||
            </Table.HeaderCell>
 | 
			
		||||
            <Table.HeaderCell
 | 
			
		||||
              style={{ cursor: 'pointer' }}
 | 
			
		||||
              onClick={() => {
 | 
			
		||||
                sortLog('content');
 | 
			
		||||
              }}
 | 
			
		||||
              width={showModePanel ? 10 : 11}
 | 
			
		||||
            >
 | 
			
		||||
              详情
 | 
			
		||||
            </Table.HeaderCell>
 | 
			
		||||
          </Table.Row>
 | 
			
		||||
        </Table.Header>
 | 
			
		||||
 | 
			
		||||
        <Table.Body>
 | 
			
		||||
          {logs
 | 
			
		||||
            .slice(
 | 
			
		||||
              (activePage - 1) * ITEMS_PER_PAGE,
 | 
			
		||||
              activePage * ITEMS_PER_PAGE
 | 
			
		||||
            )
 | 
			
		||||
            .map((log, idx) => {
 | 
			
		||||
              if (log.deleted) return <></>;
 | 
			
		||||
              return (
 | 
			
		||||
                <Table.Row key={log.created_at}>
 | 
			
		||||
                  <Table.Cell>{renderTimestamp(log.created_at)}</Table.Cell>
 | 
			
		||||
                  {
 | 
			
		||||
                    showModePanel && (
 | 
			
		||||
                      <Table.Cell><Label>{log.user_id}</Label></Table.Cell>
 | 
			
		||||
                    )
 | 
			
		||||
                  }
 | 
			
		||||
                  <Table.Cell>{renderType(log.type)}</Table.Cell>
 | 
			
		||||
                  <Table.Cell>{log.content}</Table.Cell>
 | 
			
		||||
                </Table.Row>
 | 
			
		||||
              );
 | 
			
		||||
            })}
 | 
			
		||||
        </Table.Body>
 | 
			
		||||
 | 
			
		||||
        <Table.Footer>
 | 
			
		||||
          <Table.Row>
 | 
			
		||||
            <Table.HeaderCell colSpan={showModePanel ? '5' : '4'}>
 | 
			
		||||
              {
 | 
			
		||||
                showModePanel && (
 | 
			
		||||
                  <Select
 | 
			
		||||
                    placeholder='选择模式'
 | 
			
		||||
                    options={MODE_OPTIONS}
 | 
			
		||||
                    style={{ marginRight: '8px' }}
 | 
			
		||||
                    name='mode'
 | 
			
		||||
                    value={mode}
 | 
			
		||||
                    onChange={(e, { name, value }) => {
 | 
			
		||||
                      setMode(value);
 | 
			
		||||
                    }}
 | 
			
		||||
                  />
 | 
			
		||||
                )
 | 
			
		||||
              }
 | 
			
		||||
              <Select
 | 
			
		||||
                placeholder='选择明细分类'
 | 
			
		||||
                options={LOG_OPTIONS}
 | 
			
		||||
                style={{ marginRight: '8px' }}
 | 
			
		||||
                name='logType'
 | 
			
		||||
                value={logType}
 | 
			
		||||
                onChange={(e, { name, value }) => {
 | 
			
		||||
                  setLogType(value);
 | 
			
		||||
      <Segment>
 | 
			
		||||
        <Header as='h3'>使用明细(总消耗额度:{renderQuota(stat.quota)})</Header>
 | 
			
		||||
        <Form>
 | 
			
		||||
          <Form.Group>
 | 
			
		||||
            <Form.Input fluid label={isAdminUser ? '用户名称' : '令牌名称'} width={3} value={name}
 | 
			
		||||
                        placeholder={isAdminUser ? '留空则查询全部用户' : '留空则查询全部令牌'} name='name'
 | 
			
		||||
                        onChange={handleInputChange} />
 | 
			
		||||
            <Form.Input fluid label='模型名称' width={3} value={model_name} placeholder='留空则查询全部模型' name='model_name'
 | 
			
		||||
                        onChange={handleInputChange} />
 | 
			
		||||
            <Form.Input fluid label='起始时间' width={4} value={start_timestamp} type='datetime-local'
 | 
			
		||||
                        name='start_timestamp'
 | 
			
		||||
                        onChange={handleInputChange} />
 | 
			
		||||
            <Form.Input fluid label='结束时间' width={4} value={end_timestamp} type='datetime-local'
 | 
			
		||||
                        name='end_timestamp'
 | 
			
		||||
                        onChange={handleInputChange} />
 | 
			
		||||
            <Form.Button fluid label='操作' width={2} onClick={refresh}>查询</Form.Button>
 | 
			
		||||
          </Form.Group>
 | 
			
		||||
        </Form>
 | 
			
		||||
        <Table basic compact size='small'>
 | 
			
		||||
          <Table.Header>
 | 
			
		||||
            <Table.Row>
 | 
			
		||||
              <Table.HeaderCell
 | 
			
		||||
                style={{ cursor: 'pointer' }}
 | 
			
		||||
                onClick={() => {
 | 
			
		||||
                  sortLog('created_time');
 | 
			
		||||
                }}
 | 
			
		||||
              />
 | 
			
		||||
              <Button size='small' onClick={refresh} loading={loading}>刷新</Button>
 | 
			
		||||
              <Pagination
 | 
			
		||||
                floated='right'
 | 
			
		||||
                activePage={activePage}
 | 
			
		||||
                onPageChange={onPaginationChange}
 | 
			
		||||
                size='small'
 | 
			
		||||
                siblingRange={1}
 | 
			
		||||
                totalPages={
 | 
			
		||||
                  Math.ceil(logs.length / ITEMS_PER_PAGE) +
 | 
			
		||||
                  (logs.length % ITEMS_PER_PAGE === 0 ? 1 : 0)
 | 
			
		||||
                }
 | 
			
		||||
              />
 | 
			
		||||
            </Table.HeaderCell>
 | 
			
		||||
          </Table.Row>
 | 
			
		||||
        </Table.Footer>
 | 
			
		||||
      </Table>
 | 
			
		||||
                width={3}
 | 
			
		||||
              >
 | 
			
		||||
                时间
 | 
			
		||||
              </Table.HeaderCell>
 | 
			
		||||
              <Table.HeaderCell
 | 
			
		||||
                style={{ cursor: 'pointer' }}
 | 
			
		||||
                width={1}
 | 
			
		||||
              >
 | 
			
		||||
                {isAdminUser ? '用户' : '令牌'}
 | 
			
		||||
              </Table.HeaderCell>
 | 
			
		||||
              <Table.HeaderCell
 | 
			
		||||
                style={{ cursor: 'pointer' }}
 | 
			
		||||
                onClick={() => {
 | 
			
		||||
                  sortLog('type');
 | 
			
		||||
                }}
 | 
			
		||||
                width={2}
 | 
			
		||||
              >
 | 
			
		||||
                类型
 | 
			
		||||
              </Table.HeaderCell>
 | 
			
		||||
              <Table.HeaderCell
 | 
			
		||||
                style={{ cursor: 'pointer' }}
 | 
			
		||||
                onClick={() => {
 | 
			
		||||
                  sortLog('model_name');
 | 
			
		||||
                }}
 | 
			
		||||
                width={2}
 | 
			
		||||
              >
 | 
			
		||||
                模型
 | 
			
		||||
              </Table.HeaderCell>
 | 
			
		||||
              <Table.HeaderCell
 | 
			
		||||
                style={{ cursor: 'pointer' }}
 | 
			
		||||
                onClick={() => {
 | 
			
		||||
                  sortLog('prompt_tokens');
 | 
			
		||||
                }}
 | 
			
		||||
                width={1}
 | 
			
		||||
              >
 | 
			
		||||
                提示令牌
 | 
			
		||||
              </Table.HeaderCell>
 | 
			
		||||
              <Table.HeaderCell
 | 
			
		||||
                style={{ cursor: 'pointer' }}
 | 
			
		||||
                onClick={() => {
 | 
			
		||||
                  sortLog('completion_tokens');
 | 
			
		||||
                }}
 | 
			
		||||
                width={1}
 | 
			
		||||
              >
 | 
			
		||||
                补全令牌
 | 
			
		||||
              </Table.HeaderCell>
 | 
			
		||||
              <Table.HeaderCell
 | 
			
		||||
                style={{ cursor: 'pointer' }}
 | 
			
		||||
                onClick={() => {
 | 
			
		||||
                  sortLog('quota');
 | 
			
		||||
                }}
 | 
			
		||||
                width={2}
 | 
			
		||||
              >
 | 
			
		||||
                消耗额度
 | 
			
		||||
              </Table.HeaderCell>
 | 
			
		||||
              <Table.HeaderCell
 | 
			
		||||
                style={{ cursor: 'pointer' }}
 | 
			
		||||
                onClick={() => {
 | 
			
		||||
                  sortLog('content');
 | 
			
		||||
                }}
 | 
			
		||||
                width={4}
 | 
			
		||||
              >
 | 
			
		||||
                详情
 | 
			
		||||
              </Table.HeaderCell>
 | 
			
		||||
            </Table.Row>
 | 
			
		||||
          </Table.Header>
 | 
			
		||||
 | 
			
		||||
          <Table.Body>
 | 
			
		||||
            {logs
 | 
			
		||||
              .slice(
 | 
			
		||||
                (activePage - 1) * ITEMS_PER_PAGE,
 | 
			
		||||
                activePage * ITEMS_PER_PAGE
 | 
			
		||||
              )
 | 
			
		||||
              .map((log, idx) => {
 | 
			
		||||
                if (log.deleted) return <></>;
 | 
			
		||||
                return (
 | 
			
		||||
                  <Table.Row key={log.created_at}>
 | 
			
		||||
                    <Table.Cell>{renderTimestamp(log.created_at)}</Table.Cell>
 | 
			
		||||
                    {
 | 
			
		||||
                      isAdminUser && (
 | 
			
		||||
                        <Table.Cell>{log.username ? <Label>{log.username}</Label> : ''}</Table.Cell>
 | 
			
		||||
                      )
 | 
			
		||||
                    }
 | 
			
		||||
                    {
 | 
			
		||||
                      !isAdminUser && (
 | 
			
		||||
                        <Table.Cell>{log.token_name ? <Label>{log.token_name}</Label> : ''}</Table.Cell>
 | 
			
		||||
                      )
 | 
			
		||||
                    }
 | 
			
		||||
                    <Table.Cell>{renderType(log.type)}</Table.Cell>
 | 
			
		||||
                    <Table.Cell>{log.model_name ? <Label basic>{log.model_name}</Label> : ''}</Table.Cell>
 | 
			
		||||
                    <Table.Cell>{log.prompt_tokens ? log.prompt_tokens: ''}</Table.Cell>
 | 
			
		||||
                    <Table.Cell>{log.completion_tokens ? log.completion_tokens: ''}</Table.Cell>
 | 
			
		||||
                    <Table.Cell>{log.quota ? renderQuota(log.quota, 6) : ''}</Table.Cell>
 | 
			
		||||
                    <Table.Cell>{log.content}</Table.Cell>
 | 
			
		||||
                  </Table.Row>
 | 
			
		||||
                );
 | 
			
		||||
              })}
 | 
			
		||||
          </Table.Body>
 | 
			
		||||
 | 
			
		||||
          <Table.Footer>
 | 
			
		||||
            <Table.Row>
 | 
			
		||||
              <Table.HeaderCell colSpan={'8'}>
 | 
			
		||||
                <Select
 | 
			
		||||
                  placeholder='选择明细分类'
 | 
			
		||||
                  options={LOG_OPTIONS}
 | 
			
		||||
                  style={{ marginRight: '8px' }}
 | 
			
		||||
                  name='logType'
 | 
			
		||||
                  value={logType}
 | 
			
		||||
                  onChange={(e, { name, value }) => {
 | 
			
		||||
                    setLogType(value);
 | 
			
		||||
                  }}
 | 
			
		||||
                />
 | 
			
		||||
                <Button size='small' onClick={refresh} loading={loading}>刷新</Button>
 | 
			
		||||
                <Pagination
 | 
			
		||||
                  floated='right'
 | 
			
		||||
                  activePage={activePage}
 | 
			
		||||
                  onPageChange={onPaginationChange}
 | 
			
		||||
                  size='small'
 | 
			
		||||
                  siblingRange={1}
 | 
			
		||||
                  totalPages={
 | 
			
		||||
                    Math.ceil(logs.length / ITEMS_PER_PAGE) +
 | 
			
		||||
                    (logs.length % ITEMS_PER_PAGE === 0 ? 1 : 0)
 | 
			
		||||
                  }
 | 
			
		||||
                />
 | 
			
		||||
              </Table.HeaderCell>
 | 
			
		||||
            </Table.Row>
 | 
			
		||||
          </Table.Footer>
 | 
			
		||||
        </Table>
 | 
			
		||||
      </Segment>
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -4,10 +4,7 @@ import LogsTable from '../../components/LogsTable';
 | 
			
		||||
 | 
			
		||||
const Token = () => (
 | 
			
		||||
  <>
 | 
			
		||||
    <Segment>
 | 
			
		||||
      <Header as='h3'>额度明细</Header>
 | 
			
		||||
      <LogsTable />
 | 
			
		||||
    </Segment>
 | 
			
		||||
    <LogsTable />
 | 
			
		||||
  </>
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user