mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-08 03:08:45 +00:00
(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:
parent
fd1070a7d1
commit
ad450b7616
4 changed files with 361 additions and 675 deletions
|
|
@ -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>
|
||||
|
|
|
|||
179
ui/litellm-dashboard/src/components/model_dashboard/columns.tsx
Normal file
179
ui/litellm-dashboard/src/components/model_dashboard/columns.tsx
Normal 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>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
120
ui/litellm-dashboard/src/components/model_dashboard/table.tsx
Normal file
120
ui/litellm-dashboard/src/components/model_dashboard/table.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
43
ui/litellm-dashboard/src/components/model_dashboard/types.ts
Normal file
43
ui/litellm-dashboard/src/components/model_dashboard/types.ts
Normal 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[];
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue