diff --git a/ui/src/components/profiles/profile-create-dialog.tsx b/ui/src/components/profiles/profile-create-dialog.tsx index d08f0c98..c95dfc00 100644 --- a/ui/src/components/profiles/profile-create-dialog.tsx +++ b/ui/src/components/profiles/profile-create-dialog.tsx @@ -31,7 +31,17 @@ import { Badge } from '@/components/ui/badge'; import { ProviderLogo } from '@/components/cliproxy/provider-logo'; import { useCreateProfile } from '@/hooks/use-profiles'; import { useOpenRouterCatalog } from '@/hooks/use-openrouter-models'; -import { Loader2, Plus, AlertTriangle, Info, Eye, EyeOff, Settings2, Sparkles } from 'lucide-react'; +import { + Loader2, + Plus, + AlertTriangle, + Info, + Eye, + EyeOff, + Settings2, + Sparkles, + ChevronDown, +} from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { toast } from 'sonner'; import { cn } from '@/lib/utils'; @@ -125,6 +135,7 @@ export function ProfileCreateDialog({ const [urlWarning, setUrlWarning] = useState(null); const [showApiKey, setShowApiKey] = useState(false); const [selectedPreset, setSelectedPreset] = useState(DEFAULT_PRESET_ID); + const [showMorePresets, setShowMorePresets] = useState(false); const [modelSearch, setModelSearch] = useState(''); // OpenRouter models for model picker @@ -187,6 +198,7 @@ export function ProfileCreateDialog({ setActiveTab('basic'); setUrlWarning(null); setShowApiKey(false); + setShowMorePresets(false); setModelSearch(''); // Set initial preset based on initialMode @@ -215,11 +227,13 @@ export function ProfileCreateDialog({ if (preset) { setSelectedPreset(preset.id); + setShowMorePresets(QUICK_TEMPLATE_PRESET_IDS.has(preset.id)); applyPresetToForm(preset); return; } setSelectedPreset(CUSTOM_PRESET_ID); + setShowMorePresets(false); applyPresetToForm(null); }; @@ -284,7 +298,7 @@ export function ProfileCreateDialog({ const isQuickTemplateSelected = selectedPreset !== CUSTOM_PRESET_ID && QUICK_TEMPLATE_PRESET_IDS.has(selectedPreset); const isOpenRouter = currentPreset?.id === DEFAULT_PRESET_ID; - const showQuickTemplates = selectedPreset === CUSTOM_PRESET_ID || isQuickTemplateSelected; + const showQuickTemplates = showMorePresets || isQuickTemplateSelected; return ( @@ -303,28 +317,28 @@ export function ProfileCreateDialog({ onSubmit={handleSubmit(onSubmit)} className="flex flex-col flex-1 min-h-0 overflow-hidden" > -
-
-
- -

- {t('profileEditor.providerChooserHint')} -

+
+
+
+
+ +

+ {t('profileEditor.providerChooserHint')} +

+
+ + {t('profileEditor.scrollHint')} +
-
- - - {t('profileEditor.scrollHint')} - -
+
-
+
{RECOMMENDED_PRESETS.map((preset) => ( handlePresetSelect(preset.id)} /> ))} +
+ handlePresetSelect(CUSTOM_PRESET_ID)} + />
- - handlePresetSelect(CUSTOM_PRESET_ID)} - /> -
+ - {showQuickTemplates && ( -
- + {showQuickTemplates && (
-
+
{QUICK_TEMPLATE_PRESETS.map((preset) => (
-
- )} + )} +
@@ -683,41 +706,37 @@ function CompactPresetCard({ type="button" onClick={onClick} className={cn( - 'flex min-h-[104px] w-[180px] flex-none flex-col items-start gap-3 rounded-xl border px-3 py-3 text-left transition-all', + 'flex h-[78px] w-[168px] flex-none items-start gap-3 rounded-xl border px-3 py-2.5 text-left transition-all', isSelected ? 'border-primary bg-primary/8 shadow-sm ring-1 ring-primary/10' : 'border-border/60 bg-background hover:border-primary/40 hover:bg-accent/20' )} > -
+
{isAnthropicDirect ? ( - + ) : preset.icon ? ( -
- -
+ ) : ( -
+
{preset.name.charAt(0)}
)} - {preset.badge && ( - - {preset.badge} - - )}
-
-
{preset.name}
-

+

+
+
{preset.name}
+ {preset.badge && ( + + {preset.badge} + + )} +
+

{preset.description}

@@ -733,18 +752,20 @@ function CustomPresetCard({ isSelected, onClick }: { isSelected: boolean; onClic type="button" onClick={onClick} className={cn( - 'flex w-full items-center gap-3 rounded-xl border border-dashed px-4 py-3 text-left transition-all sm:max-w-[280px]', + 'flex h-[78px] w-[188px] flex-none items-start gap-3 rounded-xl border border-dashed px-3 py-2.5 text-left transition-all', isSelected ? 'border-primary bg-primary/8 shadow-sm ring-1 ring-primary/10' : 'border-muted-foreground/30 bg-background hover:border-primary/40 hover:bg-accent/20' )} > -
+
-
-
{t('profileEditor.customEndpoint')}
-

+

+
+ {t('profileEditor.customEndpoint')} +
+

{t('profileEditor.customEndpointDescription')}

diff --git a/ui/tests/unit/components/profiles/profile-create-dialog.test.tsx b/ui/tests/unit/components/profiles/profile-create-dialog.test.tsx index 5ba4da8e..6633e771 100644 --- a/ui/tests/unit/components/profiles/profile-create-dialog.test.tsx +++ b/ui/tests/unit/components/profiles/profile-create-dialog.test.tsx @@ -23,7 +23,7 @@ describe('ProfileCreateDialog', () => { mutateAsync.mockReset(); }); - it('reveals more presets from custom mode and keeps custom unselected after choosing a template', async () => { + it('keeps advanced presets collapsed until explicitly opened and deselects custom after choosing a template', async () => { render( { ); expect(screen.getByText('Featured Providers')).toBeInTheDocument(); - expect(screen.queryByText('More Presets')).not.toBeInTheDocument(); + const morePresetsToggle = screen.getByRole('button', { name: /More Presets/i }); + expect(morePresetsToggle).toHaveAttribute('aria-expanded', 'false'); expect(document.body.querySelectorAll('.overflow-x-auto')).toHaveLength(1); const customButton = screen.getByRole('button', { name: /Custom Endpoint/i }); await userEvent.click(customButton); - expect(await screen.findByText('More Presets')).toBeInTheDocument(); + expect(morePresetsToggle).toHaveAttribute('aria-expanded', 'false'); + expect(document.body.querySelectorAll('.overflow-x-auto')).toHaveLength(1); + + await userEvent.click(morePresetsToggle); + + expect(morePresetsToggle).toHaveAttribute('aria-expanded', 'true'); expect(document.body.querySelectorAll('.overflow-x-auto')).toHaveLength(2); const glmButton = screen.getByText('GLM').closest('button'); @@ -50,6 +56,7 @@ describe('ProfileCreateDialog', () => { } await userEvent.click(glmButton); + expect(morePresetsToggle).toHaveAttribute('aria-expanded', 'true'); await waitFor(() => { expect(customButton).not.toHaveClass('border-primary'); });