From a292267fc1e955b4427e0be02e940fdc73a5ce1a Mon Sep 17 00:00:00 2001 From: LTbinglingfeng Date: Mon, 18 May 2026 00:09:22 +0800 Subject: [PATCH] feat: add dark mode support for Grok icon and normalize provider keys across components --- src/assets/icons/grok-dark.svg | 1 + .../authFiles/components/AuthFileCard.tsx | 10 ++++--- src/features/authFiles/constants.ts | 22 +++++++++----- .../authFiles/hooks/useAuthFilesData.ts | 8 ++++- src/pages/AuthFilesPage.tsx | 29 ++++++++++--------- src/pages/OAuthPage.tsx | 3 +- src/pages/SystemPage.tsx | 3 +- 7 files changed, 48 insertions(+), 28 deletions(-) create mode 100644 src/assets/icons/grok-dark.svg diff --git a/src/assets/icons/grok-dark.svg b/src/assets/icons/grok-dark.svg new file mode 100644 index 00000000..9d4ebdb8 --- /dev/null +++ b/src/assets/icons/grok-dark.svg @@ -0,0 +1 @@ +Grok diff --git a/src/features/authFiles/components/AuthFileCard.tsx b/src/features/authFiles/components/AuthFileCard.tsx index a048f6aa..662b756d 100644 --- a/src/features/authFiles/components/AuthFileCard.tsx +++ b/src/features/authFiles/components/AuthFileCard.tsx @@ -28,6 +28,7 @@ import { getTypeColor, getTypeLabel, isRuntimeOnlyAuthFile, + normalizeProviderKey, parsePriorityValue, type QuotaProviderType, type ResolvedTheme, @@ -88,11 +89,12 @@ export function AuthFileCard(props: AuthFileCardProps) { failure: normalizeUsageTotal(file.failed), }; const isRuntimeOnly = isRuntimeOnlyAuthFile(file); - const isAistudio = (file.type || '').toLowerCase() === 'aistudio'; + const providerKey = normalizeProviderKey(String(file.type ?? file.provider ?? 'unknown')); + const isAistudio = providerKey === 'aistudio'; const showModelsButton = !isRuntimeOnly || isAistudio; - const typeColor = getTypeColor(file.type || 'unknown', resolvedTheme); - const typeLabel = getTypeLabel(t, file.type || 'unknown'); - const providerIcon = getAuthFileIcon(file.type || 'unknown', resolvedTheme); + const typeColor = getTypeColor(providerKey, resolvedTheme); + const typeLabel = getTypeLabel(t, providerKey); + const providerIcon = getAuthFileIcon(providerKey, resolvedTheme); const quotaType = quotaFilterType && resolveQuotaType(file) === quotaFilterType ? quotaFilterType : null; diff --git a/src/features/authFiles/constants.ts b/src/features/authFiles/constants.ts index 3f4f370a..4a3a61d6 100644 --- a/src/features/authFiles/constants.ts +++ b/src/features/authFiles/constants.ts @@ -4,6 +4,7 @@ import iconClaude from '@/assets/icons/claude.svg'; import iconCodex from '@/assets/icons/codex.svg'; import iconGemini from '@/assets/icons/gemini.svg'; import iconGrok from '@/assets/icons/grok.svg'; +import iconGrokDark from '@/assets/icons/grok-dark.svg'; import iconIflow from '@/assets/icons/iflow.svg'; import iconKimiDark from '@/assets/icons/kimi-dark.svg'; import iconKimiLight from '@/assets/icons/kimi-light.svg'; @@ -83,10 +84,10 @@ export const TYPE_COLORS: Record = { light: { bg: '#e0f7fa', text: '#006064' }, dark: { bg: '#004d40', text: '#80deea' }, }, - // xAI / Grok: neutral graphite, kept distinct from the blue and purple providers + // xAI / Grok: graphite brand treatment, distinct from blue and purple providers xai: { - light: { bg: '#eceff3', text: '#15181d' }, - dark: { bg: '#e5e7eb', text: '#111827' }, + light: { bg: '#f3f4f6', text: '#111827', border: '1px solid #d1d5db' }, + dark: { bg: '#111827', text: '#f9fafb', border: '1px solid #374151' }, }, // iFlow logo: 品红紫渐变 #5C5CFF → #AE5CFF,偏品红以区别于 Qwen 的紫罗兰 iflow: { @@ -115,7 +116,7 @@ export const AUTH_FILE_ICONS: Record = { codex: iconCodex, gemini: iconGemini, 'gemini-cli': iconGemini, - xai: iconGrok, + xai: { light: iconGrok, dark: iconGrokDark }, iflow: iconIflow, kimi: { light: iconKimiLight, dark: iconKimiDark }, qwen: iconQwen, @@ -135,7 +136,11 @@ export const resolveQuotaErrorMessage = ( return fallback; }; -export const normalizeProviderKey = (value: string) => value.trim().toLowerCase(); +export const normalizeProviderKey = (value: string) => { + const key = value.trim().toLowerCase().replace(/_/g, '-'); + if (key === 'x-ai' || key === 'grok') return 'xai'; + return key; +}; export const getAuthFileStatusMessage = (file: AuthFileItem): string => { const raw = file['status_message'] ?? file.statusMessage; @@ -148,15 +153,16 @@ export const hasAuthFileStatusMessage = (file: AuthFileItem): boolean => getAuthFileStatusMessage(file).length > 0; export const getTypeLabel = (t: TFunction, type: string): string => { - const key = `auth_files.filter_${type}`; + const providerKey = normalizeProviderKey(type); + const key = `auth_files.filter_${providerKey}`; const translated = t(key); if (translated !== key) return translated; - if (type.toLowerCase() === 'iflow') return 'iFlow'; + if (providerKey === 'iflow') return 'iFlow'; return type.charAt(0).toUpperCase() + type.slice(1); }; export const getTypeColor = (type: string, resolvedTheme: ResolvedTheme): ThemeColors => { - const set = TYPE_COLORS[type] || TYPE_COLORS.unknown; + const set = TYPE_COLORS[normalizeProviderKey(type)] || TYPE_COLORS.unknown; return resolvedTheme === 'dark' && set.dark ? set.dark : set.light; }; diff --git a/src/features/authFiles/hooks/useAuthFilesData.ts b/src/features/authFiles/hooks/useAuthFilesData.ts index 1baca365..f9f97045 100644 --- a/src/features/authFiles/hooks/useAuthFilesData.ts +++ b/src/features/authFiles/hooks/useAuthFilesData.ts @@ -11,6 +11,7 @@ import { getTypeLabel, hasAuthFileStatusMessage, isRuntimeOnlyAuthFile, + normalizeProviderKey, } from '@/features/authFiles/constants'; type DeleteAllOptions = { @@ -310,7 +311,12 @@ export function useAuthFilesData(): UseAuthFilesDataResult { } else { const filesToDelete = files.filter((file) => { if (isRuntimeOnlyAuthFile(file)) return false; - if (isFiltered && file.type !== filter) return false; + if ( + isFiltered && + normalizeProviderKey(String(file.type ?? file.provider ?? '')) !== filter + ) { + return false; + } if (isProblemOnly && !hasAuthFileStatusMessage(file)) return false; if (isDisabledOnly && file.disabled !== true) return false; return true; diff --git a/src/pages/AuthFilesPage.tsx b/src/pages/AuthFilesPage.tsx index 4f428981..0fdf8ffd 100644 --- a/src/pages/AuthFilesPage.tsx +++ b/src/pages/AuthFilesPage.tsx @@ -194,7 +194,7 @@ export function AuthFilesPage() { const persisted = readAuthFilesUiState(); if (persisted) { if (typeof persisted.filter === 'string' && persisted.filter.trim()) { - setFilter(persisted.filter); + setFilter(normalizeProviderKey(persisted.filter)); } if (typeof persisted.problemOnly === 'boolean') { setProblemOnly(persisted.problemOnly); @@ -349,9 +349,8 @@ export function AuthFilesPage() { const existingTypes = useMemo(() => { const types = new Set(['all']); files.forEach((file) => { - if (file.type) { - types.add(file.type); - } + const type = normalizeProviderKey(String(file.type ?? file.provider ?? '')); + if (type) types.add(type); }); return Array.from(types); }, [files]); @@ -378,8 +377,9 @@ export function AuthFilesPage() { const typeCounts = useMemo(() => { const counts: Record = { all: filesMatchingStatusFilters.length }; filesMatchingStatusFilters.forEach((file) => { - if (!file.type) return; - counts[file.type] = (counts[file.type] || 0) + 1; + const type = normalizeProviderKey(String(file.type ?? file.provider ?? '')); + if (!type) return; + counts[type] = (counts[type] || 0) + 1; }); return counts; }, [filesMatchingStatusFilters]); @@ -391,7 +391,8 @@ export function AuthFilesPage() { const normalizedTerm = normalizedSearch.toLowerCase(); return filesMatchingStatusFilters.filter((item) => { - const matchType = filter === 'all' || item.type === filter; + const type = normalizeProviderKey(String(item.type ?? item.provider ?? '')); + const matchType = normalizedFilter === 'all' || type === normalizedFilter; const matchSearch = !normalizedSearch || [item.name, item.type, item.provider].some((value) => { @@ -402,7 +403,7 @@ export function AuthFilesPage() { }); return matchType && matchSearch; }); - }, [filesMatchingStatusFilters, filter, normalizedSearch, wildcardSearch]); + }, [filesMatchingStatusFilters, normalizedFilter, normalizedSearch, wildcardSearch]); const sorted = useMemo(() => { const copy = [...filtered]; @@ -586,7 +587,7 @@ export function AuthFilesPage() {
{existingTypes.map((type) => { - const isActive = filter === type; + const isActive = normalizedFilter === type; const iconSrc = getAuthFileIcon(type, resolvedTheme); const color = type === 'all' @@ -646,13 +647,15 @@ export function AuthFilesPage() { return t('auth_files.delete_filtered_result_button'); } if (problemOnly) { - return filter === 'all' + return normalizedFilter === 'all' ? t('auth_files.delete_problem_button') - : t('auth_files.delete_problem_button_with_type', { type: getTypeLabel(t, filter) }); + : t('auth_files.delete_problem_button_with_type', { + type: getTypeLabel(t, normalizedFilter), + }); } - return filter === 'all' + return normalizedFilter === 'all' ? t('auth_files.delete_all_button') - : `${t('common.delete')} ${getTypeLabel(t, filter)}`; + : `${t('common.delete')} ${getTypeLabel(t, normalizedFilter)}`; })(); return ( diff --git a/src/pages/OAuthPage.tsx b/src/pages/OAuthPage.tsx index 9b014810..c4ea8bc2 100644 --- a/src/pages/OAuthPage.tsx +++ b/src/pages/OAuthPage.tsx @@ -17,6 +17,7 @@ import iconKimiLight from '@/assets/icons/kimi-light.svg'; import iconKimiDark from '@/assets/icons/kimi-dark.svg'; import iconVertex from '@/assets/icons/vertex.svg'; import iconGrok from '@/assets/icons/grok.svg'; +import iconGrokDark from '@/assets/icons/grok-dark.svg'; interface ProviderState { url?: string; @@ -69,7 +70,7 @@ const PROVIDERS: { id: OAuthProvider; titleKey: string; hintKey: string; urlLabe { id: 'antigravity', titleKey: 'auth_login.antigravity_oauth_title', hintKey: 'auth_login.antigravity_oauth_hint', urlLabelKey: 'auth_login.antigravity_oauth_url_label', icon: iconAntigravity }, { id: 'gemini-cli', titleKey: 'auth_login.gemini_cli_oauth_title', hintKey: 'auth_login.gemini_cli_oauth_hint', urlLabelKey: 'auth_login.gemini_cli_oauth_url_label', icon: iconGemini }, { id: 'kimi', titleKey: 'auth_login.kimi_oauth_title', hintKey: 'auth_login.kimi_oauth_hint', urlLabelKey: 'auth_login.kimi_oauth_url_label', icon: { light: iconKimiLight, dark: iconKimiDark } }, - { id: 'xai', titleKey: 'auth_login.xai_oauth_title', hintKey: 'auth_login.xai_oauth_hint', urlLabelKey: 'auth_login.xai_oauth_url_label', icon: iconGrok } + { id: 'xai', titleKey: 'auth_login.xai_oauth_title', hintKey: 'auth_login.xai_oauth_hint', urlLabelKey: 'auth_login.xai_oauth_url_label', icon: { light: iconGrok, dark: iconGrokDark } } ]; const CALLBACK_SUPPORTED: OAuthProvider[] = [ diff --git a/src/pages/SystemPage.tsx b/src/pages/SystemPage.tsx index 19840cc7..3fb75e60 100644 --- a/src/pages/SystemPage.tsx +++ b/src/pages/SystemPage.tsx @@ -26,6 +26,7 @@ import iconKimiLight from '@/assets/icons/kimi-light.svg'; import iconKimiDark from '@/assets/icons/kimi-dark.svg'; import iconGlm from '@/assets/icons/glm.svg'; import iconGrok from '@/assets/icons/grok.svg'; +import iconGrokDark from '@/assets/icons/grok-dark.svg'; import iconDeepseek from '@/assets/icons/deepseek.svg'; import iconMinimax from '@/assets/icons/minimax.svg'; import styles from './SystemPage.module.scss'; @@ -37,7 +38,7 @@ const MODEL_CATEGORY_ICONS: Record