deepseek-harness-wallet 0.3.5 → 0.3.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/CHANGELOG.md +15 -0
- package/README.md +9 -6
- package/index.js +205 -14
- package/lib/client.js +246 -9
- package/package.json +2 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented in this file.
|
|
4
4
|
|
|
5
|
+
## 0.3.8 - 2026-09-02
|
|
6
|
+
|
|
7
|
+
- 新增第三方 API 自定义价格(Issue #36):可按精确 Provider/模型设置币种以及每百万 Token 的输入、缓存读、缓存写和输出价格;本会话与 365 天账本会按当前规则重新估算,并始终标注为用户自定义估算。 / Added custom third-party API pricing (Issue #36) with exact Provider/model matching, currency, and per-million input/cache-read/cache-write/output rates; current-session and 365-day ledger totals are recalculated from the active user rule and always labeled as estimates.
|
|
8
|
+
- 自定义价格保存在本机 `wallet.json`,支持修改、删除、观察到的路由建议、输入边界与官方/套餐 Provider 隔离;不会保存 API Key,也不会更改历史 Token。 / Custom prices persist locally in `wallet.json`, support editing, deletion, observed-route suggestions, bounded inputs, and strict separation from official/plan providers; no API keys or historical token counts are changed.
|
|
9
|
+
|
|
10
|
+
## 0.3.7 - 2026-09-01
|
|
11
|
+
|
|
12
|
+
- 适配 DeepSeek Harness `0.1.2-alpha.3` 的新客户端包结构:移除已被官方删除的 `@deepseek-ai/dsh-client-runtime` 清单边,继续通过 Cordis `sessions` 服务和现存 UI slot 合约工作;同时保留 `0.1.1-rc.2` 兼容。 / Adapted to the new DeepSeek Harness `0.1.2-alpha.3` client package layout by removing the retired `@deepseek-ai/dsh-client-runtime` manifest edge while continuing to consume the Cordis `sessions` service and surviving UI-slot contracts; `0.1.1-rc.2` compatibility is retained.
|
|
13
|
+
- 在隔离的 alpha.3 构建和数据目录中完成 npm 安装、完整 Harness 构建、Web 启动、插件后端、设置页、365 天账本、背景选项和健康检查验证,未向测试环境写入真实凭据。 / Verified npm installation, a full Harness build, Web startup, plugin host routes, settings UI, 365-day ledger, background controls, and health checks in isolated alpha.3 source/data directories without copying real credentials into the test environment.
|
|
14
|
+
|
|
15
|
+
## 0.3.6 - 2026-08-28
|
|
16
|
+
|
|
17
|
+
- 峰谷时钟新增明确的「透明 / 实色」背景选择,不加入自动模式;透明模式常态不铺底,悬停时使用主题实色,实色模式保持原有卡片背景。 / Added an explicit peak-clock background choice between transparent and solid, without an automatic mode; transparent mode removes the normal fill and uses the themed solid hover surface, while solid mode keeps the existing card background.
|
|
18
|
+
- 设置页和时钟控制面板共享该偏好,并同步保存到宿主钱包存储;覆盖默认、悬浮、竖排、浅色和深色主题,避免透明卡片文字或悬浮卡片背景异常。 / The setting is shared by the settings page and clock control panel and backed by the host wallet store; default, floating, vertical, light, and dark-theme paths are covered so transparent cards remain readable and floating cards keep a valid surface.
|
|
19
|
+
|
|
5
20
|
## 0.3.5 - 2026-08-27
|
|
6
21
|
|
|
7
22
|
- 修复 BigModel / Z.ai 中国区 Coding Plan 接口将 `TOKENS_LIMIT` 改名为 `CREDIT_LIMIT` 后被误判为“套餐接口返回格式已变化”的问题;兼容新旧字段,支持只有新额度字段的响应,并在接口提供时显示已用/总量、剩余量和重置时间。 / Fixed BigModel / Z.ai China Coding Plan responses being misclassified as “the plan response format changed” after `TOKENS_LIMIT` was renamed to `CREDIT_LIMIT`; both names are supported, credit-only responses are accepted, and available used/total, remaining, and reset values are shown when provided.
|
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
> A local-first companion that keeps account status, per-conversation usage, completion reminders, official recharge, flexible layout, and host-gated session controls beside the DSH composer.
|
|
16
16
|
|
|
17
|
-
> **Version:** v0.3.
|
|
17
|
+
> **Version:** v0.3.8 is the current release on GitHub and npm.
|
|
18
18
|
|
|
19
19
|
> If DeepSeek Harness Control Center helps you, please consider leaving a ⭐ Star. Thank you!
|
|
20
20
|
|
|
@@ -31,10 +31,11 @@
|
|
|
31
31
|
- **Z.ai Coding Plan quotas** — a generic official-plan adapter monitors configured Global and China plans without exposing credentials. It separates the 5-hour model-token window from monthly MCP-tool usage, leads with quota remaining from 100% down while keeping usage as secondary context, retains the last successful snapshot on failure, and never converts subscription quota into CNY balance.
|
|
32
32
|
- **Provider-aware composer surfaces** — the chip and sidebar clock follow the session's selected provider/model. Z.ai replaces DeepSeek balance, recharge, and peak pricing with plan-window summaries; unrelated providers show only their own session tokens, and DeepSeek V4 restores the wallet and peak clock.
|
|
33
33
|
- **365-day local usage ledger** — Wallet settings keeps the heatmap visible, while compact wallet panels keep it collapsible. Stable request identities are deduplicated, official cost is locked at usage time, official and third-party data stay separate, and prompts or responses are never stored. Collection begins after upgrading to v0.3.2; older aggregate counters have no trustworthy dates and are not backfilled.
|
|
34
|
-
- **Third-party total** — current-session tokens (input / cache read / output)
|
|
34
|
+
- **Third-party total** — current-session tokens (input / cache read / output) remain available with zero configuration.
|
|
35
|
+
- **Custom third-party pricing** — enter an exact Provider/model route, currency, and per-million input/cache-read/cache-write/output rates. Current-session and 365-day ledger costs are recalculated locally from the saved rule and clearly labeled as user-defined estimates, never as provider balances or invoices.
|
|
35
36
|
- **Provider classification** — observed wrapper routes appear in the settings page; opted-in routes join the official token/cost bucket for subsequent calls and are priced with the official table. Existing history is not retroactively reclassified.
|
|
36
37
|
- **Click the chip** to open the detail panel: correctly formatted per-currency balances, cost and token splits, a freely editable low-balance threshold for the active account and currency (two decimals, persisted per account; alerts never mix currencies), manual refresh, and a jump to the official recharge page (first click shows the domain for confirmation — anti-phishing).
|
|
37
|
-
- **Move, dock, and scale** — drag the chip freely, preview nearby snap targets, use compact horizontal or vertical layouts, adjust its scale from the control panel, and show official or third-party data independently. The choices are remembered locally.
|
|
38
|
+
- **Move, dock, and scale** — drag the chip freely, preview nearby snap targets, use compact horizontal or vertical layouts, adjust its scale from the control panel, and show official or third-party data independently. The peak clock background can be explicitly set to transparent (solid on hover) or solid; there is no automatic mode. The choices are remembered locally.
|
|
38
39
|
- **Composer-label controls and skin compatibility** — independently show/hide the composer label or reduce it to the primary remaining value (DeepSeek balance, Z.ai five-hour quota) without disabling reminders, settings, plan monitoring, or history. The standard chip also resists broad skin button rules and aligns with maid-atelier's 38px navy-and-gold composer toolbar.
|
|
39
40
|
- **Floating window mode** — detach the detail panel into a draggable window with a remembered position, or minimize it directly to a freely movable dot; the dot turns red below the threshold.
|
|
40
41
|
- **Completion reminders** — optionally notify when a conversation finishes, with persistent or timed modes, queueing and deduplication for simultaneous completions, cross-tab coordination, and an in-page fallback when system notifications are unavailable.
|
|
@@ -73,13 +74,13 @@ Details: [compatibility](#browser-desktop-and-os-compatibility) · [data and tru
|
|
|
73
74
|
|
|
74
75
|
## Install
|
|
75
76
|
|
|
76
|
-
From npm (stable v0.3.
|
|
77
|
+
From npm (published stable v0.3.8):
|
|
77
78
|
|
|
78
79
|
```sh
|
|
79
80
|
dsh plugin --profile web add deepseek-harness-wallet
|
|
80
81
|
```
|
|
81
82
|
|
|
82
|
-
or from GitHub `main
|
|
83
|
+
or from GitHub `main`:
|
|
83
84
|
|
|
84
85
|
```sh
|
|
85
86
|
dsh plugin --profile web add github:feibi-mochi/deepseek-harness-control-center
|
|
@@ -116,6 +117,7 @@ The client contains no operating-system-specific feature branch; it checks the W
|
|
|
116
117
|
| Verification level | Coverage |
|
|
117
118
|
| --- | --- |
|
|
118
119
|
| Real environment checked for this release | Windows + current Edge + DSH Web |
|
|
120
|
+
| Verified Harness versions | `0.1.1-rc.2` and isolated `0.1.2-alpha.3` Web profiles; the alpha profile was built from its official tag and started with no real credentials |
|
|
119
121
|
| Automated compatibility checks | Browser notification failure, in-page fallback, cross-tab fallback, storage fallback, CSS-scale fallback, and synchronous/asynchronous desktop adapters |
|
|
120
122
|
| Capability-compatible targets | Current Chrome, Edge, and Firefox on Windows/macOS/Linux; Safari on macOS; Electron/Tauri-style DSH wrappers that provide the requirements below |
|
|
121
123
|
|
|
@@ -148,7 +150,7 @@ For buildable DSH hosts, the npm package and repository include a versioned [Age
|
|
|
148
150
|
| Token accounting | Listens to the `llm/stream` event and buckets per session and provider: `deepseek-official` plus explicitly opted-in wrapper routes use the official bucket; other providers stay third-party; each usage event also locks its contemporaneous official price, so multiple sessions and pricing windows never mix. |
|
|
149
151
|
| Balance | The wallet plugin itself sends the active key directly only to the official `/user/balance` endpoint. When multi-account switching is enabled, the selected key is also written into the DSH credentials seam; DSH may then use it for subsequent model requests. |
|
|
150
152
|
| Accounts | Keys live encrypted in `$DSH_HOME/storages/accounts.json`, with an encrypted `accounts.json.bak` fallback for a missing, corrupt, or undecryptable primary. Windows uses current-user DPAPI; other platforms use an owner-only AES-GCM key file, so move `accounts.json`, `.bak`, and `.key` together. If neither copy can be read, account writes fail closed. |
|
|
151
|
-
| Usage ledger | Local events live in `$DSH_HOME/storages/wallet.json` with a `wallet.json.bak` recovery copy. Missing/corrupt primaries recover automatically; if neither copy is readable, wallet writes fail closed. Up to 365 days of session/provider/model/token metadata and locked cost are kept—never prompts, tool arguments, or
|
|
153
|
+
| Usage ledger | Local events and custom third-party price rules live in `$DSH_HOME/storages/wallet.json` with a `wallet.json.bak` recovery copy. Missing/corrupt primaries recover automatically; if neither copy is readable, wallet writes fail closed. Up to 365 days of session/provider/model/token metadata and official locked cost are kept—never prompts, tool arguments, response bodies, or API keys. Third-party estimates are recalculated from the current custom rule. |
|
|
152
154
|
| Local settings | Layout, scale, visibility, reminder, and panel settings stay in browser-compatible local storage. |
|
|
153
155
|
| Permanent deletion | Opt-in and host-gated. The wallet never advertises the action unless the host implements the matching session deletion path. |
|
|
154
156
|
| Model surface | No tools registered, no prompt injection, zero token cost. |
|
|
@@ -172,6 +174,7 @@ Historical deepseek-chat and deepseek-reasoner records retain their original fla
|
|
|
172
174
|
|
|
173
175
|
- [x] 365-day Token heatmap and rebuildable local usage ledger
|
|
174
176
|
- [x] Z.ai Coding Plan Global/China monitoring on a generic official-plan adapter contract
|
|
177
|
+
- [x] User-defined third-party Provider/model pricing
|
|
175
178
|
- [ ] Additional provider price/balance adapters only after real-account validation
|
|
176
179
|
|
|
177
180
|
## License
|
package/index.js
CHANGED
|
@@ -33,7 +33,7 @@ const OFFICIAL_PROVIDER = 'deepseek-official'
|
|
|
33
33
|
// DeepSeek's official paid-API bucket by the wrapper-provider alias control.
|
|
34
34
|
const PLAN_PROVIDER_IDS = new Set(PLAN_ADAPTERS.map((adapter) => adapter.provider))
|
|
35
35
|
const RECHARGE_URL = 'https://platform.deepseek.com/top_up'
|
|
36
|
-
const PLUGIN_VERSION = '0.3.
|
|
36
|
+
const PLUGIN_VERSION = '0.3.8'
|
|
37
37
|
const PRICING_SOURCE_URL = 'https://api-docs.deepseek.com/zh-cn/quick_start/pricing/'
|
|
38
38
|
const PRICING_SYNC_INTERVAL_MS = 6 * 60 * 60_000
|
|
39
39
|
const PRICING_SYNC_TIMEOUT_MS = 8_000
|
|
@@ -49,10 +49,12 @@ const ACCOUNTS_CRYPTO_VERSION = 1
|
|
|
49
49
|
const CREDENTIAL_REF = 'DEEPSEEK_API_KEY'
|
|
50
50
|
const DEFAULT_THRESHOLD = 5
|
|
51
51
|
const BALANCE_REFRESH_MS = 60_000
|
|
52
|
-
const STORE_VERSION =
|
|
52
|
+
const STORE_VERSION = 6
|
|
53
53
|
const HISTORY_VERSION = 1
|
|
54
54
|
const HISTORY_RETENTION_DAYS = 365
|
|
55
55
|
const HISTORY_MAX_EVENTS = 20_000
|
|
56
|
+
const CUSTOM_PRICE_MAX_RULES = 100
|
|
57
|
+
const CUSTOM_PRICE_MAX_RATE = 1_000_000
|
|
56
58
|
const HISTORY_TIMEZONE = 'Asia/Shanghai'
|
|
57
59
|
const DAY_MS = 86_400_000
|
|
58
60
|
const PLAN_REFRESH_MS = 5 * 60_000
|
|
@@ -68,6 +70,7 @@ const UI_PREFERENCE_KEYS = new Set([
|
|
|
68
70
|
'dshw-low-blink-v1',
|
|
69
71
|
'dshw-peakring-v1',
|
|
70
72
|
'dshw-peak-orient-v1',
|
|
73
|
+
'dshw-peak-background-v1',
|
|
71
74
|
'dshw-peak-recharge-v1',
|
|
72
75
|
'dshw-peak-scale-v1',
|
|
73
76
|
'dshw-peak-dock-v1',
|
|
@@ -732,6 +735,15 @@ function removeSessionContribution(event) {
|
|
|
732
735
|
bucket.unpriced = Math.max(0, currentUnpriced - 1)
|
|
733
736
|
bucket.priced = bucket.unpriced === 0
|
|
734
737
|
}
|
|
738
|
+
} else if (bucket.routes && bucket.routes[event.provider] && bucket.routes[event.provider].models) {
|
|
739
|
+
const routeCounters = bucket.routes[event.provider].models[event.model]
|
|
740
|
+
if (routeCounters !== null && typeof routeCounters === 'object') {
|
|
741
|
+
subtractUsage(routeCounters, event.usage)
|
|
742
|
+
if (Object.values(routeCounters).every((value) => finiteCounter(value) === 0)) {
|
|
743
|
+
delete bucket.routes[event.provider].models[event.model]
|
|
744
|
+
}
|
|
745
|
+
if (Object.keys(bucket.routes[event.provider].models).length === 0) delete bucket.routes[event.provider]
|
|
746
|
+
}
|
|
735
747
|
}
|
|
736
748
|
}
|
|
737
749
|
|
|
@@ -811,17 +823,24 @@ function historyAggregate(events) {
|
|
|
811
823
|
}
|
|
812
824
|
total.cost = official.cost
|
|
813
825
|
total.priced = official.priced
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
cacheHitRate: cacheInput + cacheRead > 0 ? Math.round(cacheRead / (cacheInput + cacheRead) * 1000) / 10 : null,
|
|
819
|
-
models: [...models.values()].map((model) => ({
|
|
826
|
+
const publicModels = [...models.values()].map((model) => {
|
|
827
|
+
const aggregate = publicHistoryAggregate(model, model.official)
|
|
828
|
+
const priced = model.official ? null : customCostOf(model.provider, model.model, model.tokens, store.customPrices)
|
|
829
|
+
return {
|
|
820
830
|
provider: model.provider,
|
|
821
831
|
model: model.model,
|
|
822
832
|
official: model.official,
|
|
823
|
-
...
|
|
824
|
-
|
|
833
|
+
...aggregate,
|
|
834
|
+
customCost: priced === null ? null : { cost: priced.cost, currency: priced.currency },
|
|
835
|
+
}
|
|
836
|
+
})
|
|
837
|
+
const customCosts = customCostTotals(publicModels)
|
|
838
|
+
return {
|
|
839
|
+
official: publicHistoryAggregate(official, true),
|
|
840
|
+
third: { ...publicHistoryAggregate(third, false), customCosts },
|
|
841
|
+
total: { ...publicHistoryAggregate(total, official.calls > 0), customCosts },
|
|
842
|
+
cacheHitRate: cacheInput + cacheRead > 0 ? Math.round(cacheRead / (cacheInput + cacheRead) * 1000) / 10 : null,
|
|
843
|
+
models: publicModels,
|
|
825
844
|
}
|
|
826
845
|
}
|
|
827
846
|
|
|
@@ -902,6 +921,90 @@ export function isOfficialProvider(provider, extraProviders) {
|
|
|
902
921
|
return Array.isArray(extraProviders) && extraProviders.includes(provider)
|
|
903
922
|
}
|
|
904
923
|
|
|
924
|
+
function customPriceRate(value) {
|
|
925
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value < 0 || value > CUSTOM_PRICE_MAX_RATE) return null
|
|
926
|
+
return Math.round(value * 1_000_000) / 1_000_000
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
/** Normalize one exact third-party provider/model price rule (rates are per 1M tokens). */
|
|
930
|
+
export function normalizeCustomPriceRule(value) {
|
|
931
|
+
const source = value !== null && typeof value === 'object' && !Array.isArray(value) ? value : {}
|
|
932
|
+
const provider = boundedString(source.provider, 100)
|
|
933
|
+
const model = boundedString(source.model, 160)
|
|
934
|
+
const currency = typeof source.currency === 'string' ? source.currency.toUpperCase() : ''
|
|
935
|
+
const input = customPriceRate(source.input)
|
|
936
|
+
const cacheRead = customPriceRate(source.cacheRead)
|
|
937
|
+
const cacheWrite = customPriceRate(source.cacheWrite)
|
|
938
|
+
const output = customPriceRate(source.output)
|
|
939
|
+
if (provider === null || model === null || !/^[A-Z]{3}$/.test(currency)) return null
|
|
940
|
+
if (provider === OFFICIAL_PROVIDER || isPlanProvider(provider)) return null
|
|
941
|
+
if (provider === '__proto__' || provider === 'prototype' || provider === 'constructor') return null
|
|
942
|
+
if (model === '__proto__' || model === 'prototype' || model === 'constructor') return null
|
|
943
|
+
if ([input, cacheRead, cacheWrite, output].some((rate) => rate === null)) return null
|
|
944
|
+
return { provider, model, currency, input, cacheRead, cacheWrite, output }
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
/** Normalize, deduplicate, and bound persisted custom third-party price rules. */
|
|
948
|
+
export function normalizeCustomPrices(value) {
|
|
949
|
+
if (!Array.isArray(value)) return []
|
|
950
|
+
const rules = new Map()
|
|
951
|
+
for (const candidate of value) {
|
|
952
|
+
const rule = normalizeCustomPriceRule(candidate)
|
|
953
|
+
if (rule === null) continue
|
|
954
|
+
const key = rule.provider + '\u0000' + rule.model
|
|
955
|
+
if (rules.has(key)) rules.delete(key)
|
|
956
|
+
rules.set(key, rule)
|
|
957
|
+
if (rules.size > CUSTOM_PRICE_MAX_RULES) rules.delete(rules.keys().next().value)
|
|
958
|
+
}
|
|
959
|
+
return [...rules.values()]
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
/** Price one exact third-party route from user-entered per-million-token rates. */
|
|
963
|
+
export function customCostOf(provider, model, usage, rules) {
|
|
964
|
+
if (typeof provider !== 'string' || typeof model !== 'string') return null
|
|
965
|
+
const rule = normalizeCustomPrices(rules).find((entry) => entry.provider === provider && entry.model === model)
|
|
966
|
+
if (rule === undefined) return null
|
|
967
|
+
const counters = historyUsageCounters(usage)
|
|
968
|
+
const cost = (
|
|
969
|
+
counters.input * rule.input
|
|
970
|
+
+ counters.cacheRead * rule.cacheRead
|
|
971
|
+
+ counters.cacheWrite * rule.cacheWrite
|
|
972
|
+
+ counters.output * rule.output
|
|
973
|
+
) / 1e6
|
|
974
|
+
return { cost, currency: rule.currency, rule }
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
function normalizeThirdPartyRoutes(value) {
|
|
978
|
+
const source = value !== null && typeof value === 'object' && !Array.isArray(value) ? value : {}
|
|
979
|
+
const routes = {}
|
|
980
|
+
let providers = 0
|
|
981
|
+
let models = 0
|
|
982
|
+
for (const [provider, rawProvider] of Object.entries(source)) {
|
|
983
|
+
if (boundedString(provider, 100) === null || provider === OFFICIAL_PROVIDER || isPlanProvider(provider)) continue
|
|
984
|
+
if (provider === '__proto__' || provider === 'prototype' || provider === 'constructor') continue
|
|
985
|
+
const rawModels = rawProvider !== null && typeof rawProvider === 'object' && !Array.isArray(rawProvider)
|
|
986
|
+
? rawProvider.models
|
|
987
|
+
: null
|
|
988
|
+
const normalizedModels = normalizeModels(rawModels)
|
|
989
|
+
if (Object.keys(normalizedModels).length === 0) continue
|
|
990
|
+
routes[provider] = { models: normalizedModels }
|
|
991
|
+
providers += 1
|
|
992
|
+
models += Object.keys(normalizedModels).length
|
|
993
|
+
if (providers >= 100 || models >= 1_000) break
|
|
994
|
+
}
|
|
995
|
+
return routes
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
function customCostTotals(rows) {
|
|
999
|
+
const totals = {}
|
|
1000
|
+
for (const row of rows) {
|
|
1001
|
+
if (row === null || typeof row !== 'object' || !row.customCost) continue
|
|
1002
|
+
const currency = row.customCost.currency
|
|
1003
|
+
totals[currency] = (totals[currency] || 0) + row.customCost.cost
|
|
1004
|
+
}
|
|
1005
|
+
return totals
|
|
1006
|
+
}
|
|
1007
|
+
|
|
905
1008
|
export function normalizeUiPreferences(value) {
|
|
906
1009
|
const source = value !== null && typeof value === 'object' && !Array.isArray(value) ? value : {}
|
|
907
1010
|
const normalized = {}
|
|
@@ -963,7 +1066,10 @@ export function normalizeStoreData(value, atMs = Date.now()) {
|
|
|
963
1066
|
const rawSession = value !== null && typeof value === 'object' && !Array.isArray(value) ? value : {}
|
|
964
1067
|
sessions[sessionId] = {
|
|
965
1068
|
official: migratedOfficialBucket(rawSession.official, atMs),
|
|
966
|
-
third: {
|
|
1069
|
+
third: {
|
|
1070
|
+
models: normalizeModels(rawSession.third && rawSession.third.models),
|
|
1071
|
+
routes: normalizeThirdPartyRoutes(rawSession.third && rawSession.third.routes),
|
|
1072
|
+
},
|
|
967
1073
|
}
|
|
968
1074
|
sessionCount += 1
|
|
969
1075
|
if (sessionCount >= 5_000) break
|
|
@@ -975,6 +1081,7 @@ export function normalizeStoreData(value, atMs = Date.now()) {
|
|
|
975
1081
|
sessions,
|
|
976
1082
|
officialProviders: normalizeProviderList(source.officialProviders),
|
|
977
1083
|
knownProviders: normalizeProviderList(source.knownProviders),
|
|
1084
|
+
customPrices: normalizeCustomPrices(source.customPrices),
|
|
978
1085
|
plans: normalizePlanSnapshotCache(source.plans, atMs),
|
|
979
1086
|
preferences: normalizeUiPreferences(source.preferences),
|
|
980
1087
|
}
|
|
@@ -992,7 +1099,7 @@ let usageStorageRecovered = false
|
|
|
992
1099
|
let usageStoreSkipBackupOnce = false
|
|
993
1100
|
|
|
994
1101
|
function emptyStoreData() {
|
|
995
|
-
return { version: STORE_VERSION, thresholds: { CNY: DEFAULT_THRESHOLD }, accountThresholds: {}, sessions: {}, officialProviders: [], knownProviders: [], plans: {}, preferences: {}, history: emptyHistory() }
|
|
1102
|
+
return { version: STORE_VERSION, thresholds: { CNY: DEFAULT_THRESHOLD }, accountThresholds: {}, sessions: {}, officialProviders: [], knownProviders: [], customPrices: [], plans: {}, preferences: {}, history: emptyHistory() }
|
|
996
1103
|
}
|
|
997
1104
|
|
|
998
1105
|
function readStoreFile(path) {
|
|
@@ -1702,7 +1809,7 @@ function recordUsage(options, usage, atMs = Date.now()) {
|
|
|
1702
1809
|
store.knownProviders = normalizeProviderList([...store.knownProviders, provider])
|
|
1703
1810
|
}
|
|
1704
1811
|
if (!Object.hasOwn(store.sessions, sessionId)) {
|
|
1705
|
-
store.sessions[sessionId] = { official: emptyBucket(true), third:
|
|
1812
|
+
store.sessions[sessionId] = { official: emptyBucket(true), third: { models: {}, routes: {} } }
|
|
1706
1813
|
}
|
|
1707
1814
|
const event = createHistoryEvent(request, usage, atMs, store.officialProviders)
|
|
1708
1815
|
const previousEvent = event !== null && store.history && store.history.events ? store.history.events[event.eventId] : undefined
|
|
@@ -1715,6 +1822,10 @@ function recordUsage(options, usage, atMs = Date.now()) {
|
|
|
1715
1822
|
else {
|
|
1716
1823
|
if (!Object.hasOwn(bucket.models, model)) bucket.models[model] = emptyCounters()
|
|
1717
1824
|
addUsage(bucket.models[model], usage)
|
|
1825
|
+
if (bucket.routes === null || typeof bucket.routes !== 'object' || Array.isArray(bucket.routes)) bucket.routes = {}
|
|
1826
|
+
if (!Object.hasOwn(bucket.routes, provider)) bucket.routes[provider] = { models: {} }
|
|
1827
|
+
if (!Object.hasOwn(bucket.routes[provider].models, model)) bucket.routes[provider].models[model] = emptyCounters()
|
|
1828
|
+
addUsage(bucket.routes[provider].models[model], usage)
|
|
1718
1829
|
}
|
|
1719
1830
|
recordHistoryEvent(event)
|
|
1720
1831
|
}
|
|
@@ -1836,6 +1947,42 @@ function balanceTotal() {
|
|
|
1836
1947
|
return sumBalances(balance.balances)
|
|
1837
1948
|
}
|
|
1838
1949
|
|
|
1950
|
+
function thirdPartyRouteView(bucket) {
|
|
1951
|
+
const routes = []
|
|
1952
|
+
const source = normalizeThirdPartyRoutes(bucket && bucket.routes)
|
|
1953
|
+
for (const [provider, providerState] of Object.entries(source)) {
|
|
1954
|
+
if (isPlanProvider(provider) || isOfficialProvider(provider, store.officialProviders)) continue
|
|
1955
|
+
for (const [model, counters] of Object.entries(providerState.models)) {
|
|
1956
|
+
const priced = customCostOf(provider, model, counters, store.customPrices)
|
|
1957
|
+
routes.push({
|
|
1958
|
+
provider,
|
|
1959
|
+
model,
|
|
1960
|
+
tokens: counters,
|
|
1961
|
+
totalTokens: historyTokenTotal(counters),
|
|
1962
|
+
customCost: priced === null ? null : { cost: priced.cost, currency: priced.currency },
|
|
1963
|
+
})
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
return routes
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1969
|
+
function knownThirdPartyRoutes() {
|
|
1970
|
+
const found = new Map()
|
|
1971
|
+
for (const session of Object.values(store.sessions)) {
|
|
1972
|
+
for (const route of thirdPartyRouteView(session && session.third)) {
|
|
1973
|
+
found.set(route.provider + '\u0000' + route.model, { provider: route.provider, model: route.model })
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1976
|
+
for (const event of Object.values(store.history && store.history.events ? store.history.events : {})) {
|
|
1977
|
+
if (!event || event.official || isPlanProvider(event.provider) || isOfficialProvider(event.provider, store.officialProviders)) continue
|
|
1978
|
+
const provider = boundedString(event.provider, 100)
|
|
1979
|
+
const model = boundedString(event.model, 160)
|
|
1980
|
+
if (provider !== null && model !== null) found.set(provider + '\u0000' + model, { provider, model })
|
|
1981
|
+
if (found.size >= 500) break
|
|
1982
|
+
}
|
|
1983
|
+
return [...found.values()].slice(0, 500)
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1839
1986
|
function sessionView(sessionId) {
|
|
1840
1987
|
if (sessionId === undefined || sessionId === '') return { official: null, third: null }
|
|
1841
1988
|
// Own-property lookup only: session ids like `__proto__`, `constructor` or
|
|
@@ -1852,6 +1999,7 @@ function sessionView(sessionId) {
|
|
|
1852
1999
|
}
|
|
1853
2000
|
const official = bucketTotals(session.official)
|
|
1854
2001
|
const third = bucketTotals(session.third)
|
|
2002
|
+
const thirdRoutes = thirdPartyRouteView(session.third)
|
|
1855
2003
|
return {
|
|
1856
2004
|
official: {
|
|
1857
2005
|
tokens: official,
|
|
@@ -1859,7 +2007,12 @@ function sessionView(sessionId) {
|
|
|
1859
2007
|
priced: session.official.priced === true,
|
|
1860
2008
|
models: session.official.models,
|
|
1861
2009
|
},
|
|
1862
|
-
third: {
|
|
2010
|
+
third: {
|
|
2011
|
+
tokens: third,
|
|
2012
|
+
models: session.third.models,
|
|
2013
|
+
routes: thirdRoutes,
|
|
2014
|
+
customCosts: customCostTotals(thirdRoutes),
|
|
2015
|
+
},
|
|
1863
2016
|
}
|
|
1864
2017
|
}
|
|
1865
2018
|
|
|
@@ -1907,6 +2060,8 @@ function snapshotView(sessionId) {
|
|
|
1907
2060
|
builtinOfficial: OFFICIAL_PROVIDER,
|
|
1908
2061
|
official: [...store.officialProviders],
|
|
1909
2062
|
known: store.knownProviders.filter((p) => p !== OFFICIAL_PROVIDER && !store.officialProviders.includes(p)),
|
|
2063
|
+
customPrices: [...store.customPrices],
|
|
2064
|
+
knownRoutes: knownThirdPartyRoutes(),
|
|
1910
2065
|
},
|
|
1911
2066
|
plans: planView(now),
|
|
1912
2067
|
}
|
|
@@ -2190,6 +2345,7 @@ historyEventCount = store.history && store.history.events ? Object.keys(store.hi
|
|
|
2190
2345
|
const body = await readBody(req)
|
|
2191
2346
|
if (body === null || !Array.isArray(body.providers)) return json(res, 400, { ok: false, error: 'providers array is required' })
|
|
2192
2347
|
store.officialProviders = normalizeProviderList(body.providers)
|
|
2348
|
+
store.customPrices = store.customPrices.filter((rule) => !store.officialProviders.includes(rule.provider))
|
|
2193
2349
|
scheduleSave(ctx.logger)
|
|
2194
2350
|
return json(res, 200, {
|
|
2195
2351
|
ok: true,
|
|
@@ -2198,6 +2354,40 @@ historyEventCount = store.history && store.history.events ? Object.keys(store.hi
|
|
|
2198
2354
|
})
|
|
2199
2355
|
},
|
|
2200
2356
|
})
|
|
2357
|
+
const disposeCustomPrices = ctx.webServer.register({
|
|
2358
|
+
kind: 'exact',
|
|
2359
|
+
path: '/api/wallet/custom-prices',
|
|
2360
|
+
handler: async (req, res) => {
|
|
2361
|
+
if (req.method === 'GET') {
|
|
2362
|
+
return json(res, 200, { ok: true, rules: [...store.customPrices], knownRoutes: knownThirdPartyRoutes() })
|
|
2363
|
+
}
|
|
2364
|
+
if (usageStorageLocked) return json(res, 423, { ok: false, error: 'usage-storage-locked' })
|
|
2365
|
+
if (req.method !== 'POST' && req.method !== 'DELETE') {
|
|
2366
|
+
return json(res, 405, { ok: false, error: 'method-not-allowed' })
|
|
2367
|
+
}
|
|
2368
|
+
const body = await readBody(req, 16_384)
|
|
2369
|
+
if (body === null) return json(res, 400, { ok: false, error: 'invalid-body' })
|
|
2370
|
+
if (req.method === 'POST') {
|
|
2371
|
+
const rule = normalizeCustomPriceRule(body.rule)
|
|
2372
|
+
if (rule === null) return json(res, 400, { ok: false, error: 'invalid-custom-price' })
|
|
2373
|
+
if (isOfficialProvider(rule.provider, store.officialProviders)) {
|
|
2374
|
+
return json(res, 400, { ok: false, error: 'official-provider-not-allowed' })
|
|
2375
|
+
}
|
|
2376
|
+
store.customPrices = normalizeCustomPrices([
|
|
2377
|
+
...store.customPrices.filter((entry) => entry.provider !== rule.provider || entry.model !== rule.model),
|
|
2378
|
+
rule,
|
|
2379
|
+
])
|
|
2380
|
+
scheduleSave(ctx.logger)
|
|
2381
|
+
return json(res, 200, { ok: true, rules: [...store.customPrices], knownRoutes: knownThirdPartyRoutes() })
|
|
2382
|
+
}
|
|
2383
|
+
const provider = boundedString(body.provider, 100)
|
|
2384
|
+
const model = boundedString(body.model, 160)
|
|
2385
|
+
if (provider === null || model === null) return json(res, 400, { ok: false, error: 'provider-and-model-required' })
|
|
2386
|
+
store.customPrices = store.customPrices.filter((entry) => entry.provider !== provider || entry.model !== model)
|
|
2387
|
+
scheduleSave(ctx.logger)
|
|
2388
|
+
return json(res, 200, { ok: true, rules: [...store.customPrices], knownRoutes: knownThirdPartyRoutes() })
|
|
2389
|
+
},
|
|
2390
|
+
})
|
|
2201
2391
|
const disposeAccounts = ctx.webServer.register({
|
|
2202
2392
|
kind: 'exact',
|
|
2203
2393
|
path: '/api/wallet/accounts',
|
|
@@ -2284,6 +2474,7 @@ historyEventCount = store.history && store.history.events ? Object.keys(store.hi
|
|
|
2284
2474
|
disposePlans()
|
|
2285
2475
|
disposeAccounts()
|
|
2286
2476
|
disposeProviders()
|
|
2477
|
+
disposeCustomPrices()
|
|
2287
2478
|
disposeActivate()
|
|
2288
2479
|
disposeRemove()
|
|
2289
2480
|
clearInterval(balanceTimer)
|
package/lib/client.js
CHANGED
|
@@ -23,7 +23,7 @@ window.__ModuleLoader__.load({
|
|
|
23
23
|
|
|
24
24
|
var POLL_MS = 15000
|
|
25
25
|
// Keep in lockstep with package.json; a test enforces the sync.
|
|
26
|
-
var WALLET_VERSION = '0.3.
|
|
26
|
+
var WALLET_VERSION = '0.3.8'
|
|
27
27
|
var CONFIRM_KEY = 'dsh-wallet-recharge-confirmed'
|
|
28
28
|
var CHIP_LAYOUT_KEY = 'dshw-chip-layout-v4'
|
|
29
29
|
var PANEL_POS_KEY = 'dshw-panel-pos-v1'
|
|
@@ -37,6 +37,7 @@ window.__ModuleLoader__.load({
|
|
|
37
37
|
var LOW_BLINK_KEY = 'dshw-low-blink-v1'
|
|
38
38
|
var PEAK_RING_KEY = 'dshw-peakring-v1'
|
|
39
39
|
var PEAK_ORIENT_KEY = 'dshw-peak-orient-v1'
|
|
40
|
+
var PEAK_BACKGROUND_KEY = 'dshw-peak-background-v1'
|
|
40
41
|
var PEAK_RECHARGE_KEY = 'dshw-peak-recharge-v1'
|
|
41
42
|
var PEAK_SCALE_KEY = 'dshw-peak-scale-v1'
|
|
42
43
|
var PEAK_DOCK_KEY = 'dshw-peak-dock-v1'
|
|
@@ -54,7 +55,7 @@ window.__ModuleLoader__.load({
|
|
|
54
55
|
var CHIP_HOME_SNAP = 96
|
|
55
56
|
var BACKED_PREFERENCE_KEYS = new Set([
|
|
56
57
|
CHIP_STYLE_KEY, CHIP_BALANCE_ONLY_KEY, DATA_VISIBILITY_KEY, CHIP_SCALE_KEY,
|
|
57
|
-
NOTIFY_CONFIG_KEY, LOW_BLINK_KEY, PEAK_RING_KEY, PEAK_ORIENT_KEY,
|
|
58
|
+
NOTIFY_CONFIG_KEY, LOW_BLINK_KEY, PEAK_RING_KEY, PEAK_ORIENT_KEY, PEAK_BACKGROUND_KEY,
|
|
58
59
|
PEAK_RECHARGE_KEY, PEAK_SCALE_KEY, PEAK_DOCK_KEY, PEAK_NOTIFY_KEY,
|
|
59
60
|
PERMANENT_DELETE_KEY
|
|
60
61
|
])
|
|
@@ -609,6 +610,15 @@ window.__ModuleLoader__.load({
|
|
|
609
610
|
'.dshw_settingChoiceCopy{min-width:0;display:flex;flex-direction:column;gap:1px}',
|
|
610
611
|
'.dshw_settingChoiceCopy strong{font-size:11px;font-weight:550;white-space:nowrap;color:var(--dsw-alias-label-primary,inherit)}',
|
|
611
612
|
'.dshw_settingChoiceCopy span{font-size:9.5px;color:var(--dsw-alias-label-secondary,var(--dsw-alias-label-tertiary,#667085));white-space:nowrap;overflow:hidden;text-overflow:ellipsis}',
|
|
613
|
+
'.dshw_priceForm{display:grid;grid-template-columns:minmax(120px,1.25fr) minmax(140px,1.5fr) 72px repeat(4,minmax(74px,.7fr)) auto;gap:7px;align-items:end;padding:10px 12px}',
|
|
614
|
+
'.dshw_priceField{display:grid;gap:4px;min-width:0}',
|
|
615
|
+
'.dshw_priceField>span{font-size:9.5px;color:var(--dsw-alias-label-secondary,var(--dsw-alias-label-tertiary,#667085))}',
|
|
616
|
+
'.dshw_priceField .dshw_input,.dshw_priceField .dshw_select{box-sizing:border-box;width:100%;height:28px}',
|
|
617
|
+
'.dshw_priceRules{display:grid;gap:6px;padding:0 12px 11px}',
|
|
618
|
+
'.dshw_priceRule{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:10px;align-items:center;padding:7px 9px;border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.15));border-radius:8px;background:var(--dsw-alias-bg-layer-2,var(--dsw-alias-bg-layer-1,rgba(127,127,127,.04)))}',
|
|
619
|
+
'.dshw_priceRule strong,.dshw_priceRule span{display:block;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
|
|
620
|
+
'.dshw_priceRule strong{font-size:11px}.dshw_priceRule span{font-size:9.5px;color:var(--dsw-alias-label-secondary,var(--dsw-alias-label-tertiary,#667085))}',
|
|
621
|
+
'@media(max-width:980px){.dshw_priceForm{grid-template-columns:repeat(2,minmax(0,1fr))}.dshw_priceForm>.dshw_btn{min-height:28px}}',
|
|
612
622
|
'.dshw_switch{position:relative;flex:none;width:34px;height:20px;display:inline-flex;cursor:pointer}',
|
|
613
623
|
'.dshw_switch>input{position:absolute;inset:0;opacity:0;margin:0;cursor:pointer}',
|
|
614
624
|
'.dshw_track{position:absolute;inset:0;border-radius:999px;background:var(--dsw-alias-border-l3,rgba(127,127,127,.3));transition:background-color .15s}',
|
|
@@ -643,6 +653,11 @@ window.__ModuleLoader__.load({
|
|
|
643
653
|
'.dshw_footRing{width:100%;box-sizing:border-box;display:flex;align-items:center;gap:8px;margin:2px 0 8px;padding:7px 9px;border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.18));border-radius:11px;background:var(--dsw-alias-bg-layer-1,var(--dsw-alias-bg-base,rgba(127,127,127,.04)));color:var(--dsw-alias-label-primary,inherit);font-size:12.5px;cursor:grab;user-select:none;touch-action:none;transition:border-color .15s ease,background-color .15s ease}',
|
|
644
654
|
'body[data-dsh-maid-atelier]:not([data-ds-dark-theme]) .dshw_footRing{--dsw-alias-label-primary:#f8f3e8;--dsw-alias-label-secondary:#d7def0;--dsw-alias-label-tertiary:#b5c1dd;--dsw-alias-border-l1:rgba(225,191,124,.62);--dsw-alias-border-l3:rgba(181,196,229,.34);--dsw-alias-interactive-bg-hover:rgba(41,61,111,.98);color:#f8f3e8;background:linear-gradient(145deg,rgba(34,55,106,.95),rgba(10,25,62,.97));border-color:rgba(225,191,124,.62);box-shadow:inset 0 0 0 2px rgba(5,15,45,.44),0 4px 11px rgba(1,7,24,.22)}',
|
|
645
655
|
'body[data-dsh-maid-atelier][data-ds-dark-theme] .dshw_footRing{--dsw-alias-label-primary:#f8f3e8;--dsw-alias-label-secondary:#d7def0;--dsw-alias-label-tertiary:#b5c1dd;--dsw-alias-border-l1:rgba(211,180,119,.52);--dsw-alias-border-l3:rgba(181,196,229,.34);--dsw-alias-interactive-bg-hover:rgba(41,61,111,.96);color:#f8f3e8;background:rgba(18,32,70,.92);border-color:rgba(211,180,119,.52);box-shadow:inset 0 1px rgba(255,255,255,.07),0 4px 12px rgba(0,0,0,.24)}',
|
|
656
|
+
'.dshw_footRing[data-dshw-peak-background="transparent"]:not(.dshw_footRingFloating){background:transparent;box-shadow:none}',
|
|
657
|
+
'.dshw_footRing[data-dshw-peak-background="transparent"]:not(.dshw_footRingFloating):hover{background:var(--dsw-alias-bg-layer-1,var(--dsw-alias-bg-base,rgba(127,127,127,.04)));box-shadow:none}',
|
|
658
|
+
'body[data-dsh-maid-atelier] .dshw_footRing[data-dshw-peak-background="transparent"]:not(.dshw_footRingFloating){background:transparent;box-shadow:none}',
|
|
659
|
+
'body[data-dsh-maid-atelier]:not([data-ds-dark-theme]) .dshw_footRing[data-dshw-peak-background="transparent"]:not(.dshw_footRingFloating):hover{background:linear-gradient(145deg,rgba(34,55,106,.95),rgba(10,25,62,.97));box-shadow:inset 0 0 0 2px rgba(5,15,45,.44),0 4px 11px rgba(1,7,24,.22)}',
|
|
660
|
+
'body[data-dsh-maid-atelier][data-ds-dark-theme] .dshw_footRing[data-dshw-peak-background="transparent"]:not(.dshw_footRingFloating):hover{background:rgba(18,32,70,.92);box-shadow:inset 0 1px rgba(255,255,255,.07),0 4px 12px rgba(0,0,0,.24)}',
|
|
646
661
|
'.dshw_footRing:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.1));border-color:var(--dsw-alias-border-l2,rgba(127,127,127,.28))}',
|
|
647
662
|
'.dshw_footRing.dshw_low{border-color:var(--dsw-alias-state-error-primary,#e5534b)}',
|
|
648
663
|
'.dshw_footRingRail{width:42px;height:42px;min-height:42px;margin:3px 0 6px;padding:0;justify-content:center;border-radius:50%;background:transparent;border:none;cursor:pointer}',
|
|
@@ -902,6 +917,15 @@ window.__ModuleLoader__.load({
|
|
|
902
917
|
}
|
|
903
918
|
}
|
|
904
919
|
|
|
920
|
+
function customCostsText(costs) {
|
|
921
|
+
if (!costs || typeof costs !== 'object') return ''
|
|
922
|
+
return Object.keys(costs).sort().filter(function (currency) {
|
|
923
|
+
return Number.isFinite(costs[currency])
|
|
924
|
+
}).map(function (currency) {
|
|
925
|
+
return fmtCurrency(costs[currency], currency)
|
|
926
|
+
}).join(' + ')
|
|
927
|
+
}
|
|
928
|
+
|
|
905
929
|
function selectBalanceInfo(balance) {
|
|
906
930
|
if (!balance || !Array.isArray(balance.balances) || balance.balances.length === 0) return null
|
|
907
931
|
var currency = typeof balance.currency === 'string' ? balance.currency.toUpperCase() : null
|
|
@@ -987,6 +1011,10 @@ window.__ModuleLoader__.load({
|
|
|
987
1011
|
return value === 'hidden' ? 'hidden' : 'standard'
|
|
988
1012
|
}
|
|
989
1013
|
|
|
1014
|
+
function normalizePeakBackground(value) {
|
|
1015
|
+
return value === 'solid' ? 'solid' : 'transparent'
|
|
1016
|
+
}
|
|
1017
|
+
|
|
990
1018
|
function readBalanceOnly() {
|
|
991
1019
|
try {
|
|
992
1020
|
var saved = compatibility.storage.getItem(CHIP_BALANCE_ONLY_KEY)
|
|
@@ -1390,7 +1418,8 @@ window.__ModuleLoader__.load({
|
|
|
1390
1418
|
|
|
1391
1419
|
function historyHeatValue(day, metric) {
|
|
1392
1420
|
if (!day) return 0
|
|
1393
|
-
if (metric === 'cost') return Number.isFinite(day.cost) ? day.cost : 0
|
|
1421
|
+
if (metric === 'cost') return (Number.isFinite(day.cost) ? day.cost : 0)
|
|
1422
|
+
+ (day.customCosts && Number.isFinite(day.customCosts.CNY) ? day.customCosts.CNY : 0)
|
|
1394
1423
|
return Number.isFinite(day.totalTokens) ? day.totalTokens : 0
|
|
1395
1424
|
}
|
|
1396
1425
|
|
|
@@ -1542,8 +1571,8 @@ window.__ModuleLoader__.load({
|
|
|
1542
1571
|
tabIndex: day.calls > 0 || isSelected || isToday ? 0 : -1,
|
|
1543
1572
|
className: 'dshw_historyCell dshw_historyCell' + level + (isToday ? ' dshw_historyCellToday' : '') + (isSelected ? ' dshw_historyCellSelected' : ''),
|
|
1544
1573
|
style: { gridColumn: String(Math.floor((index + firstWeekday) / 7) + 1), gridRow: String(weekday + 1) },
|
|
1545
|
-
title: day.date + ' · ' + fmtTokens(day.totalTokens) + ' token · ' + (day.cost === null ? '未定价' : historyMoney(day.cost)) + ' · ' + day.calls + ' 次调用',
|
|
1546
|
-
'aria-label': day.date + ',' + (metric === 'cost' ? (day.cost === null ? '
|
|
1574
|
+
title: day.date + ' · ' + fmtTokens(day.totalTokens) + ' token · 官方 ' + (day.cost === null ? '未定价' : historyMoney(day.cost)) + (customCostsText(day.customCosts) ? ' · 三方估 ' + customCostsText(day.customCosts) : '') + ' · ' + day.calls + ' 次调用',
|
|
1575
|
+
'aria-label': day.date + ',' + (metric === 'cost' ? ((day.cost === null ? '官方未定价' : historyMoney(day.cost)) + (customCostsText(day.customCosts) ? ',第三方估算 ' + customCostsText(day.customCosts) : '')) : fmtTokens(day.totalTokens) + ' token') + ',' + day.calls + ' 次调用',
|
|
1547
1576
|
'aria-selected': isSelected,
|
|
1548
1577
|
'aria-current': isToday ? 'date' : undefined,
|
|
1549
1578
|
onClick: function () { loadHistory(day.date) }
|
|
@@ -1560,7 +1589,7 @@ window.__ModuleLoader__.load({
|
|
|
1560
1589
|
: breakdown.map(function (row) {
|
|
1561
1590
|
return React.createElement('div', { key: row.provider + ':' + row.model, className: 'dshw_historyDetailRow' },
|
|
1562
1591
|
React.createElement('span', { className: 'dshw_historyDetailName', title: row.provider + ' · ' + row.model }, row.model),
|
|
1563
|
-
React.createElement('span', null, fmtTokens(row.totalTokens) + ' · ' + row.calls + ' 次' + (row.cost === null ? '' : ' · ' + historyMoney(row.cost))))
|
|
1592
|
+
React.createElement('span', null, fmtTokens(row.totalTokens) + ' · ' + row.calls + ' 次' + (row.cost === null ? '' : ' · ' + historyMoney(row.cost)) + (row.customCost ? ' · 估 ' + fmtCurrency(row.customCost.cost, row.customCost.currency) : '')))
|
|
1564
1593
|
})
|
|
1565
1594
|
body = React.createElement(React.Fragment, null,
|
|
1566
1595
|
React.createElement('div', { className: 'dshw_historySummary' },
|
|
@@ -1568,6 +1597,7 @@ window.__ModuleLoader__.load({
|
|
|
1568
1597
|
React.createElement('div', null, React.createElement('span', null, '今天'), React.createElement('strong', null, fmtTokens(today.totalTokens))),
|
|
1569
1598
|
React.createElement('div', null, React.createElement('span', null, '本月'), React.createElement('strong', null, fmtTokens(month.totalTokens))),
|
|
1570
1599
|
React.createElement('div', null, React.createElement('span', null, '缓存命中'), React.createElement('strong', null, history.summary.cacheHitRate === null || history.summary.cacheHitRate === undefined ? '--' : history.summary.cacheHitRate + '%'))),
|
|
1600
|
+
customCostsText(total.customCosts) ? React.createElement('div', { className: 'dshw_historyLegend' }, '第三方按当前自定义价估算:' + customCostsText(total.customCosts)) : null,
|
|
1571
1601
|
React.createElement('div', { className: 'dshw_historyMetricToggle', role: 'group', 'aria-label': '历史热力图指标' },
|
|
1572
1602
|
React.createElement('button', { type: 'button', className: metric === 'tokens' ? 'dshw_historyMetricActive' : '', 'aria-pressed': metric === 'tokens', onClick: function () { setMetric('tokens') } }, 'Token'),
|
|
1573
1603
|
React.createElement('button', { type: 'button', className: metric === 'cost' ? 'dshw_historyMetricActive' : '', 'aria-pressed': metric === 'cost', onClick: function () { setMetric('cost') } }, '费用')),
|
|
@@ -1859,6 +1889,12 @@ window.__ModuleLoader__.load({
|
|
|
1859
1889
|
var [nameDraft, setNameDraft] = React.useState('')
|
|
1860
1890
|
var [keyDraft, setKeyDraft] = React.useState('')
|
|
1861
1891
|
var [switchingId, setSwitchingId] = React.useState(null)
|
|
1892
|
+
var [priceRules, setPriceRules] = React.useState([])
|
|
1893
|
+
var [knownPriceRoutes, setKnownPriceRoutes] = React.useState([])
|
|
1894
|
+
var [priceNotice, setPriceNotice] = React.useState(null)
|
|
1895
|
+
var [priceDraft, setPriceDraft] = React.useState({
|
|
1896
|
+
provider: '', model: '', currency: 'CNY', input: '', cacheRead: '', cacheWrite: '', output: ''
|
|
1897
|
+
})
|
|
1862
1898
|
var [visibility, setVisibility] = React.useState(function () {
|
|
1863
1899
|
try {
|
|
1864
1900
|
var saved = compatibility.storage.getItem(DATA_VISIBILITY_KEY)
|
|
@@ -1896,6 +1932,9 @@ window.__ModuleLoader__.load({
|
|
|
1896
1932
|
var [peakOrient, setPeakOrient] = React.useState(function () {
|
|
1897
1933
|
try { return compatibility.storage.getItem(PEAK_ORIENT_KEY) || 'horizontal' } catch (e) { return 'horizontal' }
|
|
1898
1934
|
})
|
|
1935
|
+
var [peakBackground, setPeakBackground] = React.useState(function () {
|
|
1936
|
+
try { return normalizePeakBackground(compatibility.storage.getItem(PEAK_BACKGROUND_KEY)) } catch (e) { return 'transparent' }
|
|
1937
|
+
})
|
|
1899
1938
|
var [peakRecharge, setPeakRecharge] = React.useState(function () {
|
|
1900
1939
|
try { return compatibility.storage.getItem(PEAK_RECHARGE_KEY) !== 'false' } catch (e) { return true }
|
|
1901
1940
|
})
|
|
@@ -1929,6 +1968,7 @@ window.__ModuleLoader__.load({
|
|
|
1929
1968
|
try { setRingEnabled(compatibility.storage.getItem(PEAK_RING_KEY) !== 'false') } catch (e) { /* ignore */ }
|
|
1930
1969
|
try { setPeakNotifyEnabled(compatibility.storage.getItem(PEAK_NOTIFY_KEY) === 'true') } catch (e) { /* ignore */ }
|
|
1931
1970
|
try { setPeakOrient(compatibility.storage.getItem(PEAK_ORIENT_KEY) || 'horizontal') } catch (e) { /* ignore */ }
|
|
1971
|
+
try { setPeakBackground(normalizePeakBackground(compatibility.storage.getItem(PEAK_BACKGROUND_KEY))) } catch (e) { /* ignore */ }
|
|
1932
1972
|
try { setPeakRecharge(compatibility.storage.getItem(PEAK_RECHARGE_KEY) !== 'false') } catch (e) { /* ignore */ }
|
|
1933
1973
|
try {
|
|
1934
1974
|
var savedPeakScale = Number.parseFloat(compatibility.storage.getItem(PEAK_SCALE_KEY))
|
|
@@ -1960,6 +2000,9 @@ window.__ModuleLoader__.load({
|
|
|
1960
2000
|
fetch('/api/wallet/snapshot').then(function (resp) { return resp.json() }).then(function (json) {
|
|
1961
2001
|
if (!stopped && json && json.ok) {
|
|
1962
2002
|
setSnapshot(json)
|
|
2003
|
+
var providerState = json.providers || {}
|
|
2004
|
+
setPriceRules(Array.isArray(providerState.customPrices) ? providerState.customPrices : [])
|
|
2005
|
+
setKnownPriceRoutes(Array.isArray(providerState.knownRoutes) ? providerState.knownRoutes : [])
|
|
1963
2006
|
setThresholdDraft(json.threshold !== undefined && json.threshold !== null ? json.threshold.toFixed(2) : '')
|
|
1964
2007
|
}
|
|
1965
2008
|
}).catch(function () { /* ignore */ })
|
|
@@ -2095,6 +2138,14 @@ window.__ModuleLoader__.load({
|
|
|
2095
2138
|
compatibility.dispatch(PEAK_RING_EVENT)
|
|
2096
2139
|
}
|
|
2097
2140
|
|
|
2141
|
+
function persistPeakBackground(next) {
|
|
2142
|
+
next = normalizePeakBackground(next)
|
|
2143
|
+
setPeakBackground(next)
|
|
2144
|
+
try { compatibility.storage.setItem(PEAK_BACKGROUND_KEY, next) } catch (e) { /* ignore */ }
|
|
2145
|
+
compatibility.dispatch(SETTINGS_EVENT)
|
|
2146
|
+
compatibility.dispatch(PEAK_RING_EVENT)
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2098
2149
|
function persistPeakRecharge(next) {
|
|
2099
2150
|
setPeakRecharge(next)
|
|
2100
2151
|
try { compatibility.storage.setItem(PEAK_RECHARGE_KEY, String(next)) } catch (e) { /* ignore */ }
|
|
@@ -2138,6 +2189,85 @@ window.__ModuleLoader__.load({
|
|
|
2138
2189
|
}).catch(function () { setThresholdNotice('\u4fdd\u5b58\u5931\u8d25') })
|
|
2139
2190
|
}
|
|
2140
2191
|
|
|
2192
|
+
function applyCustomPriceResponse(json) {
|
|
2193
|
+
if (!json || json.ok !== true) return false
|
|
2194
|
+
setPriceRules(Array.isArray(json.rules) ? json.rules : [])
|
|
2195
|
+
setKnownPriceRoutes(Array.isArray(json.knownRoutes) ? json.knownRoutes : [])
|
|
2196
|
+
return true
|
|
2197
|
+
}
|
|
2198
|
+
|
|
2199
|
+
function saveCustomPrice() {
|
|
2200
|
+
if (usageLocked) { setPriceNotice('用量账本已锁定,无法保存'); return }
|
|
2201
|
+
var numeric = ['input', 'cacheRead', 'cacheWrite', 'output'].reduce(function (out, key) {
|
|
2202
|
+
var value = Number.parseFloat(priceDraft[key])
|
|
2203
|
+
out[key] = Number.isFinite(value) && value >= 0 ? value : null
|
|
2204
|
+
return out
|
|
2205
|
+
}, {})
|
|
2206
|
+
if (!priceDraft.provider.trim() || !priceDraft.model.trim() || Object.values(numeric).some(function (value) { return value === null })) {
|
|
2207
|
+
setPriceNotice('请填写 Provider、模型及四项非负价格')
|
|
2208
|
+
return
|
|
2209
|
+
}
|
|
2210
|
+
setPriceNotice('正在保存…')
|
|
2211
|
+
fetch('/api/wallet/custom-prices', {
|
|
2212
|
+
method: 'POST',
|
|
2213
|
+
headers: { 'content-type': 'application/json' },
|
|
2214
|
+
body: JSON.stringify({ rule: {
|
|
2215
|
+
provider: priceDraft.provider.trim(),
|
|
2216
|
+
model: priceDraft.model.trim(),
|
|
2217
|
+
currency: priceDraft.currency,
|
|
2218
|
+
input: numeric.input,
|
|
2219
|
+
cacheRead: numeric.cacheRead,
|
|
2220
|
+
cacheWrite: numeric.cacheWrite,
|
|
2221
|
+
output: numeric.output
|
|
2222
|
+
} })
|
|
2223
|
+
}).then(function (resp) { return resp.json().then(function (json) { return { ok: resp.ok, json: json } }) }).then(function (result) {
|
|
2224
|
+
if (!result.ok || !applyCustomPriceResponse(result.json)) throw new Error(result.json && result.json.error ? result.json.error : 'save-failed')
|
|
2225
|
+
setPriceNotice('已保存;本会话与历史账本按当前自定义价重新估算')
|
|
2226
|
+
}).catch(function (error) {
|
|
2227
|
+
setPriceNotice(error && error.message === 'official-provider-not-allowed' ? '官方计费 Provider 不能设置第三方价格' : '保存失败,请检查填写内容')
|
|
2228
|
+
})
|
|
2229
|
+
}
|
|
2230
|
+
|
|
2231
|
+
function removeCustomPrice(rule) {
|
|
2232
|
+
if (usageLocked) return
|
|
2233
|
+
setPriceNotice('正在删除…')
|
|
2234
|
+
fetch('/api/wallet/custom-prices', {
|
|
2235
|
+
method: 'DELETE',
|
|
2236
|
+
headers: { 'content-type': 'application/json' },
|
|
2237
|
+
body: JSON.stringify({ provider: rule.provider, model: rule.model })
|
|
2238
|
+
}).then(function (resp) { return resp.json().then(function (json) { return { ok: resp.ok, json: json } }) }).then(function (result) {
|
|
2239
|
+
if (!result.ok || !applyCustomPriceResponse(result.json)) throw new Error('delete-failed')
|
|
2240
|
+
setPriceNotice('已删除价格规则')
|
|
2241
|
+
}).catch(function () { setPriceNotice('删除失败') })
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
function editCustomPrice(rule) {
|
|
2245
|
+
setPriceDraft({
|
|
2246
|
+
provider: rule.provider,
|
|
2247
|
+
model: rule.model,
|
|
2248
|
+
currency: rule.currency,
|
|
2249
|
+
input: String(rule.input),
|
|
2250
|
+
cacheRead: String(rule.cacheRead),
|
|
2251
|
+
cacheWrite: String(rule.cacheWrite),
|
|
2252
|
+
output: String(rule.output)
|
|
2253
|
+
})
|
|
2254
|
+
setPriceNotice('已载入,可修改后保存')
|
|
2255
|
+
}
|
|
2256
|
+
|
|
2257
|
+
function useCurrentRouteForPrice() {
|
|
2258
|
+
if (!settingsModelSelection || typeof settingsModelSelection.provider !== 'string' || typeof settingsModelSelection.model !== 'string') {
|
|
2259
|
+
setPriceNotice('当前会话尚未选择可识别的模型')
|
|
2260
|
+
return
|
|
2261
|
+
}
|
|
2262
|
+
setPriceDraft(function (current) {
|
|
2263
|
+
return Object.assign({}, current, {
|
|
2264
|
+
provider: settingsModelSelection.provider,
|
|
2265
|
+
model: settingsModelSelection.model
|
|
2266
|
+
})
|
|
2267
|
+
})
|
|
2268
|
+
setPriceNotice('已填入当前会话的 Provider 与模型')
|
|
2269
|
+
}
|
|
2270
|
+
|
|
2141
2271
|
function reloadAccounts() {
|
|
2142
2272
|
fetch('/api/wallet/accounts').then(function (resp) { return resp.json() }).then(function (json) {
|
|
2143
2273
|
if (json && json.ok) {
|
|
@@ -2488,6 +2618,18 @@ window.__ModuleLoader__.load({
|
|
|
2488
2618
|
},
|
|
2489
2619
|
React.createElement('option', { value: 'horizontal' }, '横向排列'),
|
|
2490
2620
|
React.createElement('option', { value: 'vertical' }, '纵向排列'))),
|
|
2621
|
+
React.createElement('div', { className: 'dshw_settingChoice' },
|
|
2622
|
+
React.createElement('span', { className: 'dshw_settingChoiceCopy' },
|
|
2623
|
+
React.createElement('strong', null, '时钟背景'),
|
|
2624
|
+
React.createElement('span', null, peakBackground === 'solid' ? '侧边栏常态保持实色' : '侧边栏透明,悬停实色;悬浮保持实色')),
|
|
2625
|
+
React.createElement('select', {
|
|
2626
|
+
className: 'dshw_select',
|
|
2627
|
+
'aria-label': '峰谷时钟背景',
|
|
2628
|
+
value: peakBackground,
|
|
2629
|
+
onChange: function (event) { persistPeakBackground(event.target.value) }
|
|
2630
|
+
},
|
|
2631
|
+
React.createElement('option', { value: 'transparent' }, '透明(悬停实色)'),
|
|
2632
|
+
React.createElement('option', { value: 'solid' }, '实色'))),
|
|
2491
2633
|
React.createElement('div', { className: 'dshw_settingChoice' },
|
|
2492
2634
|
React.createElement('span', { className: 'dshw_settingChoiceCopy' },
|
|
2493
2635
|
React.createElement('strong', null, '时钟大小'),
|
|
@@ -2563,6 +2705,65 @@ window.__ModuleLoader__.load({
|
|
|
2563
2705
|
rows.push(React.createElement('div', { key: 'pr-card', className: 'dshw_setCard', style: { display: 'block' } }, providerRows))
|
|
2564
2706
|
}
|
|
2565
2707
|
|
|
2708
|
+
// —— 第三方 API 自定义价格(Issue #36) ——
|
|
2709
|
+
rows.push(React.createElement('div', { key: 'cp-head', className: 'dshw_accountHeader', style: { marginTop: '8px' } },
|
|
2710
|
+
React.createElement('span', null,
|
|
2711
|
+
React.createElement('span', { className: 'dshw_title' }, '第三方 API 自定义价格'),
|
|
2712
|
+
React.createElement('span', { className: 'dshw_muted', style: { display: 'block', marginTop: '2px', fontSize: '10px' } }, '每 100 万 Token;仅作本机估算,不代表第三方账单')),
|
|
2713
|
+
React.createElement('button', {
|
|
2714
|
+
type: 'button', className: 'dshw_btn',
|
|
2715
|
+
disabled: settingsProviderMode.kind !== 'third',
|
|
2716
|
+
title: settingsProviderMode.kind === 'third' ? '填入当前会话选择的 Provider 与模型' : '请先切换到第三方模型',
|
|
2717
|
+
onClick: useCurrentRouteForPrice
|
|
2718
|
+
}, '使用当前模型')))
|
|
2719
|
+
var providerOptions = Array.from(new Set(knownPriceRoutes.map(function (route) { return route && route.provider }).filter(Boolean)))
|
|
2720
|
+
var modelOptions = Array.from(new Set(knownPriceRoutes.filter(function (route) {
|
|
2721
|
+
return route && (!priceDraft.provider || route.provider === priceDraft.provider)
|
|
2722
|
+
}).map(function (route) { return route.model }).filter(Boolean)))
|
|
2723
|
+
function updatePriceDraft(key, value) {
|
|
2724
|
+
setPriceDraft(function (current) { var next = Object.assign({}, current); next[key] = key === 'currency' ? String(value).toUpperCase().slice(0, 3) : value; return next })
|
|
2725
|
+
}
|
|
2726
|
+
function priceField(key, label, type, options) {
|
|
2727
|
+
return React.createElement('label', { key: key, className: 'dshw_priceField' },
|
|
2728
|
+
React.createElement('span', null, label),
|
|
2729
|
+
type === 'select'
|
|
2730
|
+
? React.createElement('select', { className: 'dshw_select', value: priceDraft[key], 'aria-label': label, onChange: function (event) { updatePriceDraft(key, event.target.value) } },
|
|
2731
|
+
options.map(function (option) { return React.createElement('option', { key: option, value: option }, option) }))
|
|
2732
|
+
: React.createElement('input', {
|
|
2733
|
+
className: 'dshw_input', type: type, min: type === 'number' ? '0' : undefined,
|
|
2734
|
+
max: type === 'number' ? '1000000' : undefined, step: type === 'number' ? '0.000001' : undefined,
|
|
2735
|
+
list: key === 'provider' ? 'dshw-price-providers' : key === 'model' ? 'dshw-price-models' : undefined,
|
|
2736
|
+
value: priceDraft[key], 'aria-label': label,
|
|
2737
|
+
onChange: function (event) { updatePriceDraft(key, event.target.value) }
|
|
2738
|
+
}))
|
|
2739
|
+
}
|
|
2740
|
+
var priceForm = React.createElement('div', { className: 'dshw_priceForm' },
|
|
2741
|
+
priceField('provider', 'Provider ID', 'text'),
|
|
2742
|
+
priceField('model', '模型 ID', 'text'),
|
|
2743
|
+
priceField('currency', '币种', 'text'),
|
|
2744
|
+
priceField('input', '输入', 'number'),
|
|
2745
|
+
priceField('cacheRead', '缓存读', 'number'),
|
|
2746
|
+
priceField('cacheWrite', '缓存写', 'number'),
|
|
2747
|
+
priceField('output', '输出', 'number'),
|
|
2748
|
+
React.createElement('button', { type: 'button', className: 'dshw_btn dshw_btnPrimary', disabled: usageLocked, onClick: saveCustomPrice }, '保存'),
|
|
2749
|
+
React.createElement('datalist', { id: 'dshw-price-providers' }, providerOptions.map(function (provider) { return React.createElement('option', { key: provider, value: provider }) })),
|
|
2750
|
+
React.createElement('datalist', { id: 'dshw-price-models' }, modelOptions.map(function (model) { return React.createElement('option', { key: model, value: model }) })))
|
|
2751
|
+
var priceRuleRows = priceRules.map(function (rule) {
|
|
2752
|
+
return React.createElement('div', { key: rule.provider + ':' + rule.model, className: 'dshw_priceRule' },
|
|
2753
|
+
React.createElement('span', null,
|
|
2754
|
+
React.createElement('strong', { title: rule.provider + ' · ' + rule.model }, rule.provider + ' · ' + rule.model),
|
|
2755
|
+
React.createElement('span', null, rule.currency + '/1M · 入 ' + rule.input + ' · 缓读 ' + rule.cacheRead + ' · 缓写 ' + rule.cacheWrite + ' · 出 ' + rule.output)),
|
|
2756
|
+
React.createElement('span', { className: 'dshw_settingsInline' },
|
|
2757
|
+
React.createElement('button', { type: 'button', className: 'dshw_btn', onClick: function () { editCustomPrice(rule) } }, '编辑'),
|
|
2758
|
+
React.createElement('button', { type: 'button', className: 'dshw_btn', disabled: usageLocked, onClick: function () { removeCustomPrice(rule) } }, '删除')))
|
|
2759
|
+
})
|
|
2760
|
+
rows.push(React.createElement('div', { key: 'cp-card', className: 'dshw_setCard wide', style: { display: 'block' } },
|
|
2761
|
+
priceForm,
|
|
2762
|
+
priceNotice ? React.createElement('div', { className: 'dshw_muted', role: 'status', style: { padding: '0 12px 8px' } }, priceNotice) : null,
|
|
2763
|
+
priceRuleRows.length > 0
|
|
2764
|
+
? React.createElement('div', { className: 'dshw_priceRules' }, priceRuleRows)
|
|
2765
|
+
: React.createElement('div', { className: 'dshw_muted', style: { padding: '0 12px 11px' } }, '暂无规则;填写后即可看到第三方费用估算')))
|
|
2766
|
+
|
|
2566
2767
|
rows.push(React.createElement(PlanUsagePanel, { key: 'plans', compact: false }))
|
|
2567
2768
|
rows.push(React.createElement(UsageHistoryPanel, { key: 'history', sessionId: null, alwaysOpen: true }))
|
|
2568
2769
|
|
|
@@ -2833,6 +3034,9 @@ window.__ModuleLoader__.load({
|
|
|
2833
3034
|
var [orient, setOrient] = React.useState(function () {
|
|
2834
3035
|
try { return compatibility.storage.getItem(PEAK_ORIENT_KEY) || 'horizontal' } catch (e) { return 'horizontal' }
|
|
2835
3036
|
})
|
|
3037
|
+
var [peakBackground, setPeakBackground] = React.useState(function () {
|
|
3038
|
+
try { return normalizePeakBackground(compatibility.storage.getItem(PEAK_BACKGROUND_KEY)) } catch (e) { return 'transparent' }
|
|
3039
|
+
})
|
|
2836
3040
|
var [showRecharge, setShowRecharge] = React.useState(function () {
|
|
2837
3041
|
try { return compatibility.storage.getItem(PEAK_RECHARGE_KEY) !== 'false' } catch (e) { return true }
|
|
2838
3042
|
})
|
|
@@ -2893,6 +3097,7 @@ window.__ModuleLoader__.load({
|
|
|
2893
3097
|
try {
|
|
2894
3098
|
setShown(compatibility.storage.getItem(PEAK_RING_KEY) !== 'false')
|
|
2895
3099
|
setOrient(compatibility.storage.getItem(PEAK_ORIENT_KEY) || 'horizontal')
|
|
3100
|
+
setPeakBackground(normalizePeakBackground(compatibility.storage.getItem(PEAK_BACKGROUND_KEY)))
|
|
2896
3101
|
setShowRecharge(compatibility.storage.getItem(PEAK_RECHARGE_KEY) !== 'false')
|
|
2897
3102
|
var val = Number.parseFloat(compatibility.storage.getItem(PEAK_SCALE_KEY))
|
|
2898
3103
|
setPeakScale(Number.isFinite(val) ? Math.min(1.2, Math.max(1.0, val)) : 1.0)
|
|
@@ -3132,6 +3337,12 @@ window.__ModuleLoader__.load({
|
|
|
3132
3337
|
try { compatibility.storage.setItem(PEAK_ORIENT_KEY, val) } catch (e) { /* ignore */ }
|
|
3133
3338
|
announcePrefs()
|
|
3134
3339
|
}
|
|
3340
|
+
function updateBackground(val) {
|
|
3341
|
+
val = normalizePeakBackground(val)
|
|
3342
|
+
setPeakBackground(val)
|
|
3343
|
+
try { compatibility.storage.setItem(PEAK_BACKGROUND_KEY, val) } catch (e) { /* ignore */ }
|
|
3344
|
+
announcePrefs()
|
|
3345
|
+
}
|
|
3135
3346
|
function updateScale(val) {
|
|
3136
3347
|
val = Math.min(1.2, Math.max(1.0, Math.round(val * 20) / 20))
|
|
3137
3348
|
setPeakScale(val)
|
|
@@ -3284,6 +3495,7 @@ window.__ModuleLoader__.load({
|
|
|
3284
3495
|
var cardElement = React.createElement('div', {
|
|
3285
3496
|
ref: cardNodeRef,
|
|
3286
3497
|
className: containerClasses.join(' '),
|
|
3498
|
+
'data-dshw-peak-background': peakBackground,
|
|
3287
3499
|
style: Object.keys(cardStyle).length > 0 ? cardStyle : undefined,
|
|
3288
3500
|
title: state.tip + ' · 余额 ' + balText + ' · ' + costLabel + ' ' + costText + (isRail ? ' · 点击前往官方充值' : ' · 点击展开专属控制面板 / 按住拖拽'),
|
|
3289
3501
|
'aria-label': state.ariaText + ',余额 ' + balText + ',' + costLabel + ' ' + costText,
|
|
@@ -3406,6 +3618,17 @@ window.__ModuleLoader__.load({
|
|
|
3406
3618
|
},
|
|
3407
3619
|
React.createElement('option', { value: 'horizontal' }, '横向排列'),
|
|
3408
3620
|
React.createElement('option', { value: 'vertical' }, '纵向排列'))),
|
|
3621
|
+
React.createElement('div', { className: 'dshw_peakRow' },
|
|
3622
|
+
React.createElement('span', { className: 'dshw_muted' }, '时钟背景'),
|
|
3623
|
+
React.createElement('select', {
|
|
3624
|
+
className: 'dshw_select',
|
|
3625
|
+
style: { height: '24px', fontSize: '11px' },
|
|
3626
|
+
'aria-label': '控制面板时钟背景',
|
|
3627
|
+
value: peakBackground,
|
|
3628
|
+
onChange: function (e) { updateBackground(e.target.value) }
|
|
3629
|
+
},
|
|
3630
|
+
React.createElement('option', { value: 'transparent' }, '透明(悬停实色)'),
|
|
3631
|
+
React.createElement('option', { value: 'solid' }, '实色'))),
|
|
3409
3632
|
React.createElement('div', { className: 'dshw_peakRow' },
|
|
3410
3633
|
React.createElement('span', { className: 'dshw_muted' }, '时钟大小 (' + Math.round(peakScale * 100) + '%)'),
|
|
3411
3634
|
React.createElement('span', { className: 'dshw_scaleControl' },
|
|
@@ -4485,6 +4708,13 @@ window.__ModuleLoader__.load({
|
|
|
4485
4708
|
var third = session.third || {}
|
|
4486
4709
|
var officialTokens = official.tokens ? totalTokens(official.tokens) : 0
|
|
4487
4710
|
var thirdTokens = third.tokens ? totalTokens(third.tokens) : 0
|
|
4711
|
+
var thirdCustomText = customCostsText(third.customCosts)
|
|
4712
|
+
var activeThirdRoute = activeProviderMode.kind === 'third' && Array.isArray(third.routes)
|
|
4713
|
+
? third.routes.find(function (route) { return route.provider === activeProviderMode.provider && route.model === activeProviderMode.model })
|
|
4714
|
+
: null
|
|
4715
|
+
var activeThirdCostText = activeThirdRoute && activeThirdRoute.customCost
|
|
4716
|
+
? fmtCurrency(activeThirdRoute.customCost.cost, activeThirdRoute.customCost.currency)
|
|
4717
|
+
: ''
|
|
4488
4718
|
var activePlanSource = activeProviderMode.kind === 'zai'
|
|
4489
4719
|
? planSourceForProvider(snapshot, activeProviderMode.provider)
|
|
4490
4720
|
: null
|
|
@@ -4533,10 +4763,10 @@ window.__ModuleLoader__.load({
|
|
|
4533
4763
|
React.createElement('span', { className: 'dshw_homePrimaryLabel' }, '5h 剩 '),
|
|
4534
4764
|
React.createElement('span', { className: 'dshw_homePrimaryValue' }, planTokenRemaining)))
|
|
4535
4765
|
} else if (activeProviderMode.kind === 'third') {
|
|
4536
|
-
if (chipVertical) chipTextParts.push(verticalMetric('third', '本场', fmtTokens(thirdTokens)))
|
|
4766
|
+
if (chipVertical) chipTextParts.push(verticalMetric('third', activeThirdCostText ? '本估' : '本场', activeThirdCostText || fmtTokens(thirdTokens)))
|
|
4537
4767
|
else chipTextParts.push(React.createElement('span', { key: 'third', className: 'dshw_homePrimary' },
|
|
4538
|
-
React.createElement('span', { className: 'dshw_homePrimaryLabel' }, '本场 '),
|
|
4539
|
-
React.createElement('span', { className: 'dshw_homePrimaryValue' }, fmtTokens(thirdTokens))))
|
|
4768
|
+
React.createElement('span', { className: 'dshw_homePrimaryLabel' }, activeThirdCostText ? '本估 ' : '本场 '),
|
|
4769
|
+
React.createElement('span', { className: 'dshw_homePrimaryValue' }, activeThirdCostText || fmtTokens(thirdTokens))))
|
|
4540
4770
|
} else {
|
|
4541
4771
|
if (chipVertical) chipTextParts.push(verticalMetric('bal', '余额', balanceText))
|
|
4542
4772
|
else chipTextParts.push(React.createElement('span', { key: 'bal', className: 'dshw_balanceText dshw_homePrimary' },
|
|
@@ -4561,10 +4791,12 @@ window.__ModuleLoader__.load({
|
|
|
4561
4791
|
if (chipVertical) {
|
|
4562
4792
|
chipTextParts.push(verticalMetric('provider', '模型', thirdProviderName))
|
|
4563
4793
|
chipTextParts.push(verticalMetric('third', '本场', fmtTokens(thirdTokens)))
|
|
4794
|
+
if (activeThirdCostText) chipTextParts.push(verticalMetric('third-cost', '估算', activeThirdCostText))
|
|
4564
4795
|
} else {
|
|
4565
4796
|
chipTextParts.push(React.createElement('span', { key: 'provider', className: 'dshw_homePrimary' },
|
|
4566
4797
|
React.createElement('span', { className: 'dshw_homePrimaryValue' }, thirdProviderName)))
|
|
4567
4798
|
chipTextParts.push(React.createElement('span', { key: 'third' }, '本场 ' + fmtTokens(thirdTokens)))
|
|
4799
|
+
if (activeThirdCostText) chipTextParts.push(React.createElement('span', { key: 'third-cost' }, '估 ' + activeThirdCostText))
|
|
4568
4800
|
}
|
|
4569
4801
|
} else if (chipVertical) {
|
|
4570
4802
|
if (showDeepSeek) {
|
|
@@ -4682,6 +4914,11 @@ window.__ModuleLoader__.load({
|
|
|
4682
4914
|
thirdRows.push(React.createElement('div', { key: 't-t', className: 'dshw_row' },
|
|
4683
4915
|
React.createElement('span', { className: 'dshw_muted' }, '\u7b2c\u4e09\u65b9 token'),
|
|
4684
4916
|
React.createElement('span', null, '\u8f93\u5165 ' + fmtTokens(third.tokens && third.tokens.input) + ' \u00b7 \u7f13\u5b58\u8bfb ' + fmtTokens(third.tokens && third.tokens.cacheRead) + ' \u00b7 \u8f93\u51fa ' + fmtTokens(third.tokens && third.tokens.output))))
|
|
4917
|
+
if (thirdCustomText) {
|
|
4918
|
+
thirdRows.push(React.createElement('div', { key: 't-c', className: 'dshw_row' },
|
|
4919
|
+
React.createElement('span', { className: 'dshw_muted' }, '自定义估算'),
|
|
4920
|
+
React.createElement('span', { title: '按当前自定义价格计算,不代表第三方账单' }, thirdCustomText)))
|
|
4921
|
+
}
|
|
4685
4922
|
|
|
4686
4923
|
var thresholdInput = React.createElement('input', {
|
|
4687
4924
|
className: 'dshw_input',
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "deepseek-harness-wallet",
|
|
3
|
-
"description": "Local-first DeepSeek Harness control center for balance monitoring, usage accounting, a 365-day local token ledger, Z.ai Coding Plan
|
|
4
|
-
"version": "0.3.
|
|
3
|
+
"description": "Local-first DeepSeek Harness control center for balance monitoring, usage accounting, custom third-party API pricing, a 365-day local token ledger, Z.ai Coding Plan quotas, official pricing sync, encrypted multi-account switching, reminders, recharge, and host-gated session controls.",
|
|
4
|
+
"version": "0.3.8",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"exports": {
|
|
@@ -56,7 +56,6 @@
|
|
|
56
56
|
"client": {
|
|
57
57
|
"platform": "web",
|
|
58
58
|
"inject": [
|
|
59
|
-
"@deepseek-ai/dsh-client-runtime",
|
|
60
59
|
"@deepseek-ai/dsh-client-ui-slots",
|
|
61
60
|
"@deepseek-ai/dsh-client-ui-conversation",
|
|
62
61
|
"@deepseek-ai/dsh-client-ui-model-selection"
|