import { Button } from "@/components/ui/button"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; import { Label } from "@/components/ui/label"; import { ModelMultiselect } from "@/components/ui/modelMultiselect"; import NumberAndSelect from "@/components/ui/numberAndSelect"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { DottedSeparator } from "@/components/ui/separator"; import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "@/components/ui/sheet"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import { resetDurationOptions } from "@/lib/constants/governance"; import { RenderProviderIcon } from "@/lib/constants/icons"; import { ProviderLabels, ProviderName } from "@/lib/constants/logs"; import { getErrorMessage, useCreateModelConfigMutation, useGetProvidersQuery, useLazyGetModelsQuery, useUpdateModelConfigMutation, } from "@/lib/store"; import { KnownProvider } from "@/lib/types/config"; import { ModelConfig } from "@/lib/types/governance"; import { RbacOperation, RbacResource, useRbac } from "@enterprise/lib"; import { zodResolver } from "@hookform/resolvers/zod"; import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; interface ModelLimitSheetProps { modelConfig?: ModelConfig | null; onSave: () => void; onCancel: () => void; } const formSchema = z.object({ modelName: z.string().min(1, "Model name is required"), provider: z.string().optional(), budgetMaxLimit: z.number().nonnegative().optional(), budgetResetDuration: z.string().optional(), tokenMaxLimit: z.number().int().nonnegative().optional(), tokenResetDuration: z.string().optional(), requestMaxLimit: z.number().int().nonnegative().optional(), requestResetDuration: z.string().optional(), }); type FormData = z.infer; export default function ModelLimitSheet({ modelConfig, onSave, onCancel }: ModelLimitSheetProps) { const [isOpen, setIsOpen] = useState(true); const isEditing = !!modelConfig; const hasCreateAccess = useRbac(RbacResource.Governance, RbacOperation.Create); const hasUpdateAccess = useRbac(RbacResource.Governance, RbacOperation.Update); const canSubmit = isEditing ? hasUpdateAccess : hasCreateAccess; const handleClose = () => { setIsOpen(false); setTimeout(() => { onCancel(); }, 150); }; const { data: providersData } = useGetProvidersQuery(); const [createModelConfig, { isLoading: isCreating }] = useCreateModelConfigMutation(); const [updateModelConfig, { isLoading: isUpdating }] = useUpdateModelConfigMutation(); const [getModels] = useLazyGetModelsQuery(); const isLoading = isCreating || isUpdating; const availableProviders = providersData || []; // Handle provider change - clear model if it doesn't exist for the new provider const handleProviderChange = async (newProvider: string, currentModel: string, onChange: (value: string) => void) => { onChange(newProvider); if (!currentModel) return; try { const response = await getModels({ provider: newProvider || undefined, query: currentModel, limit: 50, }).unwrap(); const modelExists = response.models.some((model) => model.name === currentModel); if (!modelExists) { form.setValue("modelName", "", { shouldDirty: true }); } } catch { // On error, don't clear the model } }; const form = useForm({ mode: "onChange", resolver: zodResolver(formSchema), defaultValues: { modelName: modelConfig?.model_name || "", provider: modelConfig?.provider || "", budgetMaxLimit: modelConfig?.budget?.max_limit ?? undefined, budgetResetDuration: modelConfig?.budget?.reset_duration || "1M", tokenMaxLimit: modelConfig?.rate_limit?.token_max_limit ?? undefined, tokenResetDuration: modelConfig?.rate_limit?.token_reset_duration || "1h", requestMaxLimit: modelConfig?.rate_limit?.request_max_limit ?? undefined, requestResetDuration: modelConfig?.rate_limit?.request_reset_duration || "1h", }, }); const hasAnyLimit = (form.watch("budgetMaxLimit") !== undefined && form.watch("budgetMaxLimit") !== null) || (form.watch("tokenMaxLimit") !== undefined && form.watch("tokenMaxLimit") !== null) || (form.watch("requestMaxLimit") !== undefined && form.watch("requestMaxLimit") !== null); useEffect(() => { if (modelConfig) { // Never reset form if user is editing - skip reset entirely if (form.formState.isDirty) { return; } form.reset({ modelName: modelConfig.model_name || "", provider: modelConfig.provider || "", budgetMaxLimit: modelConfig.budget?.max_limit ?? undefined, budgetResetDuration: modelConfig.budget?.reset_duration || "1M", tokenMaxLimit: modelConfig.rate_limit?.token_max_limit ?? undefined, tokenResetDuration: modelConfig.rate_limit?.token_reset_duration || "1h", requestMaxLimit: modelConfig.rate_limit?.request_max_limit ?? undefined, requestResetDuration: modelConfig.rate_limit?.request_reset_duration || "1h", }); } }, [modelConfig, form]); const onSubmit = async (data: FormData) => { if (!canSubmit) { toast.error("You don't have permission to perform this action"); return; } try { const provider = data.provider && data.provider.trim() !== "" ? data.provider : undefined; if (isEditing && modelConfig) { const hadBudget = !!modelConfig.budget; const hasBudget = data.budgetMaxLimit !== undefined && data.budgetMaxLimit !== null; const hadRateLimit = !!modelConfig.rate_limit; const hasRateLimit = (data.tokenMaxLimit !== undefined && data.tokenMaxLimit !== null) || (data.requestMaxLimit !== undefined && data.requestMaxLimit !== null); let budgetPayload: { max_limit?: number; reset_duration?: string } | undefined; if (hasBudget) { budgetPayload = { max_limit: data.budgetMaxLimit, reset_duration: data.budgetResetDuration || "1M", }; } else if (hadBudget) { budgetPayload = {}; } let rateLimitPayload: | { token_max_limit?: number | null; token_reset_duration?: string | null; request_max_limit?: number | null; request_reset_duration?: string | null; } | undefined; if (hasRateLimit) { rateLimitPayload = { token_max_limit: data.tokenMaxLimit ?? null, token_reset_duration: data.tokenMaxLimit !== undefined && data.tokenMaxLimit !== null ? data.tokenResetDuration || "1h" : null, request_max_limit: data.requestMaxLimit ?? null, request_reset_duration: data.requestMaxLimit !== undefined && data.requestMaxLimit !== null ? data.requestResetDuration || "1h" : null, }; } else if (hadRateLimit) { rateLimitPayload = {}; } await updateModelConfig({ id: modelConfig.id, data: { model_name: data.modelName, provider: provider, budget: budgetPayload, rate_limit: rateLimitPayload, }, }).unwrap(); toast.success("Model limit updated successfully"); } else { await createModelConfig({ model_name: data.modelName, provider, budget: data.budgetMaxLimit !== undefined && data.budgetMaxLimit !== null ? { max_limit: data.budgetMaxLimit, reset_duration: data.budgetResetDuration || "1M", } : undefined, rate_limit: (data.tokenMaxLimit !== undefined && data.tokenMaxLimit !== null) || (data.requestMaxLimit !== undefined && data.requestMaxLimit !== null) ? { token_max_limit: data.tokenMaxLimit, token_reset_duration: data.tokenMaxLimit !== undefined && data.tokenMaxLimit !== null ? data.tokenResetDuration || "1h" : undefined, request_max_limit: data.requestMaxLimit, request_reset_duration: data.requestMaxLimit !== undefined && data.requestMaxLimit !== null ? data.requestResetDuration || "1h" : undefined, } : undefined, }).unwrap(); toast.success("Model limit created successfully"); } onSave(); } catch (error) { toast.error(getErrorMessage(error)); } }; return ( !open && handleClose()}> { if (isEditing ? form.formState.isDirty : !!form.watch("modelName") || hasAnyLimit) e.preventDefault(); }} onEscapeKeyDown={(e) => { if (isEditing ? form.formState.isDirty : !!form.watch("modelName") || hasAnyLimit) e.preventDefault(); }} data-testid="model-limit-sheet" > {isEditing ? "Edit Model Limit" : "Create Model Limit"} {isEditing ? "Update budget and rate limit configuration." : "Set up budget and rate limits for a model."}
{/* Provider */} ( Provider )} /> {/* Model Name */} ( Model Name
)} /> {/* Budget Configuration */}
( field.onChange(value)} onChangeSelect={(value) => form.setValue("budgetResetDuration", value, { shouldDirty: true })} options={resetDurationOptions} /> )} />
{/* Rate Limiting Configuration */}
( field.onChange(value)} onChangeSelect={(value) => form.setValue("tokenResetDuration", value, { shouldDirty: true })} options={resetDurationOptions} /> )} /> ( field.onChange(value)} onChangeSelect={(value) => form.setValue("requestResetDuration", value, { shouldDirty: true })} options={resetDurationOptions} /> )} />
{/* Current Usage Display (for editing) */} {isEditing && (modelConfig?.budget || modelConfig?.rate_limit) && ( <>
{modelConfig?.budget && (

Budget

${modelConfig.budget.current_usage.toFixed(2)} / ${modelConfig.budget.max_limit.toFixed(2)}

)} {modelConfig?.rate_limit?.token_max_limit && (

Tokens

{modelConfig.rate_limit.token_current_usage.toLocaleString()} /{" "} {modelConfig.rate_limit.token_max_limit.toLocaleString()}

)} {modelConfig?.rate_limit?.request_max_limit && (

Requests

{modelConfig.rate_limit.request_current_usage.toLocaleString()} /{" "} {modelConfig.rate_limit.request_max_limit.toLocaleString()}

)}
)}
{/* Footer */}
{(isLoading || !form.formState.isDirty || !form.formState.isValid || !canSubmit || !form.watch("modelName") || !hasAnyLimit) && (

{!canSubmit ? "You don't have permission" : isLoading ? "Saving..." : !form.formState.isDirty ? "No changes made" : !form.watch("modelName") ? "Model name is required" : !hasAnyLimit ? "At least one budget or rate limit is required" : "Please fix validation errors"}

)}
); }