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: [], excludedModels: [],
excludedText: '' excludedText: ''
}); });
const [providerForm, setProviderForm] = useState<ProviderKeyConfig & { modelEntries: ModelEntry[] }>({ const [providerForm, setProviderForm] = useState<ProviderKeyConfig & { modelEntries: ModelEntry[]; excludedText: string }>({
apiKey: '', apiKey: '',
baseUrl: '', baseUrl: '',
proxyUrl: '', proxyUrl: '',
headers: {}, headers: {},
models: [], models: [],
modelEntries: [{ name: '', alias: '' }] excludedModels: [],
modelEntries: [{ name: '', alias: '' }],
excludedText: ''
}); });
const [openaiForm, setOpenaiForm] = useState<OpenAIFormState>({ const [openaiForm, setOpenaiForm] = useState<OpenAIFormState>({
name: '', name: '',
@@ -231,7 +233,9 @@ export function AiProvidersPage() {
proxyUrl: '', proxyUrl: '',
headers: {}, headers: {},
models: [], models: [],
modelEntries: [{ name: '', alias: '' }] excludedModels: [],
modelEntries: [{ name: '', alias: '' }],
excludedText: ''
}); });
setOpenaiForm({ setOpenaiForm({
name: '', name: '',
@@ -269,7 +273,8 @@ export function AiProvidersPage() {
const entry = source[index]; const entry = source[index];
setProviderForm({ setProviderForm({
...entry, ...entry,
modelEntries: modelsToEntries(entry?.models) modelEntries: modelsToEntries(entry?.models),
excludedText: excludedModelsToText(entry?.excludedModels)
}); });
} }
setModal({ type, index }); setModal({ type, index });
@@ -558,7 +563,8 @@ export function AiProvidersPage() {
baseUrl, baseUrl,
proxyUrl: providerForm.proxyUrl?.trim() || undefined, proxyUrl: providerForm.proxyUrl?.trim() || undefined,
headers: buildHeaderObject(headersToEntries(providerForm.headers as any)), 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; const source = type === 'codex' ? codexConfigs : claudeConfigs;
@@ -888,6 +894,21 @@ export function AiProvidersPage() {
))} ))}
</div> </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}> <div className={styles.cardStats}>
<span className={`${styles.statPill} ${styles.statSuccess}`}> <span className={`${styles.statPill} ${styles.statSuccess}`}>
@@ -970,6 +991,21 @@ export function AiProvidersPage() {
))} ))}
</div> </div>
) : null} ) : 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}> <div className={styles.cardStats}>
<span className={`${styles.statPill} ${styles.statSuccess}`}> <span className={`${styles.statPill} ${styles.statSuccess}`}>
@@ -1213,6 +1249,17 @@ export function AiProvidersPage() {
disabled={saving} disabled={saving}
/> />
</div> </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> </Modal>
{/* OpenAI Modal */} {/* OpenAI Modal */}

View File

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

View File

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

View File

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

View File

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