Sfoglia il codice sorgente

feat: dashboard v2 group by channel and model (#261)

* feat: dashboard v2 group by channel and model

* fix: ci lint
zijiren 6 mesi fa
parent
commit
b4fd149ce5

+ 2 - 2
core/controller/dashboard.go

@@ -339,7 +339,7 @@ func GetGroupDashboardModels(c *gin.Context) {
 //	@Param			end_timestamp	query		int64	false	"End timestamp"
 //	@Param			timezone		query		string	false	"Timezone, default is Local"
 //	@Param			timespan		query		string	false	"Time span type (day, hour, minute)"
-//	@Success		200				{object}	middleware.APIResponse{data=[]model.TimeModelData}
+//	@Success		200				{object}	middleware.APIResponse{data=[]model.TimeSummaryDataV2}
 //	@Router			/api/dashboardv2/ [get]
 func GetTimeSeriesModelData(c *gin.Context) {
 	channelID, _ := strconv.Atoi(c.Query("channel"))
@@ -372,7 +372,7 @@ func GetTimeSeriesModelData(c *gin.Context) {
 //	@Param			end_timestamp	query		int64	false	"End timestamp"
 //	@Param			timezone		query		string	false	"Timezone, default is Local"
 //	@Param			timespan		query		string	false	"Time span type (day, hour, minute)"
-//	@Success		200				{object}	middleware.APIResponse{data=[]model.TimeModelData}
+//	@Success		200				{object}	middleware.APIResponse{data=[]model.TimeSummaryDataV2}
 //	@Router			/api/dashboardv2/{group} [get]
 func GetGroupTimeSeriesModelData(c *gin.Context) {
 	group := c.Param("group")

+ 72 - 67
core/docs/docs.go

@@ -1188,7 +1188,7 @@ const docTemplate = `{
                                         "data": {
                                             "type": "array",
                                             "items": {
-                                                "$ref": "#/definitions/model.TimeModelData"
+                                                "$ref": "#/definitions/model.TimeSummaryDataV2"
                                             }
                                         }
                                     }
@@ -1267,7 +1267,7 @@ const docTemplate = `{
                                         "data": {
                                             "type": "array",
                                             "items": {
-                                                "$ref": "#/definitions/model.TimeModelData"
+                                                "$ref": "#/definitions/model.TimeSummaryDataV2"
                                             }
                                         }
                                     }
@@ -10157,56 +10157,6 @@ const docTemplate = `{
                 }
             }
         },
-        "model.ModelData": {
-            "type": "object",
-            "properties": {
-                "cache_creation_tokens": {
-                    "type": "integer"
-                },
-                "cached_tokens": {
-                    "type": "integer"
-                },
-                "exception_count": {
-                    "type": "integer"
-                },
-                "input_tokens": {
-                    "type": "integer"
-                },
-                "max_rpm": {
-                    "type": "integer"
-                },
-                "max_tpm": {
-                    "type": "integer"
-                },
-                "model": {
-                    "type": "string"
-                },
-                "output_tokens": {
-                    "type": "integer"
-                },
-                "request_count": {
-                    "type": "integer"
-                },
-                "timestamp": {
-                    "type": "integer"
-                },
-                "total_time_milliseconds": {
-                    "type": "integer"
-                },
-                "total_tokens": {
-                    "type": "integer"
-                },
-                "total_ttfb_milliseconds": {
-                    "type": "integer"
-                },
-                "used_amount": {
-                    "type": "number"
-                },
-                "web_search_count": {
-                    "type": "integer"
-                }
-            }
-        },
         "model.ModelOwner": {
             "type": "string",
             "enum": [
@@ -10289,17 +10239,6 @@ const docTemplate = `{
                 }
             }
         },
-        "model.ParamType": {
-            "type": "string",
-            "enum": [
-                "header",
-                "query"
-            ],
-            "x-enum-varnames": [
-                "ParamTypeHeader",
-                "ParamTypeQuery"
-            ]
-        },
         "model.ParsePdfResponse": {
             "type": "object",
             "properties": {
@@ -10376,6 +10315,19 @@ const docTemplate = `{
                 }
             }
         },
+        "model.ProxyParamType": {
+            "type": "string",
+            "enum": [
+                "url",
+                "header",
+                "query"
+            ],
+            "x-enum-varnames": [
+                "ParamTypeURL",
+                "ParamTypeHeader",
+                "ParamTypeQuery"
+            ]
+        },
         "model.PublicMCP": {
             "type": "object",
             "properties": {
@@ -10480,7 +10432,7 @@ const docTemplate = `{
                     "type": "boolean"
                 },
                 "type": {
-                    "$ref": "#/definitions/model.ParamType"
+                    "$ref": "#/definitions/model.ProxyParamType"
                 }
             }
         },
@@ -10684,6 +10636,59 @@ const docTemplate = `{
                 }
             }
         },
+        "model.SummaryDataV2": {
+            "type": "object",
+            "properties": {
+                "cache_creation_tokens": {
+                    "type": "integer"
+                },
+                "cached_tokens": {
+                    "type": "integer"
+                },
+                "channel_id": {
+                    "type": "integer"
+                },
+                "exception_count": {
+                    "type": "integer"
+                },
+                "input_tokens": {
+                    "type": "integer"
+                },
+                "max_rpm": {
+                    "type": "integer"
+                },
+                "max_tpm": {
+                    "type": "integer"
+                },
+                "model": {
+                    "type": "string"
+                },
+                "output_tokens": {
+                    "type": "integer"
+                },
+                "request_count": {
+                    "type": "integer"
+                },
+                "timestamp": {
+                    "type": "integer"
+                },
+                "total_time_milliseconds": {
+                    "type": "integer"
+                },
+                "total_tokens": {
+                    "type": "integer"
+                },
+                "total_ttfb_milliseconds": {
+                    "type": "integer"
+                },
+                "used_amount": {
+                    "type": "number"
+                },
+                "web_search_count": {
+                    "type": "integer"
+                }
+            }
+        },
         "model.TextResponse": {
             "type": "object",
             "properties": {
@@ -10752,13 +10757,13 @@ const docTemplate = `{
                 }
             }
         },
-        "model.TimeModelData": {
+        "model.TimeSummaryDataV2": {
             "type": "object",
             "properties": {
-                "models": {
+                "summary": {
                     "type": "array",
                     "items": {
-                        "$ref": "#/definitions/model.ModelData"
+                        "$ref": "#/definitions/model.SummaryDataV2"
                     }
                 },
                 "timestamp": {

+ 72 - 67
core/docs/swagger.json

@@ -1179,7 +1179,7 @@
                                         "data": {
                                             "type": "array",
                                             "items": {
-                                                "$ref": "#/definitions/model.TimeModelData"
+                                                "$ref": "#/definitions/model.TimeSummaryDataV2"
                                             }
                                         }
                                     }
@@ -1258,7 +1258,7 @@
                                         "data": {
                                             "type": "array",
                                             "items": {
-                                                "$ref": "#/definitions/model.TimeModelData"
+                                                "$ref": "#/definitions/model.TimeSummaryDataV2"
                                             }
                                         }
                                     }
@@ -10148,56 +10148,6 @@
                 }
             }
         },
-        "model.ModelData": {
-            "type": "object",
-            "properties": {
-                "cache_creation_tokens": {
-                    "type": "integer"
-                },
-                "cached_tokens": {
-                    "type": "integer"
-                },
-                "exception_count": {
-                    "type": "integer"
-                },
-                "input_tokens": {
-                    "type": "integer"
-                },
-                "max_rpm": {
-                    "type": "integer"
-                },
-                "max_tpm": {
-                    "type": "integer"
-                },
-                "model": {
-                    "type": "string"
-                },
-                "output_tokens": {
-                    "type": "integer"
-                },
-                "request_count": {
-                    "type": "integer"
-                },
-                "timestamp": {
-                    "type": "integer"
-                },
-                "total_time_milliseconds": {
-                    "type": "integer"
-                },
-                "total_tokens": {
-                    "type": "integer"
-                },
-                "total_ttfb_milliseconds": {
-                    "type": "integer"
-                },
-                "used_amount": {
-                    "type": "number"
-                },
-                "web_search_count": {
-                    "type": "integer"
-                }
-            }
-        },
         "model.ModelOwner": {
             "type": "string",
             "enum": [
@@ -10280,17 +10230,6 @@
                 }
             }
         },
