@kenz1117/dsh-ui-usage-billing 1.2.7 → 1.2.8
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/lib/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
-
import { mkdir, readFile, stat } from "node:fs/promises";
|
|
2
|
+
import { lstat, mkdir, readFile, rm, stat } from "node:fs/promises";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
5
|
import { SessionLogOffset } from "@deepseek-ai/dsh-session/types";
|
|
@@ -305,7 +305,7 @@ function tierAt(timeMs) {
|
|
|
305
305
|
}
|
|
306
306
|
/** 时刻是否落在北京时间周末(周六/周日)。 */
|
|
307
307
|
function isBeijingWeekend(timeMs) {
|
|
308
|
-
const day = new Date(timeMs +
|
|
308
|
+
const day = new Date(timeMs + 8 * 36e5).getUTCDay();
|
|
309
309
|
return day === 0 || day === 6;
|
|
310
310
|
}
|
|
311
311
|
/**
|
|
@@ -3062,8 +3062,8 @@ function createUsageAggregator(persistence, options = {}) {
|
|
|
3062
3062
|
};
|
|
3063
3063
|
const toModelDayRecord = (map) => Object.fromEntries([...map].map(([day, models]) => [day, Object.fromEntries(models)]));
|
|
3064
3064
|
const toModelDaySiteRecord = (map) => Object.fromEntries([...map].map(([day, models]) => [day, Object.fromEntries([...models].map(([model, sites]) => [model, Object.fromEntries(sites)]))]));
|
|
3065
|
-
const modelKeys =
|
|
3066
|
-
const hourKeys =
|
|
3065
|
+
const modelKeys = new Set([...perfModel.keys(), ...perfModelDigest.keys()]);
|
|
3066
|
+
const hourKeys = new Set([...perfHourModel.keys(), ...perfHourDigest.keys()]);
|
|
3067
3067
|
const perf = modelKeys.size === 0 && hourKeys.size === 0 ? void 0 : {
|
|
3068
3068
|
byModel: Object.fromEntries([...modelKeys].map((model) => {
|
|
3069
3069
|
const live = perfModel.get(model);
|
|
@@ -3074,7 +3074,7 @@ function createUsageAggregator(persistence, options = {}) {
|
|
|
3074
3074
|
byHourModel: Object.fromEntries([...hourKeys].sort().map((hour) => {
|
|
3075
3075
|
const liveModels = perfHourModel.get(hour);
|
|
3076
3076
|
const digestModels = perfHourDigest.get(hour);
|
|
3077
|
-
const modelKeysInHour =
|
|
3077
|
+
const modelKeysInHour = new Set([...liveModels?.keys() ?? [], ...digestModels?.keys() ?? []]);
|
|
3078
3078
|
return [hour, Object.fromEntries([...modelKeysInHour].map((model) => {
|
|
3079
3079
|
const live = liveModels?.get(model);
|
|
3080
3080
|
const restored = digestModels?.get(model);
|
|
@@ -3277,11 +3277,7 @@ function tc3Authorization(secretId, secretKey, payload, timestamp) {
|
|
|
3277
3277
|
const canonicalRequest = `POST\n/\n\n${`content-type:application/json; charset=utf-8\nhost:${TOKENHUB_HOST}\n`}\ncontent-type;host\n${createHash("sha256").update(payload).digest("hex")}`;
|
|
3278
3278
|
const hashedCanonical = createHash("sha256").update(canonicalRequest).digest("hex");
|
|
3279
3279
|
const stringToSign = `TC3-HMAC-SHA256\n${String(timestamp)}\n${date}/${TOKENHUB_SERVICE}/tc3_request\n${hashedCanonical}`;
|
|
3280
|
-
|
|
3281
|
-
const kService = createHmac("sha256", kDate).update(TOKENHUB_SERVICE).digest();
|
|
3282
|
-
const kSigning = createHmac("sha256", kService).update("tc3_request").digest();
|
|
3283
|
-
const signature = createHmac("sha256", kSigning).update(stringToSign).digest("hex");
|
|
3284
|
-
return `TC3-HMAC-SHA256 Credential=${secretId}/${date}/${TOKENHUB_SERVICE}/tc3_request, SignedHeaders=content-type;host, Signature=${signature}`;
|
|
3280
|
+
return `TC3-HMAC-SHA256 Credential=${secretId}/${date}/${TOKENHUB_SERVICE}/tc3_request, SignedHeaders=content-type;host, Signature=${createHmac("sha256", createHmac("sha256", createHmac("sha256", createHmac("sha256", date).update(secretKey).digest()).update(TOKENHUB_SERVICE).digest()).update("tc3_request").digest()).update(stringToSign).digest("hex")}`;
|
|
3285
3281
|
}
|
|
3286
3282
|
/**
|
|
3287
3283
|
* 调用一次 TokenHub 管控面接口:TC3 签名 + 超时保护,返回响应 JSON 的 `Response`。
|
|
@@ -4045,7 +4041,7 @@ const declaredGate = createCooldownGate({
|
|
|
4045
4041
|
cooldownMs: 6e4
|
|
4046
4042
|
});
|
|
4047
4043
|
/** 会触及原型链的路径段:一律拒绝(读结果不是文档自带字段)。 */
|
|
4048
|
-
const FORBIDDEN_SEGMENTS =
|
|
4044
|
+
const FORBIDDEN_SEGMENTS = new Set([
|
|
4049
4045
|
"__proto__",
|
|
4050
4046
|
"constructor",
|
|
4051
4047
|
"prototype"
|
|
@@ -4595,7 +4591,7 @@ function asPrice(value) {
|
|
|
4595
4591
|
*/
|
|
4596
4592
|
function buildExtraModels(data) {
|
|
4597
4593
|
if (data === null || typeof data !== "object") return [];
|
|
4598
|
-
const catalogKeys =
|
|
4594
|
+
const catalogKeys = new Set([...MODEL_CATALOG.map((entry) => entry.key.toLowerCase()), ...Object.keys(MODEL_KEY_ALIASES).map((key) => MODEL_KEY_ALIASES[key]?.toLowerCase() ?? key.toLowerCase())]);
|
|
4599
4595
|
const extras = [];
|
|
4600
4596
|
for (const [providerId, providerDoc] of Object.entries(data)) {
|
|
4601
4597
|
if (providerDoc === null || typeof providerDoc !== "object") continue;
|
|
@@ -5315,7 +5311,7 @@ const relayGate = createCooldownGate({
|
|
|
5315
5311
|
/** Abort a relay fetch when the upstream hangs beyond this budget. */
|
|
5316
5312
|
const FETCH_TIMEOUT_MS = 8e3;
|
|
5317
5313
|
/** 指纹识别缓存 TTL(毫秒):识别结果低频变化,5 分钟内同 origin 不再重复探测。 */
|
|
5318
|
-
const FINGERPRINT_TTL_MS =
|
|
5314
|
+
const FINGERPRINT_TTL_MS = 300 * 1e3;
|
|
5319
5315
|
/** 每 origin 的识别结果缓存:`kind` 是识别出的中转站程序,`at` 是探测时刻。 */
|
|
5320
5316
|
const fingerprintCache = /* @__PURE__ */ new Map();
|
|
5321
5317
|
/** Number, or null when the value is not a finite number (nor numeric string). */
|
|
@@ -5451,7 +5447,7 @@ function originOf(baseURL) {
|
|
|
5451
5447
|
* 子路径只会得到 404/非中转站格式,因而面板应排除它们,避免误判为
|
|
5452
5448
|
* 「未识别」。中转站面板只列真正的第三方中转程序。
|
|
5453
5449
|
*/
|
|
5454
|
-
const OFFICIAL_HOSTS =
|
|
5450
|
+
const OFFICIAL_HOSTS = new Set([
|
|
5455
5451
|
"api.deepseek.com",
|
|
5456
5452
|
"api.openai.com",
|
|
5457
5453
|
"open.bigmodel.cn",
|
|
@@ -5704,10 +5700,10 @@ const usageBillingSettingsNs = validateSettingsNamespace(BILLING_SETTINGS_NAMESP
|
|
|
5704
5700
|
/** 该命名空间的 wire schema:`enableUsageStatsTool` 布尔,默认关闭(issue 诉求)。 */
|
|
5705
5701
|
const UsageBillingSettingsSchema = z.object({ [ENABLE_USAGE_STATS_TOOL_FIELD]: z.boolean().default(false) });
|
|
5706
5702
|
/** 实时定价的后台刷新间隔(毫秒):汇率/模型价低频变化,6 小时一次足够。 */
|
|
5707
|
-
const PRICING_REFRESH_INTERVAL_MS =
|
|
5703
|
+
const PRICING_REFRESH_INTERVAL_MS = 360 * 60 * 1e3;
|
|
5708
5704
|
/** 订阅套餐额度缓存时长(毫秒):上游配额 API 低频变化,5 分钟足够。 */
|
|
5709
|
-
const SUBSCRIPTION_CACHE_MS =
|
|
5710
|
-
const BALANCE_CACHE_MS =
|
|
5705
|
+
const SUBSCRIPTION_CACHE_MS = 300 * 1e3;
|
|
5706
|
+
const BALANCE_CACHE_MS = 300 * 1e3;
|
|
5711
5707
|
/** DeepSeek 余额查询的默认凭据引用(与 llm-deepseek 的默认引用一致)。 */
|
|
5712
5708
|
const DEFAULT_BALANCE_API_KEY_ENV = "DEEPSEEK_API_KEY";
|
|
5713
5709
|
/**
|
|
@@ -5724,7 +5720,7 @@ const SNAPSHOT_INTERVAL_MS = 3e5;
|
|
|
5724
5720
|
* 轻量用户暖缓存下的聚合是毫秒级,预算几乎不会触发。 */
|
|
5725
5721
|
const RESPONSE_BUDGET_MS = 1500;
|
|
5726
5722
|
/** 鉴权失败告警冷却(毫秒):同一 provider 在窗口内只提示一次,避免 30 秒轮询刷屏。 */
|
|
5727
|
-
const AUTH_WARN_COOLDOWN_MS =
|
|
5723
|
+
const AUTH_WARN_COOLDOWN_MS = 1800 * 1e3;
|
|
5728
5724
|
/**
|
|
5729
5725
|
* 鉴权失败分类告警(P1-5):余额 / 订阅查询返回 unauthorized 时,按
|
|
5730
5726
|
* `source:provider` 去重并冷却告警,提示检查 llm-pi-ai 里该 provider 的 apiKeyEnv。
|
|
@@ -5738,6 +5734,32 @@ function warnAuthOnce(source, provider, displayName) {
|
|
|
5738
5734
|
authWarnedAt.set(key, now);
|
|
5739
5735
|
console.warn(`[usage-billing] ${displayName}(${provider})鉴权失败:请检查 llm-pi-ai 设置中该 provider 的凭据环境变量是否正确/有效。`);
|
|
5740
5736
|
}
|
|
5737
|
+
/** 锁等待上限:账本提交是毫秒级 read-render-commit,2s 已数倍于正常持锁时长。 */
|
|
5738
|
+
const LEDGER_LOCK_WAIT_MS = 2e3;
|
|
5739
|
+
/**
|
|
5740
|
+
* 锁年龄超过该值判为崩溃残留的孤儿锁。正常持锁是毫秒级文件提交,没有活跃
|
|
5741
|
+
* writer 会合法持有 60s;库层刻意不做年龄判定(它无法假设调用方的操作时长),
|
|
5742
|
+
* 本调用方知道自己操作的上界,可以给出有依据的清理阈值。
|
|
5743
|
+
*/
|
|
5744
|
+
const STALE_LEDGER_LOCK_MS = 6e4;
|
|
5745
|
+
/**
|
|
5746
|
+
* 锁等待超时后的孤儿锁自愈(issue #48):仅当锁文件年龄超过
|
|
5747
|
+
* STALE_LEDGER_LOCK_MS 才清理,避免误删慢 writer 的活跃锁。
|
|
5748
|
+
* @param ledgerPath - 账本文件路径(锁为其 `.lock` 兄弟文件)。
|
|
5749
|
+
* @returns 是否清理了孤儿锁;false = 锁已消失或太新鲜,调用方上抛原错误。
|
|
5750
|
+
*/
|
|
5751
|
+
async function reclaimStaleLedgerLock(ledgerPath) {
|
|
5752
|
+
let ageMs;
|
|
5753
|
+
try {
|
|
5754
|
+
ageMs = Date.now() - (await lstat(`${ledgerPath}.lock`)).mtimeMs;
|
|
5755
|
+
} catch {
|
|
5756
|
+
return false;
|
|
5757
|
+
}
|
|
5758
|
+
if (ageMs < STALE_LEDGER_LOCK_MS) return false;
|
|
5759
|
+
console.warn(`[usage-billing] 账本锁已存在 ${Math.round(ageMs / 1e3)}s(崩溃残留),清理后重试:${ledgerPath}.lock`);
|
|
5760
|
+
await rm(`${ledgerPath}.lock`, { force: true });
|
|
5761
|
+
return true;
|
|
5762
|
+
}
|
|
5741
5763
|
/**
|
|
5742
5764
|
* Create the atomic file-backed durable-ledger store. The previous complete file
|
|
5743
5765
|
* is retained as `.bak`; a malformed/missing main file falls back to that backup.
|
|
@@ -5758,7 +5780,7 @@ function createFileUsageLedgerStore(ledgerPath) {
|
|
|
5758
5780
|
recursive: true,
|
|
5759
5781
|
mode: 448
|
|
5760
5782
|
});
|
|
5761
|
-
|
|
5783
|
+
const commit = async () => {
|
|
5762
5784
|
try {
|
|
5763
5785
|
const existing = await readFile(ledgerPath, "utf8");
|
|
5764
5786
|
JSON.parse(existing);
|
|
@@ -5771,7 +5793,14 @@ function createFileUsageLedgerStore(ledgerPath) {
|
|
|
5771
5793
|
mode: 384,
|
|
5772
5794
|
dirMode: 448
|
|
5773
5795
|
});
|
|
5774
|
-
}
|
|
5796
|
+
};
|
|
5797
|
+
try {
|
|
5798
|
+
await withFileLock(ledgerPath, commit, { waitMs: LEDGER_LOCK_WAIT_MS });
|
|
5799
|
+
} catch (error) {
|
|
5800
|
+
if (!(error instanceof Error) || !error.message.includes("timed out waiting for the writer lock")) throw error;
|
|
5801
|
+
if (!await reclaimStaleLedgerLock(ledgerPath)) throw error;
|
|
5802
|
+
await withFileLock(ledgerPath, commit, { waitMs: LEDGER_LOCK_WAIT_MS });
|
|
5803
|
+
}
|
|
5775
5804
|
}
|
|
5776
5805
|
};
|
|
5777
5806
|
}
|
|
@@ -6012,8 +6041,7 @@ function apply(ctx, config = {}) {
|
|
|
6012
6041
|
})();
|
|
6013
6042
|
const persistReconcileRef = () => {
|
|
6014
6043
|
if (reconcileRef === null) return;
|
|
6015
|
-
|
|
6016
|
-
writeFileAtomic(reconcilePath, payload, {
|
|
6044
|
+
writeFileAtomic(reconcilePath, JSON.stringify({ ref: reconcileRef }), {
|
|
6017
6045
|
mode: 384,
|
|
6018
6046
|
dirMode: 448
|
|
6019
6047
|
}).catch(() => {});
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
import type { UsageBillingKey } from './locales.ts';
|
|
17
17
|
import type { TrendSeriesModel } from './TrendChart.tsx';
|
|
18
18
|
import type { UsageStats } from './UsageBilling.tsx';
|
|
19
|
+
/** 短数字刻度:`1.2M` / `3.4K`。导出供概览 KPI 峰值日卡复用。 */
|
|
20
|
+
export declare function shortNumber(v: number): string;
|
|
19
21
|
/** 每日 token 堆叠图元(按结构视角)。 */
|
|
20
22
|
interface DailyBucket {
|
|
21
23
|
date: string;
|
|
@@ -62,6 +62,13 @@ export declare const PROVIDER_ALIASES: Readonly<Record<string, readonly string[]
|
|
|
62
62
|
export declare function providerFromModelKey(modelKey: string): string | undefined;
|
|
63
63
|
/** 仅供测试:暴露厂商映射表(subscriptionVendorOf 仍是唯一消费入口)。 */
|
|
64
64
|
export declare const SUBSCRIPTION_VENDORS_FOR_TEST: Readonly<Record<string, string>>;
|
|
65
|
+
/**
|
|
66
|
+
* 余额 provider → 官方充值页 URL。匹配顺序:归一化名精确/前缀命中 →
|
|
67
|
+
* 经 PROVIDER_ALIASES 反查(余额 provider 名多为中文显示名,归一化仍是中文,
|
|
68
|
+
* 需先命中 display 名再用其英文别名重试)。未收录返回 undefined。
|
|
69
|
+
* 导出供测试:纯函数,不依赖组件。
|
|
70
|
+
*/
|
|
71
|
+
export declare function rechargeUrlOf(provider: string): string | undefined;
|
|
65
72
|
/**
|
|
66
73
|
* 本月预计总花费:按本月已有记录的平均日消耗 × 本月天数外推;无本月记录时
|
|
67
74
|
* 回退为最近 7 天日均 × 本月天数;无任何记录时返回 0(调用方不展示)。
|
|
@@ -125,6 +132,38 @@ export declare function lastSevenDays(byDay: Record<string, {
|
|
|
125
132
|
* @returns 本周一到今天的合计;`today` 本身不在表里(无调用)时为 0。
|
|
126
133
|
*/
|
|
127
134
|
export declare function sinceMondayOf<T>(byDay: Record<string, T>, today: string, pick?: (row: T) => number): number;
|
|
135
|
+
/** 平均成本的统计范围(issue #47);`all` = 全量累计,不进逐日求和。 */
|
|
136
|
+
export type AvgCostRange = 'today' | '7d' | 'week' | 'month' | 'all';
|
|
137
|
+
/** 全局 KPI 按日聚合行的最小字段(byDay 行 = ModelUsage,结构兼容)。 */
|
|
138
|
+
interface KpiDayRow {
|
|
139
|
+
cost: number;
|
|
140
|
+
calls: number;
|
|
141
|
+
input: number;
|
|
142
|
+
output: number;
|
|
143
|
+
cacheHit: number;
|
|
144
|
+
cacheMiss: number;
|
|
145
|
+
reasoning?: number;
|
|
146
|
+
}
|
|
147
|
+
/** 全局 KPI 聚合结果:KPI 七卡按所选范围重算所需的全部字段。 */
|
|
148
|
+
export interface KpiAgg {
|
|
149
|
+
cost: number;
|
|
150
|
+
calls: number;
|
|
151
|
+
input: number;
|
|
152
|
+
output: number;
|
|
153
|
+
cacheHit: number;
|
|
154
|
+
cacheMiss: number;
|
|
155
|
+
reasoning: number;
|
|
156
|
+
/** 范围内总处理 Token(缓存读+缓存写+输出)最大的日期戳;窗口无数据时 undefined。 */
|
|
157
|
+
peakDay: string | undefined;
|
|
158
|
+
/** 峰值日的总处理 Token 量;窗口无数据时 0。 */
|
|
159
|
+
peakTokens: number;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* 按范围聚合 byDay(issue #47):今日 / 近 7 天(含今天)/ 本周(周一起)/
|
|
163
|
+
* 本月。日期戳字典序即时间序(与 dailyBurnRate 同口径);累计口径由调用方
|
|
164
|
+
* 直接取 total(含搜索估值兜底),不走此函数。
|
|
165
|
+
*/
|
|
166
|
+
export declare function sumByDayRange(byDay: Record<string, KpiDayRow>, today: string, range: Exclude<AvgCostRange, 'all'>): KpiAgg;
|
|
128
167
|
/** 会话明细行(与服务端 SessionUsageRow 同形;旧快照可能缺失整个 bySession)。 */
|
|
129
168
|
interface SessionBillingRow {
|
|
130
169
|
id: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Locale dictionaries for the usage billing surface. */
|
|
2
|
-
export type UsageBillingKey = 'title' | 'cost' | 'todayCost' | 'monthCost' | 'yearCost' | 'monthProjected' | 'liveTurn' | 'liveSession' | 'calls' | 'cacheHitRate' | 'tokens' | 'inputTokens' | 'outputTokens' | 'avgCost' | 'trend' | 'trend7d' | 'trend30d' | 'trendMetric' | 'trendMetricCost' | 'trendMetricTokens' | 'trendEmpty' | 'budget' | 'budgetAmount' | 'budgetSummary' | 'sessions' | 'sessionTitle' | 'lastActive' | 'sessionOverflow' | 'budgetTierBody' | 'models' | 'providerBilling' | 'channelUnknown' | 'actual' | 'pricing' | 'input' | 'output' | 'cacheHit' | 'peak' | 'offPeak' | 'flat' | 'band' | 'close' | 'footer' | 'footerCredit' | 'lastUpdated' | 'noData' | 'todayRate' | 'rateLive' | 'rateBuiltin' | 'promoBadge' | 'promoUntil' | 'promoOpenEnded' | 'pricingTip' | 'pricingUnit' | 'pricingNotes' | 'ubPeak' | 'ubOff' | 'ubStd' | 'peakBand' | 'pricingSource' | 'noteCache' | 'noteBand' | 'noteSource' | 'balance' | 'balanceUnconfigured' | 'balanceUnauthorized' | 'balanceUnreachable' | 'uncatalogued' | 'estimatedPricing' | 'balanceDays' | 'balanceLowBody' | 'reconcileDrift' | 'reconcileDismiss' | 'subscriptionNotConfigured' | 'subscriptionUnauthorized' | 'subscriptionUnavailable' | 'subscriptionInvalid' | 'subscriptionRateLimited' | 'subscriptionSession' | 'subscriptionWeekly' | 'subscriptionMonthly' | 'subscriptionBilling' | 'subscriptionRemaining' | 'subscriptionExhausted' | 'subscriptionReset' | 'subscriptionNoApi' | 'floatWindow' | 'floatModeCombined' | 'floatModeSubscription' | 'floatMode' | 'floatTargets' | 'floatWindowHint' | 'floatNoTargets' | 'floatNoTargetsHint' | 'cardDisplay' | 'cardDisplayHint' | 'cardMetric' | 'cardMetricMoney' | 'cardMetricTokens' | 'triggerMonthTokens' | 'subscriptionsStale' | 'staleLedgerNotice' | 'tokenCacheWrite' | 'toolRank' | 'toolName' | 'userPrices' | 'userPricesHint' | 'userPriceSave' | 'userPriceModel' | 'userPriceSource' | 'userPriceSourceHint' | 'userPriceOffPeak' | 'userPriceNormal' | 'userPriceCurrency' | 'userPriceAdd' | 'userPriceRemoveSelected' | 'userPriceSelect' | 'sessionStaleBadge' | 'sessionUntitled' | 'heatmapLess' | 'heatmapMore' | 'currency' | 'currencyCny' | 'currencyUsd' | 'heatmap' | 'rounds' | 'roundsHint' | 'anomaly' | 'model' | 'thModel' | 'thInputMiss' | 'thInputHit' | 'costAbbr' | 'tabOverview' | 'tabTrends' | 'tabProviders' | 'tabPricing' | 'tabSettings' | 'budgetHint' | 'peakAlertHint' | 'peakAlertDescPeak' | 'peakAlertDescOff' | 'export' | 'exportCsvDay' | 'exportCsvSession' | 'exportJson' | 'peakShare' | 'peakSharePerCall' | 'offPeakSavings' | 'perfMax' | 'weekCost' | 'roleCost' | 'roleUser' | 'roleAssistant' | 'roleTool' | 'roleHint' | 'tierPeak' | 'tierOff' | 'tierToPeak' | 'tierToOff' | 'tierAlertEnterPeak' | 'tierAlertEnterOff' | 'peakAlertTitlePeak' | 'peakAlertTitleOff' | 'peakAlert' | 'peakAlertLeadMin' | 'peakAlertPos' | 'peakAlertMode' | 'peakAlertPosCorner' | 'peakAlertPosCenter' | 'peakAlertModePeak' | 'peakAlertModeOff' | 'peakAlertModeBoth' | 'peakAlertWebNotify' | 'peakAlertPreview' | 'planTypeCode' | 'planTypeToken' | 'subscriptionFeePerMonth' | 'triggerToday' | 'triggerMonth' | '
|
|
2
|
+
export type UsageBillingKey = 'title' | 'cost' | 'todayCost' | 'monthCost' | 'yearCost' | 'monthProjected' | 'liveTurn' | 'liveSession' | 'calls' | 'cacheHitRate' | 'tokens' | 'inputTokens' | 'outputTokens' | 'avgCost' | 'kpiRange' | 'avgRangeToday' | 'avgRange7d' | 'avgRangeWeek' | 'avgRangeMonth' | 'avgRangeAll' | 'trend' | 'trend7d' | 'trend30d' | 'trendMetric' | 'trendMetricCost' | 'trendMetricTokens' | 'trendEmpty' | 'budget' | 'budgetRemain' | 'budgetAmount' | 'budgetSummary' | 'sessions' | 'sessionTitle' | 'lastActive' | 'sessionOverflow' | 'budgetTierBody' | 'models' | 'providerBilling' | 'channelUnknown' | 'actual' | 'pricing' | 'input' | 'output' | 'cacheHit' | 'peak' | 'offPeak' | 'flat' | 'band' | 'close' | 'footer' | 'footerCredit' | 'lastUpdated' | 'noData' | 'todayRate' | 'rateLive' | 'rateBuiltin' | 'promoBadge' | 'promoUntil' | 'promoOpenEnded' | 'pricingTip' | 'pricingUnit' | 'pricingNotes' | 'ubPeak' | 'ubOff' | 'ubStd' | 'peakBand' | 'pricingSource' | 'noteCache' | 'noteBand' | 'noteSource' | 'balance' | 'recharge' | 'rechargeHint' | 'balanceUnconfigured' | 'balanceUnauthorized' | 'balanceUnreachable' | 'uncatalogued' | 'estimatedPricing' | 'balanceDays' | 'balanceLowBody' | 'reconcileDrift' | 'reconcileDismiss' | 'subscriptionNotConfigured' | 'subscriptionUnauthorized' | 'subscriptionUnavailable' | 'subscriptionInvalid' | 'subscriptionRateLimited' | 'subscriptionSession' | 'subscriptionWeekly' | 'subscriptionMonthly' | 'subscriptionBilling' | 'subscriptionRemaining' | 'subscriptionExhausted' | 'subscriptionReset' | 'subscriptionNoApi' | 'floatWindow' | 'floatModeCombined' | 'floatModeSubscription' | 'floatMode' | 'floatTargets' | 'floatWindowHint' | 'floatPrimaryToday' | 'floatPrimaryWeek' | 'floatPrimaryMonth' | 'floatNoTargets' | 'floatNoTargetsHint' | 'cardDisplay' | 'cardDisplayHint' | 'cardMetric' | 'cardMetricMoney' | 'cardMetricTokens' | 'cardSpan' | 'cardSpanDay' | 'cardSpanWeek' | 'cardSpanMonth' | 'triggerMonthTokens' | 'triggerTodayTokens' | 'triggerWeekTokens' | 'subscriptionsStale' | 'staleLedgerNotice' | 'tokenCacheWrite' | 'toolRank' | 'toolName' | 'userPrices' | 'userPricesHint' | 'userPriceSave' | 'userPriceModel' | 'userPriceSource' | 'userPriceSourceHint' | 'userPriceOffPeak' | 'userPriceNormal' | 'userPriceCurrency' | 'userPriceAdd' | 'userPriceRemoveSelected' | 'userPriceSelect' | 'sessionStaleBadge' | 'sessionUntitled' | 'heatmapLess' | 'heatmapMore' | 'currency' | 'currencyCny' | 'currencyUsd' | 'heatmap' | 'rounds' | 'roundsHint' | 'anomaly' | 'model' | 'thModel' | 'thInputMiss' | 'thInputHit' | 'costAbbr' | 'tabOverview' | 'tabTrends' | 'tabProviders' | 'tabPricing' | 'tabSettings' | 'budgetHint' | 'peakAlertHint' | 'peakAlertDescPeak' | 'peakAlertDescOff' | 'export' | 'exportCsvDay' | 'exportCsvSession' | 'exportJson' | 'peakShare' | 'peakSharePerCall' | 'offPeakSavings' | 'perfMax' | 'weekCost' | 'roleCost' | 'roleUser' | 'roleAssistant' | 'roleTool' | 'roleHint' | 'tierPeak' | 'tierOff' | 'tierToPeak' | 'tierToOff' | 'tierAlertEnterPeak' | 'tierAlertEnterOff' | 'peakAlertTitlePeak' | 'peakAlertTitleOff' | 'peakAlert' | 'peakAlertLeadMin' | 'peakAlertPos' | 'peakAlertMode' | 'peakAlertPosCorner' | 'peakAlertPosCenter' | 'peakAlertModePeak' | 'peakAlertModeOff' | 'peakAlertModeBoth' | 'peakAlertWebNotify' | 'peakAlertPreview' | 'planTypeCode' | 'planTypeToken' | 'subscriptionFeePerMonth' | 'triggerToday' | 'triggerMonth' | 'subscriptionTag' | 'free' | 'perfSamples' | 'perfTtft' | 'perfP50' | 'perfP90' | 'perfTps' | 'perfLatency' | 'perfEstimated' | 'perfEmpty' | 'perfTpsUnit' | 'perfTitle' | 'perfHint' | 'perfAll' | 'perfChartEmpty' | 'heatmapYear' | 'heatmapMonth' | 'activeDays' | 'streakDays' | 'subscriptionAutoDetect' | 'pluginAuthor' | 'pluginRepository' | 'pluginNpm' | 'pluginLicense' | 'tabToken' | 'tokenExport' | 'tokenExportCsv' | 'tokenReasoningShare' | 'tokenReasoningShort' | 'tokenIo' | 'tokenPeak' | 'tokenDaily' | 'tokenByModel' | 'tokenMiss' | 'tokenHit' | 'tokenOutput' | 'tokenViewStructure' | 'tokenViewModel' | 'tokenHitShort' | 'tokenMissShort' | 'tokenTotal' | 'tokenShare' | 'usageStatsTool' | 'usageStatsToolHint' | 'balanceGranted' | 'balanceTopped' | 'balanceDaily' | 'balanceDaysLong' | 'balanceDaysUnit' | 'popTodayModel' | 'popNoConsumption' | 'popTitle' | 'popDirectLead' | 'popSubLead' | 'unpricedHint' | 'searchEstimateHint' | 'siteListDisplay' | 'siteListDisplayHint' | 'liveCostBar' | 'liveCostBarHint' | 'liveCostBarPosition' | 'liveCostBarPosBelow' | 'liveCostBarPosAbove' | 'liveCostBarPosToolbar' | 'exportCsvSite' | 'panelRelayQuota' | 'relayBalance' | 'relayNoQuota' | 'relayWindowUsed' | 'relayKindNewApi' | 'relayKindSub2Api' | 'relayKindUnknown';
|
|
3
3
|
export declare const NS = "usageBilling";
|
|
4
4
|
export declare const zh: Record<UsageBillingKey, string>;
|
|
5
5
|
export declare const en: Record<UsageBillingKey, string>;
|
|
@@ -30,7 +30,7 @@ export interface FloatWindowPrefs {
|
|
|
30
30
|
/** `subscription` 模式下可切换展示的订阅通道 provider id 列表(每次显示一张)。 */
|
|
31
31
|
targets: string[];
|
|
32
32
|
}
|
|
33
|
-
/**
|
|
33
|
+
/** 默认浮窗偏好:综合模式、无指定目标。 */
|
|
34
34
|
export declare const DEFAULT_FLOAT_WINDOW_PREFS: FloatWindowPrefs;
|
|
35
35
|
/** localStorage key(与 budget store 的 `dsh.ui-usage-billing.*` 命名空间一致)。 */
|
|
36
36
|
export declare const FLOAT_WINDOW_STORAGE_KEY = "dsh.ui-usage-billing.float";
|
|
@@ -41,15 +41,19 @@ export declare function loadFloatWindowPrefs(): FloatWindowPrefs;
|
|
|
41
41
|
export declare function saveFloatWindowPrefs(prefs: FloatWindowPrefs): void;
|
|
42
42
|
/** 左下角计费卡的主指标视角。 */
|
|
43
43
|
export type BillingCardMetric = 'money' | 'tokens';
|
|
44
|
+
/** 计费卡主数字的统计范围(单值卡面,issue #47 反馈)。 */
|
|
45
|
+
export type BillingCardSpan = 'day' | 'week' | 'month';
|
|
44
46
|
/**
|
|
45
|
-
*
|
|
47
|
+
* 计费卡显示偏好:卡面主数字与迷你柱的计价视角 + 主数字统计范围。
|
|
46
48
|
* 纯 client 偏好,存 localStorage(不依赖 node 半区接口/设置 schema)。
|
|
47
49
|
*/
|
|
48
50
|
export interface BillingCardPrefs {
|
|
49
51
|
/** 主指标:花费金额(CNY/USD 按币种)/ Token 消耗。 */
|
|
50
52
|
metric: BillingCardMetric;
|
|
53
|
+
/** 主数字统计范围:今日 / 本周 / 本月(默认今日)。 */
|
|
54
|
+
span: BillingCardSpan;
|
|
51
55
|
}
|
|
52
|
-
/**
|
|
56
|
+
/** 默认计费卡偏好:花费金额 + 今日口径。 */
|
|
53
57
|
export declare const DEFAULT_BILLING_CARD_PREFS: BillingCardPrefs;
|
|
54
58
|
/** localStorage key(与 budget store 的 `dsh.ui-usage-billing.*` 命名空间一致)。 */
|
|
55
59
|
export declare const BILLING_CARD_STORAGE_KEY = "dsh.ui-usage-billing.card";
|
|
@@ -57,6 +61,14 @@ export declare const BILLING_CARD_STORAGE_KEY = "dsh.ui-usage-billing.card";
|
|
|
57
61
|
export declare function loadBillingCardPrefs(): BillingCardPrefs;
|
|
58
62
|
/** 写入计费卡偏好。失败静默(展示偏好非关键)。 */
|
|
59
63
|
export declare function saveBillingCardPrefs(prefs: BillingCardPrefs): void;
|
|
64
|
+
/** 概览 KPI 全局统计范围(今日/近7天/本周/本月/累计),与组件内 AvgCostRange 同构。 */
|
|
65
|
+
export type KpiRangePref = 'today' | '7d' | 'week' | 'month' | 'all';
|
|
66
|
+
/** localStorage key(与其他 `dsh.ui-usage-billing.*` 偏好同命名空间)。 */
|
|
67
|
+
export declare const KPI_RANGE_STORAGE_KEY = "dsh.ui-usage-billing.kpi-range";
|
|
68
|
+
/** 读取 KPI 全局范围偏好(损坏/越界回退「累计」)。仅在浏览器半区调用。 */
|
|
69
|
+
export declare function loadKpiRange(): KpiRangePref;
|
|
70
|
+
/** 写入 KPI 全局范围偏好。失败静默(展示偏好非关键)。 */
|
|
71
|
+
export declare function saveKpiRange(range: KpiRangePref): void;
|
|
60
72
|
/**
|
|
61
73
|
* 中转站列表(中转站分布 / 中转站额度)的展示偏好(issue #17)。
|
|
62
74
|
* 纯 client 偏好,存 localStorage(不依赖 node 半区接口/设置 schema)。
|
|
@@ -82,10 +94,10 @@ export declare function saveSiteListPrefs(prefs: SiteListPrefs): void;
|
|
|
82
94
|
export interface LiveCostBarPrefs {
|
|
83
95
|
/** 是否显示输入框下方的即时代费条胶囊(默认 true:保持历史行为)。 */
|
|
84
96
|
show: boolean;
|
|
85
|
-
/** 胶囊位置:
|
|
97
|
+
/** 胶囊位置:toolbar = 输入框内部工具行内联 chip(默认,issue #47);below = 输入框下方;above = 输入框上方。 */
|
|
86
98
|
position: 'below' | 'above' | 'toolbar';
|
|
87
99
|
}
|
|
88
|
-
/**
|
|
100
|
+
/** 默认即时代费条偏好:显示在输入框内部(工具行内联 chip,issue #47 反馈「上方/下方」都打断输入视线)。 */
|
|
89
101
|
export declare const DEFAULT_LIVE_COST_BAR_PREFS: LiveCostBarPrefs;
|
|
90
102
|
/** localStorage key(与其他 `dsh.ui-usage-billing.*` 偏好同命名空间)。 */
|
|
91
103
|
export declare const LIVE_COST_BAR_STORAGE_KEY = "dsh.ui-usage-billing.livecost";
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kenz1117/dsh-ui-usage-billing",
|
|
3
3
|
"description": "Usage billing dashboard for DeepSeek Harness: sidebar cost metrics plus a full dashboard modal, priced from a current multi-provider catalog with real usage aggregated from session logs.",
|
|
4
|
-
"version": "1.2.
|
|
4
|
+
"version": "1.2.8",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
|
7
7
|
"deepseek",
|