feat(analytics): refactor model color management and fix UI display issues

- Extract getModelColor utility to lib/utils.ts with improved FNV-1a hash algorithm
- Replace hardcoded color palette with vibrant tones palette
- Remove color constants from model-breakdown-chart.tsx
- Fix truncated model names in analytics display
- Update project roadmap with analytics enhancements
This commit is contained in:
kaitranntt
2025-12-09 14:44:56 -05:00
parent 00cf5c20dd
commit f255a20a93
4 changed files with 33 additions and 44 deletions
+2 -1
View File
@@ -589,6 +589,7 @@ src/types/
**Release Date**: 2025-12-08 **Release Date**: 2025-12-08
#### UI Fixes & Improvements #### UI Fixes & Improvements
-**Analytics UI Enhancements**: Standardized colors, fixed truncated model names, and ensured color consistency in the analytics dashboard.
-**Auto-formatting**: 31 UI files auto-formatted for consistent styling. -**Auto-formatting**: 31 UI files auto-formatted for consistent styling.
-**Fast Refresh Exports**: Resolved `react-refresh/only-export-components` by extracting `buttonVariants`, `useSidebar`, and `useWebSocketContext` to separate files. -**Fast Refresh Exports**: Resolved `react-refresh/only-export-components` by extracting `buttonVariants`, `useSidebar`, and `useWebSocketContext` to separate files.
-**React Hooks Issues**: Fixed `react-hooks/purity` (`Math.random()` in `useMemo` for `sidebar.tsx`) and `react-hooks/set-state-in-effect` (`use-theme.ts`, `settings.tsx`). -**React Hooks Issues**: Fixed `react-hooks/purity` (`Math.random()` in `useMemo` for `sidebar.tsx`) and `react-hooks/set-state-in-effect` (`use-theme.ts`, `settings.tsx`).
@@ -821,6 +822,6 @@ src/types/
--- ---
**Document Status**: Living document, updated with each major release **Document Status**: Living document, updated with each major release
**Last Updated**: 2025-12-08 (UI Layout Improvements) **Last Updated**: 2025-12-09 (Analytics UI Enhancements)
**Next Update**: v4.6.0 UI Enhancements Planning **Next Update**: v4.6.0 UI Enhancements Planning
**Maintainer**: CCS Development Team **Maintainer**: CCS Development Team
@@ -9,7 +9,7 @@ import { useMemo } from 'react';
import { PieChart, Pie, Cell, ResponsiveContainer, Tooltip } from 'recharts'; import { PieChart, Pie, Cell, ResponsiveContainer, Tooltip } from 'recharts';
import { Skeleton } from '@/components/ui/skeleton'; import { Skeleton } from '@/components/ui/skeleton';
import type { ModelUsage } from '@/hooks/use-usage'; import type { ModelUsage } from '@/hooks/use-usage';
import { cn } from '@/lib/utils'; import { cn, getModelColor } from '@/lib/utils';
interface ModelBreakdownChartProps { interface ModelBreakdownChartProps {
data: ModelUsage[]; data: ModelUsage[];
@@ -17,30 +17,17 @@ interface ModelBreakdownChartProps {
className?: string; className?: string;
} }
const COLORS = [
'#0080FF',
'#00C49F',
'#FFBB28',
'#FF8042',
'#8884D8',
'#82CA9D',
'#FFC658',
'#8DD1E1',
'#D084D0',
'#87D068',
];
export function ModelBreakdownChart({ data, isLoading, className }: ModelBreakdownChartProps) { export function ModelBreakdownChart({ data, isLoading, className }: ModelBreakdownChartProps) {
const chartData = useMemo(() => { const chartData = useMemo(() => {
if (!data || data.length === 0) return []; if (!data || data.length === 0) return [];
return data.map((item, index) => ({ return data.map((item) => ({
name: item.model, name: item.model,
value: item.tokens, value: item.tokens,
cost: item.cost, cost: item.cost,
requests: item.requests, requests: item.requests,
percentage: item.percentage, percentage: item.percentage,
fill: COLORS[index % COLORS.length], fill: getModelColor(item.model),
})); }));
}, [data]); }, [data]);
+26
View File
@@ -4,3 +4,29 @@ import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) { export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs)); return twMerge(clsx(inputs));
} }
export function getModelColor(model: string): string {
// Vibrant Tones Palette
const colors = [
'#f94144', // Strawberry Red
'#f3722c', // Pumpkin Spice
'#f8961e', // Carrot Orange
'#f9844a', // Atomic Tangerine
'#f9c74f', // Tuscan Sun
'#90be6d', // Willow Green
'#43aa8b', // Seaweed
'#4d908e', // Dark Cyan
'#577590', // Blue Slate
'#277da1', // Cerulean
];
// FNV-1a hash algorithm
let hash = 0x811c9dc5;
for (let i = 0; i < model.length; i++) {
hash ^= model.charCodeAt(i);
hash = Math.imul(hash, 0x01000193);
}
// Ensure positive index
return colors[(hash >>> 0) % colors.length];
}
+2 -27
View File
@@ -26,6 +26,7 @@ import {
useRefreshUsage, useRefreshUsage,
useUsageStatus, useUsageStatus,
} from '@/hooks/use-usage'; } from '@/hooks/use-usage';
import { getModelColor } from '@/lib/utils';
type ViewMode = 'daily' | 'monthly' | 'sessions'; type ViewMode = 'daily' | 'monthly' | 'sessions';
@@ -214,10 +215,7 @@ export function AnalyticsPage() {
className="w-2.5 h-2.5 rounded-full shrink-0" className="w-2.5 h-2.5 rounded-full shrink-0"
style={{ backgroundColor: getModelColor(model.model) }} style={{ backgroundColor: getModelColor(model.model) }}
/> />
<span <span className="font-medium" title={model.model}>
className="font-medium truncate max-w-[150px]"
title={model.model}
>
{model.model} {model.model}
</span> </span>
</div> </div>
@@ -276,29 +274,6 @@ export function AnalyticsPage() {
); );
} }
// Helper function to generate consistent colors for models
function getModelColor(model: string): string {
const colors = [
'#0080FF',
'#00C49F',
'#FFBB28',
'#FF8042',
'#8884D8',
'#82CA9D',
'#FFC658',
'#8DD1E1',
'#D084D0',
'#87D068',
];
let hash = 0;
for (let i = 0; i < model.length; i++) {
hash = model.charCodeAt(i) + ((hash << 5) - hash);
}
return colors[Math.abs(hash) % colors.length];
}
export function AnalyticsSkeleton() { export function AnalyticsSkeleton() {
return ( return (
<div className="space-y-4 h-full overflow-hidden"> <div className="space-y-4 h-full overflow-hidden">