[Feat] V2 Add Pass through endpoints on UI (#11905)

* add pass through ui

* fix accordion for route path

* working route path renderer

* fix use sections

* clean up add pass through form

* docs fix add pass through routing

* clean up route preview

* add route preview
This commit is contained in:
Ishaan Jaff
2025-06-19 16:05:29 -07:00
committed by GitHub
parent 753ec7298a
commit 87c2be93c7
2 changed files with 261 additions and 59 deletions
@@ -18,12 +18,16 @@ import {
Select as Select2,
message,
Tooltip,
Alert,
Divider,
Collapse,
} from "antd";
import { InfoCircleOutlined, ApiOutlined } from "@ant-design/icons";
import { InfoCircleOutlined, ApiOutlined, ExclamationCircleOutlined, CheckCircleOutlined, CopyOutlined } from "@ant-design/icons";
import { keyCreateCall, slackBudgetAlertsHealthCheck, modelAvailableCall } from "./networking";
import { list } from "postcss";
import KeyValueInput from "./key_value_input";
import { passThroughItem } from "./pass_through_settings";
import RoutePreview from "./route_preview";
const { Option } = Select2;
interface AddFallbacksProps {
@@ -40,11 +44,28 @@ const AddPassThroughEndpoint: React.FC<AddFallbacksProps> = ({
const [isModalVisible, setIsModalVisible] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [selectedModel, setSelectedModel] = useState("");
const [pathValue, setPathValue] = useState("");
const [targetValue, setTargetValue] = useState("");
const [includeSubpath, setIncludeSubpath] = useState(true);
const handleCancel = () => {
form.resetFields();
setPathValue("");
setTargetValue("");
setIncludeSubpath(true);
setIsModalVisible(false);
};
const handlePathChange = (value: string) => {
// Auto-add leading slash if missing
let formattedPath = value;
if (value && !value.startsWith('/')) {
formattedPath = '/' + value;
}
setPathValue(formattedPath);
form.setFieldsValue({ path: formattedPath });
};
const addPassThrough = async (formValues: Record<string, any>) => {
setIsLoading(true);
try {
@@ -65,6 +86,9 @@ const AddPassThroughEndpoint: React.FC<AddFallbacksProps> = ({
message.success("Pass-through endpoint created successfully");
form.resetFields();
setPathValue("");
setTargetValue("");
setIncludeSubpath(true);
setIsModalVisible(false);
} catch (error) {
message.error("Error creating pass-through endpoint: " + error, 20);
@@ -73,6 +97,12 @@ const AddPassThroughEndpoint: React.FC<AddFallbacksProps> = ({
}
};
const copyToClipboard = (text: string) => {
navigator.clipboard.writeText(text);
message.success('Copied to clipboard!');
};
return (
<div>
@@ -100,103 +130,165 @@ const AddPassThroughEndpoint: React.FC<AddFallbacksProps> = ({
}}
>
<div className="mt-6">
<Alert
message="What is a Pass-Through Endpoint?"
description="Route requests from your LiteLLM proxy to any external API. Perfect for custom models, image generation APIs, or any service you want to proxy through LiteLLM."
type="info"
showIcon
className="mb-6"
/>
<Form
form={form}
onFinish={addPassThrough}
layout="vertical"
className="space-y-6"
initialValues={{ include_subpath: true }}
>
<div className="grid grid-cols-1 gap-6">
<Form.Item
label={
<span className="text-sm font-medium text-gray-700 flex items-center">
Path
<Tooltip title="The route to be added to the LiteLLM Proxy Server (e.g., /my-endpoint)">
<InfoCircleOutlined className="ml-2 text-blue-400 hover:text-blue-600 cursor-help" />
</Tooltip>
</span>
}
name="path"
rules={[{ required: true, message: 'Please enter the endpoint path' }]}
>
<TextInput
placeholder="e.g., /my-endpoint"
className="rounded-lg border-gray-300 focus:border-blue-500 focus:ring-blue-500"
/>
</Form.Item>
{/* Route Configuration Section */}
<Card className="p-5">
<Title className="text-lg font-semibold text-gray-900 mb-2">Route Configuration</Title>
<Subtitle className="text-gray-600 mb-5">Configure how requests to your domain will be forwarded to the target API</Subtitle>
<div className="space-y-5">
<Form.Item
label={
<span className="text-sm font-medium text-gray-700">
Path Prefix <span className="text-red-500">*</span>
</span>
}
name="path"
rules={[
{ required: true, message: '' },
{ pattern: /^\//, message: '' }
]}
extra={
<div className="text-xs text-gray-500 mt-1">
Example: /bria, /openai, /anthropic
</div>
}
className="mb-4"
>
<div className="flex items-center">
<span className="mr-2 text-gray-500 font-mono text-base">/</span>
<TextInput
placeholder="bria"
value={pathValue.startsWith('/') ? pathValue.slice(1) : pathValue}
onChange={(e) => handlePathChange(e.target.value)}
className="flex-1"
/>
</div>
</Form.Item>
<Form.Item
label={
<span className="text-sm font-medium text-gray-700 flex items-center">
Target URL
<Tooltip title="The URL to which requests for this path should be forwarded">
<InfoCircleOutlined className="ml-2 text-blue-400 hover:text-blue-600 cursor-help" />
</Tooltip>
</span>
}
name="target"
rules={[
{ required: true, message: 'Please enter the target URL' },
{ type: 'url', message: 'Please enter a valid URL' }
]}
>
<TextInput
placeholder="https://your-service.com/api"
className="rounded-lg border-gray-300 focus:border-blue-500 focus:ring-blue-500"
/>
</Form.Item>
<Form.Item
label={
<span className="text-sm font-medium text-gray-700">
Target URL <span className="text-red-500">*</span>
</span>
}
name="target"
rules={[
{ required: true, message: '' },
{ type: 'url', message: '' }
]}
extra={
<div className="text-xs text-gray-500 mt-1">
Example: https://api.openai.com, https://engine.prod.bria-api.com
</div>
}
className="mb-4"
>
<TextInput
placeholder="https://engine.prod.bria-api.com"
value={targetValue}
onChange={(e) => setTargetValue(e.target.value)}
/>
</Form.Item>
<div className="flex items-center justify-between py-3">
<div>
<div className="text-sm font-medium text-gray-700">Include Subpaths</div>
<div className="text-xs text-gray-500 mt-0.5">Forward all subpaths to the target API (recommended for REST APIs)</div>
</div>
<Form.Item
name="include_subpath"
valuePropName="checked"
className="mb-0"
>
<Switch
checked={includeSubpath}
onChange={setIncludeSubpath}
/>
</Form.Item>
</div>
</div>
</Card>
{/* Route Preview Section */}
<RoutePreview
pathValue={pathValue}
targetValue={targetValue}
includeSubpath={includeSubpath}
/>
{/* Headers Section */}
<Card className="p-6">
<Title className="text-lg font-semibold text-gray-900 mb-2">Headers</Title>
<Subtitle className="text-gray-600 mb-6">Add headers that will be sent with every request to the target API</Subtitle>
<Form.Item
label={
<span className="text-sm font-medium text-gray-700 flex items-center">
Headers
<Tooltip title="Key-value pairs of headers to be forwarded with the request">
Authentication Headers
<Tooltip title="Authentication and other headers to forward with requests">
<InfoCircleOutlined className="ml-2 text-blue-400 hover:text-blue-600 cursor-help" />
</Tooltip>
</span>
}
name="headers"
rules={[{ required: true, message: 'Please configure the headers' }]}
extra={
<div className="text-xs text-gray-500 mt-2">
<div className="font-medium mb-1">Add authentication tokens and other required headers</div>
<div>Common examples: auth_token, Authorization, x-api-key</div>
</div>
}
>
<KeyValueInput/>
</Form.Item>
</Card>
<Form.Item
label={
<span className="text-sm font-medium text-gray-700 flex items-center">
Include Subpath
<Tooltip title="If enabled, requests to subpaths will also be forwarded to the target endpoint">
<InfoCircleOutlined className="ml-2 text-gray-400 hover:text-gray-600" />
</Tooltip>
</span>
}
name="include_subpath"
valuePropName="checked"
>
<Switch />
</Form.Item>
{/* Billing Section */}
<Card className="p-6">
<Title className="text-lg font-semibold text-gray-900 mb-2">Billing</Title>
<Subtitle className="text-gray-600 mb-6">Optional cost tracking for this endpoint</Subtitle>
<Form.Item
label={
<span className="text-sm font-medium text-gray-700 flex items-center">
Cost Per Request (USD)
<Tooltip title="The cost in USD per request to the target endpoint">
<Tooltip title="Optional: Track costs for requests to this endpoint">
<InfoCircleOutlined className="ml-2 text-gray-400 hover:text-gray-600" />
</Tooltip>
</span>
}
name="cost_per_request"
extra={
<div className="text-xs text-gray-500 mt-2">
The cost charged for each request through this endpoint
</div>
}
>
<InputNumber
min={0}
step={0.001}
precision={6}
placeholder="0.000000"
placeholder="2.000000"
size="large"
className="rounded-lg w-full"
/>
</Form.Item>
</div>
</Card>
<div className="flex items-center justify-end space-x-3 pt-6 border-t border-gray-100">
<Button
@@ -0,0 +1,110 @@
import React from "react";
import { Card, Title, Subtitle } from "@tremor/react";
import { RightOutlined, InfoCircleOutlined } from "@ant-design/icons";
import { getProxyBaseUrl } from "./networking";
interface RoutePreviewProps {
pathValue: string;
targetValue: string;
includeSubpath: boolean;
}
const RoutePreview: React.FC<RoutePreviewProps> = ({
pathValue,
targetValue,
includeSubpath
}) => {
const proxyBaseUrl = getProxyBaseUrl();
const getLiteLLMProxyUrl = () => {
return pathValue ? `${proxyBaseUrl}${pathValue}` : '';
};
// Only show if both path and target are provided
if (!pathValue || !targetValue) {
return null;
}
return (
<Card className="p-5">
<Title className="text-lg font-semibold text-gray-900 mb-2">Route Preview</Title>
<Subtitle className="text-gray-600 mb-5">How your requests will be routed</Subtitle>
<div className="space-y-5">
{/* Basic routing */}
<div>
<div className="text-base font-semibold text-gray-900 mb-3">Basic routing:</div>
<div className="flex items-center gap-4">
{/* Your endpoint */}
<div className="flex-1 bg-gray-50 border border-gray-200 rounded-lg p-3">
<div className="text-sm text-gray-600 mb-2">Your endpoint</div>
<code className="font-mono text-sm text-gray-900">{getLiteLLMProxyUrl()}</code>
</div>
{/* Arrow */}
<div className="text-gray-400">
<RightOutlined className="text-lg" />
</div>
{/* Forwards to */}
<div className="flex-1 bg-gray-50 border border-gray-200 rounded-lg p-3">
<div className="text-sm text-gray-600 mb-2">Forwards to</div>
<code className="font-mono text-sm text-gray-900">{targetValue}</code>
</div>
</div>
</div>
{includeSubpath && (
<>
{/* With subpaths */}
<div>
<div className="text-base font-semibold text-gray-900 mb-3">With subpaths:</div>
<div className="flex items-center gap-4">
{/* Your endpoint + subpath */}
<div className="flex-1 bg-gray-50 border border-gray-200 rounded-lg p-3">
<div className="text-sm text-gray-600 mb-2">Your endpoint + subpath</div>
<code className="font-mono text-sm text-gray-900">
{pathValue && `${proxyBaseUrl}${pathValue}`}
<span className="text-blue-600">/v1/text-to-image/base/model</span>
</code>
</div>
{/* Arrow */}
<div className="text-gray-400">
<RightOutlined className="text-lg" />
</div>
{/* Forwards to with subpath */}
<div className="flex-1 bg-gray-50 border border-gray-200 rounded-lg p-3">
<div className="text-sm text-gray-600 mb-2">Forwards to</div>
<code className="font-mono text-sm text-gray-900">
{targetValue}
<span className="text-blue-600">/v1/text-to-image/base/model</span>
</code>
</div>
</div>
{/* Note */}
<div className="mt-3 text-sm text-gray-600">
Any path after {pathValue} will be appended to the target URL
</div>
</div>
</>
)}
{!includeSubpath && (
<div className="mt-4 p-3 bg-blue-50 rounded-md border border-blue-200">
<div className="flex items-start">
<InfoCircleOutlined className="text-blue-500 mt-0.5 mr-2 flex-shrink-0" />
<div className="text-sm text-blue-700">
<span className="font-medium">Not seeing the routing you wanted?</span> Try enabling "Include Subpaths" above - this allows subroutes like <code className="bg-blue-100 px-1 py-0.5 rounded font-mono text-xs">/api/v1/models</code> to be forwarded automatically.
</div>
</div>
</div>
)}
</div>
</Card>
);
};
export default RoutePreview;