fix: dashboard colors, users rank

This commit is contained in:
jialin
2026-05-12 19:01:11 +08:00
committed by jialin
parent 2527555945
commit 4e49c399d0
9 changed files with 82 additions and 88 deletions
+56
View File
@@ -0,0 +1,56 @@
import useMemoizedFn from 'ahooks/lib/useMemoizedFn';
import { converter, modeHsl, modeRgb, useMode } from 'culori/fn';
const toHsl = converter('hsl');
const DEFAULT_BRAND_HUE = 211; // brand color
const COOL_HUE_START = 180;
const COOL_HUE_END = 280;
const COOL_HUE_RANGE = COOL_HUE_END - COOL_HUE_START;
const GOLDEN_RATIO_CONJUGATE = 0.618033988749895;
function colorStringToHue(input?: string): number | undefined {
if (!input) return undefined;
const color = toHsl(input);
if (!color || typeof color.h !== 'number') return undefined;
return color.h;
}
export default function useCoolColors() {
// const brandHue = useMemo(
// () => colorStringToHue(userSettings.colorPrimary) ?? DEFAULT_BRAND_HUE,
// [userSettings.colorPrimary]
// );
useMode(modeRgb);
useMode(modeHsl);
const brandHue = DEFAULT_BRAND_HUE;
return useMemoizedFn((count: number): string[] => {
if (count <= 0) return [];
const colors: string[] = [];
for (let i = 0; i < count; i++) {
let hue: number;
if (i === 0) {
hue = brandHue - 5;
} else {
const offset = (i * GOLDEN_RATIO_CONJUGATE) % 1;
hue = COOL_HUE_START + offset * COOL_HUE_RANGE;
if (Math.abs(hue - brandHue) < 5) {
hue = (hue + 10) % COOL_HUE_END;
}
}
const s = i === 0 ? 100 : 75 + (i % 3) * 5;
const l = i === 0 ? 50 : 55 + (i % 2) * 5;
colors.push(`hsl(${Math.round(hue)}, ${s}%, ${l}%)`);
}
return colors;
});
}
+3 -19
View File
@@ -1,27 +1,10 @@
import useCoolColors from '@/hooks/use-cool-colors';
import { Chart } from '@gpustack/core-ui';
import { formatLargeNumber } from '@gpustack/core-ui/utils';
import { theme } from 'antd';
import _ from 'lodash';
import React, { useEffect, useMemo, useRef } from 'react';
const COOL_HUE_START = 180;
const COOL_HUE_END = 280;
const COOL_HUE_RANGE = COOL_HUE_END - COOL_HUE_START;
// golden angle (137.508) mod COOL_HUE_RANGE — coprime offset for even spread
const HUE_STEP = 37.508;
export function generateCoolColors(count: number): string[] {
if (count <= 0) return [];
const colors: string[] = [];
for (let i = 0; i < count; i++) {
const hue = COOL_HUE_START + ((i * HUE_STEP) % COOL_HUE_RANGE);
const saturation = 55 + (i % 3) * 5;
const lightness = 60 + (i % 2) * 6;
colors.push(`hsl(${Math.round(hue)}, ${saturation}%, ${lightness}%)`);
}
return colors;
}
export interface BarSeriesItem {
name: string;
data: any[];
@@ -55,10 +38,11 @@ const BarChart: React.FC<BarChartProps> = (props) => {
} = props;
const { token } = theme.useToken();
const chartRef = useRef<{ chart: any } | null>(null);
const generateCoolColors = useCoolColors();
const dynamicColors = useMemo(
() => generateCoolColors(seriesData.length),
[seriesData.length]
[seriesData.length, generateCoolColors]
);
const options = useMemo(() => {
+3 -2
View File
@@ -1,8 +1,8 @@
import useCoolColors from '@/hooks/use-cool-colors';
import { Chart } from '@gpustack/core-ui';
import { formatLargeNumber } from '@gpustack/core-ui/utils';
import { Empty, Spin, theme } from 'antd';
import React, { useMemo, useRef } from 'react';
import { generateCoolColors } from '../bar-chart';
export interface PieChartItem {
name: string;
@@ -30,11 +30,12 @@ const PieChart: React.FC<PieChartProps> = ({
}) => {
const { token } = theme.useToken();
const chartRef = useRef<{ chart: any } | null>(null);
const generateCoolColors = useCoolColors();
const colors = useMemo(() => {
const generatedColors = generateCoolColors(data.length + colorOffset);
return generatedColors.slice(colorOffset);
}, [colorOffset, data.length]);
}, [colorOffset, data.length, generateCoolColors]);
const options = useMemo(
() => ({
+4 -63
View File
@@ -1,20 +1,12 @@
import { activeModelsAtom } from '@/atoms/dashboard';
import useQueryBreakdownList from '@/pages/usage/services/use-query-breakdown-list';
import useQueryTimeSeriesData from '@/pages/usage/services/use-query-timeseries-data';
import { formatLargeNumber } from '@/utils';
import { PageTools } from '@gpustack/core-ui';
import { useIntl } from '@umijs/max';
import { Col, Row } from 'antd';
import dayjs from 'dayjs';
import { useAtomValue } from 'jotai';
import { useContext, useEffect, useMemo } from 'react';
import { useEffect, useMemo } from 'react';
import styled from 'styled-components';
import {
baseColorMap,
DashboardUsageCommonParams,
getUsageSummary
} from '../config';
import { DashboardContext } from '../config/dashboard-context';
import { DashboardUsageCommonParams } from '../config';
import ApiRequestsByModel from './usage-charts/api-requests-by-model';
import TokenUsageByModel from './usage-charts/token-usage-by-model';
import TopTokenUsageByApiKey from './usage-charts/top-token-usage-by-api-key';
@@ -26,9 +18,6 @@ const Section = styled.div`
const NewUsage = () => {
const intl = useIntl();
const activeModelsFromAtom = useAtomValue(activeModelsAtom);
const { active_models: activeModelsFromContext = [] } =
useContext(DashboardContext);
const tokenByModelQuery = useQueryTimeSeriesData({
key: 'tokenUsageByModelData'
});
@@ -54,10 +43,6 @@ const NewUsage = () => {
[dateRange]
);
const activeModels = activeModelsFromAtom.length
? activeModelsFromAtom
: activeModelsFromContext;
useEffect(() => {
const fetchData = async () => {
await Promise.all([
@@ -66,15 +51,6 @@ const NewUsage = () => {
metric: 'total_tokens',
group_by: ['date', 'model'],
sort_by: '-total_tokens'
}),
activeUsersQuery.fetchData({
...dateRange,
scope: 'all',
group_by: ['user'],
page: 1,
perPage: 10,
sort_by: '-total_tokens',
filters: {}
})
]);
};
@@ -82,41 +58,6 @@ const NewUsage = () => {
fetchData().catch(() => {});
}, [commonParams, dateRange]);
const summaryCards = useMemo(() => {
const summary = getUsageSummary(tokenByModelQuery.detailData) || {
total_tokens: 0,
api_requests: 0
};
return [
{
label: formatLargeNumber(summary.total_tokens) as string,
value: intl.formatMessage({ id: 'dashboard.tokens' }),
color: baseColorMap.base
},
{
label: formatLargeNumber(summary.api_requests) as string,
value: intl.formatMessage({ id: 'dashboard.apirequest' }),
color: baseColorMap.baseR1
},
{
label: formatLargeNumber(activeModels.length) as string,
value: intl.formatMessage({ id: 'dashboard.activeModels' }),
color: baseColorMap.baseR2
},
{
label: formatLargeNumber(activeUsersQuery.dataSource.total) as string,
value: intl.formatMessage({ id: 'dashboard.activeUsers' }),
color: baseColorMap.baseR3
}
];
}, [
activeModels.length,
activeUsersQuery.dataSource.total,
intl,
tokenByModelQuery.detailData
]);
return (
<>
<PageTools
@@ -143,10 +84,10 @@ const NewUsage = () => {
<Section>
<Row gutter={[20, 20]}>
<Col xs={24} sm={24} md={24} lg={12}>
<TopTokenUsageByUser commonParams={commonParams} height={450} />
<TopTokenUsageByUser commonParams={commonParams} height={460} />
</Col>
<Col xs={24} sm={24} md={24} lg={12}>
<TopTokenUsageByApiKey commonParams={commonParams} height={450} />
<TopTokenUsageByApiKey commonParams={commonParams} height={460} />
</Col>
</Row>
</Section>
@@ -53,7 +53,7 @@ const ApiRequestsByModel: React.FC<ApiRequestsByModelProps> = ({
data={chartData}
height={usageChartHeight}
loading={query.loading}
colorOffset={1}
colorOffset={5}
total={total}
/>
</UsageChartCard>
@@ -38,7 +38,7 @@ const TokenUsageByModel: React.FC<TokenUsageByModelProps> = ({
data={chartData}
height={usageChartHeight}
loading={loading}
colorOffset={3}
colorOffset={2}
total={total}
/>
</UsageChartCard>
+4 -2
View File
@@ -1,4 +1,5 @@
import BarChart, { generateCoolColors } from '@/pages/_components/bar-chart';
import useCoolColors from '@/hooks/use-cool-colors';
import BarChart from '@/pages/_components/bar-chart';
import { BaseSelect, CardWrapper } from '@gpustack/core-ui';
import { useIntl } from '@umijs/max';
import { Segmented } from 'antd';
@@ -78,6 +79,7 @@ const DailyUsage: React.FC<DailyUsageProps> = (props) => {
onGroupByChange,
onGranularityChange
} = props;
const generateCoolColors = useCoolColors();
const labelFormatter = (v: any) => {
if (granularity === 'month') {
@@ -237,7 +239,7 @@ const DailyUsage: React.FC<DailyUsageProps> = (props) => {
xAxisData: xAxis,
legendData: dedupedLegend
};
}, [timeSeriesData, intl]);
}, [timeSeriesData, intl, generateCoolColors]);
const handleOnGroupByChange = (value: string) => {
onGroupByChange(value || null);