import React, { useState, useEffect } from "react"; import Link from "next/link"; import { Typography } from "antd"; import { teamDeleteCall, teamUpdateCall, teamInfoCall, Organization, DEFAULT_ORGANIZATION } from "./networking"; import TeamMemberModal from "@/components/team/edit_membership"; import { fetchTeams } from "./common_components/fetch_teams"; import { InformationCircleIcon, PencilAltIcon, PencilIcon, RefreshIcon, StatusOnlineIcon, TrashIcon, } from "@heroicons/react/outline"; import { Button as Button2, Modal, Form, Input, Select as Select2, message, Tooltip } from "antd"; import NumericalInput from "./shared/numerical_input"; import { fetchAvailableModelsForTeamOrKey, getModelDisplayName, unfurlWildcardModelsInList } from "./key_team_helpers/fetch_available_models_team_key"; import { Select, SelectItem } from "@tremor/react"; import { InfoCircleOutlined } from '@ant-design/icons'; import { getGuardrailsList } from "./networking"; import TeamInfoView from "@/components/team/team_info"; import TeamSSOSettings from "@/components/TeamSSOSettings"; import { isAdminRole } from "@/utils/roles"; import { Table, TableBody, TableCell, TableHead, TableHeaderCell, TableRow, TextInput, Card, Icon, Button, Badge, Col, Text, Grid, Accordion, AccordionHeader, AccordionBody, TabGroup, TabList, TabPanel, TabPanels, Tab } from "@tremor/react"; import { CogIcon } from "@heroicons/react/outline"; import AvailableTeamsPanel from "@/components/team/available_teams"; import type { Team } from "./key_team_helpers/key_list"; const isLocal = process.env.NODE_ENV === "development"; const proxyBaseUrl = isLocal ? "http://localhost:4000" : null; if (isLocal != true) { console.log = function() {}; } interface TeamProps { teams: Team[] | null; searchParams: any; accessToken: string | null; setTeams: React.Dispatch>; userID: string | null; userRole: string | null; organizations: Organization[] | null; } interface FilterState { team_id: string; team_alias: string; organization_id: string; sort_by: string; sort_order: 'asc' | 'desc'; } interface EditTeamModalProps { visible: boolean; onCancel: () => void; team: any; // Assuming TeamType is a type representing your team object onSubmit: (data: FormData) => void; // Assuming FormData is the type of data to be submitted } import { teamCreateCall, teamMemberAddCall, teamMemberUpdateCall, Member, modelAvailableCall, v2TeamListCall } from "./networking"; import { updateExistingKeys } from "@/utils/dataUtils"; const getOrganizationModels = (organization: Organization | null, userModels: string[]) => { let tempModelsToPick = []; if (organization) { if (organization.models.length > 0) { console.log(`organization.models: ${organization.models}`); tempModelsToPick = organization.models; } else { // show all available models if the team has no models set tempModelsToPick = userModels; } } else { // no team set, show all available models tempModelsToPick = userModels; } return unfurlWildcardModelsInList(tempModelsToPick, userModels); } const Teams: React.FC = ({ teams, searchParams, accessToken, setTeams, userID, userRole, organizations }) => { const [lastRefreshed, setLastRefreshed] = useState(""); const [currentOrg, setCurrentOrg] = useState(null); const [currentOrgForCreateTeam, setCurrentOrgForCreateTeam] = useState(null); const [showFilters, setShowFilters] = useState(false); const [filters, setFilters] = useState({ team_id: "", team_alias: "", organization_id: "", sort_by: "created_at", sort_order: "desc" }); useEffect(() => { console.log(`inside useeffect - ${lastRefreshed}`) if (accessToken) { // Call your function here fetchTeams(accessToken, userID, userRole, currentOrg, setTeams) } handleRefreshClick() }, [lastRefreshed]); const [form] = Form.useForm(); const [memberForm] = Form.useForm(); const { Title, Paragraph } = Typography; const [value, setValue] = useState(""); const [editModalVisible, setEditModalVisible] = useState(false); const [selectedTeam, setSelectedTeam] = useState( null ); const [selectedTeamId, setSelectedTeamId] = useState(null); const [editTeam, setEditTeam] = useState(false); const [isTeamModalVisible, setIsTeamModalVisible] = useState(false); const [isAddMemberModalVisible, setIsAddMemberModalVisible] = useState(false); const [isEditMemberModalVisible, setIsEditMemberModalVisible] = useState(false); const [userModels, setUserModels] = useState([]); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const [teamToDelete, setTeamToDelete] = useState(null); const [modelsToPick, setModelsToPick] = useState([]); const [perTeamInfo, setPerTeamInfo] = useState>({}); // Add this state near the other useState declarations const [guardrailsList, setGuardrailsList] = useState([]); useEffect(() => { console.log(`currentOrgForCreateTeam: ${currentOrgForCreateTeam}`); const models = getOrganizationModels(currentOrgForCreateTeam, userModels); console.log(`models: ${models}`); setModelsToPick(models); form.setFieldValue('models', []); }, [currentOrgForCreateTeam, userModels]); // Add this useEffect to fetch guardrails useEffect(() => { const fetchGuardrails = async () => { try { if (accessToken == null) { return; } 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]); const handleOk = () => { setIsTeamModalVisible(false); form.resetFields(); }; const handleMemberOk = () => { setIsAddMemberModalVisible(false); setIsEditMemberModalVisible(false); memberForm.resetFields(); }; const handleCancel = () => { setIsTeamModalVisible(false); form.resetFields(); }; const handleMemberCancel = () => { setIsAddMemberModalVisible(false); setIsEditMemberModalVisible(false); memberForm.resetFields(); }; const handleDelete = async (team_id: string) => { // Set the team to delete and open the confirmation modal setTeamToDelete(team_id); setIsDeleteModalOpen(true); }; const confirmDelete = async () => { if (teamToDelete == null || teams == null || accessToken == null) { return; } try { await teamDeleteCall(accessToken, teamToDelete); // Successfully completed the deletion. Update the state to trigger a rerender. fetchTeams(accessToken, userID, userRole, currentOrg, setTeams) } catch (error) { console.error("Error deleting the team:", error); // Handle any error situations, such as displaying an error message to the user. } // Close the confirmation modal and reset the teamToDelete setIsDeleteModalOpen(false); setTeamToDelete(null); }; const cancelDelete = () => { // Close the confirmation modal and reset the teamToDelete setIsDeleteModalOpen(false); setTeamToDelete(null); }; 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, teams]); const handleCreate = async (formValues: Record) => { try { console.log(`formValues: ${JSON.stringify(formValues)}`); if (accessToken != null) { const newTeamAlias = formValues?.team_alias; const existingTeamAliases = teams?.map((t) => t.team_alias) ?? []; let organizationId = formValues?.organization_id || currentOrg?.organization_id; if (organizationId === "" || typeof organizationId !== 'string') { formValues.organization_id = null; } else { formValues.organization_id = organizationId.trim(); } // Remove guardrails from top level since it's now in metadata 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) { setTeams([...teams, response]); } else { setTeams([response]); } console.log(`response for team create call: ${response}`); message.success("Team created"); form.resetFields(); setIsTeamModalVisible(false); } } catch (error) { console.error("Error creating the team:", error); message.error("Error creating the team: " + error, 20); } }; const is_team_admin = (team: any) => { if (team == null || team.members_with_roles == null) { return false; } for (let i = 0; i < team.members_with_roles.length; i++) { let member = team.members_with_roles[i]; if (member.user_id == userID && member.role == "admin") { return true; } } return false; } const handleRefreshClick = () => { // Update the 'lastRefreshed' state to the current date and time const currentDate = new Date(); setLastRefreshed(currentDate.toLocaleString()); }; const handleFilterChange = (key: keyof FilterState, value: string) => { const newFilters = { ...filters, [key]: value }; setFilters(newFilters); // Call teamListCall with the new filters if (accessToken) { v2TeamListCall( accessToken, newFilters.organization_id || null, null, newFilters.team_id || null, newFilters.team_alias || null ).then((response) => { if (response && response.teams) { setTeams(response.teams); } }).catch((error) => { console.error("Error fetching teams:", error); }); } }; const handleSortChange = (sortBy: string, sortOrder: 'asc' | 'desc') => { const newFilters = { ...filters, sort_by: sortBy, sort_order: sortOrder }; setFilters(newFilters); // Call teamListCall with the new sort parameters if (accessToken) { v2TeamListCall( accessToken, filters.organization_id || null, null, filters.team_id || null, filters.team_alias || null ).then((response) => { if (response && response.teams) { setTeams(response.teams); } }).catch((error) => { console.error("Error fetching teams:", error); }); } }; const handleFilterReset = () => { setFilters({ team_id: "", team_alias: "", organization_id: "", sort_by: "created_at", sort_order: "desc" }); // Reset teams list if (accessToken) { v2TeamListCall(accessToken, null, userID || null, null, null).then((response) => { if (response && response.teams) { setTeams(response.teams); } }).catch((error) => { console.error("Error fetching teams:", error); }); } }; return (
{selectedTeamId ? ( { setTeams(teams => { if (teams == null) { return teams; } return teams.map(team => { if (data.team_id === team.team_id) { return updateExistingKeys(team, data) } return team }) }) }} onClose={() => { setSelectedTeamId(null); setEditTeam(false); }} accessToken={accessToken} is_team_admin={is_team_admin(teams?.find((team) => team.team_id === selectedTeamId))} is_proxy_admin={userRole == "Admin"} userModels={userModels} editTeam={editTeam} /> ) : (
Your Teams Available Teams {isAdminRole(userRole || "") && Default Team Settings}
{lastRefreshed && Last Refreshed: {lastRefreshed}}
Click on “Team ID” to view team details and manage team members.
{/* Search and Filter Controls */}
{/* Team Alias Search */}
handleFilterChange('team_alias', e.target.value)} />
{/* Filter Button */} {/* Reset Filters Button */}
{/* Additional Filters */} {showFilters && (
{/* Team ID Search */}
handleFilterChange('team_id', e.target.value)} />
{/* Organization Dropdown */}
)}
Team Name Team ID Created Spend (USD) Budget (USD) Models Organization Info {teams && teams.length > 0 ? teams .filter((team) => { if (!currentOrg) return true; return team.organization_id === currentOrg.organization_id; }) .sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()) .map((team: any) => ( {team["team_alias"]}
{team.created_at ? new Date(team.created_at).toLocaleDateString() : "N/A"} {team["spend"]} {team["max_budget"] !== null && team["max_budget"] !== undefined ? team["max_budget"] : "No limit"} {Array.isArray(team.models) ? (
{team.models.length === 0 ? ( All Proxy Models ) : ( team.models.map( (model: string, index: number) => model === "all-proxy-models" ? ( All Proxy Models ) : ( {model.length > 30 ? `${getModelDisplayName(model).slice(0, 30)}...` : getModelDisplayName(model)} ) ) )}
) : null}
{team.organization_id} {perTeamInfo && team.team_id && perTeamInfo[team.team_id] && perTeamInfo[team.team_id].keys && perTeamInfo[team.team_id].keys.length}{" "} Keys {perTeamInfo && team.team_id && perTeamInfo[team.team_id] && perTeamInfo[team.team_id].members_with_roles && perTeamInfo[team.team_id].members_with_roles.length}{" "} Members {userRole == "Admin" ? ( <> { setSelectedTeamId(team.team_id); setEditTeam(true); }} /> handleDelete(team.team_id)} icon={TrashIcon} size="sm" /> ) : null}
)) : null}
{isDeleteModalOpen && (
{/* Modal Panel */} {/* Confirmation Modal Content */}

Delete Team

Are you sure you want to delete this team ?

)}
{userRole == "Admin" || userRole == "Org Admin"? (
<> Organization{' '} Organizations can have multiple teams. Learn more about{' '} e.stopPropagation()} > user management hierarchy }> } name="organization_id" initialValue={currentOrg ? currentOrg.organization_id : null} className="mt-8" > { form.setFieldValue('organization_id', value); setCurrentOrgForCreateTeam(organizations?.find((org) => org.organization_id === value) || null); }} filterOption={(input, option) => { if (!option) return false; const optionValue = option.children?.toString() || ''; return optionValue.toLowerCase().includes(input.toLowerCase()); }} optionFilterProp="children" > {organizations?.map((org) => ( {org.organization_alias}{" "} ({org.organization_id}) ))} Models{' '} } name="models"> All Proxy Models {modelsToPick.map((model) => ( {getModelDisplayName(model)} ))} daily weekly monthly Additional Settings { e.target.value = e.target.value.trim(); }} /> Guardrails{' '} e.stopPropagation()} > } name="guardrails" className="mt-8" help="Select existing guardrails or enter new ones" > ({ value: name, label: name }))} />
Create Team
) : null}
{isAdminRole(userRole || "") && ( )}
)}
); }; export default Teams;