Skip to content

Commit 2e07a92

Browse files
committed
refactor(codex): simplify Codex to single-key mode with DisableCooling
- Remove apiKeyEntries and name from ProviderKeyConfig, add disableCooling - Simplify CodexSection to single key display with disableCooling badge - Rewrite CodexEditPage from multi-key table to simple key+proxy input - Update transformers/serializers for single-key format - Add i18n translations for disable-cooling (en/zh-CN/ru) - Fix downstream references in CredentialStatsCard, sourceResolver, utils
1 parent 17a3883 commit 2e07a92

12 files changed

Lines changed: 138 additions & 352 deletions

File tree

src/components/providers/CodexSection/CodexSection.tsx

Lines changed: 22 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { Fragment, useMemo } from 'react';
22
import { useTranslation } from 'react-i18next';
33
import { Button } from '@/components/ui/Button';
44
import { Card } from '@/components/ui/Card';
5-
import { IconCheck, IconX } from '@/components/ui/icons';
65
import { ToggleSwitch } from '@/components/ui/ToggleSwitch';
76
import iconCodex from '@/assets/icons/codex.svg';
87
import type { ProviderKeyConfig } from '@/types';
@@ -20,9 +19,6 @@ import styles from '@/pages/AiProvidersPage.module.scss';
2019
import { ProviderList } from '../ProviderList';
2120
import { ProviderStatusBar } from '../ProviderStatusBar';
2221
import {
23-
getApiKeyEntriesStats,
24-
getProviderApiKeyEntries,
25-
getProviderPrimaryApiKey,
2622
getStatsBySource,
2723
hasDisableAllModelsRule,
2824
} from '../utils';
@@ -62,12 +58,10 @@ export function CodexSection({
6258
configs.forEach((config, index) => {
6359
const candidates = new Set<string>();
6460
buildCandidateUsageSourceIds({ prefix: config.prefix }).forEach((id) => candidates.add(id));
65-
getProviderApiKeyEntries(config).forEach((entry) => {
66-
buildCandidateUsageSourceIds({ apiKey: entry.apiKey }).forEach((id) => candidates.add(id));
67-
});
61+
buildCandidateUsageSourceIds({ apiKey: config.apiKey }).forEach((id) => candidates.add(id));
6862
if (!candidates.size) return;
6963
cache.set(
70-
`${getProviderPrimaryApiKey(config)}:${index}`,
64+
`${config.apiKey}:${index}`,
7165
calculateStatusBarData(collectUsageDetailsForCandidates(usageDetailsBySource, candidates))
7266
);
7367
});
@@ -93,7 +87,7 @@ export function CodexSection({
9387
<ProviderList<ProviderKeyConfig>
9488
items={configs}
9589
loading={loading}
96-
keyField={(item, index) => `${getProviderPrimaryApiKey(item) || 'codex'}:${index}`}
90+
keyField={(item, index) => `${item.apiKey || 'codex'}:${index}`}
9791
emptyTitle={t('ai_providers.codex_empty_title')}
9892
emptyDescription={t('ai_providers.codex_empty_desc')}
9993
onEdit={onEdit}
@@ -109,19 +103,18 @@ export function CodexSection({
109103
/>
110104
)}
111105
renderContent={(item, index) => {
112-
const apiKeyEntries = getProviderApiKeyEntries(item);
113-
const stats = getApiKeyEntriesStats(apiKeyEntries, keyStats, item.prefix);
106+
const stats = getStatsBySource(item.apiKey, keyStats, item.prefix);
114107
const headerEntries = Object.entries(item.headers || {});
115108
const configDisabled = hasDisableAllModelsRule(item.excludedModels);
116109
const excludedModels = item.excludedModels ?? [];
117110
const statusData =
118-
statusBarCache.get(`${getProviderPrimaryApiKey(item) || 'codex'}:${index}`) ||
111+
statusBarCache.get(`${item.apiKey || 'codex'}:${index}`) ||
119112
calculateStatusBarData([]);
120113

121114
return (
122115
<Fragment>
123116
<div className="item-title">
124-
{item.name?.trim() || t('ai_providers.codex_item_title')}
117+
{t('ai_providers.codex_item_title')}
125118
{configDisabled && (
126119
<span className="status-badge warning">
127120
{t('ai_providers.config_disabled_badge')}
@@ -152,37 +145,22 @@ export function CodexSection({
152145
<span className={styles.fieldValue}>{item.websockets ? t('common.yes') : t('common.no')}</span>
153146
</div>
154147
)}
155-
{apiKeyEntries.length > 0 && (
156-
<div className={styles.apiKeyEntriesSection}>
157-
<div className={styles.apiKeyEntriesLabel}>
158-
{t('ai_providers.codex_keys_count')}: {apiKeyEntries.length}
159-
</div>
160-
<div className={styles.apiKeyEntryList}>
161-
{apiKeyEntries.map((entry, entryIndex) => {
162-
const entryStats = getStatsBySource(entry.apiKey, keyStats);
163-
return (
164-
<div key={entryIndex} className={styles.apiKeyEntryCard}>
165-
<span className={styles.apiKeyEntryIndex}>{entryIndex + 1}</span>
166-
<span className={styles.apiKeyEntryKey}>{maskApiKey(entry.apiKey)}</span>
167-
{entry.proxyUrl && (
168-
<span className={styles.apiKeyEntryProxy}>{entry.proxyUrl}</span>
169-
)}
170-
<div className={styles.apiKeyEntryStats}>
171-
<span
172-
className={`${styles.apiKeyEntryStat} ${styles.apiKeyEntryStatSuccess}`}
173-
>
174-
<IconCheck size={12} /> {entryStats.success}
175-
</span>
176-
<span
177-
className={`${styles.apiKeyEntryStat} ${styles.apiKeyEntryStatFailure}`}
178-
>
179-
<IconX size={12} /> {entryStats.failure}
180-
</span>
181-
</div>
182-
</div>
183-
);
184-
})}
185-
</div>
148+
{item.apiKey && (
149+
<div className={styles.fieldRow}>
150+
<span className={styles.fieldLabel}>{t('ai_providers.codex_api_key_label')}:</span>
151+
<span className={styles.fieldValue}>{maskApiKey(item.apiKey)}</span>
152+
</div>
153+
)}
154+
{item.proxyUrl && (
155+
<div className={styles.fieldRow}>
156+
<span className={styles.fieldLabel}>{t('common.proxy_url')}:</span>
157+
<span className={styles.fieldValue}>{item.proxyUrl}</span>
158+
</div>
159+
)}
160+
{item.disableCooling !== undefined && (
161+
<div className={styles.fieldRow}>
162+
<span className={styles.fieldLabel}>{t('ai_providers.codex_disable_cooling_label')}:</span>
163+
<span className={styles.fieldValue}>{item.disableCooling ? t('common.yes') : t('common.no')}</span>
186164
</div>
187165
)}
188166
{headerEntries.length > 0 && (

src/components/providers/utils.ts

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -144,15 +144,8 @@ export const getApiKeyEntriesStats = (
144144
export const getOpenAIProviderStats = getApiKeyEntriesStats;
145145

146146
export const getProviderApiKeyEntries = (
147-
config?: Pick<ProviderKeyConfig, 'apiKey' | 'apiKeyEntries' | 'proxyUrl'>
147+
config?: Pick<ProviderKeyConfig, 'apiKey' | 'proxyUrl'>
148148
): ApiKeyEntry[] => {
149-
const sharedProxyUrl = String(config?.proxyUrl ?? '').trim();
150-
if (config?.apiKeyEntries?.length) {
151-
return config.apiKeyEntries.map((entry) => ({
152-
...entry,
153-
proxyUrl: String(entry?.proxyUrl ?? '').trim() || sharedProxyUrl,
154-
}));
155-
}
156149
const apiKey = String(config?.apiKey ?? '').trim();
157150
if (!apiKey) {
158151
return [];
@@ -161,10 +154,9 @@ export const getProviderApiKeyEntries = (
161154
};
162155

163156
export const getProviderPrimaryApiKey = (
164-
config?: Pick<ProviderKeyConfig, 'apiKey' | 'apiKeyEntries' | 'proxyUrl'>
157+
config?: Pick<ProviderKeyConfig, 'apiKey' | 'proxyUrl'>
165158
): string => {
166-
const entries = getProviderApiKeyEntries(config);
167-
return entries[0]?.apiKey ?? String(config?.apiKey ?? '').trim();
159+
return String(config?.apiKey ?? '').trim();
168160
};
169161

170162
export const buildApiKeyEntry = (input?: Partial<ApiKeyEntry>): ApiKeyEntry => ({

src/components/usage/CredentialStatsCard.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,6 @@ interface CredentialBucket {
4040
}
4141

4242
const providerApiKeyEntries = (config: ProviderKeyConfig) => {
43-
if (config.apiKeyEntries?.length) {
44-
return config.apiKeyEntries;
45-
}
4643
return config.apiKey ? [{ apiKey: config.apiKey }] : [];
4744
};
4845

src/i18n/locales/en.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,8 @@
273273
"codex_keys_count": "Keys Count",
274274
"codex_websockets_label": "Websockets",
275275
"codex_websockets_hint": "Enable Responses API websocket transport for this key.",
276+
"codex_disable_cooling_label": "Disable Cooling",
277+
"codex_disable_cooling_hint": "When enabled, this key will not be cooled down in round-robin scheduling. Suitable for keys with no concurrency limits.",
276278
"codex_models_label": "Custom Models (Optional):",
277279
"codex_models_hint": "Leave empty to allow all models, or add name[, alias] entries to limit/alias them.",
278280
"codex_models_add_btn": "Add Model",

src/i18n/locales/ru.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,8 @@
271271
"codex_keys_count": "Количество ключей",
272272
"codex_websockets_label": "Websockets",
273273
"codex_websockets_hint": "Включает websocket-транспорт Responses API для этого ключа.",
274+
"codex_disable_cooling_label": "Отключить охлаждение",
275+
"codex_disable_cooling_hint": "Если включено, этот ключ не будет охлаждаться при балансировке. Подходит для ключей без ограничений параллелизма.",
274276
"codex_models_label": "Пользовательские модели (необязательно):",
275277
"codex_models_hint": "Оставьте пустым, чтобы разрешить все модели, или добавьте записи name[, alias], чтобы ограничить/переименовать их.",
276278
"codex_models_add_btn": "Добавить модель",

src/i18n/locales/zh-CN.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,8 @@
273273
"codex_keys_count": "密钥数量",
274274
"codex_websockets_label": "Websockets",
275275
"codex_websockets_hint": "开启 Responses API 的 websocket 传输。",
276+
"codex_disable_cooling_label": "禁用冷却",
277+
"codex_disable_cooling_hint": "开启后该密钥在轮询中不会被冷却,适合不限制并发的密钥。",
276278
"codex_models_label": "自定义模型 (可选):",
277279
"codex_models_hint": "为空表示使用全部模型;可填写 name[, alias] 以限制或重命名模型。",
278280
"codex_models_add_btn": "添加模型",

src/pages/AiProvidersClaudeEditLayout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ export function AiProvidersClaudeEditLayout() {
251251
headers: headersToEntries(initialData.headers),
252252
modelEntries: modelsToEntries(initialData.models),
253253
excludedText: excludedModelsToText(initialData.excludedModels),
254-
apiKeyEntries: initialData.apiKeyEntries ?? [],
254+
apiKeyEntries: [],
255255
};
256256
const available = seededForm.modelEntries.map((entry) => entry.name.trim()).filter(Boolean);
257257
const baseline = buildClaudeBaseline(seededForm);

0 commit comments

Comments
 (0)