mirror of
https://github.com/tiennm99/litellm.git
synced 2026-07-16 14:17:10 +00:00
Merge pull request #21076 from BerriAI/litellm_ui_model_table_cred
[Feature] UI - Model Page: Improve Credentials Messaging
This commit is contained in:
@@ -200,7 +200,7 @@ describe("columns", () => {
|
||||
expect(screen.getByText("my-credential")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should display 'No credentials' when credential name is missing", () => {
|
||||
it("should display 'Manual' when credential name is missing", () => {
|
||||
const cols = columns(
|
||||
defaultProps.userRole,
|
||||
defaultProps.userID,
|
||||
@@ -221,7 +221,132 @@ describe("columns", () => {
|
||||
});
|
||||
render(<TestTable data={[model]} columns={cols} />);
|
||||
|
||||
expect(screen.getByText("No credentials")).toBeInTheDocument();
|
||||
expect(screen.getByText("Manual")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe("credentials column", () => {
|
||||
it("should display Credentials header with info icon", () => {
|
||||
const cols = columns(
|
||||
defaultProps.userRole,
|
||||
defaultProps.userID,
|
||||
defaultProps.premiumUser,
|
||||
defaultProps.setSelectedModelId,
|
||||
defaultProps.setSelectedTeamId,
|
||||
defaultProps.getDisplayModelName,
|
||||
defaultProps.handleEditClick,
|
||||
defaultProps.handleRefreshClick,
|
||||
defaultProps.expandedRows,
|
||||
defaultProps.setExpandedRows,
|
||||
);
|
||||
|
||||
const model = createMockModel();
|
||||
render(<TestTable data={[model]} columns={cols} />);
|
||||
|
||||
expect(screen.getByText("Credentials")).toBeInTheDocument();
|
||||
// Info icon is in a flex container with Credentials - ant icons render as span with role="img"
|
||||
const credentialsHeader = screen.getByText("Credentials").closest("span");
|
||||
expect(credentialsHeader?.parentElement?.querySelector('[role="img"]')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should display reusable credential with SyncOutlined icon and credential name", () => {
|
||||
const cols = columns(
|
||||
defaultProps.userRole,
|
||||
defaultProps.userID,
|
||||
defaultProps.premiumUser,
|
||||
defaultProps.setSelectedModelId,
|
||||
defaultProps.setSelectedTeamId,
|
||||
defaultProps.getDisplayModelName,
|
||||
defaultProps.handleEditClick,
|
||||
defaultProps.handleRefreshClick,
|
||||
defaultProps.expandedRows,
|
||||
defaultProps.setExpandedRows,
|
||||
);
|
||||
|
||||
const model = createMockModel({
|
||||
litellm_params: {
|
||||
model: "gpt-4",
|
||||
litellm_credential_name: "my-reusable-credential",
|
||||
},
|
||||
});
|
||||
render(<TestTable data={[model]} columns={cols} />);
|
||||
|
||||
expect(screen.getByText("my-reusable-credential")).toBeInTheDocument();
|
||||
const credentialCell = screen.getByText("my-reusable-credential").closest("div");
|
||||
expect(credentialCell).toHaveClass("flex");
|
||||
expect(screen.getByText("my-reusable-credential")).toHaveClass("text-blue-600");
|
||||
});
|
||||
|
||||
it("should display Manual with EditOutlined when no credential name", () => {
|
||||
const cols = columns(
|
||||
defaultProps.userRole,
|
||||
defaultProps.userID,
|
||||
defaultProps.premiumUser,
|
||||
defaultProps.setSelectedModelId,
|
||||
defaultProps.setSelectedTeamId,
|
||||
defaultProps.getDisplayModelName,
|
||||
defaultProps.handleEditClick,
|
||||
defaultProps.handleRefreshClick,
|
||||
defaultProps.expandedRows,
|
||||
defaultProps.setExpandedRows,
|
||||
);
|
||||
|
||||
const model = createMockModel({
|
||||
litellm_params: {
|
||||
model: "gpt-4",
|
||||
},
|
||||
});
|
||||
render(<TestTable data={[model]} columns={cols} />);
|
||||
|
||||
expect(screen.getByText("Manual")).toBeInTheDocument();
|
||||
expect(screen.getByText("Manual")).toHaveClass("text-gray-500");
|
||||
});
|
||||
|
||||
it("should display Manual when litellm_params is undefined", () => {
|
||||
const cols = columns(
|
||||
defaultProps.userRole,
|
||||
defaultProps.userID,
|
||||
defaultProps.premiumUser,
|
||||
defaultProps.setSelectedModelId,
|
||||
defaultProps.setSelectedTeamId,
|
||||
defaultProps.getDisplayModelName,
|
||||
defaultProps.handleEditClick,
|
||||
defaultProps.handleRefreshClick,
|
||||
defaultProps.expandedRows,
|
||||
defaultProps.setExpandedRows,
|
||||
);
|
||||
|
||||
const model = createMockModel({
|
||||
litellm_params: undefined as any,
|
||||
});
|
||||
render(<TestTable data={[model]} columns={cols} />);
|
||||
|
||||
expect(screen.getByText("Manual")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should display Manual when litellm_credential_name is empty string", () => {
|
||||
const cols = columns(
|
||||
defaultProps.userRole,
|
||||
defaultProps.userID,
|
||||
defaultProps.premiumUser,
|
||||
defaultProps.setSelectedModelId,
|
||||
defaultProps.setSelectedTeamId,
|
||||
defaultProps.getDisplayModelName,
|
||||
defaultProps.handleEditClick,
|
||||
defaultProps.handleRefreshClick,
|
||||
defaultProps.expandedRows,
|
||||
defaultProps.setExpandedRows,
|
||||
);
|
||||
|
||||
const model = createMockModel({
|
||||
litellm_params: {
|
||||
model: "gpt-4",
|
||||
litellm_credential_name: "",
|
||||
},
|
||||
});
|
||||
render(<TestTable data={[model]} columns={cols} />);
|
||||
|
||||
expect(screen.getByText("Manual")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should display created by information for DB models", () => {
|
||||
|
||||
@@ -1,11 +1,45 @@
|
||||
import { KeyIcon, TrashIcon } from "@heroicons/react/outline";
|
||||
import { EditOutlined, InfoCircleOutlined, SyncOutlined } from "@ant-design/icons";
|
||||
import { TrashIcon } from "@heroicons/react/outline";
|
||||
import { ColumnDef } from "@tanstack/react-table";
|
||||
import { Badge, Button, Icon } from "@tremor/react";
|
||||
import { Popover, Tooltip, Typography, Space, Flex } from "antd";
|
||||
import { Divider, Flex, Popover, Space, Tooltip, Typography } from "antd";
|
||||
import { ModelData } from "../../model_dashboard/types";
|
||||
import { ProviderLogo } from "./ProviderLogo";
|
||||
|
||||
const { Text } = Typography;
|
||||
const { Text, Title } = Typography;
|
||||
|
||||
const credentialsInfoPopoverContent = (
|
||||
<Space direction="vertical" size={12}>
|
||||
<Text strong style={{ fontSize: 13 }}>
|
||||
Credential types
|
||||
</Text>
|
||||
<Space direction="vertical" size={8}>
|
||||
<Flex align="center" gap={8}>
|
||||
<Space direction="vertical">
|
||||
<Flex align="center" gap={8}>
|
||||
<SyncOutlined style={{ color: "#1890ff" }} />
|
||||
<Title level={5} style={{ margin: 0, color: "#1890ff" }}>Reusable</Title>
|
||||
</Flex>
|
||||
<Text type="secondary">
|
||||
Credentials saved in LiteLLM that can be added to models repeatedly.
|
||||
</Text>
|
||||
</Space>
|
||||
</Flex>
|
||||
<Divider size="small" />
|
||||
<Flex align="center" gap={8}>
|
||||
<Space direction="vertical" size={8}>
|
||||
<Flex align="center" gap={8}>
|
||||
<EditOutlined style={{ color: "#8c8c8c", fontSize: 14, flexShrink: 0 }} />
|
||||
<Title level={5} style={{ margin: 0 }}>Manual</Title>
|
||||
</Flex>
|
||||
<Text type="secondary">
|
||||
Credentials added directly during model creation or defined in the config file.
|
||||
</Text>
|
||||
</Space>
|
||||
</Flex>
|
||||
</Space>
|
||||
</Space>
|
||||
);
|
||||
|
||||
export const columns = (
|
||||
userRole: string,
|
||||
@@ -127,7 +161,21 @@ export const columns = (
|
||||
},
|
||||
},
|
||||
{
|
||||
header: () => <span className="text-sm font-semibold">Credentials</span>,
|
||||
header: () => (
|
||||
<span className="flex items-center gap-1">
|
||||
<span className="text-sm font-semibold">Credentials</span>
|
||||
<Popover
|
||||
content={credentialsInfoPopoverContent}
|
||||
placement="bottom"
|
||||
arrow={{ pointAtCenter: true }}
|
||||
>
|
||||
<InfoCircleOutlined
|
||||
className="cursor-pointer text-gray-400 hover:text-gray-600"
|
||||
style={{ fontSize: 12 }}
|
||||
/>
|
||||
</Popover>
|
||||
</span>
|
||||
),
|
||||
accessorKey: "litellm_credential_name",
|
||||
enableSorting: false,
|
||||
size: 180,
|
||||
@@ -135,20 +183,23 @@ export const columns = (
|
||||
cell: ({ row }) => {
|
||||
const model = row.original;
|
||||
const credentialName = model.litellm_params?.litellm_credential_name;
|
||||
const isReusable = !!credentialName;
|
||||
|
||||
return credentialName ? (
|
||||
<Tooltip title={`Credential: ${credentialName}`}>
|
||||
<div className="flex items-center space-x-2 min-w-0 w-full">
|
||||
<KeyIcon className="w-4 h-4 text-blue-500 flex-shrink-0" />
|
||||
<span className="text-xs truncate" title={credentialName}>
|
||||
{credentialName}
|
||||
</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
) : (
|
||||
return (
|
||||
<div className="flex items-center space-x-2 min-w-0 w-full">
|
||||
<KeyIcon className="w-4 h-4 text-gray-300 flex-shrink-0" />
|
||||
<span className="text-xs text-gray-400">No credentials</span>
|
||||
{isReusable ? (
|
||||
<>
|
||||
<SyncOutlined className="flex-shrink-0" style={{ color: "#1890ff", fontSize: 14 }} />
|
||||
<span className="text-xs truncate text-blue-600" title={credentialName}>
|
||||
{credentialName}
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<EditOutlined className="flex-shrink-0" style={{ color: "#8c8c8c", fontSize: 14 }} />
|
||||
<span className="text-xs text-gray-500">Manual</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user