|
6 | 6 | InfoIcon, |
7 | 7 | PencilIcon, |
8 | 8 | } from "lucide-react"; |
9 | | -import { type FC, useEffect, useState } from "react"; |
| 9 | +import { type FC, useEffect, useMemo, useState } from "react"; |
10 | 10 | import * as Yup from "yup"; |
11 | 11 | import type * as TypesGen from "#/api/typesGenerated"; |
12 | 12 | import { Button } from "#/components/Button/Button"; |
@@ -62,13 +62,16 @@ const getInitialSelectedConfigId = ( |
62 | 62 | editingModel: TypesGen.ChatModelConfig | undefined, |
63 | 63 | selectedProviderConfigs: readonly TypesGen.ChatProviderConfig[], |
64 | 64 | ): string => { |
65 | | - if (editingModel?.provider_config_id) { |
66 | | - const boundConfig = selectedProviderConfigs.find( |
67 | | - (config) => config.id === editingModel.provider_config_id, |
68 | | - ); |
69 | | - if (boundConfig) { |
70 | | - return boundConfig.id; |
| 65 | + if (editingModel) { |
| 66 | + if (editingModel.provider_config_id) { |
| 67 | + const boundConfig = selectedProviderConfigs.find( |
| 68 | + (config) => config.id === editingModel.provider_config_id, |
| 69 | + ); |
| 70 | + if (boundConfig) { |
| 71 | + return boundConfig.id; |
| 72 | + } |
71 | 73 | } |
| 74 | + return ""; |
72 | 75 | } |
73 | 76 | if (selectedProviderConfigs.length === 1) { |
74 | 77 | return selectedProviderConfigs[0].id; |
@@ -141,14 +144,36 @@ export const ModelForm: FC<ModelFormProps> = ({ |
141 | 144 | const [showProviderConfig, setShowProviderConfig] = useState(false); |
142 | 145 | const [confirmingDelete, setConfirmingDelete] = useState(false); |
143 | 146 | const selectedProviderConfigs = selectedProviderState?.providerConfigs ?? []; |
| 147 | + const configSignature = useMemo( |
| 148 | + () => |
| 149 | + selectedProviderConfigs |
| 150 | + .map((config) => config.id) |
| 151 | + .sort() |
| 152 | + .join(","), |
| 153 | + [selectedProviderConfigs], |
| 154 | + ); |
| 155 | + const selectableConfigs = useMemo(() => { |
| 156 | + return selectedProviderConfigs.filter( |
| 157 | + (config) => |
| 158 | + config.enabled || editingModel?.provider_config_id === config.id, |
| 159 | + ); |
| 160 | + }, [selectedProviderConfigs, editingModel?.provider_config_id]); |
144 | 161 | const [selectedConfigId, setSelectedConfigId] = useState<string>(() => |
145 | | - getInitialSelectedConfigId(editingModel, selectedProviderConfigs), |
| 162 | + getInitialSelectedConfigId(editingModel, selectableConfigs), |
146 | 163 | ); |
| 164 | + // Biome cannot infer that configSignature is the intended stable proxy |
| 165 | + // for selectedProviderConfigs, so suppress exhaustive-deps here. |
| 166 | + // biome-ignore lint/correctness/useExhaustiveDependencies: configSignature intentionally gates resets on config ID changes. |
147 | 167 | useEffect(() => { |
148 | | - setSelectedConfigId( |
149 | | - getInitialSelectedConfigId(editingModel, selectedProviderConfigs), |
150 | | - ); |
151 | | - }, [editingModel, selectedProviderConfigs]); |
| 168 | + const currentStillValid = |
| 169 | + selectedConfigId !== "" && |
| 170 | + selectedProviderConfigs.some((config) => config.id === selectedConfigId); |
| 171 | + if (!currentStillValid) { |
| 172 | + setSelectedConfigId( |
| 173 | + getInitialSelectedConfigId(editingModel, selectableConfigs), |
| 174 | + ); |
| 175 | + } |
| 176 | + }, [editingModel, configSignature]); |
152 | 177 | const canManageModels = Boolean( |
153 | 178 | selectedProviderState && |
154 | 179 | selectedProviderConfigs.length > 0 && |
@@ -226,7 +251,7 @@ export const ModelForm: FC<ModelFormProps> = ({ |
226 | 251 | if (!selectedProviderState || selectedProviderConfigs.length === 0) { |
227 | 252 | return; |
228 | 253 | } |
229 | | - if (selectedProviderConfigs.length > 1 && !selectedConfigId) { |
| 254 | + if (selectableConfigs.length > 1 && !selectedConfigId) { |
230 | 255 | return; |
231 | 256 | } |
232 | 257 |
|
@@ -275,7 +300,7 @@ export const ModelForm: FC<ModelFormProps> = ({ |
275 | 300 | const hasFieldErrors = |
276 | 301 | Object.keys(modelConfigFormBuildResult.fieldErrors).length > 0; |
277 | 302 | const requiresConfigSelection = |
278 | | - !isEditing && selectedProviderConfigs.length > 1 && !selectedConfigId; |
| 303 | + !isEditing && selectableConfigs.length > 1 && !selectedConfigId; |
279 | 304 | const defaultModelDisableGuard = isDefaultModel && form.values.enabled; |
280 | 305 |
|
281 | 306 | // ── Provider select (shared across all form states) ─────── |
@@ -340,7 +365,7 @@ export const ModelForm: FC<ModelFormProps> = ({ |
340 | 365 | No configuration |
341 | 366 | </SelectItem> |
342 | 367 | )} |
343 | | - {selectedProviderConfigs.map((config) => ( |
| 368 | + {selectableConfigs.map((config) => ( |
344 | 369 | <SelectItem key={config.id} value={config.id}> |
345 | 370 | {config.display_name || |
346 | 371 | config.base_url || |
|
0 commit comments