diff --git a/.github/pr-assets/1390/after-disclaimer-as-tooltip.png b/.github/pr-assets/1390/after-disclaimer-as-tooltip.png new file mode 100644 index 00000000..2e5c8ce4 Binary files /dev/null and b/.github/pr-assets/1390/after-disclaimer-as-tooltip.png differ diff --git a/.github/pr-assets/1390/before-disclaimer-takes-space.png b/.github/pr-assets/1390/before-disclaimer-takes-space.png new file mode 100644 index 00000000..0c64f529 Binary files /dev/null and b/.github/pr-assets/1390/before-disclaimer-takes-space.png differ diff --git a/ui/src/pages/analytics/components/analytics-header.tsx b/ui/src/pages/analytics/components/analytics-header.tsx index 51ea43dd..620fe771 100644 --- a/ui/src/pages/analytics/components/analytics-header.tsx +++ b/ui/src/pages/analytics/components/analytics-header.tsx @@ -15,8 +15,9 @@ import { SelectTrigger, SelectValue, } from '@/components/ui/select'; -import { RefreshCw } from 'lucide-react'; +import { Info, RefreshCw } from 'lucide-react'; import { useTranslation } from 'react-i18next'; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; import type { AnalyticsProfileOption } from '../hooks'; interface AnalyticsHeaderProps { @@ -53,21 +54,44 @@ export function AnalyticsHeader({

{t('analytics.subtitle')}

- +
+ + {selectedProfile !== 'all' && ( + + + + + + + Selected-profile analytics include only default/account data with stable profile + attribution. CLIProxy and native runtime snapshots remain in All profiles. + + + + )} +
- {selectedProfile !== 'all' && ( -

- Selected-profile analytics include only default/account data with stable profile - attribution. CLIProxy and native runtime snapshots remain in All profiles. -

- )} ); } diff --git a/ui/src/pages/analytics/hooks.ts b/ui/src/pages/analytics/hooks.ts index 80e16838..d08c2537 100644 --- a/ui/src/pages/analytics/hooks.ts +++ b/ui/src/pages/analytics/hooks.ts @@ -6,7 +6,18 @@ import { useState, useMemo, useCallback } from 'react'; import type { DateRange } from 'react-day-picker'; -import { subDays, formatDistanceToNow } from 'date-fns'; +import { subDays } from 'date-fns'; + +function formatCompactRelativeTime(date: Date): string { + const seconds = Math.max(1, Math.floor((Date.now() - date.getTime()) / 1000)); + if (seconds < 60) return `${seconds}s ago`; + const minutes = Math.floor(seconds / 60); + if (minutes < 60) return `${minutes}m ago`; + const hours = Math.floor(minutes / 60); + if (hours < 24) return `${hours}h ago`; + const days = Math.floor(hours / 24); + return `${days}d ago`; +} import { useUsageSummary, useUsageTrends, @@ -151,10 +162,10 @@ export function useAnalyticsPage() { persistSelectedProfile(profile); }, []); - // Format "Last updated" text + // Compact "Last updated" text (e.g. "1m ago", "2h ago") const lastUpdatedText = useMemo(() => { if (!status?.lastFetch) return null; - return formatDistanceToNow(new Date(status.lastFetch), { addSuffix: true }); + return formatCompactRelativeTime(new Date(status.lastFetch)); }, [status?.lastFetch]); // Handle model click for popover