From 11a07adee7a08862be0783017959e82ebccd3955 Mon Sep 17 00:00:00 2001 From: Antigravity Date: Thu, 28 May 2026 21:41:34 +0000 Subject: [PATCH] feat(byok): add model selection to BYOK settings panel and overlay custom model on route resolution --- .../components/ai/byok-settings-panel.tsx | 110 +++++++++++++++++- memento-note/lib/ai/provider-for-user.ts | 10 +- memento-note/lib/byok.ts | 11 +- 3 files changed, 119 insertions(+), 12 deletions(-) diff --git a/memento-note/components/ai/byok-settings-panel.tsx b/memento-note/components/ai/byok-settings-panel.tsx index 4adb96d..7be87ea 100644 --- a/memento-note/components/ai/byok-settings-panel.tsx +++ b/memento-note/components/ai/byok-settings-panel.tsx @@ -42,12 +42,40 @@ function providerLabel(t: (key: string) => string, provider: string): string { return translated === key ? provider : translated } +const PROVIDER_MODEL_SUGGESTIONS: Record = { + openai: ['gpt-4o-mini', 'gpt-4o', 'gpt-3.5-turbo'], + anthropic: ['claude-3-5-sonnet-latest', 'claude-3-5-haiku-latest', 'claude-3-opus-latest'], + google: ['gemini-1.5-flash', 'gemini-1.5-pro', 'gemini-2.0-flash-exp'], + deepseek: ['deepseek-chat', 'deepseek-coder'], + minimax: ['abab6.5-chat', 'abab6.5s-chat'], + mistral: ['mistral-small-latest', 'mistral-medium-latest', 'mistral-large-latest'], + glm: ['glm-4', 'glm-4-flash'], + openrouter: ['openai/gpt-4o-mini', 'anthropic/claude-3.5-sonnet', 'deepseek/deepseek-chat'], + custom: [], +} + export function ByokSettingsPanel() { const { t } = useLanguage() const queryClient = useQueryClient() const [provider, setProvider] = useState('') const [apiKey, setApiKey] = useState('') const [alias, setAlias] = useState('') + const [model, setModel] = useState('') + const [customModel, setCustomModel] = useState('') + const [isCustomModel, setIsCustomModel] = useState(false) + + const handleProviderChange = (p: string) => { + setProvider(p) + const sug = PROVIDER_MODEL_SUGGESTIONS[p] || [] + if (sug.length > 0) { + setModel(sug[0]) + setIsCustomModel(false) + } else { + setModel('') + setCustomModel('') + setIsCustomModel(true) + } + } const { data, isLoading, error } = useQuery({ queryKey: ['user', 'api-keys'], @@ -61,10 +89,16 @@ export function ByokSettingsPanel() { const saveMutation = useMutation({ mutationFn: async () => { + const resolvedModel = isCustomModel ? customModel : model const res = await fetch('/api/user/api-keys', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ provider, apiKey, alias: alias || undefined }), + body: JSON.stringify({ + provider, + apiKey, + alias: alias || undefined, + model: resolvedModel || undefined, + }), }) const body = await res.json().catch(() => ({})) if (!res.ok) { @@ -77,6 +111,9 @@ export function ByokSettingsPanel() { setApiKey('') setAlias('') setProvider('') + setModel('') + setCustomModel('') + setIsCustomModel(false) invalidate() }, onError: (err: Error) => { @@ -171,7 +208,7 @@ export function ByokSettingsPanel() { { + if (val === 'custom') { + setIsCustomModel(true) + } else { + setIsCustomModel(false) + setModel(val) + } + }} + disabled={saveMutation.isPending} + > + + + + + {PROVIDER_MODEL_SUGGESTIONS[provider].map((m) => ( + + {m} + + ))} + Autre / Modèle personnalisé... + + + ) : ( +
+ Spécifiez le modèle ci-contre si besoin. +
+ )} + + + {(isCustomModel || !(PROVIDER_MODEL_SUGGESTIONS[provider] && PROVIDER_MODEL_SUGGESTIONS[provider].length > 0)) && ( +
+ + { + if (isCustomModel) { + setCustomModel(e.target.value) + } else { + setModel(e.target.value) + } + }} + placeholder="ex. deepseek-reasoner, minimax-abab6.5" + disabled={saveMutation.isPending} + /> +
+ )} + + )}
{providerLabel(t, key.provider)}
- {key.alias ? ( -

{key.alias}

- ) : null} +
+ {key.alias ? ( +

{key.alias}

+ ) : null} + {key.model ? ( +

Modèle : {key.model}

+ ) : null} +