diff --git a/web/src/components/ui/Select.tsx b/web/src/components/ui/Select.tsx index 1df569f9..dfb599a7 100644 --- a/web/src/components/ui/Select.tsx +++ b/web/src/components/ui/Select.tsx @@ -16,6 +16,7 @@ import styles from './Select.module.scss'; export interface SelectOption { value: string; label: string; + triggerLabel?: string; suffix?: ReactNode; suffixAriaLabel?: string; disabled?: boolean; @@ -198,7 +199,7 @@ export function Select({ ? selectedIndex : firstEnabledIndex; const selected = selectedIndex >= 0 ? options[selectedIndex] : undefined; - const displayText = selected?.label ?? placeholder ?? ''; + const displayText = selected?.triggerLabel ?? selected?.label ?? placeholder ?? ''; const isPlaceholder = !selected && placeholder; const commitSelection = useCallback( diff --git a/web/src/features/ranking/RankingPage.module.scss b/web/src/features/ranking/RankingPage.module.scss index d5a6611a..12040b1b 100644 --- a/web/src/features/ranking/RankingPage.module.scss +++ b/web/src/features/ranking/RankingPage.module.scss @@ -892,13 +892,12 @@ @mixin ranking-header-stacked { .leaderboardHeader { - grid-template-columns: minmax(0, 1fr); + grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: - 'title' - 'toolbar' - 'profile'; + 'title title' + 'toolbar profile'; align-items: stretch; - column-gap: 0; + column-gap: 12px; row-gap: 14px; padding: 0; } @@ -921,36 +920,43 @@ } .leaderboardHeaderActions { - justify-content: center; - justify-self: center; + align-self: center; + justify-content: flex-end; + justify-self: end; max-width: 100%; width: auto; margin-right: 0; } - .profileActionShell, - .profileActionShellActive { - max-width: 100%; - } - .toolbar { - display: grid; - grid-template-columns: minmax(0, 1fr); + display: flex; + justify-content: flex-start; width: 100%; + max-width: 100%; + padding: 4px; + overflow-x: auto; + overflow-y: hidden; + overscroll-behavior-inline: contain; + scrollbar-width: none; + -webkit-overflow-scrolling: touch; + + &::-webkit-scrollbar { + display: none; + } } .periods { display: grid; - grid-template-columns: repeat(4, minmax(0, 1fr)); - width: 100%; - } - - .periodButton { - padding-inline: 6px; + grid-template-columns: repeat(4, max-content); + flex: 0 0 auto; + width: max-content; + margin-left: auto; } .metricControl { - width: 100%; + flex: 0 0 168px; + width: 168px; + margin-right: auto; } } @@ -961,6 +967,24 @@ @include mobile { @include ranking-header-stacked; + .profileActionShell { + max-width: 160px; + } + + .profileActionShellActive { + flex: 0 0 42px; + width: 42px; + } + + .profileActionShellActive :global(.main-action-button) { + width: 32px; + padding-inline: 5px; + } + + .profileActionName { + display: none; + } + .participantColumn, .participantCell { min-width: 0; diff --git a/web/src/features/ranking/RankingPage.tsx b/web/src/features/ranking/RankingPage.tsx index c6f70488..0185277b 100644 --- a/web/src/features/ranking/RankingPage.tsx +++ b/web/src/features/ranking/RankingPage.tsx @@ -594,6 +594,12 @@ function LeaderboardCard({ const podium = rows.slice(0, 3); const tableRows = rows; const scoreExplanation = resolveScoreExplanation(board, metric, language); + const hasRankingProfile = status?.status === 'active' || status?.status === 'paused'; + const profileActionAriaLabel = hasRankingProfile && status.display_name + ? `${status.display_name} · ${t('ranking.profile_action')}` + : hasRankingProfile + ? t('ranking.profile_action') + : undefined; return (
@@ -644,12 +650,13 @@ function LeaderboardCard({ data-ranking-profile-action-shell > - {status?.status === 'active' || status?.status === 'paused' ? ( + {hasRankingProfile ? ( <> {status.display_name || t('ranking.profile_action')} diff --git a/web/src/features/ranking/components/RankingToolbar.tsx b/web/src/features/ranking/components/RankingToolbar.tsx index f142a76f..eb10175e 100644 --- a/web/src/features/ranking/components/RankingToolbar.tsx +++ b/web/src/features/ranking/components/RankingToolbar.tsx @@ -11,15 +11,16 @@ const PERIODS: ReadonlyArray<{ value: RankingPeriod; labelKey: string }> = [ { value: 'previous_month', labelKey: 'ranking.period_previous_month' }, ]; -const METRICS: ReadonlyArray<{ value: RankingMetric; labelKey: string }> = [ - { value: 'overall', labelKey: 'ranking.metric_overall' }, - { value: 'total_tokens', labelKey: 'ranking.metric_total_tokens' }, - { value: 'request_count', labelKey: 'ranking.metric_request_count' }, - { value: 'cache_read_rate', labelKey: 'ranking.metric_cache_read_rate' }, - { value: 'ttft_average', labelKey: 'ranking.metric_ttft_average' }, - { value: 'latency_average', labelKey: 'ranking.metric_latency_average' }, - { value: 'peak_tpm', labelKey: 'ranking.metric_peak_tpm' }, - { value: 'peak_rpm', labelKey: 'ranking.metric_peak_rpm' }, +// 触发器保持短名称,展开菜单保留完整指标语义。 +const METRICS: ReadonlyArray<{ value: RankingMetric; labelKey: string; triggerLabelKey: string }> = [ + { value: 'overall', labelKey: 'ranking.metric_overall', triggerLabelKey: 'ranking.metric_overall' }, + { value: 'total_tokens', labelKey: 'ranking.metric_total_tokens', triggerLabelKey: 'ranking.metric_short_total_tokens' }, + { value: 'request_count', labelKey: 'ranking.metric_request_count', triggerLabelKey: 'ranking.metric_short_request_count' }, + { value: 'cache_read_rate', labelKey: 'ranking.metric_cache_read_rate', triggerLabelKey: 'ranking.metric_short_cache_read_rate' }, + { value: 'ttft_average', labelKey: 'ranking.metric_ttft_average', triggerLabelKey: 'ranking.metric_short_ttft_average' }, + { value: 'latency_average', labelKey: 'ranking.metric_latency_average', triggerLabelKey: 'ranking.metric_short_latency_average' }, + { value: 'peak_tpm', labelKey: 'ranking.metric_peak_tpm', triggerLabelKey: 'ranking.metric_short_peak_tpm' }, + { value: 'peak_rpm', labelKey: 'ranking.metric_peak_rpm', triggerLabelKey: 'ranking.metric_short_peak_rpm' }, ]; export interface RankingToolbarProps { @@ -37,7 +38,11 @@ export function RankingToolbar({ }: RankingToolbarProps) { const { t } = useTranslation(); const metricOptions = useMemo( - () => METRICS.map((option) => ({ value: option.value, label: t(option.labelKey) })), + () => METRICS.map((option) => ({ + value: option.value, + label: t(option.labelKey), + triggerLabel: t(option.triggerLabelKey), + })), [t], ); @@ -64,7 +69,7 @@ export function RankingToolbar({