fix(ui): reuse cached cliproxy catalogs for presets

- let applyDefaultPreset accept a caller-provided provider catalog

- pass cached catalog data from cliproxy and quick-setup flows

- add regression coverage for skipping the extra catalog fetch
This commit is contained in:
Tam Nhu Tran
2026-04-08 17:52:45 -04:00
parent 002b7feb1e
commit 564ed3def7
5 changed files with 53 additions and 13 deletions
@@ -29,6 +29,7 @@ import { Loader2, ExternalLink, User, Download, Copy, Check, ShieldAlert } from
import { useKiroImport } from '@/hooks/use-cliproxy';
import { useCliproxyAuthFlow } from '@/hooks/use-cliproxy-auth-flow';
import { applyDefaultPreset } from '@/lib/preset-utils';
import type { CliproxyProviderCatalog } from '@/lib/api-client';
import { AccountSafetyWarningCard } from '@/components/account/account-safety-warning-card';
import { AntigravityResponsibilityChecklist } from '@/components/account/antigravity-responsibility-checklist';
import {
@@ -56,6 +57,7 @@ interface AddAccountDialogProps {
onClose: () => void;
provider: string;
displayName: string;
catalog?: CliproxyProviderCatalog;
/** Whether this is the first account being added (shows different toast message) */
isFirstAccount?: boolean;
}
@@ -74,6 +76,7 @@ export function AddAccountDialog({
onClose,
provider,
displayName,
catalog,
isFirstAccount = false,
}: AddAccountDialogProps) {
const [nickname, setNickname] = useState('');
@@ -244,7 +247,7 @@ export function AddAccountDialog({
wasAuthenticatingRef.current = false;
const applyPresetAndClose = async () => {
try {
const result = await applyDefaultPreset(provider);
const result = await applyDefaultPreset(provider, undefined, catalog);
if (result.success && result.presetName && isFirstAccount) {
toast.success(`Applied "${result.presetName}" preset`);
}
@@ -331,7 +334,7 @@ export function AddAccountDialog({
wasAuthenticatingRef.current = true;
kiroImportMutation.mutate(undefined, {
onSuccess: async () => {
const result = await applyDefaultPreset('kiro');
const result = await applyDefaultPreset('kiro', undefined, catalog);
if (result.success && result.presetName && isFirstAccount) {
toast.success(`Applied "${result.presetName}" preset`);
}
+5 -1
View File
@@ -101,7 +101,11 @@ export function QuickSetupWizard({ open, onClose }: QuickSetupWizardProps) {
{
onSuccess: async (data) => {
if (isFirstAccount) {
const result = await applyDefaultPreset(selectedProvider);
const result = await applyDefaultPreset(
selectedProvider,
undefined,
catalogs[selectedProvider]
);
if (result.success && result.presetName) {
toast.success(`Applied "${result.presetName}" preset`);
} else if (!result.success) {
+17 -10
View File
@@ -3,6 +3,7 @@
* Shared functions for applying default presets to provider settings
*/
import type { CliproxyProviderCatalog } from './api-client';
import { MODEL_CATALOGS } from './model-catalogs';
import { buildUiCatalogs } from './model-catalogs';
import { CLIPROXY_DEFAULT_PORT } from './default-ports';
@@ -26,7 +27,11 @@ async function fetchEffectiveApiKey(): Promise<string> {
}
}
async function fetchProviderCatalog(provider: string) {
async function fetchProviderCatalog(provider: string, catalog?: CliproxyProviderCatalog) {
if (catalog) {
return catalog;
}
try {
const response = await fetch('/api/cliproxy/catalog');
if (!response.ok) {
@@ -52,18 +57,20 @@ async function fetchProviderCatalog(provider: string) {
*/
export async function applyDefaultPreset(
provider: string,
port?: number
port?: number,
catalog?: CliproxyProviderCatalog
): Promise<{ success: boolean; presetName?: string }> {
const catalog = await fetchProviderCatalog(provider);
if (!catalog) return { success: false };
const resolvedCatalog = await fetchProviderCatalog(provider, catalog);
if (!resolvedCatalog) return { success: false };
const defaultModelEntry =
catalog.models.find((model) => model.id === catalog.defaultModel) || catalog.models[0];
resolvedCatalog.models.find((model) => model.id === resolvedCatalog.defaultModel) ||
resolvedCatalog.models[0];
const mapping = defaultModelEntry?.presetMapping || {
default: catalog.defaultModel,
opus: catalog.defaultModel,
sonnet: catalog.defaultModel,
haiku: catalog.defaultModel,
default: resolvedCatalog.defaultModel,
opus: resolvedCatalog.defaultModel,
sonnet: resolvedCatalog.defaultModel,
haiku: resolvedCatalog.defaultModel,
};
// Fetch effective API key (respects user customization)
@@ -89,7 +96,7 @@ export async function applyDefaultPreset(
});
return {
success: res.ok,
presetName: defaultModelEntry?.name || catalog.defaultModel,
presetName: defaultModelEntry?.name || resolvedCatalog.defaultModel,
};
} catch {
return { success: false };
+1
View File
@@ -561,6 +561,7 @@ export function CliproxyPage() {
onClose={() => setAddAccountProvider(null)}
provider={addAccountProvider?.provider || ''}
displayName={addAccountProvider?.displayName || ''}
catalog={addAccountProvider?.provider ? catalogs[addAccountProvider.provider] : undefined}
isFirstAccount={addAccountProvider?.isFirstAccount || false}
/>
</div>
+25
View File
@@ -58,6 +58,31 @@ describe('claude preset utils', () => {
});
});
it('skips catalog fetch when the caller already has the provider catalog cached', async () => {
const fetchMock = vi
.fn()
.mockResolvedValueOnce({
ok: true,
json: async () => ({ apiKey: { value: 'managed-key' } }),
})
.mockResolvedValueOnce({ ok: true });
vi.stubGlobal('fetch', fetchMock);
const result = await applyDefaultPreset('claude', undefined, MODEL_CATALOGS.claude);
expect(result).toEqual({ success: true, presetName: 'Claude Sonnet 4.6' });
expect(fetchMock).toHaveBeenCalledTimes(2);
expect(fetchMock).toHaveBeenNthCalledWith(1, '/api/settings/auth/tokens/raw');
expect(fetchMock).toHaveBeenNthCalledWith(
2,
'/api/settings/claude',
expect.objectContaining({
method: 'PUT',
})
);
});
it('builds UI catalogs from upstream provider models without requiring static dropdown edits', () => {
const liveCatalogs = {
gemini: {