Skip to content

Commit a292267

Browse files
feat: add dark mode support for Grok icon and normalize provider keys across components
1 parent 4ef2936 commit a292267

7 files changed

Lines changed: 48 additions & 28 deletions

File tree

src/assets/icons/grok-dark.svg

Lines changed: 1 addition & 0 deletions
Loading

src/features/authFiles/components/AuthFileCard.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
getTypeColor,
2929
getTypeLabel,
3030
isRuntimeOnlyAuthFile,
31+
normalizeProviderKey,
3132
parsePriorityValue,
3233
type QuotaProviderType,
3334
type ResolvedTheme,
@@ -88,11 +89,12 @@ export function AuthFileCard(props: AuthFileCardProps) {
8889
failure: normalizeUsageTotal(file.failed),
8990
};
9091
const isRuntimeOnly = isRuntimeOnlyAuthFile(file);
91-
const isAistudio = (file.type || '').toLowerCase() === 'aistudio';
92+
const providerKey = normalizeProviderKey(String(file.type ?? file.provider ?? 'unknown'));
93+
const isAistudio = providerKey === 'aistudio';
9294
const showModelsButton = !isRuntimeOnly || isAistudio;
93-
const typeColor = getTypeColor(file.type || 'unknown', resolvedTheme);
94-
const typeLabel = getTypeLabel(t, file.type || 'unknown');
95-
const providerIcon = getAuthFileIcon(file.type || 'unknown', resolvedTheme);
95+
const typeColor = getTypeColor(providerKey, resolvedTheme);
96+
const typeLabel = getTypeLabel(t, providerKey);
97+
const providerIcon = getAuthFileIcon(providerKey, resolvedTheme);
9698

9799
const quotaType =
98100
quotaFilterType && resolveQuotaType(file) === quotaFilterType ? quotaFilterType : null;

