mirror of
https://github.com/tiennm99/litellm.git
synced 2026-07-12 11:07:15 +00:00
* ui - use common team dropdown component * re-use team component * rename org field on add model * handle add model submit * working view model_id and team_id on root models page * cleaner * show all fields * working model info view * working team info selector * clean up team id
This commit is contained in:
@@ -247,6 +247,7 @@ export default function CreateKeyPage() {
|
||||
modelData={modelData}
|
||||
setModelData={setModelData}
|
||||
premiumUser={premiumUser}
|
||||
teams={teams}
|
||||
/>
|
||||
) : page == "llm-playground" ? (
|
||||
<ChatUI
|
||||
|
||||
@@ -3,16 +3,20 @@ import { Form, Switch, Select, Input } from "antd";
|
||||
import { Text, Button, Accordion, AccordionHeader, AccordionBody, TextInput } from "@tremor/react";
|
||||
import { Row, Col, Typography, Card } from "antd";
|
||||
import TextArea from "antd/es/input/TextArea";
|
||||
import { Team } from "../key_team_helpers/key_list";
|
||||
import TeamDropdown from "../common_components/team_dropdown";
|
||||
const { Link } = Typography;
|
||||
|
||||
interface AdvancedSettingsProps {
|
||||
showAdvancedSettings: boolean;
|
||||
setShowAdvancedSettings: (show: boolean) => void;
|
||||
teams?: Team[] | null;
|
||||
}
|
||||
|
||||
const AdvancedSettings: React.FC<AdvancedSettingsProps> = ({
|
||||
showAdvancedSettings,
|
||||
setShowAdvancedSettings,
|
||||
teams,
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
const [customPricing, setCustomPricing] = React.useState(false);
|
||||
@@ -87,6 +91,14 @@ const AdvancedSettings: React.FC<AdvancedSettingsProps> = ({
|
||||
</AccordionHeader>
|
||||
<AccordionBody>
|
||||
<div className="bg-white rounded-lg">
|
||||
<Form.Item
|
||||
label="Team"
|
||||
name="team_id"
|
||||
className="mb-4"
|
||||
>
|
||||
<TeamDropdown teams={teams} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="Custom Pricing"
|
||||
name="custom_pricing"
|
||||
|
||||
@@ -76,6 +76,9 @@ export const handleAddModelSubmit = async (
|
||||
// Add key-value pair to model_info dictionary
|
||||
modelInfoObj[key] = value;
|
||||
}
|
||||
else if (key === "team_id") {
|
||||
modelInfoObj["team_id"] = value;
|
||||
}
|
||||
else if (key === "custom_model_name") {
|
||||
litellmParamsObj["model"] = value;
|
||||
} else if (key == "litellm_extra_params") {
|
||||
|
||||
@@ -24,7 +24,7 @@ const ProviderSpecificFields: React.FC<ProviderSpecificFieldsProps> = ({
|
||||
return (
|
||||
<>
|
||||
{selectedProviderEnum === Providers.OpenAI && (
|
||||
<Form.Item label="Organization ID" name="organization">
|
||||
<Form.Item label="OpenAI Organization ID" name="organization">
|
||||
<TextInput placeholder="[OPTIONAL] my-unique-org" />
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import React from "react";
|
||||
import { Select } from "antd";
|
||||
import { Team } from "../key_team_helpers/key_list";
|
||||
|
||||
interface TeamDropdownProps {
|
||||
teams?: Team[] | null;
|
||||
value?: string;
|
||||
onChange?: (value: string) => void;
|
||||
}
|
||||
|
||||
const TeamDropdown: React.FC<TeamDropdownProps> = ({ teams, value, onChange }) => {
|
||||
return (
|
||||
<Select
|
||||
showSearch
|
||||
placeholder="Search or select a team"
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
filterOption={(input, option) => {
|
||||
if (!option) return false;
|
||||
const teamAlias = option.children?.[0]?.props?.children || '';
|
||||
return teamAlias.toLowerCase().includes(input.toLowerCase());
|
||||
}}
|
||||
optionFilterProp="children"
|
||||
>
|
||||
{teams?.map((team) => (
|
||||
<Select.Option key={team.team_id} value={team.team_id}>
|
||||
<span className="font-medium">{team.team_alias}</span>{" "}
|
||||
<span className="text-gray-500">({team.team_id})</span>
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
);
|
||||
};
|
||||
|
||||
export default TeamDropdown;
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
getGuardrailsList,
|
||||
} from "./networking";
|
||||
import { Team } from "./key_team_helpers/key_list";
|
||||
import TeamDropdown from "./common_components/team_dropdown";
|
||||
import { InfoCircleOutlined } from '@ant-design/icons';
|
||||
import { Tooltip } from 'antd';
|
||||
|
||||
@@ -292,28 +293,7 @@ const CreateKey: React.FC<CreateKeyProps> = ({
|
||||
initialValue={team ? team.team_id : null}
|
||||
className="mt-8"
|
||||
>
|
||||
<Select
|
||||
showSearch
|
||||
placeholder="Search or select a team"
|
||||
onChange={(value) => {
|
||||
form.setFieldValue('team_id', value);
|
||||
const selectedTeam = teams?.find(team => team.team_id === value);
|
||||
setSelectedCreateKeyTeam(selectedTeam || null);
|
||||
}}
|
||||
filterOption={(input, option) => {
|
||||
if (!option) return false;
|
||||
const optionValue = option.children?.toString() || '';
|
||||
return optionValue.toLowerCase().includes(input.toLowerCase());
|
||||
}}
|
||||
optionFilterProp="children"
|
||||
>
|
||||
{teams?.map((team) => (
|
||||
<Select.Option key={team.team_id} value={team.team_id}>
|
||||
<span className="font-medium">{team.team_alias}</span>{" "}
|
||||
<span className="text-gray-500">({team.team_id})</span>
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
<TeamDropdown teams={teams} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,333 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import {
|
||||
Card,
|
||||
Title,
|
||||
Text,
|
||||
Tab,
|
||||
TabList,
|
||||
TabGroup,
|
||||
TabPanel,
|
||||
TabPanels,
|
||||
Grid,
|
||||
Badge,
|
||||
Button as TremorButton,
|
||||
} from "@tremor/react";
|
||||
import { ArrowLeftIcon, TrashIcon } from "@heroicons/react/outline";
|
||||
import { modelDeleteCall, modelUpdateCall } from "./networking";
|
||||
import { Button, Form, Input, InputNumber, message, Select } from "antd";
|
||||
import EditModelModal from "./edit_model/edit_model_modal";
|
||||
import { getProviderLogoAndName } from "./provider_info_helpers";
|
||||
|
||||
interface ModelInfoViewProps {
|
||||
modelId: string;
|
||||
onClose: () => void;
|
||||
modelData: any;
|
||||
accessToken: string | null;
|
||||
userID: string | null;
|
||||
userRole: string | null;
|
||||
editModel: boolean;
|
||||
}
|
||||
|
||||
export default function ModelInfoView({
|
||||
modelId,
|
||||
onClose,
|
||||
modelData,
|
||||
accessToken,
|
||||
userID,
|
||||
userRole,
|
||||
editModel
|
||||
}: ModelInfoViewProps) {
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [form] = Form.useForm();
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||
|
||||
const canEditModel = userRole === "Admin";
|
||||
|
||||
if (!modelData) {
|
||||
return (
|
||||
<div className="p-4">
|
||||
<Button
|
||||
icon={ArrowLeftIcon}
|
||||
onClick={onClose}
|
||||
className="mb-4"
|
||||
>
|
||||
Back to Models
|
||||
</Button>
|
||||
<Text>Model not found</Text>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const handleDelete = async () => {
|
||||
try {
|
||||
if (!accessToken) return;
|
||||
await modelDeleteCall(accessToken, modelId);
|
||||
message.success("Model deleted successfully");
|
||||
onClose();
|
||||
} catch (error) {
|
||||
console.error("Error deleting the model:", error);
|
||||
message.error("Failed to delete model");
|
||||
}
|
||||
};
|
||||
|
||||
const handleModelUpdate = async (values: any) => {
|
||||
try {
|
||||
if (!accessToken) return;
|
||||
|
||||
let payload = {
|
||||
litellm_params: Object.keys(values.litellm_params).length > 0 ? values.litellm_params : undefined,
|
||||
model_info: values.model_info ? {
|
||||
id: values.model_info.id,
|
||||
} : undefined,
|
||||
};
|
||||
|
||||
await modelUpdateCall(accessToken, payload);
|
||||
message.success("Model updated successfully");
|
||||
setIsEditing(false);
|
||||
} catch (error) {
|
||||
console.error("Error updating model:", error);
|
||||
message.error("Failed to update model");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<div>
|
||||
<Button onClick={onClose} className="mb-4">← Back</Button>
|
||||
<Title>{modelData.model_name}</Title>
|
||||
<Text className="text-gray-500 font-mono">{modelData.model_info.id}</Text>
|
||||
</div>
|
||||
{canEditModel && (
|
||||
<div className="flex gap-2">
|
||||
<TremorButton
|
||||
onClick={() => setIsDeleteModalOpen(true)}
|
||||
color="red"
|
||||
>
|
||||
Delete Model
|
||||
</TremorButton>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<TabGroup>
|
||||
<TabList className="mb-6">
|
||||
<Tab>Overview</Tab>
|
||||
<Tab>Raw JSON</Tab>
|
||||
</TabList>
|
||||
|
||||
<TabPanels>
|
||||
<TabPanel>
|
||||
{/* Overview Grid */}
|
||||
<Grid numItems={1} numItemsSm={2} numItemsLg={3} className="gap-6 mb-6">
|
||||
<Card>
|
||||
<Text>Provider</Text>
|
||||
<div className="mt-2 flex items-center space-x-2">
|
||||
{modelData.provider && (
|
||||
<img
|
||||
src={getProviderLogoAndName(modelData.provider).logo}
|
||||
alt={`${modelData.provider} logo`}
|
||||
className="w-4 h-4"
|
||||
onError={(e) => {
|
||||
// Create a div with provider initial as fallback
|
||||
const target = e.target as HTMLImageElement;
|
||||
const parent = target.parentElement;
|
||||
if (parent) {
|
||||
const fallbackDiv = document.createElement('div');
|
||||
fallbackDiv.className = 'w-4 h-4 rounded-full bg-gray-200 flex items-center justify-center text-xs';
|
||||
fallbackDiv.textContent = modelData.provider?.charAt(0) || '-';
|
||||
parent.replaceChild(fallbackDiv, target);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Title>{modelData.provider || "Not Set"}</Title>
|
||||
</div>
|
||||
</Card>
|
||||
<Card>
|
||||
<Text>LiteLLM Model</Text>
|
||||
<pre>
|
||||
<Title>{modelData.litellm_model_name || "Not Set"}</Title>
|
||||
</pre>
|
||||
</Card>
|
||||
<Card>
|
||||
<Text>Pricing</Text>
|
||||
<div className="mt-2">
|
||||
<Text>Input: ${modelData.input_cost}/1M tokens</Text>
|
||||
<Text>Output: ${modelData.output_cost}/1M tokens</Text>
|
||||
</div>
|
||||
</Card>
|
||||
</Grid>
|
||||
|
||||
{/* Settings Card */}
|
||||
<Card>
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<Title>Model Settings</Title>
|
||||
{(canEditModel && !isEditing) && (
|
||||
<TremorButton
|
||||
onClick={() => setIsEditing(true)}
|
||||
>
|
||||
Edit Settings
|
||||
</TremorButton>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{isEditing ? (
|
||||
<EditModelModal
|
||||
visible={isEditing}
|
||||
onCancel={() => setIsEditing(false)}
|
||||
model={modelData}
|
||||
onSubmit={handleModelUpdate}
|
||||
/>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<Text className="font-medium">Model ID</Text>
|
||||
<div className="font-mono">{modelData.model_info.id}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Text className="font-medium">Public Model Name</Text>
|
||||
<div>{modelData.model_name}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Text className="font-medium">LiteLLM Model Name</Text>
|
||||
<div>{modelData.litellm_model_name}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Text className="font-medium">Input Cost (per 1M tokens)</Text>
|
||||
<div>{modelData.litellm_params?.input_cost_per_token ? (modelData.litellm_params.input_cost_per_token * 1_000_000).toFixed(4) : "Not Set"}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Text className="font-medium">Output Cost (per 1M tokens)</Text>
|
||||
<div>{modelData.litellm_params?.output_cost_per_token ? (modelData.litellm_params.output_cost_per_token * 1_000_000).toFixed(4) : "Not Set"}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Text className="font-medium">API Base</Text>
|
||||
<div>{modelData.litellm_params?.api_base || "Not Set"}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Text className="font-medium">Custom LLM Provider</Text>
|
||||
<div>{modelData.litellm_params?.custom_llm_provider || "Not Set"}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Text className="font-medium">Model</Text>
|
||||
<div>{modelData.litellm_params?.model || "Not Set"}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Text className="font-medium">Organization</Text>
|
||||
<div>{modelData.litellm_params?.organization || "Not Set"}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Text className="font-medium">TPM (Tokens per Minute)</Text>
|
||||
<div>{modelData.litellm_params?.tpm || "Not Set"}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Text className="font-medium">RPM (Requests per Minute)</Text>
|
||||
<div>{modelData.litellm_params?.rpm || "Not Set"}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Text className="font-medium">Max Retries</Text>
|
||||
<div>{modelData.litellm_params?.max_retries || "Not Set"}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Text className="font-medium">Timeout (seconds)</Text>
|
||||
<div>{modelData.litellm_params?.timeout || "Not Set"}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Text className="font-medium">Stream Timeout (seconds)</Text>
|
||||
<div>{modelData.litellm_params?.stream_timeout || "Not Set"}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Text className="font-medium">Team ID</Text>
|
||||
<div>{modelData.model_info.team_id || "Not Set"}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Text className="font-medium">Created At</Text>
|
||||
<div>{modelData.model_info.created_at ? new Date(modelData.model_info.created_at).toLocaleString() : "Not Set"}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Text className="font-medium">Created By</Text>
|
||||
<div>{modelData.model_info.created_by || "Not Set"}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Text className="font-medium">LiteLLM Parameters</Text>
|
||||
<pre className="bg-gray-100 p-2 rounded text-xs overflow-auto mt-1">
|
||||
{JSON.stringify(modelData.cleanedLitellmParams, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel>
|
||||
<Card>
|
||||
<pre className="bg-gray-100 p-4 rounded text-xs overflow-auto">
|
||||
{JSON.stringify(modelData, null, 2)}
|
||||
</pre>
|
||||
</Card>
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</TabGroup>
|
||||
|
||||
{/* Delete Confirmation Modal */}
|
||||
{isDeleteModalOpen && (
|
||||
<div className="fixed z-10 inset-0 overflow-y-auto">
|
||||
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
|
||||
<div className="fixed inset-0 transition-opacity" aria-hidden="true">
|
||||
<div className="absolute inset-0 bg-gray-500 opacity-75"></div>
|
||||
</div>
|
||||
|
||||
<span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">​</span>
|
||||
|
||||
<div className="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full">
|
||||
<div className="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
||||
<div className="sm:flex sm:items-start">
|
||||
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
||||
<h3 className="text-lg leading-6 font-medium text-gray-900">
|
||||
Delete Model
|
||||
</h3>
|
||||
<div className="mt-2">
|
||||
<p className="text-sm text-gray-500">
|
||||
Are you sure you want to delete this model?
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
|
||||
<Button
|
||||
onClick={handleDelete}
|
||||
className="ml-2"
|
||||
danger
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
<Button onClick={() => setIsDeleteModalOpen(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user