mirror of
https://github.com/coalaura/whiskr.git
synced 2025-09-08 17:06:42 +00:00
total cost tracker
This commit is contained in:
@@ -30,7 +30,6 @@ whiskr is a private, self-hosted web chat interface for interacting with AI mode
|
|||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
- total cost tracker
|
|
||||||
- settings
|
- settings
|
||||||
- auto-retry on edit
|
- auto-retry on edit
|
||||||
- ctrl+enter vs enter for sending
|
- ctrl+enter vs enter for sending
|
||||||
|
@@ -73,29 +73,26 @@ body.resizing * {
|
|||||||
cursor: grabbing !important;
|
cursor: grabbing !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#total,
|
||||||
#version {
|
#version {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
top: 3px;
|
top: 3px;
|
||||||
right: 6px;
|
right: 4px;
|
||||||
color: #a5adcb;
|
color: #a5adcb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#total {
|
||||||
|
right: unset;
|
||||||
|
left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
#version a {
|
#version a {
|
||||||
color: #a5adcb;
|
color: #a5adcb;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.loading #version {
|
|
||||||
font-size: 0;
|
|
||||||
animation: rotating 1.2s linear infinite;
|
|
||||||
background-image: url(icons/spinner.svg);
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
top: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#loading {
|
#loading {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
@@ -16,6 +16,7 @@
|
|||||||
<title>whiskr</title>
|
<title>whiskr</title>
|
||||||
</head>
|
</head>
|
||||||
<body class="loading">
|
<body class="loading">
|
||||||
|
<div id="total" title="Accumulated total cost, middle-click to reset"></div>
|
||||||
<div id="version"></div>
|
<div id="version"></div>
|
||||||
|
|
||||||
<div id="loading">
|
<div id="loading">
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
(() => {
|
(() => {
|
||||||
const $version = document.getElementById("version"),
|
const $version = document.getElementById("version"),
|
||||||
|
$total = document.getElementById("total"),
|
||||||
$messages = document.getElementById("messages"),
|
$messages = document.getElementById("messages"),
|
||||||
$chat = document.getElementById("chat"),
|
$chat = document.getElementById("chat"),
|
||||||
$message = document.getElementById("message"),
|
$message = document.getElementById("message"),
|
||||||
@@ -41,7 +42,14 @@
|
|||||||
let searchAvailable = false,
|
let searchAvailable = false,
|
||||||
activeMessage = null,
|
activeMessage = null,
|
||||||
isResizing = false,
|
isResizing = false,
|
||||||
scrollResize = false;
|
scrollResize = false,
|
||||||
|
totalCost = 0;
|
||||||
|
|
||||||
|
function updateTotalCost() {
|
||||||
|
storeValue("total-cost", totalCost);
|
||||||
|
|
||||||
|
$total.textContent = formatMoney(totalCost);
|
||||||
|
}
|
||||||
|
|
||||||
function updateScrollButton() {
|
function updateScrollButton() {
|
||||||
const bottom = $messages.scrollHeight - ($messages.scrollTop + $messages.offsetHeight);
|
const bottom = $messages.scrollHeight - ($messages.scrollTop + $messages.offsetHeight);
|
||||||
@@ -448,8 +456,7 @@
|
|||||||
if (this.#statistics) {
|
if (this.#statistics) {
|
||||||
const { provider, model, ttft, time, input, output, cost } = this.#statistics;
|
const { provider, model, ttft, time, input, output, cost } = this.#statistics;
|
||||||
|
|
||||||
const tps = output / (time / 1000),
|
const tps = output / (time / 1000);
|
||||||
price = cost < 1 ? `${fixed(cost * 100, 1)}ct` : `$${fixed(cost, 2)}`;
|
|
||||||
|
|
||||||
html = [
|
html = [
|
||||||
provider ? `<div class="provider">${provider} (${model.split("/").pop()})</div>` : "",
|
provider ? `<div class="provider">${provider} (${model.split("/").pop()})</div>` : "",
|
||||||
@@ -462,7 +469,7 @@
|
|||||||
=
|
=
|
||||||
<div class="total">${input + output}t</div>
|
<div class="total">${input + output}t</div>
|
||||||
</div>`,
|
</div>`,
|
||||||
`<div class="cost">${price}</div>`,
|
`<div class="cost">${formatMoney(cost)}</div>`,
|
||||||
].join("");
|
].join("");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -587,6 +594,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.setStatistics(data);
|
this.setStatistics(data);
|
||||||
|
|
||||||
|
totalCost += data.cost;
|
||||||
|
|
||||||
|
updateTotalCost();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
|
||||||
@@ -1030,6 +1041,11 @@
|
|||||||
// start icon preload
|
// start icon preload
|
||||||
preloadIcons(data.icons);
|
preloadIcons(data.icons);
|
||||||
|
|
||||||
|
// render total cost
|
||||||
|
totalCost = loadValue("total-cost", 0);
|
||||||
|
|
||||||
|
updateTotalCost();
|
||||||
|
|
||||||
// render version
|
// render version
|
||||||
if (data.version === "dev") {
|
if (data.version === "dev") {
|
||||||
$version.remove();
|
$version.remove();
|
||||||
@@ -1223,6 +1239,16 @@
|
|||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$total.addEventListener("auxclick", (event) => {
|
||||||
|
if (event.button !== 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
totalCost = 0;
|
||||||
|
|
||||||
|
updateTotalCost();
|
||||||
|
});
|
||||||
|
|
||||||
$messages.addEventListener("scroll", () => {
|
$messages.addEventListener("scroll", () => {
|
||||||
updateScrollButton();
|
updateScrollButton();
|
||||||
});
|
});
|
||||||
@@ -1244,7 +1270,7 @@
|
|||||||
$resizeBar.addEventListener("mousedown", event => {
|
$resizeBar.addEventListener("mousedown", event => {
|
||||||
const isAtBottom = $messages.scrollHeight - ($messages.scrollTop + $messages.offsetHeight) <= 10;
|
const isAtBottom = $messages.scrollHeight - ($messages.scrollTop + $messages.offsetHeight) <= 10;
|
||||||
|
|
||||||
if (event.buttons === 4) {
|
if (event.button === 1) {
|
||||||
$chat.style.height = "";
|
$chat.style.height = "";
|
||||||
|
|
||||||
storeValue("resized", false);
|
storeValue("resized", false);
|
||||||
@@ -1252,7 +1278,7 @@
|
|||||||
scroll(isAtBottom, true);
|
scroll(isAtBottom, true);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
} else if (event.buttons !== 1) {
|
} else if (event.button !== 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -84,6 +84,26 @@ function fixed(num, decimals = 0) {
|
|||||||
return num.toFixed(decimals).replace(/\.?0+$/m, "");
|
return num.toFixed(decimals).replace(/\.?0+$/m, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatMoney(num) {
|
||||||
|
if (num === 0) {
|
||||||
|
return "0ct";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (num < 1) {
|
||||||
|
let decimals = 1;
|
||||||
|
|
||||||
|
if (num < 0.0001) {
|
||||||
|
decimals = 3;
|
||||||
|
} else if (num < 0.001) {
|
||||||
|
decimals = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${fixed(num * 100, decimals)}ct`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `$${fixed(num, 2)}`;
|
||||||
|
}
|
||||||
|
|
||||||
function clamp(num, min, max) {
|
function clamp(num, min, max) {
|
||||||
return Math.min(Math.max(num, min), max);
|
return Math.min(Math.max(num, min), max);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user