fix: fix styling

This commit is contained in:
Krrish Dholakia 2026-02-05 10:01:29 -08:00 • committed by Shin
parent 4e62386c65
commit a60122265b

View file

@ -1,5 +1,5 @@
import React, { useState, useRef, useEffect } from "react";
import { Modal, Select, Switch, Collapse, Input, Divider } from "antd";
import { Modal, Select, Switch, Collapse, Input, Spin } from "antd";
import { Button, TextInput } from "@tremor/react";
import {
CodeOutlined,
@ -8,10 +8,8 @@ import {
CloseCircleOutlined,
CaretRightOutlined,
SaveOutlined,
UsergroupAddOutlined,
ExportOutlined,
} from "@ant-design/icons";
import { createGuardrailCall, updateGuardrailCall, testCustomCodeGuardrail } from "../../networking";
import { createGuardrailCall, testCustomCodeGuardrail } from "../../networking";
import NotificationsManager from "../../molecules/notifications_manager";
const { Panel } = Collapse;
@ -21,7 +19,7 @@ const { TextArea } = Input;
const CODE_TEMPLATES = {
empty: {
name: "Empty Template",
code: `async def apply_guardrail(inputs, request_data, input_type):
code: `def apply_guardrail(inputs, request_data, input_type):
# inputs: {texts, images, tools, tool_calls, structured_messages, model}
# request_data: {model, user_id, team_id, end_user_id, metadata}
# input_type: "request" or "response"
@ -70,30 +68,8 @@ const CODE_TEMPLATES = {
return block("Response missing required fields")
return allow()`,
},
externalAPI: {
name: "External API Check (async)",
code: `async def apply_guardrail(inputs, request_data, input_type):
# Call an external moderation API (async for non-blocking)
for text in inputs["texts"]:
response = await http_post(
"https://api.example.com/moderate",
body={"text": text, "user_id": request_data["user_id"]},
headers={"Authorization": "Bearer YOUR_API_KEY"},
timeout=10
)
if not response["success"]:
# API call failed, allow by default or block
return allow()
if response["body"].get("flagged"):
return block(response["body"].get("reason", "Content flagged"))
return allow()`,
},
};
// Available primitives organized by category
const PRIMITIVES = {
"Return Values": [
@ -101,11 +77,6 @@ const PRIMITIVES = {
{ name: "block(reason)", desc: "Reject with message" },
{ name: "modify(texts=[], images=[], tool_calls=[])", desc: "Transform content" },
],
"HTTP Requests (async)": [
{ name: "await http_request(url, method, headers, body)", desc: "Make async HTTP request" },
{ name: "await http_get(url, headers)", desc: "Async GET request" },
{ name: "await http_post(url, body, headers)", desc: "Async POST request" },
],
"Regex Functions": [
{ name: "regex_match(text, pattern)", desc: "Returns True if pattern found" },
{ name: "regex_replace(text, pattern, replacement)", desc: "Replace all matches" },
@ -140,30 +111,13 @@ const MODE_OPTIONS = [
{ value: "post_call", label: "post_call (Response)" },
{ value: "during_call", label: "during_call (Parallel)" },
{ value: "logging_only", label: "logging_only" },
{ value: "pre_mcp_call", label: "pre_mcp_call (Before MCP Tool Call)" },
{ value: "post_mcp_call", label: "post_mcp_call (After MCP Tool Call)" },
{ value: "during_mcp_call", label: "during_mcp_call (During MCP Tool Call)" },
];
// Data for editing an existing guardrail
export interface EditGuardrailData {
guardrail_id: string;
guardrail_name: string;
litellm_params: {
mode?: string | string[];
default_on?: boolean;
custom_code?: string;
[key: string]: any;
};
}
interface CustomCodeModalProps {
visible: boolean;
onClose: () => void;
onSuccess: () => void;
accessToken: string | null;
/** If provided, the modal will be in edit mode */
editData?: EditGuardrailData | null;
}
const CustomCodeModal: React.FC<CustomCodeModalProps> = ({
@ -171,72 +125,16 @@ const CustomCodeModal: React.FC<CustomCodeModalProps> = ({
onClose,
onSuccess,
accessToken,
editData,
}) => {
const isEditMode = !!editData;
const [guardrailName, setGuardrailName] = useState("");
const [mode, setMode] = useState<string[]>(["pre_call"]);
const [mode, setMode] = useState<string>("pre_call");
const [defaultOn, setDefaultOn] = useState(false);
const [selectedTemplate, setSelectedTemplate] = useState<string>("empty");
const [code, setCode] = useState(CODE_TEMPLATES.empty.code);
const [isSaving, setIsSaving] = useState(false);
const [isTesting, setIsTesting] = useState(false);
const [testExpanded, setTestExpanded] = useState(false);
// Test input examples for pre_call and post_call
const TEST_INPUT_EXAMPLES = {
pre_call: {
name: "Pre-call (Request)",
data: {
texts: ["Hello, my SSN is 123-45-6789"],
images: [],
tools: [
{
type: "function",
function: {
name: "get_weather",
description: "Get the current weather in a location",
parameters: {
type: "object",
properties: {
location: { type: "string", description: "City name" }
},
required: ["location"]
}
}
}
],
tool_calls: [],
structured_messages: [
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content: "Hello, my SSN is 123-45-6789" }
],
model: "gpt-4"
}
},
post_call: {
name: "Post-call (Response)",
data: {
texts: ["The weather in San Francisco is 72°F and sunny."],
images: [],
tools: [],
tool_calls: [
{
id: "call_abc123",
type: "function",
function: {
name: "get_weather",
arguments: "{\"location\": \"San Francisco\"}"
}
}
],
structured_messages: [],
model: "gpt-4"
}
}
};
const [testInput, setTestInput] = useState(JSON.stringify(TEST_INPUT_EXAMPLES.pre_call.data, null, 2));
const [testInput, setTestInput] = useState('{"texts": ["Hello, my SSN is 123-45-6789"], "images": [], "tools": [], "tool_calls": [], "structured_messages": [], "model": "gpt-4"}');
const [testResult, setTestResult] = useState<any>(null);
const [copiedPrimitive, setCopiedPrimitive] = useState<string | null>(null);
const textareaRef = useRef<HTMLTextAreaElement>(null);
@ -244,40 +142,21 @@ const CustomCodeModal: React.FC<CustomCodeModalProps> = ({
// Handle template change
const handleTemplateChange = (templateKey: string) => {
setSelectedTemplate(templateKey);
// Check if it's a standard template
setCode(CODE_TEMPLATES[templateKey as keyof typeof CODE_TEMPLATES].code);
};
// Normalize mode from API (string or string[]) to string[]
const normalizeMode = (m: string | string[] | undefined): string[] => {
if (m === undefined || m === null) return ["pre_call"];
if (Array.isArray(m)) return m.length ? m : ["pre_call"];
return [m];
};
// Reset form when modal opens or editData changes
// Reset form when modal opens
useEffect(() => {
if (visible) {
if (editData) {
// Edit mode: populate with existing data
setGuardrailName(editData.guardrail_name || "");
setMode(normalizeMode(editData.litellm_params?.mode));
setDefaultOn(editData.litellm_params?.default_on || false);
setCode(editData.litellm_params?.custom_code || CODE_TEMPLATES.empty.code);
setSelectedTemplate(""); // No template selected in edit mode
} else {
// Create mode: reset to defaults
setGuardrailName("");
setMode(["pre_call"]);
setDefaultOn(false);
setSelectedTemplate("empty");
setCode(CODE_TEMPLATES.empty.code);
}
setGuardrailName("");
setMode("pre_call");
setDefaultOn(false);
setSelectedTemplate("empty");
setCode(CODE_TEMPLATES.empty.code);
setTestResult(null);
setTestExpanded(false);
}
}, [visible, editData]);
}, [visible]);
// Copy primitive to clipboard
const copyPrimitive = async (primitive: string) => {
@ -305,7 +184,7 @@ const CustomCodeModal: React.FC<CustomCodeModalProps> = ({
}
};
// Save guardrail (create or update)
// Save guardrail
const handleSave = async () => {
if (!guardrailName.trim()) {
NotificationsManager.fromBackend("Please enter a guardrail name");
@ -322,53 +201,25 @@ const CustomCodeModal: React.FC<CustomCodeModalProps> = ({
setIsSaving(true);
try {
if (isEditMode && editData) {
// Update existing guardrail
const updateData: any = {
litellm_params: {
custom_code: code,
},
};
const guardrailData = {
guardrail_name: guardrailName,
litellm_params: {
guardrail: "custom_code",
mode: mode,
default_on: defaultOn,
custom_code: code,
},
guardrail_info: {},
};
// Only include changed fields
if (guardrailName !== editData.guardrail_name) {
updateData.guardrail_name = guardrailName;
}
const existingMode = normalizeMode(editData.litellm_params?.mode);
const modeChanged =
mode.length !== existingMode.length ||
mode.some((m, i) => m !== existingMode[i]);
if (modeChanged) {
updateData.litellm_params.mode = mode;
}
if (defaultOn !== editData.litellm_params?.default_on) {
updateData.litellm_params.default_on = defaultOn;
}
await updateGuardrailCall(accessToken, editData.guardrail_id, updateData);
NotificationsManager.success("Custom code guardrail updated successfully");
} else {
// Create new guardrail
const guardrailData = {
guardrail_name: guardrailName,
litellm_params: {
guardrail: "custom_code",
mode: mode,
default_on: defaultOn,
custom_code: code,
},
guardrail_info: {},
};
await createGuardrailCall(accessToken, guardrailData);
NotificationsManager.success("Custom code guardrail created successfully");
}
await createGuardrailCall(accessToken, guardrailData);
NotificationsManager.success("Custom code guardrail created successfully");
onSuccess();
onClose();
} catch (error) {
console.error("Failed to save guardrail:", error);
console.error("Failed to create guardrail:", error);
NotificationsManager.fromBackend(
`Failed to ${isEditMode ? "update" : "create"} guardrail: ` + (error instanceof Error ? error.message : String(error))
"Failed to create guardrail: " + (error instanceof Error ? error.message : String(error))
);
} finally {
setIsSaving(false);
@ -401,20 +252,10 @@ const CustomCodeModal: React.FC<CustomCodeModalProps> = ({
parsedInput.texts = [];
}
// Use first request-like or response-like mode for test input_type
const requestModes = ["pre_call", "pre_mcp_call"];
const responseModes = ["post_call", "post_mcp_call"];
const testInputType: "request" | "response" =
mode.some((m) => requestModes.includes(m))
? "request"
: mode.some((m) => responseModes.includes(m))
? "response"
: "request";
const response = await testCustomCodeGuardrail(accessToken, {
custom_code: code,
test_input: parsedInput,
input_type: testInputType,
input_type: mode as "request" | "response",
request_data: {
model: "test-model",
metadata: {},
@ -456,9 +297,7 @@ const CustomCodeModal: React.FC<CustomCodeModalProps> = ({
<div className="flex flex-col h-[80vh]">
{/* Header */}
<div className="pb-4 border-b border-gray-200">
<h2 className="text-xl font-semibold text-gray-900">
{isEditMode ? "Edit Custom Guardrail" : "Create Custom Guardrail"}
</h2>
<h2 className="text-xl font-semibold text-gray-900">Create Custom Guardrail</h2>
<p className="text-sm text-gray-500 mt-1">Define custom logic using Python-like syntax</p>
</div>
@ -472,16 +311,14 @@ const CustomCodeModal: React.FC<CustomCodeModalProps> = ({
placeholder="e.g., block-pii-custom"
/>
</div>
<div className="w-[280px]">
<label className="block text-xs font-medium text-gray-600 mb-1">Mode (can select multiple)</label>
<div className="w-[180px]">
<label className="block text-xs font-medium text-gray-600 mb-1">Mode</label>
<Select
mode="multiple"
value={mode}
onChange={setMode}
options={MODE_OPTIONS}
className="w-full"
size="middle"
placeholder="Select modes"
/>
</div>
<div className="w-[180px]">
@ -491,45 +328,12 @@ const CustomCodeModal: React.FC<CustomCodeModalProps> = ({
onChange={handleTemplateChange}
className="w-full"
size="middle"
dropdownRender={(menu) => (
<>
{menu}
<Divider style={{ margin: '8px 0' }} />
<div
style={{
padding: '8px 12px',
cursor: 'pointer',
color: '#1890ff',
fontSize: '12px',
display: 'flex',
alignItems: 'center',
gap: '4px',
}}
onClick={(e) => {
e.preventDefault();
window.open('https://models.litellm.ai/guardrails', '_blank');
}}
onMouseEnter={(e) => {
e.currentTarget.style.backgroundColor = '#f0f0f0';
}}
onMouseLeave={(e) => {
e.currentTarget.style.backgroundColor = 'transparent';
}}
>
<UsergroupAddOutlined />
<span>Browse Community templates</span>
<ExportOutlined style={{ fontSize: '10px' }} />
</div>
</>
)}
>
<Select.OptGroup label="STANDARD">
{Object.entries(CODE_TEMPLATES).map(([key, template]) => (
<Select.Option key={key} value={key}>
{template.name}
</Select.Option>
))}
</Select.OptGroup>
{Object.entries(CODE_TEMPLATES).map(([key, template]) => (
<Select.Option key={key} value={key}>
{template.name}
</Select.Option>
))}
</Select>
</div>
<div className="flex items-center gap-2 pt-5">
@ -541,12 +345,12 @@ const CustomCodeModal: React.FC<CustomCodeModalProps> = ({
{/* Main Content */}
<div className="flex flex-1 overflow-hidden mt-4 gap-6">
{/* Code Editor */}
<div className="flex-[2] flex flex-col min-w-0 overflow-y-auto">
<div className="flex items-center justify-between mb-2 flex-shrink-0">
<div className="flex-[2] flex flex-col min-w-0">
<div className="flex items-center justify-between mb-2">
<span className="text-xs font-semibold text-gray-500 uppercase tracking-wide">Python Logic</span>
<span className="text-xs text-gray-400">Restricted environment (no imports)</span>
</div>
<div className="relative rounded-lg overflow-hidden border border-gray-700 bg-[#1e1e1e] flex-shrink-0" style={{ minHeight: "300px", maxHeight: "400px" }}>
<div className="flex-1 relative rounded-lg overflow-hidden border border-gray-700 bg-[#1e1e1e]" style={{ minHeight: "350px" }}>
{/* Line numbers */}
<div
className="absolute left-0 top-0 bottom-0 w-12 bg-[#1e1e1e] border-r border-gray-700 text-right pr-3 pt-3 select-none overflow-hidden"
@ -572,7 +376,7 @@ const CustomCodeModal: React.FC<CustomCodeModalProps> = ({
<Collapse
activeKey={testExpanded ? ["test"] : []}
onChange={(keys) => setTestExpanded(keys.includes("test"))}
className="mt-3 bg-white border border-gray-200 rounded-lg flex-shrink-0"
className="mt-3 bg-white border border-gray-200 rounded-lg"
expandIcon={({ isActive }) => <CaretRightOutlined rotate={isActive ? 90 : 0} />}
>
<Panel
@ -586,40 +390,11 @@ const CustomCodeModal: React.FC<CustomCodeModalProps> = ({
>
<div className="space-y-3">
<div>
<div className="flex items-center justify-between mb-2">
<label className="block text-xs font-medium text-gray-600">Test Input (JSON)</label>
<div className="flex items-center gap-2">
<span className="text-xs text-gray-500">Load example:</span>
<button
type="button"
onClick={() => setTestInput(JSON.stringify(TEST_INPUT_EXAMPLES.pre_call.data, null, 2))}
className="px-2 py-1 text-xs rounded border border-orange-200 bg-orange-50 text-orange-700 hover:bg-orange-100 transition-colors"
>
Pre-call
</button>
<button
type="button"
onClick={() => setTestInput(JSON.stringify(TEST_INPUT_EXAMPLES.post_call.data, null, 2))}
className="px-2 py-1 text-xs rounded border border-green-200 bg-green-50 text-green-700 hover:bg-green-100 transition-colors"
>
Post-call
</button>
</div>
</div>
<div className="mb-2 p-2 bg-gray-50 rounded text-xs text-gray-600 border border-gray-200">
<div className="grid grid-cols-2 gap-x-4 gap-y-1">
<div><strong>texts</strong>: Message content (always)</div>
<div><strong>images</strong>: Base64 images (vision)</div>
<div><strong>tools</strong>: Tool definitions <span className="text-orange-600">(pre_call)</span></div>
<div><strong>tool_calls</strong>: LLM tool calls <span className="text-green-600">(post_call)</span></div>
<div><strong>structured_messages</strong>: Full messages <span className="text-orange-600">(pre_call)</span></div>
<div><strong>model</strong>: Model name (always)</div>
</div>
</div>
<label className="block text-xs font-medium text-gray-600 mb-1">Test Input (JSON)</label>
<TextArea
value={testInput}
onChange={(e) => setTestInput(e.target.value)}
rows={8}
rows={4}
className="font-mono text-xs"
placeholder='{"texts": ["test message"], ...}'
/>
@ -670,27 +445,6 @@ const CustomCodeModal: React.FC<CustomCodeModalProps> = ({
</div>
</Panel>
</Collapse>
{/* Contribution CTA Banner */}
<div className="mt-3 p-4 bg-gradient-to-r from-blue-50 to-indigo-50 border border-blue-200 rounded-lg flex items-center justify-between flex-shrink-0">
<div className="flex items-center gap-3">
<div className="bg-blue-100 rounded-full p-2">
<UsergroupAddOutlined className="text-blue-600 text-lg" />
</div>
<div>
<div className="text-sm font-medium text-gray-900">Built a useful guardrail?</div>
<div className="text-xs text-gray-600">Share it with the community and help others build faster</div>
</div>
</div>
<Button
size="xs"
onClick={() => window.open('https://github.com/BerriAI/litellm-guardrails', '_blank')}
icon={ExportOutlined}
className="bg-blue-600 hover:bg-blue-700 text-white border-0"
>
Contribute Template
</Button>
</div>
</div>
{/* Primitives Panel */}
@ -755,7 +509,7 @@ const CustomCodeModal: React.FC<CustomCodeModalProps> = ({
disabled={isSaving || !guardrailName.trim()}
icon={SaveOutlined}
>
{isEditMode ? "Update Guardrail" : "Save Guardrail"}
Save Guardrail
</Button>
</div>
</div>