@acarmisc/backstage-plugin-litellm 0.38.0 → 0.39.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 +4 -1
- package/dist/components/ui/surfaces.d.ts +2 -0
- package/dist/index.cjs.js +159 -57
- package/dist/index.cjs.js.map +2 -2
- package/dist/index.esm.js +159 -57
- package/dist/index.esm.js.map +2 -2
- package/dist/types.d.ts +1 -1
- package/package.json +2 -2
package/dist/api.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type FetchApi, type DiscoveryApi } from '@backstage/frontend-plugin-api';
|
|
2
|
-
import { UserInfo, VirtualKey, ModelInfo, UsageMetrics, TeamMemberUsage, TeamInfo, GenerateKeyRequest, GenerateKeyResponse, UpdateKeyRequest, AuditLogsParams, PaginatedAuditLogs, SpendLogsParams, PaginatedSpendLogs, LiteLlmConfig, VectorStoreInfo, McpServerInfo, CreateTeamRequest, CreateTeamResponse, UpdateTeamRequest } from './types';
|
|
2
|
+
import { UserInfo, VirtualKey, ModelInfo, UsageMetrics, TeamMemberUsage, TeamInfo, GenerateKeyRequest, GenerateKeyResponse, UpdateKeyRequest, AuditLogsParams, PaginatedAuditLogs, SpendLogsParams, PaginatedSpendLogs, LiteLlmConfig, QualityResponse, VectorStoreInfo, McpServerInfo, CreateTeamRequest, CreateTeamResponse, UpdateTeamRequest } from './types';
|
|
3
3
|
export declare class ApiError extends Error {
|
|
4
4
|
body: unknown;
|
|
5
5
|
status: number;
|
|
@@ -46,6 +46,8 @@ export interface LiteLlmApiInterface {
|
|
|
46
46
|
getTeamMemberUsage(teamId: string, startDate: string, endDate: string): Promise<TeamMemberUsage>;
|
|
47
47
|
getAuditLogs(params: AuditLogsParams): Promise<PaginatedAuditLogs>;
|
|
48
48
|
getSpendLogs(params: SpendLogsParams): Promise<PaginatedSpendLogs>;
|
|
49
|
+
/** Langfuse quality summary; `{configured:false}` when Langfuse is off. */
|
|
50
|
+
getQuality(params: SpendLogsParams): Promise<QualityResponse>;
|
|
49
51
|
getConfig(): Promise<LiteLlmConfig>;
|
|
50
52
|
createTeam(request: CreateTeamRequest): Promise<CreateTeamResponse>;
|
|
51
53
|
updateTeam(teamId: string, request: UpdateTeamRequest): Promise<TeamInfo>;
|
|
@@ -110,6 +112,7 @@ export declare class LiteLlmApi implements LiteLlmApiInterface {
|
|
|
110
112
|
getTeamUsage(teamId: string, startDate: string, endDate: string): Promise<UsageMetrics>;
|
|
111
113
|
getTeamMemberUsage(teamId: string, startDate: string, endDate: string): Promise<TeamMemberUsage>;
|
|
112
114
|
getSpendLogs(params: SpendLogsParams): Promise<PaginatedSpendLogs>;
|
|
115
|
+
getQuality(params: SpendLogsParams): Promise<QualityResponse>;
|
|
113
116
|
getConfig(): Promise<LiteLlmConfig>;
|
|
114
117
|
createTeam(request: CreateTeamRequest): Promise<CreateTeamResponse>;
|
|
115
118
|
updateTeam(teamId: string, request: UpdateTeamRequest): Promise<TeamInfo>;
|
|
@@ -5,6 +5,8 @@ import { type Tone } from './tokens';
|
|
|
5
5
|
export declare const SectionCard: FC<{
|
|
6
6
|
title?: ReactNode;
|
|
7
7
|
subtitle?: ReactNode;
|
|
8
|
+
/** One-line explainer under the title row. */
|
|
9
|
+
description?: ReactNode;
|
|
8
10
|
actions?: ReactNode;
|
|
9
11
|
/** Set when the body renders its own edge-to-edge content (e.g. a table). */
|
|
10
12
|
flush?: boolean;
|
package/dist/index.cjs.js
CHANGED
|
@@ -328,6 +328,14 @@ var init_api = __esm({
|
|
|
328
328
|
if (params.pageSize !== void 0) strParams.page_size = String(params.pageSize);
|
|
329
329
|
return this.get("/insights/spend-logs", strParams);
|
|
330
330
|
}
|
|
331
|
+
async getQuality(params) {
|
|
332
|
+
const strParams = {
|
|
333
|
+
start_date: params.startDate,
|
|
334
|
+
end_date: params.endDate
|
|
335
|
+
};
|
|
336
|
+
if (params.userId) strParams.userId = params.userId;
|
|
337
|
+
return this.get("/insights/quality", strParams);
|
|
338
|
+
}
|
|
331
339
|
async getConfig() {
|
|
332
340
|
return this.get("/config");
|
|
333
341
|
}
|
|
@@ -726,7 +734,7 @@ var init_surfaces = __esm({
|
|
|
726
734
|
import_styles3 = require("@mui/material/styles");
|
|
727
735
|
init_tokens();
|
|
728
736
|
import_jsx_runtime3 = require("react/jsx-runtime");
|
|
729
|
-
SectionCard = ({ title, subtitle, actions, flush = false, children }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_Paper2.default, { sx: { borderRadius: 2, overflow: "hidden" }, children: [
|
|
737
|
+
SectionCard = ({ title, subtitle, description, actions, flush = false, children }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_Paper2.default, { sx: { borderRadius: 2, overflow: "hidden" }, children: [
|
|
730
738
|
(title || actions) && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
731
739
|
import_Box3.default,
|
|
732
740
|
{
|
|
@@ -748,6 +756,7 @@ var init_surfaces = __esm({
|
|
|
748
756
|
]
|
|
749
757
|
}
|
|
750
758
|
),
|
|
759
|
+
description && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_Typography2.default, { variant: "body2", color: "text.secondary", sx: { px: 2.5, pb: 1.5 }, children: description }),
|
|
751
760
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_Box3.default, { sx: flush ? void 0 : { px: 2.5, pb: 2.5, pt: title ? 0 : 2.5 }, children })
|
|
752
761
|
] });
|
|
753
762
|
ChartCard = ({ title, meta, height = 240, children }) => (
|
|
@@ -5679,7 +5688,7 @@ var init_insights = __esm({
|
|
|
5679
5688
|
"use strict";
|
|
5680
5689
|
costPer1k = (model, spend, tokens, models) => {
|
|
5681
5690
|
const info = models?.find((m) => m.model_name === model);
|
|
5682
|
-
const listed = info?.input_cost_per_token
|
|
5691
|
+
const listed = info?.input_cost_per_token !== void 0 && info?.input_cost_per_token !== null && info?.output_cost_per_token !== void 0 && info?.output_cost_per_token !== null ? (info.input_cost_per_token + info.output_cost_per_token) / 2 * 1e3 : null;
|
|
5683
5692
|
if (listed !== null) return listed;
|
|
5684
5693
|
return tokens > 0 ? spend / tokens * 1e3 : null;
|
|
5685
5694
|
};
|
|
@@ -5730,11 +5739,17 @@ var init_insights = __esm({
|
|
|
5730
5739
|
};
|
|
5731
5740
|
};
|
|
5732
5741
|
scorePractices = (summary, keyStats) => {
|
|
5733
|
-
const
|
|
5734
|
-
|
|
5735
|
-
|
|
5742
|
+
const band = (v, limits) => {
|
|
5743
|
+
for (const [max, pts] of limits) {
|
|
5744
|
+
if (v <= max) return pts;
|
|
5745
|
+
}
|
|
5746
|
+
return 0;
|
|
5747
|
+
};
|
|
5748
|
+
const waste = band(summary.retryWasteShare, [[0.01, 30], [0.05, 20], [0.1, 10]]);
|
|
5749
|
+
const premium = band(summary.premiumShare, [[0.2, 25], [0.5, 15], [0.8, 5]]);
|
|
5750
|
+
const conc = band(summary.topModelConcentration, [[0.5, 20], [0.8, 10]]);
|
|
5736
5751
|
const bad = keyStats.totalKeys > 0 ? (keyStats.idleKeys + keyStats.uncappedKeys) / keyStats.totalKeys : 0;
|
|
5737
|
-
const hygiene = keyStats.totalKeys === 0 ? 25 : bad
|
|
5752
|
+
const hygiene = keyStats.totalKeys === 0 ? 25 : band(bad, [[0, 25], [0.2, 15], [0.5, 5]]);
|
|
5738
5753
|
const checks = [
|
|
5739
5754
|
{ id: "retry-waste", label: "Low retry waste", points: waste, maxPoints: 30, detail: `${(summary.retryWasteShare * 100).toFixed(1)}% of requests failed` },
|
|
5740
5755
|
{ id: "premium-mix", label: "Balanced model mix", points: premium, maxPoints: 25, detail: `${(summary.premiumShare * 100).toFixed(1)}% of spend on premium models` },
|
|
@@ -5861,6 +5876,67 @@ var init_InsightsTab = __esm({
|
|
|
5861
5876
|
pageSize: 1e3
|
|
5862
5877
|
});
|
|
5863
5878
|
}, [api, dateRange]);
|
|
5879
|
+
const {
|
|
5880
|
+
value: quality,
|
|
5881
|
+
loading: qualityLoading,
|
|
5882
|
+
error: qualityError,
|
|
5883
|
+
retry: retryQuality
|
|
5884
|
+
} = (0, import_react_use.useAsyncRetry)(async () => {
|
|
5885
|
+
if (typeof api.getQuality !== "function") return { configured: false };
|
|
5886
|
+
return api.getQuality({
|
|
5887
|
+
startDate: toLocalDay(dateRange.start),
|
|
5888
|
+
endDate: toLocalDay(dateRange.end)
|
|
5889
|
+
});
|
|
5890
|
+
}, [api, dateRange]);
|
|
5891
|
+
const renderQualityBody = () => {
|
|
5892
|
+
if (qualityLoading) return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Skeleton6.default, { variant: "rounded", height: 120 });
|
|
5893
|
+
if (qualityError) {
|
|
5894
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_Alert5.default, { severity: "error", children: [
|
|
5895
|
+
"Failed to load quality scores: ",
|
|
5896
|
+
qualityError.message || "Unknown error",
|
|
5897
|
+
/* @__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: retryQuality, children: "Retry" }) })
|
|
5898
|
+
] });
|
|
5899
|
+
}
|
|
5900
|
+
if (!quality || quality.configured === false) {
|
|
5901
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
5902
|
+
EmptyState,
|
|
5903
|
+
{
|
|
5904
|
+
message: "Quality scores \u2014 connect Langfuse",
|
|
5905
|
+
hint: "LLM-as-judge evaluation is not configured for this deployment."
|
|
5906
|
+
}
|
|
5907
|
+
);
|
|
5908
|
+
}
|
|
5909
|
+
if (quality.byModel.length === 0 && quality.scores.length === 0) {
|
|
5910
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(EmptyState, { message: "No quality data in this period" });
|
|
5911
|
+
}
|
|
5912
|
+
return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_Box16.default, { sx: { display: "flex", flexDirection: "column", gap: 2 }, children: [
|
|
5913
|
+
quality.byModel.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Box16.default, { sx: { display: "flex", flexDirection: "column", gap: 1.5 }, children: quality.byModel.map((m) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Box16.default, { children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_Box16.default, { sx: { display: "flex", justifyContent: "space-between", gap: 1, mb: 0.5 }, children: [
|
|
5914
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Typography14.default, { variant: "body2", sx: { fontWeight: 600 }, children: m.model }),
|
|
5915
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_Typography14.default, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" }, children: [
|
|
5916
|
+
fmtUsd(m.cost),
|
|
5917
|
+
" \xB7 ",
|
|
5918
|
+
fmtInt(m.tokens),
|
|
5919
|
+
" tokens"
|
|
5920
|
+
] })
|
|
5921
|
+
] }) }, m.model)) }),
|
|
5922
|
+
quality.scores.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Box16.default, { sx: { display: "flex", flexDirection: "column", gap: 1.5 }, children: quality.scores.map((s) => /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_Box16.default, { children: [
|
|
5923
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_Box16.default, { sx: { display: "flex", justifyContent: "space-between", gap: 1, mb: 0.5 }, children: [
|
|
5924
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Typography14.default, { variant: "body2", sx: { fontWeight: 600 }, children: s.name }),
|
|
5925
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_Typography14.default, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" }, children: [
|
|
5926
|
+
s.avg.toFixed(2),
|
|
5927
|
+
s.count > 0 ? ` \xB7 n=${fmtInt(s.count)}` : ""
|
|
5928
|
+
] })
|
|
5929
|
+
] }),
|
|
5930
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
5931
|
+
Meter,
|
|
5932
|
+
{
|
|
5933
|
+
value: s.avg <= 1 ? s.avg * 100 : s.avg,
|
|
5934
|
+
ariaLabel: `${s.name}: average ${s.avg.toFixed(2)}`
|
|
5935
|
+
}
|
|
5936
|
+
)
|
|
5937
|
+
] }, s.name)) })
|
|
5938
|
+
] });
|
|
5939
|
+
};
|
|
5864
5940
|
const logStats = (0, import_react12.useMemo)(() => {
|
|
5865
5941
|
const rows = spendLogs?.data ?? [];
|
|
5866
5942
|
const hits = rows.filter((r) => r.cacheHit);
|
|
@@ -5941,64 +6017,90 @@ var init_InsightsTab = __esm({
|
|
|
5941
6017
|
{
|
|
5942
6018
|
title: "Efficiency",
|
|
5943
6019
|
subtitle: `${dateRange.start.toLocaleDateString()} \u2013 ${dateRange.end.toLocaleDateString()}`,
|
|
6020
|
+
description: "What each token and request costs, and how much failed requests waste.",
|
|
5944
6021
|
children: renderEfficiencyBody()
|
|
5945
6022
|
}
|
|
5946
6023
|
),
|
|
5947
6024
|
/* @__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)(
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_Box16.default, { sx: { display: "flex",
|
|
5955
|
-
/* @__PURE__ */ (0, import_jsx_runtime17.
|
|
5956
|
-
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
|
|
5960
|
-
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
|
|
6025
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Grid2.default, { item: true, xs: 12, md: 5, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
6026
|
+
SectionCard,
|
|
6027
|
+
{
|
|
6028
|
+
title: "Practice score",
|
|
6029
|
+
subtitle: "Out of 100",
|
|
6030
|
+
description: "Five everyday habits in one number \u2014 higher means cheaper, safer usage.",
|
|
6031
|
+
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: [
|
|
6032
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_Typography14.default, { sx: { fontSize: 30, fontWeight: 700, fontVariantNumeric: "tabular-nums" }, children: [
|
|
6033
|
+
score.total,
|
|
6034
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Typography14.default, { component: "span", color: "text.secondary", sx: { fontSize: 16 }, children: "/100" })
|
|
6035
|
+
] }),
|
|
6036
|
+
score.checks.map((c) => /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_Box16.default, { children: [
|
|
6037
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_Box16.default, { sx: { display: "flex", justifyContent: "space-between", gap: 1, mb: 0.5 }, children: [
|
|
6038
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Typography14.default, { variant: "body2", sx: { fontWeight: 600 }, children: c.label }),
|
|
6039
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_Typography14.default, { variant: "caption", color: "text.secondary", sx: { fontVariantNumeric: "tabular-nums" }, children: [
|
|
6040
|
+
c.points,
|
|
6041
|
+
"/",
|
|
6042
|
+
c.maxPoints
|
|
6043
|
+
] })
|
|
6044
|
+
] }),
|
|
6045
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
6046
|
+
Meter,
|
|
6047
|
+
{
|
|
6048
|
+
value: c.maxPoints > 0 ? c.points / c.maxPoints * 100 : 0,
|
|
6049
|
+
ariaLabel: `${c.label}: ${c.points} of ${c.maxPoints}`
|
|
6050
|
+
}
|
|
6051
|
+
),
|
|
6052
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Typography14.default, { variant: "caption", color: "text.secondary", children: c.detail })
|
|
6053
|
+
] }, c.id))
|
|
6054
|
+
] })
|
|
6055
|
+
}
|
|
6056
|
+
) }),
|
|
6057
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Grid2.default, { item: true, xs: 12, md: 7, children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
6058
|
+
SectionCard,
|
|
6059
|
+
{
|
|
6060
|
+
title: "Cache & errors",
|
|
6061
|
+
subtitle: "From spend logs (estimate)",
|
|
6062
|
+
description: "Repeated prompts served from cache save money; failures still bill.",
|
|
6063
|
+
children: renderSpendLogsBody()
|
|
6064
|
+
}
|
|
6065
|
+
) })
|
|
5973
6066
|
] }),
|
|
5974
|
-
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
5975
|
-
|
|
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,
|
|
6067
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
6068
|
+
SectionCard,
|
|
5997
6069
|
{
|
|
5998
|
-
|
|
5999
|
-
|
|
6070
|
+
title: "Model opportunities",
|
|
6071
|
+
description: "Where a cheaper model or a reliability fix would save the most.",
|
|
6072
|
+
flush: true,
|
|
6073
|
+
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: [
|
|
6074
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableHead6.default, { children: /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_TableRow6.default, { children: [
|
|
6075
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { children: "Model" }),
|
|
6076
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { align: "right", children: "Spend" }),
|
|
6077
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { align: "right", children: "Requests" }),
|
|
6078
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { align: "right", children: "Failure" }),
|
|
6079
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { align: "right", children: "Avg / req" }),
|
|
6080
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { children: "Signal" })
|
|
6081
|
+
] }) }),
|
|
6082
|
+
/* @__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: [
|
|
6083
|
+
/* @__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 }) }),
|
|
6084
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { align: "right", children: fmtUsd(o.spend) }),
|
|
6085
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { align: "right", children: fmtInt(o.requests) }),
|
|
6086
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { align: "right", children: fmtPct1(o.failureRate) }),
|
|
6087
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_TableCell6.default, { align: "right", children: fmtUsd(o.avgCostPerRequest) }),
|
|
6088
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_TableCell6.default, { children: [
|
|
6089
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(StatusPill, { label: FLAG_LABEL[o.flag], tone: FLAG_TONE[o.flag], title: o.hint }),
|
|
6090
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_Typography14.default, { variant: "caption", color: "text.secondary", sx: { display: "block", mt: 0.25 }, children: o.hint })
|
|
6091
|
+
] })
|
|
6092
|
+
] }, o.model)) })
|
|
6093
|
+
] }) })
|
|
6000
6094
|
}
|
|
6001
|
-
)
|
|
6095
|
+
),
|
|
6096
|
+
/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
|
|
6097
|
+
SectionCard,
|
|
6098
|
+
{
|
|
6099
|
+
title: "Quality scores",
|
|
6100
|
+
description: "Independent answer-quality scores from Langfuse, when connected.",
|
|
6101
|
+
children: renderQualityBody()
|
|
6102
|
+
}
|
|
6103
|
+
)
|
|
6002
6104
|
] });
|
|
6003
6105
|
};
|
|
6004
6106
|
}
|