diff --git a/components/model-config-dialog.tsx b/components/model-config-dialog.tsx index 99db30d..e94dcdb 100644 --- a/components/model-config-dialog.tsx +++ b/components/model-config-dialog.tsx @@ -162,6 +162,11 @@ export function ModelConfigDialog({ const [validatingModelIndex, setValidatingModelIndex] = useState< number | null >(null) + const [duplicateError, setDuplicateError] = useState("") + const [editError, setEditError] = useState<{ + modelId: string + message: string + } | null>(null) const { config, @@ -219,19 +224,6 @@ export function ModelConfigDialog({ (modelId) => !existingModelIds.includes(modelId), ) - // Detect duplicate models in current config - const modelIdCounts = - selectedProvider?.models.reduce( - (acc, m) => { - acc[m.modelId] = (acc[m.modelId] || 0) + 1 - return acc - }, - {} as Record, - ) || {} - const duplicateModelIds = Object.keys(modelIdCounts).filter( - (id) => modelIdCounts[id] > 1, - ) - // Handle adding a new provider const handleAddProvider = (providerType: ProviderName) => { const newProvider = addProvider(providerType) @@ -261,14 +253,17 @@ export function ModelConfigDialog({ } // Handle adding a model to current provider - const handleAddModel = (modelId: string) => { - if (!selectedProviderId || !selectedProvider) return + // Returns true if model was added successfully, false otherwise + const handleAddModel = (modelId: string): boolean => { + if (!selectedProviderId || !selectedProvider) return false // Prevent duplicate model IDs if (existingModelIds.includes(modelId)) { - toast.warning(`Model "${modelId}" already exists in this provider`) - return + setDuplicateError(`Model "${modelId}" already exists`) + return false } + setDuplicateError("") addModel(selectedProviderId, modelId) + return true } // Handle deleting a model @@ -997,30 +992,58 @@ export function ModelConfigDialog({ Models
- - setCustomModelInput( - e.target.value, - ) - } - onKeyDown={(e) => { - if ( - e.key === - "Enter" && - customModelInput.trim() - ) { - handleAddModel( - customModelInput.trim(), - ) - setCustomModelInput( - "", - ) +
+ + onChange={(e) => { + setCustomModelInput( + e.target + .value, + ) + // Clear duplicate error when typing + if ( + duplicateError + ) { + setDuplicateError( + "", + ) + } + }} + onKeyDown={(e) => { + if ( + e.key === + "Enter" && + customModelInput.trim() + ) { + const success = + handleAddModel( + customModelInput.trim(), + ) + if ( + success + ) { + setCustomModelInput( + "", + ) + } + } + }} + className={cn( + "h-8 w-48 font-mono text-xs", + duplicateError && + "border-destructive focus-visible:ring-destructive", + )} + /> + {/* Show duplicate error for custom model input */} + {duplicateError && ( +

+ {duplicateError} +

+ )} +
- {/* Duplicate Warning Banner */} - {duplicateModelIds.length > 0 && ( -
- - - { - duplicateModelIds.length - }{" "} - duplicate model - {duplicateModelIds.length > - 1 - ? "s" - : ""}{" "} - detected. Remove - duplicates to avoid - confusion. - -
- )} - {/* Model List */}
{selectedProvider.models @@ -1193,6 +1199,15 @@ export function ModelConfigDialog({ e, ) => { // Allow free typing - validation happens on blur + // Clear edit error when typing + if ( + editError?.modelId === + model.id + ) { + setEditError( + null, + ) + } updateModel( selectedProviderId!, model.id, @@ -1208,12 +1223,80 @@ export function ModelConfigDialog({ }, ) }} + onKeyDown={( + e, + ) => { + if ( + e.key === + "Enter" + ) { + e.currentTarget.blur() + } + }} onBlur={( e, ) => { const newModelId = e.target.value.trim() - // Check if new ID would be duplicate (excluding current model) + + // Helper to show error with shake + const showError = + ( + message: string, + ) => { + setEditError( + { + modelId: + model.id, + message, + }, + ) + e.target.animate( + [ + { + transform: + "translateX(0)", + }, + { + transform: + "translateX(-4px)", + }, + { + transform: + "translateX(4px)", + }, + { + transform: + "translateX(-4px)", + }, + { + transform: + "translateX(4px)", + }, + { + transform: + "translateX(0)", + }, + ], + { + duration: 400, + easing: "ease-in-out", + }, + ) + e.target.focus() + } + + // Check for empty model name + if ( + !newModelId + ) { + showError( + "Model ID cannot be empty", + ) + return + } + + // Check for duplicate const otherModelIds = selectedProvider?.models .filter( @@ -1231,15 +1314,20 @@ export function ModelConfigDialog({ ) || [] if ( - newModelId && otherModelIds.includes( newModelId, ) ) { - toast.warning( - `Model "${newModelId}" already exists. Please use a unique ID.`, + showError( + "This model ID already exists", ) + return } + + // Clear error on valid blur + setEditError( + null, + ) }} className="flex-1 min-w-0 font-mono text-sm h-8 border-0 bg-transparent focus-visible:bg-background focus-visible:ring-1" /> @@ -1261,34 +1349,20 @@ export function ModelConfigDialog({ {model.validated === false && model.validationError && ( -

+

{ model.validationError }

)} - {/* Show duplicate warning inline */} - {duplicateModelIds.includes( - model.modelId, - ) && ( -
- - - Duplicate - - -
+ {/* Show edit error inline */} + {editError?.modelId === + model.id && ( +

+ { + editError.message + } +

)}
),