@acarmisc/backstage-plugin-litellm 0.13.0 → 0.15.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/components/ModelsTable.d.ts +9 -0
- package/dist/index.cjs.js +367 -129
- package/dist/index.cjs.js.map +4 -4
- package/dist/index.esm.js +358 -120
- package/dist/index.esm.js.map +4 -4
- package/dist/types.d.ts +2 -0
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -1350,10 +1350,18 @@ import { ContentCopy as ContentCopy2, Code } from "@mui/icons-material";
|
|
|
1350
1350
|
function trimSlash(url) {
|
|
1351
1351
|
return url.replace(/\/+$/, "");
|
|
1352
1352
|
}
|
|
1353
|
+
function isModelAllowedByTeam(model, teamModels) {
|
|
1354
|
+
if (!teamModels || teamModels.length === 0) return true;
|
|
1355
|
+
if (teamModels.includes(ALL_PROXY_MODELS)) return true;
|
|
1356
|
+
if (teamModels.includes(model.model_name)) return true;
|
|
1357
|
+
return !!model.access_groups?.some((group) => teamModels.includes(group));
|
|
1358
|
+
}
|
|
1353
1359
|
function buildSnippets(baseUrl, key, model) {
|
|
1354
1360
|
const base = trimSlash(baseUrl);
|
|
1361
|
+
const apiBase = `${base}/v1`;
|
|
1355
1362
|
return {
|
|
1356
|
-
|
|
1363
|
+
publicEndpoint: apiBase,
|
|
1364
|
+
curl: `curl ${apiBase}/chat/completions \\
|
|
1357
1365
|
-H "Authorization: Bearer ${key}" \\
|
|
1358
1366
|
-H "Content-Type: application/json" \\
|
|
1359
1367
|
-d '{
|
|
@@ -1364,14 +1372,61 @@ function buildSnippets(baseUrl, key, model) {
|
|
|
1364
1372
|
|
|
1365
1373
|
client = OpenAI(
|
|
1366
1374
|
api_key="${key}",
|
|
1367
|
-
base_url="${
|
|
1375
|
+
base_url="${apiBase}",
|
|
1368
1376
|
)
|
|
1369
1377
|
|
|
1370
1378
|
response = client.chat.completions.create(
|
|
1371
1379
|
model="${model}",
|
|
1372
1380
|
messages=[{ "role": "user", "content": "Hello!" }],
|
|
1373
1381
|
)
|
|
1374
|
-
print(response.choices[0].message.content)
|
|
1382
|
+
print(response.choices[0].message.content)`,
|
|
1383
|
+
opencode: `{
|
|
1384
|
+
"$schema": "https://opencode.ai/config.json",
|
|
1385
|
+
"provider": {
|
|
1386
|
+
"litellm": {
|
|
1387
|
+
"npm": "@ai-sdk/openai-compatible",
|
|
1388
|
+
"name": "LiteLLM",
|
|
1389
|
+
"options": {
|
|
1390
|
+
"baseURL": "${apiBase}",
|
|
1391
|
+
"apiKey": "${key}"
|
|
1392
|
+
},
|
|
1393
|
+
"models": {
|
|
1394
|
+
"${model}": {}
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
}`,
|
|
1399
|
+
pi: `{
|
|
1400
|
+
"litellm": {
|
|
1401
|
+
"baseUrl": "${apiBase}",
|
|
1402
|
+
"apiKey": "${key}",
|
|
1403
|
+
"api": "openai-completions",
|
|
1404
|
+
"models": [
|
|
1405
|
+
{ "id": "${model}", "name": "${model}" }
|
|
1406
|
+
]
|
|
1407
|
+
}
|
|
1408
|
+
}`,
|
|
1409
|
+
claudeCode: `# Option 1 \u2014 Static key (Method 1: Unified Endpoint)
|
|
1410
|
+
export ANTHROPIC_AUTH_TOKEN="${key}"
|
|
1411
|
+
export ANTHROPIC_BASE_URL="${base}"
|
|
1412
|
+
claude --model ${model}
|
|
1413
|
+
|
|
1414
|
+
# Option 2 \u2014 Dynamic key via helper script
|
|
1415
|
+
# 1. Create ~/bin/get-litellm-key.sh:
|
|
1416
|
+
cat > ~/bin/get-litellm-key.sh << 'SCRIPT'
|
|
1417
|
+
#!/bin/bash
|
|
1418
|
+
curl -s -X POST ${base}/key/generate \\
|
|
1419
|
+
-H "Authorization: Bearer ${key}" \\
|
|
1420
|
+
-H "Content-Type: application/json" \\
|
|
1421
|
+
-d '{}' | jq -r '.key'
|
|
1422
|
+
SCRIPT
|
|
1423
|
+
chmod +x ~/bin/get-litellm-key.sh
|
|
1424
|
+
|
|
1425
|
+
# 2. Add to ~/.claude/settings.json:
|
|
1426
|
+
# { "apiKeyHelper": "~/bin/get-litellm-key.sh" }
|
|
1427
|
+
|
|
1428
|
+
# 3. Set refresh interval (optional, default 1h):
|
|
1429
|
+
export CLAUDE_CODE_API_KEY_HELPER_TTL_MS=3600000`
|
|
1375
1430
|
};
|
|
1376
1431
|
}
|
|
1377
1432
|
function aliasHelperText(aliasError, aliasDuplicate) {
|
|
@@ -1409,7 +1464,7 @@ function formatContextWindow2(maxInput, maxOutput) {
|
|
|
1409
1464
|
if (outPart) return `ctx ${outPart} out`;
|
|
1410
1465
|
return null;
|
|
1411
1466
|
}
|
|
1412
|
-
var generateDefaultAlias, emptyForm, SnippetTabs, GenerateKeyDialog;
|
|
1467
|
+
var generateDefaultAlias, emptyForm, ALL_PROXY_MODELS, SNIPPET_FILE_HINTS, SnippetTabs, GenerateKeyDialog;
|
|
1413
1468
|
var init_GenerateKeyDialog = __esm({
|
|
1414
1469
|
"src/components/GenerateKeyDialog.tsx"() {
|
|
1415
1470
|
"use strict";
|
|
@@ -1429,10 +1484,18 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1429
1484
|
team_id: void 0,
|
|
1430
1485
|
key_type: "llm_api"
|
|
1431
1486
|
});
|
|
1487
|
+
ALL_PROXY_MODELS = "all-proxy-models";
|
|
1488
|
+
SNIPPET_FILE_HINTS = {
|
|
1489
|
+
opencode: "Add to ~/.config/opencode/opencode.json",
|
|
1490
|
+
pi: "Add to ~/.pi/agent/models.json",
|
|
1491
|
+
"claude-code": "Requires Claude Code CLI installed"
|
|
1492
|
+
};
|
|
1432
1493
|
SnippetTabs = ({ snippets, model, onCopy }) => {
|
|
1433
1494
|
const [tab, setTab] = useState2("curl");
|
|
1434
|
-
const
|
|
1435
|
-
|
|
1495
|
+
const snippetKey = tab === "claude-code" ? "claudeCode" : tab;
|
|
1496
|
+
const code = snippets[snippetKey];
|
|
1497
|
+
const fileHint = SNIPPET_FILE_HINTS[tab];
|
|
1498
|
+
return /* @__PURE__ */ React4.createElement(Box4, null, /* @__PURE__ */ React4.createElement(Tabs, { value: tab, onChange: (_, v) => setTab(v), sx: { mb: 1 }, variant: "scrollable" }, /* @__PURE__ */ React4.createElement(Tab, { label: "curl", value: "curl" }), /* @__PURE__ */ React4.createElement(Tab, { label: "OpenAI SDK", value: "openai" }), /* @__PURE__ */ React4.createElement(Tab, { label: "opencode", value: "opencode" }), /* @__PURE__ */ React4.createElement(Tab, { label: "pi", value: "pi" }), /* @__PURE__ */ React4.createElement(Tab, { label: "Claude Code", value: "claude-code" })), fileHint && /* @__PURE__ */ React4.createElement(Typography4, { variant: "caption", color: "text.secondary", display: "block", mb: 0.5 }, fileHint), /* @__PURE__ */ React4.createElement(
|
|
1436
1499
|
Box4,
|
|
1437
1500
|
{
|
|
1438
1501
|
position: "relative",
|
|
@@ -1499,12 +1562,7 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1499
1562
|
const teamRequired = keyGenerationSettings?.teamRequired ?? true;
|
|
1500
1563
|
const selectedTeam = teams.find((t) => t.team_id === formData.team_id) ?? null;
|
|
1501
1564
|
const availableModels = useMemo3(() => {
|
|
1502
|
-
|
|
1503
|
-
if (teamModels && teamModels.length > 0) {
|
|
1504
|
-
const allowed = new Set(teamModels);
|
|
1505
|
-
return models.filter((m) => allowed.has(m.model_name));
|
|
1506
|
-
}
|
|
1507
|
-
return models;
|
|
1565
|
+
return models.filter((m) => isModelAllowedByTeam(m, selectedTeam?.models));
|
|
1508
1566
|
}, [models, selectedTeam]);
|
|
1509
1567
|
const selectedModels = availableModels.filter((m) => (formData.models || []).includes(m.model_name));
|
|
1510
1568
|
const aliasError = !(formData.alias || "").trim();
|
|
@@ -1528,12 +1586,12 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1528
1586
|
max_budget: unlimitedBudget ? null : formData.max_budget
|
|
1529
1587
|
};
|
|
1530
1588
|
const response = await onGenerateKey(request);
|
|
1589
|
+
const model = formData.models?.[0] ?? availableModels[0]?.model_name ?? "";
|
|
1531
1590
|
setNewKeyValue(response.key);
|
|
1532
|
-
setNewKeyModel(
|
|
1591
|
+
setNewKeyModel(model);
|
|
1533
1592
|
setNewKeySnippets(null);
|
|
1534
1593
|
try {
|
|
1535
1594
|
const config = await onGetConfig();
|
|
1536
|
-
const model = formData.models?.[0] ?? "";
|
|
1537
1595
|
setNewKeySnippets(buildSnippets(config.baseUrl, response.key, model));
|
|
1538
1596
|
} catch {
|
|
1539
1597
|
}
|
|
@@ -1571,8 +1629,10 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1571
1629
|
getOptionLabel: (t) => t.team_alias || t.team_id,
|
|
1572
1630
|
value: selectedTeam,
|
|
1573
1631
|
onChange: (_e, team) => {
|
|
1574
|
-
const
|
|
1575
|
-
|
|
1632
|
+
const restrictedModels = (formData.models || []).filter((name) => {
|
|
1633
|
+
const model = models.find((m) => m.model_name === name);
|
|
1634
|
+
return model ? isModelAllowedByTeam(model, team?.models) : false;
|
|
1635
|
+
});
|
|
1576
1636
|
setFormData({ ...formData, team_id: team?.team_id, models: restrictedModels });
|
|
1577
1637
|
},
|
|
1578
1638
|
renderInput: (params) => /* @__PURE__ */ React4.createElement(
|
|
@@ -1619,7 +1679,31 @@ var init_GenerateKeyDialog = __esm({
|
|
|
1619
1679
|
newKeyValue
|
|
1620
1680
|
),
|
|
1621
1681
|
/* @__PURE__ */ React4.createElement(IconButton2, { onClick: () => copyToClipboard(newKeyValue) }, /* @__PURE__ */ React4.createElement(ContentCopy2, null))
|
|
1622
|
-
), newKeySnippets && /* @__PURE__ */ React4.createElement(Box4, { mt:
|
|
1682
|
+
), newKeySnippets && /* @__PURE__ */ React4.createElement(Box4, { mt: 2 }, /* @__PURE__ */ React4.createElement(Typography4, { variant: "caption", color: "text.secondary", display: "block", gutterBottom: true }, "Public endpoint \u2014 paste into any tool's base URL / API base field"), /* @__PURE__ */ React4.createElement(
|
|
1683
|
+
Box4,
|
|
1684
|
+
{
|
|
1685
|
+
display: "flex",
|
|
1686
|
+
alignItems: "center",
|
|
1687
|
+
gap: 1,
|
|
1688
|
+
p: 1.5,
|
|
1689
|
+
sx: {
|
|
1690
|
+
backgroundColor: "action.hover",
|
|
1691
|
+
border: "1px solid",
|
|
1692
|
+
borderColor: "divider",
|
|
1693
|
+
borderRadius: 1
|
|
1694
|
+
}
|
|
1695
|
+
},
|
|
1696
|
+
/* @__PURE__ */ React4.createElement(
|
|
1697
|
+
Typography4,
|
|
1698
|
+
{
|
|
1699
|
+
component: "code",
|
|
1700
|
+
color: "text.primary",
|
|
1701
|
+
sx: { fontFamily: "monospace", fontSize: 13, wordBreak: "break-all", flex: 1 }
|
|
1702
|
+
},
|
|
1703
|
+
newKeySnippets.publicEndpoint
|
|
1704
|
+
),
|
|
1705
|
+
/* @__PURE__ */ React4.createElement(IconButton2, { size: "small", onClick: () => copyToClipboard(newKeySnippets.publicEndpoint) }, /* @__PURE__ */ React4.createElement(ContentCopy2, { fontSize: "small" }))
|
|
1706
|
+
)), newKeySnippets && /* @__PURE__ */ React4.createElement(Box4, { mt: 3 }, /* @__PURE__ */ React4.createElement(Box4, { display: "flex", alignItems: "center", gap: 1, mb: 1 }, /* @__PURE__ */ React4.createElement(Code, { fontSize: "small", color: "action" }), /* @__PURE__ */ React4.createElement(Typography4, { variant: "subtitle2" }, "Start calling the proxy \u2014 paste and run")), /* @__PURE__ */ React4.createElement(SnippetTabs, { snippets: newKeySnippets, model: newKeyModel, onCopy: copyToClipboard }))) : /* @__PURE__ */ React4.createElement(Box4, { display: "flex", flexDirection: "column", gap: 2, mt: 1 }, generateError && /* @__PURE__ */ React4.createElement(Alert, { severity: "error", onClose: () => setGenerateError(null) }, generateError), /* @__PURE__ */ React4.createElement(
|
|
1623
1707
|
TextField2,
|
|
1624
1708
|
{
|
|
1625
1709
|
label: "Alias",
|
|
@@ -2339,24 +2423,169 @@ var init_TeamUsage = __esm({
|
|
|
2339
2423
|
}
|
|
2340
2424
|
});
|
|
2341
2425
|
|
|
2342
|
-
// src/components/
|
|
2343
|
-
import React7, { useState as useState5, useCallback } from "react";
|
|
2426
|
+
// src/components/ModelsTable.tsx
|
|
2427
|
+
import React7, { useState as useState5, useMemo as useMemo5, useCallback } from "react";
|
|
2344
2428
|
import Box7 from "@mui/material/Box";
|
|
2345
|
-
import Typography7 from "@mui/material/Typography";
|
|
2346
2429
|
import Table4 from "@mui/material/Table";
|
|
2347
2430
|
import TableBody4 from "@mui/material/TableBody";
|
|
2348
2431
|
import TableCell4 from "@mui/material/TableCell";
|
|
2349
2432
|
import TableContainer4 from "@mui/material/TableContainer";
|
|
2350
2433
|
import TableHead4 from "@mui/material/TableHead";
|
|
2351
2434
|
import TableRow4 from "@mui/material/TableRow";
|
|
2435
|
+
import Typography7 from "@mui/material/Typography";
|
|
2436
|
+
import MenuItem3 from "@mui/material/MenuItem";
|
|
2437
|
+
import Select from "@mui/material/Select";
|
|
2438
|
+
import FormControl from "@mui/material/FormControl";
|
|
2439
|
+
import InputLabel from "@mui/material/InputLabel";
|
|
2440
|
+
import ContentCopyIcon from "@mui/icons-material/ContentCopy";
|
|
2441
|
+
import Tooltip3 from "@mui/material/Tooltip";
|
|
2442
|
+
import Snackbar from "@mui/material/Snackbar";
|
|
2443
|
+
import Alert2 from "@mui/material/Alert";
|
|
2444
|
+
import { alpha as alpha5, useTheme as useTheme2 } from "@mui/material/styles";
|
|
2445
|
+
function fmtCostPerToken(cost) {
|
|
2446
|
+
if (cost === void 0 || cost === null) return "\u2014";
|
|
2447
|
+
if (cost === 0) return "$0";
|
|
2448
|
+
if (cost < 1e-6) return `$${(cost * 1e6).toFixed(2)}/M`;
|
|
2449
|
+
if (cost < 1e-3) return `$${(cost * 1e3).toFixed(3)}/K`;
|
|
2450
|
+
return `$${cost.toFixed(4)}`;
|
|
2451
|
+
}
|
|
2452
|
+
function fmtTokens(n) {
|
|
2453
|
+
if (n === void 0 || n === null) return "\u2014";
|
|
2454
|
+
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
2455
|
+
if (n >= 1e3) return `${(n / 1e3).toFixed(0)}K`;
|
|
2456
|
+
return String(n);
|
|
2457
|
+
}
|
|
2458
|
+
var ModelsTable;
|
|
2459
|
+
var init_ModelsTable = __esm({
|
|
2460
|
+
"src/components/ModelsTable.tsx"() {
|
|
2461
|
+
"use strict";
|
|
2462
|
+
init_ui();
|
|
2463
|
+
ModelsTable = ({ allModels, teams, loading }) => {
|
|
2464
|
+
const theme = useTheme2();
|
|
2465
|
+
const [selectedTeamId, setSelectedTeamId] = useState5("");
|
|
2466
|
+
const [copiedSnackbar, setCopiedSnackbar] = useState5(false);
|
|
2467
|
+
const selectedTeam = useMemo5(
|
|
2468
|
+
() => teams.find((t) => t.team_id === selectedTeamId) ?? null,
|
|
2469
|
+
[teams, selectedTeamId]
|
|
2470
|
+
);
|
|
2471
|
+
const filteredModels = useMemo5(() => {
|
|
2472
|
+
if (!selectedTeam) return [];
|
|
2473
|
+
const teamModels = selectedTeam.models ?? [];
|
|
2474
|
+
if (teamModels.length === 0) return allModels;
|
|
2475
|
+
const allowed = new Set(teamModels);
|
|
2476
|
+
return allModels.filter((m) => allowed.has(m.model_name));
|
|
2477
|
+
}, [allModels, selectedTeam]);
|
|
2478
|
+
const handleCopyModelId = useCallback((modelId) => {
|
|
2479
|
+
navigator.clipboard.writeText(modelId).then(() => setCopiedSnackbar(true));
|
|
2480
|
+
}, []);
|
|
2481
|
+
const modeColor = (mode) => {
|
|
2482
|
+
switch (mode) {
|
|
2483
|
+
case "chat":
|
|
2484
|
+
return theme.palette.info.main;
|
|
2485
|
+
case "embedding":
|
|
2486
|
+
return theme.palette.success.main;
|
|
2487
|
+
case "completion":
|
|
2488
|
+
return theme.palette.primary.main;
|
|
2489
|
+
case "image":
|
|
2490
|
+
return theme.palette.warning.main;
|
|
2491
|
+
default:
|
|
2492
|
+
return theme.palette.info.main;
|
|
2493
|
+
}
|
|
2494
|
+
};
|
|
2495
|
+
return /* @__PURE__ */ React7.createElement(
|
|
2496
|
+
SectionCard,
|
|
2497
|
+
{
|
|
2498
|
+
title: "Available Models",
|
|
2499
|
+
subtitle: selectedTeam ? `${filteredModels.length} model${filteredModels.length !== 1 ? "s" : ""} in ${selectedTeam.team_alias ?? selectedTeam.team_id}` : "Select a team to view its models"
|
|
2500
|
+
},
|
|
2501
|
+
/* @__PURE__ */ React7.createElement(Box7, { sx: { px: 2.5, pb: 2.5, pt: 1 } }, /* @__PURE__ */ React7.createElement(FormControl, { size: "small", sx: { minWidth: 240, mb: 2 } }, /* @__PURE__ */ React7.createElement(InputLabel, { id: "team-select-label" }, "Team"), /* @__PURE__ */ React7.createElement(
|
|
2502
|
+
Select,
|
|
2503
|
+
{
|
|
2504
|
+
labelId: "team-select-label",
|
|
2505
|
+
value: selectedTeamId,
|
|
2506
|
+
label: "Team",
|
|
2507
|
+
onChange: (e) => setSelectedTeamId(e.target.value)
|
|
2508
|
+
},
|
|
2509
|
+
teams.map((t) => /* @__PURE__ */ React7.createElement(MenuItem3, { key: t.team_id, value: t.team_id }, t.team_alias ?? t.team_id))
|
|
2510
|
+
))),
|
|
2511
|
+
loading && /* @__PURE__ */ React7.createElement(EmptyState, { message: "Loading models\u2026" }),
|
|
2512
|
+
!loading && !selectedTeamId && /* @__PURE__ */ React7.createElement(
|
|
2513
|
+
EmptyState,
|
|
2514
|
+
{
|
|
2515
|
+
message: "Select a team to see available models",
|
|
2516
|
+
hint: "Each team may have a different set of models assigned"
|
|
2517
|
+
}
|
|
2518
|
+
),
|
|
2519
|
+
!loading && selectedTeamId && filteredModels.length === 0 && /* @__PURE__ */ React7.createElement(
|
|
2520
|
+
EmptyState,
|
|
2521
|
+
{
|
|
2522
|
+
message: "No models available for this team",
|
|
2523
|
+
hint: allModels.length === 0 ? "No models are configured in the system" : "None of the team's assigned models were found in the catalog"
|
|
2524
|
+
}
|
|
2525
|
+
),
|
|
2526
|
+
!loading && filteredModels.length > 0 && /* @__PURE__ */ React7.createElement(TableContainer4, null, /* @__PURE__ */ React7.createElement(Table4, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React7.createElement(TableHead4, null, /* @__PURE__ */ React7.createElement(TableRow4, null, /* @__PURE__ */ React7.createElement(TableCell4, null, "Model ID"), /* @__PURE__ */ React7.createElement(TableCell4, null, "Mode"), /* @__PURE__ */ React7.createElement(TableCell4, { align: "right" }, "Input Cost"), /* @__PURE__ */ React7.createElement(TableCell4, { align: "right" }, "Output Cost"), /* @__PURE__ */ React7.createElement(TableCell4, { align: "right" }, "Max Input"), /* @__PURE__ */ React7.createElement(TableCell4, { align: "right" }, "Max Output"), /* @__PURE__ */ React7.createElement(TableCell4, null, "Capabilities"))), /* @__PURE__ */ React7.createElement(TableBody4, null, filteredModels.map((m) => /* @__PURE__ */ React7.createElement(TableRow4, { key: m.model_name, hover: true }, /* @__PURE__ */ React7.createElement(TableCell4, null, /* @__PURE__ */ React7.createElement(Box7, { sx: { display: "flex", alignItems: "center", gap: 0.5 } }, /* @__PURE__ */ React7.createElement(TagChip, { label: m.model_name, title: m.model_name }), /* @__PURE__ */ React7.createElement(Tooltip3, { title: "Copy model ID" }, /* @__PURE__ */ React7.createElement(
|
|
2527
|
+
Box7,
|
|
2528
|
+
{
|
|
2529
|
+
component: "span",
|
|
2530
|
+
role: "button",
|
|
2531
|
+
tabIndex: 0,
|
|
2532
|
+
onClick: () => handleCopyModelId(m.model_name),
|
|
2533
|
+
onKeyDown: (e) => e.key === "Enter" && handleCopyModelId(m.model_name),
|
|
2534
|
+
sx: quietIconButtonSx("accent"),
|
|
2535
|
+
style: { cursor: "pointer", display: "inline-flex" }
|
|
2536
|
+
},
|
|
2537
|
+
/* @__PURE__ */ React7.createElement(ContentCopyIcon, { sx: { fontSize: 14 } })
|
|
2538
|
+
)))), /* @__PURE__ */ React7.createElement(TableCell4, null, /* @__PURE__ */ React7.createElement(
|
|
2539
|
+
Box7,
|
|
2540
|
+
{
|
|
2541
|
+
component: "span",
|
|
2542
|
+
sx: {
|
|
2543
|
+
display: "inline-block",
|
|
2544
|
+
px: 0.75,
|
|
2545
|
+
py: 0.25,
|
|
2546
|
+
borderRadius: 1,
|
|
2547
|
+
fontSize: 11,
|
|
2548
|
+
fontWeight: 600,
|
|
2549
|
+
textTransform: "capitalize",
|
|
2550
|
+
bgcolor: alpha5(modeColor(m.mode), 0.1),
|
|
2551
|
+
color: modeColor(m.mode)
|
|
2552
|
+
}
|
|
2553
|
+
},
|
|
2554
|
+
m.mode
|
|
2555
|
+
)), /* @__PURE__ */ React7.createElement(TableCell4, { align: "right" }, /* @__PURE__ */ React7.createElement(Typography7, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtCostPerToken(m.input_cost_per_token))), /* @__PURE__ */ React7.createElement(TableCell4, { align: "right" }, /* @__PURE__ */ React7.createElement(Typography7, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtCostPerToken(m.output_cost_per_token))), /* @__PURE__ */ React7.createElement(TableCell4, { align: "right" }, /* @__PURE__ */ React7.createElement(Typography7, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtTokens(m.max_input_tokens))), /* @__PURE__ */ React7.createElement(TableCell4, { align: "right" }, /* @__PURE__ */ React7.createElement(Typography7, { variant: "body2", sx: { fontVariantNumeric: "tabular-nums" } }, fmtTokens(m.max_output_tokens))), /* @__PURE__ */ React7.createElement(TableCell4, null, /* @__PURE__ */ React7.createElement(Box7, { sx: { display: "flex", gap: 0.5, flexWrap: "wrap" } }, m.supports_function_calling && /* @__PURE__ */ React7.createElement(TagChip, { label: "Fn", title: "Function calling" }), m.supports_vision && /* @__PURE__ */ React7.createElement(TagChip, { label: "\u{1F441}", title: "Vision" })))))))),
|
|
2556
|
+
/* @__PURE__ */ React7.createElement(
|
|
2557
|
+
Snackbar,
|
|
2558
|
+
{
|
|
2559
|
+
open: copiedSnackbar,
|
|
2560
|
+
autoHideDuration: 2e3,
|
|
2561
|
+
onClose: () => setCopiedSnackbar(false),
|
|
2562
|
+
anchorOrigin: { vertical: "bottom", horizontal: "center" }
|
|
2563
|
+
},
|
|
2564
|
+
/* @__PURE__ */ React7.createElement(Alert2, { severity: "success", variant: "filled", sx: { fontSize: 13 } }, "Model ID copied to clipboard")
|
|
2565
|
+
)
|
|
2566
|
+
);
|
|
2567
|
+
};
|
|
2568
|
+
}
|
|
2569
|
+
});
|
|
2570
|
+
|
|
2571
|
+
// src/components/AuditLog.tsx
|
|
2572
|
+
import React8, { useState as useState6, useCallback as useCallback2 } from "react";
|
|
2573
|
+
import Box8 from "@mui/material/Box";
|
|
2574
|
+
import Typography8 from "@mui/material/Typography";
|
|
2575
|
+
import Table5 from "@mui/material/Table";
|
|
2576
|
+
import TableBody5 from "@mui/material/TableBody";
|
|
2577
|
+
import TableCell5 from "@mui/material/TableCell";
|
|
2578
|
+
import TableContainer5 from "@mui/material/TableContainer";
|
|
2579
|
+
import TableHead5 from "@mui/material/TableHead";
|
|
2580
|
+
import TableRow5 from "@mui/material/TableRow";
|
|
2352
2581
|
import TablePagination from "@mui/material/TablePagination";
|
|
2353
2582
|
import TextField4 from "@mui/material/TextField";
|
|
2354
|
-
import
|
|
2583
|
+
import MenuItem4 from "@mui/material/MenuItem";
|
|
2355
2584
|
import Skeleton5 from "@mui/material/Skeleton";
|
|
2356
2585
|
import Collapse2 from "@mui/material/Collapse";
|
|
2357
2586
|
import IconButton4 from "@mui/material/IconButton";
|
|
2358
|
-
import
|
|
2359
|
-
import { alpha as
|
|
2587
|
+
import Alert3 from "@mui/material/Alert";
|
|
2588
|
+
import { alpha as alpha6 } from "@mui/material/styles";
|
|
2360
2589
|
import { KeyboardArrowDown } from "@mui/icons-material";
|
|
2361
2590
|
import { useAsync } from "react-use";
|
|
2362
2591
|
function actionTone(action) {
|
|
@@ -2379,10 +2608,10 @@ function formatDateTime(iso) {
|
|
|
2379
2608
|
}
|
|
2380
2609
|
function renderAuditLogBody(loading, entries) {
|
|
2381
2610
|
if (loading) {
|
|
2382
|
-
return /* @__PURE__ */
|
|
2611
|
+
return /* @__PURE__ */ React8.createElement(TableRow5, null, /* @__PURE__ */ React8.createElement(TableCell5, { colSpan: 6, sx: { py: 2 } }, /* @__PURE__ */ React8.createElement(Skeleton5, { variant: "rounded", height: 160 })));
|
|
2383
2612
|
}
|
|
2384
2613
|
if (entries.length === 0) {
|
|
2385
|
-
return /* @__PURE__ */
|
|
2614
|
+
return /* @__PURE__ */ React8.createElement(TableRow5, null, /* @__PURE__ */ React8.createElement(TableCell5, { colSpan: 6 }, /* @__PURE__ */ React8.createElement(
|
|
2386
2615
|
EmptyState,
|
|
2387
2616
|
{
|
|
2388
2617
|
message: "No audit events found",
|
|
@@ -2390,7 +2619,7 @@ function renderAuditLogBody(loading, entries) {
|
|
|
2390
2619
|
}
|
|
2391
2620
|
)));
|
|
2392
2621
|
}
|
|
2393
|
-
return entries.map((entry) => /* @__PURE__ */
|
|
2622
|
+
return entries.map((entry) => /* @__PURE__ */ React8.createElement(DetailRow, { key: entry.id, entry }));
|
|
2394
2623
|
}
|
|
2395
2624
|
var ACTION_TONES, TABLE_LABELS, DetailRow, AuditLog;
|
|
2396
2625
|
var init_AuditLog = __esm({
|
|
@@ -2410,9 +2639,9 @@ var init_AuditLog = __esm({
|
|
|
2410
2639
|
LiteLLM_UserTable: "User"
|
|
2411
2640
|
};
|
|
2412
2641
|
DetailRow = ({ entry }) => {
|
|
2413
|
-
const [open, setOpen] =
|
|
2642
|
+
const [open, setOpen] = useState6(false);
|
|
2414
2643
|
const hasDetail = entry.before_value || entry.updated_values;
|
|
2415
|
-
return /* @__PURE__ */
|
|
2644
|
+
return /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement(TableRow5, null, /* @__PURE__ */ React8.createElement(TableCell5, { sx: { width: 40, pr: 0 } }, hasDetail && /* @__PURE__ */ React8.createElement(
|
|
2416
2645
|
IconButton4,
|
|
2417
2646
|
{
|
|
2418
2647
|
size: "small",
|
|
@@ -2425,9 +2654,9 @@ var init_AuditLog = __esm({
|
|
|
2425
2654
|
transition: theme.transitions.create("transform")
|
|
2426
2655
|
})
|
|
2427
2656
|
},
|
|
2428
|
-
/* @__PURE__ */
|
|
2429
|
-
)), /* @__PURE__ */
|
|
2430
|
-
|
|
2657
|
+
/* @__PURE__ */ React8.createElement(KeyboardArrowDown, { fontSize: "small" })
|
|
2658
|
+
)), /* @__PURE__ */ React8.createElement(TableCell5, { sx: { whiteSpace: "nowrap" } }, /* @__PURE__ */ React8.createElement(Typography8, { variant: "body2", color: "text.secondary" }, formatDateTime(entry.updated_at))), /* @__PURE__ */ React8.createElement(TableCell5, null, entry.action && /* @__PURE__ */ React8.createElement(StatusPill, { label: entry.action, tone: actionTone(entry.action) })), /* @__PURE__ */ React8.createElement(TableCell5, null, /* @__PURE__ */ React8.createElement(Typography8, { variant: "body2" }, prettyTableName(entry.table_name))), /* @__PURE__ */ React8.createElement(TableCell5, null, /* @__PURE__ */ React8.createElement(
|
|
2659
|
+
Typography8,
|
|
2431
2660
|
{
|
|
2432
2661
|
variant: "body2",
|
|
2433
2662
|
component: "code",
|
|
@@ -2436,8 +2665,8 @@ var init_AuditLog = __esm({
|
|
|
2436
2665
|
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 12 }
|
|
2437
2666
|
},
|
|
2438
2667
|
entry.object_id ? entry.object_id.slice(0, 20) + (entry.object_id.length > 20 ? "\u2026" : "") : "\u2014"
|
|
2439
|
-
)), /* @__PURE__ */
|
|
2440
|
-
|
|
2668
|
+
)), /* @__PURE__ */ React8.createElement(TableCell5, null, entry.changed_by ?? "\u2014")), hasDetail && /* @__PURE__ */ React8.createElement(TableRow5, { sx: { "&&:hover": { bgcolor: "transparent" } } }, /* @__PURE__ */ React8.createElement(TableCell5, { colSpan: 6, sx: { "&&": { py: 0, border: 0 } } }, /* @__PURE__ */ React8.createElement(Collapse2, { in: open, unmountOnExit: true }, /* @__PURE__ */ React8.createElement(
|
|
2669
|
+
Box8,
|
|
2441
2670
|
{
|
|
2442
2671
|
display: "flex",
|
|
2443
2672
|
gap: 2,
|
|
@@ -2446,11 +2675,11 @@ var init_AuditLog = __esm({
|
|
|
2446
2675
|
p: 2,
|
|
2447
2676
|
mb: 1.5,
|
|
2448
2677
|
borderRadius: 1.5,
|
|
2449
|
-
bgcolor:
|
|
2678
|
+
bgcolor: alpha6(theme.palette.text.primary, theme.palette.mode === "dark" ? 0.05 : 0.03)
|
|
2450
2679
|
})
|
|
2451
2680
|
},
|
|
2452
|
-
entry.before_value && /* @__PURE__ */
|
|
2453
|
-
|
|
2681
|
+
entry.before_value && /* @__PURE__ */ React8.createElement(Box8, { flex: 1, minWidth: 200 }, /* @__PURE__ */ React8.createElement(
|
|
2682
|
+
Typography8,
|
|
2454
2683
|
{
|
|
2455
2684
|
variant: "caption",
|
|
2456
2685
|
color: "text.secondary",
|
|
@@ -2459,9 +2688,9 @@ var init_AuditLog = __esm({
|
|
|
2459
2688
|
sx: { fontSize: 10.5, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase" }
|
|
2460
2689
|
},
|
|
2461
2690
|
"Before"
|
|
2462
|
-
), /* @__PURE__ */
|
|
2463
|
-
entry.updated_values && /* @__PURE__ */
|
|
2464
|
-
|
|
2691
|
+
), /* @__PURE__ */ React8.createElement(Typography8, { component: "pre", variant: "caption", sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", whiteSpace: "pre-wrap", wordBreak: "break-all", m: 0 } }, JSON.stringify(entry.before_value, null, 2))),
|
|
2692
|
+
entry.updated_values && /* @__PURE__ */ React8.createElement(Box8, { flex: 1, minWidth: 200 }, /* @__PURE__ */ React8.createElement(
|
|
2693
|
+
Typography8,
|
|
2465
2694
|
{
|
|
2466
2695
|
variant: "caption",
|
|
2467
2696
|
color: "text.secondary",
|
|
@@ -2470,14 +2699,14 @@ var init_AuditLog = __esm({
|
|
|
2470
2699
|
sx: { fontSize: 10.5, fontWeight: 700, letterSpacing: "0.07em", textTransform: "uppercase" }
|
|
2471
2700
|
},
|
|
2472
2701
|
"After"
|
|
2473
|
-
), /* @__PURE__ */
|
|
2702
|
+
), /* @__PURE__ */ React8.createElement(Typography8, { component: "pre", variant: "caption", sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", whiteSpace: "pre-wrap", wordBreak: "break-all", m: 0 } }, JSON.stringify(entry.updated_values, null, 2)))
|
|
2474
2703
|
)))));
|
|
2475
2704
|
};
|
|
2476
2705
|
AuditLog = ({ api }) => {
|
|
2477
|
-
const [page, setPage] =
|
|
2478
|
-
const [pageSize, setPageSize] =
|
|
2479
|
-
const [filters, setFilters] =
|
|
2480
|
-
const fetchParams =
|
|
2706
|
+
const [page, setPage] = useState6(0);
|
|
2707
|
+
const [pageSize, setPageSize] = useState6(25);
|
|
2708
|
+
const [filters, setFilters] = useState6({});
|
|
2709
|
+
const fetchParams = useCallback2(
|
|
2481
2710
|
() => ({ page: page + 1, page_size: pageSize, ...filters }),
|
|
2482
2711
|
[page, pageSize, filters]
|
|
2483
2712
|
)();
|
|
@@ -2487,13 +2716,13 @@ var init_AuditLog = __esm({
|
|
|
2487
2716
|
);
|
|
2488
2717
|
const entries = value?.audit_logs ?? [];
|
|
2489
2718
|
const total = value?.total ?? 0;
|
|
2490
|
-
return /* @__PURE__ */
|
|
2719
|
+
return /* @__PURE__ */ React8.createElement(
|
|
2491
2720
|
SectionCard,
|
|
2492
2721
|
{
|
|
2493
2722
|
title: "Audit Log",
|
|
2494
2723
|
subtitle: loading ? "Loading\u2026" : `${total.toLocaleString()} event${total === 1 ? "" : "s"}`,
|
|
2495
2724
|
flush: true,
|
|
2496
|
-
actions: /* @__PURE__ */
|
|
2725
|
+
actions: /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement(
|
|
2497
2726
|
TextField4,
|
|
2498
2727
|
{
|
|
2499
2728
|
size: "small",
|
|
@@ -2506,12 +2735,12 @@ var init_AuditLog = __esm({
|
|
|
2506
2735
|
},
|
|
2507
2736
|
sx: { minWidth: 150 }
|
|
2508
2737
|
},
|
|
2509
|
-
/* @__PURE__ */
|
|
2510
|
-
/* @__PURE__ */
|
|
2511
|
-
/* @__PURE__ */
|
|
2512
|
-
/* @__PURE__ */
|
|
2513
|
-
/* @__PURE__ */
|
|
2514
|
-
), /* @__PURE__ */
|
|
2738
|
+
/* @__PURE__ */ React8.createElement(MenuItem4, { value: "" }, "All actions"),
|
|
2739
|
+
/* @__PURE__ */ React8.createElement(MenuItem4, { value: "created" }, "Created"),
|
|
2740
|
+
/* @__PURE__ */ React8.createElement(MenuItem4, { value: "updated" }, "Updated"),
|
|
2741
|
+
/* @__PURE__ */ React8.createElement(MenuItem4, { value: "deleted" }, "Deleted"),
|
|
2742
|
+
/* @__PURE__ */ React8.createElement(MenuItem4, { value: "blocked" }, "Blocked")
|
|
2743
|
+
), /* @__PURE__ */ React8.createElement(
|
|
2515
2744
|
TextField4,
|
|
2516
2745
|
{
|
|
2517
2746
|
size: "small",
|
|
@@ -2524,11 +2753,11 @@ var init_AuditLog = __esm({
|
|
|
2524
2753
|
},
|
|
2525
2754
|
sx: { minWidth: 150 }
|
|
2526
2755
|
},
|
|
2527
|
-
/* @__PURE__ */
|
|
2528
|
-
/* @__PURE__ */
|
|
2529
|
-
/* @__PURE__ */
|
|
2530
|
-
/* @__PURE__ */
|
|
2531
|
-
), /* @__PURE__ */
|
|
2756
|
+
/* @__PURE__ */ React8.createElement(MenuItem4, { value: "" }, "All tables"),
|
|
2757
|
+
/* @__PURE__ */ React8.createElement(MenuItem4, { value: "LiteLLM_VerificationToken" }, "Key"),
|
|
2758
|
+
/* @__PURE__ */ React8.createElement(MenuItem4, { value: "LiteLLM_TeamTable" }, "Team"),
|
|
2759
|
+
/* @__PURE__ */ React8.createElement(MenuItem4, { value: "LiteLLM_UserTable" }, "User")
|
|
2760
|
+
), /* @__PURE__ */ React8.createElement(
|
|
2532
2761
|
TextField4,
|
|
2533
2762
|
{
|
|
2534
2763
|
size: "small",
|
|
@@ -2542,9 +2771,9 @@ var init_AuditLog = __esm({
|
|
|
2542
2771
|
}
|
|
2543
2772
|
))
|
|
2544
2773
|
},
|
|
2545
|
-
error && /* @__PURE__ */
|
|
2546
|
-
/* @__PURE__ */
|
|
2547
|
-
/* @__PURE__ */
|
|
2774
|
+
error && /* @__PURE__ */ React8.createElement(Box8, { px: 2.5, pb: 2 }, /* @__PURE__ */ React8.createElement(Alert3, { severity: "error" }, error.message)),
|
|
2775
|
+
/* @__PURE__ */ React8.createElement(TableContainer5, null, /* @__PURE__ */ React8.createElement(Table5, { size: "small", sx: dataTableSx }, /* @__PURE__ */ React8.createElement(TableHead5, null, /* @__PURE__ */ React8.createElement(TableRow5, null, /* @__PURE__ */ React8.createElement(TableCell5, { sx: { width: 40 } }), /* @__PURE__ */ React8.createElement(TableCell5, null, "Time"), /* @__PURE__ */ React8.createElement(TableCell5, null, "Action"), /* @__PURE__ */ React8.createElement(TableCell5, null, "Table"), /* @__PURE__ */ React8.createElement(TableCell5, null, "Object ID"), /* @__PURE__ */ React8.createElement(TableCell5, null, "Changed By"))), /* @__PURE__ */ React8.createElement(TableBody5, null, renderAuditLogBody(loading, entries)))),
|
|
2776
|
+
/* @__PURE__ */ React8.createElement(
|
|
2548
2777
|
TablePagination,
|
|
2549
2778
|
{
|
|
2550
2779
|
component: "div",
|
|
@@ -2570,12 +2799,12 @@ var LiteLLMPage_exports = {};
|
|
|
2570
2799
|
__export(LiteLLMPage_exports, {
|
|
2571
2800
|
LiteLLMPage: () => LiteLLMPage
|
|
2572
2801
|
});
|
|
2573
|
-
import
|
|
2574
|
-
import
|
|
2575
|
-
import
|
|
2576
|
-
import
|
|
2802
|
+
import React9, { useState as useState7, useCallback as useCallback3, useMemo as useMemo6 } from "react";
|
|
2803
|
+
import Box9 from "@mui/material/Box";
|
|
2804
|
+
import Snackbar2 from "@mui/material/Snackbar";
|
|
2805
|
+
import Alert4 from "@mui/material/Alert";
|
|
2577
2806
|
import CircularProgress4 from "@mui/material/CircularProgress";
|
|
2578
|
-
import
|
|
2807
|
+
import Typography9 from "@mui/material/Typography";
|
|
2579
2808
|
import Paper5 from "@mui/material/Paper";
|
|
2580
2809
|
import Tabs2 from "@mui/material/Tabs";
|
|
2581
2810
|
import Tab2 from "@mui/material/Tab";
|
|
@@ -2604,17 +2833,18 @@ var init_LiteLLMPage = __esm({
|
|
|
2604
2833
|
init_GenerateKeyDialog();
|
|
2605
2834
|
init_UsageStats();
|
|
2606
2835
|
init_TeamUsage();
|
|
2836
|
+
init_ModelsTable();
|
|
2607
2837
|
init_AuditLog();
|
|
2608
2838
|
init_api();
|
|
2609
2839
|
PERIOD_LS_KEY2 = "litellm_usage_period";
|
|
2610
2840
|
LiteLLMPage = () => {
|
|
2611
2841
|
const api = useApi(liteLlmApiRef);
|
|
2612
|
-
const [dateRange, setDateRange] =
|
|
2613
|
-
const [activeTab, setActiveTab] =
|
|
2614
|
-
const [snackbar, setSnackbar] =
|
|
2615
|
-
const [generateDialogOpen, setGenerateDialogOpen] =
|
|
2616
|
-
const [teamUsageCache, setTeamUsageCache] =
|
|
2617
|
-
const [teamUsageLoading, setTeamUsageLoading] =
|
|
2842
|
+
const [dateRange, setDateRange] = useState7(initDateRange);
|
|
2843
|
+
const [activeTab, setActiveTab] = useState7("overview");
|
|
2844
|
+
const [snackbar, setSnackbar] = useState7(null);
|
|
2845
|
+
const [generateDialogOpen, setGenerateDialogOpen] = useState7(false);
|
|
2846
|
+
const [teamUsageCache, setTeamUsageCache] = useState7({});
|
|
2847
|
+
const [teamUsageLoading, setTeamUsageLoading] = useState7({});
|
|
2618
2848
|
const { value: userInfo, loading: userLoading, error: userError } = useAsync2(
|
|
2619
2849
|
() => api.getUserInfo(),
|
|
2620
2850
|
[api]
|
|
@@ -2639,7 +2869,7 @@ var init_LiteLLMPage = __esm({
|
|
|
2639
2869
|
[api]
|
|
2640
2870
|
);
|
|
2641
2871
|
const { value: liteLlmConfig } = useAsync2(() => api.getConfig(), [api]);
|
|
2642
|
-
const teams =
|
|
2872
|
+
const teams = useMemo6(() => {
|
|
2643
2873
|
if (!allTeams?.length) return [];
|
|
2644
2874
|
if (!userInfo) return allTeams;
|
|
2645
2875
|
const userId = userInfo.user_id;
|
|
@@ -2656,12 +2886,12 @@ var init_LiteLLMPage = __esm({
|
|
|
2656
2886
|
const endDate = dateRange.end.toISOString().split("T")[0];
|
|
2657
2887
|
return api.getUsage(startDate, endDate);
|
|
2658
2888
|
}, [api, dateRange]);
|
|
2659
|
-
const handleDateRangeChange =
|
|
2889
|
+
const handleDateRangeChange = useCallback3((range) => {
|
|
2660
2890
|
setDateRange(range);
|
|
2661
2891
|
setTeamUsageCache({});
|
|
2662
2892
|
setTeamUsageLoading({});
|
|
2663
2893
|
}, [setDateRange]);
|
|
2664
|
-
const loadTeamUsage =
|
|
2894
|
+
const loadTeamUsage = useCallback3(async (teamId) => {
|
|
2665
2895
|
if (teamUsageCache[teamId] !== void 0 || teamUsageLoading[teamId]) return;
|
|
2666
2896
|
setTeamUsageLoading((prev) => ({ ...prev, [teamId]: true }));
|
|
2667
2897
|
try {
|
|
@@ -2675,7 +2905,7 @@ var init_LiteLLMPage = __esm({
|
|
|
2675
2905
|
setTeamUsageLoading((prev) => ({ ...prev, [teamId]: false }));
|
|
2676
2906
|
}
|
|
2677
2907
|
}, [api, dateRange, teamUsageCache, teamUsageLoading]);
|
|
2678
|
-
const allowedModels =
|
|
2908
|
+
const allowedModels = useMemo6(() => {
|
|
2679
2909
|
if (!allModels?.length) return [];
|
|
2680
2910
|
const userModels = userInfo?.models;
|
|
2681
2911
|
const teamModels = teams?.flatMap((t) => t.models ?? []);
|
|
@@ -2688,7 +2918,7 @@ var init_LiteLLMPage = __esm({
|
|
|
2688
2918
|
]);
|
|
2689
2919
|
return allModels.filter((m) => allowed.has(m.model_name));
|
|
2690
2920
|
}, [allModels, userInfo, teams]);
|
|
2691
|
-
const handleGenerateKey =
|
|
2921
|
+
const handleGenerateKey = useCallback3(
|
|
2692
2922
|
async (request) => {
|
|
2693
2923
|
try {
|
|
2694
2924
|
const response = await api.generateKey(request);
|
|
@@ -2702,7 +2932,7 @@ var init_LiteLLMPage = __esm({
|
|
|
2702
2932
|
},
|
|
2703
2933
|
[api, refreshKeys]
|
|
2704
2934
|
);
|
|
2705
|
-
const handleUpdateKey =
|
|
2935
|
+
const handleUpdateKey = useCallback3(
|
|
2706
2936
|
async (keyId, request) => {
|
|
2707
2937
|
try {
|
|
2708
2938
|
await api.updateKey(keyId, request);
|
|
@@ -2715,7 +2945,7 @@ var init_LiteLLMPage = __esm({
|
|
|
2715
2945
|
},
|
|
2716
2946
|
[api, refreshKeys]
|
|
2717
2947
|
);
|
|
2718
|
-
const handleBlockKey =
|
|
2948
|
+
const handleBlockKey = useCallback3(
|
|
2719
2949
|
async (keyId) => {
|
|
2720
2950
|
try {
|
|
2721
2951
|
await api.blockKey(keyId);
|
|
@@ -2727,7 +2957,7 @@ var init_LiteLLMPage = __esm({
|
|
|
2727
2957
|
},
|
|
2728
2958
|
[api, refreshKeys]
|
|
2729
2959
|
);
|
|
2730
|
-
const handleUnblockKey =
|
|
2960
|
+
const handleUnblockKey = useCallback3(
|
|
2731
2961
|
async (keyId) => {
|
|
2732
2962
|
try {
|
|
2733
2963
|
await api.unblockKey(keyId);
|
|
@@ -2739,7 +2969,7 @@ var init_LiteLLMPage = __esm({
|
|
|
2739
2969
|
},
|
|
2740
2970
|
[api, refreshKeys]
|
|
2741
2971
|
);
|
|
2742
|
-
const handleResetKeySpend =
|
|
2972
|
+
const handleResetKeySpend = useCallback3(
|
|
2743
2973
|
async (keyId) => {
|
|
2744
2974
|
try {
|
|
2745
2975
|
await api.resetKeySpend(keyId);
|
|
@@ -2751,7 +2981,7 @@ var init_LiteLLMPage = __esm({
|
|
|
2751
2981
|
},
|
|
2752
2982
|
[api, refreshKeys]
|
|
2753
2983
|
);
|
|
2754
|
-
const handleDeleteKey =
|
|
2984
|
+
const handleDeleteKey = useCallback3(
|
|
2755
2985
|
async (keyId) => {
|
|
2756
2986
|
try {
|
|
2757
2987
|
await api.deleteKey(keyId);
|
|
@@ -2768,7 +2998,7 @@ var init_LiteLLMPage = __esm({
|
|
|
2768
2998
|
},
|
|
2769
2999
|
[api, refreshKeys]
|
|
2770
3000
|
);
|
|
2771
|
-
const handlePruneExpiredKeys =
|
|
3001
|
+
const handlePruneExpiredKeys = useCallback3(
|
|
2772
3002
|
async () => {
|
|
2773
3003
|
try {
|
|
2774
3004
|
const result = await api.pruneExpiredKeys();
|
|
@@ -2783,14 +3013,14 @@ var init_LiteLLMPage = __esm({
|
|
|
2783
3013
|
);
|
|
2784
3014
|
const isInitialLoading = userLoading && !userInfo;
|
|
2785
3015
|
if (isInitialLoading) {
|
|
2786
|
-
return /* @__PURE__ */
|
|
3016
|
+
return /* @__PURE__ */ React9.createElement(Box9, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: "50vh" }, /* @__PURE__ */ React9.createElement(CircularProgress4, null));
|
|
2787
3017
|
}
|
|
2788
3018
|
if (userError || !userInfo) {
|
|
2789
3019
|
const isProvisioningEnabled = userError?.body?.provisioning === true;
|
|
2790
3020
|
const hint = userError?.body?.hint;
|
|
2791
|
-
return /* @__PURE__ */
|
|
3021
|
+
return /* @__PURE__ */ React9.createElement(Box9, { p: 3 }, /* @__PURE__ */ React9.createElement(Paper5, { sx: { p: 3 } }, /* @__PURE__ */ React9.createElement(Typography9, { variant: "h6", gutterBottom: true }, "Account not provisioned"), /* @__PURE__ */ React9.createElement(Typography9, { color: "text.secondary", paragraph: true }, "Your Backstage account is not linked to a LiteLLM user."), hint ? /* @__PURE__ */ React9.createElement(Typography9, { variant: "body2", color: "text.secondary" }, hint) : /* @__PURE__ */ React9.createElement(Typography9, { variant: "body2", color: "text.secondary" }, isProvisioningEnabled ? "Auto-provisioning is enabled but failed. Check the backend logs." : "Set litellm.provisioning.enabled: true in app-config.yaml to enable auto-provisioning, or ask your administrator to create the account manually.")));
|
|
2792
3022
|
}
|
|
2793
|
-
const pageTabs = /* @__PURE__ */
|
|
3023
|
+
const pageTabs = /* @__PURE__ */ React9.createElement(
|
|
2794
3024
|
Tabs2,
|
|
2795
3025
|
{
|
|
2796
3026
|
value: activeTab,
|
|
@@ -2803,12 +3033,13 @@ var init_LiteLLMPage = __esm({
|
|
|
2803
3033
|
'& [class*="MuiTab-root"]': { minHeight: 44, textTransform: "none", fontSize: 14 }
|
|
2804
3034
|
}
|
|
2805
3035
|
},
|
|
2806
|
-
/* @__PURE__ */
|
|
2807
|
-
/* @__PURE__ */
|
|
2808
|
-
/* @__PURE__ */
|
|
2809
|
-
|
|
3036
|
+
/* @__PURE__ */ React9.createElement(Tab2, { label: "Overview", value: "overview" }),
|
|
3037
|
+
/* @__PURE__ */ React9.createElement(Tab2, { label: "Keys", value: "keys" }),
|
|
3038
|
+
/* @__PURE__ */ React9.createElement(Tab2, { label: "Teams", value: "teams" }),
|
|
3039
|
+
/* @__PURE__ */ React9.createElement(Tab2, { label: "Models", value: "models" }),
|
|
3040
|
+
userInfo.can_view_audit && /* @__PURE__ */ React9.createElement(Tab2, { label: "Audit Log", value: "audit" })
|
|
2810
3041
|
);
|
|
2811
|
-
return /* @__PURE__ */
|
|
3042
|
+
return /* @__PURE__ */ React9.createElement(Box9, { sx: { p: 3, display: "flex", flexDirection: "column", gap: 2 } }, /* @__PURE__ */ React9.createElement(
|
|
2812
3043
|
DashboardHeader,
|
|
2813
3044
|
{
|
|
2814
3045
|
userInfo,
|
|
@@ -2818,7 +3049,7 @@ var init_LiteLLMPage = __esm({
|
|
|
2818
3049
|
onGenerateKeyClick: () => setGenerateDialogOpen(true),
|
|
2819
3050
|
tabs: pageTabs
|
|
2820
3051
|
}
|
|
2821
|
-
), activeTab === "overview" && /* @__PURE__ */
|
|
3052
|
+
), activeTab === "overview" && /* @__PURE__ */ React9.createElement(
|
|
2822
3053
|
UsageStats,
|
|
2823
3054
|
{
|
|
2824
3055
|
usage: usage ?? null,
|
|
@@ -2828,7 +3059,7 @@ var init_LiteLLMPage = __esm({
|
|
|
2828
3059
|
loading: usageLoading,
|
|
2829
3060
|
userInfo
|
|
2830
3061
|
}
|
|
2831
|
-
), activeTab === "keys" && /* @__PURE__ */
|
|
3062
|
+
), activeTab === "keys" && /* @__PURE__ */ React9.createElement(
|
|
2832
3063
|
KeysTable,
|
|
2833
3064
|
{
|
|
2834
3065
|
keys: keys ?? [],
|
|
@@ -2842,7 +3073,7 @@ var init_LiteLLMPage = __esm({
|
|
|
2842
3073
|
onDeleteKey: handleDeleteKey,
|
|
2843
3074
|
onPruneExpiredKeys: handlePruneExpiredKeys
|
|
2844
3075
|
}
|
|
2845
|
-
), activeTab === "teams" && /* @__PURE__ */
|
|
3076
|
+
), activeTab === "teams" && /* @__PURE__ */ React9.createElement(
|
|
2846
3077
|
TeamUsage,
|
|
2847
3078
|
{
|
|
2848
3079
|
teams: teams ?? [],
|
|
@@ -2853,7 +3084,14 @@ var init_LiteLLMPage = __esm({
|
|
|
2853
3084
|
},
|
|
2854
3085
|
getTeamUsageLoading: (teamId) => teamUsageLoading[teamId] ?? false
|
|
2855
3086
|
}
|
|
2856
|
-
), activeTab === "
|
|
3087
|
+
), activeTab === "models" && /* @__PURE__ */ React9.createElement(
|
|
3088
|
+
ModelsTable,
|
|
3089
|
+
{
|
|
3090
|
+
allModels: allModels ?? [],
|
|
3091
|
+
teams: teams ?? [],
|
|
3092
|
+
loading: modelsLoading
|
|
3093
|
+
}
|
|
3094
|
+
), activeTab === "audit" && userInfo.can_view_audit && /* @__PURE__ */ React9.createElement(AuditLog, { api }), /* @__PURE__ */ React9.createElement(
|
|
2857
3095
|
GenerateKeyDialog,
|
|
2858
3096
|
{
|
|
2859
3097
|
open: generateDialogOpen,
|
|
@@ -2866,15 +3104,15 @@ var init_LiteLLMPage = __esm({
|
|
|
2866
3104
|
onGenerateKey: handleGenerateKey,
|
|
2867
3105
|
onGetConfig: () => api.getConfig()
|
|
2868
3106
|
}
|
|
2869
|
-
), /* @__PURE__ */
|
|
2870
|
-
|
|
3107
|
+
), /* @__PURE__ */ React9.createElement(
|
|
3108
|
+
Snackbar2,
|
|
2871
3109
|
{
|
|
2872
3110
|
open: !!snackbar,
|
|
2873
3111
|
autoHideDuration: 5e3,
|
|
2874
3112
|
onClose: () => setSnackbar(null),
|
|
2875
3113
|
anchorOrigin: { vertical: "bottom", horizontal: "right" }
|
|
2876
3114
|
},
|
|
2877
|
-
snackbar ? /* @__PURE__ */
|
|
3115
|
+
snackbar ? /* @__PURE__ */ React9.createElement(Alert4, { severity: snackbar.severity, onClose: () => setSnackbar(null) }, snackbar.message) : void 0
|
|
2878
3116
|
));
|
|
2879
3117
|
};
|
|
2880
3118
|
}
|
|
@@ -2882,7 +3120,7 @@ var init_LiteLLMPage = __esm({
|
|
|
2882
3120
|
|
|
2883
3121
|
// src/plugin.tsx
|
|
2884
3122
|
init_api();
|
|
2885
|
-
import
|
|
3123
|
+
import React10 from "react";
|
|
2886
3124
|
import { TrendingUp as TrendingUpIcon } from "@mui/icons-material";
|
|
2887
3125
|
import {
|
|
2888
3126
|
createFrontendPlugin,
|
|
@@ -2901,10 +3139,10 @@ var liteLlmPage = PageBlueprint.make({
|
|
|
2901
3139
|
params: {
|
|
2902
3140
|
path: "/litellm",
|
|
2903
3141
|
title: "LiteLLM",
|
|
2904
|
-
icon: /* @__PURE__ */
|
|
3142
|
+
icon: /* @__PURE__ */ React10.createElement(TrendingUpIcon, null),
|
|
2905
3143
|
loader: async () => {
|
|
2906
3144
|
const { LiteLLMPage: LiteLLMPage2 } = await Promise.resolve().then(() => (init_LiteLLMPage(), LiteLLMPage_exports));
|
|
2907
|
-
return /* @__PURE__ */
|
|
3145
|
+
return /* @__PURE__ */ React10.createElement(LiteLLMPage2, null);
|
|
2908
3146
|
}
|
|
2909
3147
|
}
|
|
2910
3148
|
});
|
|
@@ -2923,16 +3161,16 @@ init_TeamUsage();
|
|
|
2923
3161
|
// src/components/LiteLLMHomeWidget.tsx
|
|
2924
3162
|
init_api();
|
|
2925
3163
|
init_format();
|
|
2926
|
-
import
|
|
3164
|
+
import React11, { useState as useState8, useEffect as useEffect2 } from "react";
|
|
2927
3165
|
import Paper6 from "@mui/material/Paper";
|
|
2928
|
-
import
|
|
2929
|
-
import
|
|
2930
|
-
import
|
|
2931
|
-
import
|
|
2932
|
-
import
|
|
3166
|
+
import Box10 from "@mui/material/Box";
|
|
3167
|
+
import Typography10 from "@mui/material/Typography";
|
|
3168
|
+
import FormControl2 from "@mui/material/FormControl";
|
|
3169
|
+
import Select2 from "@mui/material/Select";
|
|
3170
|
+
import MenuItem5 from "@mui/material/MenuItem";
|
|
2933
3171
|
import Grid2 from "@mui/material/Grid";
|
|
2934
3172
|
import CircularProgress5 from "@mui/material/CircularProgress";
|
|
2935
|
-
import
|
|
3173
|
+
import Alert5 from "@mui/material/Alert";
|
|
2936
3174
|
import { AreaChart as AreaChart3, Area as Area3, ResponsiveContainer as ResponsiveContainer3 } from "recharts";
|
|
2937
3175
|
import { useApi as useApi2 } from "@backstage/core-plugin-api";
|
|
2938
3176
|
function presetToDateRange(preset) {
|
|
@@ -2947,18 +3185,18 @@ function presetToDateRange(preset) {
|
|
|
2947
3185
|
}
|
|
2948
3186
|
return { start, end };
|
|
2949
3187
|
}
|
|
2950
|
-
var Kpi = ({ label, value }) => /* @__PURE__ */
|
|
3188
|
+
var Kpi = ({ label, value }) => /* @__PURE__ */ React11.createElement(Box10, null, /* @__PURE__ */ React11.createElement(Typography10, { variant: "caption", color: "text.secondary", display: "block" }, label), /* @__PURE__ */ React11.createElement(Typography10, { variant: "subtitle1", fontWeight: 600 }, value));
|
|
2951
3189
|
var LiteLLMHomeWidget = ({
|
|
2952
3190
|
defaultPeriod = "7d",
|
|
2953
3191
|
title = "LiteLLM Usage"
|
|
2954
3192
|
}) => {
|
|
2955
3193
|
const api = useApi2(liteLlmApiRef);
|
|
2956
|
-
const [period, setPeriod] =
|
|
2957
|
-
const [loading, setLoading] =
|
|
2958
|
-
const [usageError, setUsageError] =
|
|
2959
|
-
const [keysError, setKeysError] =
|
|
2960
|
-
const [usage, setUsage] =
|
|
2961
|
-
const [keys, setKeys] =
|
|
3194
|
+
const [period, setPeriod] = useState8(defaultPeriod);
|
|
3195
|
+
const [loading, setLoading] = useState8(true);
|
|
3196
|
+
const [usageError, setUsageError] = useState8(null);
|
|
3197
|
+
const [keysError, setKeysError] = useState8(null);
|
|
3198
|
+
const [usage, setUsage] = useState8(null);
|
|
3199
|
+
const [keys, setKeys] = useState8([]);
|
|
2962
3200
|
useEffect2(() => {
|
|
2963
3201
|
let cancelled = false;
|
|
2964
3202
|
setLoading(true);
|
|
@@ -2996,17 +3234,17 @@ var LiteLLMHomeWidget = ({
|
|
|
2996
3234
|
spend: d.spend
|
|
2997
3235
|
}));
|
|
2998
3236
|
const hasSparkline = dailyData.length > 0;
|
|
2999
|
-
return /* @__PURE__ */
|
|
3000
|
-
|
|
3237
|
+
return /* @__PURE__ */ React11.createElement(Paper6, { sx: { p: 2 } }, /* @__PURE__ */ React11.createElement(Box10, { display: "flex", justifyContent: "space-between", alignItems: "center", mb: 1.5 }, /* @__PURE__ */ React11.createElement(Typography10, { variant: "h6" }, title), /* @__PURE__ */ React11.createElement(FormControl2, { size: "small", sx: { minWidth: 90 } }, /* @__PURE__ */ React11.createElement(
|
|
3238
|
+
Select2,
|
|
3001
3239
|
{
|
|
3002
3240
|
value: period,
|
|
3003
3241
|
onChange: (e) => setPeriod(e.target.value),
|
|
3004
3242
|
displayEmpty: true
|
|
3005
3243
|
},
|
|
3006
|
-
/* @__PURE__ */
|
|
3007
|
-
/* @__PURE__ */
|
|
3008
|
-
/* @__PURE__ */
|
|
3009
|
-
))), loading && /* @__PURE__ */
|
|
3244
|
+
/* @__PURE__ */ React11.createElement(MenuItem5, { value: "today" }, "Today"),
|
|
3245
|
+
/* @__PURE__ */ React11.createElement(MenuItem5, { value: "7d" }, "7d"),
|
|
3246
|
+
/* @__PURE__ */ React11.createElement(MenuItem5, { value: "30d" }, "30d")
|
|
3247
|
+
))), loading && /* @__PURE__ */ React11.createElement(Box10, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 120 }, /* @__PURE__ */ React11.createElement(CircularProgress5, { size: 32 })), !loading && totalFailure && /* @__PURE__ */ React11.createElement(Alert5, { severity: "error", sx: { mt: 1 } }, usageError ?? "Failed to load usage data"), !loading && !totalFailure && /* @__PURE__ */ React11.createElement(React11.Fragment, null, partialFailure && /* @__PURE__ */ React11.createElement(Alert5, { severity: "warning", sx: { mt: 1, mb: 1 } }, usageError ? `Usage data unavailable (${usageError}).` : "", keysError ? ` Key list unavailable (${keysError}).` : "", " Showing what loaded."), /* @__PURE__ */ React11.createElement(Grid2, { container: true, spacing: 2, sx: { mb: hasSparkline ? 1.5 : 0 } }, /* @__PURE__ */ React11.createElement(Grid2, { item: true, xs: 6 }, /* @__PURE__ */ React11.createElement(Kpi, { label: "USD Spent", value: fmtUsd(usage?.total_spend ?? 0) })), /* @__PURE__ */ React11.createElement(Grid2, { item: true, xs: 6 }, /* @__PURE__ */ React11.createElement(Kpi, { label: "Tokens In", value: fmtInt(usage?.prompt_tokens ?? 0) })), /* @__PURE__ */ React11.createElement(Grid2, { item: true, xs: 6 }, /* @__PURE__ */ React11.createElement(Kpi, { label: "Tokens Out", value: fmtInt(usage?.completion_tokens ?? 0) })), /* @__PURE__ */ React11.createElement(Grid2, { item: true, xs: 6 }, /* @__PURE__ */ React11.createElement(Kpi, { label: "Keys", value: fmtInt(keys.length) }))), hasSparkline && /* @__PURE__ */ React11.createElement(Box10, { height: 120 }, /* @__PURE__ */ React11.createElement(ResponsiveContainer3, { width: "100%", height: "100%" }, /* @__PURE__ */ React11.createElement(AreaChart3, { data: dailyData, margin: { top: 4, right: 0, bottom: 0, left: 0 } }, /* @__PURE__ */ React11.createElement(
|
|
3010
3248
|
Area3,
|
|
3011
3249
|
{
|
|
3012
3250
|
type: "monotone",
|