diff --git a/ui/litellm-dashboard/src/components/public_model_hub.test.tsx b/ui/litellm-dashboard/src/components/public_model_hub.test.tsx index 8e874989e4b..9979fce5ebd 100644 --- a/ui/litellm-dashboard/src/components/public_model_hub.test.tsx +++ b/ui/litellm-dashboard/src/components/public_model_hub.test.tsx @@ -38,10 +38,10 @@ beforeAll(() => { matches: false, media: query, onchange: null, - addListener: () => { }, - removeListener: () => { }, - addEventListener: () => { }, - removeEventListener: () => { }, + addListener: () => {}, + removeListener: () => {}, + addEventListener: () => {}, + removeEventListener: () => {}, dispatchEvent: () => false, }), }); @@ -171,4 +171,18 @@ describe("PublicModelHub", () => { expect(unknownStatus).toBeInTheDocument(); }); }); + it("handles non-array response gracefully (regression test for e.filter crash)", async () => { + const networkingModule = await import("./networking"); + // Mock the API to return an object (like an error response) instead of an array + vi.mocked(networkingModule.modelHubPublicModelsCall).mockResolvedValue({ + detail: "No models configured", + } as any); + + render(); + + await waitFor(() => { + expect(screen.getByTestId("navbar")).toBeInTheDocument(); + expect(screen.getByText("Model Hub")).toBeInTheDocument(); + }); + }); }); diff --git a/ui/litellm-dashboard/src/components/public_model_hub.tsx b/ui/litellm-dashboard/src/components/public_model_hub.tsx index db316d44106..0e42b658e50 100644 --- a/ui/litellm-dashboard/src/components/public_model_hub.tsx +++ b/ui/litellm-dashboard/src/components/public_model_hub.tsx @@ -250,7 +250,7 @@ const PublicModelHub: React.FC = ({ accessToken, isEmbedded }; const filteredData = useMemo(() => { - if (!modelHubData) return []; + if (!modelHubData || !Array.isArray(modelHubData)) return []; let searchResults = modelHubData; @@ -324,7 +324,7 @@ const PublicModelHub: React.FC = ({ accessToken, isEmbedded }, [modelHubData, searchTerm, selectedProviders, selectedModes, selectedFeatures]); const filteredAgentData = useMemo(() => { - if (!agentHubData) return []; + if (!agentHubData || !Array.isArray(agentHubData)) return []; let searchResults = agentHubData; @@ -375,7 +375,7 @@ const PublicModelHub: React.FC = ({ accessToken, isEmbedded }, [agentHubData, agentSearchTerm, selectedAgentSkills]); const filteredMcpData = useMemo(() => { - if (!mcpHubData) return []; + if (!mcpHubData || !Array.isArray(mcpHubData)) return []; let searchResults = mcpHubData; @@ -696,18 +696,29 @@ const PublicModelHub: React.FC = ({ accessToken, isEmbedded enableSorting: true, cell: ({ row }) => { const original = row.original; - const tagColor = original.health_status === "healthy" ? "green" : original.health_status === "unhealthy" ? "red" : "default"; - const responseTimeLabel = original.health_response_time ? `Response Time: ${Number(original.health_response_time).toFixed(2)}ms` : "N/A"; - const lastCheckedLabel = original.health_checked_at ? `Last Checked: ${new Date(original.health_checked_at).toLocaleString()}` : "N/A"; + const tagColor = + original.health_status === "healthy" ? "green" : original.health_status === "unhealthy" ? "red" : "default"; + const responseTimeLabel = original.health_response_time + ? `Response Time: ${Number(original.health_response_time).toFixed(2)}ms` + : "N/A"; + const lastCheckedLabel = original.health_checked_at + ? `Last Checked: ${new Date(original.health_checked_at).toLocaleString()}` + : "N/A"; - return -
- {responseTimeLabel} -
-
- {lastCheckedLabel} -
- }>{original.health_status ?? "Unknown"}
; + return ( + +
{responseTimeLabel}
+
{lastCheckedLabel}
+ + } + > + + {original.health_status ?? "Unknown"} + +
+ ); }, size: 100, }, @@ -963,7 +974,7 @@ const PublicModelHub: React.FC = ({ accessToken, isEmbedded accessToken={accessToken || null} isPublicPage={true} isDarkMode={false} - toggleDarkMode={() => { }} + toggleDarkMode={() => {}} /> )} @@ -1092,6 +1103,7 @@ const PublicModelHub: React.FC = ({ accessToken, isEmbedded }} > {modelHubData && + Array.isArray(modelHubData) && getUniqueProviders(modelHubData).map((provider) => ( {provider} @@ -1111,6 +1123,7 @@ const PublicModelHub: React.FC = ({ accessToken, isEmbedded allowClear > {modelHubData && + Array.isArray(modelHubData) && getUniqueModes(modelHubData).map((mode) => ( {mode} @@ -1130,6 +1143,7 @@ const PublicModelHub: React.FC = ({ accessToken, isEmbedded allowClear > {modelHubData && + Array.isArray(modelHubData) && getUniqueFeatures(modelHubData).map((feature) => ( {feature} @@ -1154,7 +1168,7 @@ const PublicModelHub: React.FC = ({ accessToken, isEmbedded {/* Agents Tab */} - {agentHubData && agentHubData.length > 0 && ( + {agentHubData && Array.isArray(agentHubData) && agentHubData.length > 0 && (
Available Agents @@ -1192,6 +1206,7 @@ const PublicModelHub: React.FC = ({ accessToken, isEmbedded allowClear > {agentHubData && + Array.isArray(agentHubData) && getUniqueAgentSkills(agentHubData).map((skill) => ( {skill} @@ -1217,7 +1232,7 @@ const PublicModelHub: React.FC = ({ accessToken, isEmbedded )} {/* MCP Servers Tab */} - {mcpHubData && mcpHubData.length > 0 && ( + {mcpHubData && Array.isArray(mcpHubData) && mcpHubData.length > 0 && (
Available MCP Servers @@ -1255,6 +1270,7 @@ const PublicModelHub: React.FC = ({ accessToken, isEmbedded allowClear > {mcpHubData && + Array.isArray(mcpHubData) && getUniqueMcpTransports(mcpHubData).map((transport) => ( {transport}