feat: add dark mode support for Grok icon and normalize provider keys across components

This commit is contained in:
LTbinglingfeng
2026-05-18 00:09:22 +08:00
parent 4ef2936f8c
commit a292267fc1
7 changed files with 48 additions and 28 deletions
+1
View File
@@ -0,0 +1 @@
<svg fill="#ffffff" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Grok</title><path d="M9.27 15.29l7.978-5.897c.391-.29.95-.177 1.137.272.98 2.369.542 5.215-1.41 7.169-1.951 1.954-4.667 2.382-7.149 1.406l-2.711 1.257c3.889 2.661 8.611 2.003 11.562-.953 2.341-2.344 3.066-5.539 2.388-8.42l.006.007c-.983-4.232.242-5.924 2.75-9.383.06-.082.12-.164.179-.248l-3.301 3.305v-.01L9.267 15.292M7.623 16.723c-2.792-2.67-2.31-6.801.071-9.184 1.761-1.763 4.647-2.483 7.166-1.425l2.705-1.25a7.808 7.808 0 00-1.829-1A8.975 8.975 0 005.984 5.83c-2.533 2.536-3.33 6.436-1.962 9.764 1.022 2.487-.653 4.246-2.34 6.022-.599.63-1.199 1.259-1.682 1.925l7.62-6.815"></path></svg>

After

Width:  |  Height:  |  Size: 752 B

@@ -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;
+14 -8
View File
@@ -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<string, TypeColorSet> = {
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<string, AuthFileIconAsset> = {
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;
};
@@ -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;
+16 -13
View File
@@ -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<string>(['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<string, number> = { 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() {
<div className={styles.filterRail}>
<div className={styles.filterTags}>
{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 (
+2 -1
View File
@@ -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[] = [
+2 -1
View File
@@ -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<string, string | { light: string; dark: strin
qwen: iconQwen,
kimi: { light: iconKimiLight, dark: iconKimiDark },
glm: iconGlm,
grok: iconGrok,
grok: { light: iconGrok, dark: iconGrokDark },
deepseek: iconDeepseek,
minimax: iconMinimax,
};