mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-03 04:17:54 +00:00
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:
@@ -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]);
|
||||||
|
|
||||||
|
|||||||
@@ -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];
|
||||||
|
}
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
Reference in New Issue
Block a user