瀏覽代碼

feat: channels param (#77)

* feat: dashbaord and log support channel query

* feat: soft channel delete

* fix: swag script
zijiren 9 月之前
父節點
當前提交
da34df6fbe
共有 10 個文件被更改,包括 293 次插入58 次删除
  1. 2 2
      .github/workflows/release.yml
  2. 2 2
      Dockerfile
  3. 17 8
      controller/dashboard.go
  4. 51 0
      docs/docs.go
  5. 51 0
      docs/swagger.json
  6. 34 0
      docs/swagger.yaml
  7. 19 14
      model/channel.go
  8. 111 28
      model/log.go
  9. 4 0
      scripts/swag.sh
  10. 2 4
      scripts/tiktoken.sh

+ 2 - 2
.github/workflows/release.yml

@@ -50,7 +50,7 @@ jobs:
 
       - name: Download tiktoken
         run: |
-          bash common/tiktoken/assest.sh
+          bash scripts/tiktoken.sh
 
       - name: Setup Go
         uses: actions/setup-go@v5
@@ -60,7 +60,7 @@ jobs:
       - name: Generate Swagger
         run: |
           go install github.com/swaggo/swag/cmd/swag@latest
-          swag init
+          bash scripts/swag.sh
 
       - name: Build
         run: |

+ 2 - 2
Dockerfile

@@ -6,11 +6,11 @@ COPY ./ ./
 
 RUN apk add --no-cache curl
 
-RUN sh common/tiktoken/assest.sh
+RUN sh scripts/tiktoken.sh
 
 RUN go install github.com/swaggo/swag/cmd/swag@latest
 
-RUN swag init
+RUN sh scripts/swag.sh
 
 RUN go build -trimpath -tags "jsoniter" -ldflags "-s -w" -o aiproxy
 

+ 17 - 8
controller/dashboard.go

@@ -133,6 +133,7 @@ func fillGaps(data []*model.ChartData, start, end time.Time, t model.TimeSpanTyp
 //	@Produce		json
 //	@Security		ApiKeyAuth
 //	@Param			group		query		string	false	"Group or *"
+//	@Param			channel		query		int		false	"Channel ID"
 //	@Param			type		query		string	false	"Type of time span (day, week, month, two_week)"
 //	@Param			model		query		string	false	"Model name"
 //	@Param			result_only	query		bool	false	"Only return result"
@@ -147,7 +148,11 @@ func GetDashboard(c *gin.Context) {
 	modelName := c.Query("model")
 	resultOnly, _ := strconv.ParseBool(c.Query("result_only"))
 	tokenUsage, _ := strconv.ParseBool(c.Query("token_usage"))
-	dashboards, err := model.GetDashboardData(group, start, end, modelName, timeSpan, resultOnly, false, tokenUsage)
+	channelID, _ := strconv.Atoi(c.Query("channel"))
+
+	needRPM := channelID != 0
+
+	dashboards, err := model.GetDashboardData(group, start, end, modelName, channelID, timeSpan, resultOnly, needRPM, tokenUsage)
 	if err != nil {
 		middleware.ErrorResponse(c, http.StatusOK, err.Error())
 		return
@@ -155,11 +160,13 @@ func GetDashboard(c *gin.Context) {
 
 	dashboards.ChartData = fillGaps(dashboards.ChartData, start, end, timeSpan)
 
-	rpm, err := rpmlimit.GetRPM(c.Request.Context(), group, modelName)
-	if err != nil {
-		log.Errorf("failed to get rpm: %v", err)
-	} else {
-		dashboards.RPM = rpm
+	if !needRPM {
+		rpm, err := rpmlimit.GetRPM(c.Request.Context(), group, modelName)
+		if err != nil {
+			log.Errorf("failed to get rpm: %v", err)
+		} else {
+			dashboards.RPM = rpm
+		}
 	}
 
 	middleware.SuccessResponse(c, dashboards)
@@ -267,6 +274,7 @@ func GetGroupDashboardModels(c *gin.Context) {
 //	@Produce		json
 //	@Security		ApiKeyAuth
 //	@Param			group			query		string	false	"Group or *"
+//	@Param			channel			query		int		false	"Channel ID"
 //	@Param			start_timestamp	query		int64	false	"Start timestamp"
 //	@Param			end_timestamp	query		int64	false	"End timestamp"
 //	@Param			token_usage		query		bool	false	"Token usage"
@@ -274,9 +282,10 @@ func GetGroupDashboardModels(c *gin.Context) {
 //	@Router			/api/model_cost_rank [get]
 func GetModelCostRank(c *gin.Context) {
 	group := c.Query("group")
+	channelID, _ := strconv.Atoi(c.Query("channel"))
 	startTime, endTime := parseTimeRange(c)
 	tokenUsage, _ := strconv.ParseBool(c.Query("token_usage"))
-	models, err := model.GetModelCostRank(group, startTime, endTime, tokenUsage)
+	models, err := model.GetModelCostRank(group, channelID, startTime, endTime, tokenUsage)
 	if err != nil {
 		middleware.ErrorResponse(c, http.StatusOK, err.Error())
 		return
@@ -305,7 +314,7 @@ func GetGroupModelCostRank(c *gin.Context) {
 	}
 	startTime, endTime := parseTimeRange(c)
 	tokenUsage, _ := strconv.ParseBool(c.Query("token_usage"))
-	models, err := model.GetModelCostRank(group, startTime, endTime, tokenUsage)
+	models, err := model.GetModelCostRank(group, 0, startTime, endTime, tokenUsage)
 	if err != nil {
 		middleware.ErrorResponse(c, http.StatusOK, err.Error())
 		return

+ 51 - 0
docs/docs.go

@@ -983,6 +983,12 @@ const docTemplate = `{
                         "name": "group",
                         "in": "query"
                     },
+                    {
+                        "type": "integer",
+                        "description": "Channel ID",
+                        "name": "channel",
+                        "in": "query"
+                    },
                     {
                         "type": "string",
                         "description": "Type of time span (day, week, month, two_week)",
@@ -3562,6 +3568,12 @@ const docTemplate = `{
                         "name": "group",
                         "in": "query"
                     },
+                    {
+                        "type": "integer",
+                        "description": "Channel ID",
+                        "name": "channel",
+                        "in": "query"
+                    },
                     {
                         "type": "integer",
                         "description": "Start timestamp",
@@ -6791,6 +6803,18 @@ const docTemplate = `{
                 }
             }
         },
+        "gorm.DeletedAt": {
+            "type": "object",
+            "properties": {
+                "time": {
+                    "type": "string"
+                },
+                "valid": {
+                    "description": "Valid is true if Time is not NULL",
+                    "type": "boolean"
+                }
+            }
+        },
         "middleware.APIResponse": {
             "type": "object",
             "properties": {
@@ -6872,6 +6896,9 @@ const docTemplate = `{
                 "created_at": {
                     "type": "string"
                 },
+                "deletedAt": {
+                    "$ref": "#/definitions/gorm.DeletedAt"
+                },
                 "enabled_auto_balance_check": {
                     "type": "boolean"
                 },
@@ -7003,6 +7030,12 @@ const docTemplate = `{
         "model.DashboardResponse": {
             "type": "object",
             "properties": {
+                "channels": {
+                    "type": "array",
+                    "items": {
+                        "type": "integer"
+                    }
+                },
                 "chart_data": {
                     "type": "array",
                     "items": {
@@ -7219,6 +7252,12 @@ const docTemplate = `{
         "model.GetGroupLogsResult": {
             "type": "object",
             "properties": {
+                "channels": {
+                    "type": "array",
+                    "items": {
+                        "type": "integer"
+                    }
+                },
                 "logs": {
                     "type": "array",
                     "items": {
@@ -7245,6 +7284,12 @@ const docTemplate = `{
         "model.GetLogsResult": {
             "type": "object",
             "properties": {
+                "channels": {
+                    "type": "array",
+                    "items": {
+                        "type": "integer"
+                    }
+                },
                 "logs": {
                     "type": "array",
                     "items": {
@@ -7303,6 +7348,12 @@ const docTemplate = `{
         "model.GroupDashboardResponse": {
             "type": "object",
             "properties": {
+                "channels": {
+                    "type": "array",
+                    "items": {
+                        "type": "integer"
+                    }
+                },
                 "chart_data": {
                     "type": "array",
                     "items": {

+ 51 - 0
docs/swagger.json

@@ -972,6 +972,12 @@
                         "name": "group",
                         "in": "query"
                     },
+                    {
+                        "type": "integer",
+                        "description": "Channel ID",
+                        "name": "channel",
+                        "in": "query"
+                    },
                     {
                         "type": "string",
                         "description": "Type of time span (day, week, month, two_week)",
@@ -3551,6 +3557,12 @@
                         "name": "group",
                         "in": "query"
                     },
+                    {
+                        "type": "integer",
+                        "description": "Channel ID",
+                        "name": "channel",
+                        "in": "query"
+                    },
                     {
                         "type": "integer",
                         "description": "Start timestamp",
@@ -6780,6 +6792,18 @@
                 }
             }
         },
+        "gorm.DeletedAt": {
+            "type": "object",
+            "properties": {
+                "time": {
+                    "type": "string"
+                },
+                "valid": {
+                    "description": "Valid is true if Time is not NULL",
+                    "type": "boolean"
+                }
+            }
+        },
         "middleware.APIResponse": {
             "type": "object",
             "properties": {
@@ -6861,6 +6885,9 @@
                 "created_at": {
                     "type": "string"
                 },
+                "deletedAt": {
+                    "$ref": "#/definitions/gorm.DeletedAt"
+                },
                 "enabled_auto_balance_check": {
                     "type": "boolean"
                 },
@@ -6992,6 +7019,12 @@
         "model.DashboardResponse": {
             "type": "object",
             "properties": {
+                "channels": {
+                    "type": "array",
+                    "items": {
+                        "type": "integer"
+                    }
+                },
                 "chart_data": {
                     "type": "array",
                     "items": {
@@ -7208,6 +7241,12 @@
         "model.GetGroupLogsResult": {
             "type": "object",
             "properties": {
+                "channels": {
+                    "type": "array",
+                    "items": {
+                        "type": "integer"
+                    }
+                },
                 "logs": {
                     "type": "array",
                     "items": {
@@ -7234,6 +7273,12 @@
         "model.GetLogsResult": {
             "type": "object",
             "properties": {
+                "channels": {
+                    "type": "array",
+                    "items": {
+                        "type": "integer"
+                    }
+                },
                 "logs": {
                     "type": "array",
                     "items": {
@@ -7292,6 +7337,12 @@
         "model.GroupDashboardResponse": {
             "type": "object",
             "properties": {
+                "channels": {
+                    "type": "array",
+                    "items": {
+                        "type": "integer"
+                    }
+                },
                 "chart_data": {
                     "type": "array",
                     "items": {

+ 34 - 0
docs/swagger.yaml

@@ -330,6 +330,14 @@ definitions:
       total_tokens:
         type: integer
     type: object
+  gorm.DeletedAt:
+    properties:
+      time:
+        type: string
+      valid:
+        description: Valid is true if Time is not NULL
+        type: boolean
+    type: object
   middleware.APIResponse:
     properties:
       data: {}
@@ -391,6 +399,8 @@ definitions:
         $ref: '#/definitions/model.ChannelConfig'
       created_at:
         type: string
+      deletedAt:
+        $ref: '#/definitions/gorm.DeletedAt'
       enabled_auto_balance_check:
         type: boolean
       id:
@@ -477,6 +487,10 @@ definitions:
     type: object
   model.DashboardResponse:
     properties:
+      channels:
+        items:
+          type: integer
+        type: array
       chart_data:
         items:
           $ref: '#/definitions/model.ChartData'
@@ -622,6 +636,10 @@ definitions:
     type: object
   model.GetGroupLogsResult:
     properties:
+      channels:
+        items:
+          type: integer
+        type: array
       logs:
         items:
           $ref: '#/definitions/model.Log'
@@ -639,6 +657,10 @@ definitions:
     type: object
   model.GetLogsResult:
     properties:
+      channels:
+        items:
+          type: integer
+        type: array
       logs:
         items:
           $ref: '#/definitions/model.Log'
@@ -677,6 +699,10 @@ definitions:
     type: object
   model.GroupDashboardResponse:
     properties:
+      channels:
+        items:
+          type: integer
+        type: array
       chart_data:
         items:
           $ref: '#/definitions/model.ChartData'
@@ -1717,6 +1743,10 @@ paths:
         in: query
         name: group
         type: string
+      - description: Channel ID
+        in: query
+        name: channel
+        type: integer
       - description: Type of time span (day, week, month, two_week)
         in: query
         name: type
@@ -3280,6 +3310,10 @@ paths:
         in: query
         name: group
         type: string
+      - description: Channel ID
+        in: query
+        name: channel
+        type: integer
       - description: Start timestamp
         in: query
         name: start_timestamp

+ 19 - 14
model/channel.go

@@ -35,6 +35,7 @@ type ChannelConfig struct {
 }
 
 type Channel struct {
+	DeletedAt               gorm.DeletedAt    `gorm:"index"`
 	CreatedAt               time.Time         `gorm:"index"                              json:"created_at"`
 	LastTestErrorAt         time.Time         `json:"last_test_error_at"`
 	ChannelTests            []*ChannelTest    `gorm:"foreignKey:ChannelID;references:ID" json:"channel_tests,omitempty"`
@@ -325,21 +326,25 @@ func UpdateChannel(channel *Channel) (err error) {
 	if err := CheckModelConfigExist(channel.Models); err != nil {
 		return err
 	}
+	selects := []string{
+		"model_mapping",
+		"key",
+		"base_url",
+		"models",
+		"priority",
+		"config",
+		"enabled_auto_balance_check",
+		"balance_threshold",
+		"sets",
+	}
+	if channel.Type != 0 {
+		selects = append(selects, "type")
+	}
+	if channel.Name != "" {
+		selects = append(selects, "name")
+	}
 	result := DB.
-		Model(channel).
-		Select(
-			"model_mapping",
-			"key",
-			"name",
-			"base_url",
-			"models",
-			"type",
-			"priority",
-			"config",
-			"enabled_auto_balance_check",
-			"balance_threshold",
-			"sets",
-		).
+		Select(selects).
 		Clauses(clause.Returning{}).
 		Where("id = ?", channel.ID).
 		Updates(channel)

+ 111 - 28
model/log.go

@@ -94,8 +94,12 @@ func CreateLogIndexes(db *gorm.DB) error {
 
 			// global day indexes, used by global dashboard
 			"CREATE INDEX IF NOT EXISTS idx_model_truncday ON logs (model, timestamp_trunc_by_day)",
+			"CREATE INDEX IF NOT EXISTS idx_channel_truncday ON logs (channel_id, timestamp_trunc_by_day)",
+			"CREATE INDEX IF NOT EXISTS idx_channel_model_truncday ON logs (channel_id, model, timestamp_trunc_by_day)",
 			// global hour indexes, used by global dashboard
 			"CREATE INDEX IF NOT EXISTS idx_model_trunchour ON logs (model, timestamp_trunc_by_hour)",
+			"CREATE INDEX IF NOT EXISTS idx_channel_trunchour ON logs (channel_id, timestamp_trunc_by_hour)",
+			"CREATE INDEX IF NOT EXISTS idx_channel_model_trunchour ON logs (channel_id, model, timestamp_trunc_by_hour)",
 
 			// used by search group logs
 			"CREATE INDEX IF NOT EXISTS idx_group_reqat ON logs (group_id, request_at DESC)",
@@ -116,6 +120,8 @@ func CreateLogIndexes(db *gorm.DB) error {
 			"CREATE INDEX IF NOT EXISTS idx_group_model_trunchour ON logs (group_id, model, timestamp_trunc_by_hour DESC)",
 			"CREATE INDEX IF NOT EXISTS idx_group_token_trunchour ON logs (group_id, token_name, timestamp_trunc_by_hour DESC)",
 			"CREATE INDEX IF NOT EXISTS idx_group_model_token_trunchour ON logs (group_id, model, token_name, timestamp_trunc_by_hour DESC)",
+
+			"CREATE INDEX IF NOT EXISTS idx_ip_group_reqat ON logs (ip, group_id, request_at DESC)",
 		}
 	} else {
 		indexes = []string{
@@ -127,9 +133,13 @@ func CreateLogIndexes(db *gorm.DB) error {
 			"CREATE INDEX IF NOT EXISTS idx_channel_model_reqat ON logs (channel_id, model, request_at DESC) INCLUDE (code, request_id, downstream_result)",
 
 			// global day indexes, used by global dashboard
-			"CREATE INDEX IF NOT EXISTS idx_model_truncday ON logs (model, timestamp_trunc_by_day) INCLUDE (code, downstream_result)",
+			"CREATE INDEX IF NOT EXISTS idx_model_truncday ON logs (model, timestamp_trunc_by_day) INCLUDE (downstream_result)",
+			"CREATE INDEX IF NOT EXISTS idx_channel_truncday ON logs (channel_id, timestamp_trunc_by_day) INCLUDE (downstream_result)",
+			"CREATE INDEX IF NOT EXISTS idx_channel_model_truncday ON logs (channel_id, model, timestamp_trunc_by_day) INCLUDE (downstream_result)",
 			// global hour indexes, used by global dashboard
-			"CREATE INDEX IF NOT EXISTS idx_model_trunchour ON logs (model, timestamp_trunc_by_hour) INCLUDE (code, downstream_result)",
+			"CREATE INDEX IF NOT EXISTS idx_model_trunchour ON logs (model, timestamp_trunc_by_hour) INCLUDE (downstream_result)",
+			"CREATE INDEX IF NOT EXISTS idx_channel_trunchour ON logs (channel_id, timestamp_trunc_by_hour) INCLUDE (downstream_result)",
+			"CREATE INDEX IF NOT EXISTS idx_channel_model_trunchour ON logs (channel_id, model, timestamp_trunc_by_hour) INCLUDE (downstream_result)",
 
 			// used by search group logs
 			"CREATE INDEX IF NOT EXISTS idx_group_reqat ON logs (group_id, request_at DESC) INCLUDE (code, request_id, downstream_result)",
@@ -150,6 +160,8 @@ func CreateLogIndexes(db *gorm.DB) error {
 			"CREATE INDEX IF NOT EXISTS idx_group_model_trunchour ON logs (group_id, model, timestamp_trunc_by_hour DESC) INCLUDE (downstream_result)",
 			"CREATE INDEX IF NOT EXISTS idx_group_token_trunchour ON logs (group_id, token_name, timestamp_trunc_by_hour DESC) INCLUDE (downstream_result)",
 			"CREATE INDEX IF NOT EXISTS idx_group_model_token_trunchour ON logs (group_id, model, token_name, timestamp_trunc_by_hour DESC) INCLUDE (downstream_result)",
+
+			"CREATE INDEX IF NOT EXISTS idx_ip_group_reqat ON logs (ip, group_id, request_at DESC)",
 		}
 	}
 
@@ -262,7 +274,8 @@ func cleanLog(batchSize int) error {
 	}
 
 	logContentStorageHours := config.GetLogContentStorageHours()
-	if logContentStorageHours <= 0 {
+	if logContentStorageHours <= 0 ||
+		logContentStorageHours <= logStorageHours {
 		return nil
 	}
 	return LogDB.
@@ -379,8 +392,9 @@ const (
 )
 
 type GetLogsResult struct {
-	Logs  []*Log `json:"logs"`
-	Total int64  `json:"total"`
+	Logs     []*Log `json:"logs"`
+	Total    int64  `json:"total"`
+	Channels []int  `json:"channels,omitempty"`
 }
 
 type GetGroupLogsResult struct {
@@ -560,14 +574,32 @@ func GetLogs(
 	perPage int,
 	resultOnly bool,
 ) (*GetLogsResult, error) {
-	total, logs, err := getLogs(group, startTimestamp, endTimestamp, modelName, requestID, tokenID, tokenName, channelID, endpoint, order, mode, codeType, withBody, ip, page, perPage, resultOnly)
-	if err != nil {
+	var total int64
+	var logs []*Log
+	var channels []int
+
+	g := new(errgroup.Group)
+
+	g.Go(func() error {
+		var err error
+		channels, err = GetUsedChannels(group, startTimestamp, endTimestamp)
+		return err
+	})
+
+	g.Go(func() error {
+		var err error
+		total, logs, err = getLogs(group, startTimestamp, endTimestamp, modelName, requestID, tokenID, tokenName, channelID, endpoint, order, mode, codeType, withBody, ip, page, perPage, resultOnly)
+		return err
+	})
+
+	if err := g.Wait(); err != nil {
 		return nil, err
 	}
 
 	result := &GetLogsResult{
-		Logs:  logs,
-		Total: total,
+		Logs:     logs,
+		Total:    total,
+		Channels: channels,
 	}
 
 	return result, nil
@@ -875,14 +907,32 @@ func SearchLogs(
 	perPage int,
 	resultOnly bool,
 ) (*GetLogsResult, error) {
-	total, logs, err := searchLogs(group, keyword, endpoint, requestID, tokenID, tokenName, modelName, startTimestamp, endTimestamp, channelID, order, mode, codeType, withBody, ip, page, perPage, resultOnly)
-	if err != nil {
+	var total int64
+	var logs []*Log
+	var channels []int
+
+	g := new(errgroup.Group)
+
+	g.Go(func() error {
+		var err error
+		total, logs, err = searchLogs(group, keyword, endpoint, requestID, tokenID, tokenName, modelName, startTimestamp, endTimestamp, channelID, order, mode, codeType, withBody, ip, page, perPage, resultOnly)
+		return err
+	})
+
+	g.Go(func() error {
+		var err error
+		channels, err = GetUsedChannels(group, startTimestamp, endTimestamp)
+		return err
+	})
+
+	if err := g.Wait(); err != nil {
 		return nil, err
 	}
 
 	result := &GetLogsResult{
-		Logs:  logs,
-		Total: total,
+		Logs:     logs,
+		Total:    total,
+		Channels: channels,
 	}
 
 	return result, nil
@@ -987,6 +1037,7 @@ type DashboardResponse struct {
 	UsedAmount     float64      `json:"used_amount"`
 	RPM            int64        `json:"rpm"`
 	TPM            int64        `json:"tpm"`
+	Channels       []int        `json:"channels,omitempty"`
 }
 
 type GroupDashboardResponse struct {
@@ -1013,9 +1064,13 @@ func getTimeSpanFormat(t TimeSpanType) string {
 	}
 }
 
-func getChartData(group string, start, end time.Time, tokenName, modelName string, timeSpan TimeSpanType, resultOnly bool, tokenUsage bool) ([]*ChartData, error) {
-	var chartData []*ChartData
-
+func getChartData(group string,
+	start, end time.Time,
+	tokenName, modelName string,
+	channelID int,
+	timeSpan TimeSpanType,
+	resultOnly bool, tokenUsage bool,
+) ([]*ChartData, error) {
 	timeSpanFormat := getTimeSpanFormat(timeSpan)
 	if timeSpanFormat == "" {
 		return nil, errors.New("unsupported time format")
@@ -1040,6 +1095,9 @@ func getChartData(group string, start, end time.Time, tokenName, modelName strin
 		query = query.Where("group_id = ?", group)
 	}
 
+	if channelID != 0 {
+		query = query.Where("channel_id = ?", channelID)
+	}
 	if modelName != "" {
 		query = query.Where("model = ?", modelName)
 	}
@@ -1060,11 +1118,16 @@ func getChartData(group string, start, end time.Time, tokenName, modelName strin
 		query = query.Where("downstream_result = true")
 	}
 
+	var chartData []*ChartData
 	err := query.Scan(&chartData).Error
 
 	return chartData, err
 }
 
+func GetUsedChannels(group string, start, end time.Time) ([]int, error) {
+	return getLogGroupByValues[int]("channel_id", group, start, end)
+}
+
 func GetUsedModels(group string, start, end time.Time) ([]string, error) {
 	return getLogGroupByValues[string]("model", group, start, end)
 }
@@ -1157,7 +1220,7 @@ func sumUsedAmount(chartData []*ChartData) float64 {
 	return amount.InexactFloat64()
 }
 
-func getRPM(group string, end time.Time, tokenName, modelName string, resultOnly bool) (int64, error) {
+func getRPM(group string, end time.Time, tokenName, modelName string, channelID int, resultOnly bool) (int64, error) {
 	query := LogDB.Model(&Log{})
 
 	if group == "" {
@@ -1165,6 +1228,9 @@ func getRPM(group string, end time.Time, tokenName, modelName string, resultOnly
 	} else if group != "*" {
 		query = query.Where("group_id = ?", group)
 	}
+	if channelID != 0 {
+		query = query.Where("channel_id = ?", channelID)
+	}
 	if modelName != "" {
 		query = query.Where("model = ?", modelName)
 	}
@@ -1182,16 +1248,18 @@ func getRPM(group string, end time.Time, tokenName, modelName string, resultOnly
 	return count, err
 }
 
-func getTPM(group string, end time.Time, tokenName, modelName string, resultOnly bool) (int64, error) {
+func getTPM(group string, end time.Time, tokenName, modelName string, channelID int, resultOnly bool) (int64, error) {
 	query := LogDB.Model(&Log{}).
-		Select("COALESCE(SUM(total_tokens), 0)").
-		Where("request_at >= ? AND request_at <= ?", end.Add(-time.Minute), end)
+		Select("COALESCE(SUM(total_tokens), 0)")
 
 	if group == "" {
 		query = query.Where("group_id = ''")
 	} else if group != "*" {
 		query = query.Where("group_id = ?", group)
 	}
+	if channelID != 0 {
+		query = query.Where("channel_id = ?", channelID)
+	}
 	if modelName != "" {
 		query = query.Where("model = ?", modelName)
 	}
@@ -1203,7 +1271,9 @@ func getTPM(group string, end time.Time, tokenName, modelName string, resultOnly
 	}
 
 	var tpm int64
-	err := query.Scan(&tpm).Error
+	err := query.
+		Where("request_at BETWEEN ? AND ?", end.Add(-time.Minute), end).
+		Scan(&tpm).Error
 	return tpm, err
 }
 
@@ -1212,6 +1282,7 @@ func GetDashboardData(
 	start,
 	end time.Time,
 	modelName string,
+	channelID int,
 	timeSpan TimeSpanType,
 	resultOnly bool,
 	needRPM bool,
@@ -1227,27 +1298,34 @@ func GetDashboardData(
 		chartData []*ChartData
 		rpm       int64
 		tpm       int64
+		channels  []int
 	)
 
 	g := new(errgroup.Group)
 
 	g.Go(func() error {
 		var err error
-		chartData, err = getChartData(group, start, end, "", modelName, timeSpan, resultOnly, tokenUsage)
+		chartData, err = getChartData(group, start, end, "", modelName, channelID, timeSpan, resultOnly, tokenUsage)
 		return err
 	})
 
 	if needRPM {
 		g.Go(func() error {
 			var err error
-			rpm, err = getRPM(group, end, "", modelName, resultOnly)
+			rpm, err = getRPM(group, end, "", modelName, channelID, resultOnly)
 			return err
 		})
 	}
 
 	g.Go(func() error {
 		var err error
-		tpm, err = getTPM(group, end, "", modelName, resultOnly)
+		tpm, err = getTPM(group, end, "", modelName, channelID, resultOnly)
+		return err
+	})
+
+	g.Go(func() error {
+		var err error
+		channels, err = GetUsedChannels(group, start, end)
 		return err
 	})
 
@@ -1266,6 +1344,7 @@ func GetDashboardData(
 		UsedAmount:     usedAmount,
 		RPM:            rpm,
 		TPM:            tpm,
+		Channels:       channels,
 	}, nil
 }
 
@@ -1301,7 +1380,7 @@ func GetGroupDashboardData(
 
 	g.Go(func() error {
 		var err error
-		chartData, err = getChartData(group, start, end, tokenName, modelName, timeSpan, resultOnly, tokenUsage)
+		chartData, err = getChartData(group, start, end, tokenName, modelName, 0, timeSpan, resultOnly, tokenUsage)
 		return err
 	})
 
@@ -1320,14 +1399,14 @@ func GetGroupDashboardData(
 	if needRPM {
 		g.Go(func() error {
 			var err error
-			rpm, err = getRPM(group, end, tokenName, modelName, resultOnly)
+			rpm, err = getRPM(group, end, tokenName, modelName, 0, resultOnly)
 			return err
 		})
 	}
 
 	g.Go(func() error {
 		var err error
-		tpm, err = getTPM(group, end, tokenName, modelName, resultOnly)
+		tpm, err = getTPM(group, end, tokenName, modelName, 0, resultOnly)
 		return err
 	})
 
@@ -1393,7 +1472,7 @@ type ModelCostRank struct {
 	Total               int64   `json:"total"`
 }
 
-func GetModelCostRank(group string, start, end time.Time, tokenUsage bool) ([]*ModelCostRank, error) {
+func GetModelCostRank(group string, channelID int, start, end time.Time, tokenUsage bool) ([]*ModelCostRank, error) {
 	var ranks []*ModelCostRank
 
 	var query *gorm.DB
@@ -1415,6 +1494,10 @@ func GetModelCostRank(group string, start, end time.Time, tokenUsage bool) ([]*M
 		query = query.Where("group_id = ?", group)
 	}
 
+	if channelID != 0 {
+		query = query.Where("channel_id = ?", channelID)
+	}
+
 	switch {
 	case !start.IsZero() && !end.IsZero():
 		query = query.Where("request_at BETWEEN ? AND ?", start, end)

+ 4 - 0
scripts/swag.sh

@@ -0,0 +1,4 @@
+#!/bin/bash
+
+swag init --parseDependency --parseInternal
+swag fmt

+ 2 - 4
common/tiktoken/assest.sh → scripts/tiktoken.sh

@@ -11,10 +11,8 @@ https://openaipublic.blob.core.windows.net/encodings/r50k_base.tiktoken
 EOF
 )
 
-mkdir -p "$(dirname $0)/assets"
-
-rm -f "$(dirname $0)/assets/*"
+mkdir -p "$(dirname $0)/../common/tiktoken/assets"
 
 for asset in $ASSETS; do
-    curl -L -f -o "$(dirname $0)/assets/$(basename $asset)" "$asset"
+    curl -L -f -o "$(dirname $0)/../common/tiktoken/assets/$(basename $asset)" "$asset"
 done