Merge pull request #20469 from swayambhu94/fix/ui/model-hub-table-crash
fix: Add array type checks for model, agent, and MCP hub data to prev…
This commit is contained in:
commit
5ce5399fbb
@ -83,7 +83,7 @@ const defaultServerRootPath = "/";
|
||||
export let serverRootPath = defaultServerRootPath;
|
||||
export let proxyBaseUrl = defaultProxyBaseUrl;
|
||||
if (isLocal != true) {
|
||||
console.log = function () { };
|
||||
console.log = function () {};
|
||||
}
|
||||
|
||||
const getWindowLocation = () => {
|
||||
@ -2009,12 +2009,35 @@ export const regenerateKeyCall = async (accessToken: string, keyToRegenerate: st
|
||||
let ModelListerrorShown = false;
|
||||
let errorTimer: NodeJS.Timeout | null = null;
|
||||
|
||||
export const modelInfoCall = async (accessToken: string, userID: string, userRole: string, page: number = 1, size: number = 50, search?: string, modelId?: string, teamId?: string, sortBy?: string, sortOrder?: string) => {
|
||||
export const modelInfoCall = async (
|
||||
accessToken: string,
|
||||
userID: string,
|
||||
userRole: string,
|
||||
page: number = 1,
|
||||
size: number = 50,
|
||||
search?: string,
|
||||
modelId?: string,
|
||||
teamId?: string,
|
||||
sortBy?: string,
|
||||
sortOrder?: string,
|
||||
) => {
|
||||
/**
|
||||
* Get all models on proxy
|
||||
*/
|
||||
try {
|
||||
console.log("modelInfoCall:", accessToken, userID, userRole, page, size, search, modelId, teamId, sortBy, sortOrder);
|
||||
console.log(
|
||||
"modelInfoCall:",
|
||||
accessToken,
|
||||
userID,
|
||||
userRole,
|
||||
page,
|
||||
size,
|
||||
search,
|
||||
modelId,
|
||||
teamId,
|
||||
sortBy,
|
||||
sortOrder,
|
||||
);
|
||||
let url = proxyBaseUrl ? `${proxyBaseUrl}/v2/model/info` : `/v2/model/info`;
|
||||
const params = new URLSearchParams();
|
||||
params.append("include_team_models", "true");
|
||||
@ -2118,6 +2141,10 @@ export const modelHubPublicModelsCall = async () => {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
if (!response.ok) {
|
||||
console.error(`modelHubPublicModelsCall failed with status ${response.status}`);
|
||||
return [];
|
||||
}
|
||||
return response.json();
|
||||
};
|
||||
|
||||
@ -2129,6 +2156,10 @@ export const agentHubPublicModelsCall = async () => {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
if (!response.ok) {
|
||||
console.error(`agentHubPublicModelsCall failed with status ${response.status}`);
|
||||
return [];
|
||||
}
|
||||
return response.json();
|
||||
};
|
||||
|
||||
@ -2140,6 +2171,10 @@ export const mcpHubPublicServersCall = async () => {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
if (!response.ok) {
|
||||
console.error(`mcpHubPublicServersCall failed with status ${response.status}`);
|
||||
return [];
|
||||
}
|
||||
return response.json();
|
||||
};
|
||||
|
||||
@ -2474,7 +2509,7 @@ export const modelAvailableCall = async (
|
||||
teamID: string | null = null,
|
||||
include_model_access_groups: boolean = false,
|
||||
only_model_access_groups: boolean = false,
|
||||
scope?: string
|
||||
scope?: string,
|
||||
) => {
|
||||
/**
|
||||
* Get all the models user has access to
|
||||
@ -5407,9 +5442,7 @@ export const getMCPSemanticFilterSettings = async (accessToken: string) => {
|
||||
* Get MCP semantic filter configuration
|
||||
*/
|
||||
try {
|
||||
const url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/get/mcp_semantic_filter_settings`
|
||||
: `/get/mcp_semantic_filter_settings`;
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/get/mcp_semantic_filter_settings` : `/get/mcp_semantic_filter_settings`;
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
@ -5433,10 +5466,7 @@ export const getMCPSemanticFilterSettings = async (accessToken: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const updateMCPSemanticFilterSettings = async (
|
||||
accessToken: string,
|
||||
settings: Record<string, any>
|
||||
) => {
|
||||
export const updateMCPSemanticFilterSettings = async (accessToken: string, settings: Record<string, any>) => {
|
||||
/**
|
||||
* Update MCP semantic filter settings
|
||||
* Settings will be applied across all pods within 10 seconds
|
||||
@ -5469,11 +5499,7 @@ export const updateMCPSemanticFilterSettings = async (
|
||||
}
|
||||
};
|
||||
|
||||
export const testMCPSemanticFilter = async (
|
||||
accessToken: string,
|
||||
model: string,
|
||||
query: string
|
||||
) => {
|
||||
export const testMCPSemanticFilter = async (accessToken: string, model: string, query: string) => {
|
||||
/**
|
||||
* Test MCP semantic filter by making a responses API call
|
||||
* Returns both the response data and headers containing filter information
|
||||
@ -5518,7 +5544,7 @@ export const testMCPSemanticFilter = async (
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
|
||||
// Return both data and headers
|
||||
return {
|
||||
data,
|
||||
@ -5778,7 +5804,9 @@ export const createPolicyAttachmentCall = async (accessToken: string, attachment
|
||||
|
||||
export const deletePolicyAttachmentCall = async (accessToken: string, attachmentId: string) => {
|
||||
try {
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/policies/attachments/${attachmentId}` : `/policies/attachments/${attachmentId}`;
|
||||
const url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/policies/attachments/${attachmentId}`
|
||||
: `/policies/attachments/${attachmentId}`;
|
||||
const response = await fetch(url, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
@ -5804,7 +5832,9 @@ export const deletePolicyAttachmentCall = async (accessToken: string, attachment
|
||||
|
||||
export const getResolvedGuardrails = async (accessToken: string, policyId: string) => {
|
||||
try {
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/policies/${policyId}/resolved-guardrails` : `/policies/${policyId}/resolved-guardrails`;
|
||||
const url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/policies/${policyId}/resolved-guardrails`
|
||||
: `/policies/${policyId}/resolved-guardrails`;
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
@ -7158,7 +7188,7 @@ export const ragIngestCall = async (
|
||||
vectorStoreId?: string,
|
||||
vectorStoreName?: string,
|
||||
vectorStoreDescription?: string,
|
||||
providerSpecificParams?: Record<string, any>
|
||||
providerSpecificParams?: Record<string, any>,
|
||||
): Promise<any> => {
|
||||
try {
|
||||
let url = proxyBaseUrl ? `${proxyBaseUrl}/rag/ingest` : `/rag/ingest`;
|
||||
@ -7779,12 +7809,10 @@ export interface TestCustomCodeGuardrailResponse {
|
||||
|
||||
export const testCustomCodeGuardrail = async (
|
||||
accessToken: string,
|
||||
request: TestCustomCodeGuardrailRequest
|
||||
request: TestCustomCodeGuardrailRequest,
|
||||
): Promise<TestCustomCodeGuardrailResponse> => {
|
||||
try {
|
||||
const url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/guardrails/test_custom_code`
|
||||
: `/guardrails/test_custom_code`;
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/guardrails/test_custom_code` : `/guardrails/test_custom_code`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
@ -8920,9 +8948,7 @@ export const updateUiSettings = async (accessToken: string, settings: Record<str
|
||||
export const getClaudeCodeMarketplace = async () => {
|
||||
try {
|
||||
const proxyBaseUrl = getProxyBaseUrl();
|
||||
const url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/claude-code/marketplace.json`
|
||||
: `/claude-code/marketplace.json`;
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/claude-code/marketplace.json` : `/claude-code/marketplace.json`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
@ -8951,10 +8977,7 @@ export const getClaudeCodeMarketplace = async () => {
|
||||
* @param accessToken - Admin access token
|
||||
* @param enabledOnly - If true, only return enabled plugins (default: false)
|
||||
*/
|
||||
export const getClaudeCodePluginsList = async (
|
||||
accessToken: string,
|
||||
enabledOnly: boolean = false
|
||||
) => {
|
||||
export const getClaudeCodePluginsList = async (accessToken: string, enabledOnly: boolean = false) => {
|
||||
try {
|
||||
const proxyBaseUrl = getProxyBaseUrl();
|
||||
const url = proxyBaseUrl
|
||||
@ -8989,10 +9012,7 @@ export const getClaudeCodePluginsList = async (
|
||||
* @param accessToken - Admin access token
|
||||
* @param pluginName - Name of the plugin
|
||||
*/
|
||||
export const getClaudeCodePluginDetails = async (
|
||||
accessToken: string,
|
||||
pluginName: string
|
||||
) => {
|
||||
export const getClaudeCodePluginDetails = async (accessToken: string, pluginName: string) => {
|
||||
try {
|
||||
const proxyBaseUrl = getProxyBaseUrl();
|
||||
const url = proxyBaseUrl
|
||||
@ -9038,13 +9058,11 @@ export const registerClaudeCodePlugin = async (
|
||||
homepage?: string;
|
||||
keywords?: string[];
|
||||
category?: string;
|
||||
}
|
||||
},
|
||||
) => {
|
||||
try {
|
||||
const proxyBaseUrl = getProxyBaseUrl();
|
||||
const url = proxyBaseUrl
|
||||
? `${proxyBaseUrl}/claude-code/plugins`
|
||||
: `/claude-code/plugins`;
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/claude-code/plugins` : `/claude-code/plugins`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
@ -9075,10 +9093,7 @@ export const registerClaudeCodePlugin = async (
|
||||
* @param accessToken - Admin access token
|
||||
* @param pluginName - Name of the plugin to enable
|
||||
*/
|
||||
export const enableClaudeCodePlugin = async (
|
||||
accessToken: string,
|
||||
pluginName: string
|
||||
) => {
|
||||
export const enableClaudeCodePlugin = async (accessToken: string, pluginName: string) => {
|
||||
try {
|
||||
const proxyBaseUrl = getProxyBaseUrl();
|
||||
const url = proxyBaseUrl
|
||||
@ -9113,10 +9128,7 @@ export const enableClaudeCodePlugin = async (
|
||||
* @param accessToken - Admin access token
|
||||
* @param pluginName - Name of the plugin to disable
|
||||
*/
|
||||
export const disableClaudeCodePlugin = async (
|
||||
accessToken: string,
|
||||
pluginName: string
|
||||
) => {
|
||||
export const disableClaudeCodePlugin = async (accessToken: string, pluginName: string) => {
|
||||
try {
|
||||
const proxyBaseUrl = getProxyBaseUrl();
|
||||
const url = proxyBaseUrl
|
||||
@ -9151,10 +9163,7 @@ export const disableClaudeCodePlugin = async (
|
||||
* @param accessToken - Admin access token
|
||||
* @param pluginName - Name of the plugin to delete
|
||||
*/
|
||||
export const deleteClaudeCodePlugin = async (
|
||||
accessToken: string,
|
||||
pluginName: string
|
||||
) => {
|
||||
export const deleteClaudeCodePlugin = async (accessToken: string, pluginName: string) => {
|
||||
try {
|
||||
const proxyBaseUrl = getProxyBaseUrl();
|
||||
const url = proxyBaseUrl
|
||||
|
||||
@ -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(<PublicModelHub />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("navbar")).toBeInTheDocument();
|
||||
expect(screen.getByText("Model Hub")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -250,7 +250,7 @@ const PublicModelHub: React.FC<PublicModelHubProps> = ({ 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<PublicModelHubProps> = ({ 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<PublicModelHubProps> = ({ 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<PublicModelHubProps> = ({ 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 <Tooltip title={<>
|
||||
<div>
|
||||
{responseTimeLabel}
|
||||
</div>
|
||||
<div>
|
||||
{lastCheckedLabel}
|
||||
</div>
|
||||
</>}><Tag key={original.model_group} color={tagColor}><span className="capitalize">{original.health_status ?? "Unknown"}</span></Tag></Tooltip>;
|
||||
return (
|
||||
<Tooltip
|
||||
title={
|
||||
<>
|
||||
<div>{responseTimeLabel}</div>
|
||||
<div>{lastCheckedLabel}</div>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Tag key={original.model_group} color={tagColor}>
|
||||
<span className="capitalize">{original.health_status ?? "Unknown"}</span>
|
||||
</Tag>
|
||||
</Tooltip>
|
||||
);
|
||||
},
|
||||
size: 100,
|
||||
},
|
||||
@ -963,7 +974,7 @@ const PublicModelHub: React.FC<PublicModelHubProps> = ({ accessToken, isEmbedded
|
||||
accessToken={accessToken || null}
|
||||
isPublicPage={true}
|
||||
isDarkMode={false}
|
||||
toggleDarkMode={() => { }}
|
||||
toggleDarkMode={() => {}}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -1092,6 +1103,7 @@ const PublicModelHub: React.FC<PublicModelHubProps> = ({ accessToken, isEmbedded
|
||||
}}
|
||||
>
|
||||
{modelHubData &&
|
||||
Array.isArray(modelHubData) &&
|
||||
getUniqueProviders(modelHubData).map((provider) => (
|
||||
<Select.Option key={provider} value={provider}>
|
||||
{provider}
|
||||
@ -1111,6 +1123,7 @@ const PublicModelHub: React.FC<PublicModelHubProps> = ({ accessToken, isEmbedded
|
||||
allowClear
|
||||
>
|
||||
{modelHubData &&
|
||||
Array.isArray(modelHubData) &&
|
||||
getUniqueModes(modelHubData).map((mode) => (
|
||||
<Select.Option key={mode} value={mode}>
|
||||
{mode}
|
||||
@ -1130,6 +1143,7 @@ const PublicModelHub: React.FC<PublicModelHubProps> = ({ accessToken, isEmbedded
|
||||
allowClear
|
||||
>
|
||||
{modelHubData &&
|
||||
Array.isArray(modelHubData) &&
|
||||
getUniqueFeatures(modelHubData).map((feature) => (
|
||||
<Select.Option key={feature} value={feature}>
|
||||
{feature}
|
||||
@ -1154,7 +1168,7 @@ const PublicModelHub: React.FC<PublicModelHubProps> = ({ accessToken, isEmbedded
|
||||
</TabPane>
|
||||
|
||||
{/* Agents Tab */}
|
||||
{agentHubData && agentHubData.length > 0 && (
|
||||
{agentHubData && Array.isArray(agentHubData) && agentHubData.length > 0 && (
|
||||
<TabPane tab="Agent Hub" key="agents">
|
||||
<div className="flex justify-between items-center mb-8">
|
||||
<Title className="text-2xl font-semibold text-gray-900">Available Agents</Title>
|
||||
@ -1192,6 +1206,7 @@ const PublicModelHub: React.FC<PublicModelHubProps> = ({ accessToken, isEmbedded
|
||||
allowClear
|
||||
>
|
||||
{agentHubData &&
|
||||
Array.isArray(agentHubData) &&
|
||||
getUniqueAgentSkills(agentHubData).map((skill) => (
|
||||
<Select.Option key={skill} value={skill}>
|
||||
{skill}
|
||||
@ -1217,7 +1232,7 @@ const PublicModelHub: React.FC<PublicModelHubProps> = ({ accessToken, isEmbedded
|
||||
)}
|
||||
|
||||
{/* MCP Servers Tab */}
|
||||
{mcpHubData && mcpHubData.length > 0 && (
|
||||
{mcpHubData && Array.isArray(mcpHubData) && mcpHubData.length > 0 && (
|
||||
<TabPane tab="MCP Hub" key="mcp">
|
||||
<div className="flex justify-between items-center mb-8">
|
||||
<Title className="text-2xl font-semibold text-gray-900">Available MCP Servers</Title>
|
||||
@ -1255,6 +1270,7 @@ const PublicModelHub: React.FC<PublicModelHubProps> = ({ accessToken, isEmbedded
|
||||
allowClear
|
||||
>
|
||||
{mcpHubData &&
|
||||
Array.isArray(mcpHubData) &&
|
||||
getUniqueMcpTransports(mcpHubData).map((transport) => (
|
||||
<Select.Option key={transport} value={transport}>
|
||||
{transport}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user