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