mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-02 12:19:35 +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 { useKiroImport } from '@/hooks/use-cliproxy';
|
||||||
import { useCliproxyAuthFlow } from '@/hooks/use-cliproxy-auth-flow';
|
import { useCliproxyAuthFlow } from '@/hooks/use-cliproxy-auth-flow';
|
||||||
import { applyDefaultPreset } from '@/lib/preset-utils';
|
import { applyDefaultPreset } from '@/lib/preset-utils';
|
||||||
|
import type { CliproxyProviderCatalog } from '@/lib/api-client';
|
||||||
import { AccountSafetyWarningCard } from '@/components/account/account-safety-warning-card';
|
import { AccountSafetyWarningCard } from '@/components/account/account-safety-warning-card';
|
||||||
import { AntigravityResponsibilityChecklist } from '@/components/account/antigravity-responsibility-checklist';
|
import { AntigravityResponsibilityChecklist } from '@/components/account/antigravity-responsibility-checklist';
|
||||||
import {
|
import {
|
||||||
@@ -56,6 +57,7 @@ interface AddAccountDialogProps {
|
|||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
provider: string;
|
provider: string;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
|
catalog?: CliproxyProviderCatalog;
|
||||||
/** Whether this is the first account being added (shows different toast message) */
|
/** Whether this is the first account being added (shows different toast message) */
|
||||||
isFirstAccount?: boolean;
|
isFirstAccount?: boolean;
|
||||||
}
|
}
|
||||||
@@ -74,6 +76,7 @@ export function AddAccountDialog({
|
|||||||
onClose,
|
onClose,
|
||||||
provider,
|
provider,
|
||||||
displayName,
|
displayName,
|
||||||
|
catalog,
|
||||||
isFirstAccount = false,
|
isFirstAccount = false,
|
||||||
}: AddAccountDialogProps) {
|
}: AddAccountDialogProps) {
|
||||||
const [nickname, setNickname] = useState('');
|
const [nickname, setNickname] = useState('');
|
||||||
@@ -244,7 +247,7 @@ export function AddAccountDialog({
|
|||||||
wasAuthenticatingRef.current = false;
|
wasAuthenticatingRef.current = false;
|
||||||
const applyPresetAndClose = async () => {
|
const applyPresetAndClose = async () => {
|
||||||
try {
|
try {
|
||||||
const result = await applyDefaultPreset(provider);
|
const result = await applyDefaultPreset(provider, undefined, catalog);
|
||||||
if (result.success && result.presetName && isFirstAccount) {
|
if (result.success && result.presetName && isFirstAccount) {
|
||||||
toast.success(`Applied "${result.presetName}" preset`);
|
toast.success(`Applied "${result.presetName}" preset`);
|
||||||
}
|
}
|
||||||
@@ -331,7 +334,7 @@ export function AddAccountDialog({
|
|||||||
wasAuthenticatingRef.current = true;
|
wasAuthenticatingRef.current = true;
|
||||||
kiroImportMutation.mutate(undefined, {
|
kiroImportMutation.mutate(undefined, {
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
const result = await applyDefaultPreset('kiro');
|
const result = await applyDefaultPreset('kiro', undefined, catalog);
|
||||||
if (result.success && result.presetName && isFirstAccount) {
|
if (result.success && result.presetName && isFirstAccount) {
|
||||||
toast.success(`Applied "${result.presetName}" preset`);
|
toast.success(`Applied "${result.presetName}" preset`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,7 +101,11 @@ export function QuickSetupWizard({ open, onClose }: QuickSetupWizardProps) {
|
|||||||
{
|
{
|
||||||
onSuccess: async (data) => {
|
onSuccess: async (data) => {
|
||||||
if (isFirstAccount) {
|
if (isFirstAccount) {
|
||||||
const result = await applyDefaultPreset(selectedProvider);
|
const result = await applyDefaultPreset(
|
||||||
|
selectedProvider,
|
||||||
|
undefined,
|
||||||
|
catalogs[selectedProvider]
|
||||||
|
);
|
||||||
if (result.success && result.presetName) {
|
if (result.success && result.presetName) {
|
||||||
toast.success(`Applied "${result.presetName}" preset`);
|
toast.success(`Applied "${result.presetName}" preset`);
|
||||||
} else if (!result.success) {
|
} else if (!result.success) {
|
||||||
|
|||||||
+17
-10
@@ -3,6 +3,7 @@
|
|||||||
* Shared functions for applying default presets to provider settings
|
* Shared functions for applying default presets to provider settings
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import type { CliproxyProviderCatalog } from './api-client';
|
||||||
import { MODEL_CATALOGS } from './model-catalogs';
|
import { MODEL_CATALOGS } from './model-catalogs';
|
||||||
import { buildUiCatalogs } from './model-catalogs';
|
import { buildUiCatalogs } from './model-catalogs';
|
||||||
import { CLIPROXY_DEFAULT_PORT } from './default-ports';
|
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 {
|
try {
|
||||||
const response = await fetch('/api/cliproxy/catalog');
|
const response = await fetch('/api/cliproxy/catalog');
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@@ -52,18 +57,20 @@ async function fetchProviderCatalog(provider: string) {
|
|||||||
*/
|
*/
|
||||||
export async function applyDefaultPreset(
|
export async function applyDefaultPreset(
|
||||||
provider: string,
|
provider: string,
|
||||||
port?: number
|
port?: number,
|
||||||
|
catalog?: CliproxyProviderCatalog
|
||||||
): Promise<{ success: boolean; presetName?: string }> {
|
): Promise<{ success: boolean; presetName?: string }> {
|
||||||
const catalog = await fetchProviderCatalog(provider);
|
const resolvedCatalog = await fetchProviderCatalog(provider, catalog);
|
||||||
if (!catalog) return { success: false };
|
if (!resolvedCatalog) return { success: false };
|
||||||
|
|
||||||
const defaultModelEntry =
|
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 || {
|
const mapping = defaultModelEntry?.presetMapping || {
|
||||||
default: catalog.defaultModel,
|
default: resolvedCatalog.defaultModel,
|
||||||
opus: catalog.defaultModel,
|
opus: resolvedCatalog.defaultModel,
|
||||||
sonnet: catalog.defaultModel,
|
sonnet: resolvedCatalog.defaultModel,
|
||||||
haiku: catalog.defaultModel,
|
haiku: resolvedCatalog.defaultModel,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Fetch effective API key (respects user customization)
|
// Fetch effective API key (respects user customization)
|
||||||
@@ -89,7 +96,7 @@ export async function applyDefaultPreset(
|
|||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
success: res.ok,
|
success: res.ok,
|
||||||
presetName: defaultModelEntry?.name || catalog.defaultModel,
|
presetName: defaultModelEntry?.name || resolvedCatalog.defaultModel,
|
||||||
};
|
};
|
||||||
} catch {
|
} catch {
|
||||||
return { success: false };
|
return { success: false };
|
||||||
|
|||||||
@@ -561,6 +561,7 @@ export function CliproxyPage() {
|
|||||||
onClose={() => setAddAccountProvider(null)}
|
onClose={() => setAddAccountProvider(null)}
|
||||||
provider={addAccountProvider?.provider || ''}
|
provider={addAccountProvider?.provider || ''}
|
||||||
displayName={addAccountProvider?.displayName || ''}
|
displayName={addAccountProvider?.displayName || ''}
|
||||||
|
catalog={addAccountProvider?.provider ? catalogs[addAccountProvider.provider] : undefined}
|
||||||
isFirstAccount={addAccountProvider?.isFirstAccount || false}
|
isFirstAccount={addAccountProvider?.isFirstAccount || false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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', () => {
|
it('builds UI catalogs from upstream provider models without requiring static dropdown edits', () => {
|
||||||
const liveCatalogs = {
|
const liveCatalogs = {
|
||||||
gemini: {
|
gemini: {
|
||||||
|
|||||||
Reference in New Issue
Block a user