@acarmisc/backstage-plugin-litellm 0.37.0 → 0.38.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/api.d.ts +3 -1
- package/dist/components/InsightsTab.d.ts +12 -0
- package/dist/index.cjs.js +355 -291
- package/dist/index.cjs.js.map +4 -4
- package/dist/index.esm.js +393 -329
- package/dist/index.esm.js.map +4 -4
- package/dist/insights.d.ts +8 -0
- package/dist/types.d.ts +1 -1
- package/package.json +2 -2
package/dist/index.cjs.js
CHANGED
|
@@ -317,6 +317,17 @@ var init_api = __esm({
|
|
|
317
317
|
end_date: endDate
|
|
318
318
|
});
|
|
319
319
|
}
|
|
320
|
+
async getSpendLogs(params) {
|
|
321
|
+
const strParams = {
|
|
322
|
+
start_date: params.startDate,
|
|
323
|
+
end_date: params.endDate
|
|
324
|
+
};
|
|
325
|
+
if (params.userId) strParams.userId = params.userId;
|
|
326
|
+
if (params.teamId) strParams.teamId = params.teamId;
|
|
327
|
+
if (params.page !== void 0) strParams.page = String(params.page);
|
|
328
|
+
if (params.pageSize !== void 0) strParams.page_size = String(params.pageSize);
|
|
329
|
+
return this.get("/insights/spend-logs", strParams);
|
|
330
|
+
}
|
|
320
331
|
async getConfig() {
|
|
321
332
|
return this.get("/config");
|
|
322
333
|
}
|
|
@@ -5661,292 +5672,335 @@ var init_ModelsTable = __esm({
|
|
|
5661
5672
|
}
|
|
5662
5673
|
});
|
|
5663
5674
|
|
|
5664
|
-
// src/
|
|
5665
|
-
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
5675
|
+
// src/insights.ts
|
|
5676
|
+
var costPer1k, summarizeUsage, scorePractices, modelOpportunities;
|
|
5677
|
+
var init_insights = __esm({
|
|
5678
|
+
"src/insights.ts"() {
|
|
5679
|
+
"use strict";
|
|
5680
|
+
costPer1k = (model, spend, tokens, models) => {
|
|
5681
|
+
const info = models?.find((m) => m.model_name === model);
|
|
5682
|
+
const listed = info?.input_cost_per_token != null && info?.output_cost_per_token != null ? (info.input_cost_per_token + info.output_cost_per_token) / 2 * 1e3 : null;
|
|
5683
|
+
if (listed !== null) return listed;
|
|
5684
|
+
return tokens > 0 ? spend / tokens * 1e3 : null;
|
|
5685
|
+
};
|
|
5686
|
+
summarizeUsage = (usage, models, premiumThresholdPer1k = 0.01) => {
|
|
5687
|
+
if (!usage) {
|
|
5688
|
+
return {
|
|
5689
|
+
costPer1kTokens: null,
|
|
5690
|
+
costPerRequest: null,
|
|
5691
|
+
outputInputRatio: null,
|
|
5692
|
+
retryWasteUsd: 0,
|
|
5693
|
+
retryWasteShare: 0,
|
|
5694
|
+
successRate: null,
|
|
5695
|
+
premiumShare: 0,
|
|
5696
|
+
premiumSpend: 0,
|
|
5697
|
+
topModelConcentration: 0,
|
|
5698
|
+
projectedMonthSpend: null,
|
|
5699
|
+
burnMultiple: null
|
|
5700
|
+
};
|
|
5701
|
+
}
|
|
5702
|
+
const {
|
|
5703
|
+
total_spend: spend,
|
|
5704
|
+
total_tokens: tokens,
|
|
5705
|
+
prompt_tokens: prompt,
|
|
5706
|
+
completion_tokens: completion,
|
|
5707
|
+
api_requests: reqs,
|
|
5708
|
+
successful_requests: ok,
|
|
5709
|
+
failed_requests: failed,
|
|
5710
|
+
usage_by_model: byModel
|
|
5711
|
+
} = usage;
|
|
5712
|
+
const wasteShare = reqs > 0 ? failed / reqs : 0;
|
|
5713
|
+
const entries = Object.entries(byModel ?? {});
|
|
5714
|
+
const topSpend = entries.reduce((m, [, d]) => Math.max(m, d.total_spend), 0);
|
|
5715
|
+
const premiumSpend = entries.filter(([name, d]) => (costPer1k(name, d.total_spend, d.total_tokens, models) ?? 0) > premiumThresholdPer1k).reduce((s, [, d]) => s + d.total_spend, 0);
|
|
5716
|
+
const days = usage.daily_usage?.length ?? 0;
|
|
5717
|
+
const projected = days > 0 ? spend / days * 30 : null;
|
|
5718
|
+
return {
|
|
5719
|
+
costPer1kTokens: tokens > 0 ? spend / tokens * 1e3 : null,
|
|
5720
|
+
costPerRequest: reqs > 0 ? spend / reqs : null,
|
|
5721
|
+
outputInputRatio: prompt > 0 ? completion / prompt : null,
|
|
5722
|
+
retryWasteUsd: spend * wasteShare,
|
|
5723
|
+
retryWasteShare: wasteShare,
|
|
5724
|
+
successRate: reqs > 0 ? ok / reqs : null,
|
|
5725
|
+
premiumShare: spend > 0 ? premiumSpend / spend : 0,
|
|
5726
|
+
premiumSpend,
|
|
5727
|
+
topModelConcentration: spend > 0 ? topSpend / spend : 0,
|
|
5728
|
+
projectedMonthSpend: projected,
|
|
5729
|
+
burnMultiple: projected !== null && spend > 0 ? projected / spend : null
|
|
5730
|
+
};
|
|
5731
|
+
};
|
|
5732
|
+
scorePractices = (summary, keyStats) => {
|
|
5733
|
+
const waste = summary.retryWasteShare <= 0.01 ? 30 : summary.retryWasteShare <= 0.05 ? 20 : summary.retryWasteShare <= 0.1 ? 10 : 0;
|
|
5734
|
+
const premium = summary.premiumShare <= 0.2 ? 25 : summary.premiumShare <= 0.5 ? 15 : summary.premiumShare <= 0.8 ? 5 : 0;
|
|
5735
|
+
const conc = summary.topModelConcentration <= 0.5 ? 20 : summary.topModelConcentration <= 0.8 ? 10 : 0;
|
|
5736
|
+
const bad = keyStats.totalKeys > 0 ? (keyStats.idleKeys + keyStats.uncappedKeys) / keyStats.totalKeys : 0;
|
|
5737
|
+
const hygiene = keyStats.totalKeys === 0 ? 25 : bad === 0 ? 25 : bad <= 0.2 ? 15 : bad <= 0.5 ? 5 : 0;
|
|
5738
|
+
const checks = [
|
|
5739
|
+
{ id: "retry-waste", label: "Low retry waste", points: waste, maxPoints: 30, detail: `${(summary.retryWasteShare * 100).toFixed(1)}% of requests failed` },
|
|
5740
|
+
{ id: "premium-mix", label: "Balanced model mix", points: premium, maxPoints: 25, detail: `${(summary.premiumShare * 100).toFixed(1)}% of spend on premium models` },
|
|
5741
|
+
{ id: "concentration", label: "No single-model lock-in", points: conc, maxPoints: 20, detail: `Top model is ${(summary.topModelConcentration * 100).toFixed(1)}% of spend` },
|
|
5742
|
+
{ id: "key-hygiene", label: "Key hygiene", points: hygiene, maxPoints: 25, detail: `${keyStats.idleKeys} idle, ${keyStats.uncappedKeys} uncapped of ${keyStats.totalKeys} keys` }
|
|
5743
|
+
];
|
|
5744
|
+
return { total: checks.reduce((s, c) => s + c.points, 0), checks };
|
|
5745
|
+
};
|
|
5746
|
+
modelOpportunities = (usage, models, premiumThresholdPer1k = 0.01) => {
|
|
5747
|
+
if (!usage) return [];
|
|
5748
|
+
const entries = Object.entries(usage.usage_by_model ?? {});
|
|
5749
|
+
const overallRate = usage.api_requests > 0 ? usage.failed_requests / usage.api_requests : 0;
|
|
5750
|
+
return entries.map(([model, d]) => {
|
|
5751
|
+
const failureRate = d.api_requests > 0 ? d.failed_requests / d.api_requests : null;
|
|
5752
|
+
const avgCostPerRequest = d.api_requests > 0 ? d.total_spend / d.api_requests : 0;
|
|
5753
|
+
const completionShare = d.total_tokens > 0 ? d.completion_tokens / d.total_tokens : 0;
|
|
5754
|
+
const per1k = costPer1k(model, d.total_spend, d.total_tokens, models) ?? 0;
|
|
5755
|
+
const spendShare = usage.total_spend > 0 ? d.total_spend / usage.total_spend : 0;
|
|
5756
|
+
if (failureRate !== null && overallRate > 0 && failureRate > 2 * overallRate && d.api_requests >= 10) {
|
|
5757
|
+
return { model, spend: d.total_spend, requests: d.api_requests, failureRate, avgCostPerRequest, flag: "failure-outlier", hint: `Fails ${(failureRate * 100).toFixed(1)}% vs ${(overallRate * 100).toFixed(1)}% overall` };
|
|
5758
|
+
}
|
|
5759
|
+
if (per1k > premiumThresholdPer1k && completionShare < 0.3 && spendShare > 0.05) {
|
|
5760
|
+
return { model, spend: d.total_spend, requests: d.api_requests, failureRate, avgCostPerRequest, flag: "downgrade-candidate", hint: `$${per1k.toFixed(3)}/1k with ${(completionShare * 100).toFixed(0)}% output \u2014 try a cheaper model` };
|
|
5761
|
+
}
|
|
5762
|
+
return { model, spend: d.total_spend, requests: d.api_requests, failureRate, avgCostPerRequest, flag: "ok", hint: "No action" };
|
|
5763
|
+
});
|
|
5764
|
+
};
|
|
5669
5765
|
}
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5766
|
+
});
|
|
5767
|
+
|
|
5768
|
+
// src/dates.ts
|
|
5769
|
+
function toLocalDay(d) {
|
|
5770
|
+
const year = d.getFullYear();
|
|
5771
|
+
const month = String(d.getMonth() + 1).padStart(2, "0");
|
|
5772
|
+
const day = String(d.getDate()).padStart(2, "0");
|
|
5773
|
+
return `${year}-${month}-${day}`;
|
|
5675
5774
|
}
|
|
5676
|
-
function
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
}
|
|
5775
|
+
function monthToDateRange(now = /* @__PURE__ */ new Date()) {
|
|
5776
|
+
const start = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
5777
|
+
return {
|
|
5778
|
+
startDate: toLocalDay(start),
|
|
5779
|
+
endDate: toLocalDay(now)
|
|
5780
|
+
};
|
|
5682
5781
|
}
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5686
|
-
}
|
|
5687
|
-
if (entries.length === 0) {
|
|
5688
|
-
return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableRow6.default, { children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { colSpan: 6, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
5689
|
-
EmptyState,
|
|
5690
|
-
{
|
|
5691
|
-
message: "No audit events found",
|
|
5692
|
-
hint: "Try widening the filters or a different time window."
|
|
5693
|
-
}
|
|
5694
|
-
) }) });
|
|
5782
|
+
var init_dates = __esm({
|
|
5783
|
+
"src/dates.ts"() {
|
|
5784
|
+
"use strict";
|
|
5695
5785
|
}
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
var
|
|
5700
|
-
|
|
5786
|
+
});
|
|
5787
|
+
|
|
5788
|
+
// src/components/InsightsTab.tsx
|
|
5789
|
+
var import_react12, import_Box16, import_Grid2, import_Typography14, import_Alert5, import_Button7, import_Table6, import_TableBody6, import_TableCell6, import_TableContainer6, import_TableHead6, import_TableRow6, import_Skeleton6, import_react_use, import_jsx_runtime17, FLAG_TONE, FLAG_LABEL, fmtRatio, fmtPct1, failedRow, InsightsTab;
|
|
5790
|
+
var init_InsightsTab = __esm({
|
|
5791
|
+
"src/components/InsightsTab.tsx"() {
|
|
5701
5792
|
"use strict";
|
|
5702
5793
|
import_react12 = require("react");
|
|
5703
5794
|
import_Box16 = __toESM(require("@mui/material/Box"));
|
|
5795
|
+
import_Grid2 = __toESM(require("@mui/material/Grid"));
|
|
5704
5796
|
import_Typography14 = __toESM(require("@mui/material/Typography"));
|
|
5797
|
+
import_Alert5 = __toESM(require("@mui/material/Alert"));
|
|
5798
|
+
import_Button7 = __toESM(require("@mui/material/Button"));
|
|
5705
5799
|
import_Table6 = __toESM(require("@mui/material/Table"));
|
|
5706
5800
|
import_TableBody6 = __toESM(require("@mui/material/TableBody"));
|
|
5707
5801
|
import_TableCell6 = __toESM(require("@mui/material/TableCell"));
|
|
5708
5802
|
import_TableContainer6 = __toESM(require("@mui/material/TableContainer"));
|
|
5709
5803
|
import_TableHead6 = __toESM(require("@mui/material/TableHead"));
|
|
5710
5804
|
import_TableRow6 = __toESM(require("@mui/material/TableRow"));
|
|
5711
|
-
import_TablePagination = __toESM(require("@mui/material/TablePagination"));
|
|
5712
|
-
import_TextField6 = __toESM(require("@mui/material/TextField"));
|
|
5713
|
-
import_MenuItem5 = __toESM(require("@mui/material/MenuItem"));
|
|
5714
5805
|
import_Skeleton6 = __toESM(require("@mui/material/Skeleton"));
|
|
5715
|
-
import_Collapse3 = __toESM(require("@mui/material/Collapse"));
|
|
5716
|
-
import_IconButton7 = __toESM(require("@mui/material/IconButton"));
|
|
5717
|
-
import_Alert5 = __toESM(require("@mui/material/Alert"));
|
|
5718
|
-
import_styles12 = require("@mui/material/styles");
|
|
5719
|
-
import_icons_material9 = require("@mui/icons-material");
|
|
5720
5806
|
import_react_use = require("react-use");
|
|
5807
|
+
init_insights();
|
|
5808
|
+
init_format();
|
|
5809
|
+
init_dates();
|
|
5721
5810
|
init_ui();
|
|
5722
5811
|
import_jsx_runtime17 = require("react/jsx-runtime");
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
|
|
5726
|
-
|
|
5727
|
-
blocked: "danger",
|
|
5728
|
-
unblocked: "success"
|
|
5812
|
+
FLAG_TONE = {
|
|
5813
|
+
"failure-outlier": "danger",
|
|
5814
|
+
"downgrade-candidate": "warning",
|
|
5815
|
+
ok: "success"
|
|
5729
5816
|
};
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
LiteLLM_ObjectPermissionTable: "Team access (KB / MCP)",
|
|
5735
|
-
LiteLLM_UserTable: "User"
|
|
5817
|
+
FLAG_LABEL = {
|
|
5818
|
+
"failure-outlier": "Failure outlier",
|
|
5819
|
+
"downgrade-candidate": "Downgrade candidate",
|
|
5820
|
+
ok: "OK"
|
|
5736
5821
|
};
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
5822
|
+
fmtRatio = (n) => n === null || Number.isNaN(n) ? "\u2014" : `${n.toFixed(2)}\xD7`;
|
|
5823
|
+
fmtPct1 = (n) => n === null || Number.isNaN(n) ? "\u2014" : `${(n * 100).toFixed(1)}%`;
|
|
5824
|
+
failedRow = (status) => /fail|error/i.test(status ?? "");
|
|
5825
|
+
InsightsTab = ({
|
|
5826
|
+
api,
|
|
5827
|
+
usage,
|
|
5828
|
+
models,
|
|
5829
|
+
keys,
|
|
5830
|
+
dateRange,
|
|
5831
|
+
loading
|
|
5832
|
+
}) => {
|
|
5833
|
+
const summary = (0, import_react12.useMemo)(() => summarizeUsage(usage, models), [usage, models]);
|
|
5834
|
+
const keyStats = (0, import_react12.useMemo)(
|
|
5835
|
+
() => ({
|
|
5836
|
+
idleKeys: keys.filter((k) => (k.spend ?? 0) === 0).length,
|
|
5837
|
+
uncappedKeys: keys.filter((k) => k.max_budget === void 0 || k.max_budget === null).length,
|
|
5838
|
+
totalKeys: keys.length
|
|
5839
|
+
}),
|
|
5840
|
+
[keys]
|
|
5841
|
+
);
|
|
5842
|
+
const score = (0, import_react12.useMemo)(
|
|
5843
|
+
() => scorePractices(summary, keyStats),
|
|
5844
|
+
[summary, keyStats]
|
|
5845
|
+
);
|
|
5846
|
+
const opportunities = (0, import_react12.useMemo)(() => {
|
|
5847
|
+
const opps = modelOpportunities(usage, models);
|
|
5848
|
+
return [...opps].sort(
|
|
5849
|
+
(a, b) => (a.flag === "ok" ? 1 : 0) - (b.flag === "ok" ? 1 : 0)
|
|
5850
|
+
);
|
|
5851
|
+
}, [usage, models]);
|
|
5852
|
+
const {
|
|
5853
|
+
value: spendLogs,
|
|
5854
|
+
loading: logsLoading,
|
|
5855
|
+
error: logsError,
|
|
5856
|
+
retry: retryLogs
|
|
5857
|
+
} = (0, import_react_use.useAsyncRetry)(async () => {
|
|
5858
|
+
return api.getSpendLogs({
|
|
5859
|
+
startDate: toLocalDay(dateRange.start),
|
|
5860
|
+
endDate: toLocalDay(dateRange.end),
|
|
5861
|
+
pageSize: 1e3
|
|
5862
|
+
});
|
|
5863
|
+
}, [api, dateRange]);
|
|
5864
|
+
const logStats = (0, import_react12.useMemo)(() => {
|
|
5865
|
+
const rows = spendLogs?.data ?? [];
|
|
5866
|
+
const hits = rows.filter((r) => r.cacheHit);
|
|
5867
|
+
const cachedPromptTokens = hits.reduce((s, r) => s + (r.promptTokens ?? 0), 0);
|
|
5868
|
+
const priced = models.map((m) => m.input_cost_per_token).filter((p) => p !== null && p !== void 0 && p > 0);
|
|
5869
|
+
const avgInputPrice = priced.length > 0 ? priced.reduce((s, p) => s + p, 0) / priced.length : null;
|
|
5870
|
+
return {
|
|
5871
|
+
total: spendLogs?.total ?? rows.length,
|
|
5872
|
+
hitRate: rows.length > 0 ? hits.length / rows.length : null,
|
|
5873
|
+
// Estimate: cached prompt tokens billed at the average listed input price.
|
|
5874
|
+
savedUsd: avgInputPrice !== null ? cachedPromptTokens * avgInputPrice : null,
|
|
5875
|
+
failedWasteUsd: rows.filter((r) => failedRow(r.status)).reduce((s, r) => s + (r.spend ?? 0), 0),
|
|
5876
|
+
truncated: spendLogs?.truncated ?? false
|
|
5877
|
+
};
|
|
5878
|
+
}, [spendLogs, models]);
|
|
5879
|
+
const renderSpendLogsBody = () => {
|
|
5880
|
+
if (logsLoading) return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Skeleton6.default, { variant: "rounded", height: 120 });
|
|
5881
|
+
if (logsError) {
|
|
5882
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_Alert5.default, { severity: "error", children: [
|
|
5883
|
+
"Failed to load spend logs: ",
|
|
5884
|
+
logsError.message || "Unknown error",
|
|
5885
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Box16.default, { sx: { mt: 1 }, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Button7.default, { size: "small", onClick: retryLogs, children: "Retry" }) })
|
|
5886
|
+
] });
|
|
5887
|
+
}
|
|
5888
|
+
if ((spendLogs?.data ?? []).length === 0) {
|
|
5889
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(EmptyState, { message: "No spend logs in this period" });
|
|
5890
|
+
}
|
|
5891
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_Box16.default, { sx: { display: "flex", gap: 3, flexWrap: "wrap" }, children: [
|
|
5892
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Stat, { label: "Cache-hit rate", value: fmtPct1(logStats.hitRate) }),
|
|
5893
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
5894
|
+
Stat,
|
|
5762
5895
|
{
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
color: "text.secondary",
|
|
5766
|
-
title: entry.object_id ?? void 0,
|
|
5767
|
-
sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", fontSize: 12 },
|
|
5768
|
-
children: entry.object_id ? entry.object_id.slice(0, 20) + (entry.object_id.length > 20 ? "\u2026" : "") : "\u2014"
|
|
5896
|
+
label: "Cache savings (est.)",
|
|
5897
|
+
value: logStats.savedUsd !== null ? fmtUsd(logStats.savedUsd) : "\u2014"
|
|
5769
5898
|
}
|
|
5770
|
-
)
|
|
5771
|
-
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
5772
|
-
|
|
5773
|
-
|
|
5774
|
-
|
|
5899
|
+
),
|
|
5900
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Stat, { label: "Failed-request waste", value: fmtUsd(logStats.failedWasteUsd) }),
|
|
5901
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(Stat, { label: "Log rows", value: fmtInt(logStats.total) }),
|
|
5902
|
+
logStats.truncated && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Typography14.default, { variant: "caption", color: "text.secondary", children: "Showing the first 1,000 rows \u2014 figures are a lower bound." })
|
|
5903
|
+
] });
|
|
5904
|
+
};
|
|
5905
|
+
const kpis = [
|
|
5906
|
+
{
|
|
5907
|
+
label: "Cost / 1k tokens",
|
|
5908
|
+
value: summary.costPer1kTokens !== null ? fmtUsd(summary.costPer1kTokens) : "\u2014",
|
|
5909
|
+
tone: "accent"
|
|
5910
|
+
},
|
|
5911
|
+
{
|
|
5912
|
+
label: "Cost / request",
|
|
5913
|
+
value: summary.costPerRequest !== null ? fmtUsd(summary.costPerRequest) : "\u2014",
|
|
5914
|
+
tone: "info"
|
|
5915
|
+
},
|
|
5916
|
+
{
|
|
5917
|
+
label: "Out : in ratio",
|
|
5918
|
+
value: fmtRatio(summary.outputInputRatio),
|
|
5919
|
+
tone: "neutral"
|
|
5920
|
+
},
|
|
5921
|
+
{
|
|
5922
|
+
label: "Retry waste",
|
|
5923
|
+
value: fmtUsd(summary.retryWasteUsd),
|
|
5924
|
+
hint: `${fmtPct1(summary.retryWasteShare)} of spend`,
|
|
5925
|
+
tone: "warning"
|
|
5926
|
+
},
|
|
5927
|
+
{
|
|
5928
|
+
label: "Success rate",
|
|
5929
|
+
value: fmtPct1(summary.successRate),
|
|
5930
|
+
tone: "success"
|
|
5931
|
+
}
|
|
5932
|
+
];
|
|
5933
|
+
const renderEfficiencyBody = () => {
|
|
5934
|
+
if (loading) return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Skeleton6.default, { variant: "rounded", height: 96 });
|
|
5935
|
+
if (!usage) return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(EmptyState, { message: "No usage in this period" });
|
|
5936
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(MetricStrip, { metrics: kpis });
|
|
5937
|
+
};
|
|
5938
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_Box16.default, { sx: { display: "flex", flexDirection: "column", gap: 2 }, children: [
|
|
5939
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
5940
|
+
SectionCard,
|
|
5775
5941
|
{
|
|
5776
|
-
|
|
5777
|
-
|
|
5778
|
-
|
|
5779
|
-
|
|
5780
|
-
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
children: "Before"
|
|
5796
|
-
}
|
|
5797
|
-
),
|
|
5798
|
-
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Typography14.default, { component: "pre", variant: "caption", sx: { fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", whiteSpace: "pre-wrap", wordBreak: "break-all", m: 0 }, children: JSON.stringify(entry.before_value, null, 2) })
|
|
5942
|
+
title: "Efficiency",
|
|
5943
|
+
subtitle: `${dateRange.start.toLocaleDateString()} \u2013 ${dateRange.end.toLocaleDateString()}`,
|
|
5944
|
+
children: renderEfficiencyBody()
|
|
5945
|
+
}
|
|
5946
|
+
),
|
|
5947
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_Grid2.default, { container: true, spacing: 2, alignItems: "flex-start", children: [
|
|
5948
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Grid2.default, { item: true, xs: 12, md: 5, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(SectionCard, { title: "Practice score", subtitle: "Out of 100", children: loading ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Skeleton6.default, { variant: "rounded", height: 160 }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_Box16.default, { sx: { display: "flex", flexDirection: "column", gap: 1.5 }, children: [
|
|
5949
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_Typography14.default, { sx: { fontSize: 30, fontWeight: 700, fontVariantNumeric: "tabular-nums" }, children: [
|
|
5950
|
+
score.total,
|
|
5951
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Typography14.default, { component: "span", color: "text.secondary", sx: { fontSize: 16 }, children: "/100" })
|
|
5952
|
+
] }),
|
|
5953
|
+
score.checks.map((c) => /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_Box16.default, { children: [
|
|
5954
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_Box16.default, { sx: { display: "flex", justifyContent: "space-between", gap: 1, mb: 0.5 }, children: [
|
|
5955
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Typography14.default, { variant: "body2", sx: { fontWeight: 600 }, children: c.label }),
|
|
5956
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_Typography14.default, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" }, children: [
|
|
5957
|
+
c.points,
|
|
5958
|
+
"/",
|
|
5959
|
+
c.maxPoints
|
|
5960
|
+
] })
|
|
5799
5961
|
] }),
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
|
|
5805
|
-
|
|
5806
|
-
|
|
5807
|
-
|
|
5808
|
-
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
|
|
5814
|
-
|
|
5962
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
5963
|
+
Meter,
|
|
5964
|
+
{
|
|
5965
|
+
value: c.maxPoints > 0 ? c.points / c.maxPoints * 100 : 0,
|
|
5966
|
+
ariaLabel: `${c.label}: ${c.points} of ${c.maxPoints}`
|
|
5967
|
+
}
|
|
5968
|
+
),
|
|
5969
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Typography14.default, { variant: "caption", color: "text.secondary", children: c.detail })
|
|
5970
|
+
] }, c.id))
|
|
5971
|
+
] }) }) }),
|
|
5972
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Grid2.default, { item: true, xs: 12, md: 7, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(SectionCard, { title: "Cache & errors", subtitle: "From spend logs (estimate)", children: renderSpendLogsBody() }) })
|
|
5973
|
+
] }),
|
|
5974
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(SectionCard, { title: "Model opportunities", flush: true, children: loading ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Box16.default, { sx: { p: 2.5 }, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Skeleton6.default, { variant: "rounded", height: 120 }) }) : /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableContainer6.default, { children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_Table6.default, { size: "small", sx: dataTableSx, children: [
|
|
5975
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableHead6.default, { children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_TableRow6.default, { children: [
|
|
5976
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { children: "Model" }),
|
|
5977
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { align: "right", children: "Spend" }),
|
|
5978
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { align: "right", children: "Requests" }),
|
|
5979
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { align: "right", children: "Failure" }),
|
|
5980
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { align: "right", children: "Avg / req" }),
|
|
5981
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { children: "Signal" })
|
|
5982
|
+
] }) }),
|
|
5983
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableBody6.default, { children: opportunities.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableRow6.default, { children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { colSpan: 6, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(EmptyState, { message: "No model activity in this period" }) }) }) : opportunities.map((o) => /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_TableRow6.default, { children: [
|
|
5984
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Typography14.default, { variant: "body2", sx: { fontWeight: 600 }, children: o.model }) }),
|
|
5985
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { align: "right", children: fmtUsd(o.spend) }),
|
|
5986
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { align: "right", children: fmtInt(o.requests) }),
|
|
5987
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { align: "right", children: fmtPct1(o.failureRate) }),
|
|
5988
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { align: "right", children: fmtUsd(o.avgCostPerRequest) }),
|
|
5989
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_TableCell6.default, { children: [
|
|
5990
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(StatusPill, { label: FLAG_LABEL[o.flag], tone: FLAG_TONE[o.flag], title: o.hint }),
|
|
5991
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Typography14.default, { variant: "caption", color: "text.secondary", sx: { display: "block", mt: 0.25 }, children: o.hint })
|
|
5992
|
+
] })
|
|
5993
|
+
] }, o.model)) })
|
|
5994
|
+
] }) }) }),
|
|
5995
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(SectionCard, { title: "Quality scores", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
5996
|
+
EmptyState,
|
|
5997
|
+
{
|
|
5998
|
+
message: "Quality scores \u2014 connect Langfuse",
|
|
5999
|
+
hint: "LLM-as-judge evaluation is not configured for this deployment."
|
|
5815
6000
|
}
|
|
5816
|
-
) })
|
|
6001
|
+
) })
|
|
5817
6002
|
] });
|
|
5818
6003
|
};
|
|
5819
|
-
AuditLog = ({ api }) => {
|
|
5820
|
-
const [page, setPage] = (0, import_react12.useState)(0);
|
|
5821
|
-
const [pageSize, setPageSize] = (0, import_react12.useState)(25);
|
|
5822
|
-
const [filters, setFilters] = (0, import_react12.useState)({});
|
|
5823
|
-
const fetchParams = (0, import_react12.useMemo)(
|
|
5824
|
-
() => ({ page: page + 1, page_size: pageSize, ...filters }),
|
|
5825
|
-
[page, pageSize, filters]
|
|
5826
|
-
);
|
|
5827
|
-
const { value, loading, error } = (0, import_react_use.useAsync)(
|
|
5828
|
-
() => api.getAuditLogs(fetchParams),
|
|
5829
|
-
[api, fetchParams]
|
|
5830
|
-
);
|
|
5831
|
-
const entries = value?.audit_logs ?? [];
|
|
5832
|
-
const total = value?.total ?? 0;
|
|
5833
|
-
return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
|
|
5834
|
-
SectionCard,
|
|
5835
|
-
{
|
|
5836
|
-
title: "Audit Log",
|
|
5837
|
-
subtitle: loading ? "Loading\u2026" : `${total.toLocaleString()} event${total === 1 ? "" : "s"}`,
|
|
5838
|
-
flush: true,
|
|
5839
|
-
actions: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
|
|
5840
|
-
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
|
|
5841
|
-
import_TextField6.default,
|
|
5842
|
-
{
|
|
5843
|
-
size: "small",
|
|
5844
|
-
label: "Action",
|
|
5845
|
-
select: true,
|
|
5846
|
-
value: filters.action ?? "",
|
|
5847
|
-
onChange: (e) => {
|
|
5848
|
-
setFilters((f) => ({ ...f, action: e.target.value || void 0 }));
|
|
5849
|
-
setPage(0);
|
|
5850
|
-
},
|
|
5851
|
-
sx: { minWidth: 150 },
|
|
5852
|
-
children: [
|
|
5853
|
-
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_MenuItem5.default, { value: "", children: "All actions" }),
|
|
5854
|
-
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_MenuItem5.default, { value: "created", children: "Created" }),
|
|
5855
|
-
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_MenuItem5.default, { value: "updated", children: "Updated" }),
|
|
5856
|
-
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_MenuItem5.default, { value: "deleted", children: "Deleted" }),
|
|
5857
|
-
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_MenuItem5.default, { value: "blocked", children: "Blocked" })
|
|
5858
|
-
]
|
|
5859
|
-
}
|
|
5860
|
-
),
|
|
5861
|
-
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
|
|
5862
|
-
import_TextField6.default,
|
|
5863
|
-
{
|
|
5864
|
-
size: "small",
|
|
5865
|
-
label: "Table",
|
|
5866
|
-
select: true,
|
|
5867
|
-
value: filters.table_name ?? "",
|
|
5868
|
-
onChange: (e) => {
|
|
5869
|
-
setFilters((f) => ({ ...f, table_name: e.target.value || void 0 }));
|
|
5870
|
-
setPage(0);
|
|
5871
|
-
},
|
|
5872
|
-
sx: { minWidth: 150 },
|
|
5873
|
-
children: [
|
|
5874
|
-
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_MenuItem5.default, { value: "", children: "All tables" }),
|
|
5875
|
-
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_MenuItem5.default, { value: "LiteLLM_VerificationToken", children: "Key" }),
|
|
5876
|
-
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_MenuItem5.default, { value: "LiteLLM_TeamTable", children: "Team" }),
|
|
5877
|
-
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_MenuItem5.default, { value: "LiteLLM_TeamMembership", children: "Team member" }),
|
|
5878
|
-
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_MenuItem5.default, { value: "LiteLLM_ObjectPermissionTable", children: "Team access (KB / MCP)" }),
|
|
5879
|
-
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_MenuItem5.default, { value: "LiteLLM_UserTable", children: "User" })
|
|
5880
|
-
]
|
|
5881
|
-
}
|
|
5882
|
-
),
|
|
5883
|
-
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
5884
|
-
import_TextField6.default,
|
|
5885
|
-
{
|
|
5886
|
-
size: "small",
|
|
5887
|
-
label: "Changed by",
|
|
5888
|
-
value: filters.changed_by ?? "",
|
|
5889
|
-
onChange: (e) => {
|
|
5890
|
-
setFilters((f) => ({ ...f, changed_by: e.target.value || void 0 }));
|
|
5891
|
-
setPage(0);
|
|
5892
|
-
},
|
|
5893
|
-
sx: { minWidth: 180 }
|
|
5894
|
-
}
|
|
5895
|
-
)
|
|
5896
|
-
] }),
|
|
5897
|
-
children: [
|
|
5898
|
-
error && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Box16.default, { px: 2.5, pb: 2, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Alert5.default, { severity: "error", children: error.message }) }),
|
|
5899
|
-
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableContainer6.default, { children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_Table6.default, { size: "small", sx: dataTableSx, children: [
|
|
5900
|
-
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableHead6.default, { children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_TableRow6.default, { children: [
|
|
5901
|
-
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { sx: { width: 40 } }),
|
|
5902
|
-
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { children: "Time" }),
|
|
5903
|
-
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { children: "Action" }),
|
|
5904
|
-
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { children: "Table" }),
|
|
5905
|
-
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { children: "Object ID" }),
|
|
5906
|
-
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { children: "Changed By" })
|
|
5907
|
-
] }) }),
|
|
5908
|
-
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableBody6.default, { children: renderAuditLogBody(loading, entries) })
|
|
5909
|
-
] }) }),
|
|
5910
|
-
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
5911
|
-
import_TablePagination.default,
|
|
5912
|
-
{
|
|
5913
|
-
component: "div",
|
|
5914
|
-
count: total,
|
|
5915
|
-
page,
|
|
5916
|
-
onPageChange: (_, p) => setPage(p),
|
|
5917
|
-
rowsPerPage: pageSize,
|
|
5918
|
-
onRowsPerPageChange: (e) => {
|
|
5919
|
-
setPageSize(Number(e.target.value));
|
|
5920
|
-
setPage(0);
|
|
5921
|
-
},
|
|
5922
|
-
rowsPerPageOptions: [10, 25, 50],
|
|
5923
|
-
sx: (theme) => ({ borderTop: `1px solid ${theme.palette.divider}` })
|
|
5924
|
-
}
|
|
5925
|
-
)
|
|
5926
|
-
]
|
|
5927
|
-
}
|
|
5928
|
-
);
|
|
5929
|
-
};
|
|
5930
|
-
}
|
|
5931
|
-
});
|
|
5932
|
-
|
|
5933
|
-
// src/dates.ts
|
|
5934
|
-
function toLocalDay(d) {
|
|
5935
|
-
const year = d.getFullYear();
|
|
5936
|
-
const month = String(d.getMonth() + 1).padStart(2, "0");
|
|
5937
|
-
const day = String(d.getDate()).padStart(2, "0");
|
|
5938
|
-
return `${year}-${month}-${day}`;
|
|
5939
|
-
}
|
|
5940
|
-
function monthToDateRange(now = /* @__PURE__ */ new Date()) {
|
|
5941
|
-
const start = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
5942
|
-
return {
|
|
5943
|
-
startDate: toLocalDay(start),
|
|
5944
|
-
endDate: toLocalDay(now)
|
|
5945
|
-
};
|
|
5946
|
-
}
|
|
5947
|
-
var init_dates = __esm({
|
|
5948
|
-
"src/dates.ts"() {
|
|
5949
|
-
"use strict";
|
|
5950
6004
|
}
|
|
5951
6005
|
});
|
|
5952
6006
|
|
|
@@ -6058,18 +6112,18 @@ function initDateRange() {
|
|
|
6058
6112
|
else start.setDate(start.getDate() - 7);
|
|
6059
6113
|
return { start, end };
|
|
6060
6114
|
}
|
|
6061
|
-
var import_react13, import_Box17, import_Alert6,
|
|
6115
|
+
var import_react13, import_Box17, import_Alert6, import_Button8, import_Typography15, import_Tabs2, import_Tab2, import_Grid3, import_Accordion2, import_AccordionSummary2, import_AccordionDetails2, import_ExpandMore, import_react_router_dom, import_react_use2, import_core_plugin_api5, import_core_components3, import_plugin_permission_react3, import_backstage_plugin_litellm_common5, import_jsx_runtime18, PERIOD_LS_KEY2, PAGE_TABS, isPageTab, LiteLLMPage;
|
|
6062
6116
|
var init_LiteLLMPage = __esm({
|
|
6063
6117
|
"src/components/LiteLLMPage.tsx"() {
|
|
6064
6118
|
"use strict";
|
|
6065
6119
|
import_react13 = require("react");
|
|
6066
6120
|
import_Box17 = __toESM(require("@mui/material/Box"));
|
|
6067
6121
|
import_Alert6 = __toESM(require("@mui/material/Alert"));
|
|
6068
|
-
|
|
6122
|
+
import_Button8 = __toESM(require("@mui/material/Button"));
|
|
6069
6123
|
import_Typography15 = __toESM(require("@mui/material/Typography"));
|
|
6070
6124
|
import_Tabs2 = __toESM(require("@mui/material/Tabs"));
|
|
6071
6125
|
import_Tab2 = __toESM(require("@mui/material/Tab"));
|
|
6072
|
-
|
|
6126
|
+
import_Grid3 = __toESM(require("@mui/material/Grid"));
|
|
6073
6127
|
import_Accordion2 = __toESM(require("@mui/material/Accordion"));
|
|
6074
6128
|
import_AccordionSummary2 = __toESM(require("@mui/material/AccordionSummary"));
|
|
6075
6129
|
import_AccordionDetails2 = __toESM(require("@mui/material/AccordionDetails"));
|
|
@@ -6087,7 +6141,7 @@ var init_LiteLLMPage = __esm({
|
|
|
6087
6141
|
init_LiteLLMBudgetWidget();
|
|
6088
6142
|
init_TeamUsage();
|
|
6089
6143
|
init_ModelsTable();
|
|
6090
|
-
|
|
6144
|
+
init_InsightsTab();
|
|
6091
6145
|
init_api();
|
|
6092
6146
|
init_dates();
|
|
6093
6147
|
import_backstage_plugin_litellm_common5 = require("@acarmisc/backstage-plugin-litellm-common");
|
|
@@ -6096,7 +6150,7 @@ var init_LiteLLMPage = __esm({
|
|
|
6096
6150
|
init_unprovisionedCopy();
|
|
6097
6151
|
import_jsx_runtime18 = require("react/jsx-runtime");
|
|
6098
6152
|
PERIOD_LS_KEY2 = "litellm_usage_period";
|
|
6099
|
-
PAGE_TABS = ["overview", "keys", "teams", "models", "
|
|
6153
|
+
PAGE_TABS = ["overview", "keys", "teams", "models", "insights"];
|
|
6100
6154
|
isPageTab = (v) => !!v && PAGE_TABS.includes(v);
|
|
6101
6155
|
LiteLLMPage = () => {
|
|
6102
6156
|
const api = (0, import_core_plugin_api5.useApi)(liteLlmApiRef);
|
|
@@ -6437,7 +6491,7 @@ var init_LiteLLMPage = __esm({
|
|
|
6437
6491
|
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_Tab2.default, { label: "Keys", value: "keys" }),
|
|
6438
6492
|
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_Tab2.default, { label: "Teams", value: "teams" }),
|
|
6439
6493
|
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_Tab2.default, { label: "Models", value: "models" }),
|
|
6440
|
-
|
|
6494
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_Tab2.default, { label: "Insights", value: "insights" })
|
|
6441
6495
|
]
|
|
6442
6496
|
}
|
|
6443
6497
|
);
|
|
@@ -6491,7 +6545,7 @@ var init_LiteLLMPage = __esm({
|
|
|
6491
6545
|
}
|
|
6492
6546
|
),
|
|
6493
6547
|
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
6494
|
-
|
|
6548
|
+
import_Button8.default,
|
|
6495
6549
|
{
|
|
6496
6550
|
variant: "contained",
|
|
6497
6551
|
color: "primary",
|
|
@@ -6503,8 +6557,8 @@ var init_LiteLLMPage = __esm({
|
|
|
6503
6557
|
}
|
|
6504
6558
|
) });
|
|
6505
6559
|
}
|
|
6506
|
-
return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
|
|
6507
|
-
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
6560
|
+
return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_Grid3.default, { container: true, spacing: 2, alignItems: "flex-start", children: [
|
|
6561
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_Grid3.default, { item: true, xs: 12, lg: 8, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
6508
6562
|
UsageStats,
|
|
6509
6563
|
{
|
|
6510
6564
|
usage: usage ?? null,
|
|
@@ -6518,7 +6572,7 @@ var init_LiteLLMPage = __esm({
|
|
|
6518
6572
|
userInfo
|
|
6519
6573
|
}
|
|
6520
6574
|
) }),
|
|
6521
|
-
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
6575
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_Grid3.default, { item: true, xs: 12, lg: 4, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(LiteLLMBudgetWidget, { compact: true, collapsible: true, userInfo: userInfo ?? null, teams, keys: keys ?? [] }) })
|
|
6522
6576
|
] });
|
|
6523
6577
|
})(),
|
|
6524
6578
|
activeTab === "keys" && (() => {
|
|
@@ -6526,7 +6580,7 @@ var init_LiteLLMPage = __esm({
|
|
|
6526
6580
|
return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_Alert6.default, { severity: "error", children: [
|
|
6527
6581
|
"Failed to load keys: ",
|
|
6528
6582
|
keysError.message || "Unknown error",
|
|
6529
|
-
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_Box17.default, { sx: { mt: 1 }, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
6583
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_Box17.default, { sx: { mt: 1 }, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_Button8.default, { size: "small", onClick: refreshKeys, children: "Retry" }) })
|
|
6530
6584
|
] });
|
|
6531
6585
|
}
|
|
6532
6586
|
return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
@@ -6550,7 +6604,7 @@ var init_LiteLLMPage = __esm({
|
|
|
6550
6604
|
return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_Alert6.default, { severity: "error", children: [
|
|
6551
6605
|
"Failed to load teams: ",
|
|
6552
6606
|
teamsError.message || "Unknown error",
|
|
6553
|
-
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_Box17.default, { sx: { mt: 1 }, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
6607
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_Box17.default, { sx: { mt: 1 }, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_Button8.default, { size: "small", onClick: refreshTeams, children: "Retry" }) })
|
|
6554
6608
|
] });
|
|
6555
6609
|
}
|
|
6556
6610
|
const teamsById = /* @__PURE__ */ new Map();
|
|
@@ -6587,7 +6641,7 @@ var init_LiteLLMPage = __esm({
|
|
|
6587
6641
|
return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_Alert6.default, { severity: "error", children: [
|
|
6588
6642
|
"Failed to load models: ",
|
|
6589
6643
|
modelsError.message || "Unknown error",
|
|
6590
|
-
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_Box17.default, { sx: { mt: 1 }, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
6644
|
+
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_Box17.default, { sx: { mt: 1 }, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_Button8.default, { size: "small", onClick: refreshModels, children: "Retry" }) })
|
|
6591
6645
|
] });
|
|
6592
6646
|
}
|
|
6593
6647
|
return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
@@ -6599,7 +6653,17 @@ var init_LiteLLMPage = __esm({
|
|
|
6599
6653
|
}
|
|
6600
6654
|
);
|
|
6601
6655
|
})(),
|
|
6602
|
-
activeTab === "
|
|
6656
|
+
activeTab === "insights" && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
6657
|
+
InsightsTab,
|
|
6658
|
+
{
|
|
6659
|
+
api,
|
|
6660
|
+
usage: usage ?? null,
|
|
6661
|
+
models: allModels ?? [],
|
|
6662
|
+
keys: keys ?? [],
|
|
6663
|
+
dateRange,
|
|
6664
|
+
loading: usageLoading
|
|
6665
|
+
}
|
|
6666
|
+
)
|
|
6603
6667
|
] }),
|
|
6604
6668
|
/* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
|
|
6605
6669
|
KeyFormDialog,
|
|
@@ -6773,14 +6837,14 @@ var ConnectPage_exports = {};
|
|
|
6773
6837
|
__export(ConnectPage_exports, {
|
|
6774
6838
|
ConnectPage: () => ConnectPage
|
|
6775
6839
|
});
|
|
6776
|
-
var import_react14, import_Alert7, import_Box18,
|
|
6840
|
+
var import_react14, import_Alert7, import_Box18, import_Button9, import_Checkbox3, import_FormControlLabel3, import_FormGroup, import_Paper9, import_Typography16, import_react_router_dom2, import_react_use3, import_core_plugin_api6, import_core_components4, import_jsx_runtime19, ConnectPage;
|
|
6777
6841
|
var init_ConnectPage = __esm({
|
|
6778
6842
|
"src/components/ConnectPage.tsx"() {
|
|
6779
6843
|
"use strict";
|
|
6780
6844
|
import_react14 = require("react");
|
|
6781
6845
|
import_Alert7 = __toESM(require("@mui/material/Alert"));
|
|
6782
6846
|
import_Box18 = __toESM(require("@mui/material/Box"));
|
|
6783
|
-
|
|
6847
|
+
import_Button9 = __toESM(require("@mui/material/Button"));
|
|
6784
6848
|
import_Checkbox3 = __toESM(require("@mui/material/Checkbox"));
|
|
6785
6849
|
import_FormControlLabel3 = __toESM(require("@mui/material/FormControlLabel"));
|
|
6786
6850
|
import_FormGroup = __toESM(require("@mui/material/FormGroup"));
|
|
@@ -6851,7 +6915,7 @@ var init_ConnectPage = __esm({
|
|
|
6851
6915
|
)) }),
|
|
6852
6916
|
error && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_Alert7.default, { severity: "error", sx: { mb: 2 }, children: error }),
|
|
6853
6917
|
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
6854
|
-
|
|
6918
|
+
import_Button9.default,
|
|
6855
6919
|
{
|
|
6856
6920
|
variant: "contained",
|
|
6857
6921
|
onClick: connect,
|
|
@@ -6891,7 +6955,7 @@ __export(index_exports, {
|
|
|
6891
6955
|
module.exports = __toCommonJS(index_exports);
|
|
6892
6956
|
|
|
6893
6957
|
// src/plugin.tsx
|
|
6894
|
-
var
|
|
6958
|
+
var import_icons_material9 = require("@mui/icons-material");
|
|
6895
6959
|
var import_frontend_plugin_api4 = require("@backstage/frontend-plugin-api");
|
|
6896
6960
|
init_api();
|
|
6897
6961
|
init_routes();
|
|
@@ -6907,7 +6971,7 @@ var liteLlmPage = import_frontend_plugin_api4.PageBlueprint.make({
|
|
|
6907
6971
|
params: {
|
|
6908
6972
|
path: "/litellm",
|
|
6909
6973
|
title: "LiteLLM",
|
|
6910
|
-
icon: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
|
|
6974
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_icons_material9.TrendingUp, {}),
|
|
6911
6975
|
routeRef: rootRouteRef,
|
|
6912
6976
|
loader: async () => {
|
|
6913
6977
|
const { LiteLLMPage: LiteLLMPage2 } = await Promise.resolve().then(() => (init_LiteLLMPage(), LiteLLMPage_exports));
|
|
@@ -6947,7 +7011,7 @@ var import_Box19 = __toESM(require("@mui/material/Box"));
|
|
|
6947
7011
|
var import_Typography17 = __toESM(require("@mui/material/Typography"));
|
|
6948
7012
|
var import_FormControl2 = __toESM(require("@mui/material/FormControl"));
|
|
6949
7013
|
var import_Select2 = __toESM(require("@mui/material/Select"));
|
|
6950
|
-
var
|
|
7014
|
+
var import_MenuItem5 = __toESM(require("@mui/material/MenuItem"));
|
|
6951
7015
|
var import_Skeleton7 = __toESM(require("@mui/material/Skeleton"));
|
|
6952
7016
|
var import_recharts3 = require("recharts");
|
|
6953
7017
|
var import_core_plugin_api7 = require("@backstage/core-plugin-api");
|
|
@@ -7084,9 +7148,9 @@ var LiteLLMHomeWidget = ({
|
|
|
7084
7148
|
displayEmpty: true,
|
|
7085
7149
|
inputProps: { "aria-label": "Usage period" },
|
|
7086
7150
|
children: [
|
|
7087
|
-
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
7088
|
-
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
7089
|
-
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
7151
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_MenuItem5.default, { value: "today", children: "Today" }),
|
|
7152
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_MenuItem5.default, { value: "7d", children: "7d" }),
|
|
7153
|
+
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(import_MenuItem5.default, { value: "30d", children: "30d" })
|
|
7090
7154
|
]
|
|
7091
7155
|
}
|
|
7092
7156
|
) })
|
|
@@ -7157,13 +7221,13 @@ var import_react16 = require("react");
|
|
|
7157
7221
|
var import_frontend_plugin_api6 = require("@backstage/frontend-plugin-api");
|
|
7158
7222
|
var import_Paper11 = __toESM(require("@mui/material/Paper"));
|
|
7159
7223
|
var import_Box20 = __toESM(require("@mui/material/Box"));
|
|
7160
|
-
var
|
|
7224
|
+
var import_Button10 = __toESM(require("@mui/material/Button"));
|
|
7161
7225
|
var import_Typography18 = __toESM(require("@mui/material/Typography"));
|
|
7162
7226
|
var import_Skeleton8 = __toESM(require("@mui/material/Skeleton"));
|
|
7163
|
-
var
|
|
7164
|
-
var
|
|
7227
|
+
var import_Collapse3 = __toESM(require("@mui/material/Collapse"));
|
|
7228
|
+
var import_styles12 = require("@mui/material/styles");
|
|
7165
7229
|
var import_useMediaQuery = __toESM(require("@mui/material/useMediaQuery"));
|
|
7166
|
-
var
|
|
7230
|
+
var import_icons_material10 = require("@mui/icons-material");
|
|
7167
7231
|
var import_recharts4 = require("recharts");
|
|
7168
7232
|
var import_core_plugin_api8 = require("@backstage/core-plugin-api");
|
|
7169
7233
|
var import_core_components6 = require("@backstage/core-components");
|
|
@@ -7574,7 +7638,7 @@ var LiteLLMBudgetGauges = ({
|
|
|
7574
7638
|
keys: propKeys
|
|
7575
7639
|
}) => {
|
|
7576
7640
|
const api = (0, import_core_plugin_api8.useApi)(liteLlmApiRef);
|
|
7577
|
-
const theme = (0,
|
|
7641
|
+
const theme = (0, import_styles12.useTheme)();
|
|
7578
7642
|
const isSmallScreen = (0, import_useMediaQuery.default)(theme.breakpoints.down("sm"));
|
|
7579
7643
|
const gaugeSize = isSmallScreen ? Math.min(48, size) : size;
|
|
7580
7644
|
const expandedRegionId = (0, import_react16.useId)();
|
|
@@ -7675,10 +7739,10 @@ var LiteLLMBudgetGauges = ({
|
|
|
7675
7739
|
return onCreateKey ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(GenerateKeyButton, { size: "small", label, onClick: onCreateKey }, key) : /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(GenerateKeyButton, { size: "small", label, to: `${moduleHref}?generate=1` }, key);
|
|
7676
7740
|
case "module":
|
|
7677
7741
|
if (!moduleHref) return null;
|
|
7678
|
-
return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
7742
|
+
return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_Button10.default, { size: "small", variant: "text", component: import_core_components6.Link, to: moduleHref, children: label }, key);
|
|
7679
7743
|
case "all-limits":
|
|
7680
7744
|
return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
7681
|
-
|
|
7745
|
+
import_Button10.default,
|
|
7682
7746
|
{
|
|
7683
7747
|
size: "small",
|
|
7684
7748
|
variant: "text",
|
|
@@ -7686,7 +7750,7 @@ var LiteLLMBudgetGauges = ({
|
|
|
7686
7750
|
"aria-expanded": isExpanded,
|
|
7687
7751
|
"aria-controls": expandedRegionId,
|
|
7688
7752
|
endIcon: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
7689
|
-
|
|
7753
|
+
import_icons_material10.ExpandMore,
|
|
7690
7754
|
{
|
|
7691
7755
|
sx: (muiTheme) => ({
|
|
7692
7756
|
transform: isExpanded ? "rotate(180deg)" : "none",
|
|
@@ -7781,7 +7845,7 @@ var LiteLLMBudgetGauges = ({
|
|
|
7781
7845
|
maxBudget: user?.max_budget ?? void 0
|
|
7782
7846
|
}
|
|
7783
7847
|
) }),
|
|
7784
|
-
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
7848
|
+
/* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_Collapse3.default, { in: isExpanded, unmountOnExit: true, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
7785
7849
|
import_Box20.default,
|
|
7786
7850
|
{
|
|
7787
7851
|
id: expandedRegionId,
|