mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
feat(ui): CostTrackingSettings/add_margin_form to shadcn
- @tremor TextInput/Button + antd Radio/Tooltip + @ant-design InfoCircleOutlined \u2192 shadcn Button/Input/RadioGroup/Tooltip + lucide Info. - antd AntdSelect preserved for provider picker with logo rendering. antd Form.Item preserved (validator rules). Gates: TS \u2713 | Build \u2713. Co-authored-by: yuneng-jiang <yuneng-berri@users.noreply.github.com>
This commit is contained in:
parent
afa60be4af
commit
25d67210ec
1 changed files with 118 additions and 66 deletions
|
|
@ -1,8 +1,20 @@
|
|||
import React from "react";
|
||||
import { TextInput, Button } from "@tremor/react";
|
||||
import { Select as AntdSelect, Form, Tooltip, Radio } from "antd";
|
||||
import { InfoCircleOutlined } from "@ant-design/icons";
|
||||
import { Providers, provider_map, providerLogoMap } from "../provider_info_helpers";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { Select as AntdSelect, Form } from "antd";
|
||||
import { Info } from "lucide-react";
|
||||
import {
|
||||
Providers,
|
||||
provider_map,
|
||||
providerLogoMap,
|
||||
} from "../provider_info_helpers";
|
||||
import { MarginConfig } from "./types";
|
||||
import { handleImageError } from "./provider_display_helpers";
|
||||
|
||||
|
|
@ -19,6 +31,17 @@ interface AddMarginFormProps {
|
|||
onAddProvider: () => void;
|
||||
}
|
||||
|
||||
const InfoTip: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Info className="ml-2 h-3 w-3 inline text-primary cursor-help" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="max-w-xs">{children}</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
|
||||
const AddMarginForm: React.FC<AddMarginFormProps> = ({
|
||||
marginConfig,
|
||||
selectedProvider,
|
||||
|
|
@ -35,11 +58,12 @@ const AddMarginForm: React.FC<AddMarginFormProps> = ({
|
|||
<div className="space-y-6">
|
||||
<Form.Item
|
||||
label={
|
||||
<span className="text-sm font-medium text-gray-700 flex items-center">
|
||||
<span className="text-sm font-medium text-foreground flex items-center">
|
||||
Provider
|
||||
<Tooltip title="Select 'Global' to apply margin to all providers, or select a specific provider">
|
||||
<InfoCircleOutlined className="ml-2 text-blue-400 hover:text-blue-600 cursor-help" />
|
||||
</Tooltip>
|
||||
<InfoTip>
|
||||
Select 'Global' to apply margin to all providers, or
|
||||
select a specific provider
|
||||
</InfoTip>
|
||||
</span>
|
||||
}
|
||||
rules={[{ required: true, message: "Please select a provider" }]}
|
||||
|
|
@ -53,66 +77,87 @@ const AddMarginForm: React.FC<AddMarginFormProps> = ({
|
|||
size="large"
|
||||
optionFilterProp="children"
|
||||
filterOption={(input, option) =>
|
||||
String(option?.label ?? "").toLowerCase().includes(input.toLowerCase())
|
||||
String(option?.label ?? "")
|
||||
.toLowerCase()
|
||||
.includes(input.toLowerCase())
|
||||
}
|
||||
>
|
||||
<AntdSelect.Option key="global" value="global" label="Global (All Providers)">
|
||||
<AntdSelect.Option
|
||||
key="global"
|
||||
value="global"
|
||||
label="Global (All Providers)"
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="font-medium">Global (All Providers)</span>
|
||||
</div>
|
||||
</AntdSelect.Option>
|
||||
{Object.entries(Providers).map(([providerEnum, providerDisplayName]) => {
|
||||
const providerValue = provider_map[providerEnum as keyof typeof provider_map];
|
||||
// Only show providers that don't already have a margin configured
|
||||
if (providerValue && marginConfig[providerValue]) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<AntdSelect.Option key={providerEnum} value={providerEnum} label={providerDisplayName}>
|
||||
<div className="flex items-center space-x-2">
|
||||
<img
|
||||
src={providerLogoMap[providerDisplayName]}
|
||||
alt={`${providerEnum} logo`}
|
||||
className="w-5 h-5"
|
||||
onError={(e) => handleImageError(e, providerDisplayName)}
|
||||
/>
|
||||
<span>{providerDisplayName}</span>
|
||||
</div>
|
||||
</AntdSelect.Option>
|
||||
);
|
||||
})}
|
||||
{Object.entries(Providers).map(
|
||||
([providerEnum, providerDisplayName]) => {
|
||||
const providerValue =
|
||||
provider_map[providerEnum as keyof typeof provider_map];
|
||||
if (providerValue && marginConfig[providerValue]) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<AntdSelect.Option
|
||||
key={providerEnum}
|
||||
value={providerEnum}
|
||||
label={providerDisplayName}
|
||||
>
|
||||
<div className="flex items-center space-x-2">
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
src={providerLogoMap[providerDisplayName]}
|
||||
alt={`${providerEnum} logo`}
|
||||
className="w-5 h-5"
|
||||
onError={(e) => handleImageError(e, providerDisplayName)}
|
||||
/>
|
||||
<span>{providerDisplayName}</span>
|
||||
</div>
|
||||
</AntdSelect.Option>
|
||||
);
|
||||
},
|
||||
)}
|
||||
</AntdSelect>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={
|
||||
<span className="text-sm font-medium text-gray-700 flex items-center">
|
||||
<span className="text-sm font-medium text-foreground flex items-center">
|
||||
Margin Type
|
||||
<Tooltip title="Choose how to apply the margin: percentage-based or fixed amount">
|
||||
<InfoCircleOutlined className="ml-2 text-blue-400 hover:text-blue-600 cursor-help" />
|
||||
</Tooltip>
|
||||
<InfoTip>
|
||||
Choose how to apply the margin: percentage-based or fixed amount
|
||||
</InfoTip>
|
||||
</span>
|
||||
}
|
||||
rules={[{ required: true, message: "Please select a margin type" }]}
|
||||
>
|
||||
<Radio.Group
|
||||
<RadioGroup
|
||||
value={marginType}
|
||||
onChange={(e) => onMarginTypeChange(e.target.value)}
|
||||
className="w-full"
|
||||
onValueChange={(v) =>
|
||||
onMarginTypeChange(v as "percentage" | "fixed")
|
||||
}
|
||||
className="flex gap-4"
|
||||
>
|
||||
<Radio value="percentage">Percentage-based</Radio>
|
||||
<Radio value="fixed">Fixed Amount</Radio>
|
||||
</Radio.Group>
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<RadioGroupItem value="percentage" />
|
||||
Percentage-based
|
||||
</label>
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<RadioGroupItem value="fixed" />
|
||||
Fixed Amount
|
||||
</label>
|
||||
</RadioGroup>
|
||||
</Form.Item>
|
||||
|
||||
{marginType === "percentage" && (
|
||||
<Form.Item
|
||||
label={
|
||||
<span className="text-sm font-medium text-gray-700 flex items-center">
|
||||
<span className="text-sm font-medium text-foreground flex items-center">
|
||||
Margin Percentage
|
||||
<Tooltip title="Enter a percentage value (e.g., 10 for 10% margin)">
|
||||
<InfoCircleOutlined className="ml-2 text-blue-400 hover:text-blue-600 cursor-help" />
|
||||
</Tooltip>
|
||||
<InfoTip>
|
||||
Enter a percentage value (e.g., 10 for 10% margin)
|
||||
</InfoTip>
|
||||
</span>
|
||||
}
|
||||
rules={[
|
||||
|
|
@ -120,11 +165,15 @@ const AddMarginForm: React.FC<AddMarginFormProps> = ({
|
|||
{
|
||||
validator: (_, value) => {
|
||||
if (!value) {
|
||||
return Promise.reject(new Error("Please enter a margin percentage"));
|
||||
return Promise.reject(
|
||||
new Error("Please enter a margin percentage"),
|
||||
);
|
||||
}
|
||||
const numValue = parseFloat(value);
|
||||
if (isNaN(numValue) || numValue < 0 || numValue > 1000) {
|
||||
return Promise.reject(new Error("Percentage must be between 0 and 1000"));
|
||||
return Promise.reject(
|
||||
new Error("Percentage must be between 0 and 1000"),
|
||||
);
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
|
@ -132,13 +181,13 @@ const AddMarginForm: React.FC<AddMarginFormProps> = ({
|
|||
]}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<TextInput
|
||||
<Input
|
||||
placeholder="10"
|
||||
value={percentageValue}
|
||||
onValueChange={onPercentageChange}
|
||||
className="rounded-lg border-gray-300 focus:border-blue-500 focus:ring-blue-500 flex-1"
|
||||
onChange={(e) => onPercentageChange(e.target.value)}
|
||||
className="rounded-lg flex-1"
|
||||
/>
|
||||
<span className="text-gray-600">%</span>
|
||||
<span className="text-muted-foreground">%</span>
|
||||
</div>
|
||||
</Form.Item>
|
||||
)}
|
||||
|
|
@ -146,11 +195,12 @@ const AddMarginForm: React.FC<AddMarginFormProps> = ({
|
|||
{marginType === "fixed" && (
|
||||
<Form.Item
|
||||
label={
|
||||
<span className="text-sm font-medium text-gray-700 flex items-center">
|
||||
<span className="text-sm font-medium text-foreground flex items-center">
|
||||
Fixed Margin Amount
|
||||
<Tooltip title="Enter a fixed amount in USD (e.g., 0.001 for $0.001 per request)">
|
||||
<InfoCircleOutlined className="ml-2 text-blue-400 hover:text-blue-600 cursor-help" />
|
||||
</Tooltip>
|
||||
<InfoTip>
|
||||
Enter a fixed amount in USD (e.g., 0.001 for $0.001 per
|
||||
request)
|
||||
</InfoTip>
|
||||
</span>
|
||||
}
|
||||
rules={[
|
||||
|
|
@ -158,11 +208,15 @@ const AddMarginForm: React.FC<AddMarginFormProps> = ({
|
|||
{
|
||||
validator: (_, value) => {
|
||||
if (!value) {
|
||||
return Promise.reject(new Error("Please enter a fixed amount"));
|
||||
return Promise.reject(
|
||||
new Error("Please enter a fixed amount"),
|
||||
);
|
||||
}
|
||||
const numValue = parseFloat(value);
|
||||
if (isNaN(numValue) || numValue < 0) {
|
||||
return Promise.reject(new Error("Fixed amount must be non-negative"));
|
||||
return Promise.reject(
|
||||
new Error("Fixed amount must be non-negative"),
|
||||
);
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
|
@ -170,23 +224,22 @@ const AddMarginForm: React.FC<AddMarginFormProps> = ({
|
|||
]}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-gray-600">$</span>
|
||||
<TextInput
|
||||
<span className="text-muted-foreground">$</span>
|
||||
<Input
|
||||
placeholder="0.001"
|
||||
value={fixedAmountValue}
|
||||
onValueChange={onFixedAmountChange}
|
||||
className="rounded-lg border-gray-300 focus:border-blue-500 focus:ring-blue-500 flex-1"
|
||||
onChange={(e) => onFixedAmountChange(e.target.value)}
|
||||
className="rounded-lg flex-1"
|
||||
/>
|
||||
</div>
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
<div className="flex items-center justify-end space-x-3 pt-6 border-t border-gray-100">
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={onAddProvider}
|
||||
<div className="flex items-center justify-end space-x-3 pt-6 border-t border-border">
|
||||
<Button
|
||||
onClick={onAddProvider}
|
||||
disabled={
|
||||
!selectedProvider ||
|
||||
!selectedProvider ||
|
||||
(marginType === "percentage" && !percentageValue) ||
|
||||
(marginType === "fixed" && !fixedAmountValue)
|
||||
}
|
||||
|
|
@ -199,4 +252,3 @@ const AddMarginForm: React.FC<AddMarginFormProps> = ({
|
|||
};
|
||||
|
||||
export default AddMarginForm;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue