Merge pull request 'feat: statusbar 在送出第一個提示詞前就顯示額度、ctx 與系統資源' (#25) from feat/statusbar-prefetch into main

Reviewed-on: https://gitea.jsc.idv.tw/jiantw83/jsc-marketplace/pulls/25
This commit was merged in pull request #25.
This commit is contained in:
2026-10-13 14:19:05 +00:00
2 changed files with 30 additions and 6 deletions
@@ -1,6 +1,6 @@
{ {
"name": "jsc-statusbar", "name": "jsc-statusbar",
"version": "0.0.3", "version": "0.0.4",
"description": "輸入框上方的單行狀態列:模型、目錄、分支、5h/7d 額度與重置、context、CPU/記憶體/磁碟", "description": "輸入框上方的單行狀態列:模型、目錄、分支、5h/7d 額度與重置、context、CPU/記憶體/磁碟",
"license": "MIT", "license": "MIT",
"types": "./types/index.d.ts", "types": "./types/index.d.ts",
+29 -5
View File
@@ -1,7 +1,7 @@
import { atom, read, update } from 'claude-code' import { atom, read, update } from 'claude-code'
import type { EngineInterface, Register, SessionContextUsage, SessionRateLimit } from 'claude-code' import type { EngineInterface, Register, SessionContextUsage, SessionRateLimit } from 'claude-code'
import type { Mode, Sys, Usage } from '../types' import type { Mode, Sys, Usage, Window } from '../types'
import { fit, cpuPercent, parseCpu, parseDf, parseGit, parseMem, parseOffset } from './format' import { fit, cpuPercent, parseCpu, parseDf, parseGit, parseMem, parseOffset } from './format'
import type { CpuSample } from './format' import type { CpuSample } from './format'
@@ -19,6 +19,7 @@ const ALERT_AT = 90
const GIT_TOOLS = new Set(['Bash', 'Edit', 'Write', 'NotebookEdit']) const GIT_TOOLS = new Set(['Bash', 'Edit', 'Write', 'NotebookEdit'])
const LABEL: Record<string, string> = { five_hour: '5 小時', seven_day: '7 天' } const LABEL: Record<string, string> = { five_hour: '5 小時', seven_day: '7 天' }
// 引擎要等第一次 API 回應才有額度與 ctx;先前的額度存在 $.store,開新對話時先顯示,ctx 先當作 0
function toUsage(context: SessionContextUsage, rateLimits: SessionRateLimit[]): Usage { function toUsage(context: SessionContextUsage, rateLimits: SessionRateLimit[]): Usage {
const win = (kind: string) => { const win = (kind: string) => {
const r = rateLimits.find(x => x.kind === kind) const r = rateLimits.find(x => x.kind === kind)
@@ -27,9 +28,25 @@ function toUsage(context: SessionContextUsage, rateLimits: SessionRateLimit[]):
return { return {
fiveHour: win('five_hour'), fiveHour: win('five_hour'),
sevenDay: win('seven_day'), sevenDay: win('seven_day'),
ctxTokens: context.tokens, ctxTokens: context.tokens ?? 0,
ctxWindow: context.window, ctxWindow: context.window,
ctxPercent: context.percent, ctxPercent: context.percent ?? 0,
}
}
// 已過重置時間的視窗用量已歸零,不再沿用舊值
const live = (w: Window | undefined, now: number) =>
w && (!w.resetsAt || Date.parse(w.resetsAt) > now) ? w : undefined
async function restoreRates($: EngineInterface, usage: Usage): Promise<Usage> {
const saved = (await $.store.get('rates')) as Pick<Usage, 'fiveHour' | 'sevenDay'> | undefined
if (!saved) return usage
const now = await $.clock.now()
return {
...usage,
fiveHour: usage.fiveHour ?? live(saved.fiveHour, now),
sevenDay: usage.sevenDay ?? live(saved.sevenDay, now),
} }
} }
@@ -104,9 +121,11 @@ export const register: Register = on => {
tzOffsetMin = parseOffset(r.stdout) tzOffsetMin = parseOffset(r.stdout)
} catch {} } catch {}
const u = await $.session.usage() const u = await $.session.usage()
await update($, usageAtom, () => toUsage(u.context, u.rateLimits)) const usage = await restoreRates($, toUsage(u.context, u.rateLimits))
await update($, usageAtom, () => usage)
void refreshGit($) void refreshGit($)
void refreshSys($) // CPU 要兩次取樣才算得出來,開場先隔 0.5 秒補一次,免得等到 3 秒後才出現
void refreshSys($).then(() => $.clock.after(500, () => void refreshSys($)))
$.clock.every(SYS_MS, () => void refreshSys($)) $.clock.every(SYS_MS, () => void refreshSys($))
$.clock.every(GIT_MS, () => void refreshGit($)) $.clock.every(GIT_MS, () => void refreshGit($))
@@ -115,6 +134,11 @@ export const register: Register = on => {
on('session.measure', async ($, e, next) => { on('session.measure', async ($, e, next) => {
await update($, usageAtom, () => toUsage(e.context, e.rateLimits)) await update($, usageAtom, () => toUsage(e.context, e.rateLimits))
if (e.rateLimits.length) {
const w = (kind: string) => e.rateLimits.find(x => x.kind === kind)
const pick = (r: SessionRateLimit | undefined) => r && { percent: r.percentUsed, resetsAt: r.resetsAt }
await $.store.set('rates', { fiveHour: pick(w('five_hour')), sevenDay: pick(w('seven_day')) })
}
if (e.changed.includes('rateLimits')) await alert($, e.rateLimits) if (e.changed.includes('rateLimits')) await alert($, e.rateLimits)
return next(e) return next(e)