"use client"; import React, { useState, useEffect, useRef, useCallback } 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 { Button as Button2, Modal, Form, Input, Select, message, Radio, } from "antd"; import NumericalInput from "./shared/numerical_input"; import { unfurlWildcardModelsInList, getModelDisplayName } from "./key_team_helpers/fetch_available_models_team_key"; import SchemaFormFields from './common_components/check_openapi_schema'; import { keyCreateCall, slackBudgetAlertsHealthCheck, modelAvailableCall, getGuardrailsList, proxyBaseUrl, getPossibleUserRoles, userFilterUICall, } 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'; import Createuser from "./create_user_button"; import debounce from 'lodash/debounce'; import { rolesWithWriteAccess } from '../utils/roles'; import BudgetDurationDropdown from "./common_components/budget_duration_dropdown"; const { Option } = Select; interface CreateKeyProps { userID: string; team: Team | null; userRole: string | null; accessToken: string; data: any[] | null; teams: Team[] | null; addKey: (data: any) => void; } interface User { user_id: string; user_email: string; role?: string; } interface UserOption { label: string; value: string; user: User; } const getPredefinedTags = (data: any[] | null) => { let allTags = []; console.log("data:", JSON.stringify(data)); if (data) { for (let key of data) { if (key["metadata"] && key["metadata"]["tags"]) { allTags.push(...key["metadata"]["tags"]); } } } // Deduplicate using Set const uniqueTags = Array.from(new Set(allTags)).map(tag => ({ value: tag, label: tag, })); console.log("uniqueTags:", uniqueTags); return uniqueTags; } export const fetchTeamModels = async (userID: string, userRole: string, accessToken: string, teamID: string | null): Promise => { try { if (userID === null || userRole === null) { return []; } if (accessToken !== null) { const model_available = await modelAvailableCall( accessToken, userID, userRole, true, teamID ); let available_model_names = model_available["data"].map( (element: { id: string }) => element.id ); console.log("available_model_names:", available_model_names); return available_model_names; } return []; } catch (error) { console.error("Error fetching user models:", error); return []; } }; export const fetchUserModels = async (userID: string, userRole: string, accessToken: string, setUserModels: (models: string[]) => void) => { try { if (userID === null || userRole === null) { return; } if (accessToken !== null) { const model_available = await modelAvailableCall( accessToken, userID, userRole ); let available_model_names = model_available["data"].map( (element: { id: string }) => element.id ); console.log("available_model_names:", available_model_names); setUserModels(available_model_names); } } catch (error) { console.error("Error fetching user models:", error); } }; const CreateKey: React.FC = ({ userID, team, teams, userRole, accessToken, data, addKey, }) => { const [form] = Form.useForm(); const [isModalVisible, setIsModalVisible] = useState(false); const [apiKey, setApiKey] = useState(null); const [softBudget, setSoftBudget] = useState(null); const [userModels, setUserModels] = useState([]); const [modelsToPick, setModelsToPick] = useState([]); const [keyOwner, setKeyOwner] = useState("you"); const [predefinedTags, setPredefinedTags] = useState(getPredefinedTags(data)); const [guardrailsList, setGuardrailsList] = useState([]); const [selectedCreateKeyTeam, setSelectedCreateKeyTeam] = useState(team); const [isCreateUserModalVisible, setIsCreateUserModalVisible] = useState(false); const [newlyCreatedUserId, setNewlyCreatedUserId] = useState(null); const [possibleUIRoles, setPossibleUIRoles] = useState< Record> >({}); const [userOptions, setUserOptions] = useState([]); const [userSearchLoading, setUserSearchLoading] = useState(false); const handleOk = () => { setIsModalVisible(false); form.resetFields(); }; const handleCancel = () => { setIsModalVisible(false); setApiKey(null); setSelectedCreateKeyTeam(null); form.resetFields(); }; useEffect(() => { if (userID && userRole && accessToken) { fetchUserModels(userID, userRole, accessToken, setUserModels); } }, [accessToken, userID, userRole]); 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]); // Fetch possible user roles when component mounts useEffect(() => { const fetchPossibleRoles = async () => { try { if (accessToken) { // Check if roles are cached in session storage const cachedRoles = sessionStorage.getItem('possibleUserRoles'); if (cachedRoles) { setPossibleUIRoles(JSON.parse(cachedRoles)); } else { const availableUserRoles = await getPossibleUserRoles(accessToken); sessionStorage.setItem('possibleUserRoles', JSON.stringify(availableUserRoles)); setPossibleUIRoles(availableUserRoles); } } } catch (error) { console.error("Error fetching possible user roles:", error); } }; fetchPossibleRoles(); }, [accessToken]); const handleCreate = async (formValues: Record) => { 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); if(keyOwner === "you"){ formValues.user_id = userID } // If it's a service account, add the service_account_id to the metadata if (keyOwner === "service_account") { // Parse existing metadata or create an empty object let metadata: Record = {}; try { metadata = JSON.parse(formValues.metadata || "{}"); } catch (error) { console.error("Error parsing metadata:", error); } metadata["service_account_id"] = formValues.key_alias; // Update the formValues with the new metadata formValues.metadata = JSON.stringify(metadata); } const response = await keyCreateCall(accessToken, userID, formValues); console.log("key create Response:", response); // Add the data to the state in the parent component // Also directly update the keys list in AllKeysTable without an API call addKey(response) setApiKey(response["key"]); setSoftBudget(response["soft_budget"]); message.success("API Key Created"); form.resetFields(); localStorage.removeItem("userData" + userID); } catch (error) { console.log("error in create key:", error); message.error(`Error creating the key: ${error}`); } }; const handleCopy = () => { message.success("API Key copied to clipboard"); }; useEffect(() => { if (userID && userRole && accessToken) { fetchTeamModels(userID, userRole, accessToken, selectedCreateKeyTeam?.team_id ?? null).then((models) => { let allModels = Array.from(new Set([...(selectedCreateKeyTeam?.models ?? []), ...models])); setModelsToPick(allModels); }); } form.setFieldValue('models', []); }, [selectedCreateKeyTeam, accessToken, userID, userRole]); // Add a callback function to handle user creation const handleUserCreated = (userId: string) => { setNewlyCreatedUserId(userId); form.setFieldsValue({ user_id: userId }); setIsCreateUserModalVisible(false); }; const fetchUsers = async (searchText: string): Promise => { if (!searchText) { setUserOptions([]); return; } setUserSearchLoading(true); try { const params = new URLSearchParams(); params.append('user_email', searchText); // Always search by email if (accessToken == null) { return; } const response = await userFilterUICall(accessToken, params); const data: User[] = response; const options: UserOption[] = data.map(user => ({ label: `${user.user_email} (${user.user_id})`, value: user.user_id, user })); setUserOptions(options); } catch (error) { console.error('Error fetching users:', error); message.error('Failed to search for users'); } finally { setUserSearchLoading(false); } }; const debouncedSearch = useCallback( debounce((text: string) => fetchUsers(text), 300), [accessToken] ); const handleUserSearch = (value: string): void => { debouncedSearch(value); }; const handleUserSelect = (_value: string, option: UserOption): void => { const selectedUser = option.user; form.setFieldsValue({ user_id: selectedUser.user_id }); }; return (
{userRole && rolesWithWriteAccess.includes(userRole) && ( )}
{/* Section 1: Key Ownership */}
Key Ownership Owned By{' '} } className="mb-4" > setKeyOwner(e.target.value)} value={keyOwner} > You Service Account {userRole === "Admin" && Another User} {keyOwner === "another_user" && ( User ID{' '} } name="user_id" className="mt-4" rules={[{ required: keyOwner === "another_user", message: `Please input the user ID of the user you are assigning the key to` }]} >
{ if (values.includes("all-team-models")) { form.setFieldsValue({ models: ["all-team-models"] }); } }} > {modelsToPick.map((model: string) => ( ))}
{/* Section 3: Optional Settings */}
Optional Settings 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"}`} 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}` ); } }, }, ]} > Reset Budget{' '} } name="budget_duration" help={`Team Reset Budget: ${team?.budget_duration !== null && team?.budget_duration !== undefined ? team?.budget_duration : "None"}`} > form.setFieldValue('budget_duration', value)} /> 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"}`} 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}` ); } }, }, ]} > 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"}`} 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}` ); } }, }, ]} > Expire Key{' '} } name="duration" className="mt-4" > Guardrails{' '} e.stopPropagation()} // Prevent accordion from collapsing when clicking link > } name="guardrails" className="mt-4" help="Select existing guardrails or enter new ones" >
Advanced Settings Learn more about advanced settings in our{' '} documentation }>
Create Key
{/* Add the Create User Modal */} {isCreateUserModalVisible && ( setIsCreateUserModalVisible(false)} footer={null} width={800} > )} {apiKey && ( Save your Key

Please save this secret key somewhere safe and accessible. For security reasons, you will not be able to view it again{" "} through your LiteLLM account. If you lose this secret key, you will need to generate a new one.

{apiKey != null ? (
API Key:
                      {apiKey}
                    
{/* */}
) : ( Key being created, this might take 30s )}
)}
); }; export default CreateKey;