@webfueler/oc-dash 0.1.5 → 0.1.7
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 +90 -144
- package/dist/assets/index-B9io-Y-k.js +9 -0
- package/dist/assets/{index-HDE3yxEk.css → index-CCkSH4wy.css} +1 -1
- package/dist/index.html +2 -2
- package/dist-server/index.js +15 -0
- package/dist-server/models.js +67 -0
- package/docs/screenshot.png +0 -0
- package/package.json +14 -2
- package/dist/assets/index-BGry4XSt.js +0 -9
package/dist/index.html
CHANGED
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
else document.documentElement.dataset.theme = c
|
|
22
22
|
})()
|
|
23
23
|
</script>
|
|
24
|
-
<script type="module" crossorigin src="/assets/index-
|
|
25
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
24
|
+
<script type="module" crossorigin src="/assets/index-B9io-Y-k.js"></script>
|
|
25
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CCkSH4wy.css">
|
|
26
26
|
</head>
|
|
27
27
|
<body>
|
|
28
28
|
<div id="root"></div>
|
package/dist-server/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { Hono } from "hono";
|
|
|
4
4
|
import { existsSync, readFileSync } from "node:fs";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { errorMessage, getOpencode, ocGetJson } from "./opencode.js";
|
|
7
|
+
import { modelNames } from "./models.js";
|
|
7
8
|
import { contextStatsRange, localTimezone, parseProjectParam, parseRangePreset, resolveRange } from "./ranges.js";
|
|
8
9
|
import { compareVersions } from "./version.js";
|
|
9
10
|
import { walkSessions } from "./walk.js";
|
|
@@ -235,6 +236,20 @@ app.get("/api/sessions", async (c) => {
|
|
|
235
236
|
return c.json({ error: `opencode service request failed: ${errorMessage(err)}` }, 502);
|
|
236
237
|
}
|
|
237
238
|
});
|
|
239
|
+
// Mission 044: the providerID/id -> display-name map for the label surfaces
|
|
240
|
+
// (session chips, model filter, card header, models table). A separate
|
|
241
|
+
// lookup, not the stats path; the cache inside modelNames() keeps this at
|
|
242
|
+
// one upstream fetch per TTL window. Never an error surface: the client
|
|
243
|
+
// falls back to the raw ids when the map is empty.
|
|
244
|
+
app.get("/api/model-names", async (c) => {
|
|
245
|
+
try {
|
|
246
|
+
const oc = await getOpencode();
|
|
247
|
+
return c.json({ names: await modelNames(oc) });
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
return c.json({ names: {} });
|
|
251
|
+
}
|
|
252
|
+
});
|
|
238
253
|
// Built frontend (production). API routes above take precedence.
|
|
239
254
|
app.use("*", serveStatic({ root: DIST_ROOT }));
|
|
240
255
|
app.get("*", serveStatic({ path: DIST_INDEX }));
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { ocGetJson } from "./opencode.js";
|
|
2
|
+
/**
|
|
3
|
+
* Pure parse of the opencode2 service's GET /api/model payload into the
|
|
4
|
+
* providerID/id -> name map. Entries missing any of the three fields — or
|
|
5
|
+
* carrying an empty or whitespace-only name — drop out, so the client's
|
|
6
|
+
* fallback (the raw id) never receives a blank or wrong label. Accepts both
|
|
7
|
+
* the raw array and the { data: [...] } wrapper the promise client can
|
|
8
|
+
* produce.
|
|
9
|
+
*/
|
|
10
|
+
export function parseModelNames(payload) {
|
|
11
|
+
if (payload && typeof payload === "object" && !Array.isArray(payload)) {
|
|
12
|
+
const wrapped = payload;
|
|
13
|
+
if (Array.isArray(wrapped.data))
|
|
14
|
+
payload = wrapped.data;
|
|
15
|
+
}
|
|
16
|
+
if (!Array.isArray(payload))
|
|
17
|
+
return {};
|
|
18
|
+
const names = {};
|
|
19
|
+
for (const entry of payload) {
|
|
20
|
+
if (!entry || typeof entry !== "object")
|
|
21
|
+
continue;
|
|
22
|
+
const { providerID, id, name } = entry;
|
|
23
|
+
if (typeof providerID !== "string" || typeof id !== "string" || typeof name !== "string") {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
// Trim before the non-empty check so a whitespace-only name can never
|
|
27
|
+
// reach the label surfaces as a blank; surrounding whitespace on a real
|
|
28
|
+
// name is noise, so the trimmed value is what gets stored.
|
|
29
|
+
const trimmed = name.trim();
|
|
30
|
+
if (!providerID || !id || !trimmed)
|
|
31
|
+
continue;
|
|
32
|
+
names[`${providerID}/${id}`] = trimmed;
|
|
33
|
+
}
|
|
34
|
+
return names;
|
|
35
|
+
}
|
|
36
|
+
// The model catalog is static for the life of a service, so a success lives
|
|
37
|
+
// an hour and a failure retries sooner — the update-check pattern
|
|
38
|
+
// (index.ts:43-44): the caller never waits on a cold fetch more than once
|
|
39
|
+
// per window, and any failure degrades to an empty map (raw-id labels).
|
|
40
|
+
const MODEL_NAMES_TTL_MS = 60 * 60 * 1000;
|
|
41
|
+
const MODEL_NAMES_RETRY_MS = 5 * 60 * 1000;
|
|
42
|
+
let cache = null;
|
|
43
|
+
/**
|
|
44
|
+
* The cached name map. One /api/model fetch per TTL window, never per
|
|
45
|
+
* request; any failure is an empty map so every label surface falls back to
|
|
46
|
+
* today's raw-id behavior.
|
|
47
|
+
*/
|
|
48
|
+
export async function modelNames(oc) {
|
|
49
|
+
if (cache && Date.now() < cache.expiresAt)
|
|
50
|
+
return cache.names;
|
|
51
|
+
let names;
|
|
52
|
+
let ttl;
|
|
53
|
+
try {
|
|
54
|
+
names = parseModelNames(await ocGetJson(oc, "/api/model"));
|
|
55
|
+
ttl = MODEL_NAMES_TTL_MS;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
names = {};
|
|
59
|
+
ttl = MODEL_NAMES_RETRY_MS;
|
|
60
|
+
}
|
|
61
|
+
cache = { names, expiresAt: Date.now() + ttl };
|
|
62
|
+
return names;
|
|
63
|
+
}
|
|
64
|
+
/** Test seam: forget the module-level cache. */
|
|
65
|
+
export function resetModelNamesCache() {
|
|
66
|
+
cache = null;
|
|
67
|
+
}
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webfueler/oc-dash",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Local dashboard for opencode2 session costs, token totals, and subagent spend",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"repository": "https://github.com/webfueler/oc-dash",
|
|
7
|
+
"homepage": "https://github.com/webfueler/oc-dash",
|
|
8
|
+
"author": "webfueler",
|
|
9
|
+
"keywords": [
|
|
10
|
+
"opencode",
|
|
11
|
+
"opencode2",
|
|
12
|
+
"cost",
|
|
13
|
+
"tokens",
|
|
14
|
+
"spend",
|
|
15
|
+
"dashboard"
|
|
16
|
+
],
|
|
6
17
|
"type": "module",
|
|
7
18
|
"engines": {
|
|
8
19
|
"node": ">=22"
|
|
@@ -13,7 +24,8 @@
|
|
|
13
24
|
"files": [
|
|
14
25
|
"bin",
|
|
15
26
|
"dist",
|
|
16
|
-
"dist-server"
|
|
27
|
+
"dist-server",
|
|
28
|
+
"docs/screenshot.png"
|
|
17
29
|
],
|
|
18
30
|
"publishConfig": {
|
|
19
31
|
"access": "public"
|