feat(auth-files): add oauth excluded provider tag

This commit is contained in:
Supra4E8C
2025-12-28 00:48:36 +08:00
parent 3cffa19319
commit c989dbf1b6
2 changed files with 1131 additions and 991 deletions

View File

@@ -422,6 +422,60 @@
flex-shrink: 0;
}
// OAuth 排除列表表单:提供商快捷标签
.providerField {
display: flex;
flex-direction: column;
gap: $spacing-xs;
:global(.form-group) {
margin-bottom: 0;
}
}
.providerTagList {
display: flex;
flex-wrap: wrap;
gap: $spacing-xs;
}
.providerTag {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 4px 10px;
border-radius: $radius-full;
border: 1px solid var(--border-color);
background-color: var(--bg-secondary);
color: var(--text-secondary);
font-size: 12px;
cursor: pointer;
transition: all $transition-fast;
&:hover {
border-color: var(--primary-color);
color: var(--text-primary);
background-color: var(--bg-hover);
}
&:disabled {
opacity: 0.6;
cursor: not-allowed;
}
}
.providerTagActive {
background-color: var(--primary-color);
border-color: var(--primary-color);
color: #fff;
&:hover {
background-color: var(--primary-color);
border-color: var(--primary-color);
color: #fff;
}
}
// 详情弹窗
.detailContent {
max-height: 400px;

View File

@@ -65,6 +65,20 @@ const TYPE_COLORS: Record<string, TypeColorSet> = {
}
};
const OAUTH_PROVIDER_PRESETS = [
'gemini',
'gemini-cli',
'vertex',
'aistudio',
'antigravity',
'claude',
'codex',
'qwen',
'iflow'
];
const OAUTH_PROVIDER_EXCLUDES = new Set(['all', 'unknown', 'empty']);
interface ExcludedFormState {
provider: string;
modelsText: string;
@@ -265,6 +279,44 @@ export function AuthFilesPage() {
return Array.from(types);
}, [files]);
const excludedProviderLookup = useMemo(() => {
const lookup = new Map<string, string>();
Object.keys(excluded).forEach((provider) => {
const key = provider.trim().toLowerCase();
if (key && !lookup.has(key)) {
lookup.set(key, provider);
}
});
return lookup;
}, [excluded]);
const providerOptions = useMemo(() => {
const extraProviders = new Set<string>();
Object.keys(excluded).forEach((provider) => {
extraProviders.add(provider);
});
files.forEach((file) => {
if (typeof file.type === 'string') {
extraProviders.add(file.type);
}
if (typeof file.provider === 'string') {
extraProviders.add(file.provider);
}
});
const normalizedExtras = Array.from(extraProviders)
.map((value) => value.trim())
.filter((value) => value && !OAUTH_PROVIDER_EXCLUDES.has(value.toLowerCase()));
const baseSet = new Set(OAUTH_PROVIDER_PRESETS.map((value) => value.toLowerCase()));
const extraList = normalizedExtras
.filter((value) => !baseSet.has(value.toLowerCase()))
.sort((a, b) => a.localeCompare(b));
return [...OAUTH_PROVIDER_PRESETS, ...extraList];
}, [excluded, files]);
// 过滤和搜索
const filtered = useMemo(() => {
return files.filter((item) => {
@@ -511,9 +563,14 @@ export function AuthFilesPage() {
// OAuth 排除相关方法
const openExcludedModal = (provider?: string) => {
const models = provider ? excluded[provider] : [];
const normalizedProvider = (provider || '').trim();
const fallbackProvider = normalizedProvider || (filter !== 'all' ? String(filter) : '');
const lookupKey = fallbackProvider
? excludedProviderLookup.get(fallbackProvider.toLowerCase())
: undefined;
const models = lookupKey ? excluded[lookupKey] : [];
setExcludedForm({
provider: provider || '',
provider: lookupKey || fallbackProvider,
modelsText: Array.isArray(models) ? models.join('\n') : ''
});
setExcludedModalOpen(true);
@@ -1011,12 +1068,41 @@ export function AuthFilesPage() {
</>
}
>
<div className={styles.providerField}>
<Input
id="oauth-excluded-provider"
list="oauth-excluded-provider-options"
label={t('oauth_excluded.provider_label')}
hint={t('oauth_excluded.provider_hint')}
placeholder={t('oauth_excluded.provider_placeholder')}
value={excludedForm.provider}
onChange={(e) => setExcludedForm((prev) => ({ ...prev, provider: e.target.value }))}
/>
<datalist id="oauth-excluded-provider-options">
{providerOptions.map((provider) => (
<option key={provider} value={provider} />
))}
</datalist>
{providerOptions.length > 0 && (
<div className={styles.providerTagList}>
{providerOptions.map((provider) => {
const isActive =
excludedForm.provider.trim().toLowerCase() === provider.toLowerCase();
return (
<button
key={provider}
type="button"
className={`${styles.providerTag} ${isActive ? styles.providerTagActive : ''}`}
onClick={() => setExcludedForm((prev) => ({ ...prev, provider }))}
disabled={savingExcluded}
>
{getTypeLabel(provider)}
</button>
);
})}
</div>
)}
</div>
<div className={styles.formGroup}>
<label>{t('oauth_excluded.models_label')}</label>
<textarea