@acarmisc/backstage-plugin-litellm 0.36.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/connect.d.ts +2 -0
- package/dist/index.cjs.js +363 -294
- package/dist/index.cjs.js.map +4 -4
- package/dist/index.esm.js +401 -332
- 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,
|
|
@@ -6758,6 +6822,10 @@ function postToLoopback(redirectUri, fields) {
|
|
|
6758
6822
|
document.body.appendChild(form);
|
|
6759
6823
|
form.submit();
|
|
6760
6824
|
}
|
|
6825
|
+
function initialTeams(all, have) {
|
|
6826
|
+
const missing = all.filter((id) => !have.has(id));
|
|
6827
|
+
return new Set(have.size ? missing : all);
|
|
6828
|
+
}
|
|
6761
6829
|
var init_connect = __esm({
|
|
6762
6830
|
"src/connect.ts"() {
|
|
6763
6831
|
"use strict";
|
|
@@ -6769,14 +6837,14 @@ var ConnectPage_exports = {};
|
|
|
6769
6837
|
__export(ConnectPage_exports, {
|
|
6770
6838
|
ConnectPage: () => ConnectPage
|
|
6771
6839
|
});
|
|
6772
|
-
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;
|
|
6773
6841
|
var init_ConnectPage = __esm({
|
|
6774
6842
|
"src/components/ConnectPage.tsx"() {
|
|
6775
6843
|
"use strict";
|
|
6776
6844
|
import_react14 = require("react");
|
|
6777
6845
|
import_Alert7 = __toESM(require("@mui/material/Alert"));
|
|
6778
6846
|
import_Box18 = __toESM(require("@mui/material/Box"));
|
|
6779
|
-
|
|
6847
|
+
import_Button9 = __toESM(require("@mui/material/Button"));
|
|
6780
6848
|
import_Checkbox3 = __toESM(require("@mui/material/Checkbox"));
|
|
6781
6849
|
import_FormControlLabel3 = __toESM(require("@mui/material/FormControlLabel"));
|
|
6782
6850
|
import_FormGroup = __toESM(require("@mui/material/FormGroup"));
|
|
@@ -6795,6 +6863,7 @@ var init_ConnectPage = __esm({
|
|
|
6795
6863
|
const redirectUri = params.get("redirect_uri");
|
|
6796
6864
|
const state = params.get("state") ?? "";
|
|
6797
6865
|
const client = params.get("client") || "a local application";
|
|
6866
|
+
const have = new Set((params.get("have") ?? "").split(",").filter(Boolean));
|
|
6798
6867
|
const teams = (0, import_react_use3.useAsync)(() => api.getTeams(), [api]);
|
|
6799
6868
|
const [selected, setSelected] = (0, import_react14.useState)();
|
|
6800
6869
|
const [busy, setBusy] = (0, import_react14.useState)(false);
|
|
@@ -6804,7 +6873,7 @@ var init_ConnectPage = __esm({
|
|
|
6804
6873
|
}
|
|
6805
6874
|
if (teams.loading) return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_core_components4.Progress, {});
|
|
6806
6875
|
const all = teams.value ?? [];
|
|
6807
|
-
const chosen = selected ??
|
|
6876
|
+
const chosen = selected ?? initialTeams(all.map((t) => t.team_id), have);
|
|
6808
6877
|
const toggle = (id) => {
|
|
6809
6878
|
const next = new Set(chosen);
|
|
6810
6879
|
if (!next.delete(id)) next.add(id);
|
|
@@ -6830,7 +6899,7 @@ var init_ConnectPage = __esm({
|
|
|
6830
6899
|
] }),
|
|
6831
6900
|
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_Typography16.default, { variant: "body2", color: "text.secondary", paragraph: true, children: [
|
|
6832
6901
|
client,
|
|
6833
|
-
" will receive an API key for each selected team.
|
|
6902
|
+
" will receive an API key for each selected team. Selecting a team that is already connected replaces its key, also on your other devices."
|
|
6834
6903
|
] }),
|
|
6835
6904
|
teams.error && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_Alert7.default, { severity: "error", children: [
|
|
6836
6905
|
"Could not load your teams: ",
|
|
@@ -6840,13 +6909,13 @@ var init_ConnectPage = __esm({
|
|
|
6840
6909
|
import_FormControlLabel3.default,
|
|
6841
6910
|
{
|
|
6842
6911
|
control: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_Checkbox3.default, { checked: chosen.has(t.team_id), onChange: () => toggle(t.team_id) }),
|
|
6843
|
-
label: t.team_alias ?? t.team_id
|
|
6912
|
+
label: `${t.team_alias ?? t.team_id}${have.has(t.team_id) ? " (already connected)" : ""}`
|
|
6844
6913
|
},
|
|
6845
6914
|
t.team_id
|
|
6846
6915
|
)) }),
|
|
6847
6916
|
error && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_Alert7.default, { severity: "error", sx: { mb: 2 }, children: error }),
|
|
6848
6917
|
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
6849
|
-
|
|
6918
|
+
import_Button9.default,
|
|
6850
6919
|
{
|
|
6851
6920
|
variant: "contained",
|
|
6852
6921
|
onClick: connect,
|
|
@@ -6886,7 +6955,7 @@ __export(index_exports, {
|
|
|
6886
6955
|
module.exports = __toCommonJS(index_exports);
|
|
6887
6956
|
|
|
6888
6957
|
// src/plugin.tsx
|
|
6889
|
-
var
|
|
6958
|
+
var import_icons_material9 = require("@mui/icons-material");
|
|
6890
6959
|
var import_frontend_plugin_api4 = require("@backstage/frontend-plugin-api");
|
|
6891
6960
|
init_api();
|
|
6892
6961
|
init_routes();
|
|
@@ -6902,7 +6971,7 @@ var liteLlmPage = import_frontend_plugin_api4.PageBlueprint.make({
|
|
|
6902
6971
|
params: {
|
|
6903
6972
|
path: "/litellm",
|
|
6904
6973
|
title: "LiteLLM",
|
|
6905
|
-
icon: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
|
|
6974
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(import_icons_material9.TrendingUp, {}),
|
|
6906
6975
|
routeRef: rootRouteRef,
|
|
6907
6976
|
loader: async () => {
|
|
6908
6977
|
const { LiteLLMPage: LiteLLMPage2 } = await Promise.resolve().then(() => (init_LiteLLMPage(), LiteLLMPage_exports));
|
|
@@ -6942,7 +7011,7 @@ var import_Box19 = __toESM(require("@mui/material/Box"));
|
|
|
6942
7011
|
var import_Typography17 = __toESM(require("@mui/material/Typography"));
|
|
6943
7012
|
var import_FormControl2 = __toESM(require("@mui/material/FormControl"));
|
|
6944
7013
|
var import_Select2 = __toESM(require("@mui/material/Select"));
|
|
6945
|
-
var
|
|
7014
|
+
var import_MenuItem5 = __toESM(require("@mui/material/MenuItem"));
|
|
6946
7015
|
var import_Skeleton7 = __toESM(require("@mui/material/Skeleton"));
|
|
6947
7016
|
var import_recharts3 = require("recharts");
|
|
6948
7017
|
var import_core_plugin_api7 = require("@backstage/core-plugin-api");
|
|
@@ -7079,9 +7148,9 @@ var LiteLLMHomeWidget = ({
|
|
|
7079
7148
|
displayEmpty: true,
|
|
7080
7149
|
inputProps: { "aria-label": "Usage period" },
|
|
7081
7150
|
children: [
|
|
7082
|
-
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
7083
|
-
/* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
|
|
7084
|
-
/* @__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" })
|
|
7085
7154
|
]
|
|
7086
7155
|
}
|
|
7087
7156
|
) })
|
|
@@ -7152,13 +7221,13 @@ var import_react16 = require("react");
|
|
|
7152
7221
|
var import_frontend_plugin_api6 = require("@backstage/frontend-plugin-api");
|
|
7153
7222
|
var import_Paper11 = __toESM(require("@mui/material/Paper"));
|
|
7154
7223
|
var import_Box20 = __toESM(require("@mui/material/Box"));
|
|
7155
|
-
var
|
|
7224
|
+
var import_Button10 = __toESM(require("@mui/material/Button"));
|
|
7156
7225
|
var import_Typography18 = __toESM(require("@mui/material/Typography"));
|
|
7157
7226
|
var import_Skeleton8 = __toESM(require("@mui/material/Skeleton"));
|
|
7158
|
-
var
|
|
7159
|
-
var
|
|
7227
|
+
var import_Collapse3 = __toESM(require("@mui/material/Collapse"));
|
|
7228
|
+
var import_styles12 = require("@mui/material/styles");
|
|
7160
7229
|
var import_useMediaQuery = __toESM(require("@mui/material/useMediaQuery"));
|
|
7161
|
-
var
|
|
7230
|
+
var import_icons_material10 = require("@mui/icons-material");
|
|
7162
7231
|
var import_recharts4 = require("recharts");
|
|
7163
7232
|
var import_core_plugin_api8 = require("@backstage/core-plugin-api");
|
|
7164
7233
|
var import_core_components6 = require("@backstage/core-components");
|
|
@@ -7569,7 +7638,7 @@ var LiteLLMBudgetGauges = ({
|
|
|
7569
7638
|
keys: propKeys
|
|
7570
7639
|
}) => {
|
|
7571
7640
|
const api = (0, import_core_plugin_api8.useApi)(liteLlmApiRef);
|
|
7572
|
-
const theme = (0,
|
|
7641
|
+
const theme = (0, import_styles12.useTheme)();
|
|
7573
7642
|
const isSmallScreen = (0, import_useMediaQuery.default)(theme.breakpoints.down("sm"));
|
|
7574
7643
|
const gaugeSize = isSmallScreen ? Math.min(48, size) : size;
|
|
7575
7644
|
const expandedRegionId = (0, import_react16.useId)();
|
|
@@ -7670,10 +7739,10 @@ var LiteLLMBudgetGauges = ({
|
|
|
7670
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);
|
|
7671
7740
|
case "module":
|
|
7672
7741
|
if (!moduleHref) return null;
|
|
7673
|
-
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);
|
|
7674
7743
|
case "all-limits":
|
|
7675
7744
|
return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
7676
|
-
|
|
7745
|
+
import_Button10.default,
|
|
7677
7746
|
{
|
|
7678
7747
|
size: "small",
|
|
7679
7748
|
variant: "text",
|
|
@@ -7681,7 +7750,7 @@ var LiteLLMBudgetGauges = ({
|
|
|
7681
7750
|
"aria-expanded": isExpanded,
|
|
7682
7751
|
"aria-controls": expandedRegionId,
|
|
7683
7752
|
endIcon: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
|
|
7684
|
-
|
|
7753
|
+
import_icons_material10.ExpandMore,
|
|
7685
7754
|
{
|
|
7686
7755
|
sx: (muiTheme) => ({
|
|
7687
7756
|
transform: isExpanded ? "rotate(180deg)" : "none",
|
|
@@ -7776,7 +7845,7 @@ var LiteLLMBudgetGauges = ({
|
|
|
7776
7845
|
maxBudget: user?.max_budget ?? void 0
|
|
7777
7846
|
}
|
|
7778
7847
|
) }),
|
|
7779
|
-
/* @__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)(
|
|
7780
7849
|
import_Box20.default,
|
|
7781
7850
|
{
|
|
7782
7851
|
id: expandedRegionId,
|