feat(cliproxy): auto-apply default preset when adding first account

- Add presetMapping to Gemini, Antigravity, Codex models
- Auto-apply preset when adding first account (Add/QuickSetup)
- Extract applyDefaultPreset to preset-utils.ts (DRY)
- Add warning toast on preset failure
- Extract port 8317 to CLIPROXY_PORT constant
This commit is contained in:
kaitranntt
2025-12-19 11:26:17 -05:00
parent aacff0343d
commit 4036c42687
6 changed files with 139 additions and 6 deletions
+22 -2
View File
@@ -1,6 +1,7 @@
/** /**
* Add Account Dialog Component * Add Account Dialog Component
* Triggers OAuth flow server-side to add another account to a provider * Triggers OAuth flow server-side to add another account to a provider
* Applies default preset when adding first account
*/ */
import { useState } from 'react'; import { useState } from 'react';
@@ -16,15 +17,25 @@ import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label'; import { Label } from '@/components/ui/label';
import { Loader2, ExternalLink, User } from 'lucide-react'; import { Loader2, ExternalLink, User } from 'lucide-react';
import { useStartAuth } from '@/hooks/use-cliproxy'; import { useStartAuth } from '@/hooks/use-cliproxy';
import { applyDefaultPreset } from '@/lib/preset-utils';
import { toast } from 'sonner';
interface AddAccountDialogProps { interface AddAccountDialogProps {
open: boolean; open: boolean;
onClose: () => void; onClose: () => void;
provider: string; provider: string;
displayName: string; displayName: string;
/** Whether this is the first account being added (triggers preset application) */
isFirstAccount?: boolean;
} }
export function AddAccountDialog({ open, onClose, provider, displayName }: AddAccountDialogProps) { export function AddAccountDialog({
open,
onClose,
provider,
displayName,
isFirstAccount = false,
}: AddAccountDialogProps) {
const [nickname, setNickname] = useState(''); const [nickname, setNickname] = useState('');
const startAuthMutation = useStartAuth(); const startAuthMutation = useStartAuth();
@@ -32,7 +43,16 @@ export function AddAccountDialog({ open, onClose, provider, displayName }: AddAc
startAuthMutation.mutate( startAuthMutation.mutate(
{ provider, nickname: nickname.trim() || undefined }, { provider, nickname: nickname.trim() || undefined },
{ {
onSuccess: () => { onSuccess: async () => {
// Apply default preset if this is the first account
if (isFirstAccount) {
const result = await applyDefaultPreset(provider);
if (result.success && result.presetName) {
toast.success(`Applied "${result.presetName}" preset`);
} else if (!result.success) {
toast.warning('Account added, but failed to apply default preset');
}
}
setNickname(''); setNickname('');
onClose(); onClose();
}, },
@@ -57,6 +57,7 @@ import {
useDeletePreset, useDeletePreset,
} from '@/hooks/use-cliproxy'; } from '@/hooks/use-cliproxy';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { CLIPROXY_PORT } from '@/lib/preset-utils';
import { GlobalEnvIndicator } from '@/components/global-env-indicator'; import { GlobalEnvIndicator } from '@/components/global-env-indicator';
import { usePrivacy, PRIVACY_BLUR_CLASS } from '@/contexts/privacy-context'; import { usePrivacy, PRIVACY_BLUR_CLASS } from '@/contexts/privacy-context';
@@ -298,7 +299,7 @@ export function ProviderEditor({
}; };
// Always include BASE_URL and AUTH_TOKEN for CLIProxy providers // Always include BASE_URL and AUTH_TOKEN for CLIProxy providers
updateEnvValues({ updateEnvValues({
ANTHROPIC_BASE_URL: `http://127.0.0.1:8317/api/provider/${provider}`, ANTHROPIC_BASE_URL: `http://127.0.0.1:${CLIPROXY_PORT}/api/provider/${provider}`,
ANTHROPIC_AUTH_TOKEN: 'ccs-internal-managed', ANTHROPIC_AUTH_TOKEN: 'ccs-internal-managed',
ANTHROPIC_MODEL: mapping.default, ANTHROPIC_MODEL: mapping.default,
ANTHROPIC_DEFAULT_OPUS_MODEL: mapping.opus, ANTHROPIC_DEFAULT_OPUS_MODEL: mapping.opus,
@@ -323,7 +324,7 @@ export function ProviderEditor({
onClick={() => { onClick={() => {
// Always include BASE_URL and AUTH_TOKEN for CLIProxy providers // Always include BASE_URL and AUTH_TOKEN for CLIProxy providers
updateEnvValues({ updateEnvValues({
ANTHROPIC_BASE_URL: `http://127.0.0.1:8317/api/provider/${provider}`, ANTHROPIC_BASE_URL: `http://127.0.0.1:${CLIPROXY_PORT}/api/provider/${provider}`,
ANTHROPIC_AUTH_TOKEN: 'ccs-internal-managed', ANTHROPIC_AUTH_TOKEN: 'ccs-internal-managed',
ANTHROPIC_MODEL: preset.default, ANTHROPIC_MODEL: preset.default,
ANTHROPIC_DEFAULT_OPUS_MODEL: preset.opus, ANTHROPIC_DEFAULT_OPUS_MODEL: preset.opus,
@@ -663,7 +664,7 @@ export function ProviderEditor({
onApply={(values, presetName) => { onApply={(values, presetName) => {
// Always include BASE_URL and AUTH_TOKEN for CLIProxy providers // Always include BASE_URL and AUTH_TOKEN for CLIProxy providers
updateEnvValues({ updateEnvValues({
ANTHROPIC_BASE_URL: `http://127.0.0.1:8317/api/provider/${provider}`, ANTHROPIC_BASE_URL: `http://127.0.0.1:${CLIPROXY_PORT}/api/provider/${provider}`,
ANTHROPIC_AUTH_TOKEN: 'ccs-internal-managed', ANTHROPIC_AUTH_TOKEN: 'ccs-internal-managed',
ANTHROPIC_MODEL: values.default, ANTHROPIC_MODEL: values.default,
ANTHROPIC_DEFAULT_OPUS_MODEL: values.opus, ANTHROPIC_DEFAULT_OPUS_MODEL: values.opus,
+16 -1
View File
@@ -39,8 +39,10 @@ import {
import { useCliproxyAuth, useCreateVariant, useStartAuth } from '@/hooks/use-cliproxy'; import { useCliproxyAuth, useCreateVariant, useStartAuth } from '@/hooks/use-cliproxy';
import type { AuthStatus, OAuthAccount } from '@/lib/api-client'; import type { AuthStatus, OAuthAccount } from '@/lib/api-client';
import { MODEL_CATALOGS } from '@/lib/model-catalogs'; import { MODEL_CATALOGS } from '@/lib/model-catalogs';
import { applyDefaultPreset } from '@/lib/preset-utils';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { usePrivacy, PRIVACY_BLUR_CLASS } from '@/contexts/privacy-context'; import { usePrivacy, PRIVACY_BLUR_CLASS } from '@/contexts/privacy-context';
import { toast } from 'sonner';
interface QuickSetupWizardProps { interface QuickSetupWizardProps {
open: boolean; open: boolean;
@@ -118,10 +120,23 @@ export function QuickSetupWizard({ open, onClose }: QuickSetupWizardProps) {
}; };
const handleStartAuth = () => { const handleStartAuth = () => {
// Check if this is the first account before auth
const isFirstAccount = (providerAuth?.accounts?.length || 0) === 0;
startAuthMutation.mutate( startAuthMutation.mutate(
{ provider: selectedProvider }, { provider: selectedProvider },
{ {
onSuccess: (data) => { onSuccess: async (data) => {
// Apply default preset if this was the first account
if (isFirstAccount) {
const result = await applyDefaultPreset(selectedProvider);
if (result.success && result.presetName) {
toast.success(`Applied "${result.presetName}" preset`);
} else if (!result.success) {
toast.warning('Account added, but failed to apply default preset');
}
}
// Account created, select it and advance to variant step // Account created, select it and advance to variant step
if (data.account) { if (data.account) {
setSelectedAccount(data.account as OAuthAccount); setSelectedAccount(data.account as OAuthAccount);
+36
View File
@@ -16,21 +16,45 @@ export const MODEL_CATALOGS: Record<string, ProviderCatalog> = {
id: 'gemini-claude-opus-4-5-thinking', id: 'gemini-claude-opus-4-5-thinking',
name: 'Claude Opus 4.5 Thinking', name: 'Claude Opus 4.5 Thinking',
description: 'Most capable, extended thinking', description: 'Most capable, extended thinking',
presetMapping: {
default: 'gemini-claude-opus-4-5-thinking',
opus: 'gemini-claude-opus-4-5-thinking',
sonnet: 'gemini-claude-sonnet-4-5-thinking',
haiku: 'gemini-claude-sonnet-4-5',
},
}, },
{ {
id: 'gemini-claude-sonnet-4-5-thinking', id: 'gemini-claude-sonnet-4-5-thinking',
name: 'Claude Sonnet 4.5 Thinking', name: 'Claude Sonnet 4.5 Thinking',
description: 'Balanced with extended thinking', description: 'Balanced with extended thinking',
presetMapping: {
default: 'gemini-claude-sonnet-4-5-thinking',
opus: 'gemini-claude-opus-4-5-thinking',
sonnet: 'gemini-claude-sonnet-4-5-thinking',
haiku: 'gemini-claude-sonnet-4-5',
},
}, },
{ {
id: 'gemini-claude-sonnet-4-5', id: 'gemini-claude-sonnet-4-5',
name: 'Claude Sonnet 4.5', name: 'Claude Sonnet 4.5',
description: 'Fast and capable', description: 'Fast and capable',
presetMapping: {
default: 'gemini-claude-sonnet-4-5',
opus: 'gemini-claude-opus-4-5-thinking',
sonnet: 'gemini-claude-sonnet-4-5',
haiku: 'gemini-claude-sonnet-4-5',
},
}, },
{ {
id: 'gemini-3-pro-preview', id: 'gemini-3-pro-preview',
name: 'Gemini 3 Pro', name: 'Gemini 3 Pro',
description: 'Google latest model via Antigravity', description: 'Google latest model via Antigravity',
presetMapping: {
default: 'gemini-3-pro-preview',
opus: 'gemini-3-pro-preview',
sonnet: 'gemini-3-pro-preview',
haiku: 'gemini-2.5-flash',
},
}, },
], ],
}, },
@@ -44,11 +68,23 @@ export const MODEL_CATALOGS: Record<string, ProviderCatalog> = {
name: 'Gemini 3 Pro', name: 'Gemini 3 Pro',
tier: 'paid', tier: 'paid',
description: 'Latest model, requires paid Google account', description: 'Latest model, requires paid Google account',
presetMapping: {
default: 'gemini-3-pro-preview',
opus: 'gemini-3-pro-preview',
sonnet: 'gemini-3-pro-preview',
haiku: 'gemini-2.5-flash',
},
}, },
{ {
id: 'gemini-2.5-pro', id: 'gemini-2.5-pro',
name: 'Gemini 2.5 Pro', name: 'Gemini 2.5 Pro',
description: 'Stable, works with free Google account', description: 'Stable, works with free Google account',
presetMapping: {
default: 'gemini-2.5-pro',
opus: 'gemini-2.5-pro',
sonnet: 'gemini-2.5-pro',
haiku: 'gemini-2.5-flash',
},
}, },
], ],
}, },
+57
View File
@@ -0,0 +1,57 @@
/**
* Preset Utilities
* Shared functions for applying default presets to provider settings
*/
import { MODEL_CATALOGS } from './model-catalogs';
/** CLIProxy port - should match the backend configuration */
export const CLIPROXY_PORT = 8317;
/**
* Apply default preset for a provider to its settings
* Uses the first model's presetMapping or falls back to using defaultModel for all tiers
*
* @param provider - The provider ID (e.g., 'gemini', 'codex', 'agy')
* @returns Object with success status and applied preset name
*/
export async function applyDefaultPreset(
provider: string
): Promise<{ success: boolean; presetName?: string }> {
const catalog = MODEL_CATALOGS[provider];
if (!catalog) return { success: false };
// Get the first (recommended) model's preset mapping
const firstModel = catalog.models[0];
const mapping = firstModel?.presetMapping || {
default: catalog.defaultModel,
opus: catalog.defaultModel,
sonnet: catalog.defaultModel,
haiku: catalog.defaultModel,
};
const settings = {
env: {
ANTHROPIC_BASE_URL: `http://127.0.0.1:${CLIPROXY_PORT}/api/provider/${provider}`,
ANTHROPIC_AUTH_TOKEN: 'ccs-internal-managed',
ANTHROPIC_MODEL: mapping.default,
ANTHROPIC_DEFAULT_OPUS_MODEL: mapping.opus,
ANTHROPIC_DEFAULT_SONNET_MODEL: mapping.sonnet,
ANTHROPIC_DEFAULT_HAIKU_MODEL: mapping.haiku,
},
};
try {
const res = await fetch(`/api/settings/${provider}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ settings }),
});
return {
success: res.ok,
presetName: firstModel?.name || catalog.defaultModel,
};
} catch {
return { success: false };
}
}
+4
View File
@@ -188,6 +188,7 @@ export function CliproxyPage() {
const [addAccountProvider, setAddAccountProvider] = useState<{ const [addAccountProvider, setAddAccountProvider] = useState<{
provider: string; provider: string;
displayName: string; displayName: string;
isFirstAccount: boolean;
} | null>(null); } | null>(null);
const providers = authData?.authStatus || []; const providers = authData?.authStatus || [];
@@ -341,6 +342,7 @@ export function CliproxyPage() {
setAddAccountProvider({ setAddAccountProvider({
provider: selectedVariantData.provider, provider: selectedVariantData.provider,
displayName: parentAuthForVariant.displayName, displayName: parentAuthForVariant.displayName,
isFirstAccount: (parentAuthForVariant.accounts?.length || 0) === 0,
}) })
} }
onSetDefault={(accountId) => onSetDefault={(accountId) =>
@@ -367,6 +369,7 @@ export function CliproxyPage() {
setAddAccountProvider({ setAddAccountProvider({
provider: selectedStatus.provider, provider: selectedStatus.provider,
displayName: selectedStatus.displayName, displayName: selectedStatus.displayName,
isFirstAccount: (selectedStatus.accounts?.length || 0) === 0,
}) })
} }
onSetDefault={(accountId) => onSetDefault={(accountId) =>
@@ -395,6 +398,7 @@ export function CliproxyPage() {
onClose={() => setAddAccountProvider(null)} onClose={() => setAddAccountProvider(null)}
provider={addAccountProvider?.provider || ''} provider={addAccountProvider?.provider || ''}
displayName={addAccountProvider?.displayName || ''} displayName={addAccountProvider?.displayName || ''}
isFirstAccount={addAccountProvider?.isFirstAccount || false}
/> />
</div> </div>
); );