forked from phoenix/litellm-mirror
Merge pull request #3572 from powerhouseofthecell/feature/enforce-unique-key-and-team-aliases
enforce unique key and team aliases in the ui
This commit is contained in:
commit
69068f9577
2 changed files with 495 additions and 339 deletions
|
@ -2,8 +2,17 @@
|
|||
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { Button, TextInput, Grid, Col } from "@tremor/react";
|
||||
import { Card, Metric, Text, Title, Subtitle, Accordion, AccordionHeader, AccordionBody, } from "@tremor/react";
|
||||
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
||||
import {
|
||||
Card,
|
||||
Metric,
|
||||
Text,
|
||||
Title,
|
||||
Subtitle,
|
||||
Accordion,
|
||||
AccordionHeader,
|
||||
AccordionBody,
|
||||
} from "@tremor/react";
|
||||
import { CopyToClipboard } from "react-copy-to-clipboard";
|
||||
import {
|
||||
Button as Button2,
|
||||
Modal,
|
||||
|
@ -13,7 +22,11 @@ import {
|
|||
Select,
|
||||
message,
|
||||
} from "antd";
|
||||
import { keyCreateCall, slackBudgetAlertsHealthCheck, modelAvailableCall } from "./networking";
|
||||
import {
|
||||
keyCreateCall,
|
||||
slackBudgetAlertsHealthCheck,
|
||||
modelAvailableCall,
|
||||
} from "./networking";
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
|
@ -59,7 +72,11 @@ const CreateKey: React.FC<CreateKeyProps> = ({
|
|||
}
|
||||
|
||||
if (accessToken !== null) {
|
||||
const model_available = await modelAvailableCall(accessToken, userID, userRole);
|
||||
const model_available = await modelAvailableCall(
|
||||
accessToken,
|
||||
userID,
|
||||
userRole
|
||||
);
|
||||
let available_model_names = model_available["data"].map(
|
||||
(element: { id: string }) => element.id
|
||||
);
|
||||
|
@ -76,6 +93,19 @@ const CreateKey: React.FC<CreateKeyProps> = ({
|
|||
|
||||
const handleCreate = async (formValues: Record<string, any>) => {
|
||||
try {
|
||||
const newKeyAlias = formValues?.key_alias ?? "";
|
||||
const newKeyTeamId = formValues?.team_id ?? null;
|
||||
const existingKeyAliases =
|
||||
data
|
||||
?.filter((k) => k.team_id === newKeyTeamId)
|
||||
.map((k) => k.key_alias) ?? [];
|
||||
|
||||
if (existingKeyAliases.includes(newKeyAlias)) {
|
||||
throw new Error(
|
||||
`Key alias ${newKeyAlias} already exists for team with ID ${newKeyTeamId}, please provide another key alias`
|
||||
);
|
||||
}
|
||||
|
||||
message.info("Making API Call");
|
||||
setIsModalVisible(true);
|
||||
const response = await keyCreateCall(accessToken, userID, formValues);
|
||||
|
@ -89,11 +119,12 @@ const CreateKey: React.FC<CreateKeyProps> = ({
|
|||
localStorage.removeItem("userData" + userID);
|
||||
} catch (error) {
|
||||
console.error("Error creating the key:", error);
|
||||
message.error(`Error creating the key: ${error}`, 20);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCopy = () => {
|
||||
message.success('API Key copied to clipboard');
|
||||
message.success("API Key copied to clipboard");
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -120,7 +151,6 @@ const CreateKey: React.FC<CreateKeyProps> = ({
|
|||
setModelsToPick(tempModelsToPick);
|
||||
}, [team, userModels]);
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Button className="mx-auto" onClick={() => setIsModalVisible(true)}>
|
||||
|
@ -145,7 +175,7 @@ const CreateKey: React.FC<CreateKeyProps> = ({
|
|||
<Form.Item
|
||||
label="Key Name"
|
||||
name="key_alias"
|
||||
rules={[{ required: true, message: 'Please input a key name' }]}
|
||||
rules={[{ required: true, message: "Please input a key name" }]}
|
||||
help="required"
|
||||
>
|
||||
<TextInput placeholder="" />
|
||||
|
@ -164,7 +194,7 @@ const CreateKey: React.FC<CreateKeyProps> = ({
|
|||
<Form.Item
|
||||
label="Models"
|
||||
name="models"
|
||||
rules={[{ required: true, message: 'Please select a model' }]}
|
||||
rules={[{ required: true, message: "Please select a model" }]}
|
||||
help="required"
|
||||
>
|
||||
<Select
|
||||
|
@ -173,7 +203,8 @@ const CreateKey: React.FC<CreateKeyProps> = ({
|
|||
style={{ width: "100%" }}
|
||||
onChange={(values) => {
|
||||
// Check if "All Team Models" is selected
|
||||
const isAllTeamModelsSelected = values.includes("all-team-models");
|
||||
const isAllTeamModelsSelected =
|
||||
values.includes("all-team-models");
|
||||
|
||||
// If "All Team Models" is selected, deselect all other models
|
||||
if (isAllTeamModelsSelected) {
|
||||
|
@ -186,15 +217,11 @@ const CreateKey: React.FC<CreateKeyProps> = ({
|
|||
<Option key="all-team-models" value="all-team-models">
|
||||
All Team Models
|
||||
</Option>
|
||||
{
|
||||
modelsToPick.map((model: string) => (
|
||||
(
|
||||
{modelsToPick.map((model: string) => (
|
||||
<Option key={model} value={model}>
|
||||
{model}
|
||||
</Option>
|
||||
)
|
||||
))
|
||||
}
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Accordion className="mt-20 mb-8">
|
||||
|
@ -206,12 +233,19 @@ const CreateKey: React.FC<CreateKeyProps> = ({
|
|||
className="mt-8"
|
||||
label="Max Budget (USD)"
|
||||
name="max_budget"
|
||||
help={`Budget cannot exceed team max budget: $${team?.max_budget !== null && team?.max_budget !== undefined ? team?.max_budget : 'unlimited'}`}
|
||||
help={`Budget cannot exceed team max budget: $${team?.max_budget !== null && team?.max_budget !== undefined ? team?.max_budget : "unlimited"}`}
|
||||
rules={[
|
||||
{
|
||||
validator: async (_, value) => {
|
||||
if (value && team && team.max_budget !== null && value > team.max_budget) {
|
||||
throw new Error(`Budget cannot exceed team max budget: $${team.max_budget}`);
|
||||
if (
|
||||
value &&
|
||||
team &&
|
||||
team.max_budget !== null &&
|
||||
value > team.max_budget
|
||||
) {
|
||||
throw new Error(
|
||||
`Budget cannot exceed team max budget: $${team.max_budget}`
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
@ -223,7 +257,7 @@ const CreateKey: React.FC<CreateKeyProps> = ({
|
|||
className="mt-8"
|
||||
label="Reset Budget"
|
||||
name="budget_duration"
|
||||
help={`Team Reset Budget: ${team?.budget_duration !== null && team?.budget_duration !== undefined ? team?.budget_duration : 'None'}`}
|
||||
help={`Team Reset Budget: ${team?.budget_duration !== null && team?.budget_duration !== undefined ? team?.budget_duration : "None"}`}
|
||||
>
|
||||
<Select defaultValue={null} placeholder="n/a">
|
||||
<Select.Option value="24h">daily</Select.Option>
|
||||
|
@ -234,12 +268,19 @@ const CreateKey: React.FC<CreateKeyProps> = ({
|
|||
className="mt-8"
|
||||
label="Tokens per minute Limit (TPM)"
|
||||
name="tpm_limit"
|
||||
help={`TPM cannot exceed team TPM limit: ${team?.tpm_limit !== null && team?.tpm_limit !== undefined ? team?.tpm_limit : 'unlimited'}`}
|
||||
help={`TPM cannot exceed team TPM limit: ${team?.tpm_limit !== null && team?.tpm_limit !== undefined ? team?.tpm_limit : "unlimited"}`}
|
||||
rules={[
|
||||
{
|
||||
validator: async (_, value) => {
|
||||
if (value && team && team.tpm_limit !== null && value > team.tpm_limit) {
|
||||
throw new Error(`TPM limit cannot exceed team TPM limit: ${team.tpm_limit}`);
|
||||
if (
|
||||
value &&
|
||||
team &&
|
||||
team.tpm_limit !== null &&
|
||||
value > team.tpm_limit
|
||||
) {
|
||||
throw new Error(
|
||||
`TPM limit cannot exceed team TPM limit: ${team.tpm_limit}`
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
@ -251,12 +292,19 @@ const CreateKey: React.FC<CreateKeyProps> = ({
|
|||
className="mt-8"
|
||||
label="Requests per minute Limit (RPM)"
|
||||
name="rpm_limit"
|
||||
help={`RPM cannot exceed team RPM limit: ${team?.rpm_limit !== null && team?.rpm_limit !== undefined ? team?.rpm_limit : 'unlimited'}`}
|
||||
help={`RPM cannot exceed team RPM limit: ${team?.rpm_limit !== null && team?.rpm_limit !== undefined ? team?.rpm_limit : "unlimited"}`}
|
||||
rules={[
|
||||
{
|
||||
validator: async (_, value) => {
|
||||
if (value && team && team.rpm_limit !== null && value > team.rpm_limit) {
|
||||
throw new Error(`RPM limit cannot exceed team RPM limit: ${team.rpm_limit}`);
|
||||
if (
|
||||
value &&
|
||||
team &&
|
||||
team.rpm_limit !== null &&
|
||||
value > team.rpm_limit
|
||||
) {
|
||||
throw new Error(
|
||||
`RPM limit cannot exceed team RPM limit: ${team.rpm_limit}`
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
@ -264,13 +312,19 @@ const CreateKey: React.FC<CreateKeyProps> = ({
|
|||
>
|
||||
<InputNumber step={1} width={400} />
|
||||
</Form.Item>
|
||||
<Form.Item label="Expire Key (eg: 30s, 30h, 30d)" name="duration" className="mt-8">
|
||||
<Form.Item
|
||||
label="Expire Key (eg: 30s, 30h, 30d)"
|
||||
name="duration"
|
||||
className="mt-8"
|
||||
>
|
||||
<TextInput placeholder="" />
|
||||
</Form.Item>
|
||||
<Form.Item label="Metadata" name="metadata">
|
||||
<Input.TextArea rows={4} placeholder="Enter metadata as JSON" />
|
||||
<Input.TextArea
|
||||
rows={4}
|
||||
placeholder="Enter metadata as JSON"
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
</AccordionBody>
|
||||
</Accordion>
|
||||
</>
|
||||
|
@ -288,7 +342,6 @@ const CreateKey: React.FC<CreateKeyProps> = ({
|
|||
footer={null}
|
||||
>
|
||||
<Grid numItems={1} className="gap-2 w-full">
|
||||
|
||||
<Title>Save your Key</Title>
|
||||
<Col numColSpan={1}>
|
||||
<p>
|
||||
|
@ -302,8 +355,19 @@ const CreateKey: React.FC<CreateKeyProps> = ({
|
|||
{apiKey != null ? (
|
||||
<div>
|
||||
<Text className="mt-3">API Key:</Text>
|
||||
<div style={{ background: '#f8f8f8', padding: '10px', borderRadius: '5px', marginBottom: '10px' }}>
|
||||
<pre style={{ wordWrap: 'break-word', whiteSpace: 'normal' }}>{apiKey}</pre>
|
||||
<div
|
||||
style={{
|
||||
background: "#f8f8f8",
|
||||
padding: "10px",
|
||||
borderRadius: "5px",
|
||||
marginBottom: "10px",
|
||||
}}
|
||||
>
|
||||
<pre
|
||||
style={{ wordWrap: "break-word", whiteSpace: "normal" }}
|
||||
>
|
||||
{apiKey}
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<CopyToClipboard text={apiKey} onCopy={handleCopy}>
|
||||
|
@ -317,7 +381,6 @@ const CreateKey: React.FC<CreateKeyProps> = ({
|
|||
<Text>Key being created, this might take 30s</Text>
|
||||
)}
|
||||
</Col>
|
||||
|
||||
</Grid>
|
||||
</Modal>
|
||||
)}
|
||||
|
|
|
@ -2,7 +2,13 @@ import React, { useState, useEffect } from "react";
|
|||
import Link from "next/link";
|
||||
import { Typography } from "antd";
|
||||
import { teamDeleteCall, teamUpdateCall, teamInfoCall } from "./networking";
|
||||
import { InformationCircleIcon, PencilAltIcon, PencilIcon, StatusOnlineIcon, TrashIcon } from "@heroicons/react/outline";
|
||||
import {
|
||||
InformationCircleIcon,
|
||||
PencilAltIcon,
|
||||
PencilIcon,
|
||||
StatusOnlineIcon,
|
||||
TrashIcon,
|
||||
} from "@heroicons/react/outline";
|
||||
import {
|
||||
Button as Button2,
|
||||
Modal,
|
||||
|
@ -46,8 +52,12 @@ interface EditTeamModalProps {
|
|||
onSubmit: (data: FormData) => void; // Assuming FormData is the type of data to be submitted
|
||||
}
|
||||
|
||||
|
||||
import { teamCreateCall, teamMemberAddCall, Member, modelAvailableCall } from "./networking";
|
||||
import {
|
||||
teamCreateCall,
|
||||
teamMemberAddCall,
|
||||
Member,
|
||||
modelAvailableCall,
|
||||
} from "./networking";
|
||||
|
||||
const Team: React.FC<TeamProps> = ({
|
||||
teams,
|
||||
|
@ -63,7 +73,6 @@ const Team: React.FC<TeamProps> = ({
|
|||
const [value, setValue] = useState("");
|
||||
const [editModalVisible, setEditModalVisible] = useState(false);
|
||||
|
||||
|
||||
const [selectedTeam, setSelectedTeam] = useState<null | any>(
|
||||
teams ? teams[0] : null
|
||||
);
|
||||
|
@ -76,8 +85,12 @@ const Team: React.FC<TeamProps> = ({
|
|||
// store team info as {"team_id": team_info_object}
|
||||
const [perTeamInfo, setPerTeamInfo] = useState<Record<string, any>>({});
|
||||
|
||||
|
||||
const EditTeamModal: React.FC<EditTeamModalProps> = ({ visible, onCancel, team, onSubmit }) => {
|
||||
const EditTeamModal: React.FC<EditTeamModalProps> = ({
|
||||
visible,
|
||||
onCancel,
|
||||
team,
|
||||
onSubmit,
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const handleOk = () => {
|
||||
|
@ -114,7 +127,7 @@ const Team: React.FC<TeamProps> = ({
|
|||
<Form.Item
|
||||
label="Team Name"
|
||||
name="team_alias"
|
||||
rules={[{ required: true, message: 'Please input a team name' }]}
|
||||
rules={[{ required: true, message: "Please input a team name" }]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
@ -127,27 +140,21 @@ const Team: React.FC<TeamProps> = ({
|
|||
<Select2.Option key="all-proxy-models" value="all-proxy-models">
|
||||
{"All Proxy Models"}
|
||||
</Select2.Option>
|
||||
{userModels && userModels.map((model) => (
|
||||
{userModels &&
|
||||
userModels.map((model) => (
|
||||
<Select2.Option key={model} value={model}>
|
||||
{model}
|
||||
</Select2.Option>
|
||||
))}
|
||||
|
||||
</Select2>
|
||||
</Form.Item>
|
||||
<Form.Item label="Max Budget (USD)" name="max_budget">
|
||||
<InputNumber step={0.01} precision={2} width={200} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Tokens per minute Limit (TPM)"
|
||||
name="tpm_limit"
|
||||
>
|
||||
<Form.Item label="Tokens per minute Limit (TPM)" name="tpm_limit">
|
||||
<InputNumber step={1} width={400} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Requests per minute Limit (RPM)"
|
||||
name="rpm_limit"
|
||||
>
|
||||
<Form.Item label="Requests per minute Limit (RPM)" name="rpm_limit">
|
||||
<InputNumber step={1} width={400} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
|
@ -224,9 +231,6 @@ const handleEditSubmit = async (formValues: Record<string, any>) => {
|
|||
setIsDeleteModalOpen(true);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
const confirmDelete = async () => {
|
||||
if (teamToDelete == null || teams == null || accessToken == null) {
|
||||
return;
|
||||
|
@ -235,7 +239,9 @@ const handleEditSubmit = async (formValues: Record<string, any>) => {
|
|||
try {
|
||||
await teamDeleteCall(accessToken, teamToDelete);
|
||||
// Successfully completed the deletion. Update the state to trigger a rerender.
|
||||
const filteredData = teams.filter((item) => item.team_id !== teamToDelete);
|
||||
const filteredData = teams.filter(
|
||||
(item) => item.team_id !== teamToDelete
|
||||
);
|
||||
setTeams(filteredData);
|
||||
} catch (error) {
|
||||
console.error("Error deleting the team:", error);
|
||||
|
@ -253,8 +259,6 @@ const handleEditSubmit = async (formValues: Record<string, any>) => {
|
|||
setTeamToDelete(null);
|
||||
};
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const fetchUserModels = async () => {
|
||||
try {
|
||||
|
@ -263,7 +267,11 @@ const handleEditSubmit = async (formValues: Record<string, any>) => {
|
|||
}
|
||||
|
||||
if (accessToken !== null) {
|
||||
const model_available = await modelAvailableCall(accessToken, userID, userRole);
|
||||
const model_available = await modelAvailableCall(
|
||||
accessToken,
|
||||
userID,
|
||||
userRole
|
||||
);
|
||||
let available_model_names = model_available["data"].map(
|
||||
(element: { id: string }) => element.id
|
||||
);
|
||||
|
@ -275,7 +283,6 @@ const handleEditSubmit = async (formValues: Record<string, any>) => {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
const fetchTeamInfo = async () => {
|
||||
try {
|
||||
if (userID === null || userRole === null || accessToken === null) {
|
||||
|
@ -288,7 +295,6 @@ const handleEditSubmit = async (formValues: Record<string, any>) => {
|
|||
|
||||
console.log("fetching team info:");
|
||||
|
||||
|
||||
let _team_id_to_info: Record<string, any> = {};
|
||||
for (let i = 0; i < teams?.length; i++) {
|
||||
let _team_id = teams[i].team_id;
|
||||
|
@ -311,6 +317,15 @@ const handleEditSubmit = async (formValues: Record<string, any>) => {
|
|||
const handleCreate = async (formValues: Record<string, any>) => {
|
||||
try {
|
||||
if (accessToken != null) {
|
||||
const newTeamAlias = formValues?.team_alias;
|
||||
const existingTeamAliases = teams?.map((t) => t.team_alias) ?? [];
|
||||
|
||||
if (existingTeamAliases.includes(newTeamAlias)) {
|
||||
throw new Error(
|
||||
`Team alias ${newTeamAlias} already exists, please pick another alias`
|
||||
);
|
||||
}
|
||||
|
||||
message.info("Creating Team");
|
||||
const response: any = await teamCreateCall(accessToken, formValues);
|
||||
if (teams !== null) {
|
||||
|
@ -364,7 +379,7 @@ const handleEditSubmit = async (formValues: Record<string, any>) => {
|
|||
console.error("Error creating the team:", error);
|
||||
}
|
||||
};
|
||||
console.log(`received teams ${teams}`);
|
||||
console.log(`received teams ${JSON.stringify(teams)}`);
|
||||
return (
|
||||
<div className="w-full mx-4">
|
||||
<Grid numItems={1} className="gap-2 p-8 h-[75vh] w-full mt-2">
|
||||
|
@ -387,47 +402,116 @@ const handleEditSubmit = async (formValues: Record<string, any>) => {
|
|||
{teams && teams.length > 0
|
||||
? teams.map((team: any) => (
|
||||
<TableRow key={team.team_id}>
|
||||
<TableCell style={{ maxWidth: "4px", whiteSpace: "pre-wrap", overflow: "hidden" }}>{team["team_alias"]}</TableCell>
|
||||
<TableCell style={{ maxWidth: "4px", whiteSpace: "pre-wrap", overflow: "hidden" }}>{team["spend"]}</TableCell>
|
||||
<TableCell style={{ maxWidth: "4px", whiteSpace: "pre-wrap", overflow: "hidden" }}>
|
||||
<TableCell
|
||||
style={{
|
||||
maxWidth: "4px",
|
||||
whiteSpace: "pre-wrap",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{team["team_alias"]}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
style={{
|
||||
maxWidth: "4px",
|
||||
whiteSpace: "pre-wrap",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{team["spend"]}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
style={{
|
||||
maxWidth: "4px",
|
||||
whiteSpace: "pre-wrap",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{team["max_budget"] ? team["max_budget"] : "No limit"}
|
||||
</TableCell>
|
||||
<TableCell style={{ maxWidth: "8-x", whiteSpace: "pre-wrap", overflow: "hidden" }}>
|
||||
<TableCell
|
||||
style={{
|
||||
maxWidth: "8-x",
|
||||
whiteSpace: "pre-wrap",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{Array.isArray(team.models) ? (
|
||||
<div style={{ display: "flex", flexDirection: "column" }}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
{team.models.length === 0 ? (
|
||||
<Badge size={"xs"} className="mb-1" color="red">
|
||||
<Text>All Proxy Models</Text>
|
||||
</Badge>
|
||||
) : (
|
||||
team.models.map((model: string, index: number) => (
|
||||
team.models.map(
|
||||
(model: string, index: number) =>
|
||||
model === "all-proxy-models" ? (
|
||||
<Badge key={index} size={"xs"} className="mb-1" color="red">
|
||||
<Badge
|
||||
key={index}
|
||||
size={"xs"}
|
||||
className="mb-1"
|
||||
color="red"
|
||||
>
|
||||
<Text>All Proxy Models</Text>
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge key={index} size={"xs"} className="mb-1" color="blue">
|
||||
<Text>{model.length > 30 ? `${model.slice(0, 30)}...` : model}</Text>
|
||||
<Badge
|
||||
key={index}
|
||||
size={"xs"}
|
||||
className="mb-1"
|
||||
color="blue"
|
||||
>
|
||||
<Text>
|
||||
{model.length > 30
|
||||
? `${model.slice(0, 30)}...`
|
||||
: model}
|
||||
</Text>
|
||||
</Badge>
|
||||
)
|
||||
))
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</TableCell>
|
||||
|
||||
|
||||
<TableCell style={{ maxWidth: "4px", whiteSpace: "pre-wrap", overflow: "hidden" }}>
|
||||
<TableCell
|
||||
style={{
|
||||
maxWidth: "4px",
|
||||
whiteSpace: "pre-wrap",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
<Text>
|
||||
TPM:{" "}
|
||||
{team.tpm_limit ? team.tpm_limit : "Unlimited"}{" "}
|
||||
TPM: {team.tpm_limit ? team.tpm_limit : "Unlimited"}{" "}
|
||||
<br></br>RPM:{" "}
|
||||
{team.rpm_limit ? team.rpm_limit : "Unlimited"}
|
||||
</Text>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Text>{perTeamInfo && team.team_id && perTeamInfo[team.team_id] && perTeamInfo[team.team_id].keys && perTeamInfo[team.team_id].keys.length} Keys</Text>
|
||||
<Text>{perTeamInfo && team.team_id && perTeamInfo[team.team_id] && perTeamInfo[team.team_id].team_info && perTeamInfo[team.team_id].team_info.members_with_roles && perTeamInfo[team.team_id].team_info.members_with_roles.length} Members</Text>
|
||||
<Text>
|
||||
{perTeamInfo &&
|
||||
team.team_id &&
|
||||
perTeamInfo[team.team_id] &&
|
||||
perTeamInfo[team.team_id].keys &&
|
||||
perTeamInfo[team.team_id].keys.length}{" "}
|
||||
Keys
|
||||
</Text>
|
||||
<Text>
|
||||
{perTeamInfo &&
|
||||
team.team_id &&
|
||||
perTeamInfo[team.team_id] &&
|
||||
perTeamInfo[team.team_id].team_info &&
|
||||
perTeamInfo[team.team_id].team_info
|
||||
.members_with_roles &&
|
||||
perTeamInfo[team.team_id].team_info
|
||||
.members_with_roles.length}{" "}
|
||||
Members
|
||||
</Text>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Icon
|
||||
|
@ -481,7 +565,11 @@ const handleEditSubmit = async (formValues: Record<string, any>) => {
|
|||
</div>
|
||||
</div>
|
||||
<div className="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
|
||||
<Button onClick={confirmDelete} color="red" className="ml-2">
|
||||
<Button
|
||||
onClick={confirmDelete}
|
||||
color="red"
|
||||
className="ml-2"
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
<Button onClick={cancelDelete}>Cancel</Button>
|
||||
|
@ -518,7 +606,9 @@ const handleEditSubmit = async (formValues: Record<string, any>) => {
|
|||
<Form.Item
|
||||
label="Team Name"
|
||||
name="team_alias"
|
||||
rules={[{ required: true, message: 'Please input a team name' }]}
|
||||
rules={[
|
||||
{ required: true, message: "Please input a team name" },
|
||||
]}
|
||||
>
|
||||
<TextInput placeholder="" />
|
||||
</Form.Item>
|
||||
|
@ -528,7 +618,10 @@ const handleEditSubmit = async (formValues: Record<string, any>) => {
|
|||
placeholder="Select models"
|
||||
style={{ width: "100%" }}
|
||||
>
|
||||
<Select2.Option key="all-proxy-models" value="all-proxy-models">
|
||||
<Select2.Option
|
||||
key="all-proxy-models"
|
||||
value="all-proxy-models"
|
||||
>
|
||||
All Proxy Models
|
||||
</Select2.Option>
|
||||
{userModels.map((model) => (
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue