diff --git a/ui/litellm-dashboard/src/components/guardrails.tsx b/ui/litellm-dashboard/src/components/guardrails.tsx index 26b9acb6a26..d0031b872f7 100644 --- a/ui/litellm-dashboard/src/components/guardrails.tsx +++ b/ui/litellm-dashboard/src/components/guardrails.tsx @@ -1,5 +1,7 @@ import React, { useState, useEffect } from "react"; import { Button, TabGroup, TabList, Tab, TabPanels, TabPanel } from "@tremor/react"; +import { Dropdown } from "antd"; +import { DownOutlined, PlusOutlined, CodeOutlined } from "@ant-design/icons"; import { getGuardrailsList, deleteGuardrailCall } from "./networking"; import AddGuardrailForm from "./guardrails/add_guardrail_form"; import GuardrailTable from "./guardrails/guardrail_table"; @@ -10,6 +12,7 @@ import NotificationsManager from "./molecules/notifications_manager"; import { Guardrail, GuardrailDefinitionLocation } from "./guardrails/types"; import DeleteResourceModal from "./common_components/DeleteResourceModal"; import { getGuardrailLogoAndName } from "./guardrails/guardrail_info_helpers"; +import { CustomCodeModal } from "./guardrails/custom_code"; interface GuardrailsPanelProps { accessToken: string | null; @@ -37,6 +40,7 @@ interface GuardrailsResponse { const GuardrailsPanel: React.FC = ({ accessToken, userRole }) => { const [guardrailsList, setGuardrailsList] = useState([]); const [isAddModalVisible, setIsAddModalVisible] = useState(false); + const [isCustomCodeModalVisible, setIsCustomCodeModalVisible] = useState(false); const [isLoading, setIsLoading] = useState(false); const [isDeleting, setIsDeleting] = useState(false); const [guardrailToDelete, setGuardrailToDelete] = useState(null); @@ -74,10 +78,21 @@ const GuardrailsPanel: React.FC = ({ accessToken, userRole setIsAddModalVisible(true); }; + const handleAddCustomCodeGuardrail = () => { + if (selectedGuardrailId) { + setSelectedGuardrailId(null); + } + setIsCustomCodeModalVisible(true); + }; + const handleCloseModal = () => { setIsAddModalVisible(false); }; + const handleCloseCustomCodeModal = () => { + setIsCustomCodeModalVisible(false); + }; + const handleSuccess = () => { fetchGuardrails(); }; @@ -128,9 +143,30 @@ const GuardrailsPanel: React.FC = ({ accessToken, userRole
- + , + label: "Add Provider Guardrail", + onClick: handleAddGuardrail, + }, + { + key: "custom_code", + icon: , + label: "Create Custom Code Guardrail", + onClick: handleAddCustomCodeGuardrail, + }, + ], + }} + trigger={["click"]} + disabled={!accessToken} + > + +
{selectedGuardrailId ? ( @@ -159,6 +195,13 @@ const GuardrailsPanel: React.FC = ({ accessToken, userRole onSuccess={handleSuccess} /> + + void; + height?: string; + placeholder?: string; + disabled?: boolean; +} + +const CustomCodeEditor: React.FC = ({ + value, + onChange, + height = "350px", + placeholder = `def apply_guardrail(inputs, request_data, input_type): + # inputs: contains texts, images, tools, tool_calls, structured_messages, model + # request_data: contains model, user_id, team_id, end_user_id, metadata + # input_type: "request" or "response" + + for text in inputs["texts"]: + # Example: Block if SSN pattern is detected + if regex_match(text, r"\\d{3}-\\d{2}-\\d{4}"): + return block("SSN detected in message") + + return allow()`, + disabled = false, +}) => { + const textareaRef = useRef(null); + const [activeTab, setActiveTab] = useState("edit"); + const [cursorPosition, setCursorPosition] = useState({ line: 1, column: 1 }); + + // Calculate cursor position + const updateCursorPosition = () => { + if (textareaRef.current) { + const textarea = textareaRef.current; + const textBeforeCursor = value.substring(0, textarea.selectionStart); + const lines = textBeforeCursor.split("\n"); + const line = lines.length; + const column = lines[lines.length - 1].length + 1; + setCursorPosition({ line, column }); + } + }; + + // Handle tab key for indentation + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "Tab") { + e.preventDefault(); + const textarea = e.currentTarget; + const start = textarea.selectionStart; + const end = textarea.selectionEnd; + + // Insert 4 spaces at cursor position + const newValue = value.substring(0, start) + " " + value.substring(end); + onChange(newValue); + + // Move cursor after the inserted spaces + setTimeout(() => { + textarea.selectionStart = textarea.selectionEnd = start + 4; + }, 0); + } + }; + + const lineCount = value.split("\n").length; + + const tabItems = [ + { + key: "edit", + label: ( + + + Edit + + ), + children: ( +
+ {/* Line numbers */} +
+ {Array.from({ length: Math.max(lineCount, 15) }, (_, i) => ( +
+ {i + 1} +
+ ))} +
+ + {/* Code editor */} +