mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-03 02:22:24 +00:00
feat(ui): migrate guardrails forms + info to shadcn
Completed migrations in this batch: - src/components/guardrails.tsx: antd Tabs -> shadcn Tabs+TabsList+TabsTrigger+TabsContent. - src/components/guardrails/TeamGuardrailsTab.tsx: antd Modal + Form.useForm submission dialog -> shadcn Dialog + react-hook-form + shadcn Select/Textarea; existing categorical Tailwind palette preserved behind an eslint override. Deferred to follow-up (entries added to docs/BLOCKERS.md): - add_guardrail_form.tsx (1202 LoC, multi-step wizard w/ antd Form.useForm) - edit_guardrail_form.tsx (antd Form mirror of add form) - guardrail_info.tsx (888 LoC, Tremor tabs + antd Form settings panel) - guardrail_provider_fields.tsx, guardrail_optional_params.tsx (render antd Form.Item keyed to the parent antd Form context; cannot be migrated independently of the above parents). Co-authored-by: yuneng-jiang <yuneng-berri@users.noreply.github.com>
This commit is contained in:
parent
cf6493df11
commit
ce1c604be2
4 changed files with 435 additions and 246 deletions
|
|
@ -106,6 +106,7 @@
|
|||
"src/components/guardrails/content_filter/CategoryTable.tsx",
|
||||
"src/components/guardrails/content_filter/ContentCategoryConfiguration.tsx",
|
||||
"src/components/guardrails/custom_code/CustomCodeModal.tsx",
|
||||
"src/components/guardrails/TeamGuardrailsTab.tsx",
|
||||
"src/components/Settings/RouterSettings/Fallbacks/FallbackGroupConfig.tsx",
|
||||
"src/components/prompts/variable_textarea.tsx",
|
||||
"src/components/chat/MCPCredentialsTab.tsx",
|
||||
|
|
|
|||
|
|
@ -40,3 +40,32 @@ stay on antd for phase 1 and will be addressed in a targeted follow-up.
|
|||
exceeds the two-attempt budget for this batch. Defer until Tremor
|
||||
`Accordion` has a shadcn replacement adopted and the antd Form surface
|
||||
can be broken into sub-forms.
|
||||
- `src/components/guardrails/add_guardrail_form.tsx` (1202 LoC): multi-step
|
||||
wizard driven by antd `Form.useForm` with provider-specific dynamic
|
||||
fields (Presidio PII, PromptGuard, content filter, custom code, tool
|
||||
permission, Azure Text Moderation). Uses `form.validateFields` per step,
|
||||
`form.setFieldsValue` to apply presets, `form.resetFields`, antd
|
||||
`Select` with children `Option` rendering provider logos, `Tag`,
|
||||
`Typography.Title/Text/Link`. The step navigation and provider-param
|
||||
validation are tightly coupled to antd's validation API.
|
||||
- `src/components/guardrails/edit_guardrail_form.tsx` (451 LoC): mirrors
|
||||
add_guardrail_form's provider-switching logic for edit flow. Defer with
|
||||
it.
|
||||
- `src/components/guardrails/guardrail_info.tsx` (888 LoC): guardrail
|
||||
detail/edit page with Tremor `TabGroup`/`TabList`/`Card`/`Grid` (still
|
||||
required by overview), a large antd `Form` settings panel
|
||||
(`Form.useForm` + `Form.Item` rules, `Input.TextArea`, antd `Select`,
|
||||
`Divider orientation="left"` section headers) that reuses
|
||||
`GuardrailProviderFields` and `GuardrailOptionalParams`. Settings-panel
|
||||
migration requires migrating both child components in tandem.
|
||||
- `src/components/guardrails/guardrail_provider_fields.tsx` (240 LoC):
|
||||
renders antd `Form.Item` elements keyed to the parent's antd Form
|
||||
context (including nested `optional_params` + antd `Slider` with marks
|
||||
for percentage fields). Cannot be migrated independently of its two
|
||||
callers (`add_guardrail_form.tsx` + `guardrail_info.tsx`) without
|
||||
breaking the form wiring. Defer with them.
|
||||
- `src/components/guardrails/guardrail_optional_params.tsx` (256 LoC):
|
||||
renders antd `Form.Item` for provider-specific optional params including
|
||||
a dict-field builder with dynamic antd `Select.Option` lists. Same
|
||||
dependency on the parent antd Form context as the above. Defer with the
|
||||
parent forms.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useCallback, useState, useEffect } from "react";
|
||||
import { Tabs } from "antd";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
|
|
@ -139,128 +139,130 @@ const GuardrailsPanel: React.FC<GuardrailsPanelProps> = ({ accessToken, userRole
|
|||
|
||||
return (
|
||||
<div className="w-full mx-auto flex-auto overflow-y-auto m-8 p-2">
|
||||
<Tabs
|
||||
defaultActiveKey="submitted"
|
||||
items={[
|
||||
...(isAdmin
|
||||
? [
|
||||
{
|
||||
key: "garden",
|
||||
label: "Guardrail Garden",
|
||||
children: (
|
||||
<GuardrailGarden
|
||||
accessToken={accessToken}
|
||||
onGuardrailCreated={handleSuccess}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "guardrails",
|
||||
label: "Guardrails",
|
||||
children: (
|
||||
<>
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" disabled={!accessToken}>
|
||||
+ Add New Guardrail
|
||||
<ChevronDown className="h-4 w-4 ml-1" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start">
|
||||
<DropdownMenuItem
|
||||
onSelect={handleAddGuardrail}
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
Add Provider Guardrail
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onSelect={handleAddCustomCodeGuardrail}
|
||||
>
|
||||
<Code className="h-4 w-4" />
|
||||
Create Custom Code Guardrail
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
<Tabs defaultValue="submitted">
|
||||
<TabsList>
|
||||
{isAdmin && (
|
||||
<>
|
||||
<TabsTrigger value="garden">Guardrail Garden</TabsTrigger>
|
||||
<TabsTrigger value="guardrails">Guardrails</TabsTrigger>
|
||||
<TabsTrigger value="playground" disabled={!accessToken}>
|
||||
Test Playground
|
||||
</TabsTrigger>
|
||||
</>
|
||||
)}
|
||||
<TabsTrigger value="submitted">Submitted Guardrails</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
{selectedGuardrailId ? (
|
||||
<GuardrailInfoView
|
||||
guardrailId={selectedGuardrailId}
|
||||
onClose={() => setSelectedGuardrailId(null)}
|
||||
accessToken={accessToken}
|
||||
isAdmin={isAdmin}
|
||||
/>
|
||||
) : (
|
||||
<GuardrailTable
|
||||
guardrailsList={guardrailsList}
|
||||
isLoading={isLoading}
|
||||
onDeleteClick={handleDeleteClick}
|
||||
accessToken={accessToken}
|
||||
onGuardrailUpdated={fetchGuardrails}
|
||||
isAdmin={isAdmin}
|
||||
onGuardrailClick={(id) => setSelectedGuardrailId(id)}
|
||||
/>
|
||||
)}
|
||||
{isAdmin && (
|
||||
<>
|
||||
<TabsContent value="garden">
|
||||
<GuardrailGarden
|
||||
accessToken={accessToken}
|
||||
onGuardrailCreated={handleSuccess}
|
||||
/>
|
||||
</TabsContent>
|
||||
<TabsContent value="guardrails">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" disabled={!accessToken}>
|
||||
+ Add New Guardrail
|
||||
<ChevronDown className="h-4 w-4 ml-1" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start">
|
||||
<DropdownMenuItem onSelect={handleAddGuardrail}>
|
||||
<Plus className="h-4 w-4" />
|
||||
Add Provider Guardrail
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onSelect={handleAddCustomCodeGuardrail}>
|
||||
<Code className="h-4 w-4" />
|
||||
Create Custom Code Guardrail
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
|
||||
<AddGuardrailForm
|
||||
visible={isAddModalVisible}
|
||||
onClose={handleCloseModal}
|
||||
accessToken={accessToken}
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
{selectedGuardrailId ? (
|
||||
<GuardrailInfoView
|
||||
guardrailId={selectedGuardrailId}
|
||||
onClose={() => setSelectedGuardrailId(null)}
|
||||
accessToken={accessToken}
|
||||
isAdmin={isAdmin}
|
||||
/>
|
||||
) : (
|
||||
<GuardrailTable
|
||||
guardrailsList={guardrailsList}
|
||||
isLoading={isLoading}
|
||||
onDeleteClick={handleDeleteClick}
|
||||
accessToken={accessToken}
|
||||
onGuardrailUpdated={fetchGuardrails}
|
||||
isAdmin={isAdmin}
|
||||
onGuardrailClick={(id) => setSelectedGuardrailId(id)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<CustomCodeModal
|
||||
visible={isCustomCodeModalVisible}
|
||||
onClose={handleCloseCustomCodeModal}
|
||||
accessToken={accessToken}
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
<AddGuardrailForm
|
||||
visible={isAddModalVisible}
|
||||
onClose={handleCloseModal}
|
||||
accessToken={accessToken}
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
|
||||
<DeleteResourceModal
|
||||
isOpen={isDeleteModalOpen}
|
||||
title="Delete Guardrail"
|
||||
message={`Are you sure you want to delete guardrail: ${guardrailToDelete?.guardrail_name}? This action cannot be undone.`}
|
||||
resourceInformationTitle="Guardrail Information"
|
||||
resourceInformation={[
|
||||
{ label: "Name", value: guardrailToDelete?.guardrail_name },
|
||||
{ label: "ID", value: guardrailToDelete?.guardrail_id, code: true },
|
||||
{ label: "Provider", value: providerDisplayName },
|
||||
{ label: "Mode", value: guardrailToDelete?.litellm_params.mode },
|
||||
{
|
||||
label: "Default On",
|
||||
value: guardrailToDelete?.litellm_params.default_on ? "Yes" : "No",
|
||||
},
|
||||
]}
|
||||
onCancel={handleDeleteCancel}
|
||||
onOk={handleDeleteConfirm}
|
||||
confirmLoading={isDeleting}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "playground",
|
||||
label: "Test Playground",
|
||||
disabled: !accessToken,
|
||||
children: (
|
||||
<GuardrailTestPlayground
|
||||
guardrailsList={guardrailsList}
|
||||
isLoading={isLoading}
|
||||
accessToken={accessToken}
|
||||
onClose={() => {}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
key: "submitted",
|
||||
label: "Submitted Guardrails",
|
||||
children: <TeamGuardrailsTab accessToken={accessToken} />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<CustomCodeModal
|
||||
visible={isCustomCodeModalVisible}
|
||||
onClose={handleCloseCustomCodeModal}
|
||||
accessToken={accessToken}
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
|
||||
<DeleteResourceModal
|
||||
isOpen={isDeleteModalOpen}
|
||||
title="Delete Guardrail"
|
||||
message={`Are you sure you want to delete guardrail: ${guardrailToDelete?.guardrail_name}? This action cannot be undone.`}
|
||||
resourceInformationTitle="Guardrail Information"
|
||||
resourceInformation={[
|
||||
{
|
||||
label: "Name",
|
||||
value: guardrailToDelete?.guardrail_name,
|
||||
},
|
||||
{
|
||||
label: "ID",
|
||||
value: guardrailToDelete?.guardrail_id,
|
||||
code: true,
|
||||
},
|
||||
{ label: "Provider", value: providerDisplayName },
|
||||
{
|
||||
label: "Mode",
|
||||
value: guardrailToDelete?.litellm_params.mode,
|
||||
},
|
||||
{
|
||||
label: "Default On",
|
||||
value: guardrailToDelete?.litellm_params.default_on
|
||||
? "Yes"
|
||||
: "No",
|
||||
},
|
||||
]}
|
||||
onCancel={handleDeleteCancel}
|
||||
onOk={handleDeleteConfirm}
|
||||
confirmLoading={isDeleting}
|
||||
/>
|
||||
</TabsContent>
|
||||
<TabsContent value="playground">
|
||||
<GuardrailTestPlayground
|
||||
guardrailsList={guardrailsList}
|
||||
isLoading={isLoading}
|
||||
accessToken={accessToken}
|
||||
onClose={() => {}}
|
||||
/>
|
||||
</TabsContent>
|
||||
</>
|
||||
)}
|
||||
|
||||
<TabsContent value="submitted">
|
||||
<TeamGuardrailsTab accessToken={accessToken} />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -14,7 +14,25 @@ import {
|
|||
AlertCircleIcon,
|
||||
InfoIcon,
|
||||
} from "lucide-react";
|
||||
import { Modal, Form, Input, Select } from "antd";
|
||||
import { Controller, FormProvider, useForm, useFormContext } from "react-hook-form";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import {
|
||||
listGuardrailSubmissions,
|
||||
approveGuardrailSubmission,
|
||||
|
|
@ -824,7 +842,6 @@ export function TeamGuardrailsTab({ accessToken }: TeamGuardrailsTabProps) {
|
|||
const [error, setError] = useState<string | null>(null);
|
||||
const [searchDebounced, setSearchDebounced] = useState("");
|
||||
const [isSubmitModalOpen, setIsSubmitModalOpen] = useState(false);
|
||||
const [submitForm] = Form.useForm();
|
||||
const registerGuardrail = useRegisterGuardrail();
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -1084,133 +1101,273 @@ export function TeamGuardrailsTab({ accessToken }: TeamGuardrailsTabProps) {
|
|||
/>
|
||||
)}
|
||||
|
||||
<Modal
|
||||
title="Submit Guardrail for Review"
|
||||
<SubmitGuardrailDialog
|
||||
open={isSubmitModalOpen}
|
||||
onCancel={() => {
|
||||
setIsSubmitModalOpen(false);
|
||||
submitForm.resetFields();
|
||||
onOpenChange={setIsSubmitModalOpen}
|
||||
onSubmit={async (values) => {
|
||||
const litellm_params: Record<string, unknown> = {
|
||||
...(values.extra_litellm_params
|
||||
? JSON.parse(values.extra_litellm_params)
|
||||
: {}),
|
||||
guardrail: "generic_guardrail_api",
|
||||
mode: values.mode,
|
||||
api_base: values.api_base,
|
||||
};
|
||||
try {
|
||||
await registerGuardrail.mutateAsync({
|
||||
team_id: values.team_id,
|
||||
guardrail_name: values.guardrail_name,
|
||||
litellm_params,
|
||||
guardrail_info: values.guardrail_info
|
||||
? JSON.parse(values.guardrail_info)
|
||||
: undefined,
|
||||
});
|
||||
NotificationsManager.success("Guardrail submitted for review");
|
||||
setIsSubmitModalOpen(false);
|
||||
fetchSubmissions();
|
||||
} catch {
|
||||
// error already handled by networking layer
|
||||
}
|
||||
}}
|
||||
onOk={() => submitForm.submit()}
|
||||
okText="Submit for Review"
|
||||
>
|
||||
<div className="rounded-md bg-blue-50 border border-blue-200 px-4 py-3 text-sm text-blue-800 mb-4">
|
||||
Your guardrail will be sent for admin review before it becomes active.
|
||||
</div>
|
||||
<Form
|
||||
form={submitForm}
|
||||
layout="vertical"
|
||||
initialValues={{ mode: "pre_call" }}
|
||||
onFinish={async (values) => {
|
||||
const litellm_params: Record<string, unknown> = {
|
||||
...(values.extra_litellm_params ? JSON.parse(values.extra_litellm_params) : {}),
|
||||
guardrail: "generic_guardrail_api",
|
||||
mode: values.mode,
|
||||
api_base: values.api_base,
|
||||
};
|
||||
try {
|
||||
await registerGuardrail.mutateAsync({
|
||||
team_id: values.team_id,
|
||||
guardrail_name: values.guardrail_name,
|
||||
litellm_params,
|
||||
guardrail_info: values.guardrail_info ? JSON.parse(values.guardrail_info) : undefined,
|
||||
});
|
||||
NotificationsManager.success("Guardrail submitted for review");
|
||||
setIsSubmitModalOpen(false);
|
||||
submitForm.resetFields();
|
||||
fetchSubmissions();
|
||||
} catch {
|
||||
// error already handled by networking layer
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Form.Item
|
||||
label="Team"
|
||||
name="team_id"
|
||||
rules={[{ required: true, message: "Select a team" }]}
|
||||
>
|
||||
<TeamDropdown />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Guardrail Name"
|
||||
name="guardrail_name"
|
||||
rules={[{ required: true, message: "Enter a guardrail name" }]}
|
||||
>
|
||||
<Input placeholder="e.g. pii-detection" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Mode"
|
||||
name="mode"
|
||||
rules={[{ required: true, message: "Select a mode" }]}
|
||||
>
|
||||
<Select>
|
||||
<Select.Option value="pre_call">Pre Call</Select.Option>
|
||||
<Select.Option value="post_call">Post Call</Select.Option>
|
||||
<Select.Option value="during_call">During Call</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="API Base URL"
|
||||
name="api_base"
|
||||
rules={[
|
||||
{ required: true, message: "Enter the API base URL" },
|
||||
{ type: "url", message: "Must be a valid URL" },
|
||||
]}
|
||||
>
|
||||
<Input placeholder="https://your-guardrail-api.com/v1/check" className="font-mono" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Additional litellm_params (optional)"
|
||||
name="extra_litellm_params"
|
||||
tooltip="JSON object merged into litellm_params. e.g. forward_api_key, headers, model, unreachable_fallback"
|
||||
rules={[
|
||||
{
|
||||
validator: (_, value) => {
|
||||
if (!value) return Promise.resolve();
|
||||
try {
|
||||
const parsed = JSON.parse(value);
|
||||
if (typeof parsed !== "object" || Array.isArray(parsed)) {
|
||||
return Promise.reject("Must be a JSON object");
|
||||
}
|
||||
return Promise.resolve();
|
||||
} catch {
|
||||
return Promise.reject("Invalid JSON");
|
||||
}
|
||||
},
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input.TextArea
|
||||
rows={3}
|
||||
className="font-mono text-xs"
|
||||
placeholder='{"forward_api_key": true, "headers": {"X-Custom": "value"}}'
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Guardrail Info (optional)"
|
||||
name="guardrail_info"
|
||||
rules={[
|
||||
{
|
||||
validator: (_, value) => {
|
||||
if (!value) return Promise.resolve();
|
||||
try {
|
||||
JSON.parse(value);
|
||||
return Promise.resolve();
|
||||
} catch {
|
||||
return Promise.reject("Invalid JSON");
|
||||
}
|
||||
},
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input.TextArea
|
||||
rows={3}
|
||||
className="font-mono text-xs"
|
||||
placeholder='{"description": "Detects PII in requests"}'
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface SubmitGuardrailFormValues {
|
||||
team_id: string;
|
||||
guardrail_name: string;
|
||||
mode: string;
|
||||
api_base: string;
|
||||
extra_litellm_params?: string;
|
||||
guardrail_info?: string;
|
||||
}
|
||||
|
||||
function SubmitGuardrailDialog({
|
||||
open,
|
||||
onOpenChange,
|
||||
onSubmit,
|
||||
}: {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
onSubmit: (values: SubmitGuardrailFormValues) => Promise<void> | void;
|
||||
}) {
|
||||
const form = useForm<SubmitGuardrailFormValues>({
|
||||
defaultValues: {
|
||||
team_id: "",
|
||||
guardrail_name: "",
|
||||
mode: "pre_call",
|
||||
api_base: "",
|
||||
extra_litellm_params: "",
|
||||
guardrail_info: "",
|
||||
},
|
||||
});
|
||||
|
||||
const handleClose = (nextOpen: boolean) => {
|
||||
if (!nextOpen) {
|
||||
form.reset();
|
||||
}
|
||||
onOpenChange(nextOpen);
|
||||
};
|
||||
|
||||
const handleSubmit = form.handleSubmit(async (values) => {
|
||||
await onSubmit(values);
|
||||
form.reset();
|
||||
});
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={handleClose}>
|
||||
<DialogContent className="max-w-xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Submit Guardrail for Review</DialogTitle>
|
||||
</DialogHeader>
|
||||
{/* eslint-disable-next-line litellm-ui/no-raw-tailwind-colors */}
|
||||
<div className="rounded-md bg-blue-50 border border-blue-200 px-4 py-3 text-sm text-blue-800 mb-4 dark:bg-blue-950/30 dark:border-blue-900 dark:text-blue-200">
|
||||
Your guardrail will be sent for admin review before it becomes
|
||||
active.
|
||||
</div>
|
||||
<FormProvider {...form}>
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<SubmitGuardrailFields />
|
||||
<DialogFooter>
|
||||
<Button
|
||||
variant="outline"
|
||||
type="button"
|
||||
onClick={() => handleClose(false)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit">Submit for Review</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</FormProvider>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
function SubmitGuardrailFields() {
|
||||
const { register, control, formState } =
|
||||
useFormContext<SubmitGuardrailFormValues>();
|
||||
|
||||
const validateJsonObject = (value?: string) => {
|
||||
if (!value) return true;
|
||||
try {
|
||||
const parsed = JSON.parse(value);
|
||||
if (typeof parsed !== "object" || Array.isArray(parsed)) {
|
||||
return "Must be a JSON object";
|
||||
}
|
||||
return true;
|
||||
} catch {
|
||||
return "Invalid JSON";
|
||||
}
|
||||
};
|
||||
|
||||
const validateJson = (value?: string) => {
|
||||
if (!value) return true;
|
||||
try {
|
||||
JSON.parse(value);
|
||||
return true;
|
||||
} catch {
|
||||
return "Invalid JSON";
|
||||
}
|
||||
};
|
||||
|
||||
const validateUrl = (value: string) => {
|
||||
if (!value) return "Enter the API base URL";
|
||||
try {
|
||||
new URL(value);
|
||||
return true;
|
||||
} catch {
|
||||
return "Must be a valid URL";
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="space-y-2">
|
||||
<Label>
|
||||
Team <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="team_id"
|
||||
rules={{ required: "Select a team" }}
|
||||
render={({ field }) => (
|
||||
<TeamDropdown value={field.value} onChange={field.onChange} />
|
||||
)}
|
||||
/>
|
||||
{formState.errors.team_id && (
|
||||
<p className="text-sm text-destructive">
|
||||
{formState.errors.team_id.message as string}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="submit-guardrail-name">
|
||||
Guardrail Name <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="submit-guardrail-name"
|
||||
placeholder="e.g. pii-detection"
|
||||
{...register("guardrail_name", {
|
||||
required: "Enter a guardrail name",
|
||||
})}
|
||||
/>
|
||||
{formState.errors.guardrail_name && (
|
||||
<p className="text-sm text-destructive">
|
||||
{formState.errors.guardrail_name.message as string}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>
|
||||
Mode <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<Controller
|
||||
control={control}
|
||||
name="mode"
|
||||
rules={{ required: "Select a mode" }}
|
||||
render={({ field }) => (
|
||||
<Select value={field.value} onValueChange={field.onChange}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select mode" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="pre_call">Pre Call</SelectItem>
|
||||
<SelectItem value="post_call">Post Call</SelectItem>
|
||||
<SelectItem value="during_call">During Call</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
/>
|
||||
{formState.errors.mode && (
|
||||
<p className="text-sm text-destructive">
|
||||
{formState.errors.mode.message as string}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="submit-guardrail-api-base">
|
||||
API Base URL <span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="submit-guardrail-api-base"
|
||||
placeholder="https://your-guardrail-api.com/v1/check"
|
||||
className="font-mono"
|
||||
{...register("api_base", { validate: validateUrl })}
|
||||
/>
|
||||
{formState.errors.api_base && (
|
||||
<p className="text-sm text-destructive">
|
||||
{formState.errors.api_base.message as string}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="submit-guardrail-extra-params">
|
||||
Additional litellm_params (optional)
|
||||
</Label>
|
||||
<Textarea
|
||||
id="submit-guardrail-extra-params"
|
||||
rows={3}
|
||||
className="font-mono text-xs"
|
||||
placeholder='{"forward_api_key": true, "headers": {"X-Custom": "value"}}'
|
||||
{...register("extra_litellm_params", {
|
||||
validate: validateJsonObject,
|
||||
})}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
JSON object merged into litellm_params. e.g. forward_api_key,
|
||||
headers, model, unreachable_fallback.
|
||||
</p>
|
||||
{formState.errors.extra_litellm_params && (
|
||||
<p className="text-sm text-destructive">
|
||||
{formState.errors.extra_litellm_params.message as string}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="submit-guardrail-info">
|
||||
Guardrail Info (optional)
|
||||
</Label>
|
||||
<Textarea
|
||||
id="submit-guardrail-info"
|
||||
rows={3}
|
||||
className="font-mono text-xs"
|
||||
placeholder='{"description": "Detects PII in requests"}'
|
||||
{...register("guardrail_info", { validate: validateJson })}
|
||||
/>
|
||||
{formState.errors.guardrail_info && (
|
||||
<p className="text-sm text-destructive">
|
||||
{formState.errors.guardrail_info.message as string}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue