feat: add excluded models support for Codex/Claude providers and fix header alignment

- Add excludedModels field to ProviderKeyConfig type for Codex and Claude providers
  - Add excluded models textarea input in Codex/Claude edit modal
  - Display excluded models badges in Codex and Claude provider cards
  - Fix header connection status badge vertical alignment with IP address
  - Update dark theme to use pure black color scheme
This commit is contained in:
Supra4E8C
2025-12-13 02:20:08 +08:00
parent da9469c5aa
commit 62486534e4
5 changed files with 69 additions and 14 deletions

View File

@@ -132,13 +132,15 @@ export function AiProvidersPage() {
excludedModels: [],
excludedText: ''
});
const [providerForm, setProviderForm] = useState<ProviderKeyConfig & { modelEntries: ModelEntry[] }>({
const [providerForm, setProviderForm] = useState<ProviderKeyConfig & { modelEntries: ModelEntry[]; excludedText: string }>({
apiKey: '',
baseUrl: '',
proxyUrl: '',
headers: {},
models: [],
modelEntries: [{ name: '', alias: '' }]
excludedModels: [],
modelEntries: [{ name: '', alias: '' }],
excludedText: ''
});
const [openaiForm, setOpenaiForm] = useState<OpenAIFormState>({
name: '',
@@ -231,7 +233,9 @@ export function AiProvidersPage() {
proxyUrl: '',
headers: {},
models: [],
modelEntries: [{ name: '', alias: '' }]
excludedModels: [],
modelEntries: [{ name: '', alias: '' }],
excludedText: ''
});
setOpenaiForm({
name: '',
@@ -269,7 +273,8 @@ export function AiProvidersPage() {
const entry = source[index];
setProviderForm({
...entry,
modelEntries: modelsToEntries(entry?.models)
modelEntries: modelsToEntries(entry?.models),
excludedText: excludedModelsToText(entry?.excludedModels)
});
}
setModal({ type, index });
@@ -558,7 +563,8 @@ export function AiProvidersPage() {
baseUrl,
proxyUrl: providerForm.proxyUrl?.trim() || undefined,
headers: buildHeaderObject(headersToEntries(providerForm.headers as any)),
models: entriesToModels(providerForm.modelEntries)
models: entriesToModels(providerForm.modelEntries),
excludedModels: parseExcludedModels(providerForm.excludedText)
};
const source = type === 'codex' ? codexConfigs : claudeConfigs;
@@ -888,6 +894,21 @@ export function AiProvidersPage() {
))}
</div>
)}
{/* 排除模型徽章 */}
{item.excludedModels?.length ? (
<div className={styles.excludedModelsSection}>
<div className={styles.excludedModelsLabel}>
{t('ai_providers.excluded_models_count', { count: item.excludedModels.length })}
</div>
<div className={styles.modelTagList}>
{item.excludedModels.map((model) => (
<span key={model} className={`${styles.modelTag} ${styles.excludedModelTag}`}>
<span className={styles.modelName}>{model}</span>
</span>
))}
</div>
</div>
) : null}
{/* 成功/失败统计 */}
<div className={styles.cardStats}>
<span className={`${styles.statPill} ${styles.statSuccess}`}>
@@ -970,6 +991,21 @@ export function AiProvidersPage() {
))}
</div>
) : null}
{/* 排除模型徽章 */}
{item.excludedModels?.length ? (
<div className={styles.excludedModelsSection}>
<div className={styles.excludedModelsLabel}>
{t('ai_providers.excluded_models_count', { count: item.excludedModels.length })}
</div>
<div className={styles.modelTagList}>
{item.excludedModels.map((model) => (
<span key={model} className={`${styles.modelTag} ${styles.excludedModelTag}`}>
<span className={styles.modelName}>{model}</span>
</span>
))}
</div>
</div>
) : null}
{/* 成功/失败统计 */}
<div className={styles.cardStats}>
<span className={`${styles.statPill} ${styles.statSuccess}`}>
@@ -1213,6 +1249,17 @@ export function AiProvidersPage() {
disabled={saving}
/>
</div>
<div className="form-group">
<label>{t('ai_providers.excluded_models_label')}</label>
<textarea
className="input"
placeholder={t('ai_providers.excluded_models_placeholder')}
value={providerForm.excludedText}
onChange={(e) => setProviderForm((prev) => ({ ...prev, excludedText: e.target.value }))}
rows={4}
/>
<div className="hint">{t('ai_providers.excluded_models_hint')}</div>
</div>
</Modal>
{/* OpenAI Modal */}

View File

@@ -135,6 +135,12 @@ textarea {
border-radius: $radius-full;
font-size: 13px;
border: 1px solid var(--border-color);
margin-bottom: $spacing-md;
// 确保后续内容换行显示
+ * {
display: block;
}
&.success {
color: $success-color;

View File

@@ -195,6 +195,7 @@
.status-badge {
flex-shrink: 0;
white-space: nowrap;
margin-bottom: 0;
}
.base {

View File

@@ -36,18 +36,18 @@
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1);
}
// 深色主题
// 深色主题(纯黑)
[data-theme='dark'] {
--bg-primary: #1f2937;
--bg-secondary: #111827;
--bg-tertiary: #374151;
--bg-primary: #0a0a0a;
--bg-secondary: #000000;
--bg-tertiary: #171717;
--text-primary: #f9fafb;
--text-secondary: #d1d5db;
--text-tertiary: #9ca3af;
--text-primary: #fafafa;
--text-secondary: #a3a3a3;
--text-tertiary: #737373;
--border-color: #374151;
--border-hover: #4b5563;
--border-color: #262626;
--border-hover: #404040;
--primary-color: #3b82f6;
--primary-hover: #60a5fa;

View File

@@ -29,6 +29,7 @@ export interface ProviderKeyConfig {
proxyUrl?: string;
headers?: Record<string, string>;
models?: ModelAlias[];
excludedModels?: string[];
}
export interface OpenAIProviderConfig {