@acarmisc/backstage-plugin-litellm 0.10.2 → 0.12.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/README.md +57 -0
- package/dist/components/DashboardHeader.d.ts +3 -1
- package/dist/components/GenerateKeyDialog.d.ts +20 -0
- package/dist/components/KeysTable.d.ts +3 -5
- package/dist/index.cjs.js +560 -397
- package/dist/index.cjs.js.map +4 -4
- package/dist/index.esm.js +564 -388
- package/dist/index.esm.js.map +4 -4
- package/dist/types.d.ts +7 -0
- package/package.json +1 -1
package/dist/index.cjs.js
CHANGED
|
@@ -175,13 +175,26 @@ var init_api = __esm({
|
|
|
175
175
|
});
|
|
176
176
|
|
|
177
177
|
// src/components/DashboardHeader.tsx
|
|
178
|
-
var import_react, import_material, DashboardHeader;
|
|
178
|
+
var import_react, import_material, import_icons_material, DashboardHeader;
|
|
179
179
|
var init_DashboardHeader = __esm({
|
|
180
180
|
"src/components/DashboardHeader.tsx"() {
|
|
181
181
|
"use strict";
|
|
182
182
|
import_react = __toESM(require("react"));
|
|
183
183
|
import_material = require("@mui/material");
|
|
184
|
-
|
|
184
|
+
import_icons_material = require("@mui/icons-material");
|
|
185
|
+
init_api();
|
|
186
|
+
DashboardHeader = ({
|
|
187
|
+
userInfo,
|
|
188
|
+
teams,
|
|
189
|
+
keys,
|
|
190
|
+
loading,
|
|
191
|
+
onGenerateKeyClick
|
|
192
|
+
}) => {
|
|
193
|
+
const counts = (0, import_react.useMemo)(() => {
|
|
194
|
+
const expired = keys.filter((k) => expiryStatus(k.expires_at) === "expired").length;
|
|
195
|
+
const expiringSoon = keys.filter((k) => expiryStatus(k.expires_at) === "soon").length;
|
|
196
|
+
return { total: keys.length, expired, expiringSoon };
|
|
197
|
+
}, [keys]);
|
|
185
198
|
if (loading) {
|
|
186
199
|
return /* @__PURE__ */ import_react.default.createElement(import_material.Paper, { sx: { p: 2, mb: 2 } }, /* @__PURE__ */ import_react.default.createElement(import_material.LinearProgress, null));
|
|
187
200
|
}
|
|
@@ -195,21 +208,32 @@ var init_DashboardHeader = __esm({
|
|
|
195
208
|
variant: "outlined",
|
|
196
209
|
color: "primary"
|
|
197
210
|
}
|
|
198
|
-
))))))
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
})
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
211
|
+
)))), /* @__PURE__ */ import_react.default.createElement(import_material.Box, { display: "flex", alignItems: "center", gap: 2, flexWrap: "wrap" }, /* @__PURE__ */ import_react.default.createElement(import_material.Stack, { direction: "row", spacing: 1 }, /* @__PURE__ */ import_react.default.createElement(import_material.Chip, { icon: /* @__PURE__ */ import_react.default.createElement(import_icons_material.Key, { fontSize: "small" }), label: `${counts.total} keys`, size: "small" }), /* @__PURE__ */ import_react.default.createElement(
|
|
212
|
+
import_material.Chip,
|
|
213
|
+
{
|
|
214
|
+
icon: /* @__PURE__ */ import_react.default.createElement(import_icons_material.Warning, { fontSize: "small" }),
|
|
215
|
+
label: `${counts.expired} expired`,
|
|
216
|
+
size: "small",
|
|
217
|
+
color: counts.expired > 0 ? "error" : "default"
|
|
218
|
+
}
|
|
219
|
+
), /* @__PURE__ */ import_react.default.createElement(
|
|
220
|
+
import_material.Chip,
|
|
221
|
+
{
|
|
222
|
+
icon: /* @__PURE__ */ import_react.default.createElement(import_icons_material.Schedule, { fontSize: "small" }),
|
|
223
|
+
label: `${counts.expiringSoon} expiring soon`,
|
|
224
|
+
size: "small",
|
|
225
|
+
color: counts.expiringSoon > 0 ? "warning" : "default"
|
|
226
|
+
}
|
|
227
|
+
)), /* @__PURE__ */ import_react.default.createElement(
|
|
228
|
+
import_material.Button,
|
|
229
|
+
{
|
|
230
|
+
variant: "contained",
|
|
231
|
+
color: "primary",
|
|
232
|
+
startIcon: /* @__PURE__ */ import_react.default.createElement(import_icons_material.Add, null),
|
|
233
|
+
onClick: onGenerateKeyClick
|
|
234
|
+
},
|
|
235
|
+
"Generate New Key"
|
|
236
|
+
))));
|
|
213
237
|
};
|
|
214
238
|
}
|
|
215
239
|
});
|
|
@@ -220,7 +244,7 @@ function ExpiryChip({ expiresAt }) {
|
|
|
220
244
|
if (!status) return /* @__PURE__ */ import_react2.default.createElement(import_material2.Typography, { variant: "body2", color: "text.secondary" }, "-");
|
|
221
245
|
const label = status === "expired" ? "Expired" : status === "soon" ? `${Math.ceil((new Date(expiresAt).getTime() - Date.now()) / 864e5)}d left` : formatDate(expiresAt);
|
|
222
246
|
const color = status === "expired" ? "error" : status === "soon" ? "warning" : "default";
|
|
223
|
-
const icon = status === "expired" || status === "soon" ? /* @__PURE__ */ import_react2.default.createElement(
|
|
247
|
+
const icon = status === "expired" || status === "soon" ? /* @__PURE__ */ import_react2.default.createElement(import_icons_material2.Warning, { fontSize: "small" }) : void 0;
|
|
224
248
|
return /* @__PURE__ */ import_react2.default.createElement(import_material2.Chip, { label, color, size: "small", icon });
|
|
225
249
|
}
|
|
226
250
|
function BudgetCell({ spend, maxBudget }) {
|
|
@@ -249,42 +273,14 @@ function formatContextWindow(maxInput, maxOutput) {
|
|
|
249
273
|
if (outPart) return `ctx ${outPart} out`;
|
|
250
274
|
return null;
|
|
251
275
|
}
|
|
252
|
-
|
|
253
|
-
return url.replace(/\/+$/, "");
|
|
254
|
-
}
|
|
255
|
-
function buildSnippets(baseUrl, key, model) {
|
|
256
|
-
const base = trimSlash(baseUrl);
|
|
257
|
-
return {
|
|
258
|
-
curl: `curl ${base}/v1/chat/completions \\
|
|
259
|
-
-H "Authorization: Bearer ${key}" \\
|
|
260
|
-
-H "Content-Type: application/json" \\
|
|
261
|
-
-d '{
|
|
262
|
-
"model": "${model}",
|
|
263
|
-
"messages": [{ "role": "user", "content": "Hello!" }]
|
|
264
|
-
}'`,
|
|
265
|
-
openai: `from openai import OpenAI
|
|
266
|
-
|
|
267
|
-
client = OpenAI(
|
|
268
|
-
api_key="${key}",
|
|
269
|
-
base_url="${base}/v1",
|
|
270
|
-
)
|
|
271
|
-
|
|
272
|
-
response = client.chat.completions.create(
|
|
273
|
-
model="${model}",
|
|
274
|
-
messages=[{ "role": "user", "content": "Hello!" }],
|
|
275
|
-
)
|
|
276
|
-
print(response.choices[0].message.content)`
|
|
277
|
-
};
|
|
278
|
-
}
|
|
279
|
-
var import_react2, import_material2, import_icons_material, maskKey, shortKeyId, formatDate, emptyForm, keyToEditForm, KeysTable, SnippetTabs;
|
|
276
|
+
var import_react2, import_material2, import_icons_material2, maskKey, shortKeyId, formatDate, keyToEditForm, KeysTable;
|
|
280
277
|
var init_KeysTable = __esm({
|
|
281
278
|
"src/components/KeysTable.tsx"() {
|
|
282
279
|
"use strict";
|
|
283
280
|
import_react2 = __toESM(require("react"));
|
|
284
281
|
import_material2 = require("@mui/material");
|
|
285
|
-
|
|
282
|
+
import_icons_material2 = require("@mui/icons-material");
|
|
286
283
|
init_api();
|
|
287
|
-
init_format();
|
|
288
284
|
maskKey = (key) => {
|
|
289
285
|
if (key.length <= 8) return "***";
|
|
290
286
|
return `${key.slice(0, 4)}...${key.slice(-4)}`;
|
|
@@ -301,16 +297,6 @@ var init_KeysTable = __esm({
|
|
|
301
297
|
return dateStr;
|
|
302
298
|
}
|
|
303
299
|
};
|
|
304
|
-
emptyForm = () => ({
|
|
305
|
-
alias: "",
|
|
306
|
-
models: [],
|
|
307
|
-
duration: "30d",
|
|
308
|
-
max_budget: 100,
|
|
309
|
-
tpm_limit: void 0,
|
|
310
|
-
rpm_limit: void 0,
|
|
311
|
-
team_id: void 0,
|
|
312
|
-
key_type: "llm_api"
|
|
313
|
-
});
|
|
314
300
|
keyToEditForm = (k) => ({
|
|
315
301
|
key_alias: k.key_alias ?? "",
|
|
316
302
|
models: k.models ?? [],
|
|
@@ -321,24 +307,15 @@ var init_KeysTable = __esm({
|
|
|
321
307
|
KeysTable = ({
|
|
322
308
|
keys,
|
|
323
309
|
models,
|
|
324
|
-
teams,
|
|
325
310
|
loading,
|
|
326
|
-
|
|
311
|
+
onGenerateKeyClick,
|
|
327
312
|
onUpdateKey,
|
|
328
313
|
onBlockKey,
|
|
329
314
|
onUnblockKey,
|
|
330
315
|
onResetKeySpend,
|
|
331
316
|
onDeleteKey,
|
|
332
|
-
onPruneExpiredKeys
|
|
333
|
-
onGetConfig
|
|
317
|
+
onPruneExpiredKeys
|
|
334
318
|
}) => {
|
|
335
|
-
const [generateModalOpen, setGenerateModalOpen] = (0, import_react2.useState)(false);
|
|
336
|
-
const [newKeyValue, setNewKeyValue] = (0, import_react2.useState)(null);
|
|
337
|
-
const [newKeySnippets, setNewKeySnippets] = (0, import_react2.useState)(null);
|
|
338
|
-
const [newKeyModel, setNewKeyModel] = (0, import_react2.useState)("");
|
|
339
|
-
const [formData, setFormData] = (0, import_react2.useState)(emptyForm());
|
|
340
|
-
const [unlimitedBudget, setUnlimitedBudget] = (0, import_react2.useState)(false);
|
|
341
|
-
const [submitting, setSubmitting] = (0, import_react2.useState)(false);
|
|
342
319
|
const [editingKey, setEditingKey] = (0, import_react2.useState)(null);
|
|
343
320
|
const [editForm, setEditForm] = (0, import_react2.useState)({});
|
|
344
321
|
const [editSubmitting, setEditSubmitting] = (0, import_react2.useState)(false);
|
|
@@ -355,51 +332,7 @@ var init_KeysTable = __esm({
|
|
|
355
332
|
(k) => (k.key_alias ?? "").toLowerCase().includes(q) || k.models?.some((m) => m.toLowerCase().includes(q))
|
|
356
333
|
);
|
|
357
334
|
}, [keys, filterText]);
|
|
358
|
-
const selectedModels = models.filter((m) => (formData.models || []).includes(m.model_name));
|
|
359
|
-
const selectedTeam = teams.find((t) => t.team_id === formData.team_id) ?? null;
|
|
360
335
|
const editSelectedModels = models.filter((m) => (editForm.models || []).includes(m.model_name));
|
|
361
|
-
const aliasError = !(formData.alias || "").trim();
|
|
362
|
-
const budgetInvalid = !unlimitedBudget && (formData.max_budget === void 0 || formData.max_budget === null || formData.max_budget <= 0);
|
|
363
|
-
const canGenerate = !aliasError && !budgetInvalid && !submitting;
|
|
364
|
-
const budgetEstimate = (0, import_react2.useMemo)(() => {
|
|
365
|
-
if (unlimitedBudget || budgetInvalid) return null;
|
|
366
|
-
const inputCosts = selectedModels.map((m) => m.input_cost_per_token).filter((c) => typeof c === "number" && c > 0);
|
|
367
|
-
if (inputCosts.length === 0) return null;
|
|
368
|
-
const pricePerToken = Math.max(...inputCosts);
|
|
369
|
-
return estimateTokensFromBudget(formData.max_budget ?? 0, pricePerToken);
|
|
370
|
-
}, [formData.max_budget, selectedModels, unlimitedBudget, budgetInvalid]);
|
|
371
|
-
const handleGenerate = async () => {
|
|
372
|
-
setSubmitting(true);
|
|
373
|
-
try {
|
|
374
|
-
const request = {
|
|
375
|
-
...formData,
|
|
376
|
-
max_budget: unlimitedBudget ? null : formData.max_budget
|
|
377
|
-
};
|
|
378
|
-
const response = await onGenerateKey(request);
|
|
379
|
-
setNewKeyValue(response.key);
|
|
380
|
-
setNewKeyModel(formData.models?.[0] ?? "");
|
|
381
|
-
setNewKeySnippets(null);
|
|
382
|
-
try {
|
|
383
|
-
const config = await onGetConfig();
|
|
384
|
-
const model = formData.models?.[0] ?? "";
|
|
385
|
-
setNewKeySnippets(buildSnippets(config.baseUrl, response.key, model));
|
|
386
|
-
} catch {
|
|
387
|
-
}
|
|
388
|
-
setFormData(emptyForm());
|
|
389
|
-
setUnlimitedBudget(false);
|
|
390
|
-
} catch (error) {
|
|
391
|
-
console.error("Failed to generate key:", error);
|
|
392
|
-
} finally {
|
|
393
|
-
setSubmitting(false);
|
|
394
|
-
}
|
|
395
|
-
};
|
|
396
|
-
const handleCloseModal = () => {
|
|
397
|
-
setGenerateModalOpen(false);
|
|
398
|
-
setNewKeyValue(null);
|
|
399
|
-
setNewKeySnippets(null);
|
|
400
|
-
setFormData(emptyForm());
|
|
401
|
-
setUnlimitedBudget(false);
|
|
402
|
-
};
|
|
403
336
|
const handleOpenEdit = (k) => {
|
|
404
337
|
setEditingKey(k);
|
|
405
338
|
setEditForm(keyToEditForm(k));
|
|
@@ -490,7 +423,7 @@ var init_KeysTable = __esm({
|
|
|
490
423
|
onChange: (e) => setFilterText(e.target.value),
|
|
491
424
|
sx: { minWidth: 240 },
|
|
492
425
|
InputProps: {
|
|
493
|
-
startAdornment: /* @__PURE__ */ import_react2.default.createElement(import_material2.InputAdornment, { position: "start" }, /* @__PURE__ */ import_react2.default.createElement(
|
|
426
|
+
startAdornment: /* @__PURE__ */ import_react2.default.createElement(import_material2.InputAdornment, { position: "start" }, /* @__PURE__ */ import_react2.default.createElement(import_icons_material2.Search, { fontSize: "small" }))
|
|
494
427
|
}
|
|
495
428
|
}
|
|
496
429
|
), expiredKeys.length > 0 && /* @__PURE__ */ import_react2.default.createElement(
|
|
@@ -498,7 +431,7 @@ var init_KeysTable = __esm({
|
|
|
498
431
|
{
|
|
499
432
|
variant: "outlined",
|
|
500
433
|
color: "error",
|
|
501
|
-
startIcon: /* @__PURE__ */ import_react2.default.createElement(
|
|
434
|
+
startIcon: /* @__PURE__ */ import_react2.default.createElement(import_icons_material2.Autorenew, null),
|
|
502
435
|
onClick: () => setPruneConfirmCount(expiredKeys.length)
|
|
503
436
|
},
|
|
504
437
|
"Prune expired (",
|
|
@@ -509,8 +442,8 @@ var init_KeysTable = __esm({
|
|
|
509
442
|
{
|
|
510
443
|
variant: "contained",
|
|
511
444
|
color: "primary",
|
|
512
|
-
startIcon: /* @__PURE__ */ import_react2.default.createElement(
|
|
513
|
-
onClick:
|
|
445
|
+
startIcon: /* @__PURE__ */ import_react2.default.createElement(import_icons_material2.Add, null),
|
|
446
|
+
onClick: onGenerateKeyClick
|
|
514
447
|
},
|
|
515
448
|
"Generate New Key"
|
|
516
449
|
))), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableContainer, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.Table, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.TableHead, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.TableRow, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, "Alias"), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, "Key ID"), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, "Created"), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, "Expires"), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, "Budget"), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, "TPM / RPM"), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, "Models"), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, { align: "right" }, "Actions"))), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableBody, null, loading ? /* @__PURE__ */ import_react2.default.createElement(import_material2.TableRow, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, { colSpan: 8, align: "center" }, /* @__PURE__ */ import_react2.default.createElement(import_material2.CircularProgress, { size: 24 }))) : filteredKeys.length === 0 ? /* @__PURE__ */ import_react2.default.createElement(import_material2.TableRow, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, { colSpan: 8, align: "center", sx: { py: 4 } }, filterText ? /* @__PURE__ */ import_react2.default.createElement(import_material2.Typography, { color: "text.secondary" }, "No keys match filter") : /* @__PURE__ */ import_react2.default.createElement(import_material2.Box, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.Typography, { color: "text.secondary", gutterBottom: true }, "No keys yet \u2014 generate your first key to start calling models."), /* @__PURE__ */ import_react2.default.createElement(
|
|
@@ -519,8 +452,8 @@ var init_KeysTable = __esm({
|
|
|
519
452
|
variant: "contained",
|
|
520
453
|
color: "primary",
|
|
521
454
|
size: "small",
|
|
522
|
-
startIcon: /* @__PURE__ */ import_react2.default.createElement(
|
|
523
|
-
onClick:
|
|
455
|
+
startIcon: /* @__PURE__ */ import_react2.default.createElement(import_icons_material2.Add, null),
|
|
456
|
+
onClick: onGenerateKeyClick
|
|
524
457
|
},
|
|
525
458
|
"Generate Your First Key"
|
|
526
459
|
)))) : filteredKeys.map((key) => {
|
|
@@ -542,7 +475,7 @@ var init_KeysTable = __esm({
|
|
|
542
475
|
}
|
|
543
476
|
},
|
|
544
477
|
shortKeyId(keyId)
|
|
545
|
-
), /* @__PURE__ */ import_react2.default.createElement(import_material2.IconButton, { size: "small", onClick: () => copyToClipboard(keyId), title: "Copy Key ID" }, /* @__PURE__ */ import_react2.default.createElement(
|
|
478
|
+
), /* @__PURE__ */ import_react2.default.createElement(import_material2.IconButton, { size: "small", onClick: () => copyToClipboard(keyId), title: "Copy Key ID" }, /* @__PURE__ */ import_react2.default.createElement(import_icons_material2.ContentCopy, { fontSize: "small" })))), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, formatDate(key.created_at)), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, /* @__PURE__ */ import_react2.default.createElement(ExpiryChip, { expiresAt: key.expires_at })), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, /* @__PURE__ */ import_react2.default.createElement(BudgetCell, { spend: key.spend ?? 0, maxBudget: key.max_budget })), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.Typography, { variant: "body2" }, key.tpm_limit ?? "-", " / ", key.rpm_limit ?? "-")), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.Box, { display: "flex", gap: 0.5, flexWrap: "wrap" }, key.models?.slice(0, 2).map((model) => /* @__PURE__ */ import_react2.default.createElement(import_material2.Chip, { key: model, label: model, size: "small" })), (key.models?.length || 0) > 2 && /* @__PURE__ */ import_react2.default.createElement(import_material2.Chip, { label: `+${(key.models?.length || 0) - 2}`, size: "small", variant: "outlined" }))), /* @__PURE__ */ import_react2.default.createElement(import_material2.TableCell, { align: "right" }, /* @__PURE__ */ import_react2.default.createElement(import_material2.IconButton, { onClick: () => handleOpenEdit(key), title: "Edit key" }, /* @__PURE__ */ import_react2.default.createElement(import_icons_material2.Edit, { fontSize: "small" })), /* @__PURE__ */ import_react2.default.createElement(
|
|
546
479
|
import_material2.IconButton,
|
|
547
480
|
{
|
|
548
481
|
onClick: () => handleToggleBlock(key),
|
|
@@ -550,7 +483,7 @@ var init_KeysTable = __esm({
|
|
|
550
483
|
color: key.blocked ? "warning" : "default",
|
|
551
484
|
title: key.blocked ? "Unblock key" : "Block key \u2014 suspends without revoking"
|
|
552
485
|
},
|
|
553
|
-
isBlocking ? /* @__PURE__ */ import_react2.default.createElement(import_material2.CircularProgress, { size: 18 }) : key.blocked ? /* @__PURE__ */ import_react2.default.createElement(
|
|
486
|
+
isBlocking ? /* @__PURE__ */ import_react2.default.createElement(import_material2.CircularProgress, { size: 18 }) : key.blocked ? /* @__PURE__ */ import_react2.default.createElement(import_icons_material2.LockOpen, { fontSize: "small" }) : /* @__PURE__ */ import_react2.default.createElement(import_icons_material2.Lock, { fontSize: "small" })
|
|
554
487
|
), /* @__PURE__ */ import_react2.default.createElement(
|
|
555
488
|
import_material2.IconButton,
|
|
556
489
|
{
|
|
@@ -558,10 +491,281 @@ var init_KeysTable = __esm({
|
|
|
558
491
|
onClick: () => setDeleteConfirmId(keyId),
|
|
559
492
|
title: "Revoke key"
|
|
560
493
|
},
|
|
561
|
-
/* @__PURE__ */ import_react2.default.createElement(
|
|
494
|
+
/* @__PURE__ */ import_react2.default.createElement(import_icons_material2.Delete, null)
|
|
562
495
|
)));
|
|
563
|
-
}))))), /* @__PURE__ */ import_react2.default.createElement(import_material2.Dialog, { open:
|
|
564
|
-
import_material2.
|
|
496
|
+
}))))), /* @__PURE__ */ import_react2.default.createElement(import_material2.Dialog, { open: !!editingKey, onClose: handleCloseEdit, maxWidth: "sm", fullWidth: true }, /* @__PURE__ */ import_react2.default.createElement(import_material2.DialogTitle, null, "Edit Key"), /* @__PURE__ */ import_react2.default.createElement(import_material2.DialogContent, null, editingKey && /* @__PURE__ */ import_react2.default.createElement(import_material2.Box, { display: "flex", flexDirection: "column", gap: 2, mt: 1 }, /* @__PURE__ */ import_react2.default.createElement(import_material2.Typography, { variant: "body2", color: "text.secondary" }, /* @__PURE__ */ import_react2.default.createElement("code", { style: { fontFamily: "monospace", color: "inherit" } }, maskKey(editingKey.key))), /* @__PURE__ */ import_react2.default.createElement(
|
|
497
|
+
import_material2.TextField,
|
|
498
|
+
{
|
|
499
|
+
label: "Alias",
|
|
500
|
+
value: editForm.key_alias || "",
|
|
501
|
+
onChange: (e) => setEditForm({ ...editForm, key_alias: e.target.value }),
|
|
502
|
+
fullWidth: true
|
|
503
|
+
}
|
|
504
|
+
), models.length > 0 && /* @__PURE__ */ import_react2.default.createElement(
|
|
505
|
+
import_material2.Autocomplete,
|
|
506
|
+
{
|
|
507
|
+
multiple: true,
|
|
508
|
+
options: models,
|
|
509
|
+
groupBy: (m) => m.mode || "other",
|
|
510
|
+
getOptionLabel: (m) => m.model_name,
|
|
511
|
+
value: editSelectedModels,
|
|
512
|
+
onChange: (_e, selected) => setEditForm({ ...editForm, models: selected.map((m) => m.model_name) }),
|
|
513
|
+
renderOption: (props, m) => /* @__PURE__ */ import_react2.default.createElement("li", { ...props }, modelOption(m)),
|
|
514
|
+
renderInput: (params) => /* @__PURE__ */ import_react2.default.createElement(import_material2.TextField, { ...params, label: "Models", fullWidth: true })
|
|
515
|
+
}
|
|
516
|
+
), /* @__PURE__ */ import_react2.default.createElement(
|
|
517
|
+
import_material2.TextField,
|
|
518
|
+
{
|
|
519
|
+
label: "Max Budget (USD)",
|
|
520
|
+
type: "number",
|
|
521
|
+
value: editForm.max_budget ?? "",
|
|
522
|
+
onChange: (e) => setEditForm({ ...editForm, max_budget: e.target.value ? Number(e.target.value) : void 0 }),
|
|
523
|
+
fullWidth: true
|
|
524
|
+
}
|
|
525
|
+
), /* @__PURE__ */ import_react2.default.createElement(
|
|
526
|
+
import_material2.TextField,
|
|
527
|
+
{
|
|
528
|
+
label: "TPM Limit",
|
|
529
|
+
type: "number",
|
|
530
|
+
value: editForm.tpm_limit ?? "",
|
|
531
|
+
onChange: (e) => setEditForm({ ...editForm, tpm_limit: e.target.value ? Number(e.target.value) : void 0 }),
|
|
532
|
+
helperText: "Max tokens per minute this key can consume across all models. Leave blank for no limit.",
|
|
533
|
+
fullWidth: true
|
|
534
|
+
}
|
|
535
|
+
), /* @__PURE__ */ import_react2.default.createElement(
|
|
536
|
+
import_material2.TextField,
|
|
537
|
+
{
|
|
538
|
+
label: "RPM Limit",
|
|
539
|
+
type: "number",
|
|
540
|
+
value: editForm.rpm_limit ?? "",
|
|
541
|
+
onChange: (e) => setEditForm({ ...editForm, rpm_limit: e.target.value ? Number(e.target.value) : void 0 }),
|
|
542
|
+
helperText: "Max requests per minute this key can make across all models. Leave blank for no limit.",
|
|
543
|
+
fullWidth: true
|
|
544
|
+
}
|
|
545
|
+
), /* @__PURE__ */ import_react2.default.createElement(import_material2.Box, { mt: 1, pt: 2, borderTop: "1px solid", sx: { borderColor: "divider" } }, /* @__PURE__ */ import_react2.default.createElement(import_material2.Typography, { variant: "caption", color: "text.secondary", display: "block", mb: 1 }, "Danger Zone"), !resetSpendConfirm ? /* @__PURE__ */ import_react2.default.createElement(
|
|
546
|
+
import_material2.Button,
|
|
547
|
+
{
|
|
548
|
+
size: "small",
|
|
549
|
+
color: "warning",
|
|
550
|
+
variant: "outlined",
|
|
551
|
+
onClick: () => setResetSpendConfirm(true)
|
|
552
|
+
},
|
|
553
|
+
"Reset Spend to $0"
|
|
554
|
+
) : /* @__PURE__ */ import_react2.default.createElement(import_material2.Box, { display: "flex", alignItems: "center", gap: 1, flexWrap: "wrap" }, /* @__PURE__ */ import_react2.default.createElement(import_material2.Typography, { variant: "body2", color: "warning.main" }, "Zero out spend counter?"), /* @__PURE__ */ import_react2.default.createElement(
|
|
555
|
+
import_material2.Button,
|
|
556
|
+
{
|
|
557
|
+
size: "small",
|
|
558
|
+
color: "warning",
|
|
559
|
+
variant: "contained",
|
|
560
|
+
disabled: resetSpendSubmitting,
|
|
561
|
+
onClick: handleResetSpend
|
|
562
|
+
},
|
|
563
|
+
resetSpendSubmitting ? /* @__PURE__ */ import_react2.default.createElement(import_material2.CircularProgress, { size: 16 }) : "Confirm"
|
|
564
|
+
), /* @__PURE__ */ import_react2.default.createElement(import_material2.Button, { size: "small", onClick: () => setResetSpendConfirm(false) }, "Cancel"))))), /* @__PURE__ */ import_react2.default.createElement(import_material2.DialogActions, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.Button, { onClick: handleCloseEdit }, "Cancel"), /* @__PURE__ */ import_react2.default.createElement(import_material2.Button, { onClick: handleUpdate, variant: "contained", color: "primary", disabled: editSubmitting }, editSubmitting ? /* @__PURE__ */ import_react2.default.createElement(import_material2.CircularProgress, { size: 24 }) : "Save"))), /* @__PURE__ */ import_react2.default.createElement(import_material2.Dialog, { open: !!deleteConfirmId, onClose: () => setDeleteConfirmId(null), maxWidth: "xs", fullWidth: true }, /* @__PURE__ */ import_react2.default.createElement(import_material2.DialogTitle, null, "Revoke Key?"), /* @__PURE__ */ import_react2.default.createElement(import_material2.DialogContent, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.Typography, null, "This will permanently revoke the key. Any integrations using it will stop working immediately.")), /* @__PURE__ */ import_react2.default.createElement(import_material2.DialogActions, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.Button, { onClick: () => setDeleteConfirmId(null), disabled: deleteSubmitting }, "Cancel"), /* @__PURE__ */ import_react2.default.createElement(
|
|
565
|
+
import_material2.Button,
|
|
566
|
+
{
|
|
567
|
+
onClick: handleConfirmDelete,
|
|
568
|
+
variant: "contained",
|
|
569
|
+
color: "error",
|
|
570
|
+
disabled: deleteSubmitting
|
|
571
|
+
},
|
|
572
|
+
deleteSubmitting ? /* @__PURE__ */ import_react2.default.createElement(import_material2.CircularProgress, { size: 20 }) : "Revoke"
|
|
573
|
+
))), /* @__PURE__ */ import_react2.default.createElement(import_material2.Dialog, { open: pruneConfirmCount !== null, onClose: () => setPruneConfirmCount(null), maxWidth: "xs", fullWidth: true }, /* @__PURE__ */ import_react2.default.createElement(import_material2.DialogTitle, null, "Prune Expired Keys?"), /* @__PURE__ */ import_react2.default.createElement(import_material2.DialogContent, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.Typography, null, "This will permanently delete ", pruneConfirmCount, " expired key", pruneConfirmCount !== 1 ? "s" : "", " from LiteLLM. Any integrations using them will stop working immediately.")), /* @__PURE__ */ import_react2.default.createElement(import_material2.DialogActions, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.Button, { onClick: () => setPruneConfirmCount(null), disabled: pruneSubmitting }, "Cancel"), /* @__PURE__ */ import_react2.default.createElement(
|
|
574
|
+
import_material2.Button,
|
|
575
|
+
{
|
|
576
|
+
onClick: handlePruneExpired,
|
|
577
|
+
variant: "contained",
|
|
578
|
+
color: "error",
|
|
579
|
+
disabled: pruneSubmitting
|
|
580
|
+
},
|
|
581
|
+
pruneSubmitting ? /* @__PURE__ */ import_react2.default.createElement(import_material2.CircularProgress, { size: 20 }) : "Prune"
|
|
582
|
+
))));
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
// src/format.ts
|
|
588
|
+
var fmtUsd, fmtInt, estimateTokensFromBudget;
|
|
589
|
+
var init_format = __esm({
|
|
590
|
+
"src/format.ts"() {
|
|
591
|
+
"use strict";
|
|
592
|
+
fmtUsd = (n) => `$${(n ?? 0).toFixed(n < 1 ? 4 : 2)}`;
|
|
593
|
+
fmtInt = (n) => (n ?? 0).toLocaleString();
|
|
594
|
+
estimateTokensFromBudget = (budget, pricePerToken) => {
|
|
595
|
+
if (!pricePerToken || pricePerToken <= 0 || !budget || budget <= 0) return null;
|
|
596
|
+
return Math.floor(budget / pricePerToken);
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
// src/components/GenerateKeyDialog.tsx
|
|
602
|
+
function trimSlash(url) {
|
|
603
|
+
return url.replace(/\/+$/, "");
|
|
604
|
+
}
|
|
605
|
+
function buildSnippets(baseUrl, key, model) {
|
|
606
|
+
const base = trimSlash(baseUrl);
|
|
607
|
+
return {
|
|
608
|
+
curl: `curl ${base}/v1/chat/completions \\
|
|
609
|
+
-H "Authorization: Bearer ${key}" \\
|
|
610
|
+
-H "Content-Type: application/json" \\
|
|
611
|
+
-d '{
|
|
612
|
+
"model": "${model}",
|
|
613
|
+
"messages": [{ "role": "user", "content": "Hello!" }]
|
|
614
|
+
}'`,
|
|
615
|
+
openai: `from openai import OpenAI
|
|
616
|
+
|
|
617
|
+
client = OpenAI(
|
|
618
|
+
api_key="${key}",
|
|
619
|
+
base_url="${base}/v1",
|
|
620
|
+
)
|
|
621
|
+
|
|
622
|
+
response = client.chat.completions.create(
|
|
623
|
+
model="${model}",
|
|
624
|
+
messages=[{ "role": "user", "content": "Hello!" }],
|
|
625
|
+
)
|
|
626
|
+
print(response.choices[0].message.content)`
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
function fmtCost2(perToken) {
|
|
630
|
+
if (!perToken) return null;
|
|
631
|
+
const per1k = perToken * 1e3;
|
|
632
|
+
return per1k < 0.01 ? `$${(perToken * 1e6).toFixed(2)}/M` : `$${per1k.toFixed(3)}/1K`;
|
|
633
|
+
}
|
|
634
|
+
function formatContextWindow2(maxInput, maxOutput) {
|
|
635
|
+
if (!maxInput && !maxOutput) return null;
|
|
636
|
+
const fmt = (n) => {
|
|
637
|
+
if (!n) return null;
|
|
638
|
+
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
639
|
+
if (n >= 1e3) return `${Math.round(n / 1e3)}K`;
|
|
640
|
+
return String(n);
|
|
641
|
+
};
|
|
642
|
+
const inPart = fmt(maxInput);
|
|
643
|
+
const outPart = fmt(maxOutput);
|
|
644
|
+
if (inPart && outPart) return `ctx ${inPart} in / ${outPart} out`;
|
|
645
|
+
if (inPart) return `ctx ${inPart}`;
|
|
646
|
+
if (outPart) return `ctx ${outPart} out`;
|
|
647
|
+
return null;
|
|
648
|
+
}
|
|
649
|
+
var import_react3, import_material3, import_icons_material3, generateDefaultAlias, emptyForm, GenerateKeyDialog, SnippetTabs;
|
|
650
|
+
var init_GenerateKeyDialog = __esm({
|
|
651
|
+
"src/components/GenerateKeyDialog.tsx"() {
|
|
652
|
+
"use strict";
|
|
653
|
+
import_react3 = __toESM(require("react"));
|
|
654
|
+
import_material3 = require("@mui/material");
|
|
655
|
+
import_icons_material3 = require("@mui/icons-material");
|
|
656
|
+
init_format();
|
|
657
|
+
generateDefaultAlias = (username) => {
|
|
658
|
+
const base = (username || "user").split("@")[0].toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "user";
|
|
659
|
+
const hash = Math.random().toString(36).slice(2, 8);
|
|
660
|
+
return `${base}-${hash}`;
|
|
661
|
+
};
|
|
662
|
+
emptyForm = () => ({
|
|
663
|
+
alias: "",
|
|
664
|
+
models: [],
|
|
665
|
+
duration: "30d",
|
|
666
|
+
max_budget: 100,
|
|
667
|
+
tpm_limit: void 0,
|
|
668
|
+
rpm_limit: void 0,
|
|
669
|
+
team_id: void 0,
|
|
670
|
+
key_type: "llm_api"
|
|
671
|
+
});
|
|
672
|
+
GenerateKeyDialog = ({
|
|
673
|
+
open,
|
|
674
|
+
onClose,
|
|
675
|
+
keys,
|
|
676
|
+
models,
|
|
677
|
+
teams,
|
|
678
|
+
username,
|
|
679
|
+
keyGenerationSettings,
|
|
680
|
+
onGenerateKey,
|
|
681
|
+
onGetConfig
|
|
682
|
+
}) => {
|
|
683
|
+
const [formData, setFormData] = (0, import_react3.useState)(emptyForm());
|
|
684
|
+
const [unlimitedBudget, setUnlimitedBudget] = (0, import_react3.useState)(false);
|
|
685
|
+
const [submitting, setSubmitting] = (0, import_react3.useState)(false);
|
|
686
|
+
const [newKeyValue, setNewKeyValue] = (0, import_react3.useState)(null);
|
|
687
|
+
const [newKeySnippets, setNewKeySnippets] = (0, import_react3.useState)(null);
|
|
688
|
+
const [newKeyModel, setNewKeyModel] = (0, import_react3.useState)("");
|
|
689
|
+
const [generateError, setGenerateError] = (0, import_react3.useState)(null);
|
|
690
|
+
(0, import_react3.useEffect)(() => {
|
|
691
|
+
if (open) {
|
|
692
|
+
setFormData({ ...emptyForm(), alias: generateDefaultAlias(username) });
|
|
693
|
+
setUnlimitedBudget(false);
|
|
694
|
+
setNewKeyValue(null);
|
|
695
|
+
setNewKeySnippets(null);
|
|
696
|
+
setNewKeyModel("");
|
|
697
|
+
setGenerateError(null);
|
|
698
|
+
}
|
|
699
|
+
}, [open, username]);
|
|
700
|
+
const allowUnlimitedBudget = keyGenerationSettings?.allowUnlimitedBudget ?? false;
|
|
701
|
+
const teamRequired = keyGenerationSettings?.teamRequired ?? true;
|
|
702
|
+
const selectedTeam = teams.find((t) => t.team_id === formData.team_id) ?? null;
|
|
703
|
+
const availableModels = (0, import_react3.useMemo)(() => {
|
|
704
|
+
const teamModels = selectedTeam?.models;
|
|
705
|
+
if (teamModels && teamModels.length > 0) {
|
|
706
|
+
const allowed = new Set(teamModels);
|
|
707
|
+
return models.filter((m) => allowed.has(m.model_name));
|
|
708
|
+
}
|
|
709
|
+
return models;
|
|
710
|
+
}, [models, selectedTeam]);
|
|
711
|
+
const selectedModels = availableModels.filter((m) => (formData.models || []).includes(m.model_name));
|
|
712
|
+
const aliasError = !(formData.alias || "").trim();
|
|
713
|
+
const teamError = teamRequired && !formData.team_id;
|
|
714
|
+
const budgetInvalid = !unlimitedBudget && (formData.max_budget === void 0 || formData.max_budget === null || formData.max_budget <= 0);
|
|
715
|
+
const canGenerate = !aliasError && !teamError && !budgetInvalid && !submitting;
|
|
716
|
+
const aliasDuplicate = !!formData.alias && keys.some((k) => k.key_alias === formData.alias);
|
|
717
|
+
const budgetEstimate = (0, import_react3.useMemo)(() => {
|
|
718
|
+
if (unlimitedBudget || budgetInvalid) return null;
|
|
719
|
+
const inputCosts = selectedModels.map((m) => m.input_cost_per_token).filter((c) => typeof c === "number" && c > 0);
|
|
720
|
+
if (inputCosts.length === 0) return null;
|
|
721
|
+
const pricePerToken = Math.max(...inputCosts);
|
|
722
|
+
return estimateTokensFromBudget(formData.max_budget ?? 0, pricePerToken);
|
|
723
|
+
}, [formData.max_budget, selectedModels, unlimitedBudget, budgetInvalid]);
|
|
724
|
+
const handleGenerate = async () => {
|
|
725
|
+
setSubmitting(true);
|
|
726
|
+
setGenerateError(null);
|
|
727
|
+
try {
|
|
728
|
+
const request = {
|
|
729
|
+
...formData,
|
|
730
|
+
max_budget: unlimitedBudget ? null : formData.max_budget
|
|
731
|
+
};
|
|
732
|
+
const response = await onGenerateKey(request);
|
|
733
|
+
setNewKeyValue(response.key);
|
|
734
|
+
setNewKeyModel(formData.models?.[0] ?? "");
|
|
735
|
+
setNewKeySnippets(null);
|
|
736
|
+
try {
|
|
737
|
+
const config = await onGetConfig();
|
|
738
|
+
const model = formData.models?.[0] ?? "";
|
|
739
|
+
setNewKeySnippets(buildSnippets(config.baseUrl, response.key, model));
|
|
740
|
+
} catch {
|
|
741
|
+
}
|
|
742
|
+
setFormData(emptyForm());
|
|
743
|
+
setUnlimitedBudget(false);
|
|
744
|
+
} catch (error) {
|
|
745
|
+
setGenerateError(error?.message ?? "Failed to generate key");
|
|
746
|
+
} finally {
|
|
747
|
+
setSubmitting(false);
|
|
748
|
+
}
|
|
749
|
+
};
|
|
750
|
+
const handleClose = () => {
|
|
751
|
+
onClose();
|
|
752
|
+
setGenerateError(null);
|
|
753
|
+
setNewKeyValue(null);
|
|
754
|
+
setNewKeySnippets(null);
|
|
755
|
+
setFormData(emptyForm());
|
|
756
|
+
setUnlimitedBudget(false);
|
|
757
|
+
};
|
|
758
|
+
const copyToClipboard = (text) => {
|
|
759
|
+
navigator.clipboard.writeText(text);
|
|
760
|
+
};
|
|
761
|
+
const modelOption = (m) => {
|
|
762
|
+
const inCost = fmtCost2(m.input_cost_per_token);
|
|
763
|
+
const outCost = fmtCost2(m.output_cost_per_token);
|
|
764
|
+
const ctx = formatContextWindow2(m.max_input_tokens, m.max_output_tokens);
|
|
765
|
+
return /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, null, /* @__PURE__ */ import_react3.default.createElement("span", null, m.model_name), m.supports_function_calling && " \u{1F527}", m.supports_vision && " \u{1F441}\uFE0F", ctx && /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "caption", color: "text.secondary", sx: { ml: 1 } }, ctx), (inCost || outCost) && /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "caption", color: "text.secondary", sx: { ml: 1 } }, inCost, " in \xB7 ", outCost, " out"));
|
|
766
|
+
};
|
|
767
|
+
return /* @__PURE__ */ import_react3.default.createElement(import_material3.Dialog, { open, onClose: handleClose, maxWidth: "sm", fullWidth: true }, /* @__PURE__ */ import_react3.default.createElement(import_material3.DialogTitle, null, newKeyValue ? "Key Generated" : "Generate New Key"), /* @__PURE__ */ import_react3.default.createElement(import_material3.DialogContent, null, newKeyValue ? /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "body2", color: "text.secondary", gutterBottom: true }, "Copy this key now. You won't be able to see it again."), /* @__PURE__ */ import_react3.default.createElement(
|
|
768
|
+
import_material3.Box,
|
|
565
769
|
{
|
|
566
770
|
display: "flex",
|
|
567
771
|
alignItems: "center",
|
|
@@ -575,8 +779,8 @@ var init_KeysTable = __esm({
|
|
|
575
779
|
borderRadius: 1
|
|
576
780
|
}
|
|
577
781
|
},
|
|
578
|
-
/* @__PURE__ */
|
|
579
|
-
|
|
782
|
+
/* @__PURE__ */ import_react3.default.createElement(
|
|
783
|
+
import_material3.Typography,
|
|
580
784
|
{
|
|
581
785
|
component: "code",
|
|
582
786
|
color: "text.primary",
|
|
@@ -584,20 +788,24 @@ var init_KeysTable = __esm({
|
|
|
584
788
|
},
|
|
585
789
|
newKeyValue
|
|
586
790
|
),
|
|
587
|
-
/* @__PURE__ */
|
|
588
|
-
), newKeySnippets && /* @__PURE__ */
|
|
589
|
-
|
|
791
|
+
/* @__PURE__ */ import_react3.default.createElement(import_material3.IconButton, { onClick: () => copyToClipboard(newKeyValue) }, /* @__PURE__ */ import_react3.default.createElement(import_icons_material3.ContentCopy, null))
|
|
792
|
+
), newKeySnippets && /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { mt: 3 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { display: "flex", alignItems: "center", gap: 1, mb: 1 }, /* @__PURE__ */ import_react3.default.createElement(import_icons_material3.Code, { fontSize: "small", color: "action" }), /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "subtitle2" }, "Start calling the proxy \u2014 paste and run")), /* @__PURE__ */ import_react3.default.createElement(SnippetTabs, { snippets: newKeySnippets, model: newKeyModel, onCopy: copyToClipboard }))) : /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { display: "flex", flexDirection: "column", gap: 2, mt: 1 }, generateError && /* @__PURE__ */ import_react3.default.createElement(import_material3.Alert, { severity: "error", onClose: () => setGenerateError(null) }, generateError), /* @__PURE__ */ import_react3.default.createElement(
|
|
793
|
+
import_material3.TextField,
|
|
590
794
|
{
|
|
591
795
|
label: "Alias",
|
|
592
796
|
value: formData.alias || "",
|
|
593
|
-
onChange: (e) =>
|
|
797
|
+
onChange: (e) => {
|
|
798
|
+
setFormData({ ...formData, alias: e.target.value });
|
|
799
|
+
setGenerateError(null);
|
|
800
|
+
},
|
|
594
801
|
error: aliasError,
|
|
595
|
-
|
|
802
|
+
color: aliasDuplicate ? "warning" : void 0,
|
|
803
|
+
helperText: aliasError ? "Alias is required" : aliasDuplicate ? "This alias is already used by one of your keys \u2014 LiteLLM requires aliases to be unique across all keys" : void 0,
|
|
596
804
|
required: true,
|
|
597
805
|
fullWidth: true
|
|
598
806
|
}
|
|
599
|
-
), /* @__PURE__ */
|
|
600
|
-
|
|
807
|
+
), /* @__PURE__ */ import_react3.default.createElement(
|
|
808
|
+
import_material3.TextField,
|
|
601
809
|
{
|
|
602
810
|
select: true,
|
|
603
811
|
label: "Duration",
|
|
@@ -605,45 +813,59 @@ var init_KeysTable = __esm({
|
|
|
605
813
|
onChange: (e) => setFormData({ ...formData, duration: e.target.value }),
|
|
606
814
|
fullWidth: true
|
|
607
815
|
},
|
|
608
|
-
/* @__PURE__ */
|
|
609
|
-
/* @__PURE__ */
|
|
610
|
-
/* @__PURE__ */
|
|
611
|
-
/* @__PURE__ */
|
|
612
|
-
/* @__PURE__ */
|
|
613
|
-
), teams.length > 0
|
|
614
|
-
|
|
816
|
+
/* @__PURE__ */ import_react3.default.createElement(import_material3.MenuItem, { value: "1d" }, "1 Day"),
|
|
817
|
+
/* @__PURE__ */ import_react3.default.createElement(import_material3.MenuItem, { value: "7d" }, "7 Days"),
|
|
818
|
+
/* @__PURE__ */ import_react3.default.createElement(import_material3.MenuItem, { value: "30d" }, "30 Days"),
|
|
819
|
+
/* @__PURE__ */ import_react3.default.createElement(import_material3.MenuItem, { value: "90d" }, "90 Days"),
|
|
820
|
+
/* @__PURE__ */ import_react3.default.createElement(import_material3.MenuItem, { value: "1y" }, "1 Year")
|
|
821
|
+
), teams.length > 0 ? /* @__PURE__ */ import_react3.default.createElement(
|
|
822
|
+
import_material3.Autocomplete,
|
|
615
823
|
{
|
|
616
824
|
options: teams,
|
|
617
825
|
getOptionLabel: (t) => t.team_alias || t.team_id,
|
|
618
826
|
value: selectedTeam,
|
|
619
|
-
onChange: (_e, team) =>
|
|
620
|
-
|
|
621
|
-
|
|
827
|
+
onChange: (_e, team) => {
|
|
828
|
+
const teamModels = team?.models;
|
|
829
|
+
const restrictedModels = teamModels && teamModels.length > 0 ? (formData.models || []).filter((m) => teamModels.includes(m)) : formData.models;
|
|
830
|
+
setFormData({ ...formData, team_id: team?.team_id, models: restrictedModels });
|
|
831
|
+
},
|
|
832
|
+
renderInput: (params) => /* @__PURE__ */ import_react3.default.createElement(
|
|
833
|
+
import_material3.TextField,
|
|
622
834
|
{
|
|
623
835
|
...params,
|
|
624
836
|
label: "Team",
|
|
625
|
-
|
|
837
|
+
error: teamError,
|
|
838
|
+
helperText: teamError ? "Team is required" : teamRequired ? "Bind this key to a team for scoped access" : "Optional: bind this key to a specific team for scoped access",
|
|
839
|
+
required: teamRequired,
|
|
626
840
|
fullWidth: true
|
|
627
841
|
}
|
|
628
842
|
)
|
|
629
843
|
}
|
|
630
|
-
),
|
|
631
|
-
|
|
844
|
+
) : teamRequired ? /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "body2", color: "error" }, "Team selection is required, but you don't belong to any team yet \u2014 contact your administrator.") : null, availableModels.length > 0 && /* @__PURE__ */ import_react3.default.createElement(
|
|
845
|
+
import_material3.Autocomplete,
|
|
632
846
|
{
|
|
633
847
|
multiple: true,
|
|
634
|
-
options:
|
|
848
|
+
options: availableModels,
|
|
635
849
|
groupBy: (m) => m.mode || "other",
|
|
636
850
|
getOptionLabel: (m) => m.model_name,
|
|
637
851
|
value: selectedModels,
|
|
638
852
|
onChange: (_e, selected) => setFormData({ ...formData, models: selected.map((m) => m.model_name) }),
|
|
639
|
-
renderOption: (props, m) => /* @__PURE__ */
|
|
640
|
-
renderInput: (params) => /* @__PURE__ */
|
|
853
|
+
renderOption: (props, m) => /* @__PURE__ */ import_react3.default.createElement("li", { ...props }, modelOption(m)),
|
|
854
|
+
renderInput: (params) => /* @__PURE__ */ import_react3.default.createElement(
|
|
855
|
+
import_material3.TextField,
|
|
856
|
+
{
|
|
857
|
+
...params,
|
|
858
|
+
label: "Models",
|
|
859
|
+
helperText: selectedTeam ? "Leave empty to allow all models available to this team" : "Leave empty to allow all models",
|
|
860
|
+
fullWidth: true
|
|
861
|
+
}
|
|
862
|
+
)
|
|
641
863
|
}
|
|
642
|
-
), /* @__PURE__ */
|
|
643
|
-
|
|
864
|
+
), allowUnlimitedBudget && /* @__PURE__ */ import_react3.default.createElement(
|
|
865
|
+
import_material3.FormControlLabel,
|
|
644
866
|
{
|
|
645
|
-
control: /* @__PURE__ */
|
|
646
|
-
|
|
867
|
+
control: /* @__PURE__ */ import_react3.default.createElement(
|
|
868
|
+
import_material3.Checkbox,
|
|
647
869
|
{
|
|
648
870
|
checked: unlimitedBudget,
|
|
649
871
|
onChange: (e) => {
|
|
@@ -658,8 +880,8 @@ var init_KeysTable = __esm({
|
|
|
658
880
|
),
|
|
659
881
|
label: "Unlimited budget"
|
|
660
882
|
}
|
|
661
|
-
), /* @__PURE__ */
|
|
662
|
-
|
|
883
|
+
), /* @__PURE__ */ import_react3.default.createElement(
|
|
884
|
+
import_material3.TextField,
|
|
663
885
|
{
|
|
664
886
|
label: "Max Budget (USD)",
|
|
665
887
|
type: "number",
|
|
@@ -671,141 +893,59 @@ var init_KeysTable = __esm({
|
|
|
671
893
|
required: true,
|
|
672
894
|
fullWidth: true
|
|
673
895
|
}
|
|
674
|
-
), /* @__PURE__ */
|
|
675
|
-
|
|
896
|
+
), /* @__PURE__ */ import_react3.default.createElement(
|
|
897
|
+
import_material3.TextField,
|
|
676
898
|
{
|
|
677
899
|
label: "TPM Limit",
|
|
678
900
|
type: "number",
|
|
679
901
|
value: formData.tpm_limit ?? "",
|
|
680
902
|
onChange: (e) => setFormData({ ...formData, tpm_limit: e.target.value ? Number(e.target.value) : void 0 }),
|
|
903
|
+
helperText: "Max tokens per minute this key can consume across all models. Leave blank for no limit.",
|
|
681
904
|
fullWidth: true
|
|
682
905
|
}
|
|
683
|
-
), /* @__PURE__ */
|
|
684
|
-
|
|
906
|
+
), /* @__PURE__ */ import_react3.default.createElement(
|
|
907
|
+
import_material3.TextField,
|
|
685
908
|
{
|
|
686
909
|
label: "RPM Limit",
|
|
687
910
|
type: "number",
|
|
688
911
|
value: formData.rpm_limit ?? "",
|
|
689
912
|
onChange: (e) => setFormData({ ...formData, rpm_limit: e.target.value ? Number(e.target.value) : void 0 }),
|
|
913
|
+
helperText: "Max requests per minute this key can make across all models. Leave blank for no limit.",
|
|
690
914
|
fullWidth: true
|
|
691
915
|
}
|
|
692
|
-
))), /* @__PURE__ */
|
|
693
|
-
|
|
916
|
+
))), /* @__PURE__ */ import_react3.default.createElement(import_material3.DialogActions, null, newKeyValue ? /* @__PURE__ */ import_react3.default.createElement(import_material3.Button, { onClick: handleClose, variant: "contained", color: "success" }, "Done") : /* @__PURE__ */ import_react3.default.createElement(import_react3.default.Fragment, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.Button, { onClick: handleClose }, "Cancel"), /* @__PURE__ */ import_react3.default.createElement(
|
|
917
|
+
import_material3.Button,
|
|
694
918
|
{
|
|
695
919
|
onClick: handleGenerate,
|
|
696
920
|
variant: "contained",
|
|
697
921
|
color: "primary",
|
|
698
922
|
disabled: !canGenerate
|
|
699
923
|
},
|
|
700
|
-
submitting ? /* @__PURE__ */
|
|
701
|
-
)))), /* @__PURE__ */ import_react2.default.createElement(import_material2.Dialog, { open: !!editingKey, onClose: handleCloseEdit, maxWidth: "sm", fullWidth: true }, /* @__PURE__ */ import_react2.default.createElement(import_material2.DialogTitle, null, "Edit Key"), /* @__PURE__ */ import_react2.default.createElement(import_material2.DialogContent, null, editingKey && /* @__PURE__ */ import_react2.default.createElement(import_material2.Box, { display: "flex", flexDirection: "column", gap: 2, mt: 1 }, /* @__PURE__ */ import_react2.default.createElement(import_material2.Typography, { variant: "body2", color: "text.secondary" }, /* @__PURE__ */ import_react2.default.createElement("code", { style: { fontFamily: "monospace", color: "inherit" } }, maskKey(editingKey.key))), /* @__PURE__ */ import_react2.default.createElement(
|
|
702
|
-
import_material2.TextField,
|
|
703
|
-
{
|
|
704
|
-
label: "Alias",
|
|
705
|
-
value: editForm.key_alias || "",
|
|
706
|
-
onChange: (e) => setEditForm({ ...editForm, key_alias: e.target.value }),
|
|
707
|
-
fullWidth: true
|
|
708
|
-
}
|
|
709
|
-
), models.length > 0 && /* @__PURE__ */ import_react2.default.createElement(
|
|
710
|
-
import_material2.Autocomplete,
|
|
711
|
-
{
|
|
712
|
-
multiple: true,
|
|
713
|
-
options: models,
|
|
714
|
-
groupBy: (m) => m.mode || "other",
|
|
715
|
-
getOptionLabel: (m) => m.model_name,
|
|
716
|
-
value: editSelectedModels,
|
|
717
|
-
onChange: (_e, selected) => setEditForm({ ...editForm, models: selected.map((m) => m.model_name) }),
|
|
718
|
-
renderOption: (props, m) => /* @__PURE__ */ import_react2.default.createElement("li", { ...props }, modelOption(m)),
|
|
719
|
-
renderInput: (params) => /* @__PURE__ */ import_react2.default.createElement(import_material2.TextField, { ...params, label: "Models", fullWidth: true })
|
|
720
|
-
}
|
|
721
|
-
), /* @__PURE__ */ import_react2.default.createElement(
|
|
722
|
-
import_material2.TextField,
|
|
723
|
-
{
|
|
724
|
-
label: "Max Budget (USD)",
|
|
725
|
-
type: "number",
|
|
726
|
-
value: editForm.max_budget ?? "",
|
|
727
|
-
onChange: (e) => setEditForm({ ...editForm, max_budget: e.target.value ? Number(e.target.value) : void 0 }),
|
|
728
|
-
fullWidth: true
|
|
729
|
-
}
|
|
730
|
-
), /* @__PURE__ */ import_react2.default.createElement(
|
|
731
|
-
import_material2.TextField,
|
|
732
|
-
{
|
|
733
|
-
label: "TPM Limit",
|
|
734
|
-
type: "number",
|
|
735
|
-
value: editForm.tpm_limit ?? "",
|
|
736
|
-
onChange: (e) => setEditForm({ ...editForm, tpm_limit: e.target.value ? Number(e.target.value) : void 0 }),
|
|
737
|
-
fullWidth: true
|
|
738
|
-
}
|
|
739
|
-
), /* @__PURE__ */ import_react2.default.createElement(
|
|
740
|
-
import_material2.TextField,
|
|
741
|
-
{
|
|
742
|
-
label: "RPM Limit",
|
|
743
|
-
type: "number",
|
|
744
|
-
value: editForm.rpm_limit ?? "",
|
|
745
|
-
onChange: (e) => setEditForm({ ...editForm, rpm_limit: e.target.value ? Number(e.target.value) : void 0 }),
|
|
746
|
-
fullWidth: true
|
|
747
|
-
}
|
|
748
|
-
), /* @__PURE__ */ import_react2.default.createElement(import_material2.Box, { mt: 1, pt: 2, borderTop: "1px solid", sx: { borderColor: "divider" } }, /* @__PURE__ */ import_react2.default.createElement(import_material2.Typography, { variant: "caption", color: "text.secondary", display: "block", mb: 1 }, "Danger Zone"), !resetSpendConfirm ? /* @__PURE__ */ import_react2.default.createElement(
|
|
749
|
-
import_material2.Button,
|
|
750
|
-
{
|
|
751
|
-
size: "small",
|
|
752
|
-
color: "warning",
|
|
753
|
-
variant: "outlined",
|
|
754
|
-
onClick: () => setResetSpendConfirm(true)
|
|
755
|
-
},
|
|
756
|
-
"Reset Spend to $0"
|
|
757
|
-
) : /* @__PURE__ */ import_react2.default.createElement(import_material2.Box, { display: "flex", alignItems: "center", gap: 1, flexWrap: "wrap" }, /* @__PURE__ */ import_react2.default.createElement(import_material2.Typography, { variant: "body2", color: "warning.main" }, "Zero out spend counter?"), /* @__PURE__ */ import_react2.default.createElement(
|
|
758
|
-
import_material2.Button,
|
|
759
|
-
{
|
|
760
|
-
size: "small",
|
|
761
|
-
color: "warning",
|
|
762
|
-
variant: "contained",
|
|
763
|
-
disabled: resetSpendSubmitting,
|
|
764
|
-
onClick: handleResetSpend
|
|
765
|
-
},
|
|
766
|
-
resetSpendSubmitting ? /* @__PURE__ */ import_react2.default.createElement(import_material2.CircularProgress, { size: 16 }) : "Confirm"
|
|
767
|
-
), /* @__PURE__ */ import_react2.default.createElement(import_material2.Button, { size: "small", onClick: () => setResetSpendConfirm(false) }, "Cancel"))))), /* @__PURE__ */ import_react2.default.createElement(import_material2.DialogActions, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.Button, { onClick: handleCloseEdit }, "Cancel"), /* @__PURE__ */ import_react2.default.createElement(import_material2.Button, { onClick: handleUpdate, variant: "contained", color: "primary", disabled: editSubmitting }, editSubmitting ? /* @__PURE__ */ import_react2.default.createElement(import_material2.CircularProgress, { size: 24 }) : "Save"))), /* @__PURE__ */ import_react2.default.createElement(import_material2.Dialog, { open: !!deleteConfirmId, onClose: () => setDeleteConfirmId(null), maxWidth: "xs", fullWidth: true }, /* @__PURE__ */ import_react2.default.createElement(import_material2.DialogTitle, null, "Revoke Key?"), /* @__PURE__ */ import_react2.default.createElement(import_material2.DialogContent, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.Typography, null, "This will permanently revoke the key. Any integrations using it will stop working immediately.")), /* @__PURE__ */ import_react2.default.createElement(import_material2.DialogActions, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.Button, { onClick: () => setDeleteConfirmId(null), disabled: deleteSubmitting }, "Cancel"), /* @__PURE__ */ import_react2.default.createElement(
|
|
768
|
-
import_material2.Button,
|
|
769
|
-
{
|
|
770
|
-
onClick: handleConfirmDelete,
|
|
771
|
-
variant: "contained",
|
|
772
|
-
color: "error",
|
|
773
|
-
disabled: deleteSubmitting
|
|
774
|
-
},
|
|
775
|
-
deleteSubmitting ? /* @__PURE__ */ import_react2.default.createElement(import_material2.CircularProgress, { size: 20 }) : "Revoke"
|
|
776
|
-
))), /* @__PURE__ */ import_react2.default.createElement(import_material2.Dialog, { open: pruneConfirmCount !== null, onClose: () => setPruneConfirmCount(null), maxWidth: "xs", fullWidth: true }, /* @__PURE__ */ import_react2.default.createElement(import_material2.DialogTitle, null, "Prune Expired Keys?"), /* @__PURE__ */ import_react2.default.createElement(import_material2.DialogContent, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.Typography, null, "This will permanently delete ", pruneConfirmCount, " expired key", pruneConfirmCount !== 1 ? "s" : "", " from LiteLLM. Any integrations using them will stop working immediately.")), /* @__PURE__ */ import_react2.default.createElement(import_material2.DialogActions, null, /* @__PURE__ */ import_react2.default.createElement(import_material2.Button, { onClick: () => setPruneConfirmCount(null), disabled: pruneSubmitting }, "Cancel"), /* @__PURE__ */ import_react2.default.createElement(
|
|
777
|
-
import_material2.Button,
|
|
778
|
-
{
|
|
779
|
-
onClick: handlePruneExpired,
|
|
780
|
-
variant: "contained",
|
|
781
|
-
color: "error",
|
|
782
|
-
disabled: pruneSubmitting
|
|
783
|
-
},
|
|
784
|
-
pruneSubmitting ? /* @__PURE__ */ import_react2.default.createElement(import_material2.CircularProgress, { size: 20 }) : "Prune"
|
|
924
|
+
submitting ? /* @__PURE__ */ import_react3.default.createElement(import_material3.CircularProgress, { size: 24 }) : "Generate"
|
|
785
925
|
))));
|
|
786
926
|
};
|
|
787
927
|
SnippetTabs = ({ snippets, model, onCopy }) => {
|
|
788
|
-
const [tab, setTab] = (0,
|
|
928
|
+
const [tab, setTab] = (0, import_react3.useState)("curl");
|
|
789
929
|
const code = tab === "curl" ? snippets.curl : snippets.openai;
|
|
790
|
-
return /* @__PURE__ */
|
|
791
|
-
|
|
930
|
+
return /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.Tabs, { value: tab, onChange: (_, v) => setTab(v), sx: { mb: 1 } }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Tab, { label: "curl", value: "curl" }), /* @__PURE__ */ import_react3.default.createElement(import_material3.Tab, { label: "OpenAI SDK", value: "openai" })), /* @__PURE__ */ import_react3.default.createElement(
|
|
931
|
+
import_material3.Box,
|
|
792
932
|
{
|
|
793
933
|
position: "relative",
|
|
794
934
|
p: 1.5,
|
|
795
935
|
sx: { backgroundColor: "action.hover", border: "1px solid", borderColor: "divider", borderRadius: 1 }
|
|
796
936
|
},
|
|
797
|
-
/* @__PURE__ */
|
|
798
|
-
|
|
937
|
+
/* @__PURE__ */ import_react3.default.createElement(
|
|
938
|
+
import_material3.IconButton,
|
|
799
939
|
{
|
|
800
940
|
size: "small",
|
|
801
941
|
onClick: () => onCopy(code),
|
|
802
942
|
title: "Copy snippet",
|
|
803
943
|
sx: { position: "absolute", top: 4, right: 4 }
|
|
804
944
|
},
|
|
805
|
-
/* @__PURE__ */
|
|
945
|
+
/* @__PURE__ */ import_react3.default.createElement(import_icons_material3.ContentCopy, { fontSize: "small" })
|
|
806
946
|
),
|
|
807
|
-
/* @__PURE__ */
|
|
808
|
-
|
|
947
|
+
/* @__PURE__ */ import_react3.default.createElement(
|
|
948
|
+
import_material3.Typography,
|
|
809
949
|
{
|
|
810
950
|
component: "pre",
|
|
811
951
|
sx: {
|
|
@@ -819,7 +959,7 @@ var init_KeysTable = __esm({
|
|
|
819
959
|
},
|
|
820
960
|
code
|
|
821
961
|
),
|
|
822
|
-
model && /* @__PURE__ */
|
|
962
|
+
model && /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "caption", color: "text.secondary", display: "block", mt: 1 }, "Using model \u201C", model, "\u201D \u2014 swap it for any model you have access to.")
|
|
823
963
|
));
|
|
824
964
|
};
|
|
825
965
|
}
|
|
@@ -831,12 +971,12 @@ function modelColor(model) {
|
|
|
831
971
|
for (let i = 0; i < model.length; i++) h = (h << 5) + h + model.charCodeAt(i) >>> 0;
|
|
832
972
|
return MODEL_COLORS[h % MODEL_COLORS.length];
|
|
833
973
|
}
|
|
834
|
-
var
|
|
974
|
+
var import_react4, import_material4, import_recharts, TOP_N_MODELS, PERIOD_LS_KEY, MODEL_COLORS, fmtPct, KpiCard, ChartSkeleton, EmptyChart, UsageStats;
|
|
835
975
|
var init_UsageStats = __esm({
|
|
836
976
|
"src/components/UsageStats.tsx"() {
|
|
837
977
|
"use strict";
|
|
838
|
-
|
|
839
|
-
|
|
978
|
+
import_react4 = __toESM(require("react"));
|
|
979
|
+
import_material4 = require("@mui/material");
|
|
840
980
|
import_recharts = require("recharts");
|
|
841
981
|
init_format();
|
|
842
982
|
TOP_N_MODELS = 6;
|
|
@@ -854,12 +994,12 @@ var init_UsageStats = __esm({
|
|
|
854
994
|
"#d0ed57"
|
|
855
995
|
];
|
|
856
996
|
fmtPct = (n) => `${(n * 100).toFixed(1)}%`;
|
|
857
|
-
KpiCard = ({ label, value, hint }) => /* @__PURE__ */
|
|
858
|
-
ChartSkeleton = ({ height = 260 }) => /* @__PURE__ */
|
|
997
|
+
KpiCard = ({ label, value, hint }) => /* @__PURE__ */ import_react4.default.createElement(import_material4.Paper, { variant: "outlined", sx: { p: 2, height: "100%" } }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "caption", color: "text.secondary" }, label), /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "h5", sx: { mt: 0.5 } }, value), hint ? /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "caption", color: "text.secondary" }, hint) : null);
|
|
998
|
+
ChartSkeleton = ({ height = 260 }) => /* @__PURE__ */ import_react4.default.createElement(import_material4.Skeleton, { variant: "rectangular", height, sx: { borderRadius: 1 } });
|
|
859
999
|
EmptyChart = ({
|
|
860
1000
|
height = 260,
|
|
861
1001
|
message = "No data for this period"
|
|
862
|
-
}) => /* @__PURE__ */
|
|
1002
|
+
}) => /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { height, display: "flex", alignItems: "center", justifyContent: "center" }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { color: "text.secondary", variant: "body2" }, message));
|
|
863
1003
|
UsageStats = ({
|
|
864
1004
|
usage,
|
|
865
1005
|
models,
|
|
@@ -868,15 +1008,15 @@ var init_UsageStats = __esm({
|
|
|
868
1008
|
loading,
|
|
869
1009
|
userInfo
|
|
870
1010
|
}) => {
|
|
871
|
-
const theme = (0,
|
|
872
|
-
const [selectedModel, setSelectedModel] = (0,
|
|
873
|
-
const [tab, setTab] = (0,
|
|
1011
|
+
const theme = (0, import_material4.useTheme)();
|
|
1012
|
+
const [selectedModel, setSelectedModel] = (0, import_react4.useState)("all");
|
|
1013
|
+
const [tab, setTab] = (0, import_react4.useState)("costs");
|
|
874
1014
|
const gridStroke = theme.palette.divider;
|
|
875
1015
|
const tickFill = theme.palette.text.secondary;
|
|
876
1016
|
const tickStyle = { fontSize: 12, fill: tickFill };
|
|
877
1017
|
const tickStyleSmall = { fontSize: 11, fill: tickFill };
|
|
878
1018
|
const tickStyleTiny = { fontSize: 10, fill: tickFill };
|
|
879
|
-
const selectedPreset = (0,
|
|
1019
|
+
const selectedPreset = (0, import_react4.useMemo)(() => {
|
|
880
1020
|
if (dateRange.start.toDateString() === dateRange.end.toDateString()) return "today";
|
|
881
1021
|
const diffMs = dateRange.end.getTime() - dateRange.start.getTime();
|
|
882
1022
|
const diffHours = diffMs / (1e3 * 60 * 60);
|
|
@@ -898,7 +1038,7 @@ var init_UsageStats = __esm({
|
|
|
898
1038
|
}
|
|
899
1039
|
onDateRangeChange({ start, end });
|
|
900
1040
|
};
|
|
901
|
-
const dailyData = (0,
|
|
1041
|
+
const dailyData = (0, import_react4.useMemo)(
|
|
902
1042
|
() => (usage?.daily_usage ?? []).map((d) => ({
|
|
903
1043
|
date: d.date,
|
|
904
1044
|
spend: d.spend,
|
|
@@ -911,21 +1051,21 @@ var init_UsageStats = __esm({
|
|
|
911
1051
|
})),
|
|
912
1052
|
[usage]
|
|
913
1053
|
);
|
|
914
|
-
const cumulativeData = (0,
|
|
1054
|
+
const cumulativeData = (0, import_react4.useMemo)(() => {
|
|
915
1055
|
let cum = 0;
|
|
916
1056
|
return dailyData.map((d) => {
|
|
917
1057
|
cum += d.spend;
|
|
918
1058
|
return { date: d.date, cumulative: cum };
|
|
919
1059
|
});
|
|
920
1060
|
}, [dailyData]);
|
|
921
|
-
const successRateData = (0,
|
|
1061
|
+
const successRateData = (0, import_react4.useMemo)(
|
|
922
1062
|
() => dailyData.filter((d) => d.apiRequests > 0).map((d) => ({
|
|
923
1063
|
date: d.date,
|
|
924
1064
|
successRate: parseFloat((d.successfulRequests / d.apiRequests * 100).toFixed(1))
|
|
925
1065
|
})),
|
|
926
1066
|
[dailyData]
|
|
927
1067
|
);
|
|
928
|
-
const { modelSpendByDate, topModels: topSpendModels } = (0,
|
|
1068
|
+
const { modelSpendByDate, topModels: topSpendModels } = (0, import_react4.useMemo)(() => {
|
|
929
1069
|
const rows = usage?.daily_by_model ?? [];
|
|
930
1070
|
const modelTotals = {};
|
|
931
1071
|
for (const r of rows) modelTotals[r.model] = (modelTotals[r.model] ?? 0) + r.spend;
|
|
@@ -940,7 +1080,7 @@ var init_UsageStats = __esm({
|
|
|
940
1080
|
const hasOther = sorted.some((r) => r.Other > 0);
|
|
941
1081
|
return { modelSpendByDate: sorted, topModels: hasOther ? [...top, "Other"] : top };
|
|
942
1082
|
}, [usage]);
|
|
943
|
-
const modelRows = (0,
|
|
1083
|
+
const modelRows = (0, import_react4.useMemo)(() => {
|
|
944
1084
|
const entries = Object.entries(usage?.usage_by_model ?? {}).map(([model, d]) => ({
|
|
945
1085
|
model,
|
|
946
1086
|
spend: d.total_spend,
|
|
@@ -954,11 +1094,11 @@ var init_UsageStats = __esm({
|
|
|
954
1094
|
}));
|
|
955
1095
|
return selectedModel === "all" ? entries : entries.filter((e) => e.model === selectedModel);
|
|
956
1096
|
}, [usage, selectedModel]);
|
|
957
|
-
const topModelSpendBars = (0,
|
|
1097
|
+
const topModelSpendBars = (0, import_react4.useMemo)(
|
|
958
1098
|
() => [...modelRows].sort((a, b) => b.spend - a.spend).slice(0, 10),
|
|
959
1099
|
[modelRows]
|
|
960
1100
|
);
|
|
961
|
-
const keyRows = (0,
|
|
1101
|
+
const keyRows = (0, import_react4.useMemo)(
|
|
962
1102
|
() => Object.entries(usage?.usage_by_key ?? {}).map(([keyHash, d]) => ({
|
|
963
1103
|
keyHash,
|
|
964
1104
|
keyAlias: d.key_alias ?? keyHash.slice(0, 8),
|
|
@@ -975,7 +1115,7 @@ var init_UsageStats = __esm({
|
|
|
975
1115
|
})),
|
|
976
1116
|
[usage]
|
|
977
1117
|
);
|
|
978
|
-
const topKeySpendBars = (0,
|
|
1118
|
+
const topKeySpendBars = (0, import_react4.useMemo)(
|
|
979
1119
|
() => [...keyRows].sort((a, b) => b.spend - a.spend).slice(0, 10),
|
|
980
1120
|
[keyRows]
|
|
981
1121
|
);
|
|
@@ -983,34 +1123,34 @@ var init_UsageStats = __esm({
|
|
|
983
1123
|
const overallSuccessRate = totalRequests > 0 ? (usage?.successful_requests ?? 0) / totalRequests : 0;
|
|
984
1124
|
const maxBudget = userInfo?.max_budget ?? 0;
|
|
985
1125
|
const totalCumSpend = cumulativeData[cumulativeData.length - 1]?.cumulative ?? 0;
|
|
986
|
-
return /* @__PURE__ */
|
|
987
|
-
|
|
1126
|
+
return /* @__PURE__ */ import_react4.default.createElement(import_material4.Paper, { sx: { p: 2 } }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { display: "flex", justifyContent: "space-between", alignItems: "center", mb: 2, flexWrap: "wrap", gap: 2 }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "h6" }, "Usage Analytics"), /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { display: "flex", gap: 2, alignItems: "center", flexWrap: "wrap" }, /* @__PURE__ */ import_react4.default.createElement(import_material4.FormControl, { size: "small", sx: { minWidth: 140 } }, /* @__PURE__ */ import_react4.default.createElement(import_material4.InputLabel, null, "Period"), /* @__PURE__ */ import_react4.default.createElement(
|
|
1127
|
+
import_material4.Select,
|
|
988
1128
|
{
|
|
989
1129
|
value: selectedPreset,
|
|
990
1130
|
label: "Period",
|
|
991
1131
|
onChange: (e) => handlePresetChange(e.target.value)
|
|
992
1132
|
},
|
|
993
|
-
/* @__PURE__ */
|
|
994
|
-
/* @__PURE__ */
|
|
995
|
-
/* @__PURE__ */
|
|
996
|
-
/* @__PURE__ */
|
|
997
|
-
)), tab === "models" && /* @__PURE__ */
|
|
998
|
-
|
|
1133
|
+
/* @__PURE__ */ import_react4.default.createElement(import_material4.MenuItem, { value: "today" }, "Today"),
|
|
1134
|
+
/* @__PURE__ */ import_react4.default.createElement(import_material4.MenuItem, { value: "24h" }, "Last 24h"),
|
|
1135
|
+
/* @__PURE__ */ import_react4.default.createElement(import_material4.MenuItem, { value: "7d" }, "Last 7 days"),
|
|
1136
|
+
/* @__PURE__ */ import_react4.default.createElement(import_material4.MenuItem, { value: "30d" }, "Last 30 days")
|
|
1137
|
+
)), tab === "models" && /* @__PURE__ */ import_react4.default.createElement(import_material4.FormControl, { size: "small", sx: { minWidth: 180 } }, /* @__PURE__ */ import_react4.default.createElement(import_material4.InputLabel, null, "Model"), /* @__PURE__ */ import_react4.default.createElement(
|
|
1138
|
+
import_material4.Select,
|
|
999
1139
|
{
|
|
1000
1140
|
value: selectedModel,
|
|
1001
1141
|
label: "Model",
|
|
1002
1142
|
onChange: (e) => setSelectedModel(e.target.value)
|
|
1003
1143
|
},
|
|
1004
|
-
/* @__PURE__ */
|
|
1005
|
-
models.map((m) => /* @__PURE__ */
|
|
1006
|
-
)))), /* @__PURE__ */
|
|
1144
|
+
/* @__PURE__ */ import_react4.default.createElement(import_material4.MenuItem, { value: "all" }, "All Models"),
|
|
1145
|
+
models.map((m) => /* @__PURE__ */ import_react4.default.createElement(import_material4.MenuItem, { key: m.model_name, value: m.model_name }, m.model_name))
|
|
1146
|
+
)))), /* @__PURE__ */ import_react4.default.createElement(import_material4.Grid, { container: true, spacing: 2, sx: { mb: 2 } }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Grid, { item: true, xs: 6, sm: 3 }, loading ? /* @__PURE__ */ import_react4.default.createElement(ChartSkeleton, { height: 80 }) : /* @__PURE__ */ import_react4.default.createElement(KpiCard, { label: "Total Spend", value: fmtUsd(usage?.total_spend ?? 0) })), /* @__PURE__ */ import_react4.default.createElement(import_material4.Grid, { item: true, xs: 6, sm: 3 }, loading ? /* @__PURE__ */ import_react4.default.createElement(ChartSkeleton, { height: 80 }) : /* @__PURE__ */ import_react4.default.createElement(KpiCard, { label: "Total Requests", value: fmtInt(totalRequests), hint: `${fmtInt(usage?.failed_requests ?? 0)} failed` })), /* @__PURE__ */ import_react4.default.createElement(import_material4.Grid, { item: true, xs: 6, sm: 3 }, loading ? /* @__PURE__ */ import_react4.default.createElement(ChartSkeleton, { height: 80 }) : /* @__PURE__ */ import_react4.default.createElement(KpiCard, { label: "Success Rate", value: totalRequests > 0 ? fmtPct(overallSuccessRate) : "\u2014" })), /* @__PURE__ */ import_react4.default.createElement(import_material4.Grid, { item: true, xs: 6, sm: 3 }, loading ? /* @__PURE__ */ import_react4.default.createElement(ChartSkeleton, { height: 80 }) : /* @__PURE__ */ import_react4.default.createElement(
|
|
1007
1147
|
KpiCard,
|
|
1008
1148
|
{
|
|
1009
1149
|
label: "Total Tokens",
|
|
1010
1150
|
value: fmtInt(usage?.total_tokens ?? 0),
|
|
1011
1151
|
hint: `${fmtInt(usage?.prompt_tokens ?? 0)} in \xB7 ${fmtInt(usage?.completion_tokens ?? 0)} out`
|
|
1012
1152
|
}
|
|
1013
|
-
))), /* @__PURE__ */
|
|
1153
|
+
))), /* @__PURE__ */ import_react4.default.createElement(import_material4.Tabs, { value: tab, onChange: (_, v) => setTab(v), sx: { mb: 2 } }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Tab, { value: "costs", label: "Costs" }), /* @__PURE__ */ import_react4.default.createElement(import_material4.Tab, { value: "models", label: "Model Activity" }), /* @__PURE__ */ import_react4.default.createElement(import_material4.Tab, { value: "keys", label: "Key Activity" })), tab === "costs" && /* @__PURE__ */ import_react4.default.createElement(import_material4.Grid, { container: true, spacing: 3 }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Spend by Model"), loading ? /* @__PURE__ */ import_react4.default.createElement(ChartSkeleton, null) : modelSpendByDate.length === 0 ? /* @__PURE__ */ import_react4.default.createElement(EmptyChart, null) : /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { height: 260 }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.AreaChart, { data: modelSpendByDate }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.XAxis, { dataKey: "date", tick: tickStyle }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.YAxis, { tick: tickStyle, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Tooltip, { formatter: (v) => [`$${v.toFixed(4)}`, void 0] }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Legend, null), topSpendModels.map((m) => /* @__PURE__ */ import_react4.default.createElement(
|
|
1014
1154
|
import_recharts.Area,
|
|
1015
1155
|
{
|
|
1016
1156
|
key: m,
|
|
@@ -1021,38 +1161,38 @@ var init_UsageStats = __esm({
|
|
|
1021
1161
|
fill: modelColor(m),
|
|
1022
1162
|
fillOpacity: 0.6
|
|
1023
1163
|
}
|
|
1024
|
-
)))))), /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Token Usage"), loading ? /* @__PURE__ */ import_react3.default.createElement(ChartSkeleton, null) : dailyData.length === 0 ? /* @__PURE__ */ import_react3.default.createElement(EmptyChart, null) : /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { height: 260 }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.AreaChart, { data: dailyData }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.XAxis, { dataKey: "date", tick: tickStyle }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.YAxis, { tick: tickStyle, tickFormatter: fmtInt }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Tooltip, { formatter: (v) => [fmtInt(v), void 0] }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Legend, null), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Area, { type: "monotone", dataKey: "promptTokens", name: "Input (prompt)", stackId: "tok", stroke: "#8884d8", fill: "#8884d8", fillOpacity: 0.5 }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Area, { type: "monotone", dataKey: "completionTokens", name: "Output (completion)", stackId: "tok", stroke: "#82ca9d", fill: "#82ca9d", fillOpacity: 0.5 }))))), /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Requests"), loading ? /* @__PURE__ */ import_react3.default.createElement(ChartSkeleton, null) : dailyData.length === 0 ? /* @__PURE__ */ import_react3.default.createElement(EmptyChart, null) : /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { height: 260 }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.BarChart, { data: dailyData }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.XAxis, { dataKey: "date", tick: tickStyle }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.YAxis, { tick: tickStyle }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Tooltip, null), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Legend, null), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Bar, { dataKey: "successfulRequests", name: "Successful", fill: "#82ca9d", stackId: "r" }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Bar, { dataKey: "failedRequests", name: "Failed", fill: "#e57373", stackId: "r" }))))), /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Success Rate"), loading ? /* @__PURE__ */ import_react3.default.createElement(ChartSkeleton, null) : successRateData.length === 0 ? /* @__PURE__ */ import_react3.default.createElement(EmptyChart, null) : /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { height: 260 }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.LineChart, { data: successRateData }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.XAxis, { dataKey: "date", tick: tickStyle }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.YAxis, { domain: [0, 100], tick: tickStyle, tickFormatter: (v) => `${v}%` }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Tooltip, { formatter: (v) => [`${v}%`, "Success rate"] }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.ReferenceLine, { y: 100, stroke: "#82ca9d", strokeDasharray: "4 2" }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Line, { type: "monotone", dataKey: "successRate", name: "Success rate", stroke: "#8884d8", dot: { r: 3 } }))))), (maxBudget > 0 || cumulativeData.length > 0) && /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 12 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Cumulative Spend", maxBudget > 0 ? ` vs Budget (${fmtUsd(maxBudget)})` : "", maxBudget > 0 && /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { component: "span", variant: "caption", color: "text.secondary", sx: { ml: 1 } }, fmtUsd(totalCumSpend), " used \xB7 ", fmtUsd(Math.max(0, maxBudget - totalCumSpend)), " remaining")), loading ? /* @__PURE__ */ import_react3.default.createElement(ChartSkeleton, { height: 180 }) : cumulativeData.length === 0 ? /* @__PURE__ */ import_react3.default.createElement(EmptyChart, { height: 180 }) : /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { height: 180 }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.AreaChart, { data: cumulativeData }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.XAxis, { dataKey: "date", tick: tickStyle }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.YAxis, { tick: tickStyle, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Tooltip, { formatter: (v) => [`$${v.toFixed(4)}`, "Cumulative spend"] }), maxBudget > 0 && /* @__PURE__ */ import_react3.default.createElement(import_recharts.ReferenceLine, { y: maxBudget, stroke: "#e57373", strokeDasharray: "6 3", label: { value: `Budget ${fmtUsd(maxBudget)}`, position: "insideTopRight", fontSize: 11 } }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Area, { type: "monotone", dataKey: "cumulative", name: "Cumulative spend", stroke: "#ffc658", fill: "#ffc658", fillOpacity: 0.3 })))))), tab === "models" && /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { container: true, spacing: 3 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 12 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Spend by Model"), loading ? /* @__PURE__ */ import_react3.default.createElement(ChartSkeleton, null) : topModelSpendBars.length === 0 ? /* @__PURE__ */ import_react3.default.createElement(EmptyChart, null) : /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { height: Math.max(200, topModelSpendBars.length * 36) }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.BarChart, { data: topModelSpendBars, layout: "vertical", margin: { left: 20 } }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.XAxis, { type: "number", tick: tickStyle, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.YAxis, { type: "category", dataKey: "model", tick: tickStyleSmall, width: 200 }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Tooltip, { formatter: (v) => [fmtUsd(v), "Spend"] }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Bar, { dataKey: "spend", name: "Spend", radius: [0, 4, 4, 0] }, topModelSpendBars.map((r) => /* @__PURE__ */ import_react3.default.createElement("rect", { key: r.model, fill: modelColor(r.model) }))))))), /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 12 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Tokens by Model"), loading ? /* @__PURE__ */ import_react3.default.createElement(ChartSkeleton, null) : modelRows.length === 0 ? /* @__PURE__ */ import_react3.default.createElement(EmptyChart, null) : /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { height: 260 }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.BarChart, { data: modelRows }, /* @__PURE__ */ import_react3.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.XAxis, { dataKey: "model", tick: tickStyleTiny, interval: 0, angle: -15, textAnchor: "end", height: 60 }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.YAxis, { tick: tickStyle }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Tooltip, null), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Legend, null), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Bar, { dataKey: "promptTokens", name: "Prompt", fill: "#8884d8", stackId: "t" }), /* @__PURE__ */ import_react3.default.createElement(import_recharts.Bar, { dataKey: "completionTokens", name: "Completion", fill: "#82ca9d", stackId: "t" }))))), /* @__PURE__ */ import_react3.default.createElement(import_material3.Grid, { item: true, xs: 12 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableContainer, { component: import_material3.Paper, variant: "outlined" }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Table, { size: "small" }, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableHead, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableRow, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, null, "Model"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, "Spend"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, "Requests"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, "Success"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, "Failed"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, "Prompt"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, "Completion"), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { sx: { minWidth: 120 } }, "Success rate"))), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableBody, null, loading ? /* @__PURE__ */ import_react3.default.createElement(import_material3.TableRow, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { colSpan: 8 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.LinearProgress, null))) : modelRows.length === 0 ? /* @__PURE__ */ import_react3.default.createElement(import_material3.TableRow, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { colSpan: 8, align: "center" }, "No model activity")) : [...modelRows].sort((a, b) => b.spend - a.spend || b.totalTokens - a.totalTokens).map((r) => /* @__PURE__ */ import_react3.default.createElement(import_material3.TableRow, { key: r.model }, /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { display: "flex", alignItems: "center", gap: 0.75 }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { sx: { width: 10, height: 10, borderRadius: "50%", bgcolor: modelColor(r.model), flexShrink: 0 } }), r.model)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, fmtUsd(r.spend)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, fmtInt(r.apiRequests)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, fmtInt(r.successfulRequests)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, fmtInt(r.failedRequests)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, fmtInt(r.promptTokens)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, { align: "right" }, fmtInt(r.completionTokens)), /* @__PURE__ */ import_react3.default.createElement(import_material3.TableCell, null, r.apiRequests > 0 ? /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ import_react3.default.createElement(
|
|
1025
|
-
|
|
1164
|
+
)))))), /* @__PURE__ */ import_react4.default.createElement(import_material4.Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Token Usage"), loading ? /* @__PURE__ */ import_react4.default.createElement(ChartSkeleton, null) : dailyData.length === 0 ? /* @__PURE__ */ import_react4.default.createElement(EmptyChart, null) : /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { height: 260 }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.AreaChart, { data: dailyData }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.XAxis, { dataKey: "date", tick: tickStyle }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.YAxis, { tick: tickStyle, tickFormatter: fmtInt }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Tooltip, { formatter: (v) => [fmtInt(v), void 0] }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Legend, null), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Area, { type: "monotone", dataKey: "promptTokens", name: "Input (prompt)", stackId: "tok", stroke: "#8884d8", fill: "#8884d8", fillOpacity: 0.5 }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Area, { type: "monotone", dataKey: "completionTokens", name: "Output (completion)", stackId: "tok", stroke: "#82ca9d", fill: "#82ca9d", fillOpacity: 0.5 }))))), /* @__PURE__ */ import_react4.default.createElement(import_material4.Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Requests"), loading ? /* @__PURE__ */ import_react4.default.createElement(ChartSkeleton, null) : dailyData.length === 0 ? /* @__PURE__ */ import_react4.default.createElement(EmptyChart, null) : /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { height: 260 }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.BarChart, { data: dailyData }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.XAxis, { dataKey: "date", tick: tickStyle }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.YAxis, { tick: tickStyle }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Tooltip, null), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Legend, null), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Bar, { dataKey: "successfulRequests", name: "Successful", fill: "#82ca9d", stackId: "r" }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Bar, { dataKey: "failedRequests", name: "Failed", fill: "#e57373", stackId: "r" }))))), /* @__PURE__ */ import_react4.default.createElement(import_material4.Grid, { item: true, xs: 12, md: 6 }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Success Rate"), loading ? /* @__PURE__ */ import_react4.default.createElement(ChartSkeleton, null) : successRateData.length === 0 ? /* @__PURE__ */ import_react4.default.createElement(EmptyChart, null) : /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { height: 260 }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.LineChart, { data: successRateData }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.XAxis, { dataKey: "date", tick: tickStyle }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.YAxis, { domain: [0, 100], tick: tickStyle, tickFormatter: (v) => `${v}%` }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Tooltip, { formatter: (v) => [`${v}%`, "Success rate"] }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.ReferenceLine, { y: 100, stroke: "#82ca9d", strokeDasharray: "4 2" }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Line, { type: "monotone", dataKey: "successRate", name: "Success rate", stroke: "#8884d8", dot: { r: 3 } }))))), (maxBudget > 0 || cumulativeData.length > 0) && /* @__PURE__ */ import_react4.default.createElement(import_material4.Grid, { item: true, xs: 12 }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Cumulative Spend", maxBudget > 0 ? ` vs Budget (${fmtUsd(maxBudget)})` : "", maxBudget > 0 && /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { component: "span", variant: "caption", color: "text.secondary", sx: { ml: 1 } }, fmtUsd(totalCumSpend), " used \xB7 ", fmtUsd(Math.max(0, maxBudget - totalCumSpend)), " remaining")), loading ? /* @__PURE__ */ import_react4.default.createElement(ChartSkeleton, { height: 180 }) : cumulativeData.length === 0 ? /* @__PURE__ */ import_react4.default.createElement(EmptyChart, { height: 180 }) : /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { height: 180 }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.AreaChart, { data: cumulativeData }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.XAxis, { dataKey: "date", tick: tickStyle }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.YAxis, { tick: tickStyle, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Tooltip, { formatter: (v) => [`$${v.toFixed(4)}`, "Cumulative spend"] }), maxBudget > 0 && /* @__PURE__ */ import_react4.default.createElement(import_recharts.ReferenceLine, { y: maxBudget, stroke: "#e57373", strokeDasharray: "6 3", label: { value: `Budget ${fmtUsd(maxBudget)}`, position: "insideTopRight", fontSize: 11 } }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Area, { type: "monotone", dataKey: "cumulative", name: "Cumulative spend", stroke: "#ffc658", fill: "#ffc658", fillOpacity: 0.3 })))))), tab === "models" && /* @__PURE__ */ import_react4.default.createElement(import_material4.Grid, { container: true, spacing: 3 }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Grid, { item: true, xs: 12 }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Spend by Model"), loading ? /* @__PURE__ */ import_react4.default.createElement(ChartSkeleton, null) : topModelSpendBars.length === 0 ? /* @__PURE__ */ import_react4.default.createElement(EmptyChart, null) : /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { height: Math.max(200, topModelSpendBars.length * 36) }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.BarChart, { data: topModelSpendBars, layout: "vertical", margin: { left: 20 } }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.XAxis, { type: "number", tick: tickStyle, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.YAxis, { type: "category", dataKey: "model", tick: tickStyleSmall, width: 200 }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Tooltip, { formatter: (v) => [fmtUsd(v), "Spend"] }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Bar, { dataKey: "spend", name: "Spend", radius: [0, 4, 4, 0] }, topModelSpendBars.map((r) => /* @__PURE__ */ import_react4.default.createElement("rect", { key: r.model, fill: modelColor(r.model) }))))))), /* @__PURE__ */ import_react4.default.createElement(import_material4.Grid, { item: true, xs: 12 }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Tokens by Model"), loading ? /* @__PURE__ */ import_react4.default.createElement(ChartSkeleton, null) : modelRows.length === 0 ? /* @__PURE__ */ import_react4.default.createElement(EmptyChart, null) : /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { height: 260 }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.BarChart, { data: modelRows }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.XAxis, { dataKey: "model", tick: tickStyleTiny, interval: 0, angle: -15, textAnchor: "end", height: 60 }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.YAxis, { tick: tickStyle }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Tooltip, null), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Legend, null), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Bar, { dataKey: "promptTokens", name: "Prompt", fill: "#8884d8", stackId: "t" }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Bar, { dataKey: "completionTokens", name: "Completion", fill: "#82ca9d", stackId: "t" }))))), /* @__PURE__ */ import_react4.default.createElement(import_material4.Grid, { item: true, xs: 12 }, /* @__PURE__ */ import_react4.default.createElement(import_material4.TableContainer, { component: import_material4.Paper, variant: "outlined" }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Table, { size: "small" }, /* @__PURE__ */ import_react4.default.createElement(import_material4.TableHead, null, /* @__PURE__ */ import_react4.default.createElement(import_material4.TableRow, null, /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, null, "Model"), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { align: "right" }, "Spend"), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { align: "right" }, "Requests"), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { align: "right" }, "Success"), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { align: "right" }, "Failed"), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { align: "right" }, "Prompt"), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { align: "right" }, "Completion"), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { sx: { minWidth: 120 } }, "Success rate"))), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableBody, null, loading ? /* @__PURE__ */ import_react4.default.createElement(import_material4.TableRow, null, /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { colSpan: 8 }, /* @__PURE__ */ import_react4.default.createElement(import_material4.LinearProgress, null))) : modelRows.length === 0 ? /* @__PURE__ */ import_react4.default.createElement(import_material4.TableRow, null, /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { colSpan: 8, align: "center" }, "No model activity")) : [...modelRows].sort((a, b) => b.spend - a.spend || b.totalTokens - a.totalTokens).map((r) => /* @__PURE__ */ import_react4.default.createElement(import_material4.TableRow, { key: r.model }, /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, null, /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { display: "flex", alignItems: "center", gap: 0.75 }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { sx: { width: 10, height: 10, borderRadius: "50%", bgcolor: modelColor(r.model), flexShrink: 0 } }), r.model)), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { align: "right" }, fmtUsd(r.spend)), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { align: "right" }, fmtInt(r.apiRequests)), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { align: "right" }, fmtInt(r.successfulRequests)), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { align: "right" }, fmtInt(r.failedRequests)), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { align: "right" }, fmtInt(r.promptTokens)), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { align: "right" }, fmtInt(r.completionTokens)), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, null, r.apiRequests > 0 ? /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ import_react4.default.createElement(
|
|
1165
|
+
import_material4.LinearProgress,
|
|
1026
1166
|
{
|
|
1027
1167
|
variant: "determinate",
|
|
1028
1168
|
value: r.successRate * 100,
|
|
1029
1169
|
sx: { flex: 1, height: 6, borderRadius: 3 }
|
|
1030
1170
|
}
|
|
1031
|
-
), /* @__PURE__ */
|
|
1032
|
-
|
|
1171
|
+
), /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "caption" }, fmtPct(r.successRate))) : "\u2014")))))))), tab === "keys" && /* @__PURE__ */ import_react4.default.createElement(import_material4.Grid, { container: true, spacing: 3 }, (loading || topKeySpendBars.length > 0) && /* @__PURE__ */ import_react4.default.createElement(import_material4.Grid, { item: true, xs: 12 }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Spend by Key"), loading ? /* @__PURE__ */ import_react4.default.createElement(ChartSkeleton, null) : /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { height: Math.max(160, topKeySpendBars.length * 36) }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.BarChart, { data: topKeySpendBars, layout: "vertical", margin: { left: 20 } }, /* @__PURE__ */ import_react4.default.createElement(import_recharts.CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.XAxis, { type: "number", tick: tickStyle, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.YAxis, { type: "category", dataKey: "keyAlias", tick: tickStyleSmall, width: 160 }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Tooltip, { formatter: (v) => [fmtUsd(v), "Spend"] }), /* @__PURE__ */ import_react4.default.createElement(import_recharts.Bar, { dataKey: "spend", name: "Spend", fill: "#8884d8", radius: [0, 4, 4, 0] }))))), /* @__PURE__ */ import_react4.default.createElement(import_material4.Grid, { item: true, xs: 12 }, /* @__PURE__ */ import_react4.default.createElement(import_material4.TableContainer, { component: import_material4.Paper, variant: "outlined" }, /* @__PURE__ */ import_react4.default.createElement(import_material4.Table, { size: "small" }, /* @__PURE__ */ import_react4.default.createElement(import_material4.TableHead, null, /* @__PURE__ */ import_react4.default.createElement(import_material4.TableRow, null, /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, null, "Key"), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, null, "Models"), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { align: "right" }, "Spend"), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { align: "right" }, "Requests"), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { align: "right" }, "Tokens"), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { align: "right" }, "Failed"), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { sx: { minWidth: 120 } }, "Success rate"))), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableBody, null, loading ? /* @__PURE__ */ import_react4.default.createElement(import_material4.TableRow, null, /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { colSpan: 7 }, /* @__PURE__ */ import_react4.default.createElement(import_material4.LinearProgress, null))) : keyRows.length === 0 ? /* @__PURE__ */ import_react4.default.createElement(import_material4.TableRow, null, /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { colSpan: 7, align: "center" }, "No key activity")) : [...keyRows].sort((a, b) => b.spend - a.spend || b.apiRequests - a.apiRequests).map((r) => /* @__PURE__ */ import_react4.default.createElement(import_material4.TableRow, { key: r.keyHash }, /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, null, /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "body2" }, r.keyAlias), r.teamId ? /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "caption", color: "text.secondary" }, "team: ", r.teamId) : null), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, null, /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { display: "flex", gap: 0.5, flexWrap: "wrap" }, r.models.map((m) => /* @__PURE__ */ import_react4.default.createElement(import_material4.Chip, { key: m, label: m, size: "small", variant: "outlined" })))), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { align: "right" }, fmtUsd(r.spend)), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { align: "right" }, fmtInt(r.apiRequests)), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { align: "right" }, fmtInt(r.totalTokens)), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, { align: "right" }, fmtInt(r.failedRequests)), /* @__PURE__ */ import_react4.default.createElement(import_material4.TableCell, null, r.apiRequests > 0 ? /* @__PURE__ */ import_react4.default.createElement(import_material4.Box, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ import_react4.default.createElement(
|
|
1172
|
+
import_material4.LinearProgress,
|
|
1033
1173
|
{
|
|
1034
1174
|
variant: "determinate",
|
|
1035
1175
|
value: r.successRate * 100,
|
|
1036
1176
|
sx: { flex: 1, height: 6, borderRadius: 3 }
|
|
1037
1177
|
}
|
|
1038
|
-
), /* @__PURE__ */
|
|
1178
|
+
), /* @__PURE__ */ import_react4.default.createElement(import_material4.Typography, { variant: "caption" }, fmtPct(r.successRate))) : "\u2014")))))))));
|
|
1039
1179
|
};
|
|
1040
1180
|
}
|
|
1041
1181
|
});
|
|
1042
1182
|
|
|
1043
1183
|
// src/components/TeamUsage.tsx
|
|
1044
|
-
var
|
|
1184
|
+
var import_react5, import_material5, import_icons_material4, import_recharts2, Stat, TeamCard, TeamUsage;
|
|
1045
1185
|
var init_TeamUsage = __esm({
|
|
1046
1186
|
"src/components/TeamUsage.tsx"() {
|
|
1047
1187
|
"use strict";
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1188
|
+
import_react5 = __toESM(require("react"));
|
|
1189
|
+
import_material5 = require("@mui/material");
|
|
1190
|
+
import_icons_material4 = require("@mui/icons-material");
|
|
1051
1191
|
import_recharts2 = require("recharts");
|
|
1052
|
-
Stat = ({ label, value }) => /* @__PURE__ */
|
|
1192
|
+
Stat = ({ label, value }) => /* @__PURE__ */ import_react5.default.createElement(import_material5.Box, null, /* @__PURE__ */ import_react5.default.createElement(import_material5.Typography, { variant: "caption", color: "text.secondary", display: "block" }, label), /* @__PURE__ */ import_react5.default.createElement(import_material5.Typography, { variant: "body2", fontWeight: 600, sx: { fontFamily: "monospace" } }, value));
|
|
1053
1193
|
TeamCard = ({ team, usage, usageLoading }) => {
|
|
1054
|
-
const [expanded, setExpanded] = (0,
|
|
1055
|
-
const theme = (0,
|
|
1194
|
+
const [expanded, setExpanded] = (0, import_react5.useState)(false);
|
|
1195
|
+
const theme = (0, import_material5.useTheme)();
|
|
1056
1196
|
const gridStroke = theme.palette.divider;
|
|
1057
1197
|
const tickFill = theme.palette.text.secondary;
|
|
1058
1198
|
const budget = team.max_budget ?? 0;
|
|
@@ -1061,66 +1201,66 @@ var init_TeamUsage = __esm({
|
|
|
1061
1201
|
const isOver = budget > 0 && spend >= budget;
|
|
1062
1202
|
const isNear = budget > 0 && spend >= budget * 0.8 && !isOver;
|
|
1063
1203
|
const dailyData = usage?.daily_usage?.map((d) => ({ date: d.date, spend: d.spend })) ?? [];
|
|
1064
|
-
return /* @__PURE__ */
|
|
1204
|
+
return /* @__PURE__ */ import_react5.default.createElement(import_material5.Paper, { variant: "outlined", sx: { p: 2, mb: 2 } }, /* @__PURE__ */ import_react5.default.createElement(import_material5.Box, { display: "flex", alignItems: "center", gap: 1 }, /* @__PURE__ */ import_react5.default.createElement(import_icons_material4.Group, { color: "action" }), /* @__PURE__ */ import_react5.default.createElement(import_material5.Box, { flexGrow: 1 }, /* @__PURE__ */ import_react5.default.createElement(import_material5.Typography, { variant: "subtitle1", fontWeight: 600 }, team.team_alias || "Untitled team"), /* @__PURE__ */ import_react5.default.createElement(import_material5.Typography, { variant: "caption", color: "text.secondary", sx: { fontFamily: "monospace" } }, team.team_id)), /* @__PURE__ */ import_react5.default.createElement(import_material5.IconButton, { size: "small", onClick: () => setExpanded((e) => !e), "aria-label": "toggle details" }, expanded ? /* @__PURE__ */ import_react5.default.createElement(import_icons_material4.ExpandLess, null) : /* @__PURE__ */ import_react5.default.createElement(import_icons_material4.ExpandMore, null))), /* @__PURE__ */ import_react5.default.createElement(import_material5.Stack, { direction: "row", spacing: 3, mt: 1.5, flexWrap: "wrap", useFlexGap: true, gap: 1.5 }, /* @__PURE__ */ import_react5.default.createElement(
|
|
1065
1205
|
Stat,
|
|
1066
1206
|
{
|
|
1067
1207
|
label: "Members",
|
|
1068
1208
|
value: team.members_with_roles?.length ? String(team.members_with_roles.length) : "\u2014"
|
|
1069
1209
|
}
|
|
1070
|
-
), /* @__PURE__ */
|
|
1210
|
+
), /* @__PURE__ */ import_react5.default.createElement(
|
|
1071
1211
|
Stat,
|
|
1072
1212
|
{
|
|
1073
1213
|
label: "Models",
|
|
1074
1214
|
value: team.models?.length ? String(team.models.length) : "All"
|
|
1075
1215
|
}
|
|
1076
|
-
), /* @__PURE__ */
|
|
1216
|
+
), /* @__PURE__ */ import_react5.default.createElement(
|
|
1077
1217
|
Stat,
|
|
1078
1218
|
{
|
|
1079
1219
|
label: "Budget",
|
|
1080
1220
|
value: budget > 0 ? `$${budget.toFixed(2)}` : "Unlimited"
|
|
1081
1221
|
}
|
|
1082
|
-
), /* @__PURE__ */
|
|
1222
|
+
), /* @__PURE__ */ import_react5.default.createElement(
|
|
1083
1223
|
Stat,
|
|
1084
1224
|
{
|
|
1085
1225
|
label: "Spend",
|
|
1086
1226
|
value: `$${spend.toFixed(2)}`
|
|
1087
1227
|
}
|
|
1088
|
-
), /* @__PURE__ */
|
|
1228
|
+
), /* @__PURE__ */ import_react5.default.createElement(
|
|
1089
1229
|
Stat,
|
|
1090
1230
|
{
|
|
1091
1231
|
label: "TPM",
|
|
1092
1232
|
value: team.tpm_limit != null && team.tpm_limit > 0 ? String(team.tpm_limit) : "\u2014"
|
|
1093
1233
|
}
|
|
1094
|
-
), /* @__PURE__ */
|
|
1234
|
+
), /* @__PURE__ */ import_react5.default.createElement(
|
|
1095
1235
|
Stat,
|
|
1096
1236
|
{
|
|
1097
1237
|
label: "RPM",
|
|
1098
1238
|
value: team.rpm_limit != null && team.rpm_limit > 0 ? String(team.rpm_limit) : "\u2014"
|
|
1099
1239
|
}
|
|
1100
|
-
)), budget > 0 && /* @__PURE__ */
|
|
1101
|
-
|
|
1240
|
+
)), budget > 0 && /* @__PURE__ */ import_react5.default.createElement(import_material5.Box, { mt: 1.5 }, /* @__PURE__ */ import_react5.default.createElement(import_material5.Box, { display: "flex", justifyContent: "space-between", mb: 0.5 }, /* @__PURE__ */ import_react5.default.createElement(import_material5.Typography, { variant: "body2" }, "$", spend.toFixed(2), " / $", budget.toFixed(2)), isOver && /* @__PURE__ */ import_react5.default.createElement(import_material5.Chip, { label: "Over Budget", size: "small", color: "error" }), isNear && /* @__PURE__ */ import_react5.default.createElement(import_material5.Chip, { label: "Near Limit", size: "small", color: "warning" })), /* @__PURE__ */ import_react5.default.createElement(
|
|
1241
|
+
import_material5.LinearProgress,
|
|
1102
1242
|
{
|
|
1103
1243
|
variant: "determinate",
|
|
1104
1244
|
value: budgetPct,
|
|
1105
1245
|
color: isOver ? "error" : isNear ? "warning" : "primary",
|
|
1106
1246
|
sx: { height: 6, borderRadius: 1 }
|
|
1107
1247
|
}
|
|
1108
|
-
)), /* @__PURE__ */
|
|
1109
|
-
|
|
1248
|
+
)), /* @__PURE__ */ import_react5.default.createElement(import_material5.Collapse, { in: expanded }, /* @__PURE__ */ import_react5.default.createElement(import_material5.Box, { mt: 2 }, /* @__PURE__ */ import_react5.default.createElement(import_material5.Box, { display: "flex", alignItems: "center", gap: 1, mb: 1 }, /* @__PURE__ */ import_react5.default.createElement(import_icons_material4.Speed, { fontSize: "small", color: "action" }), /* @__PURE__ */ import_react5.default.createElement(import_material5.Typography, { variant: "subtitle2", color: "text.secondary" }, "Rate limits")), /* @__PURE__ */ import_react5.default.createElement(import_material5.Stack, { direction: "row", spacing: 3, flexWrap: "wrap", useFlexGap: true, gap: 1.5, mb: 2 }, /* @__PURE__ */ import_react5.default.createElement(Stat, { label: "TPM limit", value: team.tpm_limit != null && team.tpm_limit > 0 ? String(team.tpm_limit) : "Unlimited" }), /* @__PURE__ */ import_react5.default.createElement(Stat, { label: "RPM limit", value: team.rpm_limit != null && team.rpm_limit > 0 ? String(team.rpm_limit) : "Unlimited" }), /* @__PURE__ */ import_react5.default.createElement(Stat, { label: "Max budget", value: budget > 0 ? `$${budget.toFixed(2)}` : "Unlimited" })), /* @__PURE__ */ import_react5.default.createElement(import_material5.Divider, { sx: { mb: 2 } }), /* @__PURE__ */ import_react5.default.createElement(import_material5.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Models"), team.models?.length ? /* @__PURE__ */ import_react5.default.createElement(import_material5.Box, { display: "flex", gap: 0.5, flexWrap: "wrap", mb: 2 }, team.models.map((m) => /* @__PURE__ */ import_react5.default.createElement(import_material5.Chip, { key: m, label: m, size: "small", variant: "outlined" }))) : /* @__PURE__ */ import_react5.default.createElement(import_material5.Typography, { variant: "body2", color: "text.secondary", mb: 2 }, "All models allowed"), /* @__PURE__ */ import_react5.default.createElement(import_material5.Divider, { sx: { mb: 2 } }), usageLoading ? /* @__PURE__ */ import_react5.default.createElement(import_material5.Box, { display: "flex", justifyContent: "center", p: 2 }, /* @__PURE__ */ import_react5.default.createElement(import_material5.CircularProgress, { size: 24 })) : dailyData.length > 0 ? /* @__PURE__ */ import_react5.default.createElement(import_react5.default.Fragment, null, /* @__PURE__ */ import_react5.default.createElement(import_material5.Typography, { variant: "subtitle2", color: "text.secondary", gutterBottom: true }, "Daily Spend"), /* @__PURE__ */ import_react5.default.createElement(import_material5.Box, { height: 160, mb: 2 }, /* @__PURE__ */ import_react5.default.createElement(import_recharts2.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react5.default.createElement(import_recharts2.AreaChart, { data: dailyData }, /* @__PURE__ */ import_react5.default.createElement(import_recharts2.CartesianGrid, { strokeDasharray: "3 3", stroke: gridStroke }), /* @__PURE__ */ import_react5.default.createElement(import_recharts2.XAxis, { dataKey: "date", tick: { fontSize: 11, fill: tickFill } }), /* @__PURE__ */ import_react5.default.createElement(import_recharts2.YAxis, { tick: { fontSize: 11, fill: tickFill }, tickFormatter: (v) => `$${v.toFixed(2)}` }), /* @__PURE__ */ import_react5.default.createElement(import_recharts2.Tooltip, { formatter: (v) => [`$${v.toFixed(4)}`, "Spend"] }), /* @__PURE__ */ import_react5.default.createElement(import_recharts2.Area, { type: "monotone", dataKey: "spend", stroke: "#8884d8", fill: "#8884d8", fillOpacity: 0.3 })))), /* @__PURE__ */ import_react5.default.createElement(import_material5.Divider, { sx: { mb: 2 } })) : null, /* @__PURE__ */ import_react5.default.createElement(import_material5.Box, { display: "flex", alignItems: "center", gap: 1, mb: 1 }, /* @__PURE__ */ import_react5.default.createElement(import_icons_material4.Memory, { fontSize: "small", color: "action" }), /* @__PURE__ */ import_react5.default.createElement(import_material5.Typography, { variant: "subtitle2", color: "text.secondary" }, "Members")), team.members_with_roles?.length ? /* @__PURE__ */ import_react5.default.createElement(import_material5.TableContainer, null, /* @__PURE__ */ import_react5.default.createElement(import_material5.Table, { size: "small" }, /* @__PURE__ */ import_react5.default.createElement(import_material5.TableHead, null, /* @__PURE__ */ import_react5.default.createElement(import_material5.TableRow, null, /* @__PURE__ */ import_react5.default.createElement(import_material5.TableCell, null, "User"), /* @__PURE__ */ import_react5.default.createElement(import_material5.TableCell, null, "Role"))), /* @__PURE__ */ import_react5.default.createElement(import_material5.TableBody, null, team.members_with_roles.map((m) => /* @__PURE__ */ import_react5.default.createElement(import_material5.TableRow, { key: m.user_id }, /* @__PURE__ */ import_react5.default.createElement(import_material5.TableCell, null, m.user_email ? /* @__PURE__ */ import_react5.default.createElement(import_react5.default.Fragment, null, /* @__PURE__ */ import_react5.default.createElement(import_material5.Typography, { variant: "body2" }, m.user_email), /* @__PURE__ */ import_react5.default.createElement(
|
|
1249
|
+
import_material5.Typography,
|
|
1110
1250
|
{
|
|
1111
1251
|
variant: "caption",
|
|
1112
1252
|
color: "text.secondary",
|
|
1113
1253
|
sx: { fontFamily: "monospace" }
|
|
1114
1254
|
},
|
|
1115
1255
|
m.user_id
|
|
1116
|
-
)) : /* @__PURE__ */
|
|
1117
|
-
|
|
1256
|
+
)) : /* @__PURE__ */ import_react5.default.createElement(import_material5.Typography, { variant: "body2", sx: { fontFamily: "monospace" } }, m.user_id)), /* @__PURE__ */ import_react5.default.createElement(import_material5.TableCell, null, /* @__PURE__ */ import_react5.default.createElement(
|
|
1257
|
+
import_material5.Chip,
|
|
1118
1258
|
{
|
|
1119
1259
|
label: m.role,
|
|
1120
1260
|
size: "small",
|
|
1121
1261
|
color: m.role === "admin" ? "primary" : "default"
|
|
1122
1262
|
}
|
|
1123
|
-
))))))) : /* @__PURE__ */
|
|
1263
|
+
))))))) : /* @__PURE__ */ import_react5.default.createElement(import_material5.Typography, { variant: "body2", color: "text.secondary" }, "No members assigned."))));
|
|
1124
1264
|
};
|
|
1125
1265
|
TeamUsage = ({
|
|
1126
1266
|
teams,
|
|
@@ -1129,12 +1269,12 @@ var init_TeamUsage = __esm({
|
|
|
1129
1269
|
getTeamUsageLoading
|
|
1130
1270
|
}) => {
|
|
1131
1271
|
if (loading) {
|
|
1132
|
-
return /* @__PURE__ */
|
|
1272
|
+
return /* @__PURE__ */ import_react5.default.createElement(import_material5.Paper, { sx: { p: 2 } }, /* @__PURE__ */ import_react5.default.createElement(import_material5.LinearProgress, null));
|
|
1133
1273
|
}
|
|
1134
1274
|
if (!teams.length) {
|
|
1135
|
-
return /* @__PURE__ */
|
|
1275
|
+
return /* @__PURE__ */ import_react5.default.createElement(import_material5.Paper, { sx: { p: 2 } }, /* @__PURE__ */ import_react5.default.createElement(import_material5.Box, { display: "flex", alignItems: "flex-start", gap: 1.5 }, /* @__PURE__ */ import_react5.default.createElement(import_icons_material4.Group, { color: "disabled", sx: { mt: 0.5 } }), /* @__PURE__ */ import_react5.default.createElement(import_material5.Box, null, /* @__PURE__ */ import_react5.default.createElement(import_material5.Typography, { color: "text.secondary", variant: "body2" }, "You're not a member of any LiteLLM team yet."), /* @__PURE__ */ import_react5.default.createElement(import_material5.Typography, { color: "text.secondary", variant: "body2", mt: 0.5 }, "That's fine \u2014 your account is provisioned, so you can still generate personal keys and use models. If you need a shared budget with colleagues, ask an admin to add you to a team."))));
|
|
1136
1276
|
}
|
|
1137
|
-
return /* @__PURE__ */
|
|
1277
|
+
return /* @__PURE__ */ import_react5.default.createElement(import_material5.Box, null, /* @__PURE__ */ import_react5.default.createElement(import_material5.Typography, { variant: "h6", mb: 1 }, "Teams"), teams.map((team) => /* @__PURE__ */ import_react5.default.createElement(
|
|
1138
1278
|
TeamCard,
|
|
1139
1279
|
{
|
|
1140
1280
|
key: team.team_id,
|
|
@@ -1162,13 +1302,13 @@ function formatDateTime(iso) {
|
|
|
1162
1302
|
return iso;
|
|
1163
1303
|
}
|
|
1164
1304
|
}
|
|
1165
|
-
var
|
|
1305
|
+
var import_react6, import_material6, import_icons_material5, import_react_use, ACTION_COLORS, DetailRow, AuditLog;
|
|
1166
1306
|
var init_AuditLog = __esm({
|
|
1167
1307
|
"src/components/AuditLog.tsx"() {
|
|
1168
1308
|
"use strict";
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1309
|
+
import_react6 = __toESM(require("react"));
|
|
1310
|
+
import_material6 = require("@mui/material");
|
|
1311
|
+
import_icons_material5 = require("@mui/icons-material");
|
|
1172
1312
|
import_react_use = require("react-use");
|
|
1173
1313
|
ACTION_COLORS = {
|
|
1174
1314
|
created: "success",
|
|
@@ -1178,15 +1318,15 @@ var init_AuditLog = __esm({
|
|
|
1178
1318
|
unblocked: "success"
|
|
1179
1319
|
};
|
|
1180
1320
|
DetailRow = ({ entry }) => {
|
|
1181
|
-
const [open, setOpen] = (0,
|
|
1321
|
+
const [open, setOpen] = (0, import_react6.useState)(false);
|
|
1182
1322
|
const hasDetail = entry.before_value || entry.updated_values;
|
|
1183
|
-
return /* @__PURE__ */
|
|
1323
|
+
return /* @__PURE__ */ import_react6.default.createElement(import_react6.default.Fragment, null, /* @__PURE__ */ import_react6.default.createElement(import_material6.TableRow, { hover: true }, /* @__PURE__ */ import_react6.default.createElement(import_material6.TableCell, { sx: { width: 40, pr: 0 } }, hasDetail && /* @__PURE__ */ import_react6.default.createElement(import_material6.IconButton, { size: "small", onClick: () => setOpen((o) => !o) }, open ? /* @__PURE__ */ import_react6.default.createElement(import_icons_material5.KeyboardArrowUp, { fontSize: "small" }) : /* @__PURE__ */ import_react6.default.createElement(import_icons_material5.KeyboardArrowDown, { fontSize: "small" }))), /* @__PURE__ */ import_react6.default.createElement(import_material6.TableCell, { sx: { whiteSpace: "nowrap" } }, formatDateTime(entry.updated_at)), /* @__PURE__ */ import_react6.default.createElement(import_material6.TableCell, null, entry.action && /* @__PURE__ */ import_react6.default.createElement(import_material6.Chip, { label: entry.action, color: actionColor(entry.action), size: "small" })), /* @__PURE__ */ import_react6.default.createElement(import_material6.TableCell, null, entry.table_name ?? "-"), /* @__PURE__ */ import_react6.default.createElement(import_material6.TableCell, null, /* @__PURE__ */ import_react6.default.createElement(import_material6.Typography, { variant: "body2", component: "code", sx: { fontFamily: "monospace", fontSize: "0.75rem" } }, entry.object_id ? entry.object_id.slice(0, 20) + (entry.object_id.length > 20 ? "\u2026" : "") : "-")), /* @__PURE__ */ import_react6.default.createElement(import_material6.TableCell, null, entry.changed_by ?? "-")), hasDetail && /* @__PURE__ */ import_react6.default.createElement(import_material6.TableRow, null, /* @__PURE__ */ import_react6.default.createElement(import_material6.TableCell, { colSpan: 6, sx: { py: 0 } }, /* @__PURE__ */ import_react6.default.createElement(import_material6.Collapse, { in: open, unmountOnExit: true }, /* @__PURE__ */ import_react6.default.createElement(import_material6.Box, { p: 2, display: "flex", gap: 2, flexWrap: "wrap" }, entry.before_value && /* @__PURE__ */ import_react6.default.createElement(import_material6.Box, { flex: 1, minWidth: 200 }, /* @__PURE__ */ import_react6.default.createElement(import_material6.Typography, { variant: "caption", color: "text.secondary", display: "block", mb: 0.5 }, "Before"), /* @__PURE__ */ import_react6.default.createElement(import_material6.Typography, { component: "pre", variant: "caption", sx: { fontFamily: "monospace", whiteSpace: "pre-wrap", wordBreak: "break-all" } }, JSON.stringify(entry.before_value, null, 2))), entry.updated_values && /* @__PURE__ */ import_react6.default.createElement(import_material6.Box, { flex: 1, minWidth: 200 }, /* @__PURE__ */ import_react6.default.createElement(import_material6.Typography, { variant: "caption", color: "text.secondary", display: "block", mb: 0.5 }, "After"), /* @__PURE__ */ import_react6.default.createElement(import_material6.Typography, { component: "pre", variant: "caption", sx: { fontFamily: "monospace", whiteSpace: "pre-wrap", wordBreak: "break-all" } }, JSON.stringify(entry.updated_values, null, 2))))))));
|
|
1184
1324
|
};
|
|
1185
1325
|
AuditLog = ({ api }) => {
|
|
1186
|
-
const [page, setPage] = (0,
|
|
1187
|
-
const [pageSize, setPageSize] = (0,
|
|
1188
|
-
const [filters, setFilters] = (0,
|
|
1189
|
-
const fetchParams = (0,
|
|
1326
|
+
const [page, setPage] = (0, import_react6.useState)(0);
|
|
1327
|
+
const [pageSize, setPageSize] = (0, import_react6.useState)(25);
|
|
1328
|
+
const [filters, setFilters] = (0, import_react6.useState)({});
|
|
1329
|
+
const fetchParams = (0, import_react6.useCallback)(
|
|
1190
1330
|
() => ({ page: page + 1, page_size: pageSize, ...filters }),
|
|
1191
1331
|
[page, pageSize, filters]
|
|
1192
1332
|
)();
|
|
@@ -1196,8 +1336,8 @@ var init_AuditLog = __esm({
|
|
|
1196
1336
|
);
|
|
1197
1337
|
const entries = value?.audit_logs ?? [];
|
|
1198
1338
|
const total = value?.total ?? 0;
|
|
1199
|
-
return /* @__PURE__ */
|
|
1200
|
-
|
|
1339
|
+
return /* @__PURE__ */ import_react6.default.createElement(import_material6.Paper, null, /* @__PURE__ */ import_react6.default.createElement(import_material6.Box, { p: 2, display: "flex", gap: 2, flexWrap: "wrap", alignItems: "center" }, /* @__PURE__ */ import_react6.default.createElement(import_material6.Typography, { variant: "h6", sx: { flex: "0 0 auto", mr: 1 } }, "Audit Log"), /* @__PURE__ */ import_react6.default.createElement(
|
|
1340
|
+
import_material6.TextField,
|
|
1201
1341
|
{
|
|
1202
1342
|
size: "small",
|
|
1203
1343
|
label: "Action",
|
|
@@ -1209,13 +1349,13 @@ var init_AuditLog = __esm({
|
|
|
1209
1349
|
},
|
|
1210
1350
|
sx: { minWidth: 160 }
|
|
1211
1351
|
},
|
|
1212
|
-
/* @__PURE__ */
|
|
1213
|
-
/* @__PURE__ */
|
|
1214
|
-
/* @__PURE__ */
|
|
1215
|
-
/* @__PURE__ */
|
|
1216
|
-
/* @__PURE__ */
|
|
1217
|
-
), /* @__PURE__ */
|
|
1218
|
-
|
|
1352
|
+
/* @__PURE__ */ import_react6.default.createElement(import_material6.MenuItem, { value: "" }, "All actions"),
|
|
1353
|
+
/* @__PURE__ */ import_react6.default.createElement(import_material6.MenuItem, { value: "created" }, "Created"),
|
|
1354
|
+
/* @__PURE__ */ import_react6.default.createElement(import_material6.MenuItem, { value: "updated" }, "Updated"),
|
|
1355
|
+
/* @__PURE__ */ import_react6.default.createElement(import_material6.MenuItem, { value: "deleted" }, "Deleted"),
|
|
1356
|
+
/* @__PURE__ */ import_react6.default.createElement(import_material6.MenuItem, { value: "blocked" }, "Blocked")
|
|
1357
|
+
), /* @__PURE__ */ import_react6.default.createElement(
|
|
1358
|
+
import_material6.TextField,
|
|
1219
1359
|
{
|
|
1220
1360
|
size: "small",
|
|
1221
1361
|
label: "Table",
|
|
@@ -1227,12 +1367,12 @@ var init_AuditLog = __esm({
|
|
|
1227
1367
|
},
|
|
1228
1368
|
sx: { minWidth: 160 }
|
|
1229
1369
|
},
|
|
1230
|
-
/* @__PURE__ */
|
|
1231
|
-
/* @__PURE__ */
|
|
1232
|
-
/* @__PURE__ */
|
|
1233
|
-
/* @__PURE__ */
|
|
1234
|
-
), /* @__PURE__ */
|
|
1235
|
-
|
|
1370
|
+
/* @__PURE__ */ import_react6.default.createElement(import_material6.MenuItem, { value: "" }, "All tables"),
|
|
1371
|
+
/* @__PURE__ */ import_react6.default.createElement(import_material6.MenuItem, { value: "LiteLLM_VerificationToken" }, "Key"),
|
|
1372
|
+
/* @__PURE__ */ import_react6.default.createElement(import_material6.MenuItem, { value: "LiteLLM_TeamTable" }, "Team"),
|
|
1373
|
+
/* @__PURE__ */ import_react6.default.createElement(import_material6.MenuItem, { value: "LiteLLM_UserTable" }, "User")
|
|
1374
|
+
), /* @__PURE__ */ import_react6.default.createElement(
|
|
1375
|
+
import_material6.TextField,
|
|
1236
1376
|
{
|
|
1237
1377
|
size: "small",
|
|
1238
1378
|
label: "Changed by",
|
|
@@ -1243,8 +1383,8 @@ var init_AuditLog = __esm({
|
|
|
1243
1383
|
},
|
|
1244
1384
|
sx: { minWidth: 200 }
|
|
1245
1385
|
}
|
|
1246
|
-
)), error && /* @__PURE__ */
|
|
1247
|
-
|
|
1386
|
+
)), error && /* @__PURE__ */ import_react6.default.createElement(import_material6.Box, { px: 2, pb: 2 }, /* @__PURE__ */ import_react6.default.createElement(import_material6.Alert, { severity: "error" }, error.message)), /* @__PURE__ */ import_react6.default.createElement(import_material6.TableContainer, null, /* @__PURE__ */ import_react6.default.createElement(import_material6.Table, { size: "small" }, /* @__PURE__ */ import_react6.default.createElement(import_material6.TableHead, null, /* @__PURE__ */ import_react6.default.createElement(import_material6.TableRow, null, /* @__PURE__ */ import_react6.default.createElement(import_material6.TableCell, { sx: { width: 40 } }), /* @__PURE__ */ import_react6.default.createElement(import_material6.TableCell, null, "Time"), /* @__PURE__ */ import_react6.default.createElement(import_material6.TableCell, null, "Action"), /* @__PURE__ */ import_react6.default.createElement(import_material6.TableCell, null, "Table"), /* @__PURE__ */ import_react6.default.createElement(import_material6.TableCell, null, "Object ID"), /* @__PURE__ */ import_react6.default.createElement(import_material6.TableCell, null, "Changed By"))), /* @__PURE__ */ import_react6.default.createElement(import_material6.TableBody, null, loading ? /* @__PURE__ */ import_react6.default.createElement(import_material6.TableRow, null, /* @__PURE__ */ import_react6.default.createElement(import_material6.TableCell, { colSpan: 6, align: "center", sx: { py: 4 } }, /* @__PURE__ */ import_react6.default.createElement(import_material6.CircularProgress, { size: 24 }))) : entries.length === 0 ? /* @__PURE__ */ import_react6.default.createElement(import_material6.TableRow, null, /* @__PURE__ */ import_react6.default.createElement(import_material6.TableCell, { colSpan: 6, align: "center", sx: { py: 4 } }, /* @__PURE__ */ import_react6.default.createElement(import_material6.Typography, { color: "text.secondary" }, "No audit events found"))) : entries.map((entry) => /* @__PURE__ */ import_react6.default.createElement(DetailRow, { key: entry.id, entry }))))), /* @__PURE__ */ import_react6.default.createElement(
|
|
1387
|
+
import_material6.TablePagination,
|
|
1248
1388
|
{
|
|
1249
1389
|
component: "div",
|
|
1250
1390
|
count: total,
|
|
@@ -1281,16 +1421,17 @@ function initDateRange() {
|
|
|
1281
1421
|
else start.setDate(start.getDate() - 7);
|
|
1282
1422
|
return { start, end };
|
|
1283
1423
|
}
|
|
1284
|
-
var
|
|
1424
|
+
var import_react7, import_material7, import_react_use2, import_core_plugin_api2, PERIOD_LS_KEY2, LiteLLMPage;
|
|
1285
1425
|
var init_LiteLLMPage = __esm({
|
|
1286
1426
|
"src/components/LiteLLMPage.tsx"() {
|
|
1287
1427
|
"use strict";
|
|
1288
|
-
|
|
1289
|
-
|
|
1428
|
+
import_react7 = __toESM(require("react"));
|
|
1429
|
+
import_material7 = require("@mui/material");
|
|
1290
1430
|
import_react_use2 = require("react-use");
|
|
1291
1431
|
import_core_plugin_api2 = require("@backstage/core-plugin-api");
|
|
1292
1432
|
init_DashboardHeader();
|
|
1293
1433
|
init_KeysTable();
|
|
1434
|
+
init_GenerateKeyDialog();
|
|
1294
1435
|
init_UsageStats();
|
|
1295
1436
|
init_TeamUsage();
|
|
1296
1437
|
init_AuditLog();
|
|
@@ -1298,11 +1439,12 @@ var init_LiteLLMPage = __esm({
|
|
|
1298
1439
|
PERIOD_LS_KEY2 = "litellm_usage_period";
|
|
1299
1440
|
LiteLLMPage = () => {
|
|
1300
1441
|
const api = (0, import_core_plugin_api2.useApi)(liteLlmApiRef);
|
|
1301
|
-
const [dateRange, setDateRange] = (0,
|
|
1302
|
-
const [activeTab, setActiveTab] = (0,
|
|
1303
|
-
const [snackbar, setSnackbar] = (0,
|
|
1304
|
-
const [
|
|
1305
|
-
const [
|
|
1442
|
+
const [dateRange, setDateRange] = (0, import_react7.useState)(initDateRange);
|
|
1443
|
+
const [activeTab, setActiveTab] = (0, import_react7.useState)("overview");
|
|
1444
|
+
const [snackbar, setSnackbar] = (0, import_react7.useState)(null);
|
|
1445
|
+
const [generateDialogOpen, setGenerateDialogOpen] = (0, import_react7.useState)(false);
|
|
1446
|
+
const [teamUsageCache, setTeamUsageCache] = (0, import_react7.useState)({});
|
|
1447
|
+
const [teamUsageLoading, setTeamUsageLoading] = (0, import_react7.useState)({});
|
|
1306
1448
|
const { value: userInfo, loading: userLoading, error: userError } = (0, import_react_use2.useAsync)(
|
|
1307
1449
|
() => api.getUserInfo(),
|
|
1308
1450
|
[api]
|
|
@@ -1326,7 +1468,8 @@ var init_LiteLLMPage = __esm({
|
|
|
1326
1468
|
() => api.getTeams().catch(() => []),
|
|
1327
1469
|
[api]
|
|
1328
1470
|
);
|
|
1329
|
-
const
|
|
1471
|
+
const { value: liteLlmConfig } = (0, import_react_use2.useAsync)(() => api.getConfig(), [api]);
|
|
1472
|
+
const teams = (0, import_react7.useMemo)(() => {
|
|
1330
1473
|
if (!allTeams?.length) return [];
|
|
1331
1474
|
if (!userInfo) return allTeams;
|
|
1332
1475
|
const userId = userInfo.user_id;
|
|
@@ -1343,12 +1486,12 @@ var init_LiteLLMPage = __esm({
|
|
|
1343
1486
|
const endDate = dateRange.end.toISOString().split("T")[0];
|
|
1344
1487
|
return api.getUsage(startDate, endDate);
|
|
1345
1488
|
}, [api, dateRange]);
|
|
1346
|
-
const handleDateRangeChange = (0,
|
|
1489
|
+
const handleDateRangeChange = (0, import_react7.useCallback)((range) => {
|
|
1347
1490
|
setDateRange(range);
|
|
1348
1491
|
setTeamUsageCache({});
|
|
1349
1492
|
setTeamUsageLoading({});
|
|
1350
1493
|
}, [setDateRange]);
|
|
1351
|
-
const loadTeamUsage = (0,
|
|
1494
|
+
const loadTeamUsage = (0, import_react7.useCallback)(async (teamId) => {
|
|
1352
1495
|
if (teamUsageCache[teamId] !== void 0 || teamUsageLoading[teamId]) return;
|
|
1353
1496
|
setTeamUsageLoading((prev) => ({ ...prev, [teamId]: true }));
|
|
1354
1497
|
try {
|
|
@@ -1362,7 +1505,7 @@ var init_LiteLLMPage = __esm({
|
|
|
1362
1505
|
setTeamUsageLoading((prev) => ({ ...prev, [teamId]: false }));
|
|
1363
1506
|
}
|
|
1364
1507
|
}, [api, dateRange, teamUsageCache, teamUsageLoading]);
|
|
1365
|
-
const allowedModels = (0,
|
|
1508
|
+
const allowedModels = (0, import_react7.useMemo)(() => {
|
|
1366
1509
|
if (!allModels?.length) return [];
|
|
1367
1510
|
const userModels = userInfo?.models;
|
|
1368
1511
|
const teamModels = teams?.flatMap((t) => t.models ?? []);
|
|
@@ -1375,7 +1518,7 @@ var init_LiteLLMPage = __esm({
|
|
|
1375
1518
|
]);
|
|
1376
1519
|
return allModels.filter((m) => allowed.has(m.model_name));
|
|
1377
1520
|
}, [allModels, userInfo, teams]);
|
|
1378
|
-
const handleGenerateKey = (0,
|
|
1521
|
+
const handleGenerateKey = (0, import_react7.useCallback)(
|
|
1379
1522
|
async (request) => {
|
|
1380
1523
|
try {
|
|
1381
1524
|
const response = await api.generateKey(request);
|
|
@@ -1389,7 +1532,7 @@ var init_LiteLLMPage = __esm({
|
|
|
1389
1532
|
},
|
|
1390
1533
|
[api, refreshKeys]
|
|
1391
1534
|
);
|
|
1392
|
-
const handleUpdateKey = (0,
|
|
1535
|
+
const handleUpdateKey = (0, import_react7.useCallback)(
|
|
1393
1536
|
async (keyId, request) => {
|
|
1394
1537
|
try {
|
|
1395
1538
|
await api.updateKey(keyId, request);
|
|
@@ -1402,7 +1545,7 @@ var init_LiteLLMPage = __esm({
|
|
|
1402
1545
|
},
|
|
1403
1546
|
[api, refreshKeys]
|
|
1404
1547
|
);
|
|
1405
|
-
const handleBlockKey = (0,
|
|
1548
|
+
const handleBlockKey = (0, import_react7.useCallback)(
|
|
1406
1549
|
async (keyId) => {
|
|
1407
1550
|
try {
|
|
1408
1551
|
await api.blockKey(keyId);
|
|
@@ -1414,7 +1557,7 @@ var init_LiteLLMPage = __esm({
|
|
|
1414
1557
|
},
|
|
1415
1558
|
[api, refreshKeys]
|
|
1416
1559
|
);
|
|
1417
|
-
const handleUnblockKey = (0,
|
|
1560
|
+
const handleUnblockKey = (0, import_react7.useCallback)(
|
|
1418
1561
|
async (keyId) => {
|
|
1419
1562
|
try {
|
|
1420
1563
|
await api.unblockKey(keyId);
|
|
@@ -1426,7 +1569,7 @@ var init_LiteLLMPage = __esm({
|
|
|
1426
1569
|
},
|
|
1427
1570
|
[api, refreshKeys]
|
|
1428
1571
|
);
|
|
1429
|
-
const handleResetKeySpend = (0,
|
|
1572
|
+
const handleResetKeySpend = (0, import_react7.useCallback)(
|
|
1430
1573
|
async (keyId) => {
|
|
1431
1574
|
try {
|
|
1432
1575
|
await api.resetKeySpend(keyId);
|
|
@@ -1438,7 +1581,7 @@ var init_LiteLLMPage = __esm({
|
|
|
1438
1581
|
},
|
|
1439
1582
|
[api, refreshKeys]
|
|
1440
1583
|
);
|
|
1441
|
-
const handleDeleteKey = (0,
|
|
1584
|
+
const handleDeleteKey = (0, import_react7.useCallback)(
|
|
1442
1585
|
async (keyId) => {
|
|
1443
1586
|
try {
|
|
1444
1587
|
await api.deleteKey(keyId);
|
|
@@ -1455,7 +1598,7 @@ var init_LiteLLMPage = __esm({
|
|
|
1455
1598
|
},
|
|
1456
1599
|
[api, refreshKeys]
|
|
1457
1600
|
);
|
|
1458
|
-
const handlePruneExpiredKeys = (0,
|
|
1601
|
+
const handlePruneExpiredKeys = (0, import_react7.useCallback)(
|
|
1459
1602
|
async () => {
|
|
1460
1603
|
try {
|
|
1461
1604
|
const result = await api.pruneExpiredKeys();
|
|
@@ -1470,25 +1613,34 @@ var init_LiteLLMPage = __esm({
|
|
|
1470
1613
|
);
|
|
1471
1614
|
const isInitialLoading = userLoading && !userInfo;
|
|
1472
1615
|
if (isInitialLoading) {
|
|
1473
|
-
return /* @__PURE__ */
|
|
1616
|
+
return /* @__PURE__ */ import_react7.default.createElement(import_material7.Box, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: "50vh" }, /* @__PURE__ */ import_react7.default.createElement(import_material7.CircularProgress, null));
|
|
1474
1617
|
}
|
|
1475
1618
|
if (userError || !userInfo) {
|
|
1476
1619
|
const isProvisioningEnabled = userError?.body?.provisioning === true;
|
|
1477
1620
|
const hint = userError?.body?.hint;
|
|
1478
|
-
return /* @__PURE__ */
|
|
1621
|
+
return /* @__PURE__ */ import_react7.default.createElement(import_material7.Box, { p: 3 }, /* @__PURE__ */ import_react7.default.createElement(import_material7.Paper, { sx: { p: 3 } }, /* @__PURE__ */ import_react7.default.createElement(import_material7.Typography, { variant: "h6", gutterBottom: true }, "Account not provisioned"), /* @__PURE__ */ import_react7.default.createElement(import_material7.Typography, { color: "text.secondary", paragraph: true }, "Your Backstage account is not linked to a LiteLLM user."), hint ? /* @__PURE__ */ import_react7.default.createElement(import_material7.Typography, { variant: "body2", color: "text.secondary" }, hint) : /* @__PURE__ */ import_react7.default.createElement(import_material7.Typography, { variant: "body2", color: "text.secondary" }, isProvisioningEnabled ? "Auto-provisioning is enabled but failed. Check the backend logs." : "Set litellm.provisioning.enabled: true in app-config.yaml to enable auto-provisioning, or ask your administrator to create the account manually.")));
|
|
1479
1622
|
}
|
|
1480
|
-
return /* @__PURE__ */
|
|
1481
|
-
|
|
1623
|
+
return /* @__PURE__ */ import_react7.default.createElement(import_material7.Box, { p: 3 }, /* @__PURE__ */ import_react7.default.createElement(import_material7.Box, { mb: 2 }, /* @__PURE__ */ import_react7.default.createElement(
|
|
1624
|
+
DashboardHeader,
|
|
1625
|
+
{
|
|
1626
|
+
userInfo,
|
|
1627
|
+
teams: teams ?? [],
|
|
1628
|
+
keys: keys ?? [],
|
|
1629
|
+
loading: userLoading || teamsLoading,
|
|
1630
|
+
onGenerateKeyClick: () => setGenerateDialogOpen(true)
|
|
1631
|
+
}
|
|
1632
|
+
)), /* @__PURE__ */ import_react7.default.createElement(
|
|
1633
|
+
import_material7.Tabs,
|
|
1482
1634
|
{
|
|
1483
1635
|
value: activeTab,
|
|
1484
1636
|
onChange: (_, v) => setActiveTab(v),
|
|
1485
1637
|
sx: { mb: 2, borderBottom: 1, borderColor: "divider" }
|
|
1486
1638
|
},
|
|
1487
|
-
/* @__PURE__ */
|
|
1488
|
-
/* @__PURE__ */
|
|
1489
|
-
/* @__PURE__ */
|
|
1490
|
-
userInfo.can_view_audit && /* @__PURE__ */
|
|
1491
|
-
), activeTab === "overview" && /* @__PURE__ */
|
|
1639
|
+
/* @__PURE__ */ import_react7.default.createElement(import_material7.Tab, { label: "Overview", value: "overview" }),
|
|
1640
|
+
/* @__PURE__ */ import_react7.default.createElement(import_material7.Tab, { label: "Keys", value: "keys" }),
|
|
1641
|
+
/* @__PURE__ */ import_react7.default.createElement(import_material7.Tab, { label: "Teams", value: "teams" }),
|
|
1642
|
+
userInfo.can_view_audit && /* @__PURE__ */ import_react7.default.createElement(import_material7.Tab, { label: "Audit Log", value: "audit" })
|
|
1643
|
+
), activeTab === "overview" && /* @__PURE__ */ import_react7.default.createElement(
|
|
1492
1644
|
UsageStats,
|
|
1493
1645
|
{
|
|
1494
1646
|
usage: usage ?? null,
|
|
@@ -1498,23 +1650,21 @@ var init_LiteLLMPage = __esm({
|
|
|
1498
1650
|
loading: usageLoading,
|
|
1499
1651
|
userInfo
|
|
1500
1652
|
}
|
|
1501
|
-
), activeTab === "keys" && /* @__PURE__ */
|
|
1653
|
+
), activeTab === "keys" && /* @__PURE__ */ import_react7.default.createElement(
|
|
1502
1654
|
KeysTable,
|
|
1503
1655
|
{
|
|
1504
1656
|
keys: keys ?? [],
|
|
1505
1657
|
models: allowedModels,
|
|
1506
|
-
teams: teams ?? [],
|
|
1507
1658
|
loading: keysLoading || modelsLoading,
|
|
1508
|
-
|
|
1659
|
+
onGenerateKeyClick: () => setGenerateDialogOpen(true),
|
|
1509
1660
|
onUpdateKey: handleUpdateKey,
|
|
1510
1661
|
onBlockKey: handleBlockKey,
|
|
1511
1662
|
onUnblockKey: handleUnblockKey,
|
|
1512
1663
|
onResetKeySpend: handleResetKeySpend,
|
|
1513
1664
|
onDeleteKey: handleDeleteKey,
|
|
1514
|
-
onPruneExpiredKeys: handlePruneExpiredKeys
|
|
1515
|
-
onGetConfig: () => api.getConfig()
|
|
1665
|
+
onPruneExpiredKeys: handlePruneExpiredKeys
|
|
1516
1666
|
}
|
|
1517
|
-
), activeTab === "teams" && /* @__PURE__ */
|
|
1667
|
+
), activeTab === "teams" && /* @__PURE__ */ import_react7.default.createElement(
|
|
1518
1668
|
TeamUsage,
|
|
1519
1669
|
{
|
|
1520
1670
|
teams: teams ?? [],
|
|
@@ -1525,15 +1675,28 @@ var init_LiteLLMPage = __esm({
|
|
|
1525
1675
|
},
|
|
1526
1676
|
getTeamUsageLoading: (teamId) => teamUsageLoading[teamId] ?? false
|
|
1527
1677
|
}
|
|
1528
|
-
), activeTab === "audit" && userInfo.can_view_audit && /* @__PURE__ */
|
|
1529
|
-
|
|
1678
|
+
), activeTab === "audit" && userInfo.can_view_audit && /* @__PURE__ */ import_react7.default.createElement(AuditLog, { api }), /* @__PURE__ */ import_react7.default.createElement(
|
|
1679
|
+
GenerateKeyDialog,
|
|
1680
|
+
{
|
|
1681
|
+
open: generateDialogOpen,
|
|
1682
|
+
onClose: () => setGenerateDialogOpen(false),
|
|
1683
|
+
keys: keys ?? [],
|
|
1684
|
+
models: allowedModels,
|
|
1685
|
+
teams: teams ?? [],
|
|
1686
|
+
username: userInfo.user_id,
|
|
1687
|
+
keyGenerationSettings: liteLlmConfig?.keyGeneration,
|
|
1688
|
+
onGenerateKey: handleGenerateKey,
|
|
1689
|
+
onGetConfig: () => api.getConfig()
|
|
1690
|
+
}
|
|
1691
|
+
), /* @__PURE__ */ import_react7.default.createElement(
|
|
1692
|
+
import_material7.Snackbar,
|
|
1530
1693
|
{
|
|
1531
1694
|
open: !!snackbar,
|
|
1532
1695
|
autoHideDuration: 5e3,
|
|
1533
1696
|
onClose: () => setSnackbar(null),
|
|
1534
1697
|
anchorOrigin: { vertical: "bottom", horizontal: "right" }
|
|
1535
1698
|
},
|
|
1536
|
-
snackbar ? /* @__PURE__ */
|
|
1699
|
+
snackbar ? /* @__PURE__ */ import_react7.default.createElement(import_material7.Alert, { severity: snackbar.severity, onClose: () => setSnackbar(null) }, snackbar.message) : void 0
|
|
1537
1700
|
));
|
|
1538
1701
|
};
|
|
1539
1702
|
}
|
|
@@ -1555,8 +1718,8 @@ __export(index_exports, {
|
|
|
1555
1718
|
module.exports = __toCommonJS(index_exports);
|
|
1556
1719
|
|
|
1557
1720
|
// src/plugin.tsx
|
|
1558
|
-
var
|
|
1559
|
-
var
|
|
1721
|
+
var import_react8 = __toESM(require("react"));
|
|
1722
|
+
var import_icons_material6 = require("@mui/icons-material");
|
|
1560
1723
|
var import_frontend_plugin_api = require("@backstage/frontend-plugin-api");
|
|
1561
1724
|
init_api();
|
|
1562
1725
|
var liteLlmApi = import_frontend_plugin_api.ApiBlueprint.make({
|
|
@@ -1570,10 +1733,10 @@ var liteLlmPage = import_frontend_plugin_api.PageBlueprint.make({
|
|
|
1570
1733
|
params: {
|
|
1571
1734
|
path: "/litellm",
|
|
1572
1735
|
title: "LiteLLM",
|
|
1573
|
-
icon: /* @__PURE__ */
|
|
1736
|
+
icon: /* @__PURE__ */ import_react8.default.createElement(import_icons_material6.TrendingUp, null),
|
|
1574
1737
|
loader: async () => {
|
|
1575
1738
|
const { LiteLLMPage: LiteLLMPage2 } = await Promise.resolve().then(() => (init_LiteLLMPage(), LiteLLMPage_exports));
|
|
1576
|
-
return /* @__PURE__ */
|
|
1739
|
+
return /* @__PURE__ */ import_react8.default.createElement(LiteLLMPage2, null);
|
|
1577
1740
|
}
|
|
1578
1741
|
}
|
|
1579
1742
|
});
|
|
@@ -1590,8 +1753,8 @@ init_UsageStats();
|
|
|
1590
1753
|
init_TeamUsage();
|
|
1591
1754
|
|
|
1592
1755
|
// src/components/LiteLLMHomeWidget.tsx
|
|
1593
|
-
var
|
|
1594
|
-
var
|
|
1756
|
+
var import_react9 = __toESM(require("react"));
|
|
1757
|
+
var import_material8 = require("@mui/material");
|
|
1595
1758
|
var import_recharts3 = require("recharts");
|
|
1596
1759
|
var import_core_plugin_api3 = require("@backstage/core-plugin-api");
|
|
1597
1760
|
init_api();
|
|
@@ -1608,19 +1771,19 @@ function presetToDateRange(preset) {
|
|
|
1608
1771
|
}
|
|
1609
1772
|
return { start, end };
|
|
1610
1773
|
}
|
|
1611
|
-
var Kpi = ({ label, value }) => /* @__PURE__ */
|
|
1774
|
+
var Kpi = ({ label, value }) => /* @__PURE__ */ import_react9.default.createElement(import_material8.Box, null, /* @__PURE__ */ import_react9.default.createElement(import_material8.Typography, { variant: "caption", color: "text.secondary", display: "block" }, label), /* @__PURE__ */ import_react9.default.createElement(import_material8.Typography, { variant: "subtitle1", fontWeight: 600 }, value));
|
|
1612
1775
|
var LiteLLMHomeWidget = ({
|
|
1613
1776
|
defaultPeriod = "7d",
|
|
1614
1777
|
title = "LiteLLM Usage"
|
|
1615
1778
|
}) => {
|
|
1616
1779
|
const api = (0, import_core_plugin_api3.useApi)(liteLlmApiRef);
|
|
1617
|
-
const [period, setPeriod] = (0,
|
|
1618
|
-
const [loading, setLoading] = (0,
|
|
1619
|
-
const [usageError, setUsageError] = (0,
|
|
1620
|
-
const [keysError, setKeysError] = (0,
|
|
1621
|
-
const [usage, setUsage] = (0,
|
|
1622
|
-
const [keys, setKeys] = (0,
|
|
1623
|
-
(0,
|
|
1780
|
+
const [period, setPeriod] = (0, import_react9.useState)(defaultPeriod);
|
|
1781
|
+
const [loading, setLoading] = (0, import_react9.useState)(true);
|
|
1782
|
+
const [usageError, setUsageError] = (0, import_react9.useState)(null);
|
|
1783
|
+
const [keysError, setKeysError] = (0, import_react9.useState)(null);
|
|
1784
|
+
const [usage, setUsage] = (0, import_react9.useState)(null);
|
|
1785
|
+
const [keys, setKeys] = (0, import_react9.useState)([]);
|
|
1786
|
+
(0, import_react9.useEffect)(() => {
|
|
1624
1787
|
let cancelled = false;
|
|
1625
1788
|
setLoading(true);
|
|
1626
1789
|
setUsageError(null);
|
|
@@ -1657,17 +1820,17 @@ var LiteLLMHomeWidget = ({
|
|
|
1657
1820
|
spend: d.spend
|
|
1658
1821
|
}));
|
|
1659
1822
|
const hasSparkline = dailyData.length > 0;
|
|
1660
|
-
return /* @__PURE__ */
|
|
1661
|
-
|
|
1823
|
+
return /* @__PURE__ */ import_react9.default.createElement(import_material8.Paper, { sx: { p: 2 } }, /* @__PURE__ */ import_react9.default.createElement(import_material8.Box, { display: "flex", justifyContent: "space-between", alignItems: "center", mb: 1.5 }, /* @__PURE__ */ import_react9.default.createElement(import_material8.Typography, { variant: "h6" }, title), /* @__PURE__ */ import_react9.default.createElement(import_material8.FormControl, { size: "small", sx: { minWidth: 90 } }, /* @__PURE__ */ import_react9.default.createElement(
|
|
1824
|
+
import_material8.Select,
|
|
1662
1825
|
{
|
|
1663
1826
|
value: period,
|
|
1664
1827
|
onChange: (e) => setPeriod(e.target.value),
|
|
1665
1828
|
displayEmpty: true
|
|
1666
1829
|
},
|
|
1667
|
-
/* @__PURE__ */
|
|
1668
|
-
/* @__PURE__ */
|
|
1669
|
-
/* @__PURE__ */
|
|
1670
|
-
))), loading && /* @__PURE__ */
|
|
1830
|
+
/* @__PURE__ */ import_react9.default.createElement(import_material8.MenuItem, { value: "today" }, "Today"),
|
|
1831
|
+
/* @__PURE__ */ import_react9.default.createElement(import_material8.MenuItem, { value: "7d" }, "7d"),
|
|
1832
|
+
/* @__PURE__ */ import_react9.default.createElement(import_material8.MenuItem, { value: "30d" }, "30d")
|
|
1833
|
+
))), loading && /* @__PURE__ */ import_react9.default.createElement(import_material8.Box, { display: "flex", justifyContent: "center", alignItems: "center", minHeight: 120 }, /* @__PURE__ */ import_react9.default.createElement(import_material8.CircularProgress, { size: 32 })), !loading && totalFailure && /* @__PURE__ */ import_react9.default.createElement(import_material8.Alert, { severity: "error", sx: { mt: 1 } }, usageError ?? "Failed to load usage data"), !loading && !totalFailure && /* @__PURE__ */ import_react9.default.createElement(import_react9.default.Fragment, null, partialFailure && /* @__PURE__ */ import_react9.default.createElement(import_material8.Alert, { severity: "warning", sx: { mt: 1, mb: 1 } }, usageError ? `Usage data unavailable (${usageError}).` : "", keysError ? ` Key list unavailable (${keysError}).` : "", " Showing what loaded."), /* @__PURE__ */ import_react9.default.createElement(import_material8.Grid, { container: true, spacing: 2, sx: { mb: hasSparkline ? 1.5 : 0 } }, /* @__PURE__ */ import_react9.default.createElement(import_material8.Grid, { item: true, xs: 6 }, /* @__PURE__ */ import_react9.default.createElement(Kpi, { label: "USD Spent", value: fmtUsd(usage?.total_spend ?? 0) })), /* @__PURE__ */ import_react9.default.createElement(import_material8.Grid, { item: true, xs: 6 }, /* @__PURE__ */ import_react9.default.createElement(Kpi, { label: "Tokens In", value: fmtInt(usage?.prompt_tokens ?? 0) })), /* @__PURE__ */ import_react9.default.createElement(import_material8.Grid, { item: true, xs: 6 }, /* @__PURE__ */ import_react9.default.createElement(Kpi, { label: "Tokens Out", value: fmtInt(usage?.completion_tokens ?? 0) })), /* @__PURE__ */ import_react9.default.createElement(import_material8.Grid, { item: true, xs: 6 }, /* @__PURE__ */ import_react9.default.createElement(Kpi, { label: "Keys", value: fmtInt(keys.length) }))), hasSparkline && /* @__PURE__ */ import_react9.default.createElement(import_material8.Box, { height: 120 }, /* @__PURE__ */ import_react9.default.createElement(import_recharts3.ResponsiveContainer, { width: "100%", height: "100%" }, /* @__PURE__ */ import_react9.default.createElement(import_recharts3.AreaChart, { data: dailyData, margin: { top: 4, right: 0, bottom: 0, left: 0 } }, /* @__PURE__ */ import_react9.default.createElement(
|
|
1671
1834
|
import_recharts3.Area,
|
|
1672
1835
|
{
|
|
1673
1836
|
type: "monotone",
|