mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-08 03:08:45 +00:00
Improve display of model tags on Keys, Teams and Org page (#11022)
* added accordian for models tag * remove margin top * match badge styles same as on teams page * add badge accordian for organizations * badges fixed on keys page * fixed for singular and plural * fix merge conflict
This commit is contained in:
parent
461e1a4751
commit
14cdf61537
3 changed files with 423 additions and 212 deletions
|
|
@ -33,8 +33,11 @@ import {
|
|||
TableBody,
|
||||
TableRow,
|
||||
TableCell,
|
||||
Icon,
|
||||
} from "@tremor/react";
|
||||
import { SwitchVerticalIcon, ChevronUpIcon, ChevronDownIcon } from "@heroicons/react/outline";
|
||||
import { SwitchVerticalIcon, ChevronUpIcon, ChevronDownIcon, ChevronRightIcon } from "@heroicons/react/outline";
|
||||
import { Badge, Text } from "@tremor/react";
|
||||
import { getModelDisplayName } from "./key_team_helpers/fetch_available_models_team_key";
|
||||
|
||||
interface AllKeysTableProps {
|
||||
keys: KeyResponse[];
|
||||
|
|
@ -152,6 +155,7 @@ export function AllKeysTable({
|
|||
desc: true
|
||||
}];
|
||||
});
|
||||
const [expandedAccordions, setExpandedAccordions] = useState<Record<string, boolean>>({});
|
||||
|
||||
// Use the filter logic hook
|
||||
|
||||
|
|
@ -363,19 +367,93 @@ export function AllKeysTable({
|
|||
cell: (info) => {
|
||||
const models = info.getValue() as string[];
|
||||
return (
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{models && models.length > 0 ? (
|
||||
models.map((model, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className="px-2 py-1 bg-blue-100 rounded text-xs"
|
||||
>
|
||||
{model}
|
||||
</span>
|
||||
))
|
||||
) : (
|
||||
"-"
|
||||
)}
|
||||
<div className="flex flex-col py-2">
|
||||
{Array.isArray(models) ? (
|
||||
<div className="flex flex-col">
|
||||
{models.length === 0 ? (
|
||||
<Badge size={"xs"} className="mb-1" color="red">
|
||||
<Text>All Proxy Models</Text>
|
||||
</Badge>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex items-start">
|
||||
{models.length > 3 && (
|
||||
<div>
|
||||
<Icon
|
||||
icon={expandedAccordions[info.row.id] ? ChevronDownIcon : ChevronRightIcon}
|
||||
className="cursor-pointer"
|
||||
size="xs"
|
||||
onClick={() => {
|
||||
setExpandedAccordions(prev => ({
|
||||
...prev,
|
||||
[info.row.id]: !prev[info.row.id]
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{models.slice(0, 3).map((model, index) => (
|
||||
model === "all-proxy-models" ? (
|
||||
<Badge
|
||||
key={index}
|
||||
size={"xs"}
|
||||
color="red"
|
||||
>
|
||||
<Text>All Proxy Models</Text>
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge
|
||||
key={index}
|
||||
size={"xs"}
|
||||
color="blue"
|
||||
>
|
||||
<Text>
|
||||
{model.length > 30
|
||||
? `${getModelDisplayName(model).slice(0, 30)}...`
|
||||
: getModelDisplayName(model)}
|
||||
</Text>
|
||||
</Badge>
|
||||
)
|
||||
))}
|
||||
{models.length > 3 && !expandedAccordions[info.row.id] && (
|
||||
<Badge size={"xs"} color="gray" className="cursor-pointer">
|
||||
<Text>+{models.length - 3} {models.length - 3 === 1 ? 'more model' : 'more models'}</Text>
|
||||
</Badge>
|
||||
)}
|
||||
{expandedAccordions[info.row.id] && (
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{models.slice(3).map((model, index) => (
|
||||
model === "all-proxy-models" ? (
|
||||
<Badge
|
||||
key={index + 3}
|
||||
size={"xs"}
|
||||
color="red"
|
||||
>
|
||||
<Text>All Proxy Models</Text>
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge
|
||||
key={index + 3}
|
||||
size={"xs"}
|
||||
color="blue"
|
||||
>
|
||||
<Text>
|
||||
{model.length > 30
|
||||
? `${getModelDisplayName(model).slice(0, 30)}...`
|
||||
: getModelDisplayName(model)}
|
||||
</Text>
|
||||
</Badge>
|
||||
)
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
|
@ -621,11 +699,12 @@ export function AllKeysTable({
|
|||
{row.getVisibleCells().map((cell) => (
|
||||
<TableCell
|
||||
key={cell.id}
|
||||
className={`py-0.5 max-h-8 overflow-hidden text-ellipsis whitespace-nowrap ${
|
||||
cell.column.id === 'actions'
|
||||
? 'sticky right-0 bg-white shadow-[-4px_0_8px_-6px_rgba(0,0,0,0.1)]'
|
||||
: ''
|
||||
}`}
|
||||
style={{
|
||||
maxWidth: "8-x",
|
||||
whiteSpace: "pre-wrap",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
className={`py-0.5 max-h-8 overflow-hidden text-ellipsis whitespace-nowrap ${cell.column.id === 'models' && (cell.getValue() as string[]).length > 3 ? "px-0" : ""}`}
|
||||
>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</TableCell>
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import NumericalInput from "./shared/numerical_input";
|
|||
import { Input } from "antd";
|
||||
import { Modal, Form, Tooltip, Select as Select2 } from "antd";
|
||||
import { InfoCircleOutlined } from '@ant-design/icons';
|
||||
import { PencilAltIcon, TrashIcon, RefreshIcon } from "@heroicons/react/outline";
|
||||
import { PencilAltIcon, TrashIcon, RefreshIcon, ChevronDownIcon, ChevronRightIcon } from "@heroicons/react/outline";
|
||||
import { TextInput } from "@tremor/react";
|
||||
import { getModelDisplayName } from './key_team_helpers/fetch_available_models_team_key';
|
||||
import { message } from 'antd';
|
||||
|
|
@ -65,6 +65,7 @@ const OrganizationsTable: React.FC<OrganizationsTableProps> = ({
|
|||
const [orgToDelete, setOrgToDelete] = useState<string | null>(null);
|
||||
const [isOrgModalVisible, setIsOrgModalVisible] = useState(false);
|
||||
const [form] = Form.useForm();
|
||||
const [expandedAccordions, setExpandedAccordions] = useState<Record<string, boolean>>({});
|
||||
|
||||
useEffect(() => {
|
||||
if (organizations.length === 0 && accessToken) {
|
||||
|
|
@ -264,169 +265,248 @@ const OrganizationsTable: React.FC<OrganizationsTableProps> = ({
|
|||
</TableRow>
|
||||
</TableHead>
|
||||
|
||||
<TableBody>
|
||||
{organizations && organizations.length > 0
|
||||
? organizations
|
||||
.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())
|
||||
.map((org: Organization) => (
|
||||
<TableRow key={org.organization_id}>
|
||||
<TableCell>
|
||||
<div className="overflow-hidden">
|
||||
<Tooltip title={org.organization_id}>
|
||||
<Button
|
||||
size="xs"
|
||||
variant="light"
|
||||
className="font-mono text-blue-500 bg-blue-50 hover:bg-blue-100 text-xs font-normal px-2 py-0.5 text-left overflow-hidden truncate max-w-[200px]"
|
||||
onClick={() => setSelectedOrgId(org.organization_id)}
|
||||
>
|
||||
{org.organization_id?.slice(0, 7)}...
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>{org.organization_alias}</TableCell>
|
||||
<TableCell>
|
||||
{org.created_at ? new Date(org.created_at).toLocaleDateString() : "N/A"}
|
||||
</TableCell>
|
||||
<TableCell>{org.spend.toFixed(4)}</TableCell>
|
||||
<TableCell>
|
||||
{org.litellm_budget_table?.max_budget !== null && org.litellm_budget_table?.max_budget !== undefined ? org.litellm_budget_table?.max_budget : "No limit"}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{Array.isArray(org.models) && (
|
||||
<div className="flex flex-col">
|
||||
{org.models.length === 0 ? (
|
||||
<Badge size="xs" className="mb-1" color="red">
|
||||
All Proxy Models
|
||||
</Badge>
|
||||
) : (
|
||||
org.models.map((model, index) =>
|
||||
model === "all-proxy-models" ? (
|
||||
<Badge key={index} size="xs" className="mb-1" color="red">
|
||||
All Proxy Models
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge key={index} size="xs" className="mb-1" color="blue">
|
||||
{model.length > 30
|
||||
? `${getModelDisplayName(model).slice(0, 30)}...`
|
||||
: getModelDisplayName(model)}
|
||||
</Badge>
|
||||
)
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Text>
|
||||
TPM: {org.litellm_budget_table?.tpm_limit ? org.litellm_budget_table?.tpm_limit : "Unlimited"}
|
||||
<br />
|
||||
RPM: {org.litellm_budget_table?.rpm_limit ? org.litellm_budget_table?.rpm_limit : "Unlimited"}
|
||||
</Text>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Text>{org.members?.length || 0} Members</Text>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{userRole === "Admin" && (
|
||||
<TableBody>
|
||||
{organizations && organizations.length > 0
|
||||
? organizations
|
||||
.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())
|
||||
.map((org: Organization) => (
|
||||
<TableRow key={org.organization_id}>
|
||||
<TableCell>
|
||||
<div className="overflow-hidden">
|
||||
<Tooltip title={org.organization_id}>
|
||||
<Button
|
||||
size="xs"
|
||||
variant="light"
|
||||
className="font-mono text-blue-500 bg-blue-50 hover:bg-blue-100 text-xs font-normal px-2 py-0.5 text-left overflow-hidden truncate max-w-[200px]"
|
||||
onClick={() => setSelectedOrgId(org.organization_id)}
|
||||
>
|
||||
{org.organization_id?.slice(0, 7)}...
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>{org.organization_alias}</TableCell>
|
||||
<TableCell>
|
||||
{org.created_at ? new Date(org.created_at).toLocaleDateString() : "N/A"}
|
||||
</TableCell>
|
||||
<TableCell>{org.spend}</TableCell>
|
||||
<TableCell>
|
||||
{org.litellm_budget_table?.max_budget !== null && org.litellm_budget_table?.max_budget !== undefined ? org.litellm_budget_table?.max_budget : "No limit"}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
style={{
|
||||
maxWidth: "8-x",
|
||||
whiteSpace: "pre-wrap",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
className={org.models.length > 3 ? "px-0" : ""}
|
||||
>
|
||||
<div className="flex flex-col">
|
||||
{Array.isArray(org.models) ? (
|
||||
<div className="flex flex-col">
|
||||
{org.models.length === 0 ? (
|
||||
<Badge size={"xs"} className="mb-1" color="red">
|
||||
<Text>All Proxy Models</Text>
|
||||
</Badge>
|
||||
) : (
|
||||
<>
|
||||
<Icon
|
||||
icon={PencilAltIcon}
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setSelectedOrgId(org.organization_id);
|
||||
setEditOrg(true);
|
||||
}}
|
||||
/>
|
||||
<Icon
|
||||
onClick={() => handleDelete(org.organization_id)}
|
||||
icon={TrashIcon}
|
||||
size="sm"
|
||||
/>
|
||||
<div className="flex items-start">
|
||||
{org.models.length > 3 && (
|
||||
<div>
|
||||
<Icon
|
||||
icon={expandedAccordions[org.organization_id || ''] ? ChevronDownIcon : ChevronRightIcon}
|
||||
className="cursor-pointer"
|
||||
size="xs"
|
||||
onClick={() => {
|
||||
setExpandedAccordions(prev => ({
|
||||
...prev,
|
||||
[org.organization_id || '']: !prev[org.organization_id || '']
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{org.models.slice(0, 3).map((model, index) => (
|
||||
model === "all-proxy-models" ? (
|
||||
<Badge
|
||||
key={index}
|
||||
size={"xs"}
|
||||
color="red"
|
||||
>
|
||||
<Text>All Proxy Models</Text>
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge
|
||||
key={index}
|
||||
size={"xs"}
|
||||
color="blue"
|
||||
>
|
||||
<Text>
|
||||
{model.length > 30
|
||||
? `${getModelDisplayName(model).slice(0, 30)}...`
|
||||
: getModelDisplayName(model)}
|
||||
</Text>
|
||||
</Badge>
|
||||
)
|
||||
))}
|
||||
{org.models.length > 3 && !expandedAccordions[org.organization_id || ''] && (
|
||||
<Badge size={"xs"} color="gray" className="cursor-pointer">
|
||||
<Text>+{org.models.length - 3} {org.models.length - 3 === 1 ? 'more model' : 'more models'}</Text>
|
||||
</Badge>
|
||||
)}
|
||||
{expandedAccordions[org.organization_id || ''] && (
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{org.models.slice(3).map((model, index) => (
|
||||
model === "all-proxy-models" ? (
|
||||
<Badge
|
||||
key={index + 3}
|
||||
size={"xs"}
|
||||
color="red"
|
||||
>
|
||||
<Text>All Proxy Models</Text>
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge
|
||||
key={index + 3}
|
||||
size={"xs"}
|
||||
color="blue"
|
||||
>
|
||||
<Text>
|
||||
{model.length > 30
|
||||
? `${getModelDisplayName(model).slice(0, 30)}...`
|
||||
: getModelDisplayName(model)}
|
||||
</Text>
|
||||
</Badge>
|
||||
)
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
: null}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Card>
|
||||
</Col>
|
||||
{(userRole === "Admin" || userRole === "Org Admin") && (
|
||||
<Col numColSpan={1}>
|
||||
<Modal
|
||||
title="Create Organization"
|
||||
visible={isOrgModalVisible}
|
||||
width={800}
|
||||
footer={null}
|
||||
onCancel={handleCancel}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Text>
|
||||
TPM: {org.litellm_budget_table?.tpm_limit ? org.litellm_budget_table?.tpm_limit : "Unlimited"}
|
||||
<br />
|
||||
RPM: {org.litellm_budget_table?.rpm_limit ? org.litellm_budget_table?.rpm_limit : "Unlimited"}
|
||||
</Text>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Text>{org.members?.length || 0} Members</Text>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{userRole === "Admin" && (
|
||||
<>
|
||||
<Icon
|
||||
icon={PencilAltIcon}
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setSelectedOrgId(org.organization_id);
|
||||
setEditOrg(true);
|
||||
}}
|
||||
/>
|
||||
<Icon
|
||||
onClick={() => handleDelete(org.organization_id)}
|
||||
icon={TrashIcon}
|
||||
size="sm"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
: null}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Card>
|
||||
</Col>
|
||||
{(userRole === "Admin" || userRole === "Org Admin") && (
|
||||
<Col numColSpan={1}>
|
||||
<Button
|
||||
className="mx-auto"
|
||||
onClick={() => setIsOrgModalVisible(true)}
|
||||
>
|
||||
+ Create New Organization
|
||||
</Button>
|
||||
<Modal
|
||||
title="Create Organization"
|
||||
visible={isOrgModalVisible}
|
||||
width={800}
|
||||
footer={null}
|
||||
onCancel={handleCancel}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={handleCreate}
|
||||
labelCol={{ span: 8 }}
|
||||
wrapperCol={{ span: 16 }}
|
||||
labelAlign="left"
|
||||
>
|
||||
<Form.Item
|
||||
label="Organization Name"
|
||||
name="organization_alias"
|
||||
rules={[{ required: true, message: "Please input an organization name" }]}
|
||||
>
|
||||
<TextInput placeholder="" />
|
||||
</Form.Item>
|
||||
<Form.Item label="Models" name="models">
|
||||
<Select2
|
||||
mode="multiple"
|
||||
placeholder="Select models"
|
||||
style={{ width: "100%" }}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={handleCreate}
|
||||
labelCol={{ span: 8 }}
|
||||
wrapperCol={{ span: 16 }}
|
||||
labelAlign="left"
|
||||
>
|
||||
<Form.Item
|
||||
label="Organization Name"
|
||||
name="organization_alias"
|
||||
rules={[{ required: true, message: "Please input an organization name" }]}
|
||||
>
|
||||
<TextInput placeholder="" />
|
||||
</Form.Item>
|
||||
<Form.Item label="Models" name="models">
|
||||
<Select2
|
||||
mode="multiple"
|
||||
placeholder="Select models"
|
||||
style={{ width: "100%" }}
|
||||
>
|
||||
<Select2.Option key="all-proxy-models" value="all-proxy-models">
|
||||
All Proxy Models
|
||||
</Select2.Option>
|
||||
{userModels && userModels.length > 0 && userModels.map((model) => (
|
||||
<Select2.Option key={model} value={model}>
|
||||
{getModelDisplayName(model)}
|
||||
</Select2.Option>
|
||||
))}
|
||||
</Select2>
|
||||
</Form.Item>
|
||||
<Select2.Option key="all-proxy-models" value="all-proxy-models">
|
||||
All Proxy Models
|
||||
</Select2.Option>
|
||||
{userModels && userModels.length > 0 && userModels.map((model) => (
|
||||
<Select2.Option key={model} value={model}>
|
||||
{getModelDisplayName(model)}
|
||||
</Select2.Option>
|
||||
))}
|
||||
</Select2>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="Max Budget (USD)" name="max_budget">
|
||||
<NumericalInput step={0.01} precision={2} width={200} />
|
||||
</Form.Item>
|
||||
<Form.Item label="Reset Budget" name="budget_duration">
|
||||
<Select2 defaultValue={null} placeholder="n/a">
|
||||
<Select2.Option value="24h">daily</Select2.Option>
|
||||
<Select2.Option value="7d">weekly</Select2.Option>
|
||||
<Select2.Option value="30d">monthly</Select2.Option>
|
||||
</Select2>
|
||||
</Form.Item>
|
||||
<Form.Item label="Tokens per minute Limit (TPM)" name="tpm_limit">
|
||||
<NumericalInput step={1} width={400} />
|
||||
</Form.Item>
|
||||
<Form.Item label="Requests per minute Limit (RPM)" name="rpm_limit">
|
||||
<NumericalInput step={1} width={400} />
|
||||
</Form.Item>
|
||||
<Form.Item label="Max Budget (USD)" name="max_budget">
|
||||
<NumericalInput step={0.01} precision={2} width={200} />
|
||||
</Form.Item>
|
||||
<Form.Item label="Reset Budget" name="budget_duration">
|
||||
<Select2 defaultValue={null} placeholder="n/a">
|
||||
<Select2.Option value="24h">daily</Select2.Option>
|
||||
<Select2.Option value="7d">weekly</Select2.Option>
|
||||
<Select2.Option value="30d">monthly</Select2.Option>
|
||||
</Select2>
|
||||
</Form.Item>
|
||||
<Form.Item label="Tokens per minute Limit (TPM)" name="tpm_limit">
|
||||
<NumericalInput step={1} width={400} />
|
||||
</Form.Item>
|
||||
<Form.Item label="Requests per minute Limit (RPM)" name="rpm_limit">
|
||||
<NumericalInput step={1} width={400} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="Metadata" name="metadata">
|
||||
<Input.TextArea rows={4} />
|
||||
</Form.Item>
|
||||
<Form.Item label="Metadata" name="metadata">
|
||||
<Input.TextArea rows={4} />
|
||||
</Form.Item>
|
||||
|
||||
<div style={{ textAlign: "right", marginTop: "10px" }}>
|
||||
<Button type="submit">Create Organization</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</Modal>
|
||||
</Col>
|
||||
)}
|
||||
</Grid>
|
||||
<div style={{ textAlign: "right", marginTop: "10px" }}>
|
||||
<Button type="submit">Create Organization</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</Modal>
|
||||
</Col>
|
||||
)}
|
||||
</Grid>
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</TabGroup>
|
||||
</Col>
|
||||
</Grid>
|
||||
|
||||
{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">
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ import {
|
|||
RefreshIcon,
|
||||
StatusOnlineIcon,
|
||||
TrashIcon,
|
||||
ChevronDownIcon,
|
||||
ChevronRightIcon
|
||||
} from "@heroicons/react/outline";
|
||||
import {
|
||||
Button as Button2,
|
||||
|
|
@ -177,6 +179,7 @@ const Teams: React.FC<TeamProps> = ({
|
|||
|
||||
// Add this state near the other useState declarations
|
||||
const [guardrailsList, setGuardrailsList] = useState<string[]>([]);
|
||||
const [expandedAccordions, setExpandedAccordions] = useState<Record<string, boolean>>({});
|
||||
|
||||
useEffect(() => {
|
||||
console.log(`currentOrgForCreateTeam: ${currentOrgForCreateTeam}`);
|
||||
|
|
@ -697,48 +700,97 @@ const Teams: React.FC<TeamProps> = ({
|
|||
whiteSpace: "pre-wrap",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
className={team.models.length > 3 ? "px-0" : ""}
|
||||
>
|
||||
{Array.isArray(team.models) ? (
|
||||
<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) =>
|
||||
model === "all-proxy-models" ? (
|
||||
<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
|
||||
? `${getModelDisplayName(model).slice(0, 30)}...`
|
||||
: getModelDisplayName(model)}
|
||||
</Text>
|
||||
</Badge>
|
||||
)
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
<div className="flex flex-col">
|
||||
{Array.isArray(team.models) ? (
|
||||
<div className="flex flex-col">
|
||||
{team.models.length === 0 ? (
|
||||
<Badge size={"xs"} className="mb-1" color="red">
|
||||
<Text>All Proxy Models</Text>
|
||||
</Badge>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex items-start">
|
||||
{team.models.length > 3 && (
|
||||
<div>
|
||||
<Icon
|
||||
icon={expandedAccordions[team.team_id] ? ChevronDownIcon : ChevronRightIcon}
|
||||
className="cursor-pointer"
|
||||
size="xs"
|
||||
onClick={() => {
|
||||
setExpandedAccordions(prev => ({
|
||||
...prev,
|
||||
[team.team_id]: !prev[team.team_id]
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{team.models.slice(0, 3).map((model: string, index: number) => (
|
||||
model === "all-proxy-models" ? (
|
||||
<Badge
|
||||
key={index}
|
||||
size={"xs"}
|
||||
color="red"
|
||||
>
|
||||
<Text>All Proxy Models</Text>
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge
|
||||
key={index}
|
||||
size={"xs"}
|
||||
color="blue"
|
||||
>
|
||||
<Text>
|
||||
{model.length > 30
|
||||
? `${getModelDisplayName(model).slice(0, 30)}...`
|
||||
: getModelDisplayName(model)}
|
||||
</Text>
|
||||
</Badge>
|
||||
)
|
||||
))}
|
||||
{team.models.length > 3 && !expandedAccordions[team.team_id] && (
|
||||
<Badge size={"xs"} color="gray" className="cursor-pointer">
|
||||
<Text>+{team.models.length - 3} {team.models.length - 3 === 1 ? 'more model' : 'more models'}</Text>
|
||||
</Badge>
|
||||
)}
|
||||
{expandedAccordions[team.team_id] && (
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{team.models.slice(3).map((model: string, index: number) => (
|
||||
model === "all-proxy-models" ? (
|
||||
<Badge
|
||||
key={index + 3}
|
||||
size={"xs"}
|
||||
color="red"
|
||||
>
|
||||
<Text>All Proxy Models</Text>
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge
|
||||
key={index + 3}
|
||||
size={"xs"}
|
||||
color="blue"
|
||||
>
|
||||
<Text>
|
||||
{model.length > 30
|
||||
? `${getModelDisplayName(model).slice(0, 30)}...`
|
||||
: getModelDisplayName(model)}
|
||||
</Text>
|
||||
</Badge>
|
||||
)
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue