improves display of large token numbers
This commit is contained in:
parent
4b32130eaa
commit
fe0f04ceb8
|
@ -11,7 +11,8 @@ import {
|
|||
UserSchema,
|
||||
HttpError,
|
||||
} from "../common";
|
||||
import { keyPool } from "../../key-management";
|
||||
import { ModelFamily, keyPool } from "../../key-management";
|
||||
import { getTokenCostUsd, prettyTokens } from "../../stats";
|
||||
|
||||
const router = Router();
|
||||
|
||||
|
@ -56,15 +57,22 @@ router.get("/view-user/:token", (req, res) => {
|
|||
});
|
||||
|
||||
router.get("/list-users", (req, res) => {
|
||||
const sort = parseSort(req.query.sort) || ["promptCount", "lastUsedAt"];
|
||||
const sort = parseSort(req.query.sort) || ["sumCost", "createdAt"];
|
||||
const requestedPageSize =
|
||||
Number(req.query.perPage) || Number(req.cookies.perPage) || 20;
|
||||
const perPage = Math.max(1, Math.min(1000, requestedPageSize));
|
||||
const users = userStore
|
||||
.getUsers()
|
||||
.map((user) => {
|
||||
const sum = Object.values(user.tokenCounts).reduce((a, b) => a + b, 0); // TODO: cache
|
||||
return { ...user, sumTokenCounts: sum };
|
||||
const sums = { sumTokens: 0, sumCost: 0, prettyUsage: "" };
|
||||
Object.entries(user.tokenCounts).forEach(([model, tokens]) => {
|
||||
sums.sumTokens += tokens;
|
||||
sums.sumCost += getTokenCostUsd(model as ModelFamily, tokens);
|
||||
});
|
||||
sums.prettyUsage = `${prettyTokens(
|
||||
sums.sumTokens
|
||||
)} ($${sums.sumCost.toFixed(2)}) `;
|
||||
return { ...user, ...sums };
|
||||
})
|
||||
.sort(sortBy(sort, false));
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
import { getUniqueIps } from "./proxy/rate-limit";
|
||||
import { getEstimatedWaitTime, getQueueLength } from "./proxy/queue";
|
||||
import { logger } from "./logger";
|
||||
import { getTokenCostUsd, prettyTokens } from "./stats";
|
||||
|
||||
const INFO_PAGE_TTL = 2000;
|
||||
let infoPageHtml: string | undefined;
|
||||
|
@ -49,27 +50,6 @@ type ServiceAggregates = {
|
|||
const modelStats = new Map<ModelAggregateKey, number>();
|
||||
const serviceStats = new Map<keyof ServiceAggregates, number>();
|
||||
|
||||
// technically slightly underestimates, because completion tokens cost more
|
||||
// than prompt tokens but we don't track those separately right now
|
||||
function getTokenCostUsd(model: ModelFamily, tokens: number) {
|
||||
let cost = 0;
|
||||
switch (model) {
|
||||
case "gpt4-32k":
|
||||
cost = 0.00006;
|
||||
break;
|
||||
case "gpt4":
|
||||
cost = 0.00003;
|
||||
break;
|
||||
case "turbo":
|
||||
cost = 0.0000015;
|
||||
break;
|
||||
case "claude":
|
||||
cost = 0.00001102;
|
||||
break;
|
||||
}
|
||||
return cost * tokens;
|
||||
}
|
||||
|
||||
export const handleInfoPage = (req: Request, res: Response) => {
|
||||
if (infoPageLastUpdated + INFO_PAGE_TTL > Date.now()) {
|
||||
res.send(infoPageHtml);
|
||||
|
@ -96,6 +76,7 @@ function cacheInfoPageHtml(baseUrl: string) {
|
|||
const anthropicKeys = serviceStats.get("anthropicKeys") || 0;
|
||||
const proompts = serviceStats.get("proompts") || 0;
|
||||
const tokens = serviceStats.get("tokens") || 0;
|
||||
const tokenCost = serviceStats.get("tokenCost") || 0;
|
||||
|
||||
const info = {
|
||||
uptime: Math.floor(process.uptime()),
|
||||
|
@ -104,7 +85,7 @@ function cacheInfoPageHtml(baseUrl: string) {
|
|||
...(anthropicKeys ? { anthropic: baseUrl + "/proxy/anthropic" } : {}),
|
||||
},
|
||||
proompts,
|
||||
tookens: `${tokens} ($${(serviceStats.get("tokenCost") || 0).toFixed(2)})`,
|
||||
tookens: `${prettyTokens(tokens)} ($${tokenCost.toFixed(2)})`,
|
||||
...(config.modelRateLimit ? { proomptersNow: getUniqueIps() } : {}),
|
||||
openaiKeys,
|
||||
anthropicKeys,
|
||||
|
@ -243,7 +224,7 @@ function getOpenAIInfo() {
|
|||
const cost = getTokenCostUsd(f, tokens);
|
||||
|
||||
info[f] = {
|
||||
usage: `${tokens} tokens ($${cost.toFixed(2)})`,
|
||||
usage: `${prettyTokens(tokens)} tokens ($${cost.toFixed(2)})`,
|
||||
activeKeys: modelStats.get(`${f}__active`) || 0,
|
||||
trialKeys: modelStats.get(`${f}__trial`) || 0,
|
||||
revokedKeys: modelStats.get(`${f}__revoked`) || 0,
|
||||
|
@ -282,7 +263,7 @@ function getAnthropicInfo() {
|
|||
const cost = getTokenCostUsd("claude", tokens);
|
||||
return {
|
||||
claude: {
|
||||
usage: `${tokens} tokens ($${cost.toFixed(2)})`,
|
||||
usage: `${prettyTokens(tokens)} tokens ($${cost.toFixed(2)})`,
|
||||
activeKeys: claudeInfo.active,
|
||||
proomptersInQueue: claudeInfo.queued,
|
||||
estimatedQueueTime: claudeInfo.queueTime,
|
||||
|
|
|
@ -16,11 +16,10 @@ import { logger } from "../../logger";
|
|||
|
||||
const log = logger.child({ module: "users" });
|
||||
|
||||
export type UserTokenCounts = {
|
||||
turbo: number;
|
||||
gpt4: number;
|
||||
claude: number;
|
||||
"gpt4-32k"?: number;
|
||||
type UserTokenCounts = {
|
||||
[K in Exclude<ModelFamily, "gpt4-32k">]: number;
|
||||
} & {
|
||||
[K in "gpt4-32k"]?: number; // Optional because it was added later
|
||||
};
|
||||
|
||||
export interface User {
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
import { ModelFamily } from "../key-management";
|
||||
|
||||
// technically slightly underestimates, because completion tokens cost more
|
||||
// than prompt tokens but we don't track those separately right now
|
||||
export function getTokenCostUsd(model: ModelFamily, tokens: number) {
|
||||
let cost = 0;
|
||||
switch (model) {
|
||||
case "gpt4-32k":
|
||||
cost = 0.00006;
|
||||
break;
|
||||
case "gpt4":
|
||||
cost = 0.00003;
|
||||
break;
|
||||
case "turbo":
|
||||
cost = 0.0000015;
|
||||
break;
|
||||
case "claude":
|
||||
cost = 0.00001102;
|
||||
break;
|
||||
}
|
||||
return cost * tokens;
|
||||
}
|
||||
|
||||
export function prettyTokens(tokens: number): string {
|
||||
if (tokens < 1000) {
|
||||
return tokens.toString();
|
||||
} else if (tokens < 1000000) {
|
||||
return (tokens / 1000).toFixed(1) + "k";
|
||||
} else if (tokens < 1000000000) {
|
||||
return (tokens / 1000000).toFixed(2) + "m";
|
||||
} else {
|
||||
return (tokens / 1000000000).toFixed(2) + "b";
|
||||
}
|
||||
}
|
|
@ -2,5 +2,14 @@
|
|||
<footer>
|
||||
<a href="/admin">Index</a> | <a href="/admin/logout">Logout</a>
|
||||
</footer>
|
||||
<script>
|
||||
document.querySelectorAll("td").forEach(function(td) {
|
||||
if (td.innerText.match(/^\d{13}$/)) {
|
||||
if (td.innerText == 0) return 'never';
|
||||
var date = new Date(parseInt(td.innerText));
|
||||
td.innerText = date.toISOString().replace("T", " ").replace(/\.\d+Z$/, "");
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
<th>User</th>
|
||||
<th <% if (sort.includes("ip")) { %>class="active"<% } %> ><a href="/admin/manage/list-users?sort=ip">IPs</a></th>
|
||||
<th <% if (sort.includes("promptCount")) { %>class="active"<% } %> ><a href="/admin/manage/list-users?sort=promptCount">Prompts</a></th>
|
||||
<th <% if (sort.includes("sumTokenCounts")) { %>class="active"<% } %> ><a href="/admin/manage/list-users?sort=sumTokenCounts">Tokens</a></th>
|
||||
<th <% if (sort.includes("sumCost")) { %>class="active"<% } %> ><a href="/admin/manage/list-users?sort=sumCost">Usage</a></th>
|
||||
<th>Type</th>
|
||||
<th <% if (sort.includes("createdAt")) { %>class="active"<% } %> ><a href="/admin/manage/list-users?sort=createdAt">Created (UTC)</a></th>
|
||||
<th <% if (sort.includes("lastUsedAt")) { %>class="active"<% } %> ><a href="/admin/manage/list-users?sort=lastUsedAt">Last Used (UTC)</a></th>
|
||||
|
@ -34,7 +34,7 @@
|
|||
</td>
|
||||
<td><%= user.ip.length %></td>
|
||||
<td><%= user.promptCount %></td>
|
||||
<td><%= user.sumTokenCounts %></td>
|
||||
<td><%= user.prettyUsage %></td>
|
||||
<td><%= user.type %></td>
|
||||
<td><%= user.createdAt %></td>
|
||||
<td><%= user.lastUsedAt ?? "never" %></td>
|
||||
|
@ -117,14 +117,4 @@
|
|||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<script>
|
||||
document.querySelectorAll("td").forEach(function(td) {
|
||||
if (td.innerText.match(/^\d{13}$/)) {
|
||||
if (td.innerText == 0) return 'never';
|
||||
var date = new Date(parseInt(td.innerText));
|
||||
td.innerText = date.toISOString().replace("T", " ").replace(/\.\d+Z$/, "");
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<%- include("../_partials/admin-footer") %>
|
||||
|
|
Loading…
Reference in New Issue