diff --git a/kloe.example.json b/kloe.example.json index 72420f7..1e84ef2 100644 --- a/kloe.example.json +++ b/kloe.example.json @@ -41,7 +41,23 @@ "baseUrl": "https://kloe.dunkirk.sh", "allowedSubs": [], "sessionTtlDays": 30, - "appName": "kloe" + "appName": "kloe", + "roles": { + "owner": { + "admin": true, + "sandbox": true, + "publish": true, + "models": ["*"], + "search": ["*"], + "subs": ["https://you.example/"] + }, + "guest": { + "models": ["hyper/*"], + "search": ["duckduckgo"], + "usdPerDay": 0.5, + "tokensPerDay": 0 + } + } }, "prompt": { "name": "Kloe", diff --git a/kloe.schema.json b/kloe.schema.json index c01aa77..1afdf00 100644 --- a/kloe.schema.json +++ b/kloe.schema.json @@ -450,6 +450,14 @@ }, "default": [] }, + "usdPerDay": { + "type": "number", + "default": 0 + }, + "tokensPerDay": { + "type": "number", + "default": 0 + }, "subs": { "type": "array", "items": { diff --git a/src/auth.ts b/src/auth.ts index 036ff5c..a56b568 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -348,6 +348,8 @@ const BUILT_IN: Record = { publish: true, models: ["*"], search: ["*"], + usdPerDay: 0, + tokensPerDay: 0, subs: [], providerRoles: [], }, @@ -357,6 +359,8 @@ const BUILT_IN: Record = { publish: false, models: [], search: [], + usdPerDay: 0, + tokensPerDay: 0, subs: [], providerRoles: [], }, diff --git a/src/client/app.css b/src/client/app.css index 5a2f529..e872e7b 100644 --- a/src/client/app.css +++ b/src/client/app.css @@ -3527,6 +3527,18 @@ body.lb-open { color: var(--ink-muted); margin-top: 2px; } +/* The figure at the end of a usage row. Tabular so a column of amounts lines + up on the decimal instead of shimmering as it updates. */ +.usagefig { + flex: none; + font-size: 13px; + font-variant-numeric: tabular-nums; + color: var(--ink-muted); +} +.usagebar { + margin: 8px 2px 0; +} + .connrow .btn, .connrow .connkey { flex: none; diff --git a/src/client/app.js b/src/client/app.js index d7604bf..993f5b9 100644 --- a/src/client/app.js +++ b/src/client/app.js @@ -817,7 +817,10 @@ import { mountSidebar } from "./sidebar.js"; delete queued[runId]; renderQueue(); } - function failUser(runId) { + // `reason` is the server's own words when it had any (a budget, a model a + // role may not reach). Silence there reads as a bug in kloe rather than a + // rule the person could do something about. + function failUser(runId, reason) { var p = pending[runId]; if (!p) return; p.turn.classList.remove("pending"); @@ -825,7 +828,7 @@ import { mountSidebar } from "./sidebar.js"; if (p.turn.querySelector(".failbar")) return; var fb = document.createElement("div"); fb.className = "failbar"; - fb.textContent = "not sent — "; + fb.textContent = reason ? reason + " " : "not sent — "; var btn = document.createElement("button"); btn.type = "button"; btn.textContent = "Retry"; @@ -3103,7 +3106,7 @@ import { mountSidebar } from "./sidebar.js"; var err = await res.json().catch(function () { return {}; }); - failUser(runId); + failUser(runId, err.error); if (err.error) console.warn("prompt rejected:", err.error); return; } diff --git a/src/client/views/settings.js b/src/client/views/settings.js index 116f205..df112eb 100644 --- a/src/client/views/settings.js +++ b/src/client/views/settings.js @@ -37,6 +37,7 @@ var TEMPLATE = '
' + '' + '' + + '' + '' + '' + '' + @@ -48,6 +49,14 @@ var TEMPLATE = '

Your picker. Turn models on, drag them into the order you want (⌘-click to move several at once), and rename any of them. Which models you can choose from is set in kloe.json; what you do with them is yours.

' + '
Loading…
' + "" + + '" + '