(Feat) - UI, Allow sorting models by Created_At and all other columns on the UI (#8725)

* order models by created at

* use existing table component on models page

* sorting for created at

* ui clean up models page

* remove provider filter

* fix columns sorting

* decent switching

* ui fix models page
This commit is contained in:
Ishaan Jaff 2025-02-21 19:25:08 -08:00 • committed by GitHub
parent fd1070a7d1
commit ad450b7616
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 361 additions and 675 deletions

View file

@ -108,6 +108,8 @@ import TeamInfoView from "./team/team_info";
import { Providers, provider_map, providerLogoMap, getProviderLogoAndName, getPlaceholder, getProviderModels } from "./provider_info_helpers";
import ModelInfoView from "./model_info_view";
import AddModelTab from "./add_model/add_model_tab";
import { ModelDataTable } from "./model_dashboard/table";
import { columns } from "./model_dashboard/columns";
interface ModelDashboardProps {
accessToken: string | null;
@ -733,20 +735,6 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
}
// when users click request access show pop up to allow them to request access
// sort modelData.data by provider alphabetically, check if provider exists and is not null / undefined
if (modelData.data && modelData.data.length > 0) {
modelData.data.sort((a: any, b: any) => {
if (a.provider && b.provider) {
return a.provider.localeCompare(b.provider);
} else if (a.provider && !b.provider) {
return -1;
} else if (!a.provider && b.provider) {
return 1;
} else {
return 0;
}
});
}
if (userRole && userRole == "Admin Viewer") {
const { Title, Paragraph } = Typography;
return (
@ -1097,416 +1085,23 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
))}
</Select>
</div>
<Card>
<Table style={{ maxWidth: "1500px", width: "100%" }}>
<TableHead>
<TableRow>
<TableHeaderCell
style={{
maxWidth: "100px",
whiteSpace: "normal",
wordBreak: "break-word",
fontSize: "11px",
}}
>
Model ID
</TableHeaderCell>
<TableHeaderCell
style={{
maxWidth: "150px",
whiteSpace: "normal",
wordBreak: "break-word",
fontSize: "11px",
}}
>
Public Model Name
</TableHeaderCell>
<TableHeaderCell
style={{
maxWidth: "100px",
whiteSpace: "normal",
wordBreak: "break-word",
fontSize: "11px",
}}
>
Provider
</TableHeaderCell>
<TableHeaderCell
style={{
maxWidth: "150px",
whiteSpace: "normal",
wordBreak: "break-word",
fontSize: "11px",
}}
>
LiteLLM Model
</TableHeaderCell>
{userRole === "Admin" && (
<TableHeaderCell
style={{
maxWidth: "150px",
whiteSpace: "normal",
wordBreak: "break-word",
fontSize: "11px",
}}
>
API Base
</TableHeaderCell>
)}
<TableHeaderCell
style={{
maxWidth: "85px",
whiteSpace: "normal",
wordBreak: "break-word",
fontSize: "11px",
}}
>
Input Price{" "}
<p style={{ fontSize: "10px", color: "gray" }}>
/1M Tokens ($)
</p>
</TableHeaderCell>
<TableHeaderCell
style={{
maxWidth: "85px",
whiteSpace: "normal",
wordBreak: "break-word",
fontSize: "11px",
}}
>
Output Price{" "}
<p style={{ fontSize: "10px", color: "gray" }}>
/1M Tokens ($)
</p>
</TableHeaderCell>
<TableHeaderCell
style={{
maxWidth: "100px",
whiteSpace: "normal",
wordBreak: "break-word",
fontSize: "11px",
}}
>
{premiumUser ? (
"Created At"
) : (
<a
href="https://forms.gle/W3U4PZpJGFHWtHyA9"
target="_blank"
style={{ color: "#72bcd4" }}
>
{" "}
✨ Created At
</a>
)}
</TableHeaderCell>
<TableHeaderCell
style={{
maxWidth: "100px",
whiteSpace: "normal",
wordBreak: "break-word",
fontSize: "11px",
}}
>
{premiumUser ? (
"Created By"
) : (
<a
href="https://forms.gle/W3U4PZpJGFHWtHyA9"
target="_blank"
style={{ color: "#72bcd4" }}
>
{" "}
✨ Created By
</a>
)}
</TableHeaderCell>
<TableHeaderCell
style={{
maxWidth: "100px",
whiteSpace: "normal",
wordBreak: "break-word",
fontSize: "11px",
}}
>
Team ID
</TableHeaderCell>
<TableHeaderCell
style={{
maxWidth: "50px",
whiteSpace: "normal",
wordBreak: "break-word",
fontSize: "11px",
}}
>
Status
</TableHeaderCell>
<TableHeaderCell></TableHeaderCell>
</TableRow>
</TableHead>
<TableBody>
{modelData.data
.filter(
(model: any) =>
selectedModelGroup === "all" ||
model.model_name === selectedModelGroup ||
selectedModelGroup === null ||
selectedModelGroup === undefined ||
selectedModelGroup === ""
)
.map((model: any, index: number) => (
<TableRow
key={index}
style={{ maxHeight: "1px", minHeight: "1px" }}
>
<TableCell
style={{
maxWidth: "100px",
whiteSpace: "normal",
wordBreak: "break-word",
}}
>
<div className="overflow-hidden">
<Tooltip title={model.model_info.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={() => setSelectedModelId(model.model_info.id)}
>
{model.model_info.id.slice(0, 7)}...
</Button>
</Tooltip>
</div>
</TableCell>
<TableCell
style={{
maxWidth: "100px",
whiteSpace: "normal",
wordBreak: "break-word",
}}
>
<p className="text-xs">{getDisplayModelName(model) || "-"}</p>
</TableCell>
<TableCell
style={{
maxWidth: "100px",
whiteSpace: "normal",
wordBreak: "break-word",
}}
>
<div className="flex items-center space-x-2">
{model.provider && (
<img
src={getProviderLogoAndName(model.provider).logo}
alt={`${model.provider} logo`}
className="w-4 h-4"
onError={(e) => {
// Create a div with provider initial as fallback
const target = e.target as HTMLImageElement;
const parent = target.parentElement;
if (parent) {
const fallbackDiv = document.createElement('div');
fallbackDiv.className = 'w-4 h-4 rounded-full bg-gray-200 flex items-center justify-center text-xs';
fallbackDiv.textContent = model.provider?.charAt(0) || '-';
parent.replaceChild(fallbackDiv, target);
}
}}
/>
)}
<p className="text-xs">{model.provider || "-"}</p>
</div>
</TableCell>
<TableCell
style={{
maxWidth: "100px",
whiteSpace: "normal",
wordBreak: "break-word",
}}
>
<Tooltip title={model && model.litellm_model_name}>
<pre
style={{
maxWidth: "150px",
whiteSpace: "normal",
wordBreak: "break-word",
}}
className="text-xs"
title={
model && model.litellm_model_name
? model.litellm_model_name
: ""
}
>
{model && model.litellm_model_name
? model.litellm_model_name.slice(0, 20) + (model.litellm_model_name.length > 20 ? "..." : "")
: "-"}
</pre>
</Tooltip>
</TableCell>
{userRole === "Admin" && (
<TableCell
style={{
maxWidth: "150px",
whiteSpace: "normal",
wordBreak: "break-word",
}}
>
<Tooltip title={model && model.api_base}>
<pre
style={{
maxWidth: "150px",
whiteSpace: "normal",
wordBreak: "break-word",
}}
className="text-xs"
title={
model && model.api_base
? model.api_base
: ""
}
>
{model && model.api_base
? model.api_base.slice(0, 20)
: "-"}
</pre>
</Tooltip>
</TableCell>
)}
<TableCell
style={{
maxWidth: "80px",
whiteSpace: "normal",
wordBreak: "break-word",
}}
>
<pre className="text-xs">
{model.input_cost
? model.input_cost
: model.litellm_params.input_cost_per_token != null && model.litellm_params.input_cost_per_token != undefined
? (
Number(
model.litellm_params
.input_cost_per_token
) * 1000000
).toFixed(2)
: null}
</pre>
</TableCell>
<TableCell
style={{
maxWidth: "80px",
whiteSpace: "normal",
wordBreak: "break-word",
}}
>
<pre className="text-xs">
{model.output_cost
? model.output_cost
: model.litellm_params.output_cost_per_token
? (
Number(
model.litellm_params
.output_cost_per_token
) * 1000000
).toFixed(2)
: null}
</pre>
</TableCell>
<TableCell>
<p className="text-xs">
{premiumUser
? formatCreatedAt(
model.model_info.created_at
) || "-"
: "-"}
</p>
</TableCell>
<TableCell>
<p className="text-xs">
{premiumUser
? model.model_info.created_by || "-"
: "-"}
</p>
</TableCell>
<TableCell
style={{
maxWidth: "100px",
whiteSpace: "normal",
wordBreak: "break-word",
}}
>
<div className="overflow-hidden">
{model.model_info.team_id ? (
<Tooltip title={model.model_info.team_id}>
<Button
size="xs"
variant="light"
className="font-mono text-purple-500 bg-purple-50 hover:bg-purple-100 text-xs font-normal px-2 py-0.5 text-left overflow-hidden truncate max-w-[200px]"
onClick={() => {
setSelectedTeamId(model.model_info.team_id);
}}
>
{model.model_info.team_id.slice(0, 7)}...
</Button>
</Tooltip>
) : (
"-"
)}
</div>
</TableCell>
<TableCell
style={{
maxWidth: "100px",
whiteSpace: "normal",
wordBreak: "break-word",
}}
>
{model.model_info.db_model ? (
<Badge size="xs" className="text-white">
<p className="text-xs">DB Model</p>
</Badge>
) : (
<Badge size="xs" className="text-black">
<p className="text-xs">Config Model</p>
</Badge>
)}
</TableCell>
<TableCell
style={{
maxWidth: "150px",
whiteSpace: "normal",
wordBreak: "break-word",
}}
>
<Grid numItems={2}>
<Col>
<Icon
icon={PencilAltIcon}
size="sm"
onClick={() => handleEditClick(model)}
/>
</Col>
<Col>
<DeleteModelButton
modelID={model.model_info.id}
accessToken={accessToken}
callback={handleRefreshClick}
/>
</Col>
</Grid>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Card>
<ModelDataTable
columns={columns(
premiumUser,
setSelectedModelId,
setSelectedTeamId,
getDisplayModelName,
handleEditClick,
handleRefreshClick
)}
data={modelData.data.filter(
(model: any) =>
selectedModelGroup === "all" ||
model.model_name === selectedModelGroup ||
!selectedModelGroup
)}
isLoading={false} // Add loading state if needed
/>
</Grid>
<EditModelModal
visible={editModalVisible}
@ -1543,257 +1138,6 @@ const ModelDashboard: React.FC<ModelDashboardProps> = ({
)}
</Card>
</TabPanel>
<TabPanel>
<Grid numItems={4} className="mt-2 mb-2">
<Col>
<Text>Select Time Range</Text>
<DateRangePicker
enableSelect={true}
value={dateValue}
className="mr-2"
onValueChange={(value) => {
setDateValue(value);
updateModelMetrics(
selectedModelGroup,
value.from,
value.to
); // Call updateModelMetrics with the new date range
}}
/>
</Col>
<Col className="ml-2">
<Text>Select Model Group</Text>
<Select
defaultValue={
selectedModelGroup
? selectedModelGroup
: availableModelGroups[0]
}
value={
selectedModelGroup
? selectedModelGroup
: availableModelGroups[0]
}
>
{availableModelGroups.map((group, idx) => (
<SelectItem
key={idx}
value={group}
onClick={() =>
updateModelMetrics(group, dateValue.from, dateValue.to)
}
>
{group}
</SelectItem>
))}
</Select>
</Col>
<Col>
<Popover
trigger="click" content={FilterByContent}
overlayStyle={{
width: "20vw"
}}
>
<Button
icon={FilterIcon}
size="md"
variant="secondary"
className="mt-4 ml-2"
style={{
border: "none",
}}
onClick={() => setShowAdvancedFilters(true)}
>
</Button>
</Popover>
</Col>
</Grid>
<Grid numItems={2}>
<Col>
<Card className="mr-2 max-h-[400px] min-h-[400px]">
<TabGroup>
<TabList variant="line" defaultValue="1">
<Tab value="1">Avg. Latency per Token</Tab>
<Tab value="2">✨ Time to first token</Tab>
</TabList>
<TabPanels>
<TabPanel>
<p className="text-gray-500 italic"> (seconds/token)</p>
<Text className="text-gray-500 italic mt-1 mb-1">
average Latency for successfull requests divided by
the total tokens
</Text>
{modelMetrics && modelMetricsCategories && (
<AreaChart
title="Model Latency"
className="h-72"
data={modelMetrics}
showLegend={false}
index="date"
categories={modelMetricsCategories}
connectNulls={true}
customTooltip={customTooltip}
/>
)}
</TabPanel>
<TabPanel>
<TimeToFirstToken
modelMetrics={streamingModelMetrics}
modelMetricsCategories={
streamingModelMetricsCategories
}
customTooltip={customTooltip}
premiumUser={premiumUser}
/>
</TabPanel>
</TabPanels>
</TabGroup>
</Card>
</Col>
<Col>
<Card className="ml-2 max-h-[400px] min-h-[400px] overflow-y-auto">
<Table>
<TableHead>
<TableRow>
<TableHeaderCell>Deployment</TableHeaderCell>
<TableHeaderCell>Success Responses</TableHeaderCell>
<TableHeaderCell>
Slow Responses <p>Success Responses taking 600+s</p>
</TableHeaderCell>
</TableRow>
</TableHead>
<TableBody>
{slowResponsesData.map((metric, idx) => (
<TableRow key={idx}>
<TableCell>{metric.api_base}</TableCell>
<TableCell>{metric.total_count}</TableCell>
<TableCell>{metric.slow_count}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Card>
</Col>
</Grid>
<Grid numItems={1} className="gap-2 w-full mt-2">
<Card>
<Title>All Exceptions for {selectedModelGroup}</Title>
<BarChart
className="h-60"
data={modelExceptions}
index="model"
categories={allExceptions}
stack={true}
yAxisWidth={30}
/>
</Card>
</Grid>
<Grid numItems={1} className="gap-2 w-full mt-2">
<Card>
<Title>All Up Rate Limit Errors (429) for {selectedModelGroup}</Title>
<Grid numItems={1}>
<Col>
<Subtitle style={{ fontSize: "15px", fontWeight: "normal", color: "#535452"}}>Num Rate Limit Errors { (globalExceptionData.sum_num_rate_limit_exceptions)}</Subtitle>
<BarChart
className="h-40"
data={globalExceptionData.daily_data}
index="date"
colors={['rose']}
categories={['num_rate_limit_exceptions']}
onValueChange={(v) => console.log(v)}
/>
</Col>
<Col>
</Col>
</Grid>
</Card>
{
premiumUser ? (
<>
{globalExceptionPerDeployment.map((globalActivity, index) => (
<Card key={index}>
<Title>{globalActivity.api_base ? globalActivity.api_base : "Unknown API Base"}</Title>
<Grid numItems={1}>
<Col>
<Subtitle style={{ fontSize: "15px", fontWeight: "normal", color: "#535452"}}>Num Rate Limit Errors (429) {(globalActivity.sum_num_rate_limit_exceptions)}</Subtitle>
<BarChart
className="h-40"
data={globalActivity.daily_data}
index="date"
colors={['rose']}
categories={['num_rate_limit_exceptions']}
onValueChange={(v) => console.log(v)}
/>
</Col>
</Grid>
</Card>
))}
</>
) :
<>
{globalExceptionPerDeployment && globalExceptionPerDeployment.length > 0 &&
globalExceptionPerDeployment.slice(0, 1).map((globalActivity, index) => (
<Card key={index}>
<Title>✨ Rate Limit Errors by Deployment</Title>
<p className="mb-2 text-gray-500 italic text-[12px]">Upgrade to see exceptions for all deployments</p>
<Button variant="primary" className="mb-2">
<a href="https://forms.gle/W3U4PZpJGFHWtHyA9" target="_blank">
Get Free Trial
</a>
</Button>
<Card>
<Title>{globalActivity.api_base}</Title>
<Grid numItems={1}>
<Col>
<Subtitle
style={{
fontSize: "15px",
fontWeight: "normal",
color: "#535452",
}}
>
Num Rate Limit Errors {(globalActivity.sum_num_rate_limit_exceptions)}
</Subtitle>
<BarChart
className="h-40"
data={globalActivity.daily_data}
index="date"
colors={['rose']}
categories={['num_rate_limit_exceptions']}
onValueChange={(v) => console.log(v)}
/>
</Col>
</Grid>
</Card>
</Card>
))}
</>
}
</Grid>
</TabPanel>
<TabPanel>
<div className="flex items-center">
<Text>Filter by Public Model Name</Text>

View file

@ -0,0 +1,179 @@
import { ColumnDef } from "@tanstack/react-table";
import { Button, Badge } from "@tremor/react";
import { Tooltip } from "antd";
import { getProviderLogoAndName } from "../provider_info_helpers";
import { ModelData } from "./types";
import { TrashIcon, PencilIcon } from "@heroicons/react/outline";
import DeleteModelButton from "../delete_model_button";
export const columns = (
premiumUser: boolean,
setSelectedModelId: (id: string) => void,
setSelectedTeamId: (id: string) => void,
getDisplayModelName: (model: any) => string,
handleEditClick: (model: any) => void,
handleRefreshClick: () => void,
): ColumnDef<ModelData>[] => [
{
header: "Model ID",
accessorKey: "model_info.id",
cell: ({ row }) => {
const model = row.original;
return (
<div className="overflow-hidden">
<Tooltip title={model.model_info.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={() => setSelectedModelId(model.model_info.id)}
>
{model.model_info.id.slice(0, 7)}...
</Button>
</Tooltip>
</div>
);
},
},
{
header: "Model Name",
accessorKey: "model_name",
cell: ({ row }) => (
<p className="text-xs">{getDisplayModelName(row.original) || "-"}</p>
),
},
{
header: "Provider",
accessorKey: "provider",
cell: ({ row }) => {
const model = row.original;
return (
<div className="flex items-center space-x-2">
{model.provider && (
<img
src={getProviderLogoAndName(model.provider).logo}
alt={`${model.provider} logo`}
className="w-4 h-4"
onError={(e) => {
const target = e.target as HTMLImageElement;
const parent = target.parentElement;
if (parent) {
const fallbackDiv = document.createElement('div');
fallbackDiv.className = 'w-4 h-4 rounded-full bg-gray-200 flex items-center justify-center text-xs';
fallbackDiv.textContent = model.provider?.charAt(0) || '-';
parent.replaceChild(fallbackDiv, target);
}
}}
/>
)}
<p className="text-xs">{model.provider || "-"}</p>
</div>
);
},
},
{
header: "LiteLLM Model Name",
accessorKey: "litellm_model_name",
cell: ({ row }) => {
const model = row.original;
return (
<Tooltip title={model.litellm_model_name}>
<pre className="text-xs">
{model.litellm_model_name
? model.litellm_model_name.slice(0, 20) + (model.litellm_model_name.length > 20 ? "..." : "")
: "-"}
</pre>
</Tooltip>
);
},
},
{
header: "Created At",
accessorKey: "model_info.created_at",
sortingFn: "datetime",
cell: ({ row }) => {
const model = row.original;
return (
<span className="text-xs">
{model.model_info.created_at ? new Date(model.model_info.created_at).toLocaleDateString() : "-"}
</span>
);
},
},
{
header: "Created By",
accessorKey: "model_info.created_by",
cell: ({ row }) => {
const model = row.original;
return (
<span className="text-xs">
{model.model_info.created_by || "-"}
</span>
);
},
},
{
header: "Input Cost (per 1M tokens)",
accessorKey: "input_cost",
cell: ({ row }) => {
const model = row.original;
return (
<pre className="text-xs">
{model.input_cost || "-"}
</pre>
);
},
},
{
header: "Output Cost (per 1M tokens)",
accessorKey: "output_cost",
cell: ({ row }) => {
const model = row.original;
return (
<pre className="text-xs">
{model.output_cost || "-"}
</pre>
);
},
},
{
header: "Team ID",
accessorKey: "model_info.team_id",
cell: ({ row }) => {
const model = row.original;
return model.model_info.team_id ? (
<div className="overflow-hidden">
<Tooltip title={model.model_info.team_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={() => setSelectedTeamId(model.model_info.team_id)}
>
{model.model_info.team_id.slice(0, 7)}...
</Button>
</Tooltip>
</div>
) : (
"-"
);
},
},
{
header: "Status",
accessorKey: "model_info.db_model",
cell: ({ row }) => {
const model = row.original;
return (
<div className={`
inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium
${model.model_info.db_model
? 'bg-blue-50 text-blue-600'
: 'bg-gray-100 text-gray-600'}
`}>
{model.model_info.db_model ? "DB Model" : "Config Model"}
</div>
);
},
},
];

View file

@ -0,0 +1,120 @@
import { Fragment } from "react";
import {
ColumnDef,
flexRender,
getCoreRowModel,
getSortedRowModel,
SortingState,
useReactTable,
} from "@tanstack/react-table";
import React from "react";
import {
Table,
TableHead,
TableHeaderCell,
TableBody,
TableRow,
TableCell,
} from "@tremor/react";
import { SwitchVerticalIcon, ChevronUpIcon, ChevronDownIcon } from "@heroicons/react/outline";
interface ModelDataTableProps<TData, TValue> {
data: TData[];
columns: ColumnDef<TData, TValue>[];
isLoading?: boolean;
}
export function ModelDataTable<TData, TValue>({
data = [],
columns,
isLoading = false,
}: ModelDataTableProps<TData, TValue>) {
const [sorting, setSorting] = React.useState<SortingState>([
{ id: "model_info.created_at", desc: true }
]);
const table = useReactTable({
data,
columns,
state: {
sorting,
},
onSortingChange: setSorting,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
enableSorting: true,
});
return (
<div className="rounded-lg custom-border">
<Table className="[&_td]:py-0.5 [&_th]:py-1">
<TableHead>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<TableHeaderCell
key={header.id}
className="py-1 h-8 cursor-pointer hover:bg-gray-50"
onClick={header.column.getToggleSortingHandler()}
>
<div className="flex items-center justify-between gap-2">
<div className="flex items-center">
{header.isPlaceholder ? null : (
flexRender(
header.column.columnDef.header,
header.getContext()
)
)}
</div>
<div className="w-4">
{header.column.getIsSorted() ? (
{
asc: <ChevronUpIcon className="h-4 w-4 text-blue-500" />,
desc: <ChevronDownIcon className="h-4 w-4 text-blue-500" />
}[header.column.getIsSorted() as string]
) : (
<SwitchVerticalIcon className="h-4 w-4 text-gray-400" />
)}
</div>
</div>
</TableHeaderCell>
))}
</TableRow>
))}
</TableHead>
<TableBody>
{isLoading ? (
<TableRow>
<TableCell colSpan={columns.length} className="h-8 text-center">
<div className="text-center text-gray-500">
<p>🚅 Loading models...</p>
</div>
</TableCell>
</TableRow>
) : table.getRowModel().rows.length > 0 ? (
table.getRowModel().rows.map((row) => (
<TableRow key={row.id} className="h-8">
{row.getVisibleCells().map((cell) => (
<TableCell
key={cell.id}
className="py-0.5 max-h-8 overflow-hidden text-ellipsis whitespace-nowrap"
>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-8 text-center">
<div className="text-center text-gray-500">
<p>No models found</p>
</div>
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
);
}

View file

@ -0,0 +1,43 @@
export interface ModelInfo {
id: string;
created_at: string;
created_by: string;
team_id: string;
db_model: boolean;
}
export interface LiteLLMParams {
model: string;
api_base?: string;
input_cost_per_token?: number;
output_cost_per_token?: number;
custom_llm_provider?: string;
[key: string]: any;
}
export interface ModelData {
model_info: ModelInfo;
model_name: string;
provider: string;
litellm_model_name: string;
input_cost: number;
output_cost: number;
max_tokens: number;
max_input_tokens: number;
api_base?: string;
litellm_params: LiteLLMParams;
cleanedLitellmParams: Record<string, any>;
accessToken?: string;
}
export interface ModelDashboardProps {
accessToken: string;
token: string;
userRole: string;
userID: string;
modelData: { data: ModelData[] };
keys: any[];
setModelData: (data: any) => void;
premiumUser: boolean;
teams: any[];
}