From 9290fd1212f3156b1f2200983e78a3c8a1085a91 Mon Sep 17 00:00:00 2001 From: Jiayuan Zhang Date: Tue, 10 Feb 2026 22:52:00 +0800 Subject: [PATCH] feat(desktop): add searchable model dropdown for OpenRouter Replace plain text input with Combobox for model selection. Users can search and select from the provider's model list via dropdown. Co-Authored-By: Claude Opus 4.6 --- .../src/components/api-key-dialog.tsx | 56 +++++++++++-------- .../renderer/src/pages/onboarding/setup.tsx | 1 + 2 files changed, 35 insertions(+), 22 deletions(-) diff --git a/apps/desktop/src/renderer/src/components/api-key-dialog.tsx b/apps/desktop/src/renderer/src/components/api-key-dialog.tsx index a0e92af4..165b956a 100644 --- a/apps/desktop/src/renderer/src/components/api-key-dialog.tsx +++ b/apps/desktop/src/renderer/src/components/api-key-dialog.tsx @@ -10,6 +10,14 @@ import { import { Button } from '@multica/ui/components/ui/button' import { Input } from '@multica/ui/components/ui/input' import { Label } from '@multica/ui/components/ui/label' +import { + Combobox, + ComboboxInput, + ComboboxContent, + ComboboxList, + ComboboxItem, + ComboboxEmpty, +} from '@multica/ui/components/ui/combobox' import { HugeiconsIcon } from '@hugeicons/react' import { Loading03Icon, Key01Icon } from '@hugeicons/core-free-icons' @@ -19,6 +27,7 @@ interface ApiKeyDialogProps { providerId: string providerName: string showModelInput?: boolean + models?: string[] onSuccess?: (modelId?: string) => void } @@ -28,10 +37,11 @@ export function ApiKeyDialog({ providerId, providerName, showModelInput, + models, onSuccess, }: ApiKeyDialogProps) { const [apiKey, setApiKey] = useState('') - const [modelId, setModelId] = useState('') + const [modelId, setModelId] = useState(null) const [saving, setSaving] = useState(false) const [error, setError] = useState(null) @@ -41,8 +51,8 @@ export function ApiKeyDialog({ return } - if (showModelInput && !modelId.trim()) { - setError('Model name is required') + if (showModelInput && !modelId) { + setError('Please select a model') return } @@ -53,9 +63,9 @@ export function ApiKeyDialog({ const result = await window.electronAPI.provider.saveApiKey(providerId, apiKey.trim()) if (result.ok) { setApiKey('') - setModelId('') + setModelId(null) onOpenChange(false) - onSuccess?.(showModelInput ? modelId.trim() : undefined) + onSuccess?.(showModelInput && modelId ? modelId : undefined) } else { setError(result.error ?? 'Failed to save API key') } @@ -70,7 +80,7 @@ export function ApiKeyDialog({ const handleClose = (isOpen: boolean) => { if (!isOpen) { setApiKey('') - setModelId('') + setModelId(null) setError(null) } onOpenChange(isOpen) @@ -106,23 +116,25 @@ export function ApiKeyDialog({ /> - {showModelInput && ( + {showModelInput && models && models.length > 0 && (
- - Model + setModelId(e.target.value)} - placeholder="anthropic/claude-sonnet-4" - onKeyDown={(e) => { - if (e.key === 'Enter' && !saving) { - handleSave() - } - }} - /> -

- Enter the model identifier from your provider (e.g. anthropic/claude-sonnet-4) -

+ onValueChange={(value) => setModelId(value)} + > + + + + {models.map((model) => ( + + {model} + + ))} + + No models found + +
)} @@ -139,7 +151,7 @@ export function ApiKeyDialog({ - diff --git a/apps/desktop/src/renderer/src/pages/onboarding/setup.tsx b/apps/desktop/src/renderer/src/pages/onboarding/setup.tsx index b8391f51..9b48b1a0 100644 --- a/apps/desktop/src/renderer/src/pages/onboarding/setup.tsx +++ b/apps/desktop/src/renderer/src/pages/onboarding/setup.tsx @@ -118,6 +118,7 @@ export default function SetupStep() { providerId={selectedProvider.id} providerName={selectedProvider.name} showModelInput={selectedProvider.id === 'openrouter'} + models={selectedProvider.models} onSuccess={handleProviderSuccess} /> )}