-        "model.ParamType": {
-            "type": "string",
-            "enum": [
-                "header",
-                "query"
-            ],
-            "x-enum-varnames": [
-                "ParamTypeHeader",
-                "ParamTypeQuery"
-            ]
-        },
         "model.ParsePdfResponse": {
             "type": "object",
             "properties": {
@@ -10367,6 +10306,19 @@
                 }
             }
         },
+        "model.ProxyParamType": {
+            "type": "string",
+            "enum": [
+                "url",
+                "header",
+                "query"
+            ],
+            "x-enum-varnames": [
+                "ParamTypeURL",
+                "ParamTypeHeader",
+                "ParamTypeQuery"
+            ]
+        },
         "model.PublicMCP": {
             "type": "object",
             "properties": {
@@ -10471,7 +10423,7 @@
                     "type": "boolean"
                 },
                 "type": {
-                    "$ref": "#/definitions/model.ParamType"
+                    "$ref": "#/definitions/model.ProxyParamType"
                 }
             }
         },
@@ -10675,6 +10627,59 @@
                 }
             }
         },
+        "model.SummaryDataV2": {
+            "type": "object",
+            "properties": {
+                "cache_creation_tokens": {
+                    "type": "integer"
+                },
+                "cached_tokens": {
+                    "type": "integer"
+                },
+                "channel_id": {
+                    "type": "integer"
+                },
+                "exception_count": {
+                    "type": "integer"
+                },
+                "input_tokens": {
+                    "type": "integer"
+                },
+                "max_rpm": {
+                    "type": "integer"
+                },
+                "max_tpm": {
+                    "type": "integer"
+                },
+                "model": {
+                    "type": "string"
+                },
+                "output_tokens": {
+                    "type": "integer"
+                },
+                "request_count": {
+                    "type": "integer"
+                },
+                "timestamp": {
+                    "type": "integer"
+                },
+                "total_time_milliseconds": {
+                    "type": "integer"
+                },
+                "total_tokens": {
+                    "type": "integer"
+                },
+                "total_ttfb_milliseconds": {
+                    "type": "integer"
+                },
+                "used_amount": {
+                    "type": "number"
+                },
+                "web_search_count": {
+                    "type": "integer"
+                }
+            }
+        },
         "model.TextResponse": {
             "type": "object",
             "properties": {
@@ -10743,13 +10748,13 @@
                 }
             }
         },
