mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 08:17:11 +00:00
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:
@@ -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`);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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 };
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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: {
|
||||
|
||||
Reference in New Issue
Block a user