feat(ui): add codex fast service-tier toggle

This commit is contained in:
milesnguyen2405 committed 2026-07-22 15:13:42 +07:00
1 parent 30b54a0966
commit 108ff843f1
4 files changed
+80 -1

No files matched your search

@@ -18,15 +18,22 @@ import {
SelectValue,
} from '@/components/ui/select';
import { Skeleton } from '@/components/ui/skeleton';
import { Switch } from '@/components/ui/switch';
import type { CliproxyProviderRoutingHints } from '@/lib/api-client';
import type { CodexEffort, CodexServiceTier } from '@/lib/codex-effort';
import {
applyCodexServiceTierSuffix,
getCodexEffortDisplay,
getCodexEffortVariants,
parseCodexEffort,
parseCodexServiceTier,
stripCodexEffortSuffix,
} from '@/lib/codex-effort';
import { getResolvedCatalogModels, getSupplementalCatalogModels } from '@/lib/model-catalogs';
import {
findCatalogModel,
getResolvedCatalogModels,
getSupplementalCatalogModels,
} from '@/lib/model-catalogs';
import { getReasoningAdapter } from '@/lib/reasoning-control';
import { cn } from '@/lib/utils';
@@ -586,6 +593,19 @@ export function FlexibleModelSelector({
!selectableReasoningLevels.includes(currentReasoningLevel)
? [...selectableReasoningLevels, currentReasoningLevel]
: selectableReasoningLevels;
const currentServiceTier = isCodexProvider ? parseCodexServiceTier(value) : undefined;
const selectedCodexBaseModel =
isCodexProvider && catalog && value
? findCatalogModel(
catalog.provider,
stripCodexEffortSuffix(normalizeModelValue(value, routing)),
catalog
)
: undefined;
const showFastToggle =
isCodexProvider &&
(selectedCodexBaseModel?.codexServiceTiers?.includes('fast') === true ||
currentServiceTier === 'fast');
return (
<div className="space-y-1.5">
@@ -702,6 +722,19 @@ export function FlexibleModelSelector({
</SelectContent>
</Select>
)}
{showFastToggle && (
<label className="flex h-9 shrink-0 items-center gap-1.5">
<Switch
checked={currentServiceTier === 'fast'}
onCheckedChange={(checked) => onChange(applyCodexServiceTierSuffix(value, checked))}
disabled={disabled || !value}
aria-label={t('providerModelSelector.fastTier')}
/>
<span className="text-xs text-muted-foreground">
{t('providerModelSelector.fastTier')}
</span>
</label>
)}
</div>
{selectedRoutingHint ? (
<div
+6
View File
@@ -66,6 +66,12 @@ export function applyCodexEffortSuffix(
return [normalizedModelId, effort, parsed.serviceTier].filter(Boolean).join('-');
}
export function applyCodexServiceTierSuffix(modelId: string | undefined, enabled: boolean): string {
const parsed = parseCodexTuningSuffix(modelId);
if (!parsed.baseModel) return parsed.baseModel;
return [parsed.baseModel, parsed.effort, enabled ? 'fast' : undefined].filter(Boolean).join('-');
}
export function getCodexEffortVariants(
modelId: string,
maxEffort: CodexEffort | undefined,
+5
View File
@@ -230,6 +230,7 @@ const resources = {
codexFastVariants: 'Fast variants',
effortLabel: 'Effort',
effortAuto: 'Auto',
fastTier: 'Fast',
},
createAuthProfileDialog: {
title: 'Create New Account',
@@ -2958,6 +2959,7 @@ const resources = {
codexFastVariants: '快速变体',
effortLabel: '推理强度',
effortAuto: '自动',
fastTier: '快速',
},
createAuthProfileDialog: {
title: '创建新账号',
@@ -5569,6 +5571,7 @@ const resources = {
codexFastVariants: 'Biến thể nhanh',
effortLabel: 'Mức suy luận',
effortAuto: 'Tự động',
fastTier: 'Nhanh',
},
createAuthProfileDialog: {
title: 'Tạo tài khoản mới',
@@ -8296,6 +8299,7 @@ const resources = {
codexFastVariants: '高速バリアント',
effortLabel: '推論レベル',
effortAuto: '自動',
fastTier: '高速',
},
createAuthProfileDialog: {
title: '新しいアカウントを作成',
@@ -11046,6 +11050,7 @@ const resources = {
codexFastVariants: '빠른 변형',
effortLabel: '추론 강도',
effortAuto: '자동',
fastTier: '고속',
},
createAuthProfileDialog: {
title: '새 계정 생성',
+35
View File
@@ -1,6 +1,7 @@
import { describe, expect, it } from 'vitest';
import {
applyCodexEffortSuffix,
applyCodexServiceTierSuffix,
getCodexEffortDisplay,
getCodexEffortVariants,
getSelectableCodexEfforts,
@@ -111,6 +112,40 @@ describe('codex effort helpers', () => {
});
});
describe('applyCodexServiceTierSuffix', () => {
it('adds the fast tier while preserving the effort suffix', () => {
expect(applyCodexServiceTierSuffix('gpt-5.4-high', true)).toBe('gpt-5.4-high-fast');
});
it('removes only the fast tier', () => {
expect(applyCodexServiceTierSuffix('gpt-5.4-high-fast', false)).toBe('gpt-5.4-high');
expect(applyCodexServiceTierSuffix('gpt-5.4-fast', false)).toBe('gpt-5.4');
});
it('preserves routing prefixes', () => {
expect(applyCodexServiceTierSuffix('codex/gpt-5.6-sol', true)).toBe('codex/gpt-5.6-sol-fast');
});
it('is idempotent and safe on empty input', () => {
expect(applyCodexServiceTierSuffix('gpt-5.4-fast', true)).toBe('gpt-5.4-fast');
expect(applyCodexServiceTierSuffix('gpt-5.4', false)).toBe('gpt-5.4');
expect(applyCodexServiceTierSuffix(undefined, true)).toBe('');
expect(applyCodexServiceTierSuffix('', true)).toBe('');
});
it('composes with applyCodexEffortSuffix in both orders', () => {
expect(applyCodexEffortSuffix(applyCodexServiceTierSuffix('gpt-5.4', true), 'high')).toBe(
'gpt-5.4-high-fast'
);
expect(applyCodexServiceTierSuffix(applyCodexEffortSuffix('gpt-5.4', 'high'), true)).toBe(
'gpt-5.4-high-fast'
);
expect(
applyCodexEffortSuffix(applyCodexServiceTierSuffix('gpt-5.4-high-fast', false), 'low')
).toBe('gpt-5.4-low');
});
});
describe('getSelectableCodexEfforts', () => {
it('returns the explicit efforts list when provided', () => {
expect(getSelectableCodexEfforts('xhigh', ['low', 'medium', 'high', 'xhigh'])).toEqual([