UI - Model Hub - refactor 'Make Public' to have a select + confirm form (#12555)

* fix(model_hub_table.tsx): fix null link

* fix(model_hub_table.tsx): add tooltip on disabled make public

tell user how to enable button

* feat(model_hub_table.tsx): make Model Hub OSS

allow more teams to share available models via litellm

* fix(model_hub_table_columns.tsx): make table view only model hub view, rename 'model' column to 'public model name', make model hub url copyable

* fix(page.tsx): fix logo re-rendering

* fix(public_model_hub.tsx): fix theme

* style(public_model_hub.tsx): move filters into same card as model table

filters are for table

* fix(page.tsx): fix ui linting error

* refactor(model_hub_table.tsx): refactor public model selection to inside the 'make public' button - modal

similar flow to adding guardrails

consistent flow for doing stuff on the proxy

* style(ui/): make 'make public' modal wider, to have all filters on same horizontal axis

* style(model_hub_table.tsx): have already public models be pre-selected

* fix(model_hub_table.tsx): ensure only admin can make models public via UI

* fix: fix linting errors

* fix(model_hub_table.tsx): make model hub purpose clearer

* fix(model_hub_table_columns.tsx): fix linting error
This commit is contained in:
Krish Dholakia 2025-07-12 14:12:52 -07:00 • committed by GitHub
parent 1f5ab2ee74
commit d8ae044ec5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 582 additions and 229 deletions

View file

@ -20,6 +20,6 @@ export default function PublicModelHubTable() {
*
*/
return (
<ModelHubTable accessToken={accessToken} publicPage={true} premiumUser={false} />
<ModelHubTable accessToken={accessToken} publicPage={true} premiumUser={false} userRole={null}/>
);
}

View file

@ -375,6 +375,7 @@ export default function CreateKeyPage() {
accessToken={accessToken}
publicPage={false}
premiumUser={premiumUser}
userRole={userRole}
/>
) : page == "caching" ? (
<CacheDashboard

View file

@ -0,0 +1,317 @@
import React, { useState, useCallback, useEffect } from "react";
import { Modal, Form, Steps, Button, message, Checkbox } from "antd";
import { Text, Title, Badge } from "@tremor/react";
import { makeModelGroupPublic } from "./networking";
import ModelFilters from "./model_filters";
const { Step } = Steps;
interface ModelGroupInfo {
model_group: string;
providers: string[];
max_input_tokens?: number;
max_output_tokens?: number;
input_cost_per_token?: number;
output_cost_per_token?: number;
mode?: string;
tpm?: number;
rpm?: number;
supports_parallel_function_calling: boolean;
supports_vision: boolean;
supports_function_calling: boolean;
supported_openai_params?: string[];
is_public_model_group: boolean;
[key: string]: any;
}
interface MakeModelPublicFormProps {
visible: boolean;
onClose: () => void;
accessToken: string;
modelHubData: ModelGroupInfo[];
onSuccess: () => void;
}
const MakeModelPublicForm: React.FC<MakeModelPublicFormProps> = ({
visible,
onClose,
accessToken,
modelHubData,
onSuccess,
}) => {
const [currentStep, setCurrentStep] = useState(0);
const [selectedModels, setSelectedModels] = useState<Set<string>>(new Set());
const [filteredData, setFilteredData] = useState<ModelGroupInfo[]>([]);
const [loading, setLoading] = useState(false);
const [form] = Form.useForm();
const handleClose = () => {
setCurrentStep(0);
setSelectedModels(new Set());
setFilteredData([]);
form.resetFields();
onClose();
};
const handleNext = () => {
if (currentStep === 0) {
if (selectedModels.size === 0) {
message.error("Please select at least one model to make public");
return;
}
setCurrentStep(1);
}
};
const handlePrevious = () => {
if (currentStep === 1) {
setCurrentStep(0);
}
};
const handleModelSelection = (modelGroup: string, checked: boolean) => {
const newSelection = new Set(selectedModels);
if (checked) {
newSelection.add(modelGroup);
} else {
newSelection.delete(modelGroup);
}
setSelectedModels(newSelection);
};
const handleSelectAll = (checked: boolean) => {
if (checked) {
const allModelGroups = filteredData.map(model => model.model_group);
setSelectedModels(new Set(allModelGroups));
} else {
setSelectedModels(new Set());
}
};
const handleFilteredDataChange = useCallback((newFilteredData: ModelGroupInfo[]) => {
setFilteredData(newFilteredData);
// Keep existing selections when filters change - don't clear them
}, []);
// Initialize filtered data and preselect already public models when modal opens
useEffect(() => {
if (visible && modelHubData.length > 0) {
setFilteredData(modelHubData);
// Preselect models that are already public
const alreadyPublicModels = modelHubData
.filter(model => model.is_public_model_group === true)
.map(model => model.model_group);
setSelectedModels(new Set(alreadyPublicModels));
}
}, [visible, modelHubData]);
const handleSubmit = async () => {
if (selectedModels.size === 0) {
message.error("Please select at least one model to make public");
return;
}
setLoading(true);
try {
const modelGroupsToMakePublic = Array.from(selectedModels);
await makeModelGroupPublic(accessToken, modelGroupsToMakePublic);
message.success(`Successfully made ${modelGroupsToMakePublic.length} model group(s) public!`);
handleClose();
onSuccess();
} catch (error) {
console.error("Error making model groups public:", error);
message.error("Failed to make model groups public. Please try again.");
} finally {
setLoading(false);
}
};
const renderStep1Content = () => {
const allModelsSelected = filteredData.length > 0 && filteredData.every(model => selectedModels.has(model.model_group));
const isIndeterminate = selectedModels.size > 0 && !allModelsSelected;
return (
<div className="space-y-4">
<div className="flex items-center justify-between">
<Title>Select Models to Make Public</Title>
<div className="flex items-center space-x-2">
<Checkbox
checked={allModelsSelected}
indeterminate={isIndeterminate}
onChange={(e) => handleSelectAll(e.target.checked)}
disabled={filteredData.length === 0}
>
Select All {filteredData.length > 0 && `(${filteredData.length})`}
</Checkbox>
</div>
</div>
<Text className="text-sm text-gray-600">
Select the models you want to make publicly accessible. These models will be visible to anyone with access to the public model hub.
</Text>
{/* Filters */}
<ModelFilters
modelHubData={modelHubData}
onFilteredDataChange={handleFilteredDataChange}
showFiltersCard={false}
className="border rounded-lg p-4 bg-gray-50"
/>
<div className="max-h-96 overflow-y-auto border rounded-lg p-4">
<div className="space-y-3">
{filteredData.length === 0 ? (
<div className="text-center py-8 text-gray-500">
<Text>No models match the current filters.</Text>
</div>
) : (
filteredData.map((model) => (
<div key={model.model_group} className="flex items-center space-x-3 p-3 border rounded-lg hover:bg-gray-50">
<Checkbox
checked={selectedModels.has(model.model_group)}
onChange={(e) => handleModelSelection(model.model_group, e.target.checked)}
/>
<div className="flex-1">
<div className="flex items-center space-x-2">
<Text className="font-medium">{model.model_group}</Text>
{model.mode && (
<Badge color="green" size="sm">{model.mode}</Badge>
)}
</div>
<div className="flex flex-wrap gap-1 mt-1">
{model.providers.map(provider => (
<Badge key={provider} color="blue" size="xs">{provider}</Badge>
))}
</div>
</div>
</div>
))
)}
</div>
</div>
{selectedModels.size > 0 && (
<div className="bg-blue-50 border border-blue-200 rounded-lg p-3">
<Text className="text-sm text-blue-800">
<strong>{selectedModels.size}</strong> model{selectedModels.size !== 1 ? 's' : ''} selected
</Text>
</div>
)}
</div>
);
};
const renderStep2Content = () => {
return (
<div className="space-y-4">
<Title>Confirm Making Models Public</Title>
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4">
<Text className="text-sm text-yellow-800">
<strong>Warning:</strong> Once you make these models public, anyone who can go to the <code>/ui/model_hub_table</code> will be able to know they exist on the proxy.
</Text>
</div>
<div className="space-y-3">
<Text className="font-medium">Models to be made public:</Text>
<div className="max-h-48 overflow-y-auto border rounded-lg p-3">
<div className="space-y-2">
{Array.from(selectedModels).map((modelGroup) => {
const model = modelHubData.find(m => m.model_group === modelGroup);
return (
<div key={modelGroup} className="flex items-center justify-between p-2 bg-gray-50 rounded">
<div>
<Text className="font-medium">{modelGroup}</Text>
{model && (
<div className="flex flex-wrap gap-1 mt-1">
{model.providers.map(provider => (
<Badge key={provider} color="blue" size="xs">{provider}</Badge>
))}
</div>
)}
</div>
</div>
);
})}
</div>
</div>
</div>
<div className="bg-blue-50 border border-blue-200 rounded-lg p-3">
<Text className="text-sm text-blue-800">
Total: <strong>{selectedModels.size}</strong> model{selectedModels.size !== 1 ? 's' : ''} will be made public
</Text>
</div>
</div>
);
};
const renderStepContent = () => {
switch (currentStep) {
case 0:
return renderStep1Content();
case 1:
return renderStep2Content();
default:
return null;
}
};
const renderStepButtons = () => {
return (
<div className="flex justify-between mt-6">
<Button
onClick={currentStep === 0 ? handleClose : handlePrevious}
>
{currentStep === 0 ? "Cancel" : "Previous"}
</Button>
<div className="flex space-x-2">
{currentStep === 0 && (
<Button
onClick={handleNext}
disabled={selectedModels.size === 0}
>
Next
</Button>
)}
{currentStep === 1 && (
<Button
onClick={handleSubmit}
loading={loading}
>
Make Public
</Button>
)}
</div>
</div>
);
};
return (
<Modal
title="Make Models Public"
open={visible}
onCancel={handleClose}
footer={null}
width={1200}
maskClosable={false}
>
<Form form={form} layout="vertical">
<Steps current={currentStep} className="mb-6">
<Step title="Select Models" />
<Step title="Confirm" />
</Steps>
{renderStepContent()}
{renderStepButtons()}
</Form>
</Modal>
);
};
export default MakeModelPublicForm;

View file

@ -0,0 +1,212 @@
import React, { useState, useEffect, useMemo, useRef } from "react";
import { Card, Text } from "@tremor/react";
interface ModelGroupInfo {
model_group: string;
providers: string[];
max_input_tokens?: number;
max_output_tokens?: number;
input_cost_per_token?: number;
output_cost_per_token?: number;
mode?: string;
tpm?: number;
rpm?: number;
supports_parallel_function_calling: boolean;
supports_vision: boolean;
supports_function_calling: boolean;
supported_openai_params?: string[];
is_public_model_group: boolean;
[key: string]: any;
}
interface ModelFiltersProps {
modelHubData: ModelGroupInfo[];
onFilteredDataChange: (filteredData: ModelGroupInfo[]) => void;
showFiltersCard?: boolean;
className?: string;
}
const ModelFilters: React.FC<ModelFiltersProps> = ({
modelHubData,
onFilteredDataChange,
showFiltersCard = true,
className = "",
}) => {
const [searchTerm, setSearchTerm] = useState<string>("");
const [selectedProvider, setSelectedProvider] = useState<string>("");
const [selectedMode, setSelectedMode] = useState<string>("");
const [selectedFeature, setSelectedFeature] = useState<string>("");
const previousFilteredDataRef = useRef<ModelGroupInfo[]>([]);
// Helper functions to get unique values
const getUniqueProviders = (data: ModelGroupInfo[]) => {
const providers = new Set<string>();
data.forEach(model => {
model.providers.forEach(provider => providers.add(provider));
});
return Array.from(providers);
};
const getUniqueModes = (data: ModelGroupInfo[]) => {
const modes = new Set<string>();
data.forEach(model => {
if (model.mode) modes.add(model.mode);
});
return Array.from(modes);
};
const getUniqueFeatures = (data: ModelGroupInfo[]) => {
const features = new Set<string>();
data.forEach(model => {
Object.entries(model)
.filter(([key, value]) => key.startsWith('supports_') && value === true)
.forEach(([key]) => {
const featureName = key
.replace(/^supports_/, '')
.split('_')
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
features.add(featureName);
});
});
return Array.from(features).sort();
};
// Memoized filtered data
const filteredData = useMemo(() => {
return modelHubData?.filter(model => {
const matchesSearch = model.model_group.toLowerCase().includes(searchTerm.toLowerCase());
const matchesProvider = selectedProvider === "" || model.providers.includes(selectedProvider);
const matchesMode = selectedMode === "" || model.mode === selectedMode;
// Check if model has the selected feature
const matchesFeature = selectedFeature === "" ||
Object.entries(model)
.filter(([key, value]) => key.startsWith('supports_') && value === true)
.some(([key]) => {
const featureName = key
.replace(/^supports_/, '')
.split('_')
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
return featureName === selectedFeature;
});
return matchesSearch && matchesProvider && matchesMode && matchesFeature;
}) || [];
}, [modelHubData, searchTerm, selectedProvider, selectedMode, selectedFeature]);
// Update parent component when filtered data changes
useEffect(() => {
// Only call the callback if the filtered data actually changed
const hasChanged =
filteredData.length !== previousFilteredDataRef.current.length ||
filteredData.some((model, index) =>
model.model_group !== previousFilteredDataRef.current[index]?.model_group
);
if (hasChanged) {
previousFilteredDataRef.current = filteredData;
onFilteredDataChange(filteredData);
}
}, [filteredData, onFilteredDataChange]);
// Reset filters function
const resetFilters = () => {
setSearchTerm("");
setSelectedProvider("");
setSelectedMode("");
setSelectedFeature("");
};
// Expose filter values and reset function
const filterValues = {
searchTerm,
selectedProvider,
selectedMode,
selectedFeature,
resetFilters,
};
const filtersContent = (
<div className="flex flex-wrap gap-4 items-center">
<div>
<Text className="text-sm font-medium mb-2">Search Models:</Text>
<input
type="text"
placeholder="Search model names..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="border rounded px-3 py-2 w-64 h-10 text-sm"
/>
</div>
<div>
<Text className="text-sm font-medium mb-2">Provider:</Text>
<select
value={selectedProvider}
onChange={(e) => setSelectedProvider(e.target.value)}
className="border rounded px-3 py-2 text-sm text-gray-600 w-40 h-10"
>
<option value="" className="text-sm text-gray-600">All Providers</option>
{modelHubData && getUniqueProviders(modelHubData).map(provider => (
<option key={provider} value={provider} className="text-sm text-gray-800">{provider}</option>
))}
</select>
</div>
<div>
<Text className="text-sm font-medium mb-2">Mode:</Text>
<select
value={selectedMode}
onChange={(e) => setSelectedMode(e.target.value)}
className="border rounded px-3 py-2 text-sm text-gray-600 w-32 h-10"
>
<option value="" className="text-sm text-gray-600">All Modes</option>
{modelHubData && getUniqueModes(modelHubData).map(mode => (
<option key={mode} value={mode} className="text-sm text-gray-800">{mode}</option>
))}
</select>
</div>
<div>
<Text className="text-sm font-medium mb-2">Features:</Text>
<select
value={selectedFeature}
onChange={(e) => setSelectedFeature(e.target.value)}
className="border rounded px-3 py-2 text-sm text-gray-600 w-48 h-10"
>
<option value="" className="text-sm text-gray-600">All Features</option>
{modelHubData && getUniqueFeatures(modelHubData).map(feature => (
<option key={feature} value={feature} className="text-sm text-gray-800">{feature}</option>
))}
</select>
</div>
{/* Clear filters button */}
{(searchTerm || selectedProvider || selectedMode || selectedFeature) && (
<div className="flex items-end">
<button
onClick={resetFilters}
className="text-blue-600 hover:text-blue-800 text-sm underline h-10 flex items-center"
>
Clear Filters
</button>
</div>
)}
</div>
);
if (showFiltersCard) {
return (
<Card className={`mb-6 ${className}`}>
{filtersContent}
</Card>
);
}
return (
<div className={className}>
{filtersContent}
</div>
);
};
export default ModelFilters;

View file

@ -1,10 +1,12 @@
import React, { useEffect, useState, useRef } from "react";
import React, { useEffect, useState, useRef, useCallback } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { modelHubCall, makeModelGroupPublic, modelHubPublicModelsCall, getProxyBaseUrl } from "./networking";
import { getConfigFieldSetting, updateConfigFieldSetting } from "./networking";
import { ModelDataTable } from "./model_dashboard/table";
import { modelHubColumns } from "./model_hub_table_columns";
import PublicModelHub from "./public_model_hub";
import MakeModelPublicForm from "./make_model_public_form";
import ModelFilters from "./model_filters";
import {
Card,
Text,
@ -17,11 +19,13 @@ import { Modal, message, Tooltip } from "antd";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { Table as TableInstance } from '@tanstack/react-table';
import { Copy } from "lucide-react";
import { isAdminRole } from "../utils/roles";
interface ModelHubTableProps {
accessToken: string | null;
publicPage: boolean;
premiumUser: boolean;
userRole: string | null;
}
interface ModelGroupInfo {
@ -38,7 +42,7 @@ interface ModelGroupInfo {
supports_vision: boolean;
supports_function_calling: boolean;
supported_openai_params?: string[];
public?: boolean; // Whether the model is public (defaults to false)
is_public_model_group: boolean;
// Allow any additional properties for flexibility
[key: string]: any;
}
@ -47,6 +51,7 @@ const ModelHubTable: React.FC<ModelHubTableProps> = ({
accessToken,
publicPage,
premiumUser,
userRole,
}) => {
const [publicPageAllowed, setPublicPageAllowed] = useState<boolean>(false);
const [modelHubData, setModelHubData] = useState<ModelGroupInfo[] | null>(null);
@ -54,11 +59,8 @@ const ModelHubTable: React.FC<ModelHubTableProps> = ({
const [isModalVisible, setIsModalVisible] = useState(false);
const [isPublicPageModalVisible, setIsPublicPageModalVisible] = useState(false);
const [selectedModel, setSelectedModel] = useState<null | ModelGroupInfo>(null);
const [searchTerm, setSearchTerm] = useState<string>("");
const [selectedProvider, setSelectedProvider] = useState<string>("");
const [selectedMode, setSelectedMode] = useState<string>("");
const [selectedFeature, setSelectedFeature] = useState<string>("");
const [selectedModels, setSelectedModels] = useState<Set<string>>(new Set());
const [filteredData, setFilteredData] = useState<ModelGroupInfo[]>([]);
const [isMakePublicModalVisible, setIsMakePublicModalVisible] = useState(false);
const router = useRouter();
const tableRef = useRef<TableInstance<any>>(null);
@ -120,34 +122,13 @@ const ModelHubTable: React.FC<ModelHubTableProps> = ({
router.replace(`/model_hub_table?key=${accessToken}`);
};
const handleMakePublicPage = async () => {
const handleMakePublicPage = () => {
if (!accessToken) {
return;
}
try {
// Get the selected model groups or use all if none are selected
const modelGroupsToMakePublic = selectedModels.size > 0
? Array.from(selectedModels)
: modelHubData?.map(model => model.model_group) || [];
if (modelGroupsToMakePublic.length > 0) {
// Call the endpoint to make the selected model groups public
await makeModelGroupPublic(accessToken, modelGroupsToMakePublic);
// Show success message
message.success(`Successfully made ${modelGroupsToMakePublic.length} model group(s) public!`);
// Route to the model hub table
router.push(`/ui/model_hub_table`);
} else {
// Show the modal if no model groups available
setIsPublicPageModalVisible(true);
}
} catch (error) {
console.error("Error making model groups public:", error);
message.error("Failed to make model groups public. Please try again.");
}
// Show the modal for selecting models to make public
setIsMakePublicModalVisible(true);
};
const handleOk = () => {
@ -187,90 +168,24 @@ const ModelHubTable: React.FC<ModelHubTableProps> = ({
return `$${(cost * 1_000_000).toFixed(2)}`;
};
const getUniqueProviders = (data: ModelGroupInfo[]) => {
const providers = new Set<string>();
data.forEach(model => {
model.providers.forEach(provider => providers.add(provider));
});
return Array.from(providers);
};
const getUniqueModes = (data: ModelGroupInfo[]) => {
const modes = new Set<string>();
data.forEach(model => {
if (model.mode) modes.add(model.mode);
});
return Array.from(modes);
};
const getUniqueFeatures = (data: ModelGroupInfo[]) => {
const features = new Set<string>();
data.forEach(model => {
// Find all properties that start with 'supports_' and are true
Object.entries(model)
.filter(([key, value]) => key.startsWith('supports_') && value === true)
.forEach(([key]) => {
// Format the feature name (remove 'supports_' prefix and convert to title case)
const featureName = key
.replace(/^supports_/, '')
.split('_')
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
features.add(featureName);
});
});
return Array.from(features).sort();
};
const filteredData = modelHubData?.filter(model => {
const matchesSearch = model.model_group.toLowerCase().includes(searchTerm.toLowerCase());
const matchesProvider = selectedProvider === "" || model.providers.includes(selectedProvider);
const matchesMode = selectedMode === "" || model.mode === selectedMode;
// Check if model has the selected feature
const matchesFeature = selectedFeature === "" ||
Object.entries(model)
.filter(([key, value]) => key.startsWith('supports_') && value === true)
.some(([key]) => {
const featureName = key
.replace(/^supports_/, '')
.split('_')
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
return featureName === selectedFeature;
});
return matchesSearch && matchesProvider && matchesMode && matchesFeature;
}) || [];
const handleRowSelection = (modelGroup: string, isSelected: boolean) => {
const newSelection = new Set(selectedModels);
if (isSelected) {
newSelection.add(modelGroup);
} else {
newSelection.delete(modelGroup);
}
setSelectedModels(newSelection);
};
const handleSelectAll = (checked: boolean) => {
console.log("checked", checked);
if (checked) {
const allModelGroups = filteredData.map(model => model.model_group);
setSelectedModels(new Set(allModelGroups));
} else {
setSelectedModels(new Set());
const handleMakePublicSuccess = () => {
// Refresh the model hub data after successful public operation
if (accessToken) {
const fetchData = async () => {
try {
const _modelHubData = await modelHubCall(accessToken);
setModelHubData(_modelHubData.data);
} catch (error) {
console.error("Error refreshing model data:", error);
}
};
fetchData();
}
};
// Use the same logic as health check columns
const allModelsSelected = filteredData.length > 0 && filteredData.every(model => selectedModels.has(model.model_group));
const isIndeterminate = selectedModels.size > 0 && !allModelsSelected;
// Clear selections when filters change to avoid confusion
useEffect(() => {
setSelectedModels(new Set());
}, [searchTerm, selectedProvider, selectedMode, selectedFeature]);
const handleFilteredDataChange = useCallback((newFilteredData: ModelGroupInfo[]) => {
setFilteredData(newFilteredData);
}, []);
console.log("publicPage: ", publicPage);
console.log("publicPageAllowed: ", publicPageAllowed);
@ -287,9 +202,15 @@ const ModelHubTable: React.FC<ModelHubTableProps> = ({
<div className="flex justify-between items-center mb-6">
<div className="flex flex-col items-start">
<Title className="text-center">Model Hub</Title>
{isAdminRole(userRole || "") ? (
<p className="text-sm text-gray-600">
Make models public for developers to know what models are available on the proxy.
</p>
): (
<p className="text-sm text-gray-600">
A list of all public model names personally available to you.
</p>
)}
</div>
<div className="flex items-center space-x-4">
<Text>Model Hub URL:</Text>
@ -304,86 +225,26 @@ const ModelHubTable: React.FC<ModelHubTableProps> = ({
</button>
</div>
{publicPage == false && (
<Tooltip
title={selectedModels.size === 0 ? "Select models to make them publicly known" : ""}
placement="top"
>
{publicPage == false && isAdminRole(userRole || "") && (
<Button
className="ml-4"
onClick={() => handleMakePublicPage()}
disabled={selectedModels.size === 0}
>
Make Public
</Button>
</Tooltip>
)}
</div>
</div>
{/* Filters */}
<Card className="mb-6">
<div className="flex flex-wrap gap-4 items-center">
<div>
<Text className="text-sm font-medium mb-2">Search Models:</Text>
<input
type="text"
placeholder="Search model names..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="border rounded px-3 py-2 w-64 h-10 text-sm"
/>
</div>
<div>
<Text className="text-sm font-medium mb-2">Provider:</Text>
<select
value={selectedProvider}
onChange={(e) => setSelectedProvider(e.target.value)}
className="border rounded px-3 py-2 text-sm text-gray-600 w-40 h-10"
>
<option value="" className="text-sm text-gray-600">All Providers</option>
{modelHubData && getUniqueProviders(modelHubData).map(provider => (
<option key={provider} value={provider} className="text-sm text-gray-800">{provider}</option>
))}
</select>
</div>
<div>
<Text className="text-sm font-medium mb-2">Mode:</Text>
<select
value={selectedMode}
onChange={(e) => setSelectedMode(e.target.value)}
className="border rounded px-3 py-2 text-sm text-gray-600 w-32 h-10"
>
<option value="" className="text-sm text-gray-600">All Modes</option>
{modelHubData && getUniqueModes(modelHubData).map(mode => (
<option key={mode} value={mode} className="text-sm text-gray-800">{mode}</option>
))}
</select>
</div>
<div>
<Text className="text-sm font-medium mb-2">Features:</Text>
<select
value={selectedFeature}
onChange={(e) => setSelectedFeature(e.target.value)}
className="border rounded px-3 py-2 text-sm text-gray-600 w-48 h-10"
>
<option value="" className="text-sm text-gray-600">All Features</option>
{modelHubData && getUniqueFeatures(modelHubData).map(feature => (
<option key={feature} value={feature} className="text-sm text-gray-800">{feature}</option>
))}
</select>
</div>
</div>
</Card>
<ModelFilters
modelHubData={modelHubData || []}
onFilteredDataChange={handleFilteredDataChange}
/>
{/* Model Table */}
<ModelDataTable
columns={modelHubColumns(
selectedModels,
allModelsSelected,
isIndeterminate,
handleRowSelection,
handleSelectAll,
showModal,
copyToClipboard,
publicPage,
@ -398,21 +259,6 @@ const ModelHubTable: React.FC<ModelHubTableProps> = ({
<Text className="text-sm text-gray-600">
Showing {filteredData.length} of {modelHubData?.length || 0} models
</Text>
{selectedModels.size > 0 && (
<div className="flex items-center justify-center space-x-4">
<Text className="text-sm text-blue-600">
{selectedModels.size} model{selectedModels.size !== 1 ? 's' : ''} selected
</Text>
<Button
size="xs"
variant="secondary"
onClick={() => setSelectedModels(new Set())}
className="text-xs"
>
Unselect All
</Button>
</div>
)}
</div>
</div>
) : (
@ -589,6 +435,15 @@ print(response.choices[0].message.content)`}
</div>
)}
</Modal>
{/* Make Model Public Form */}
<MakeModelPublicForm
visible={isMakePublicModalVisible}
onClose={() => setIsMakePublicModalVisible(false)}
accessToken={accessToken || ""}
modelHubData={modelHubData || []}
onSuccess={handleMakePublicSuccess}
/>
</div>
);
};

View file

@ -1,6 +1,6 @@
import { ColumnDef } from "@tanstack/react-table";
import { Button, Badge, Text } from "@tremor/react";
import { Tooltip, Checkbox, Tag } from "antd";
import { Tooltip, Tag } from "antd";
import {
CopyOutlined,
InfoCircleOutlined
@ -20,7 +20,7 @@ interface ModelHubData {
supports_vision: boolean;
supports_function_calling: boolean;
supported_openai_params?: string[];
public?: boolean;
is_public_model_group: boolean;
[key: string]: any;
}
@ -52,40 +52,11 @@ const formatTokens = (tokens: number) => {
};
export const modelHubColumns = (
selectedModels: Set<string>,
allModelsSelected: boolean,
isIndeterminate: boolean,
handleModelSelection: (modelGroup: string, checked: boolean) => void,
handleSelectAll: (checked: boolean) => void,
showModal: (model: ModelHubData) => void,
copyToClipboard: (text: string) => void,
publicPage: boolean = false,
): ColumnDef<ModelHubData>[] => {
const allColumns: ColumnDef<ModelHubData>[] = [
{
header: () => (
<Checkbox
checked={allModelsSelected}
indeterminate={isIndeterminate}
onChange={(e) => handleSelectAll(e.target.checked)}
onClick={(e) => e.stopPropagation()}
/>
),
id: "select",
enableSorting: false,
cell: ({ row }) => {
const model = row.original;
const isSelected = selectedModels.has(model.model_group);
return (
<Checkbox
checked={isSelected}
onChange={(e) => handleModelSelection(model.model_group, e.target.checked)}
onClick={(e) => e.stopPropagation()}
/>
);
},
},
{
header: "Public Model Name",
accessorKey: "model_group",
@ -240,7 +211,7 @@ export const modelHubColumns = (
},
{
header: "Public",
accessorKey: "public",
accessorKey: "is_public_model_group",
enableSorting: true,
sortingFn: (rowA, rowB) => {
const publicA = rowA.original.is_public_model_group === true ? 1 : 0;
@ -285,11 +256,8 @@ export const modelHubColumns = (
// Filter out columns based on publicPage setting
if (publicPage) {
return allColumns.filter(column => {
// Remove the select/checkbox column
if (column.id === "select") return false;
// Remove the public column
if ('accessorKey' in column && column.accessorKey === "public") return false;
if ('accessorKey' in column && column.accessorKey === "is_public_model_group") return false;
return true;
});