src/features/authFiles/constants.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import iconClaude from '@/assets/icons/claude.svg';
44
import iconCodex from '@/assets/icons/codex.svg';
55
import iconGemini from '@/assets/icons/gemini.svg';
66
import iconGrok from '@/assets/icons/grok.svg';
7+
import iconGrokDark from '@/assets/icons/grok-dark.svg';
78
import iconIflow from '@/assets/icons/iflow.svg';
89
import iconKimiDark from '@/assets/icons/kimi-dark.svg';
910
import iconKimiLight from '@/assets/icons/kimi-light.svg';
@@ -83,10 +84,10 @@ export const TYPE_COLORS: Record<string, TypeColorSet> = {
8384
light: { bg: '#e0f7fa', text: '#006064' },
8485
dark: { bg: '#004d40', text: '#80deea' },
8586
},
86-
// xAI / Grok: neutral graphite, kept distinct from the blue and purple providers
87+
// xAI / Grok: graphite brand treatment, distinct from blue and purple providers
8788
xai: {
88-
light: { bg: '#eceff3', text: '#15181d' },
89-
dark: { bg: '#e5e7eb', text: '#111827' },
89+
light: { bg: '#f3f4f6', text: '#111827', border: '1px solid #d1d5db' },
90+
dark: { bg: '#111827', text: '#f9fafb', border: '1px solid #374151' },
9091
},
9192
// iFlow logo: 品红紫渐变 #5C5CFF → #AE5CFF,偏品红以区别于 Qwen 的紫罗兰
9293
iflow: {
@@ -115,7 +116,7 @@ export const AUTH_FILE_ICONS: Record<string, AuthFileIconAsset> = {
115116
codex: iconCodex,
116117
gemini: iconGemini,
117118
'gemini-cli': iconGemini,
118-
xai: iconGrok,
119+
xai: { light: iconGrok, dark: iconGrokDark },
119120
iflow: iconIflow,
120121
kimi: { light: iconKimiLight, dark: iconKimiDark },
121122
qwen: iconQwen,
@@ -135,7 +136,11 @@ export const resolveQuotaErrorMessage = (
135136
return fallback;
136137
};
137138

138-
export const normalizeProviderKey = (value: string) => value.trim().toLowerCase();
139+
export const normalizeProviderKey = (value: string) => {
140+
const key = value.trim().toLowerCase().replace(/_/g, '-');
141+
if (key === 'x-ai' || key === 'grok') return 'xai';
142+
return key;
143+
};
139144

140145
export const getAuthFileStatusMessage = (file: AuthFileItem): string => {
141146
const raw = file['status_message'] ?? file.statusMessage;
@@ -148,15 +153,16 @@ export const hasAuthFileStatusMessage = (file: AuthFileItem): boolean =>
148153
getAuthFileStatusMessage(file).length > 0;
149154

150155
export const getTypeLabel = (t: TFunction, type: string): string => {
151-
const key = `auth_files.filter_${type}`;
156+
const providerKey = normalizeProviderKey(type);
157+
const key = `auth_files.filter_${providerKey}`;
152158
const translated = t(key);
153159
if (translated !== key) return translated;
154-
if (type.toLowerCase() === 'iflow') return 'iFlow';
160+
if (providerKey === 'iflow') return 'iFlow';
155161
return type.charAt(0).toUpperCase() + type.slice(1);
156162
};
157163

158164
export const getTypeColor = (type: string, resolvedTheme: ResolvedTheme): ThemeColors => {
159-
const set = TYPE_COLORS[type] || TYPE_COLORS.unknown;
165+
const set = TYPE_COLORS[normalizeProviderKey(type)] || TYPE_COLORS.unknown;
160166
return resolvedTheme === 'dark' && set.dark ? set.dark : set.light;
161167
};
162168

src/features/authFiles/hooks/useAuthFilesData.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
getTypeLabel,
1212
hasAuthFileStatusMessage,
1313
isRuntimeOnlyAuthFile,
14+
normalizeProviderKey,
1415
} from '@/features/authFiles/constants';
1516

1617
type DeleteAllOptions = {
@@ -310,7 +311,12 @@ export function useAuthFilesData(): UseAuthFilesDataResult {
310311
} else {
311312
const filesToDelete = files.filter((file) => {
312313
if (isRuntimeOnlyAuthFile(file)) return false;
313-
if (isFiltered && file.type !== filter) return false;
314+
if (
315+
isFiltered &&
316+
normalizeProviderKey(String(file.type ?? file.provider ?? '')) !== filter
317+
) {
318+
return false;
319+
}
314320
if (isProblemOnly && !hasAuthFileStatusMessage(file)) return false;
315321
if (isDisabledOnly && file.disabled !== true) return false;
316322
return true;

src/pages/AuthFilesPage.tsx

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ export function AuthFilesPage() {
194194
const persisted = readAuthFilesUiState();
195195
if (persisted) {
196196
if (typeof persisted.filter === 'string' && persisted.filter.trim()) {
197-
setFilter(persisted.filter);
197+
setFilter(normalizeProviderKey(persisted.filter));
198198
}
199199
if (typeof persisted.problemOnly === 'boolean') {
200200
setProblemOnly(persisted.problemOnly);
@@ -349,9 +349,8 @@ export function AuthFilesPage() {
349349
const existingTypes = useMemo(() => {
350350
const types = new Set<string>(['all']);
351351
files.forEach((file) => {
352-
if (file.type) {
353-
types.add(file.type);
354-
}
352+
const type = normalizeProviderKey(String(file.type ?? file.provider ?? ''));
353+
if (type) types.add(type);
355354
});
356355
return Array.from(types);
357356
}, [files]);
@@ -378,8 +377,9 @@ export function AuthFilesPage() {
378377
const typeCounts = useMemo(() => {
379378
const counts: Record<string, number> = { all: filesMatchingStatusFilters.length };
380379
filesMatchingStatusFilters.forEach((file) => {
381-
if (!file.type) return;
382-
counts[file.type] = (counts[file.type] || 0) + 1;
380+
const type = normalizeProviderKey(String(file.type ?? file.provider ?? ''));
381+
if (!type) return;
382+
counts[type] = (counts[type] || 0) + 1;
383383
});
384384
return counts;
385385
}, [filesMatchingStatusFilters]);
@@ -391,7 +391,8 @@ export function AuthFilesPage() {
391391
const normalizedTerm = normalizedSearch.toLowerCase();
392392

393393
return filesMatchingStatusFilters.filter((item) => {
394-
const matchType = filter === 'all' || item.type === filter;
394+
const type = normalizeProviderKey(String(item.type ?? item.provider ?? ''));
395+
const matchType = normalizedFilter === 'all' || type === normalizedFilter;
395396
const matchSearch =
396397
!normalizedSearch ||
397398
[item.name, item.type, item.provider].some((value) => {
@@ -402,7 +403,7 @@ export function AuthFilesPage() {
402403
});
403404
return matchType && matchSearch;
404405
});
405-
}, [filesMatchingStatusFilters, filter, normalizedSearch, wildcardSearch]);
406+
}, [filesMatchingStatusFilters, normalizedFilter, normalizedSearch, wildcardSearch]);
406407

407408
const sorted = useMemo(() => {
408409
const copy = [...filtered];
@@ -586,7 +587,7 @@ export function AuthFilesPage() {
586587
<div className={styles.filterRail}>
587588
<div className={styles.filterTags}>
588589
{existingTypes.map((type) => {
589-
const isActive = filter === type;
590+
const isActive = normalizedFilter === type;
590591
const iconSrc = getAuthFileIcon(type, resolvedTheme);
591592
const color =
592593
type === 'all'
@@ -646,13 +647,15 @@ export function AuthFilesPage() {
646647
return t('auth_files.delete_filtered_result_button');
647648
}
648649
if (problemOnly) {
649-
return filter === 'all'
650+
return normalizedFilter === 'all'
650651
? t('auth_files.delete_problem_button')
651-
: t('auth_files.delete_problem_button_with_type', { type: getTypeLabel(t, filter) });
652+
: t('auth_files.delete_problem_button_with_type', {
653+
type: getTypeLabel(t, normalizedFilter),
654+
});
652655
}
653-
return filter === 'all'
656+
return normalizedFilter === 'all'
654657
? t('auth_files.delete_all_button')
655-
: `${t('common.delete')} ${getTypeLabel(t, filter)}`;
658+
: `${t('common.delete')} ${getTypeLabel(t, normalizedFilter)}`;
656659
})();
657660

658661
return (

src/pages/OAuthPage.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import iconKimiLight from '@/assets/icons/kimi-light.svg';
1717
import iconKimiDark from '@/assets/icons/kimi-dark.svg';
1818
import iconVertex from '@/assets/icons/vertex.svg';
1919
import iconGrok from '@/assets/icons/grok.svg';
20+
import iconGrokDark from '@/assets/icons/grok-dark.svg';
2021

2122
interface ProviderState {
2223
url?: string;
@@ -69,7 +70,7 @@ const PROVIDERS: { id: OAuthProvider; titleKey: string; hintKey: string; urlLabe
6970
{ id: 'antigravity', titleKey: 'auth_login.antigravity_oauth_title', hintKey: 'auth_login.antigravity_oauth_hint', urlLabelKey: 'auth_login.antigravity_oauth_url_label', icon: iconAntigravity },
7071
{ 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 },
7172
{ 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 } },
72-
{ id: 'xai', titleKey: 'auth_login.xai_oauth_title', hintKey: 'auth_login.xai_oauth_hint', urlLabelKey: 'auth_login.xai_oauth_url_label', icon: iconGrok }
73+
{ 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 } }
7374
];
7475

7576
const CALLBACK_SUPPORTED: OAuthProvider[] = [

src/pages/SystemPage.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import iconKimiLight from '@/assets/icons/kimi-light.svg';
2626
import iconKimiDark from '@/assets/icons/kimi-dark.svg';
2727
import iconGlm from '@/assets/icons/glm.svg';
2828
import iconGrok from '@/assets/icons/grok.svg';
29+
import iconGrokDark from '@/assets/icons/grok-dark.svg';
2930
import iconDeepseek from '@/assets/icons/deepseek.svg';
3031
import iconMinimax from '@/assets/icons/minimax.svg';
3132
import styles from './SystemPage.module.scss';
@@ -37,7 +38,7 @@ const MODEL_CATEGORY_ICONS: Record<string, string | { light: string; dark: strin
3738
qwen: iconQwen,
3839
kimi: { light: iconKimiLight, dark: iconKimiDark },
3940
glm: iconGlm,
40-
grok: iconGrok,
41+
grok: { light: iconGrok, dark: iconGrokDark },
4142
deepseek: iconDeepseek,
4243
minimax: iconMinimax,
4344
};

0 commit comments

Comments
 (0)