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
* Triggers OAuth flow server-side to add another account to a provider
* Applies default preset when adding first account
*/
import { useState } from 'react';
@@ -16,15 +17,25 @@ import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Loader2, ExternalLink, User } from 'lucide-react';
import { useStartAuth } from '@/hooks/use-cliproxy';
import { applyDefaultPreset } from '@/lib/preset-utils';
import { toast } from 'sonner';
interface AddAccountDialogProps {
open: boolean;
onClose: () => void;
provider: 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 startAuthMutation = useStartAuth();
@@ -32,7 +43,16 @@ export function AddAccountDialog({ open, onClose, provider, displayName }: AddAc
startAuthMutation.mutate(
{ 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('');
onClose();
},
@@ -57,6 +57,7 @@ import {
useDeletePreset,
} from '@/hooks/use-cliproxy';
import { cn } from '@/lib/utils';
import { CLIPROXY_PORT } from '@/lib/preset-utils';
import { GlobalEnvIndicator } from '@/components/global-env-indicator';
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
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_MODEL: mapping.default,
ANTHROPIC_DEFAULT_OPUS_MODEL: mapping.opus,
@@ -323,7 +324,7 @@ export function ProviderEditor({
onClick={() => {
// Always include BASE_URL and AUTH_TOKEN for CLIProxy providers
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_MODEL: preset.default,
ANTHROPIC_DEFAULT_OPUS_MODEL: preset.opus,
@@ -663,7 +664,7 @@ export function ProviderEditor({
onApply={(values, presetName) => {
// Always include BASE_URL and AUTH_TOKEN for CLIProxy providers
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_MODEL: values.default,
ANTHROPIC_DEFAULT_OPUS_MODEL: values.opus,
+16 -1
View File
@@ -39,8 +39,10 @@ import {
import { useCliproxyAuth, useCreateVariant, useStartAuth } from '@/hooks/use-cliproxy';
import type { AuthStatus, OAuthAccount } from '@/lib/api-client';
import { MODEL_CATALOGS } from '@/lib/model-catalogs';
import { applyDefaultPreset } from '@/lib/preset-utils';
import { cn } from '@/lib/utils';
import { usePrivacy, PRIVACY_BLUR_CLASS } from '@/contexts/privacy-context';
import { toast } from 'sonner';
interface QuickSetupWizardProps {
open: boolean;
@@ -118,10 +120,23 @@ export function QuickSetupWizard({ open, onClose }: QuickSetupWizardProps) {
};
const handleStartAuth = () => {
// Check if this is the first account before auth
const isFirstAccount = (providerAuth?.accounts?.length || 0) === 0;
startAuthMutation.mutate(
{ 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
if (data.account) {
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',
name: 'Claude Opus 4.5 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',
name: 'Claude Sonnet 4.5 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',
name: 'Claude Sonnet 4.5',
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',
name: 'Gemini 3 Pro',
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',
tier: 'paid',
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',
name: 'Gemini 2.5 Pro',
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<{
provider: string;
displayName: string;
isFirstAccount: boolean;
} | null>(null);
const providers = authData?.authStatus || [];
@@ -341,6 +342,7 @@ export function CliproxyPage() {
setAddAccountProvider({
provider: selectedVariantData.provider,
displayName: parentAuthForVariant.displayName,
isFirstAccount: (parentAuthForVariant.accounts?.length || 0) === 0,
})
}
onSetDefault={(accountId) =>
@@ -367,6 +369,7 @@ export function CliproxyPage() {
setAddAccountProvider({
provider: selectedStatus.provider,
displayName: selectedStatus.displayName,
isFirstAccount: (selectedStatus.accounts?.length || 0) === 0,
})
}
onSetDefault={(accountId) =>
@@ -395,6 +398,7 @@ export function CliproxyPage() {
onClose={() => setAddAccountProvider(null)}
provider={addAccountProvider?.provider || ''}
displayName={addAccountProvider?.displayName || ''}
isFirstAccount={addAccountProvider?.isFirstAccount || false}
/>
</div>
);