重构页面布局,新增标签页导航功能,整合提供商管理、虚拟模型和请求日志页面
This commit is contained in:
@@ -140,6 +140,7 @@ func createRequestLog(apiKeyID uint, virtualModelName string, backendModel *mode
|
||||
APIKeyID: apiKeyID,
|
||||
VirtualModelName: virtualModelName,
|
||||
BackendModelName: backendModel.Name,
|
||||
ProviderName: backendModel.Provider.Name,
|
||||
RequestTimestamp: requestTimestamp,
|
||||
ResponseTimestamp: responseTimestamp,
|
||||
RequestTokens: requestTokenCount,
|
||||
@@ -172,3 +173,155 @@ func copyResponseHeaders(c *gin.Context, resp *http.Response) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetRequestLogsHandler 获取请求日志列表
|
||||
func (h *APIHandler) GetRequestLogsHandler(c *gin.Context) {
|
||||
// 获取分页参数
|
||||
page := 1
|
||||
pageSize := 20
|
||||
if pageParam := c.Query("page"); pageParam != "" {
|
||||
fmt.Sscanf(pageParam, "%d", &page)
|
||||
}
|
||||
if pageSizeParam := c.Query("page_size"); pageSizeParam != "" {
|
||||
fmt.Sscanf(pageSizeParam, "%d", &pageSize)
|
||||
}
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize < 1 || pageSize > 100 {
|
||||
pageSize = 20
|
||||
}
|
||||
|
||||
// 构建查询
|
||||
query := h.DB.Model(&models.RequestLog{})
|
||||
|
||||
// 按时间范围过滤
|
||||
if startTime := c.Query("start_time"); startTime != "" {
|
||||
if t, err := time.Parse(time.RFC3339, startTime); err == nil {
|
||||
query = query.Where("request_timestamp >= ?", t)
|
||||
}
|
||||
}
|
||||
if endTime := c.Query("end_time"); endTime != "" {
|
||||
if t, err := time.Parse(time.RFC3339, endTime); err == nil {
|
||||
query = query.Where("request_timestamp <= ?", t)
|
||||
}
|
||||
}
|
||||
|
||||
// 按虚拟模型名称过滤
|
||||
if virtualModel := c.Query("virtual_model"); virtualModel != "" {
|
||||
query = query.Where("virtual_model_name = ?", virtualModel)
|
||||
}
|
||||
|
||||
// 按后端模型名称过滤
|
||||
if backendModel := c.Query("backend_model"); backendModel != "" {
|
||||
query = query.Where("backend_model_name = ?", backendModel)
|
||||
}
|
||||
|
||||
// 获取总数
|
||||
var total int64
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to count logs"})
|
||||
return
|
||||
}
|
||||
|
||||
// 获取日志列表
|
||||
var logs []models.RequestLog
|
||||
offset := (page - 1) * pageSize
|
||||
if err := query.Order("request_timestamp DESC").Limit(pageSize).Offset(offset).Find(&logs).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch logs"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"logs": logs,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": pageSize,
|
||||
"total_pages": (total + int64(pageSize) - 1) / int64(pageSize),
|
||||
})
|
||||
}
|
||||
|
||||
// GetRequestLogStatsHandler 获取请求日志统计信息
|
||||
func (h *APIHandler) GetRequestLogStatsHandler(c *gin.Context) {
|
||||
// 获取时间范围参数
|
||||
var startTime, endTime time.Time
|
||||
var err error
|
||||
|
||||
if startTimeParam := c.Query("start_time"); startTimeParam != "" {
|
||||
startTime, err = time.Parse(time.RFC3339, startTimeParam)
|
||||
if err != nil {
|
||||
// 默认最近7天
|
||||
startTime = time.Now().AddDate(0, 0, -7)
|
||||
}
|
||||
} else {
|
||||
startTime = time.Now().AddDate(0, 0, -7)
|
||||
}
|
||||
|
||||
if endTimeParam := c.Query("end_time"); endTimeParam != "" {
|
||||
endTime, err = time.Parse(time.RFC3339, endTimeParam)
|
||||
if err != nil {
|
||||
endTime = time.Now()
|
||||
}
|
||||
} else {
|
||||
endTime = time.Now()
|
||||
}
|
||||
|
||||
// 统计总请求数
|
||||
var totalRequests int64
|
||||
h.DB.Model(&models.RequestLog{}).
|
||||
Where("request_timestamp BETWEEN ? AND ?", startTime, endTime).
|
||||
Count(&totalRequests)
|
||||
|
||||
// 统计总成本
|
||||
var totalCost float64
|
||||
h.DB.Model(&models.RequestLog{}).
|
||||
Where("request_timestamp BETWEEN ? AND ?", startTime, endTime).
|
||||
Select("COALESCE(SUM(cost), 0)").
|
||||
Scan(&totalCost)
|
||||
|
||||
// 统计总token数
|
||||
var totalTokens struct {
|
||||
RequestTokens int64
|
||||
ResponseTokens int64
|
||||
}
|
||||
h.DB.Model(&models.RequestLog{}).
|
||||
Where("request_timestamp BETWEEN ? AND ?", startTime, endTime).
|
||||
Select("COALESCE(SUM(request_tokens), 0) as request_tokens, COALESCE(SUM(response_tokens), 0) as response_tokens").
|
||||
Scan(&totalTokens)
|
||||
|
||||
// 按虚拟模型统计
|
||||
type ModelStats struct {
|
||||
ModelName string `json:"model_name"`
|
||||
Count int64 `json:"count"`
|
||||
TotalCost float64 `json:"total_cost"`
|
||||
}
|
||||
var virtualModelStats []ModelStats
|
||||
h.DB.Model(&models.RequestLog{}).
|
||||
Where("request_timestamp BETWEEN ? AND ?", startTime, endTime).
|
||||
Select("virtual_model_name as model_name, COUNT(*) as count, COALESCE(SUM(cost), 0) as total_cost").
|
||||
Group("virtual_model_name").
|
||||
Order("count DESC").
|
||||
Limit(10).
|
||||
Scan(&virtualModelStats)
|
||||
|
||||
// 按后端模型统计
|
||||
var backendModelStats []ModelStats
|
||||
h.DB.Model(&models.RequestLog{}).
|
||||
Where("request_timestamp BETWEEN ? AND ?", startTime, endTime).
|
||||
Select("backend_model_name as model_name, COUNT(*) as count, COALESCE(SUM(cost), 0) as total_cost").
|
||||
Group("backend_model_name").
|
||||
Order("count DESC").
|
||||
Limit(10).
|
||||
Scan(&backendModelStats)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"start_time": startTime,
|
||||
"end_time": endTime,
|
||||
"total_requests": totalRequests,
|
||||
"total_cost": totalCost,
|
||||
"total_request_tokens": totalTokens.RequestTokens,
|
||||
"total_response_tokens": totalTokens.ResponseTokens,
|
||||
"virtual_model_stats": virtualModelStats,
|
||||
"backend_model_stats": backendModelStats,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ type BackendModel struct {
|
||||
type RequestLog struct {
|
||||
gorm.Model
|
||||
APIKeyID uint `gorm:"index"` // API密钥ID
|
||||
ProviderName string `gorm:"index"` // 服务商名称
|
||||
VirtualModelName string `gorm:"index"` // 虚拟模型名称
|
||||
BackendModelName string `gorm:"index"` // 后端模型名称
|
||||
RequestTimestamp time.Time `gorm:"index;not null"` // 请求时间戳
|
||||
|
||||
@@ -72,6 +72,10 @@ func main() {
|
||||
api_.GET("/virtual-models/:id", handler.GetVirtualModelHandler)
|
||||
api_.PUT("/virtual-models/:id", handler.UpdateVirtualModelHandler)
|
||||
api_.DELETE("/virtual-models/:id", handler.DeleteVirtualModelHandler)
|
||||
|
||||
// Request Logs
|
||||
api_.GET("/logs", handler.GetRequestLogsHandler)
|
||||
api_.GET("/logs/stats", handler.GetRequestLogStatsHandler)
|
||||
}
|
||||
|
||||
// 启动HTTP服务器
|
||||
|
||||
@@ -2,17 +2,26 @@ import React from 'react';
|
||||
import PageLayout from './components/layout/PageLayout';
|
||||
import ProvidersPage from './features/providers';
|
||||
import VirtualModelsPage from './features/virtual-models';
|
||||
import LogsPage from './features/logs';
|
||||
import './App.css';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<PageLayout>
|
||||
<div className="space-y-8">
|
||||
<ProvidersPage />
|
||||
<VirtualModelsPage />
|
||||
</div>
|
||||
</PageLayout>
|
||||
);
|
||||
const tabs = [
|
||||
{
|
||||
label: '提供商管理',
|
||||
content: <ProvidersPage />
|
||||
},
|
||||
{
|
||||
label: '虚拟模型',
|
||||
content: <VirtualModelsPage />
|
||||
},
|
||||
{
|
||||
label: '请求日志',
|
||||
content: <LogsPage />
|
||||
}
|
||||
];
|
||||
|
||||
return <PageLayout tabs={tabs} />;
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
||||
@@ -1,12 +1,39 @@
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
const PageLayout = ({ tabs }) => {
|
||||
const [activeTab, setActiveTab] = useState(0);
|
||||
|
||||
const PageLayout = ({ children }) => {
|
||||
return (
|
||||
<div className="container mx-auto p-4">
|
||||
<header className="mb-4">
|
||||
<h1 className="text-2xl font-bold">AI Gateway Config</h1>
|
||||
<header className="mb-6">
|
||||
<h1 className="text-2xl font-bold mb-4">AI Gateway Config</h1>
|
||||
|
||||
{/* 标签页导航 */}
|
||||
<div className="border-b border-gray-200">
|
||||
<nav className="-mb-px flex space-x-8">
|
||||
{tabs.map((tab, index) => (
|
||||
<button
|
||||
key={index}
|
||||
onClick={() => setActiveTab(index)}
|
||||
className={`
|
||||
py-2 px-1 border-b-2 font-medium text-sm transition-colors
|
||||
${activeTab === index
|
||||
? 'border-blue-500 text-blue-600'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
|
||||
}
|
||||
`}
|
||||
>
|
||||
{tab.label}
|
||||
</button>
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
<main>{children}</main>
|
||||
|
||||
{/* 标签页内容 */}
|
||||
<main className="mt-6">
|
||||
{tabs[activeTab]?.content}
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user