@acarmisc/backstage-plugin-litellm 0.18.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs.js CHANGED
@@ -3007,7 +3007,7 @@ function fmtTokens(n) {
3007
3007
  if (n >= 1e3) return `${(n / 1e3).toFixed(0)}K`;
3008
3008
  return String(n);
3009
3009
  }
3010
- var import_react8, import_Box8, import_Table5, import_TableBody5, import_TableCell5, import_TableContainer5, import_TableHead5, import_TableRow5, import_Typography8, import_MenuItem4, import_Select, import_FormControl, import_InputLabel, import_ContentCopy, import_Tooltip, import_Snackbar, import_Alert3, import_styles5, ALL_PROXY_MODELS2, ModelsTable;
3010
+ var import_react8, import_Box8, import_Table5, import_TableBody5, import_TableCell5, import_TableContainer5, import_TableHead5, import_TableRow5, import_Typography8, import_MenuItem4, import_Select, import_FormControl, import_InputLabel, import_TextField5, import_InputAdornment2, import_icons_material6, import_ContentCopy, import_Tooltip, import_Snackbar, import_Alert3, import_styles5, ALL_PROXY_MODELS2, ModelsTable;
3011
3011
  var init_ModelsTable = __esm({
3012
3012
  "src/components/ModelsTable.tsx"() {
3013
3013
  "use strict";
@@ -3024,6 +3024,9 @@ var init_ModelsTable = __esm({
3024
3024
  import_Select = __toESM(require("@mui/material/Select"));
3025
3025
  import_FormControl = __toESM(require("@mui/material/FormControl"));
3026
3026
  import_InputLabel = __toESM(require("@mui/material/InputLabel"));
3027
+ import_TextField5 = __toESM(require("@mui/material/TextField"));
3028
+ import_InputAdornment2 = __toESM(require("@mui/material/InputAdornment"));
3029
+ import_icons_material6 = require("@mui/icons-material");
3027
3030
  import_ContentCopy = __toESM(require("@mui/icons-material/ContentCopy"));
3028
3031
  import_Tooltip = __toESM(require("@mui/material/Tooltip"));
3029
3032
  import_Snackbar = __toESM(require("@mui/material/Snackbar"));
@@ -3034,15 +3037,23 @@ var init_ModelsTable = __esm({
3034
3037
  ModelsTable = ({ allModels, teams, loading }) => {
3035
3038
  const theme = (0, import_styles5.useTheme)();
3036
3039
  const [selectedTeamId, setSelectedTeamId] = (0, import_react8.useState)("");
3040
+ const [filterText, setFilterText] = (0, import_react8.useState)("");
3037
3041
  const [copiedSnackbar, setCopiedSnackbar] = (0, import_react8.useState)(false);
3038
3042
  const selectedTeam = (0, import_react8.useMemo)(
3039
3043
  () => teams.find((t) => t.team_id === selectedTeamId) ?? null,
3040
3044
  [teams, selectedTeamId]
3041
3045
  );
3042
- const filteredModels = (0, import_react8.useMemo)(() => {
3046
+ const teamModels = (0, import_react8.useMemo)(() => {
3043
3047
  if (!selectedTeam) return [];
3044
3048
  return allModels.filter((model) => isModelAllowedByTeam2(model, selectedTeam.models));
3045
3049
  }, [allModels, selectedTeam]);
3050
+ const filteredModels = (0, import_react8.useMemo)(() => {
3051
+ const q = filterText.trim().toLowerCase();
3052
+ if (!q) return teamModels;
3053
+ return teamModels.filter(
3054
+ (model) => model.model_name.toLowerCase().includes(q) || (model.access_groups || []).some((group) => group.toLowerCase().includes(q))
3055
+ );
3056
+ }, [teamModels, filterText]);
3046
3057
  const handleCopyModelId = (0, import_react8.useCallback)((modelId) => {
3047
3058
  navigator.clipboard.writeText(modelId).then(() => setCopiedSnackbar(true));
3048
3059
  }, []);
@@ -3064,9 +3075,9 @@ var init_ModelsTable = __esm({
3064
3075
  SectionCard,
3065
3076
  {
3066
3077
  title: "Available Models",
3067
- subtitle: selectedTeam ? `${filteredModels.length} model${filteredModels.length !== 1 ? "s" : ""} in ${selectedTeam.team_alias ?? selectedTeam.team_id}` : "Select a team to view its models"
3078
+ subtitle: selectedTeam ? `${filteredModels.length}${filterText.trim() ? ` of ${teamModels.length}` : ""} model${filteredModels.length === 1 ? "" : "s"}` : "Select a team to view its models"
3068
3079
  },
3069
- /* @__PURE__ */ import_react8.default.createElement(import_Box8.default, { sx: { px: 2.5, pb: 2.5, pt: 1 } }, /* @__PURE__ */ import_react8.default.createElement(import_FormControl.default, { size: "small", sx: { minWidth: 240, mb: 2 } }, /* @__PURE__ */ import_react8.default.createElement(import_InputLabel.default, { id: "team-select-label" }, "Team"), /* @__PURE__ */ import_react8.default.createElement(
3080
+ /* @__PURE__ */ import_react8.default.createElement(import_Box8.default, { sx: { px: 2.5, pb: 2.5, pt: 1, display: "flex", gap: 2, flexWrap: "wrap" } }, /* @__PURE__ */ import_react8.default.createElement(import_FormControl.default, { size: "small", sx: { minWidth: 240, mb: 2 } }, /* @__PURE__ */ import_react8.default.createElement(import_InputLabel.default, { id: "team-select-label" }, "Team"), /* @__PURE__ */ import_react8.default.createElement(
3070
3081
  import_Select.default,
3071
3082
  {
3072
3083
  labelId: "team-select-label",
@@ -3075,7 +3086,20 @@ var init_ModelsTable = __esm({
3075
3086
  onChange: (e) => setSelectedTeamId(e.target.value)
3076
3087
  },
3077
3088
  teams.map((t) => /* @__PURE__ */ import_react8.default.createElement(import_MenuItem4.default, { key: t.team_id, value: t.team_id }, t.team_alias ?? t.team_id))
3078
- ))),
3089
+ )), /* @__PURE__ */ import_react8.default.createElement(
3090
+ import_TextField5.default,
3091
+ {
3092
+ label: "Filter Models",
3093
+ size: "small",
3094
+ value: filterText,
3095
+ onChange: (e) => setFilterText(e.target.value),
3096
+ disabled: !selectedTeamId,
3097
+ sx: { minWidth: 240, mb: 2 },
3098
+ InputProps: {
3099
+ startAdornment: /* @__PURE__ */ import_react8.default.createElement(import_InputAdornment2.default, { position: "start" }, /* @__PURE__ */ import_react8.default.createElement(import_icons_material6.Search, { fontSize: "small" }))
3100
+ }
3101
+ }
3102
+ )),
3079
3103
  loading && /* @__PURE__ */ import_react8.default.createElement(EmptyState, { message: "Loading models\u2026" }),
3080
3104
  !loading && !selectedTeamId && /* @__PURE__ */ import_react8.default.createElement(
3081
3105
  EmptyState,
@@ -3084,14 +3108,32 @@ var init_ModelsTable = __esm({
3084
3108
  hint: "Each team may have a different set of models assigned"
3085
3109
  }
3086
3110
  ),
3087
- !loading && selectedTeamId && filteredModels.length === 0 && /* @__PURE__ */ import_react8.default.createElement(
3111
+ !loading && selectedTeamId && teamModels.length === 0 && /* @__PURE__ */ import_react8.default.createElement(
3088
3112
  EmptyState,
3089
3113
  {
3090
3114
  message: "No models available for this team",
3091
3115
  hint: allModels.length === 0 ? "No models are configured in the system" : "None of the team's assigned models were found in the catalog"
3092
3116
  }
3093
3117
  ),
3094
- !loading && filteredModels.length > 0 && /* @__PURE__ */ import_react8.default.createElement(import_TableContainer5.default, null, /* @__PURE__ */ import_react8.default.createElement(import_Table5.default, { size: "small", sx: dataTableSx }, /* @__PURE__ */ import_react8.default.createElement(import_TableHead5.default, null, /* @__PURE__ */ import_react8.default.createElement(import_TableRow5.default, null, /* @__PURE__ */ import_react8.default.createElement(import_TableCell5.default, null, "Model ID"), /* @__PURE__ */ import_react8.default.createElement(import_TableCell5.default, null, "Mode"), /* @__PURE__ */ import_react8.default.createElement(import_TableCell5.default, { align: "right" }, "Input Cost"), /* @__PURE__ */ import_react8.default.createElement(import_TableCell5.default, { align: "right" }, "Output Cost"), /* @__PURE__ */ import_react8.default.createElement(import_TableCell5.default, { align: "right" }, "Max Input"), /* @__PURE__ */ import_react8.default.createElement(import_TableCell5.default, { align: "right" }, "Max Output"), /* @__PURE__ */ import_react8.default.createElement(import_TableCell5.default, null, "Capabilities"))), /* @__PURE__ */ import_react8.default.createElement(import_TableBody5.default, null, filteredModels.map((m) => /* @__PURE__ */ import_react8.default.createElement(import_TableRow5.default, { key: m.model_name, hover: true }, /* @__PURE__ */ import_react8.default.createElement(import_TableCell5.default, null, /* @__PURE__ */ import_react8.default.createElement(import_Box8.default, { sx: { display: "flex", alignItems: "center", gap: 0.5 } }, /* @__PURE__ */ import_react8.default.createElement(TagChip, { label: m.model_name, title: m.model_name }), /* @__PURE__ */ import_react8.default.createElement(import_Tooltip.default, { title: "Copy model ID" }, /* @__PURE__ */ import_react8.default.createElement(
3118
+ !loading && selectedTeamId && teamModels.length > 0 && filteredModels.length === 0 && /* @__PURE__ */ import_react8.default.createElement(
3119
+ EmptyState,
3120
+ {
3121
+ message: "No models match your search",
3122
+ hint: "Try a different search term"
3123
+ }
3124
+ ),
3125
+ !loading && filteredModels.length > 0 && /* @__PURE__ */ import_react8.default.createElement(import_TableContainer5.default, null, /* @__PURE__ */ import_react8.default.createElement(import_Table5.default, { size: "small", sx: dataTableSx }, /* @__PURE__ */ import_react8.default.createElement(import_TableHead5.default, null, /* @__PURE__ */ import_react8.default.createElement(import_TableRow5.default, null, /* @__PURE__ */ import_react8.default.createElement(import_TableCell5.default, null, "Model ID"), /* @__PURE__ */ import_react8.default.createElement(import_TableCell5.default, null, "Mode"), /* @__PURE__ */ import_react8.default.createElement(import_TableCell5.default, { align: "right" }, "Input Cost"), /* @__PURE__ */ import_react8.default.createElement(import_TableCell5.default, { align: "right" }, "Output Cost"), /* @__PURE__ */ import_react8.default.createElement(import_TableCell5.default, { align: "right" }, "Max Input"), /* @__PURE__ */ import_react8.default.createElement(import_TableCell5.default, { align: "right" }, "Max Output"), /* @__PURE__ */ import_react8.default.createElement(import_TableCell5.default, null, "Capabilities"))), /* @__PURE__ */ import_react8.default.createElement(import_TableBody5.default, null, filteredModels.map((m) => /* @__PURE__ */ import_react8.default.createElement(import_TableRow5.default, { key: m.model_name, hover: true }, /* @__PURE__ */ import_react8.default.createElement(import_TableCell5.default, null, /* @__PURE__ */ import_react8.default.createElement(import_Box8.default, { sx: { display: "flex", alignItems: "center", gap: 0.5 } }, /* @__PURE__ */ import_react8.default.createElement(import_Tooltip.default, { title: m.model_name, placement: "top" }, /* @__PURE__ */ import_react8.default.createElement(
3126
+ "span",
3127
+ {
3128
+ role: "button",
3129
+ tabIndex: 0,
3130
+ "aria-label": "Copy model ID",
3131
+ onClick: () => handleCopyModelId(m.model_name),
3132
+ onKeyDown: (e) => e.key === "Enter" && handleCopyModelId(m.model_name),
3133
+ style: { cursor: "pointer", display: "inline-flex" }
3134
+ },
3135
+ /* @__PURE__ */ import_react8.default.createElement(TagChip, { label: m.model_name, title: "Copy model ID" })
3136
+ )), /* @__PURE__ */ import_react8.default.createElement(import_Tooltip.default, { title: "Copy model ID" }, /* @__PURE__ */ import_react8.default.createElement(
3095
3137
  import_Box8.default,
3096
3138
  {
3097
3139
  component: "span",
@@ -3170,7 +3212,7 @@ function renderAuditLogBody(loading, entries) {
3170
3212
  }
3171
3213
  return entries.map((entry) => /* @__PURE__ */ import_react9.default.createElement(DetailRow, { key: entry.id, entry }));
3172
3214
  }
3173
- var import_react9, import_Box9, import_Typography9, import_Table6, import_TableBody6, import_TableCell6, import_TableContainer6, import_TableHead6, import_TableRow6, import_TablePagination, import_TextField5, import_MenuItem5, import_Skeleton5, import_Collapse2, import_IconButton5, import_Alert4, import_styles6, import_icons_material6, import_react_use2, ACTION_TONES, TABLE_LABELS, DetailRow, AuditLog;
3215
+ var import_react9, import_Box9, import_Typography9, import_Table6, import_TableBody6, import_TableCell6, import_TableContainer6, import_TableHead6, import_TableRow6, import_TablePagination, import_TextField6, import_MenuItem5, import_Skeleton5, import_Collapse2, import_IconButton5, import_Alert4, import_styles6, import_icons_material7, import_react_use2, ACTION_TONES, TABLE_LABELS, DetailRow, AuditLog;
3174
3216
  var init_AuditLog = __esm({
3175
3217
  "src/components/AuditLog.tsx"() {
3176
3218
  "use strict";
@@ -3184,14 +3226,14 @@ var init_AuditLog = __esm({
3184
3226
  import_TableHead6 = __toESM(require("@mui/material/TableHead"));
3185
3227
  import_TableRow6 = __toESM(require("@mui/material/TableRow"));
3186
3228
  import_TablePagination = __toESM(require("@mui/material/TablePagination"));
3187
- import_TextField5 = __toESM(require("@mui/material/TextField"));
3229
+ import_TextField6 = __toESM(require("@mui/material/TextField"));
3188
3230
  import_MenuItem5 = __toESM(require("@mui/material/MenuItem"));
3189
3231
  import_Skeleton5 = __toESM(require("@mui/material/Skeleton"));
3190
3232
  import_Collapse2 = __toESM(require("@mui/material/Collapse"));
3191
3233
  import_IconButton5 = __toESM(require("@mui/material/IconButton"));
3192
3234
  import_Alert4 = __toESM(require("@mui/material/Alert"));
3193
3235
  import_styles6 = require("@mui/material/styles");
3194
- import_icons_material6 = require("@mui/icons-material");
3236
+ import_icons_material7 = require("@mui/icons-material");
3195
3237
  import_react_use2 = require("react-use");
3196
3238
  init_ui();
3197
3239
  ACTION_TONES = {
@@ -3224,7 +3266,7 @@ var init_AuditLog = __esm({
3224
3266
  transition: theme.transitions.create("transform")
3225
3267
  })
3226
3268
  },
3227
- /* @__PURE__ */ import_react9.default.createElement(import_icons_material6.KeyboardArrowDown, { fontSize: "small" })
3269
+ /* @__PURE__ */ import_react9.default.createElement(import_icons_material7.KeyboardArrowDown, { fontSize: "small" })
3228
3270
  )), /* @__PURE__ */ import_react9.default.createElement(import_TableCell6.default, { sx: { whiteSpace: "nowrap" } }, /* @__PURE__ */ import_react9.default.createElement(import_Typography9.default, { variant: "body2", color: "text.secondary" }, formatDateTime(entry.updated_at))), /* @__PURE__ */ import_react9.default.createElement(import_TableCell6.default, null, entry.action && /* @__PURE__ */ import_react9.default.createElement(StatusPill, { label: entry.action, tone: actionTone(entry.action) })), /* @__PURE__ */ import_react9.default.createElement(import_TableCell6.default, null, /* @__PURE__ */ import_react9.default.createElement(import_Typography9.default, { variant: "body2" }, prettyTableName(entry.table_name))), /* @__PURE__ */ import_react9.default.createElement(import_TableCell6.default, null, /* @__PURE__ */ import_react9.default.createElement(
3229
3271
  import_Typography9.default,
3230
3272
  {
@@ -3293,7 +3335,7 @@ var init_AuditLog = __esm({
3293
3335
  subtitle: loading ? "Loading\u2026" : `${total.toLocaleString()} event${total === 1 ? "" : "s"}`,
3294
3336
  flush: true,
3295
3337
  actions: /* @__PURE__ */ import_react9.default.createElement(import_react9.default.Fragment, null, /* @__PURE__ */ import_react9.default.createElement(
3296
- import_TextField5.default,
3338
+ import_TextField6.default,
3297
3339
  {
3298
3340
  size: "small",
3299
3341
  label: "Action",
@@ -3311,7 +3353,7 @@ var init_AuditLog = __esm({
3311
3353
  /* @__PURE__ */ import_react9.default.createElement(import_MenuItem5.default, { value: "deleted" }, "Deleted"),
3312
3354
  /* @__PURE__ */ import_react9.default.createElement(import_MenuItem5.default, { value: "blocked" }, "Blocked")
3313
3355
  ), /* @__PURE__ */ import_react9.default.createElement(
3314
- import_TextField5.default,
3356
+ import_TextField6.default,
3315
3357
  {
3316
3358
  size: "small",
3317
3359
  label: "Table",
@@ -3330,7 +3372,7 @@ var init_AuditLog = __esm({
3330
3372
  /* @__PURE__ */ import_react9.default.createElement(import_MenuItem5.default, { value: "LiteLLM_ObjectPermissionTable" }, "Team access (KB / MCP)"),
3331
3373
  /* @__PURE__ */ import_react9.default.createElement(import_MenuItem5.default, { value: "LiteLLM_UserTable" }, "User")
3332
3374
  ), /* @__PURE__ */ import_react9.default.createElement(
3333
- import_TextField5.default,
3375
+ import_TextField6.default,
3334
3376
  {
3335
3377
  size: "small",
3336
3378
  label: "Changed by",
@@ -3849,7 +3891,7 @@ module.exports = __toCommonJS(index_exports);
3849
3891
 
3850
3892
  // src/plugin.tsx
3851
3893
  var import_react11 = __toESM(require("react"));
3852
- var import_icons_material7 = require("@mui/icons-material");
3894
+ var import_icons_material8 = require("@mui/icons-material");
3853
3895
  var import_frontend_plugin_api = require("@backstage/frontend-plugin-api");
3854
3896
  init_api();
3855
3897
  var liteLlmApi = import_frontend_plugin_api.ApiBlueprint.make({
@@ -3863,7 +3905,7 @@ var liteLlmPage = import_frontend_plugin_api.PageBlueprint.make({
3863
3905
  params: {
3864
3906
  path: "/litellm",
3865
3907
  title: "LiteLLM",
3866
- icon: /* @__PURE__ */ import_react11.default.createElement(import_icons_material7.TrendingUp, null),
3908
+ icon: /* @__PURE__ */ import_react11.default.createElement(import_icons_material8.TrendingUp, null),
3867
3909
  loader: async () => {
3868
3910
  const { LiteLLMPage: LiteLLMPage2 } = await Promise.resolve().then(() => (init_LiteLLMPage(), LiteLLMPage_exports));
3869
3911
  return /* @__PURE__ */ import_react11.default.createElement(LiteLLMPage2, null);