opencode-visual-cache 1.0.5 → 1.0.6
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/dist/index.js +26 -3
- package/package.json +1 -1
- package/src/index.tsx +66 -12
package/dist/index.js
CHANGED
|
@@ -57,6 +57,7 @@ const T = LANG_ZH ? {
|
|
|
57
57
|
cost: "费用:",
|
|
58
58
|
saved: "累计节省:",
|
|
59
59
|
model: "模型:",
|
|
60
|
+
provider: "提供商:",
|
|
60
61
|
rate: "单价:",
|
|
61
62
|
hitFolded: "命中",
|
|
62
63
|
inputRate: "输入",
|
|
@@ -72,6 +73,7 @@ const T = LANG_ZH ? {
|
|
|
72
73
|
cost: "Cost:",
|
|
73
74
|
saved: "Total Saved:",
|
|
74
75
|
model: "Model:",
|
|
76
|
+
provider: "Provider:",
|
|
75
77
|
rate: "Rate:",
|
|
76
78
|
hitFolded: "hit",
|
|
77
79
|
inputRate: "in",
|
|
@@ -201,10 +203,19 @@ function TokenCachePanel(props) {
|
|
|
201
203
|
let cost = 0;
|
|
202
204
|
let pid = "";
|
|
203
205
|
let mid = "";
|
|
206
|
+
// Track individual hit rates per assistant message to compute trend
|
|
207
|
+
let prevMsgHitRate = -1;
|
|
208
|
+
let lastMsgHitRate = -1;
|
|
204
209
|
for (const msg of msgs) {
|
|
205
210
|
if (msg.role !== "assistant")
|
|
206
211
|
continue;
|
|
207
212
|
const t = msg.tokens;
|
|
213
|
+
const msgInputTokens = num(t.input) + num(t.cache?.read);
|
|
214
|
+
const msgReadTokens = num(t.cache?.read);
|
|
215
|
+
if (msgInputTokens > 0) {
|
|
216
|
+
prevMsgHitRate = lastMsgHitRate;
|
|
217
|
+
lastMsgHitRate = (msgReadTokens / msgInputTokens) * 100;
|
|
218
|
+
}
|
|
208
219
|
input += num(t.input);
|
|
209
220
|
read += num(t.cache?.read);
|
|
210
221
|
write += num(t.cache?.write);
|
|
@@ -240,10 +251,18 @@ function TokenCachePanel(props) {
|
|
|
240
251
|
const hitRate = totalInput > 0 ? (read / totalInput) * 100 : 0;
|
|
241
252
|
const model = mid.split("/").pop() ?? mid;
|
|
242
253
|
const hasPricing = inputRate > 0 || cacheReadRate > 0;
|
|
254
|
+
let trend = 0;
|
|
255
|
+
const hasTrendData = prevMsgHitRate >= 0 && lastMsgHitRate >= 0;
|
|
256
|
+
if (hasTrendData) {
|
|
257
|
+
trend = lastMsgHitRate - prevMsgHitRate;
|
|
258
|
+
}
|
|
259
|
+
const providerName = pid || "";
|
|
243
260
|
return {
|
|
244
261
|
hitRate, read, write, freshInput: input, output,
|
|
245
262
|
cost, saved, model, inputRate, cacheReadRate, hasPricing,
|
|
246
|
-
hasData: read > 0 || write > 0 || cost > 0,
|
|
263
|
+
hasData: read > 0 || write > 0 || input > 0 || output > 0 || cost > 0,
|
|
264
|
+
trend, hasTrendData,
|
|
265
|
+
providerName,
|
|
247
266
|
};
|
|
248
267
|
});
|
|
249
268
|
const d = () => data();
|
|
@@ -272,8 +291,12 @@ function TokenCachePanel(props) {
|
|
|
272
291
|
return pal().error;
|
|
273
292
|
});
|
|
274
293
|
const sep = createMemo(() => "\u2500".repeat(Math.max(1, panelWidth() - GUTTER)));
|
|
294
|
+
function trendLabel(t) {
|
|
295
|
+
return (t > 0 ? "\u2191" : t < 0 ? "\u2193" : "-") + (t !== 0 ? Math.abs(t).toFixed(1) + "%" : "");
|
|
296
|
+
}
|
|
275
297
|
const barW = createMemo(() => {
|
|
276
|
-
const
|
|
298
|
+
const trendSpace = d().hasTrendData ? 1 + visualWidth(trendLabel(d().trend)) : 0;
|
|
299
|
+
const overhead = visualWidth(T.hit) + 1 + 2 + 1 + /*pct*/ 5 + trendSpace + GUTTER;
|
|
277
300
|
return Math.max(3, panelWidth() - overhead);
|
|
278
301
|
});
|
|
279
302
|
const bar = createMemo(() => progressBar(d().hitRate, barW()));
|
|
@@ -288,7 +311,7 @@ function TokenCachePanel(props) {
|
|
|
288
311
|
return (_jsx(Show, { when: d().hasData, children: _jsxs("box", { border: true, borderColor: pal().border, paddingTop: 0, paddingBottom: 0, paddingLeft: 2, paddingRight: 2, flexDirection: "column", gap: 0, onSizeChange: function () {
|
|
289
312
|
const w = Math.max(MIN_PANEL_WIDTH, this.width);
|
|
290
313
|
setPanelWidth((prev) => (prev === w ? prev : w));
|
|
291
|
-
}, children: [_jsxs("text", { onMouseUp: () => setOpen((o) => !o), children: [_jsx("span", { style: { fg: pal().muted }, children: open() ? "\u25bc " : "\u25b6 " }), _jsx("span", { style: { fg: pal().primary }, children: _jsx("b", { children: T.title }) }), _jsxs(Show, { when: !open(), children: [_jsx("span", { children: " ".repeat(Math.max(1, panelWidth() - GUTTER - 2 - visualWidth(T.title) - visualWidth(pct() + " " + T.hitFolded))) }), _jsxs("span", { style: { fg: hitColor() }, children: [pct(), " ", T.hitFolded] })] })] }), _jsxs(Show, { when: open(), children: [_jsx("text", { fg: pal().muted, children: sep() }), _jsxs("text", { children: [_jsxs("span", { style: { fg: pal().text }, children: [T.hit, " "] }), _jsxs("span", { style: { fg: hitColor() }, children: ["[", bar(), "] "] }), _jsx("span", { style: { fg: pal().text }, children: pct() })] }), _jsx("text", { fg: pal().muted, children: justify(T.read, fmt(d().read), T.tok) }), _jsx("text", { fg: pal().muted, children: justify(T.write, fmt(d().write), T.tok) }), _jsx("text", { fg: pal().muted, children: justify(T.miss, fmt(d().freshInput), T.tok) }), _jsx("text", { fg: pal().muted, children: justify(T.out, fmt(d().output), T.tok) }), _jsx("text", { fg: pal().muted, children: sep() }), _jsx("text", { fg: pal().text, children: justify(T.cost, fmtCost(d().cost)) }), _jsx(Show, { when: d().saved > 0, children: _jsxs("text", { children: [_jsx("span", { style: { fg: pal().muted }, children: T.saved }), _jsx("span", { children: " ".repeat(Math.max(1, panelWidth() - GUTTER - visualWidth(T.saved) - visualWidth("~" + fmtCost(d().saved)))) }), _jsxs("span", { style: { fg: pal().success }, children: ["~", fmtCost(d().saved)] })] }) }), _jsx("text", { fg: pal().muted, children: justify(T.model, d().model) }), _jsxs(Show, { when: d().hasPricing, children: [_jsx("text", { fg: pal().muted, children: justify(T.rate, "$" + d().inputRate.toFixed(2) + "/M " + T.inputRate) }), _jsx(Show, { when: d().cacheReadRate > 0, children: _jsx("text", { fg: pal().muted, children: justify("", "$" + d().cacheReadRate.toFixed(2) + "/M " + T.cacheRate) }) })] })] })] }) }));
|
|
314
|
+
}, children: [_jsxs("text", { onMouseUp: () => setOpen((o) => !o), children: [_jsx("span", { style: { fg: pal().muted }, children: open() ? "\u25bc " : "\u25b6 " }), _jsx("span", { style: { fg: pal().primary }, children: _jsx("b", { children: T.title }) }), _jsxs(Show, { when: !open(), children: [_jsxs(Show, { when: d().hasTrendData, children: [_jsx("span", { children: " ".repeat(Math.max(1, panelWidth() - GUTTER - 2 - visualWidth(T.title) - visualWidth(pct() + " " + T.hitFolded + " " + trendLabel(d().trend)))) }), _jsxs("span", { style: { fg: hitColor() }, children: [pct(), " ", T.hitFolded] }), _jsxs("span", { style: { fg: d().trend !== 0 ? (d().trend > 0 ? pal().success : pal().error) : pal().text }, children: [" ", trendLabel(d().trend)] })] }), _jsxs(Show, { when: !d().hasTrendData, children: [_jsx("span", { children: " ".repeat(Math.max(1, panelWidth() - GUTTER - 2 - visualWidth(T.title) - visualWidth(pct() + " " + T.hitFolded))) }), _jsxs("span", { style: { fg: hitColor() }, children: [pct(), " ", T.hitFolded] })] })] })] }), _jsxs(Show, { when: open(), children: [_jsx("text", { fg: pal().muted, children: sep() }), _jsxs("text", { children: [_jsxs("span", { style: { fg: pal().text }, children: [T.hit, " "] }), _jsxs("span", { style: { fg: hitColor() }, children: ["[", bar(), "] "] }), _jsx("span", { style: { fg: pal().text }, children: pct() }), _jsx(Show, { when: d().hasTrendData, children: _jsxs("span", { style: { fg: d().trend !== 0 ? (d().trend > 0 ? pal().success : pal().error) : pal().text }, children: [" ", trendLabel(d().trend)] }) })] }), _jsx(Show, { when: d().read > 0, children: _jsx("text", { fg: pal().muted, children: justify(T.read, fmt(d().read), T.tok) }) }), _jsx(Show, { when: d().write > 0, children: _jsx("text", { fg: pal().muted, children: justify(T.write, fmt(d().write), T.tok) }) }), _jsx("text", { fg: pal().muted, children: justify(T.miss, fmt(d().freshInput), T.tok) }), _jsx("text", { fg: pal().muted, children: justify(T.out, fmt(d().output), T.tok) }), _jsx("text", { fg: pal().muted, children: sep() }), _jsx("text", { fg: pal().text, children: justify(T.cost, fmtCost(d().cost)) }), _jsx(Show, { when: d().saved > 0, children: _jsxs("text", { children: [_jsx("span", { style: { fg: pal().muted }, children: T.saved }), _jsx("span", { children: " ".repeat(Math.max(1, panelWidth() - GUTTER - visualWidth(T.saved) - visualWidth("~" + fmtCost(d().saved)))) }), _jsxs("span", { style: { fg: pal().success }, children: ["~", fmtCost(d().saved)] })] }) }), _jsx(Show, { when: d().providerName, children: _jsx("text", { fg: pal().muted, children: justify(T.provider, d().providerName) }) }), _jsx("text", { fg: pal().muted, children: justify(T.model, d().model) }), _jsxs(Show, { when: d().hasPricing, children: [_jsx("text", { fg: pal().muted, children: justify(T.rate, "$" + d().inputRate.toFixed(2) + "/M " + T.inputRate) }), _jsx(Show, { when: d().cacheReadRate > 0, children: _jsx("text", { fg: pal().muted, children: justify("", "$" + d().cacheReadRate.toFixed(2) + "/M " + T.cacheRate) }) })] })] })] }) }));
|
|
292
315
|
}
|
|
293
316
|
// ---------------------------------------------------------------------------
|
|
294
317
|
// Plugin entry
|
package/package.json
CHANGED
package/src/index.tsx
CHANGED
|
@@ -73,6 +73,7 @@ const T = LANG_ZH ? {
|
|
|
73
73
|
cost: "费用:",
|
|
74
74
|
saved: "累计节省:",
|
|
75
75
|
model: "模型:",
|
|
76
|
+
provider: "提供商:",
|
|
76
77
|
rate: "单价:",
|
|
77
78
|
hitFolded: "命中",
|
|
78
79
|
inputRate: "输入",
|
|
@@ -88,6 +89,7 @@ const T = LANG_ZH ? {
|
|
|
88
89
|
cost: "Cost:",
|
|
89
90
|
saved: "Total Saved:",
|
|
90
91
|
model: "Model:",
|
|
92
|
+
provider: "Provider:",
|
|
91
93
|
rate: "Rate:",
|
|
92
94
|
hitFolded: "hit",
|
|
93
95
|
inputRate: "in",
|
|
@@ -229,9 +231,19 @@ function TokenCachePanel(props: {
|
|
|
229
231
|
let pid = ""
|
|
230
232
|
let mid = ""
|
|
231
233
|
|
|
234
|
+
// Track individual hit rates per assistant message to compute trend
|
|
235
|
+
let prevMsgHitRate = -1
|
|
236
|
+
let lastMsgHitRate = -1
|
|
237
|
+
|
|
232
238
|
for (const msg of msgs) {
|
|
233
239
|
if (msg.role !== "assistant") continue
|
|
234
240
|
const t = (msg as AssistantMessage).tokens
|
|
241
|
+
const msgInputTokens = num(t.input) + num(t.cache?.read)
|
|
242
|
+
const msgReadTokens = num(t.cache?.read)
|
|
243
|
+
if (msgInputTokens > 0) {
|
|
244
|
+
prevMsgHitRate = lastMsgHitRate
|
|
245
|
+
lastMsgHitRate = (msgReadTokens / msgInputTokens) * 100
|
|
246
|
+
}
|
|
235
247
|
input += num(t.input)
|
|
236
248
|
read += num(t.cache?.read)
|
|
237
249
|
write += num(t.cache?.write)
|
|
@@ -267,10 +279,20 @@ function TokenCachePanel(props: {
|
|
|
267
279
|
const model = mid.split("/").pop() ?? mid
|
|
268
280
|
const hasPricing = inputRate > 0 || cacheReadRate > 0
|
|
269
281
|
|
|
282
|
+
let trend = 0
|
|
283
|
+
const hasTrendData = prevMsgHitRate >= 0 && lastMsgHitRate >= 0
|
|
284
|
+
if (hasTrendData) {
|
|
285
|
+
trend = lastMsgHitRate - prevMsgHitRate
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const providerName = pid || ""
|
|
289
|
+
|
|
270
290
|
return {
|
|
271
291
|
hitRate, read, write, freshInput: input, output,
|
|
272
292
|
cost, saved, model, inputRate, cacheReadRate, hasPricing,
|
|
273
|
-
hasData: read > 0 || write > 0 || cost > 0,
|
|
293
|
+
hasData: read > 0 || write > 0 || input > 0 || output > 0 || cost > 0,
|
|
294
|
+
trend, hasTrendData,
|
|
295
|
+
providerName,
|
|
274
296
|
}
|
|
275
297
|
})
|
|
276
298
|
|
|
@@ -301,8 +323,13 @@ function TokenCachePanel(props: {
|
|
|
301
323
|
})
|
|
302
324
|
|
|
303
325
|
const sep = createMemo(() => "\u2500".repeat(Math.max(1, panelWidth() - GUTTER)))
|
|
326
|
+
function trendLabel(t: number): string {
|
|
327
|
+
return (t > 0 ? "\u2191" : t < 0 ? "\u2193" : "-") + (t !== 0 ? Math.abs(t).toFixed(1) + "%" : "")
|
|
328
|
+
}
|
|
329
|
+
|
|
304
330
|
const barW = createMemo(() => {
|
|
305
|
-
const
|
|
331
|
+
const trendSpace = d().hasTrendData ? 1 + visualWidth(trendLabel(d().trend)) : 0
|
|
332
|
+
const overhead = visualWidth(T.hit) + 1 + 2 + 1 + /*pct*/5 + trendSpace + GUTTER
|
|
306
333
|
return Math.max(3, panelWidth() - overhead)
|
|
307
334
|
})
|
|
308
335
|
const bar = createMemo(() => progressBar(d().hitRate, barW()))
|
|
@@ -337,10 +364,21 @@ function TokenCachePanel(props: {
|
|
|
337
364
|
<span style={{ fg: pal().muted }}>{open() ? "\u25bc " : "\u25b6 "}</span>
|
|
338
365
|
<span style={{ fg: pal().primary }}><b>{T.title}</b></span>
|
|
339
366
|
<Show when={!open()}>
|
|
340
|
-
<
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
367
|
+
<Show when={d().hasTrendData}>
|
|
368
|
+
<span>
|
|
369
|
+
{" ".repeat(Math.max(1, panelWidth() - GUTTER - 2 - visualWidth(T.title) - visualWidth(pct() + " " + T.hitFolded + " " + trendLabel(d().trend))))}
|
|
370
|
+
</span>
|
|
371
|
+
<span style={{ fg: hitColor() }}>{pct()} {T.hitFolded}</span>
|
|
372
|
+
<span style={{ fg: d().trend !== 0 ? (d().trend > 0 ? pal().success : pal().error) : pal().text }}>
|
|
373
|
+
{" "}{trendLabel(d().trend)}
|
|
374
|
+
</span>
|
|
375
|
+
</Show>
|
|
376
|
+
<Show when={!d().hasTrendData}>
|
|
377
|
+
<span>
|
|
378
|
+
{" ".repeat(Math.max(1, panelWidth() - GUTTER - 2 - visualWidth(T.title) - visualWidth(pct() + " " + T.hitFolded)))}
|
|
379
|
+
</span>
|
|
380
|
+
<span style={{ fg: hitColor() }}>{pct()} {T.hitFolded}</span>
|
|
381
|
+
</Show>
|
|
344
382
|
</Show>
|
|
345
383
|
</text>
|
|
346
384
|
|
|
@@ -352,15 +390,24 @@ function TokenCachePanel(props: {
|
|
|
352
390
|
<span style={{ fg: pal().text }}>{T.hit} </span>
|
|
353
391
|
<span style={{ fg: hitColor() }}>[{bar()}] </span>
|
|
354
392
|
<span style={{ fg: pal().text }}>{pct()}</span>
|
|
393
|
+
<Show when={d().hasTrendData}>
|
|
394
|
+
<span style={{ fg: d().trend !== 0 ? (d().trend > 0 ? pal().success : pal().error) : pal().text }}>
|
|
395
|
+
{" "}{trendLabel(d().trend)}
|
|
396
|
+
</span>
|
|
397
|
+
</Show>
|
|
355
398
|
</text>
|
|
356
399
|
|
|
357
400
|
{/* token breakdown */}
|
|
358
|
-
<
|
|
359
|
-
{
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
401
|
+
<Show when={d().read > 0}>
|
|
402
|
+
<text fg={pal().muted}>
|
|
403
|
+
{justify(T.read, fmt(d().read), T.tok)}
|
|
404
|
+
</text>
|
|
405
|
+
</Show>
|
|
406
|
+
<Show when={d().write > 0}>
|
|
407
|
+
<text fg={pal().muted}>
|
|
408
|
+
{justify(T.write, fmt(d().write), T.tok)}
|
|
409
|
+
</text>
|
|
410
|
+
</Show>
|
|
364
411
|
<text fg={pal().muted}>
|
|
365
412
|
{justify(T.miss, fmt(d().freshInput), T.tok)}
|
|
366
413
|
</text>
|
|
@@ -384,6 +431,13 @@ function TokenCachePanel(props: {
|
|
|
384
431
|
</text>
|
|
385
432
|
</Show>
|
|
386
433
|
|
|
434
|
+
{/* provider */}
|
|
435
|
+
<Show when={d().providerName}>
|
|
436
|
+
<text fg={pal().muted}>
|
|
437
|
+
{justify(T.provider, d().providerName)}
|
|
438
|
+
</text>
|
|
439
|
+
</Show>
|
|
440
|
+
|
|
387
441
|
{/* model */}
|
|
388
442
|
<text fg={pal().muted}>
|
|
389
443
|
{justify(T.model, d().model)}
|