"use client"; import React, { useEffect, useState } from "react"; import { keyDeleteCall, modelAvailableCall, getGuardrailsList, } from "./networking"; import { add } from "date-fns"; import { InformationCircleIcon, StatusOnlineIcon, TrashIcon, PencilAltIcon, RefreshIcon, } from "@heroicons/react/outline"; import { keySpendLogsCall, PredictedSpendLogsCall, keyUpdateCall, modelInfoCall, regenerateKeyCall, } from "./networking"; import { Badge, Card, Table, Grid, Col, Button, TableBody, TableCell, TableHead, TableHeaderCell, TableRow, Dialog, DialogPanel, Text, Title, Subtitle, Icon, BarChart, TextInput, Textarea, } from "@tremor/react"; import { InfoCircleOutlined } from "@ant-design/icons"; import { fetchAvailableModelsForTeamOrKey, getModelDisplayName, } from "./key_team_helpers/fetch_available_models_team_key"; import { Select as Select3, SelectItem, MultiSelect, MultiSelectItem, } from "@tremor/react"; import { Button as Button2, Modal, Form, Input, Select as Select2, InputNumber, message, Select, Tooltip, DatePicker, } from "antd"; import { CopyToClipboard } from "react-copy-to-clipboard"; import TextArea from "antd/es/input/TextArea"; const { Option } = Select; const isLocal = process.env.NODE_ENV === "development"; const proxyBaseUrl = isLocal ? "http://localhost:4000" : null; if (isLocal != true) { console.log = function () {}; } interface EditKeyModalProps { visible: boolean; onCancel: () => void; token: any; // Assuming TeamType is a type representing your team object onSubmit: (data: FormData) => void; // Assuming FormData is the type of data to be submitted } interface ModelLimitModalProps { visible: boolean; onCancel: () => void; token: ItemData; onSubmit: (updatedMetadata: any) => void; accessToken: string; } // Define the props type interface ViewKeyTableProps { userID: string; userRole: string | null; accessToken: string; selectedTeam: any | null; data: any[] | null; setData: React.Dispatch>; teams: any[] | null; premiumUser: boolean; } interface ItemData { key_alias: string | null; key_name: string; spend: string; max_budget: string | null; models: string[]; tpm_limit: string | null; rpm_limit: string | null; token: string; token_id: string | null; id: number; team_id: string; metadata: any; user_id: string | null; expires: any; budget_duration: string | null; budget_reset_at: string | null; // Add any other properties that exist in the item data } const ViewKeyTable: React.FC = ({ userID, userRole, accessToken, selectedTeam, data, setData, teams, premiumUser, }) => { const [isButtonClicked, setIsButtonClicked] = useState(false); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const [keyToDelete, setKeyToDelete] = useState(null); const [selectedItem, setSelectedItem] = useState(null); const [spendData, setSpendData] = useState< { day: string; spend: number }[] | null >(null); const [predictedSpendString, setPredictedSpendString] = useState(""); const [editModalVisible, setEditModalVisible] = useState(false); const [infoDialogVisible, setInfoDialogVisible] = useState(false); const [selectedToken, setSelectedToken] = useState(null); const [userModels, setUserModels] = useState([]); const initialKnownTeamIDs: Set = new Set(); const [modelLimitModalVisible, setModelLimitModalVisible] = useState(false); const [regenerateDialogVisible, setRegenerateDialogVisible] = useState(false); const [regeneratedKey, setRegeneratedKey] = useState(null); const [regenerateFormData, setRegenerateFormData] = useState(null); const [regenerateForm] = Form.useForm(); const [newExpiryTime, setNewExpiryTime] = useState(null); const [knownTeamIDs, setKnownTeamIDs] = useState(initialKnownTeamIDs); const [guardrailsList, setGuardrailsList] = useState([]); // Function to check if user is admin of a team const isUserTeamAdmin = (team: any) => { if (!team.members_with_roles) return false; return team.members_with_roles.some( (member: any) => member.role === "admin" && member.user_id === userID ); }; // Combine all keys that user should have access to const all_keys_to_display = React.useMemo(() => { let allKeys: any[] = []; // If no teams, return personal keys if (!teams || teams.length === 0) { return data; } teams.forEach((team) => { // For default team or when user is not admin, use personal keys (data) if (team.team_id === "default-team" || !isUserTeamAdmin(team)) { if (selectedTeam && selectedTeam.team_id === team.team_id && data) { allKeys = [ ...allKeys, ...data.filter((key) => key.team_id === team.team_id), ]; } } // For teams where user is admin, use team keys else if (isUserTeamAdmin(team)) { if (selectedTeam && selectedTeam.team_id === team.team_id) { allKeys = [ ...allKeys, ...(data?.filter((key) => key?.team_id === team?.team_id) || []), ]; } } }); // If no team is selected, show all accessible keys if ((!selectedTeam || selectedTeam.team_alias === "Default Team") && data) { const personalKeys = data.filter( (key) => !key.team_id || key.team_id === "default-team" ); const adminTeamKeys = teams .filter((team) => isUserTeamAdmin(team)) .flatMap((team) => team.keys || []); allKeys = [...personalKeys, ...adminTeamKeys]; } // Filter out litellm-dashboard keys allKeys = allKeys.filter((key) => key.team_id !== "litellm-dashboard"); // Remove duplicates based on token const uniqueKeys = Array.from( new Map(allKeys.map((key) => [key.token, key])).values() ); return uniqueKeys; }, [data, teams, selectedTeam, userID]); useEffect(() => { const calculateNewExpiryTime = (duration: string | undefined) => { if (!duration) { return null; } try { const now = new Date(); let newExpiry: Date; if (duration.endsWith("s")) { newExpiry = add(now, { seconds: parseInt(duration) }); } else if (duration.endsWith("h")) { newExpiry = add(now, { hours: parseInt(duration) }); } else if (duration.endsWith("d")) { newExpiry = add(now, { days: parseInt(duration) }); } else { throw new Error("Invalid duration format"); } return newExpiry.toLocaleString("en-US", { year: "numeric", month: "numeric", day: "numeric", hour: "numeric", minute: "numeric", second: "numeric", hour12: true, }); } catch (error) { return null; } }; console.log("in calculateNewExpiryTime for selectedToken", selectedToken); // When a new duration is entered if (regenerateFormData?.duration) { setNewExpiryTime(calculateNewExpiryTime(regenerateFormData.duration)); } else { setNewExpiryTime(null); } console.log("calculateNewExpiryTime:", newExpiryTime); }, [selectedToken, regenerateFormData?.duration]); useEffect(() => { const fetchUserModels = async () => { try { if (userID === null || userRole === null || accessToken === null) { return; } const models = await fetchAvailableModelsForTeamOrKey( userID, userRole, accessToken ); if (models) { setUserModels(models); } } catch (error) { console.error("Error fetching user models:", error); } }; fetchUserModels(); }, [accessToken, userID, userRole]); const handleModelLimitClick = (token: ItemData) => { setSelectedToken(token); setModelLimitModalVisible(true); }; const handleModelLimitSubmit = async (updatedMetadata: any) => { if (accessToken == null || selectedToken == null) { return; } const formValues = { ...selectedToken, metadata: updatedMetadata, key: selectedToken.token, }; try { let newKeyValues = await keyUpdateCall(accessToken, formValues); console.log("Model limits updated:", newKeyValues); // Update the keys with the updated key if (data) { const updatedData = data.map((key) => key.token === selectedToken.token ? newKeyValues : key ); setData(updatedData); } message.success("Model-specific limits updated successfully"); } catch (error) { console.error("Error updating model-specific limits:", error); message.error("Failed to update model-specific limits"); } setModelLimitModalVisible(false); setSelectedToken(null); }; useEffect(() => { if (teams) { const teamIDSet: Set = new Set(); teams.forEach((team: any, index: number) => { const team_obj: string = team.team_id; teamIDSet.add(team_obj); }); setKnownTeamIDs(teamIDSet); } }, [teams]); const EditKeyModal: React.FC = ({ visible, onCancel, token, onSubmit, }) => { const [form] = Form.useForm(); const [keyTeam, setKeyTeam] = useState(selectedTeam); const [errorModels, setErrorModels] = useState([]); const [errorBudget, setErrorBudget] = useState(false); const [guardrailsList, setGuardrailsList] = useState([]); useEffect(() => { const fetchGuardrails = async () => { try { const response = await getGuardrailsList(accessToken); const guardrailNames = response.guardrails.map( (g: { guardrail_name: string }) => g.guardrail_name ); setGuardrailsList(guardrailNames); } catch (error) { console.error("Failed to fetch guardrails:", error); } }; fetchGuardrails(); }, [accessToken]); let metadataString = ""; try { // Create a copy of metadata without guardrails for display const displayMetadata = { ...token.metadata }; delete displayMetadata.guardrails; metadataString = JSON.stringify(displayMetadata, null, 2); } catch (error) { console.error("Error stringifying metadata:", error); metadataString = ""; } // Extract existing guardrails from metadata let existingGuardrails: string[] = []; try { existingGuardrails = token.metadata?.guardrails || []; } catch (error) { console.error("Error extracting guardrails:", error); } const initialValues = token ? { ...token, budget_duration: token.budget_duration, metadata: metadataString, guardrails: existingGuardrails, } : { metadata: metadataString, guardrails: [] }; const handleOk = () => { form .validateFields() .then((values) => { // const updatedValues = {...values, team_id: team.team_id}; // onSubmit(updatedValues); form.resetFields(); }) .catch((error) => { console.error("Validation failed:", error); }); }; return (
<> { if (keyTeam.team_alias === "Default Team") { return Promise.resolve(); } const errorModels = value.filter( (model: string) => !keyTeam.models.includes(model) && model !== "all-team-models" && model !== "all-proxy-models" && !keyTeam.models.includes("all-proxy-models") ); console.log(`errorModels: ${errorModels}`); if (errorModels.length > 0) { return Promise.reject( `Some models are not part of the new team's models - ${errorModels} Team models: ${keyTeam.models}` ); } else { return Promise.resolve(); } }, }, ]} > { if ( value && keyTeam && keyTeam.max_budget !== null && value > keyTeam.max_budget ) { console.log(`keyTeam.max_budget: ${keyTeam.max_budget}`); throw new Error( `Budget cannot exceed team max budget: $${keyTeam.max_budget}` ); } }, }, ]} > {teams?.map((team_obj, index) => ( setKeyTeam(team_obj)} > {team_obj.team_alias} ))} { if ( value && keyTeam && keyTeam.tpm_limit !== null && value > keyTeam.tpm_limit ) { console.log(`keyTeam.tpm_limit: ${keyTeam.tpm_limit}`); throw new Error( `tpm_limit cannot exceed team max tpm_limit: $${keyTeam.tpm_limit}` ); } }, }, ]} > { if ( value && keyTeam && keyTeam.rpm_limit !== null && value > keyTeam.rpm_limit ) { console.log(`keyTeam.rpm_limit: ${keyTeam.rpm_limit}`); throw new Error( `rpm_limit cannot exceed team max rpm_limit: $${keyTeam.rpm_limit}` ); } }, }, ]} > Guardrails{" "} e.stopPropagation()} > } name="guardrails" className="mt-8" help="Select existing guardrails or enter new ones" >