mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
feat(ui): mcp_tools/mcp_server_cost_config to shadcn
- antd InputNumber/Collapse/Badge/Tooltip + @tremor Card/Title/Text + @ant-design InfoCircle/Dollar/Tool \u2192 shadcn Accordion/Badge/Card/ Input/Tooltip + lucide DollarSign/Info/Wrench. - Custom MoneyInput wrapper ties a '$' prefix chip to a shadcn Input type='number' since Radix Input has no prefix slot. - Emerald badge + blue summary callout are categorical; file added to .eslintrc.json overrides. Gates: TS \u2713 | Build \u2713. Co-authored-by: yuneng-jiang <yuneng-berri@users.noreply.github.com>
This commit is contained in:
parent
fd5e3d191a
commit
3224841391
2 changed files with 146 additions and 87 deletions
|
|
@ -67,6 +67,7 @@
|
|||
"src/components/guardrails/GuardrailTestResults.tsx",
|
||||
"src/components/guardrails/GuardrailTestPlayground.tsx",
|
||||
"src/components/vector_store_management/S3VectorsConfig.tsx",
|
||||
"src/components/mcp_tools/mcp_server_cost_config.tsx",
|
||||
"src/components/prompts/prompt_editor_view/PromptEditorHeader.tsx",
|
||||
"src/components/prompts/prompt_editor_view/VersionHistorySidePanel.tsx",
|
||||
"src/app/(dashboard)/teams/components/TeamsTable/ModelsCell.tsx",
|
||||
|
|
|
|||
|
|
@ -1,16 +1,71 @@
|
|||
import React from "react";
|
||||
import { Tooltip, InputNumber, Collapse, Badge } from "antd";
|
||||
import { InfoCircleOutlined, DollarOutlined, ToolOutlined } from "@ant-design/icons";
|
||||
import { Card, Title, Text } from "@tremor/react";
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "@/components/ui/accordion";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { DollarSign, Info, Wrench } from "lucide-react";
|
||||
import { MCPServerCostInfo } from "./types";
|
||||
|
||||
interface MCPServerCostConfigProps {
|
||||
value?: MCPServerCostInfo;
|
||||
onChange?: (value: MCPServerCostInfo) => void;
|
||||
tools?: any[]; // Receive tools from connection component
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
tools?: any[];
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const InfoTip: React.FC<{ children: React.ReactNode }> = ({ children }) => (
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Info className="ml-1 h-3 w-3 inline text-muted-foreground" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="max-w-xs">{children}</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
|
||||
const MoneyInput: React.FC<{
|
||||
value?: number | null;
|
||||
onChange: (v: number | null) => void;
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
}> = ({ value, onChange, placeholder, disabled, className }) => (
|
||||
<div className={`inline-flex items-center rounded-md border border-input overflow-hidden ${className ?? ""}`}>
|
||||
<span className="px-2 text-sm bg-muted text-muted-foreground">$</span>
|
||||
<Input
|
||||
type="number"
|
||||
min={0}
|
||||
step={0.0001}
|
||||
value={value ?? ""}
|
||||
onChange={(e) => {
|
||||
const raw = e.target.value;
|
||||
if (raw === "") {
|
||||
onChange(null);
|
||||
return;
|
||||
}
|
||||
const n = Number(raw);
|
||||
onChange(isNaN(n) ? null : n);
|
||||
}}
|
||||
placeholder={placeholder}
|
||||
disabled={disabled}
|
||||
className="border-0 rounded-none shadow-none"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
const MCPServerCostConfig: React.FC<MCPServerCostConfigProps> = ({
|
||||
value = {},
|
||||
onChange,
|
||||
|
|
@ -18,136 +73,139 @@ const MCPServerCostConfig: React.FC<MCPServerCostConfigProps> = ({
|
|||
disabled = false,
|
||||
}) => {
|
||||
const handleDefaultCostChange = (defaultCost: number | null) => {
|
||||
const updated = {
|
||||
onChange?.({
|
||||
...value,
|
||||
default_cost_per_query: defaultCost,
|
||||
};
|
||||
onChange?.(updated);
|
||||
});
|
||||
};
|
||||
|
||||
const handleToolCostChange = (toolName: string, cost: number | null) => {
|
||||
const updated = {
|
||||
onChange?.({
|
||||
...value,
|
||||
tool_name_to_cost_per_query: {
|
||||
...value.tool_name_to_cost_per_query,
|
||||
[toolName]: cost,
|
||||
},
|
||||
};
|
||||
onChange?.(updated);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<Card className="p-4">
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<DollarOutlined className="text-green-600" />
|
||||
<Title>Cost Configuration</Title>
|
||||
<Tooltip title="Configure costs for this MCP server's tool calls. Set a default rate and per-tool overrides.">
|
||||
<InfoCircleOutlined className="text-gray-400" />
|
||||
</Tooltip>
|
||||
<DollarSign className="h-4 w-4 text-emerald-600 dark:text-emerald-400" />
|
||||
<h3 className="text-lg font-semibold">Cost Configuration</h3>
|
||||
<InfoTip>
|
||||
Configure costs for this MCP server's tool calls. Set a
|
||||
default rate and per-tool overrides.
|
||||
</InfoTip>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
<label className="block text-sm font-medium text-foreground mb-2">
|
||||
Default Cost per Query ($)
|
||||
<Tooltip title="Default cost charged for each tool call to this server.">
|
||||
<InfoCircleOutlined className="ml-1 text-gray-400" />
|
||||
</Tooltip>
|
||||
<InfoTip>
|
||||
Default cost charged for each tool call to this server.
|
||||
</InfoTip>
|
||||
</label>
|
||||
<InputNumber
|
||||
min={0}
|
||||
step={0.0001}
|
||||
precision={4}
|
||||
placeholder="0.0000"
|
||||
<MoneyInput
|
||||
value={value.default_cost_per_query}
|
||||
onChange={handleDefaultCostChange}
|
||||
placeholder="0.0000"
|
||||
disabled={disabled}
|
||||
style={{ width: "200px" }}
|
||||
addonBefore="$"
|
||||
className="w-[200px]"
|
||||
/>
|
||||
<Text className="block mt-1 text-gray-500 text-sm">
|
||||
<p className="mt-1 text-muted-foreground text-sm">
|
||||
Set a default cost for all tool calls to this server
|
||||
</Text>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{tools.length > 0 && (
|
||||
<div className="space-y-4">
|
||||
<label className="block text-sm font-medium text-gray-700">
|
||||
<label className="block text-sm font-medium text-foreground">
|
||||
Tool-Specific Costs ($)
|
||||
<Tooltip title="Override the default cost for specific tools. Leave blank to use the default rate.">
|
||||
<InfoCircleOutlined className="ml-1 text-gray-400" />
|
||||
</Tooltip>
|
||||
<InfoTip>
|
||||
Override the default cost for specific tools. Leave blank
|
||||
to use the default rate.
|
||||
</InfoTip>
|
||||
</label>
|
||||
<Collapse
|
||||
items={[
|
||||
{
|
||||
key: "1",
|
||||
label: (
|
||||
<div className="flex items-center">
|
||||
<ToolOutlined className="mr-2 text-blue-500" />
|
||||
<span className="font-medium">Available Tools</span>
|
||||
<Badge
|
||||
count={tools.length}
|
||||
style={{
|
||||
backgroundColor: "#52c41a",
|
||||
marginLeft: "8px",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
children: (
|
||||
<div className="space-y-3 max-h-64 overflow-y-auto">
|
||||
{tools.map((tool, index) => (
|
||||
<div key={index} className="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
|
||||
<div className="flex-1">
|
||||
<Text className="font-medium text-gray-900">{tool.name}</Text>
|
||||
{tool.description && (
|
||||
<Text className="text-gray-500 text-sm block mt-1">{tool.description}</Text>
|
||||
)}
|
||||
</div>
|
||||
<div className="ml-4">
|
||||
<InputNumber
|
||||
min={0}
|
||||
step={0.0001}
|
||||
precision={4}
|
||||
placeholder="Use default"
|
||||
value={value.tool_name_to_cost_per_query?.[tool.name]}
|
||||
onChange={(cost) => handleToolCostChange(tool.name, cost)}
|
||||
disabled={disabled}
|
||||
style={{ width: "120px" }}
|
||||
addonBefore="$"
|
||||
/>
|
||||
</div>
|
||||
<Accordion type="single" collapsible>
|
||||
<AccordionItem value="tools">
|
||||
<AccordionTrigger>
|
||||
<div className="flex items-center gap-2">
|
||||
<Wrench className="h-4 w-4 text-blue-500" />
|
||||
<span className="font-medium">Available Tools</span>
|
||||
<Badge className="bg-emerald-100 text-emerald-700 dark:bg-emerald-950 dark:text-emerald-300">
|
||||
{tools.length}
|
||||
</Badge>
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<div className="space-y-3 max-h-64 overflow-y-auto">
|
||||
{tools.map((tool, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex items-center justify-between p-3 bg-muted rounded-lg"
|
||||
>
|
||||
<div className="flex-1">
|
||||
<span className="font-medium text-foreground">
|
||||
{tool.name}
|
||||
</span>
|
||||
{tool.description && (
|
||||
<span className="text-muted-foreground text-sm block mt-1">
|
||||
{tool.description}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<div className="ml-4">
|
||||
<MoneyInput
|
||||
value={
|
||||
value.tool_name_to_cost_per_query?.[tool.name]
|
||||
}
|
||||
onChange={(cost) =>
|
||||
handleToolCostChange(tool.name, cost)
|
||||
}
|
||||
placeholder="Use default"
|
||||
disabled={disabled}
|
||||
className="w-[160px]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{(value.default_cost_per_query ||
|
||||
(value.tool_name_to_cost_per_query && Object.keys(value.tool_name_to_cost_per_query).length > 0)) && (
|
||||
<div className="mt-6 p-4 bg-blue-50 border border-blue-200 rounded-lg">
|
||||
<Text className="text-blue-800 font-medium">Cost Summary:</Text>
|
||||
(value.tool_name_to_cost_per_query &&
|
||||
Object.keys(value.tool_name_to_cost_per_query).length > 0)) && (
|
||||
<div className="mt-6 p-4 bg-blue-50 dark:bg-blue-950/30 border border-blue-200 dark:border-blue-900 rounded-lg">
|
||||
<p className="text-blue-800 dark:text-blue-200 font-medium">
|
||||
Cost Summary:
|
||||
</p>
|
||||
<div className="mt-2 space-y-1">
|
||||
{value.default_cost_per_query && (
|
||||
<Text className="text-blue-700">
|
||||
• Default cost: ${value.default_cost_per_query.toFixed(4)} per query
|
||||
</Text>
|
||||
<p className="text-blue-700 dark:text-blue-300">
|
||||
• Default cost: $
|
||||
{value.default_cost_per_query.toFixed(4)} per query
|
||||
</p>
|
||||
)}
|
||||
{value.tool_name_to_cost_per_query &&
|
||||
Object.entries(value.tool_name_to_cost_per_query).map(
|
||||
([toolName, cost]) =>
|
||||
cost !== null &&
|
||||
cost !== undefined && (
|
||||
<Text key={toolName} className="text-blue-700">
|
||||
<p
|
||||
key={toolName}
|
||||
className="text-blue-700 dark:text-blue-300"
|
||||
>
|
||||
• {toolName}: ${cost.toFixed(4)} per query
|
||||
</Text>
|
||||
</p>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue