mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-05-06 09:11:38 +00:00
📊 feat: add comprehensive model monitoring dashboard
This commit introduces a complete model monitoring system that provides real-time insights into model performance and usage statistics. ## ✨ Features Added ### Core Components - **ModelMonitoringStats**: Four key metric cards displaying total models, active models, total requests, and average success rate - **ModelMonitoringTable**: Interactive table with search, filtering, and pagination for detailed model data - **useModelMonitoring**: Custom hook for data fetching and state management ### Dashboard Integration - Added new "模型观测" (Model Monitoring) tab to main dashboard - Integrated monitoring components with existing dashboard layout - Maintained consistent UI/UX with shadcn/ui design system ### Data Processing - Smart data aggregation from existing `/api/data/` endpoints - Automatic calculation of success rates and average metrics - Support for both admin and user-specific data views ### Interactive Features - Real-time search by model name - Business group filtering - Pagination with 10 items per page - Color-coded success rate indicators (green >95%, yellow 90-95%, red <90%) - Refresh capability for up-to-date data ## 🔧 Technical Implementation ### Type Safety - Added comprehensive TypeScript interfaces in `@/types/api.ts` - Defined `ModelInfo`, `ModelMonitoringStats`, and related types ### Utility Functions - Enhanced color utilities with `modelToColor()` for consistent model identification - Improved formatters for quota, tokens, and percentage display - Maintained existing utility function architecture ### Architecture - Follows established patterns from dashboard components - Reuses existing HTTP client and authentication utilities - Consistent error handling and loading states ### Code Quality - All components pass linter checks - Proper import organization and formatting - Responsive design for mobile compatibility ## 🎯 User Experience ### Visual Design - Color-coded model indicators for quick identification - Success rate visualization with icons and colors - Clean table layout with proper spacing and typography - Skeleton loading states for smooth UX ### Functionality - Search models by name with instant filtering - Filter by business groups for organized viewing - Navigate through paginated results efficiently - Refresh data manually when needed ## 📋 Files Modified - `web/src/types/api.ts`: Added model monitoring type definitions - `web/src/features/dashboard/hooks/use-model-monitoring.ts`: Core data hook - `web/src/features/dashboard/components/model-monitoring-stats.tsx`: Stats cards - `web/src/features/dashboard/components/model-monitoring-table.tsx`: Data table - `web/src/features/dashboard/index.tsx`: Dashboard integration - Various formatting and import organization improvements This implementation provides a comprehensive solution for model monitoring that aligns with the existing codebase architecture while delivering powerful insights into model performance and usage patterns.
This commit is contained in:
270
web/src/lib/comparisons.ts
Normal file
270
web/src/lib/comparisons.ts
Normal file
@@ -0,0 +1,270 @@
|
||||
/**
|
||||
* 对象比较和差异检测工具函数
|
||||
*/
|
||||
|
||||
export interface PropertyChange {
|
||||
key: string
|
||||
oldValue: any
|
||||
newValue: any
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较两个对象的属性,找出有变化的属性
|
||||
* @param oldObject 旧对象
|
||||
* @param newObject 新对象
|
||||
* @returns 包含变化属性信息的数组
|
||||
*/
|
||||
export function compareObjects(
|
||||
oldObject: Record<string, any>,
|
||||
newObject: Record<string, any>
|
||||
): PropertyChange[] {
|
||||
const changedProperties: PropertyChange[] = []
|
||||
|
||||
// 比较两个对象的属性
|
||||
for (const key in oldObject) {
|
||||
if (oldObject.hasOwnProperty(key) && newObject.hasOwnProperty(key)) {
|
||||
if (oldObject[key] !== newObject[key]) {
|
||||
changedProperties.push({
|
||||
key: key,
|
||||
oldValue: oldObject[key],
|
||||
newValue: newObject[key],
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查新对象中新增的属性
|
||||
for (const key in newObject) {
|
||||
if (newObject.hasOwnProperty(key) && !oldObject.hasOwnProperty(key)) {
|
||||
changedProperties.push({
|
||||
key: key,
|
||||
oldValue: undefined,
|
||||
newValue: newObject[key],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return changedProperties
|
||||
}
|
||||
|
||||
/**
|
||||
* 深度比较两个对象是否相等
|
||||
* @param obj1 对象1
|
||||
* @param obj2 对象2
|
||||
* @returns 是否相等
|
||||
*/
|
||||
export function deepEqual(obj1: any, obj2: any): boolean {
|
||||
if (obj1 === obj2) return true
|
||||
|
||||
if (obj1 == null || obj2 == null) return false
|
||||
|
||||
if (typeof obj1 !== typeof obj2) return false
|
||||
|
||||
if (typeof obj1 !== 'object') return obj1 === obj2
|
||||
|
||||
if (Array.isArray(obj1) !== Array.isArray(obj2)) return false
|
||||
|
||||
const keys1 = Object.keys(obj1)
|
||||
const keys2 = Object.keys(obj2)
|
||||
|
||||
if (keys1.length !== keys2.length) return false
|
||||
|
||||
for (const key of keys1) {
|
||||
if (!keys2.includes(key)) return false
|
||||
if (!deepEqual(obj1[key], obj2[key])) return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取对象的差异
|
||||
* @param source 源对象
|
||||
* @param target 目标对象
|
||||
* @returns 差异对象
|
||||
*/
|
||||
export function getDifference(
|
||||
source: Record<string, any>,
|
||||
target: Record<string, any>
|
||||
): Record<string, any> {
|
||||
const diff: Record<string, any> = {}
|
||||
|
||||
// 检查修改和新增的属性
|
||||
for (const key in target) {
|
||||
if (!deepEqual(source[key], target[key])) {
|
||||
diff[key] = target[key]
|
||||
}
|
||||
}
|
||||
|
||||
// 检查删除的属性(设为undefined)
|
||||
for (const key in source) {
|
||||
if (!(key in target)) {
|
||||
diff[key] = undefined
|
||||
}
|
||||
}
|
||||
|
||||
return diff
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并对象(深度合并)
|
||||
* @param target 目标对象
|
||||
* @param sources 源对象数组
|
||||
* @returns 合并后的对象
|
||||
*/
|
||||
export function deepMerge(target: any, ...sources: any[]): any {
|
||||
if (!sources.length) return target
|
||||
const source = sources.shift()
|
||||
|
||||
if (isObject(target) && isObject(source)) {
|
||||
for (const key in source) {
|
||||
if (isObject(source[key])) {
|
||||
if (!target[key]) Object.assign(target, { [key]: {} })
|
||||
deepMerge(target[key], source[key])
|
||||
} else {
|
||||
Object.assign(target, { [key]: source[key] })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return deepMerge(target, ...sources)
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为对象
|
||||
* @param item 检查项
|
||||
* @returns 是否为对象
|
||||
*/
|
||||
function isObject(item: any): boolean {
|
||||
return item && typeof item === 'object' && !Array.isArray(item)
|
||||
}
|
||||
|
||||
/**
|
||||
* 克隆对象(深度克隆)
|
||||
* @param obj 要克隆的对象
|
||||
* @returns 克隆后的对象
|
||||
*/
|
||||
export function deepClone<T>(obj: T): T {
|
||||
if (obj === null || typeof obj !== 'object') return obj
|
||||
|
||||
if (obj instanceof Date) return new Date(obj.getTime()) as T
|
||||
|
||||
if (obj instanceof Array) {
|
||||
return obj.map((item) => deepClone(item)) as T
|
||||
}
|
||||
|
||||
if (typeof obj === 'object') {
|
||||
const cloned = {} as T
|
||||
Object.keys(obj).forEach((key) => {
|
||||
;(cloned as any)[key] = deepClone((obj as any)[key])
|
||||
})
|
||||
return cloned
|
||||
}
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查对象是否为空
|
||||
* @param obj 对象
|
||||
* @returns 是否为空
|
||||
*/
|
||||
export function isEmpty(obj: any): boolean {
|
||||
if (obj == null) return true
|
||||
if (Array.isArray(obj)) return obj.length === 0
|
||||
if (typeof obj === 'object') return Object.keys(obj).length === 0
|
||||
if (typeof obj === 'string') return obj.trim().length === 0
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择对象的特定属性
|
||||
* @param obj 源对象
|
||||
* @param keys 要选择的属性键
|
||||
* @returns 包含选定属性的新对象
|
||||
*/
|
||||
export function pick<T extends Record<string, any>, K extends keyof T>(
|
||||
obj: T,
|
||||
keys: K[]
|
||||
): Pick<T, K> {
|
||||
const result = {} as Pick<T, K>
|
||||
keys.forEach((key) => {
|
||||
if (key in obj) {
|
||||
result[key] = obj[key]
|
||||
}
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 省略对象的特定属性
|
||||
* @param obj 源对象
|
||||
* @param keys 要省略的属性键
|
||||
* @returns 省略指定属性后的新对象
|
||||
*/
|
||||
export function omit<T, K extends keyof T>(obj: T, keys: K[]): Omit<T, K> {
|
||||
const result = { ...obj } as any
|
||||
keys.forEach((key) => {
|
||||
delete result[key]
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 扁平化嵌套对象
|
||||
* @param obj 嵌套对象
|
||||
* @param prefix 键前缀
|
||||
* @returns 扁平化后的对象
|
||||
*/
|
||||
export function flatten(
|
||||
obj: Record<string, any>,
|
||||
prefix: string = ''
|
||||
): Record<string, any> {
|
||||
let flattened: Record<string, any> = {}
|
||||
|
||||
for (const key in obj) {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
const newKey = prefix ? `${prefix}.${key}` : key
|
||||
|
||||
if (
|
||||
typeof obj[key] === 'object' &&
|
||||
obj[key] !== null &&
|
||||
!Array.isArray(obj[key])
|
||||
) {
|
||||
Object.assign(flattened, flatten(obj[key], newKey))
|
||||
} else {
|
||||
flattened[newKey] = obj[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return flattened
|
||||
}
|
||||
|
||||
/**
|
||||
* 反扁平化对象
|
||||
* @param obj 扁平化的对象
|
||||
* @returns 嵌套对象
|
||||
*/
|
||||
export function unflatten(obj: Record<string, any>): Record<string, any> {
|
||||
const result: Record<string, any> = {}
|
||||
|
||||
for (const key in obj) {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
const keys = key.split('.')
|
||||
let current = result
|
||||
|
||||
for (let i = 0; i < keys.length - 1; i++) {
|
||||
const k = keys[i]
|
||||
if (!(k in current)) {
|
||||
current[k] = {}
|
||||
}
|
||||
current = current[k]
|
||||
}
|
||||
|
||||
current[keys[keys.length - 1]] = obj[key]
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
Reference in New Issue
Block a user