-        "model.TimeModelData": {
+        "model.TimeSummaryDataV2": {
             "type": "object",
             "properties": {
-                "models": {
+                "summary": {
                     "type": "array",
                     "items": {
-                        "$ref": "#/definitions/model.ModelData"
+                        "$ref": "#/definitions/model.SummaryDataV2"
                     }
                 },
                 "timestamp": {

+ 51 - 47
core/docs/swagger.yaml

@@ -1487,39 +1487,6 @@ definitions:
       updated_at:
         type: string
     type: object
-  model.ModelData:
-    properties:
-      cache_creation_tokens:
-        type: integer
-      cached_tokens:
-        type: integer
-      exception_count:
-        type: integer
-      input_tokens:
-        type: integer
-      max_rpm:
-        type: integer
-      max_tpm:
-        type: integer
-      model:
-        type: string
-      output_tokens:
-        type: integer
-      request_count:
-        type: integer
-      timestamp:
-        type: integer
-      total_time_milliseconds:
-        type: integer
-      total_tokens:
-        type: integer
-      total_ttfb_milliseconds:
-        type: integer
-      used_amount:
-        type: number
-      web_search_count:
-        type: integer
-    type: object
   model.ModelOwner:
     enum:
     - openai
@@ -1595,14 +1562,6 @@ definitions:
       value:
         type: string
     type: object
-  model.ParamType:
-    enum:
-    - header
-    - query
-    type: string
-    x-enum-varnames:
-    - ParamTypeHeader
-    - ParamTypeQuery
   model.ParsePdfResponse:
     properties:
       markdown:
@@ -1655,6 +1614,16 @@ definitions:
       cached_tokens:
         type: integer
     type: object
+  model.ProxyParamType:
+    enum:
+    - url
+    - header
+    - query
+    type: string
+    x-enum-varnames:
+    - ParamTypeURL
+    - ParamTypeHeader
+    - ParamTypeQuery
   model.PublicMCP:
     properties:
       created_at:
@@ -1724,7 +1693,7 @@ definitions:
       required:
         type: boolean
       type:
-        $ref: '#/definitions/model.ParamType'
+        $ref: '#/definitions/model.ProxyParamType'
     type: object
   model.PublicMCPReusingParam:
     properties:
@@ -1859,6 +1828,41 @@ definitions:
       text:
         type: string
     type: object
+  model.SummaryDataV2:
+    properties:
+      cache_creation_tokens:
+        type: integer
+      cached_tokens:
+        type: integer
+      channel_id:
+        type: integer
+      exception_count:
+        type: integer
+      input_tokens:
+        type: integer
+      max_rpm:
+        type: integer
+      max_tpm:
+        type: integer
+      model:
+        type: string
+      output_tokens:
+        type: integer
+      request_count:
+        type: integer
+      timestamp:
+        type: integer
+      total_time_milliseconds:
+        type: integer
+      total_tokens:
+        type: integer
+      total_ttfb_milliseconds:
+        type: integer
+      used_amount:
+        type: number
+      web_search_count:
+        type: integer
+    type: object
   model.TextResponse:
     properties:
       choices:
@@ -1904,11 +1908,11 @@ definitions:
     - model
     - voice
     type: object
-  model.TimeModelData:
+  model.TimeSummaryDataV2:
     properties:
-      models:
+      summary:
         items:
-          $ref: '#/definitions/model.ModelData'
+          $ref: '#/definitions/model.SummaryDataV2'
         type: array
       timestamp:
         type: integer
@@ -2727,7 +2731,7 @@ paths:
             - properties:
                 data:
                   items:
-                    $ref: '#/definitions/model.TimeModelData'
+                    $ref: '#/definitions/model.TimeSummaryDataV2'
                   type: array
               type: object
       security:
@@ -2775,7 +2779,7 @@ paths:
             - properties:
                 data:
                   items:
-                    $ref: '#/definitions/model.TimeModelData'
+                    $ref: '#/definitions/model.TimeSummaryDataV2'
                   type: array
               type: object
       security:

+ 48 - 19
core/model/summary-minute.go

@@ -465,12 +465,39 @@ func GetGroupDashboardDataMinute(
 	}, nil
 }
 
+type SummaryDataV2 struct {
+	Timestamp      int64   `json:"timestamp,omitempty"`
+	ChannelID      int     `json:"channel_id,omitempty"`
+	Model          string  `json:"model"`
+	RequestCount   int64   `json:"request_count"`
+	UsedAmount     float64 `json:"used_amount"`
+	ExceptionCount int64   `json:"exception_count"`
+
+	TotalTimeMilliseconds int64 `json:"total_time_milliseconds,omitempty"`
+	TotalTTFBMilliseconds int64 `json:"total_ttfb_milliseconds,omitempty"`
+
+	InputTokens         int64 `json:"input_tokens,omitempty"`
+	OutputTokens        int64 `json:"output_tokens,omitempty"`
+	CachedTokens        int64 `json:"cached_tokens,omitempty"`
+	CacheCreationTokens int64 `json:"cache_creation_tokens,omitempty"`
+	TotalTokens         int64 `json:"total_tokens,omitempty"`
+	WebSearchCount      int64 `json:"web_search_count,omitempty"`
+
+	MaxRPM int64 `json:"max_rpm,omitempty"`
+	MaxTPM int64 `json:"max_tpm,omitempty"`
+}
+
+type TimeSummaryDataV2 struct {
+	Timestamp int64            `json:"timestamp"`
+	Summary   []*SummaryDataV2 `json:"summary"`
+}
+
 func GetTimeSeriesModelDataMinute(
 	channelID int,
 	start, end time.Time,
 	timeSpan TimeSpanType,
 	timezone *time.Location,
-) ([]*TimeModelData, error) {
+) ([]*TimeSummaryDataV2, error) {
 	if end.IsZero() {
 		end = time.Now()
 	} else if end.Before(start) {
@@ -492,7 +519,7 @@ func GetTimeSeriesModelDataMinute(
 		query = query.Where("minute_timestamp <= ?", end.Unix())
 	}
 
-	selectFields := "minute_timestamp as timestamp, model, " +
+	selectFields := "minute_timestamp as timestamp, channel_id, model, " +
 		"sum(request_count) as request_count, sum(used_amount) as used_amount, " +
 		"sum(exception_count) as exception_count, sum(total_time_milliseconds) as total_time_milliseconds, sum(total_ttfb_milliseconds) as total_ttfb_milliseconds, " +
 		"sum(input_tokens) as input_tokens, " +
@@ -500,10 +527,10 @@ func GetTimeSeriesModelDataMinute(
 		"sum(cache_creation_tokens) as cache_creation_tokens, sum(total_tokens) as total_tokens, " +
 		"sum(web_search_count) as web_search_count, sum(request_count) as max_rpm, sum(total_tokens) as max_tpm"
 
-	var rawData []ModelData
+	var rawData []SummaryDataV2
 	err := query.
 		Select(selectFields).
-		Group("timestamp, model").
+		Group("timestamp, channel_id, model").
 		Order("timestamp ASC").
 		Scan(&rawData).Error
 	if err != nil {
@@ -523,7 +550,7 @@ func GetGroupTimeSeriesModelDataMinute(
 	start, end time.Time,
 	timeSpan TimeSpanType,
 	timezone *time.Location,
-) ([]*TimeModelData, error) {
+) ([]*TimeSummaryDataV2, error) {
 	if end.IsZero() {
 		end = time.Now()
 	} else if end.Before(start) {
@@ -553,7 +580,7 @@ func GetGroupTimeSeriesModelDataMinute(
 		"sum(cache_creation_tokens) as cache_creation_tokens, sum(total_tokens) as total_tokens, " +
 		"sum(web_search_count) as web_search_count, sum(request_count) as max_rpm, sum(total_tokens) as max_tpm"
 
-	var rawData []ModelData
+	var rawData []SummaryDataV2
 	err := query.
 		Select(selectFields).
 		Group("timestamp, model").
@@ -571,10 +598,10 @@ func GetGroupTimeSeriesModelDataMinute(
 }
 
 func aggregatToSpan(
-	minuteData []ModelData,
+	minuteData []SummaryDataV2,
 	timeSpan TimeSpanType,
 	timezone *time.Location,
-) []ModelData {
+) []SummaryDataV2 {
 	if timezone == nil {
 		timezone = time.Local
 	}
@@ -583,7 +610,7 @@ func aggregatToSpan(
 		Timestamp int64
 		Model     string
 	}
-	dataMap := make(map[AggKey]*ModelData)
+	dataMap := make(map[AggKey]*SummaryDataV2)
 
 	for _, data := range minuteData {
 		t := time.Unix(data.Timestamp, 0).In(timezone)
@@ -616,8 +643,9 @@ func aggregatToSpan(
 		}
 
 		if _, exists := dataMap[key]; !exists {
-			dataMap[key] = &ModelData{
+			dataMap[key] = &SummaryDataV2{
 				Timestamp: key.Timestamp,
+				ChannelID: data.ChannelID,
 				Model:     data.Model,
 			}
 		}
@@ -646,7 +674,7 @@ func aggregatToSpan(
 		}
 	}
 
-	result := make([]ModelData, 0, len(dataMap))
+	result := make([]SummaryDataV2, 0, len(dataMap))
 	for _, data := range dataMap {
 		result = append(result, *data)
 	}
@@ -654,11 +682,12 @@ func aggregatToSpan(
 	return result
 }
 
-func convertToTimeModelData(rawData []ModelData) []*TimeModelData {
-	timeMap := make(map[int64][]*ModelData)
+func convertToTimeModelData(rawData []SummaryDataV2) []*TimeSummaryDataV2 {
+	timeMap := make(map[int64][]*SummaryDataV2)
 
 	for _, data := range rawData {
-		modelData := &ModelData{
+		modelData := &SummaryDataV2{
+			ChannelID:             data.ChannelID,
 			Model:                 data.Model,
 			RequestCount:          data.RequestCount,
 			UsedAmount:            data.UsedAmount,
@@ -678,9 +707,9 @@ func convertToTimeModelData(rawData []ModelData) []*TimeModelData {
 		timeMap[data.Timestamp] = append(timeMap[data.Timestamp], modelData)
 	}
 
-	result := make([]*TimeModelData, 0, len(timeMap))
+	result := make([]*TimeSummaryDataV2, 0, len(timeMap))
 	for timestamp, models := range timeMap {
-		slices.SortFunc(models, func(a, b *ModelData) int {
+		slices.SortFunc(models, func(a, b *SummaryDataV2) int {
 			if a.UsedAmount != b.UsedAmount {
 				return cmp.Compare(b.UsedAmount, a.UsedAmount)
 			}
@@ -693,13 +722,13 @@ func convertToTimeModelData(rawData []ModelData) []*TimeModelData {
 			return cmp.Compare(a.Model, b.Model)
 		})
 
-		result = append(result, &TimeModelData{
+		result = append(result, &TimeSummaryDataV2{
 			Timestamp: timestamp,
-			Models:    models,
+			Summary:   models,
 		})
 	}
 
-	slices.SortFunc(result, func(a, b *TimeModelData) int {
+	slices.SortFunc(result, func(a, b *TimeSummaryDataV2) int {
 		return cmp.Compare(a.Timestamp, b.Timestamp)
 	})
 

+ 0 - 27
core/model/summary.go

@@ -703,30 +703,3 @@ func GetGroupDashboardData(
 		TokenNames:        tokenNames,
 	}, nil
 }
-
-//nolint:revive
-type ModelData struct {
-	Timestamp      int64   `json:"timestamp,omitempty"`
-	Model          string  `json:"model"`
-	RequestCount   int64   `json:"request_count"`
-	UsedAmount     float64 `json:"used_amount"`
-	ExceptionCount int64   `json:"exception_count"`
-
-	TotalTimeMilliseconds int64 `json:"total_time_milliseconds,omitempty"`
-	TotalTTFBMilliseconds int64 `json:"total_ttfb_milliseconds,omitempty"`
-
-	InputTokens         int64 `json:"input_tokens,omitempty"`
-	OutputTokens        int64 `json:"output_tokens,omitempty"`
-	CachedTokens        int64 `json:"cached_tokens,omitempty"`
-	CacheCreationTokens int64 `json:"cache_creation_tokens,omitempty"`
-	TotalTokens         int64 `json:"total_tokens,omitempty"`
-	WebSearchCount      int64 `json:"web_search_count,omitempty"`
-
-	MaxRPM int64 `json:"max_rpm,omitempty"`
-	MaxTPM int64 `json:"max_tpm,omitempty"`
-}
-
-type TimeModelData struct {
-	Timestamp int64        `json:"timestamp"`
-	Models    []*ModelData `json:"models"`
-}