From d4b8c01f11bfcb70139b73449d866e475362ca9d Mon Sep 17 00:00:00 2001 From: Ishaan Jaffer Date: Fri, 13 Feb 2026 19:04:35 -0800 Subject: [PATCH] Add Zapier-style pipeline flow builder UI component --- .../policies/pipeline_flow_builder.tsx | 419 ++++++++++++++++++ 1 file changed, 419 insertions(+) create mode 100644 ui/litellm-dashboard/src/components/policies/pipeline_flow_builder.tsx diff --git a/ui/litellm-dashboard/src/components/policies/pipeline_flow_builder.tsx b/ui/litellm-dashboard/src/components/policies/pipeline_flow_builder.tsx new file mode 100644 index 00000000000..9df2a33fa66 --- /dev/null +++ b/ui/litellm-dashboard/src/components/policies/pipeline_flow_builder.tsx @@ -0,0 +1,419 @@ +import React from "react"; +import { Select, Typography, Tag } from "antd"; +import { TextInput } from "@tremor/react"; +import { PlusIcon, TrashIcon } from "@heroicons/react/outline"; +import { GuardrailPipeline, PipelineStep } from "./types"; +import { Guardrail } from "../guardrails/types"; + +const { Text } = Typography; + +const ACTION_OPTIONS = [ + { label: "Continue to next step", value: "next" }, + { label: "Allow (end flow)", value: "allow" }, + { label: "Block with error response", value: "block" }, + { label: "Return custom response", value: "modify_response" }, +]; + +function createDefaultStep(): PipelineStep { + return { + guardrail: "", + on_pass: "next", + on_fail: "block", + pass_data: false, + modify_response_message: null, + }; +} + +function insertStep(steps: PipelineStep[], atIndex: number): PipelineStep[] { + const newSteps = [...steps]; + newSteps.splice(atIndex, 0, createDefaultStep()); + return newSteps; +} + +function removeStep(steps: PipelineStep[], index: number): PipelineStep[] { + if (steps.length <= 1) return steps; + const newSteps = [...steps]; + newSteps.splice(index, 1); + return newSteps; +} + +function updateStepAtIndex( + steps: PipelineStep[], + index: number, + updated: Partial +): PipelineStep[] { + return steps.map((s, i) => (i === index ? { ...s, ...updated } : s)); +} + +// ───────────────────────────────────────────────────────────────────────────── +// Sub-components +// ───────────────────────────────────────────────────────────────────────────── + +interface ConnectorProps { + onInsert: () => void; +} + +const Connector: React.FC = ({ onInsert }) => ( +
+
+ +
+
+); + +interface StepCardProps { + step: PipelineStep; + stepIndex: number; + totalSteps: number; + onChange: (updated: Partial) => void; + onDelete: () => void; + availableGuardrails: Guardrail[]; +} + +const StepCard: React.FC = ({ + step, + stepIndex, + totalSteps, + onChange, + onDelete, + availableGuardrails, +}) => { + const guardrailOptions = availableGuardrails.map((g) => ({ + label: g.guardrail_name || g.guardrail_id, + value: g.guardrail_name || g.guardrail_id, + })); + + return ( +
+ {/* Header */} +
+
+ + GUARDRAIL + + {step.guardrail || "Select guardrail..."} +
+
+ + Step {stepIndex + 1} + + +
+
+ + {/* Guardrail Selector */} +
+ + Guardrail + + onChange({ on_pass: value as PipelineStep["on_pass"] })} + options={ACTION_OPTIONS} + /> + {step.on_pass === "modify_response" && ( +
+ onChange({ modify_response_message: e.target.value || null })} + /> +
+ )} +
+ + {/* ON FAIL */} +
+ + ON FAIL + +