@rayadesu/dsh-client-ui-billing 0.2.2 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +2 -2
- package/README.md +8 -4
- package/README.zh.md +8 -4
- package/lib/client.js +255 -60
- package/lib/types/client/BalanceBadge.d.ts +11 -2
- package/lib/types/client/TurnCostAction.d.ts +28 -0
- package/lib/types/client/index.d.ts +1 -0
- package/lib/types/client/locales.d.ts +4 -0
- package/package.json +3 -3
package/README.i18n.yaml
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# last confirmed-consistent state. Both languages carry equal authority; after
|
|
3
3
|
# editing either side, bring the other along and re-record both hashes with:
|
|
4
4
|
# git hash-object README.md README.zh.md
|
|
5
|
-
README.md:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: b4ac01bab9f32a7bdad2dbd1f86f2f4a53f25af6
|
|
6
|
+
README.zh.md: d7719cf98df1cf5b8c8189c99901019a37f04e45
|
package/README.md
CHANGED
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
English | [中文](README.zh.md)
|
|
4
4
|
|
|
5
|
-
Web billing feature owner: contributes one entry to `conversation.session.header.utilities` that mounts its own `billing` Remote, reads the DeepSeek account balance, this session's billed spend,
|
|
5
|
+
Web billing feature owner: contributes one entry to `conversation.session.header.utilities` that mounts its own `billing` Remote, reads the DeepSeek account balance, this session's billed spend, today's spend across every session, today's per-session ranking, and one turn's cost through `billing/getBalance`, `billing/getSessionSpend`, `billing/getTodaySpend`, `billing/getTodaySessionsSpend`, and `billing/getTurnSpend`, and renders them as a top-right label box. The host half of the capability lives in [`dsh-llm-billing`](../llm-billing/README.md), which owns the `/user/balance` transport, the peak/off-peak pricing table, and the `billing` Remote namespace; this package mounts that Remote and renders what it returns.
|
|
6
6
|
|
|
7
|
-
The trigger shows two lines — the remaining balance and this conversation's billed spend ("本轮对话花费 ¥X") — and renders nothing while the first fetch is in flight. Clicking it opens a label box with the remaining amount, this session's billed spend (本会话花费, priced per message at the official peak/off-peak rate — peak windows apply weekdays Mon–Fri only, weekends are off-peak) with today's all-session spend beside it (今日共花费), one priced row per model with the cache-hit / cache-miss-input / output cost breakdown ("缓存命中 ¥X · 未命中输入 ¥Y · 输出 ¥Z"), a manual refresh action, and a spend disclaimer. Refreshing keeps the last values visible rather than blanking them, and a refresh failure retains the last good value. A session or day without priced usage shows a "no usage recorded" word rather than a fabricated figure. A failure — no API key configured, a rejected credential, a transport error — renders a muted "Balance unavailable" word whose tooltip carries the Remote's own error message.
|
|
7
|
+
The trigger shows two lines — the remaining balance and this conversation's billed spend ("本轮对话花费 ¥X") — and renders nothing while the first fetch is in flight. Clicking it opens a label box with the remaining amount, this session's billed spend (本会话花费, priced per message at the official peak/off-peak rate — peak windows apply weekdays Mon–Fri only, weekends are off-peak) with today's all-session spend beside it (今日共花费), one priced row per model with the cache-hit / cache-miss-input / output cost breakdown ("缓存命中 ¥X · 未命中输入 ¥Y · 输出 ¥Z"), a manual refresh action, and a spend disclaimer. Below the model rows the panel ends with the **today session-spend ranking** (`今日会话花费`): sessions sorted by today's cost, highest first, each row carrying the session's durable title (renames sync automatically) and its amount; at most `SESSION_RANKING_LIMIT` (10) rows render, with a "…还有 N 个会话" hint for the rest. Refreshing keeps the last values visible rather than blanking them, and a refresh failure retains the last good value. A session or day without priced usage shows a "no usage recorded" word rather than a fabricated figure. A failure — no API key configured, a rejected credential, a transport error — renders a muted "Balance unavailable" word whose tooltip carries the Remote's own error message.
|
|
8
|
+
|
|
9
|
+
The package also contributes the **turn-cost entry** (`billing-turn-cost`, order 20) to ui-chat's `conversation.chat.assistant-actions` list slot — the same strip `ui-message-feedback` uses, so entries coexist. For each completed turn's closing assistant message, the entry reads `billing/getTurnSpend` (memoized per session + message, since settled turns never change) and renders a muted `本轮花费:¥X` label in the actions row, next to the turn's usage card. Turns that priced to zero (no DeepSeek usage) and failed loads render nothing, so a Remote outage never clutters the row.
|
|
8
10
|
|
|
9
11
|
The badge is account-level even though the slot is session-scoped: the header utilities row is simply the one always-visible title-bar seat the shell offers, and the balance it shows does not vary by session — but the spend lines do, because they price the current session's own token usage through `billing/getSessionSpend` and every session's usage through `billing/getTodaySpend`. Copy goes through the package's own `billing` locale namespace; styling uses tokens only.
|
|
10
12
|
|
|
@@ -12,9 +14,9 @@ The badge is account-level even though the slot is session-scoped: the header ut
|
|
|
12
14
|
|
|
13
15
|
The spends follow the conversation and the balance stays a manual snapshot:
|
|
14
16
|
|
|
15
|
-
- **Spends follow settled turns, debounced** — the component subscribes to the current session's running flag through the framework `useSession` seat. When a prompt turn settles (the flag flips back to idle), it recomputes this session's spend (`billing/getSessionSpend`)
|
|
17
|
+
- **Spends follow settled turns, debounced** — the component subscribes to the current session's running flag through the framework `useSession` seat. When a prompt turn settles (the flag flips back to idle), it recomputes this session's spend (`billing/getSessionSpend`), today's spend (`billing/getTodaySpend`), and the ranking (`billing/getTodaySessionsSpend`), local pricing passes with no network request, so the spend lines stay live during an ongoing conversation. The recompute is debounced for two seconds, so a burst of turns (an agent continuing across turns) prices once instead of once per turn; the host-side cache (see `dsh-llm-billing`) then serves the first miss for the rest of the minute.
|
|
16
18
|
- **Balance is manual** — the account balance is fetched on mount, on session switch, and on the explicit refresh action (`billing/getBalance`). There is no polling and no automatic refetch: the balance line only changes when one of those events happens.
|
|
17
|
-
- **Refresh keeps the last values** — an in-flight refresh leaves the previous values on screen, and a failed refresh retains the last good value instead of blanking it. The refresh action calls `billing/getTodaySpend(true)`, which
|
|
19
|
+
- **Refresh keeps the last values** — an in-flight refresh leaves the previous values on screen, and a failed refresh retains the last good value instead of blanking it. The refresh action calls `billing/getTodaySpend(true)` and `billing/getTodaySessionsSpend(true)`, which bypass the host-side cache; the turn-triggered recompute and the mount/session-switch read use the cached path.
|
|
18
20
|
|
|
19
21
|
## Model Experience
|
|
20
22
|
|
|
@@ -27,4 +29,6 @@ None; the package never assembles or sends provider requests, and its one RPC is
|
|
|
27
29
|
## Known Limitations and Deferred Work
|
|
28
30
|
|
|
29
31
|
- **First balance line only** — the balance reads the primary (`balance_infos[0]`) currency line; other currency lines are not shown.
|
|
32
|
+
- **Ranking capped at 10** — the panel shows at most `SESSION_RANKING_LIMIT` sessions, with a "…N more sessions" hint.
|
|
33
|
+
- **Turn cost needs a finalized closing message** — interrupted turns have no actions row, so no turn cost is shown for them.
|
|
30
34
|
- **Balance does not follow automatically** — the balance is a manual snapshot (no polling): spending from another client does not move the shown value until a refresh or browser reload.
|
package/README.zh.md
CHANGED
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
[English](README.md) | 中文
|
|
4
4
|
|
|
5
|
-
Web 额度特性的归属方:向 `conversation.session.header.utilities` 贡献一个条目,自己挂载 `billing` Remote,通过 `billing/getBalance`、`billing/getSessionSpend` 与 `billing/
|
|
5
|
+
Web 额度特性的归属方:向 `conversation.session.header.utilities` 贡献一个条目,自己挂载 `billing` Remote,通过 `billing/getBalance`、`billing/getSessionSpend`、`billing/getTodaySpend`、`billing/getTodaySessionsSpend` 与 `billing/getTurnSpend` 读取 DeepSeek 账户余额、本会话的计费花费、今日所有会话的计费花费、今日按会话的排行与单个回合的花费,并在右上角渲染成一个标签框。该能力的宿主侧在 [`dsh-llm-billing`](../llm-billing/README.md),它拥有 `/user/balance` 传输、峰谷计价表与 `billing` Remote 命名空间;本包挂载那个 Remote 并渲染它返回的内容。
|
|
6
6
|
|
|
7
|
-
触发器显示两行——剩余额度与本轮对话的计费花费(「本轮对话花费 ¥X」)——首个请求在途时不渲染任何东西。点击后展开一个标签框:剩余金额、本会话的计费花费(本会话花费,按官方峰/谷单价逐条消息计价——高峰窗口仅周一至周五适用,周末按低谷价——右侧并列今日所有会话的合计「今日共花费」)、每个模型一行的花费及其缓存命中/未命中输入/输出分项(「缓存命中 ¥X · 未命中输入 ¥Y · 输出 ¥Z
|
|
7
|
+
触发器显示两行——剩余额度与本轮对话的计费花费(「本轮对话花费 ¥X」)——首个请求在途时不渲染任何东西。点击后展开一个标签框:剩余金额、本会话的计费花费(本会话花费,按官方峰/谷单价逐条消息计价——高峰窗口仅周一至周五适用,周末按低谷价——右侧并列今日所有会话的合计「今日共花费」)、每个模型一行的花费及其缓存命中/未命中输入/输出分项(「缓存命中 ¥X · 未命中输入 ¥Y · 输出 ¥Z」)、手动刷新按钮与花费说明。模型行下方是**今日会话花费排行**(`今日会话花费`):按今日花费从高到低排列的会话列表,每行带会话的持久标题(重命名自动同步)与金额;最多渲染 `SESSION_RANKING_LIMIT`(10)行,其余以「…还有 N 个会话」提示。刷新时旧值不消失,刷新失败保留上一次有效值。没有可计价消耗的会话或日子显示「暂无消耗记录」而不是编造数字。失败——未配置 API key、凭据被拒绝、传输错误——渲染弱化的「额度不可用」,其提示携带 Remote 自己的错误信息。
|
|
8
|
+
|
|
9
|
+
本包还贡献**本轮花费条目**(`billing-turn-cost`,order 20)到 ui-chat 的 `conversation.chat.assistant-actions` list slot——与 `ui-message-feedback` 同一条操作条,多条目共存。对每条已完成回合的收尾助手消息,条目通过 `billing/getTurnSpend` 读取花费(按会话 + 消息 memo,回合落定后不再变化),在操作行里(与时间、复制、分叉同一排,紧邻该回合的「本轮用量」卡片)渲染弱化的「本轮花费:¥X」。回合没有 DeepSeek 用量(花费为 0)或加载失败时不渲染,Remote 故障不会污染操作行。
|
|
8
10
|
|
|
9
11
|
角标是账户级的,尽管插槽是会话作用域:顶栏工具行不过是外壳提供的那个始终可见的标题栏席位,余额并不随会话变化——但花费行会,因为它通过 `billing/getSessionSpend` 按当前会话自己的 token 用量计价、通过 `billing/getTodaySpend` 按所有会话的用量计价。文案走本包自己的 `billing` locale 命名空间;样式只用 token。
|
|
10
12
|
|
|
@@ -12,9 +14,9 @@ Web 额度特性的归属方:向 `conversation.session.header.utilities` 贡
|
|
|
12
14
|
|
|
13
15
|
花费跟随会话、额度保持手动快照:
|
|
14
16
|
|
|
15
|
-
- **花费随回合结束自动更新(去抖)** —— 组件通过框架 `useSession` 座位订阅当前会话的「运行中」标志。每当一轮 prompt 回合结束(标志翻回空闲),就只重算**本会话的花费**(`billing/getSessionSpend
|
|
17
|
+
- **花费随回合结束自动更新(去抖)** —— 组件通过框架 `useSession` 座位订阅当前会话的「运行中」标志。每当一轮 prompt 回合结束(标志翻回空闲),就只重算**本会话的花费**(`billing/getSessionSpend`)、**今日共花费**(`billing/getTodaySpend`)与**排行**(`billing/getTodaySessionsSpend`,纯本地计价、不发网络请求),连续对话时花费行实时跟随。重算有 2 秒去抖,回合突发(agent 连续多轮)只计价一次;宿主侧缓存(见 `dsh-llm-billing`)随后在一分钟内服务首次未命中。
|
|
16
18
|
- **额度保持手动** —— 账户余额在挂载、切换会话、点刷新按钮时拉取(`billing/getBalance`)。**没有轮询、不会自动重拉**:余额行只在这几个时机变化。
|
|
17
|
-
- **刷新保留旧值** —— 刷新在途时旧值留在屏幕上,刷新失败保留上一次有效值,不清空。刷新按钮调用 `billing/getTodaySpend(true)`,绕过宿主侧缓存;回合结束触发的重算与挂载/切换会话的读取走缓存路径。
|
|
19
|
+
- **刷新保留旧值** —— 刷新在途时旧值留在屏幕上,刷新失败保留上一次有效值,不清空。刷新按钮调用 `billing/getTodaySpend(true)` 与 `billing/getTodaySessionsSpend(true)`,绕过宿主侧缓存;回合结束触发的重算与挂载/切换会话的读取走缓存路径。
|
|
18
20
|
|
|
19
21
|
## 模型体验
|
|
20
22
|
|
|
@@ -27,4 +29,6 @@ Web 额度特性的归属方:向 `conversation.session.header.utilities` 贡
|
|
|
27
29
|
## 已知限制与暂缓事项
|
|
28
30
|
|
|
29
31
|
- **只取首条余额线** —— 余额读取主要(`balance_infos[0]`)币种行;其它币种行不显示。
|
|
32
|
+
- **排行上限 10** —— 面板最多显示 `SESSION_RANKING_LIMIT` 个会话,其余以「…还有 N 个会话」提示。
|
|
33
|
+
- **本轮花费需要已定稿的收尾消息** —— 中断的回合没有操作行,不显示本轮花费。
|
|
30
34
|
- **额度不自动跟随** —— 余额保持手动快照(无轮询):其他客户端产生的消耗不会让界面数值自动变化,需手动刷新或刷新浏览器。
|
package/lib/client.js
CHANGED
|
@@ -4120,6 +4120,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4120
4120
|
"outputCost": number()
|
|
4121
4121
|
}))
|
|
4122
4122
|
});
|
|
4123
|
+
const _rayadesu_dsh_llm_billing_billing_getTodaySessionsSpend_parameter_0$schema = union([
|
|
4124
|
+
_undefined(),
|
|
4125
|
+
literal(false),
|
|
4126
|
+
literal(true)
|
|
4127
|
+
]);
|
|
4128
|
+
const _rayadesu_dsh_llm_billing_billing_getTodaySessionsSpend_result$schema = object({ "sessions": array(object({
|
|
4129
|
+
"sessionId": intersection(string(), unknown()),
|
|
4130
|
+
"title": union([literal(null), string()]),
|
|
4131
|
+
"total": number()
|
|
4132
|
+
})) });
|
|
4123
4133
|
const _rayadesu_dsh_llm_billing_billing_getTodaySpend_parameter_0$schema = union([
|
|
4124
4134
|
_undefined(),
|
|
4125
4135
|
literal(false),
|
|
@@ -4141,6 +4151,9 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4141
4151
|
"outputCost": number()
|
|
4142
4152
|
}))
|
|
4143
4153
|
});
|
|
4154
|
+
const _rayadesu_dsh_llm_billing_billing_getTurnSpend_parameter_0$schema = intersection(string(), unknown());
|
|
4155
|
+
const _rayadesu_dsh_llm_billing_billing_getTurnSpend_parameter_1$schema = string();
|
|
4156
|
+
const _rayadesu_dsh_llm_billing_billing_getTurnSpend_result$schema = object({ "total": number() });
|
|
4144
4157
|
const TYPERT_REMOTE = {
|
|
4145
4158
|
package: "@rayadesu/dsh-llm-billing",
|
|
4146
4159
|
descriptors: [
|
|
@@ -4158,7 +4171,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4158
4171
|
},
|
|
4159
4172
|
sourceLocation: {
|
|
4160
4173
|
"file": "packages/llm-billing/src/balance.ts",
|
|
4161
|
-
"line":
|
|
4174
|
+
"line": 154,
|
|
4162
4175
|
"column": 3
|
|
4163
4176
|
}
|
|
4164
4177
|
},
|
|
@@ -4185,7 +4198,35 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4185
4198
|
},
|
|
4186
4199
|
sourceLocation: {
|
|
4187
4200
|
"file": "packages/llm-billing/src/balance.ts",
|
|
4188
|
-
"line":
|
|
4201
|
+
"line": 165,
|
|
4202
|
+
"column": 3
|
|
4203
|
+
}
|
|
4204
|
+
},
|
|
4205
|
+
{
|
|
4206
|
+
id: "@rayadesu/dsh-llm-billing#billing/getTodaySessionsSpend",
|
|
4207
|
+
service: "billing",
|
|
4208
|
+
namespace: "billing",
|
|
4209
|
+
method: "getTodaySessionsSpend",
|
|
4210
|
+
invocation: { kind: "direct" },
|
|
4211
|
+
parameters: [{
|
|
4212
|
+
name: "force",
|
|
4213
|
+
wire: "force",
|
|
4214
|
+
source: "json",
|
|
4215
|
+
acceptsUndefined: true,
|
|
4216
|
+
codec: {
|
|
4217
|
+
mode: "strict",
|
|
4218
|
+
typeSymbol: "@rayadesu/dsh-llm-billing#billing/getTodaySessionsSpend:force",
|
|
4219
|
+
schema: _rayadesu_dsh_llm_billing_billing_getTodaySessionsSpend_parameter_0$schema
|
|
4220
|
+
}
|
|
4221
|
+
}],
|
|
4222
|
+
result: {
|
|
4223
|
+
mode: "strict",
|
|
4224
|
+
typeSymbol: "@rayadesu/dsh-llm-billing/types#DeepSeekTodaySessionsSpend",
|
|
4225
|
+
schema: _rayadesu_dsh_llm_billing_billing_getTodaySessionsSpend_result$schema
|
|
4226
|
+
},
|
|
4227
|
+
sourceLocation: {
|
|
4228
|
+
"file": "packages/llm-billing/src/balance.ts",
|
|
4229
|
+
"line": 192,
|
|
4189
4230
|
"column": 3
|
|
4190
4231
|
}
|
|
4191
4232
|
},
|
|
@@ -4213,7 +4254,43 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4213
4254
|
},
|
|
4214
4255
|
sourceLocation: {
|
|
4215
4256
|
"file": "packages/llm-billing/src/balance.ts",
|
|
4216
|
-
"line":
|
|
4257
|
+
"line": 178,
|
|
4258
|
+
"column": 3
|
|
4259
|
+
}
|
|
4260
|
+
},
|
|
4261
|
+
{
|
|
4262
|
+
id: "@rayadesu/dsh-llm-billing#billing/getTurnSpend",
|
|
4263
|
+
service: "billing",
|
|
4264
|
+
namespace: "billing",
|
|
4265
|
+
method: "getTurnSpend",
|
|
4266
|
+
invocation: { kind: "direct" },
|
|
4267
|
+
parameters: [{
|
|
4268
|
+
name: "sessionId",
|
|
4269
|
+
wire: "sessionId",
|
|
4270
|
+
source: "json",
|
|
4271
|
+
codec: {
|
|
4272
|
+
mode: "strict",
|
|
4273
|
+
typeSymbol: "@rayadesu/dsh-llm-billing#billing/getTurnSpend:sessionId",
|
|
4274
|
+
schema: _rayadesu_dsh_llm_billing_billing_getTurnSpend_parameter_0$schema
|
|
4275
|
+
}
|
|
4276
|
+
}, {
|
|
4277
|
+
name: "messageId",
|
|
4278
|
+
wire: "messageId",
|
|
4279
|
+
source: "json",
|
|
4280
|
+
codec: {
|
|
4281
|
+
mode: "strict",
|
|
4282
|
+
typeSymbol: "@rayadesu/dsh-llm-billing#billing/getTurnSpend:messageId",
|
|
4283
|
+
schema: _rayadesu_dsh_llm_billing_billing_getTurnSpend_parameter_1$schema
|
|
4284
|
+
}
|
|
4285
|
+
}],
|
|
4286
|
+
result: {
|
|
4287
|
+
mode: "strict",
|
|
4288
|
+
typeSymbol: "@rayadesu/dsh-llm-billing/types#DeepSeekTurnSpend",
|
|
4289
|
+
schema: _rayadesu_dsh_llm_billing_billing_getTurnSpend_result$schema
|
|
4290
|
+
},
|
|
4291
|
+
sourceLocation: {
|
|
4292
|
+
"file": "packages/llm-billing/src/balance.ts",
|
|
4293
|
+
"line": 205,
|
|
4217
4294
|
"column": 3
|
|
4218
4295
|
}
|
|
4219
4296
|
}
|
|
@@ -4221,13 +4298,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4221
4298
|
};
|
|
4222
4299
|
//#endregion
|
|
4223
4300
|
//#region \0dsh-css:C:\Users\admin\Desktop\me\code\DeepSeek-Harness-chat-billing\packages\ui-billing\src\client\BalanceBadge.module.css.mjs
|
|
4224
|
-
const css = "._4_Wqcq_root{position:relative}._4_Wqcq_trigger{background:var(--dsw-alias-fill-l2);min-height:32px;color:var(--dsw-alias-label-secondary);cursor:pointer;border:0;border-radius:8px;align-items:center;gap:4px;padding:4px 8px;display:inline-flex}._4_Wqcq_trigger:hover,._4_Wqcq_trigger:focus-visible{color:var(--dsw-alias-label-primary)}._4_Wqcq_trigger svg{transition:transform .12s}._4_Wqcq_chevronOpen{transform:rotate(180deg)}._4_Wqcq_triggerLines{text-align:left;flex-direction:column;align-items:flex-start;gap:0;display:flex}._4_Wqcq_linePrimary{font-variant-numeric:tabular-nums;font-size:12px;line-height:16px}._4_Wqcq_lineSecondary{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;font-size:11px;line-height:15px}._4_Wqcq_unavailable{color:var(--dsw-alias-label-tertiary)}._4_Wqcq_inlineIcon{color:var(--dsw-alias-label-tertiary);flex:none}._4_Wqcq_spinning{color:var(--dsw-alias-label-tertiary);flex:none;animation:.8s linear infinite _4_Wqcq_spin}@keyframes _4_Wqcq_spin{to{transform:rotate(360deg)}}._4_Wqcq_panel{z-index:100;box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-specific-menu);width:336px;max-width:min(360px,100vw - 32px);box-shadow:var(--dsw-shadow-lv3);border-radius:12px;flex-direction:column;gap:2px;padding:6px;display:flex;position:absolute;top:calc(100% + 6px);right:0}._4_Wqcq_amountRow{border-radius:8px;justify-content:space-between;align-items:center;gap:8px;padding:6px 8px;display:flex}._4_Wqcq_amountLabel{color:var(--dsw-alias-label-primary);font-variant-numeric:tabular-nums;font-size:13px;line-height:18px}._4_Wqcq_spendRow{border-top:1px solid var(--dsw-alias-border-l2);border-radius:0;justify-content:space-between;align-items:center;gap:8px;padding:6px 8px;display:flex}._4_Wqcq_costRow{align-items:center;padding:2px 8px 6px;display:flex}._4_Wqcq_costBreakdown{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;font-size:11px;line-height:16px}._4_Wqcq_amountActions{align-items:center;gap:2px;display:inline-flex}._4_Wqcq_infoButton,._4_Wqcq_refreshButton{width:24px;height:24px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:0;border-radius:6px;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}._4_Wqcq_infoButton:hover,._4_Wqcq_infoButton:focus-visible,._4_Wqcq_refreshButton:hover,._4_Wqcq_refreshButton:focus-visible{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-fill-l2)}._4_Wqcq_modelRow{border-radius:8px;justify-content:space-between;align-items:baseline;gap:8px;padding:6px 8px;display:flex}._4_Wqcq_modelName{color:var(--dsw-alias-label-secondary);flex:none;font-size:12px;line-height:18px}._4_Wqcq_tasks{text-align:right;min-width:0;color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;overflow-wrap:anywhere;font-size:12px;line-height:18px}";
|
|
4225
|
-
const tagId = "@rayadesu/dsh-client-ui-billing/BalanceBadge.module.css";
|
|
4226
|
-
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
4301
|
+
const css$1 = "._4_Wqcq_root{position:relative}._4_Wqcq_trigger{background:var(--dsw-alias-fill-l2);min-height:32px;color:var(--dsw-alias-label-secondary);cursor:pointer;border:0;border-radius:8px;align-items:center;gap:4px;padding:4px 8px;display:inline-flex}._4_Wqcq_trigger:hover,._4_Wqcq_trigger:focus-visible{color:var(--dsw-alias-label-primary)}._4_Wqcq_trigger svg{transition:transform .12s}._4_Wqcq_chevronOpen{transform:rotate(180deg)}._4_Wqcq_triggerLines{text-align:left;flex-direction:column;align-items:flex-start;gap:0;display:flex}._4_Wqcq_linePrimary{font-variant-numeric:tabular-nums;font-size:12px;line-height:16px}._4_Wqcq_lineSecondary{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;font-size:11px;line-height:15px}._4_Wqcq_unavailable{color:var(--dsw-alias-label-tertiary)}._4_Wqcq_inlineIcon{color:var(--dsw-alias-label-tertiary);flex:none}._4_Wqcq_spinning{color:var(--dsw-alias-label-tertiary);flex:none;animation:.8s linear infinite _4_Wqcq_spin}@keyframes _4_Wqcq_spin{to{transform:rotate(360deg)}}._4_Wqcq_panel{z-index:100;box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-specific-menu);width:336px;max-width:min(360px,100vw - 32px);box-shadow:var(--dsw-shadow-lv3);border-radius:12px;flex-direction:column;gap:2px;padding:6px;display:flex;position:absolute;top:calc(100% + 6px);right:0}._4_Wqcq_amountRow{border-radius:8px;justify-content:space-between;align-items:center;gap:8px;padding:6px 8px;display:flex}._4_Wqcq_amountLabel{color:var(--dsw-alias-label-primary);font-variant-numeric:tabular-nums;font-size:13px;line-height:18px}._4_Wqcq_spendRow{border-top:1px solid var(--dsw-alias-border-l2);border-radius:0;justify-content:space-between;align-items:center;gap:8px;padding:6px 8px;display:flex}._4_Wqcq_costRow{align-items:center;padding:2px 8px 6px;display:flex}._4_Wqcq_costBreakdown{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;font-size:11px;line-height:16px}._4_Wqcq_amountActions{align-items:center;gap:2px;display:inline-flex}._4_Wqcq_infoButton,._4_Wqcq_refreshButton{width:24px;height:24px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:0;border-radius:6px;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}._4_Wqcq_infoButton:hover,._4_Wqcq_infoButton:focus-visible,._4_Wqcq_refreshButton:hover,._4_Wqcq_refreshButton:focus-visible{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-fill-l2)}._4_Wqcq_modelRow{border-radius:8px;justify-content:space-between;align-items:baseline;gap:8px;padding:6px 8px;display:flex}._4_Wqcq_modelName{color:var(--dsw-alias-label-secondary);flex:none;font-size:12px;line-height:18px}._4_Wqcq_tasks{text-align:right;min-width:0;color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;overflow-wrap:anywhere;font-size:12px;line-height:18px}._4_Wqcq_ranking{border-top:1px solid var(--dsw-alias-border-l2);flex-direction:column;gap:0;padding-top:2px;display:flex}._4_Wqcq_rankingTitle{color:var(--dsw-alias-label-tertiary);padding:6px 8px 2px;font-size:11px;line-height:16px}._4_Wqcq_rankingRow{border-radius:8px;align-items:baseline;gap:8px;min-width:0;padding:3px 8px;display:flex}._4_Wqcq_rankingRow:hover{background:var(--dsw-alias-fill-l2)}._4_Wqcq_rankingIndex{width:16px;color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;text-align:right;flex:none;font-size:11px;line-height:16px}._4_Wqcq_rankingName{min-width:0;color:var(--dsw-alias-label-secondary);text-overflow:ellipsis;white-space:nowrap;flex:1;font-size:12px;line-height:18px;overflow:hidden}._4_Wqcq_rankingAmount{color:var(--dsw-alias-label-primary);font-variant-numeric:tabular-nums;flex:none;font-size:12px;line-height:18px}._4_Wqcq_rankingMore{color:var(--dsw-alias-label-tertiary);padding:2px 8px 4px;font-size:11px;line-height:16px}";
|
|
4302
|
+
const tagId$1 = "@rayadesu/dsh-client-ui-billing/BalanceBadge.module.css";
|
|
4303
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
|
|
4227
4304
|
const tag = document.createElement("style");
|
|
4228
4305
|
tag.dataset.plugin = "@rayadesu/dsh-client-ui-billing";
|
|
4229
|
-
tag.dataset.pluginCss = tagId;
|
|
4230
|
-
tag.textContent = css;
|
|
4306
|
+
tag.dataset.pluginCss = tagId$1;
|
|
4307
|
+
tag.textContent = css$1;
|
|
4231
4308
|
document.head.appendChild(tag);
|
|
4232
4309
|
}
|
|
4233
4310
|
var BalanceBadge_module_css_default = {
|
|
@@ -4244,6 +4321,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4244
4321
|
"modelName": "_4_Wqcq_modelName",
|
|
4245
4322
|
"modelRow": "_4_Wqcq_modelRow",
|
|
4246
4323
|
"panel": "_4_Wqcq_panel",
|
|
4324
|
+
"ranking": "_4_Wqcq_ranking",
|
|
4325
|
+
"rankingAmount": "_4_Wqcq_rankingAmount",
|
|
4326
|
+
"rankingIndex": "_4_Wqcq_rankingIndex",
|
|
4327
|
+
"rankingMore": "_4_Wqcq_rankingMore",
|
|
4328
|
+
"rankingName": "_4_Wqcq_rankingName",
|
|
4329
|
+
"rankingRow": "_4_Wqcq_rankingRow",
|
|
4330
|
+
"rankingTitle": "_4_Wqcq_rankingTitle",
|
|
4247
4331
|
"refreshButton": "_4_Wqcq_refreshButton",
|
|
4248
4332
|
"root": "_4_Wqcq_root",
|
|
4249
4333
|
"spendRow": "_4_Wqcq_spendRow",
|
|
@@ -4273,10 +4357,24 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4273
4357
|
};
|
|
4274
4358
|
}
|
|
4275
4359
|
/** CNY amount, up to four decimals with trailing zeros trimmed. */
|
|
4276
|
-
function formatSpend(amount) {
|
|
4360
|
+
function formatSpend$1(amount) {
|
|
4277
4361
|
return `¥${amount.toFixed(4).replace(/\.?0+$/, "")}`;
|
|
4278
4362
|
}
|
|
4279
4363
|
/**
|
|
4364
|
+
* Run one fetch line: the fetch is deferred to a microtask so the effect's
|
|
4365
|
+
* render commits before any state update lands; a settled value is stored
|
|
4366
|
+
* while `current` stays true, and a rejection keeps the previous value (a
|
|
4367
|
+
* no-op unless `onReject` supplies fallback handling) so a failed refetch
|
|
4368
|
+
* never blanks the UI.
|
|
4369
|
+
*/
|
|
4370
|
+
function fetchLine(current, fetch, store, onReject) {
|
|
4371
|
+
return Promise.resolve().then(fetch).then((value) => {
|
|
4372
|
+
if (current()) store(value);
|
|
4373
|
+
}, (reason) => {
|
|
4374
|
+
if (current()) onReject?.(reason);
|
|
4375
|
+
});
|
|
4376
|
+
}
|
|
4377
|
+
/**
|
|
4280
4378
|
* Render the billing badge in the session-header utilities row. The trigger
|
|
4281
4379
|
* shows the remaining balance and this conversation's billed spend and opens
|
|
4282
4380
|
* a label box with the amount, this session's spend with its cache-hit /
|
|
@@ -4292,10 +4390,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4292
4390
|
* @param props - Remote face, locale, and the standard session-header runtime share.
|
|
4293
4391
|
* @returns the badge, or null until the first balance fetch settles.
|
|
4294
4392
|
*/
|
|
4295
|
-
function BalanceBadge({ getBalance, getSessionSpend, getTodaySpend, sessionId, useSession, t }) {
|
|
4393
|
+
function BalanceBadge({ getBalance, getSessionSpend, getTodaySpend, getTodaySessionsSpend, sessionId, useSession, t }) {
|
|
4296
4394
|
const [balance, setBalance] = (0, react.useState)(null);
|
|
4297
4395
|
const [spend, setSpend] = (0, react.useState)(null);
|
|
4298
4396
|
const [todaySpend, setTodaySpend] = (0, react.useState)(null);
|
|
4397
|
+
const [sessionsSpend, setSessionsSpend] = (0, react.useState)(null);
|
|
4299
4398
|
const [error, setError] = (0, react.useState)(null);
|
|
4300
4399
|
const [refreshing, setRefreshing] = (0, react.useState)(false);
|
|
4301
4400
|
const [open, setOpen] = (0, react.useState)(false);
|
|
@@ -4305,28 +4404,23 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4305
4404
|
const pricedRunningRef = (0, react.useRef)(running);
|
|
4306
4405
|
(0, react.useEffect)(() => {
|
|
4307
4406
|
let current = true;
|
|
4407
|
+
const isCurrent = () => current;
|
|
4308
4408
|
setRefreshing(balance !== null);
|
|
4309
4409
|
Promise.resolve().then(() => {
|
|
4310
|
-
const balanceRequest =
|
|
4311
|
-
if (!current) return;
|
|
4410
|
+
const balanceRequest = fetchLine(isCurrent, getBalance, (value) => {
|
|
4312
4411
|
setBalance(value);
|
|
4313
4412
|
setError(null);
|
|
4314
4413
|
}, (reason) => {
|
|
4315
|
-
if (!current) return;
|
|
4316
4414
|
if (balance === null) setError(reason instanceof Error ? reason.message : String(reason));
|
|
4317
4415
|
});
|
|
4318
|
-
const sessionSpendRequest =
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
}, () => {});
|
|
4322
|
-
const todaySpendRequest = Promise.resolve().then(() => getTodaySpend(request > 0)).then((value) => {
|
|
4323
|
-
if (!current) return;
|
|
4324
|
-
setTodaySpend(value);
|
|
4325
|
-
}, () => {});
|
|
4416
|
+
const sessionSpendRequest = fetchLine(isCurrent, () => getSessionSpend(sessionId), setSpend);
|
|
4417
|
+
const todaySpendRequest = fetchLine(isCurrent, () => getTodaySpend(request > 0), setTodaySpend);
|
|
4418
|
+
const sessionsSpendRequest = fetchLine(isCurrent, () => getTodaySessionsSpend(request > 0), setSessionsSpend);
|
|
4326
4419
|
Promise.allSettled([
|
|
4327
4420
|
balanceRequest,
|
|
4328
4421
|
sessionSpendRequest,
|
|
4329
|
-
todaySpendRequest
|
|
4422
|
+
todaySpendRequest,
|
|
4423
|
+
sessionsSpendRequest
|
|
4330
4424
|
]).then(() => {
|
|
4331
4425
|
if (current) setRefreshing(false);
|
|
4332
4426
|
});
|
|
@@ -4338,6 +4432,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4338
4432
|
getBalance,
|
|
4339
4433
|
getSessionSpend,
|
|
4340
4434
|
getTodaySpend,
|
|
4435
|
+
getTodaySessionsSpend,
|
|
4341
4436
|
sessionId,
|
|
4342
4437
|
request
|
|
4343
4438
|
]);
|
|
@@ -4346,17 +4441,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4346
4441
|
pricedRunningRef.current = running;
|
|
4347
4442
|
if (running) return;
|
|
4348
4443
|
let current = true;
|
|
4444
|
+
const isCurrent = () => current;
|
|
4349
4445
|
const timer = setTimeout(() => {
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
setSpend(value);
|
|
4354
|
-
}, () => {});
|
|
4355
|
-
Promise.resolve().then(() => getTodaySpend()).then((value) => {
|
|
4356
|
-
if (!current) return;
|
|
4357
|
-
setTodaySpend(value);
|
|
4358
|
-
}, () => {});
|
|
4359
|
-
});
|
|
4446
|
+
fetchLine(isCurrent, () => getSessionSpend(sessionId), setSpend);
|
|
4447
|
+
fetchLine(isCurrent, () => getTodaySpend(), setTodaySpend);
|
|
4448
|
+
fetchLine(isCurrent, () => getTodaySessionsSpend(), setSessionsSpend);
|
|
4360
4449
|
}, 2e3);
|
|
4361
4450
|
return () => {
|
|
4362
4451
|
clearTimeout(timer);
|
|
@@ -4365,6 +4454,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4365
4454
|
}, [
|
|
4366
4455
|
getSessionSpend,
|
|
4367
4456
|
getTodaySpend,
|
|
4457
|
+
getTodaySessionsSpend,
|
|
4368
4458
|
sessionId,
|
|
4369
4459
|
running
|
|
4370
4460
|
]);
|
|
@@ -4402,7 +4492,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4402
4492
|
};
|
|
4403
4493
|
const line = primaryLine(balance);
|
|
4404
4494
|
const amount = line === void 0 ? "—" : `${line.symbol}${line.total}`;
|
|
4405
|
-
const spendLine = spend !== null && spend.models.length > 0 ? t("trigger.conversationSpend", { amount: formatSpend(spend.total) }) : void 0;
|
|
4495
|
+
const spendLine = spend !== null && spend.models.length > 0 ? t("trigger.conversationSpend", { amount: formatSpend$1(spend.total) }) : void 0;
|
|
4406
4496
|
return (0, react_jsx_runtime.jsxs)("div", {
|
|
4407
4497
|
ref: rootRef,
|
|
4408
4498
|
className: BalanceBadge_module_css_default.root,
|
|
@@ -4460,10 +4550,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4460
4550
|
className: BalanceBadge_module_css_default.spendRow,
|
|
4461
4551
|
children: [(0, react_jsx_runtime.jsx)("span", {
|
|
4462
4552
|
className: BalanceBadge_module_css_default.amountLabel,
|
|
4463
|
-
children: t("label.sessionSpend", { amount: spend === null ? "—" : spend.models.length === 0 ? t("stat.none") : formatSpend(spend.total) })
|
|
4553
|
+
children: t("label.sessionSpend", { amount: spend === null ? "—" : spend.models.length === 0 ? t("stat.none") : formatSpend$1(spend.total) })
|
|
4464
4554
|
}), (0, react_jsx_runtime.jsx)("span", {
|
|
4465
4555
|
className: BalanceBadge_module_css_default.amountLabel,
|
|
4466
|
-
children: t("label.todaySpend", { amount: todaySpend === null ? "—" : todaySpend.models.length === 0 ? t("stat.none") : formatSpend(todaySpend.total) })
|
|
4556
|
+
children: t("label.todaySpend", { amount: todaySpend === null ? "—" : todaySpend.models.length === 0 ? t("stat.none") : formatSpend$1(todaySpend.total) })
|
|
4467
4557
|
})]
|
|
4468
4558
|
}),
|
|
4469
4559
|
spend?.models.map((model) => (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [(0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -4473,26 +4563,120 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4473
4563
|
children: model.displayName
|
|
4474
4564
|
}), (0, react_jsx_runtime.jsx)("span", {
|
|
4475
4565
|
className: BalanceBadge_module_css_default.tasks,
|
|
4476
|
-
children: formatSpend(model.cost)
|
|
4566
|
+
children: formatSpend$1(model.cost)
|
|
4477
4567
|
})]
|
|
4478
4568
|
}), (0, react_jsx_runtime.jsx)("div", {
|
|
4479
4569
|
className: BalanceBadge_module_css_default.costRow,
|
|
4480
4570
|
children: (0, react_jsx_runtime.jsxs)("span", {
|
|
4481
4571
|
className: BalanceBadge_module_css_default.costBreakdown,
|
|
4482
4572
|
children: [
|
|
4483
|
-
t("label.cost.hit", { amount: formatSpend(model.cacheHitInputCost) }),
|
|
4573
|
+
t("label.cost.hit", { amount: formatSpend$1(model.cacheHitInputCost) }),
|
|
4484
4574
|
" · ",
|
|
4485
|
-
t("label.cost.input", { amount: formatSpend(model.cacheMissInputCost) }),
|
|
4575
|
+
t("label.cost.input", { amount: formatSpend$1(model.cacheMissInputCost) }),
|
|
4486
4576
|
" · ",
|
|
4487
|
-
t("label.cost.output", { amount: formatSpend(model.outputCost) })
|
|
4577
|
+
t("label.cost.output", { amount: formatSpend$1(model.outputCost) })
|
|
4488
4578
|
]
|
|
4489
4579
|
})
|
|
4490
|
-
})] }, model.model))
|
|
4580
|
+
})] }, model.model)),
|
|
4581
|
+
sessionsSpend !== null && sessionsSpend.sessions.length > 0 && (0, react_jsx_runtime.jsxs)("div", {
|
|
4582
|
+
className: BalanceBadge_module_css_default.ranking,
|
|
4583
|
+
children: [
|
|
4584
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
4585
|
+
className: BalanceBadge_module_css_default.rankingTitle,
|
|
4586
|
+
children: t("label.sessionRanking")
|
|
4587
|
+
}),
|
|
4588
|
+
sessionsSpend.sessions.slice(0, 10).map((row, index) => (0, react_jsx_runtime.jsxs)("div", {
|
|
4589
|
+
className: BalanceBadge_module_css_default.rankingRow,
|
|
4590
|
+
children: [
|
|
4591
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
4592
|
+
className: BalanceBadge_module_css_default.rankingIndex,
|
|
4593
|
+
children: index + 1
|
|
4594
|
+
}),
|
|
4595
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
4596
|
+
className: BalanceBadge_module_css_default.rankingName,
|
|
4597
|
+
title: row.title ?? void 0,
|
|
4598
|
+
children: row.title ?? t("stat.untitled")
|
|
4599
|
+
}),
|
|
4600
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
4601
|
+
className: BalanceBadge_module_css_default.rankingAmount,
|
|
4602
|
+
children: formatSpend$1(row.total)
|
|
4603
|
+
})
|
|
4604
|
+
]
|
|
4605
|
+
}, row.sessionId)),
|
|
4606
|
+
sessionsSpend.sessions.length > 10 && (0, react_jsx_runtime.jsx)("div", {
|
|
4607
|
+
className: BalanceBadge_module_css_default.rankingMore,
|
|
4608
|
+
children: t("label.sessionRanking.more", { count: sessionsSpend.sessions.length - 10 })
|
|
4609
|
+
})
|
|
4610
|
+
]
|
|
4611
|
+
})
|
|
4491
4612
|
]
|
|
4492
4613
|
}) : null]
|
|
4493
4614
|
});
|
|
4494
4615
|
}
|
|
4495
4616
|
//#endregion
|
|
4617
|
+
//#region \0dsh-css:C:\Users\admin\Desktop\me\code\DeepSeek-Harness-chat-billing\packages\ui-billing\src\client\TurnCostAction.module.css.mjs
|
|
4618
|
+
const css = ".l2XZIa_cost{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums;white-space:nowrap;flex:none;font-size:12px;line-height:18px}";
|
|
4619
|
+
const tagId = "@rayadesu/dsh-client-ui-billing/TurnCostAction.module.css";
|
|
4620
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
4621
|
+
const tag = document.createElement("style");
|
|
4622
|
+
tag.dataset.plugin = "@rayadesu/dsh-client-ui-billing";
|
|
4623
|
+
tag.dataset.pluginCss = tagId;
|
|
4624
|
+
tag.textContent = css;
|
|
4625
|
+
document.head.appendChild(tag);
|
|
4626
|
+
}
|
|
4627
|
+
var TurnCostAction_module_css_default = { "cost": "l2XZIa_cost" };
|
|
4628
|
+
//#endregion
|
|
4629
|
+
//#region lib/types/client/TurnCostAction.js
|
|
4630
|
+
/** Per-turn billed-cost label in the closing assistant message's actions row. */
|
|
4631
|
+
/** CNY amount, up to four decimals with trailing zeros trimmed. */
|
|
4632
|
+
function formatSpend(amount) {
|
|
4633
|
+
return `¥${amount.toFixed(4).replace(/\.?0+$/, "")}`;
|
|
4634
|
+
}
|
|
4635
|
+
const turnCostCache = /* @__PURE__ */ new Map();
|
|
4636
|
+
const TURN_COST_CACHE_LIMIT = 1024;
|
|
4637
|
+
async function cachedTurnCost(sessionId, messageId, getTurnSpend) {
|
|
4638
|
+
const key = `${sessionId}\0${messageId}`;
|
|
4639
|
+
const cached = turnCostCache.get(key);
|
|
4640
|
+
if (cached !== void 0) return cached;
|
|
4641
|
+
const { total } = await getTurnSpend(sessionId, messageId);
|
|
4642
|
+
if (turnCostCache.size >= TURN_COST_CACHE_LIMIT) turnCostCache.clear();
|
|
4643
|
+
turnCostCache.set(key, total);
|
|
4644
|
+
return total;
|
|
4645
|
+
}
|
|
4646
|
+
/**
|
|
4647
|
+
* Render one Turn's billed cost as a muted label inside the closing message's
|
|
4648
|
+
* actions row, between the built-in copy/branch controls and the message
|
|
4649
|
+
* clock. The label appears only after the cost settles and hides again when
|
|
4650
|
+
* the Turn priced to zero (no DeepSeek usage); a failed fetch stays hidden so
|
|
4651
|
+
* a Remote outage never clutters the row.
|
|
4652
|
+
* @param props - the closing message id, the session runtime share, and the injected cost reader.
|
|
4653
|
+
* @returns the cost label, or null while loading, on failure, or for zero cost.
|
|
4654
|
+
*/
|
|
4655
|
+
function TurnCostAction({ messageId, sessionId, getTurnSpend, t }) {
|
|
4656
|
+
const [cost, setCost] = (0, react.useState)(null);
|
|
4657
|
+
(0, react.useEffect)(() => {
|
|
4658
|
+
let current = true;
|
|
4659
|
+
Promise.resolve().then(() => cachedTurnCost(sessionId, messageId, getTurnSpend)).then((total) => {
|
|
4660
|
+
if (current) setCost(total);
|
|
4661
|
+
}, () => {
|
|
4662
|
+
if (current) setCost(null);
|
|
4663
|
+
});
|
|
4664
|
+
return () => {
|
|
4665
|
+
current = false;
|
|
4666
|
+
};
|
|
4667
|
+
}, [
|
|
4668
|
+
getTurnSpend,
|
|
4669
|
+
messageId,
|
|
4670
|
+
sessionId
|
|
4671
|
+
]);
|
|
4672
|
+
if (cost === null || cost <= 0) return null;
|
|
4673
|
+
return (0, react_jsx_runtime.jsx)("span", {
|
|
4674
|
+
className: TurnCostAction_module_css_default.cost,
|
|
4675
|
+
"data-turn-cost": true,
|
|
4676
|
+
children: t("turnCost", { amount: formatSpend(cost) })
|
|
4677
|
+
});
|
|
4678
|
+
}
|
|
4679
|
+
//#endregion
|
|
4496
4680
|
//#region lib/types/client/locales.js
|
|
4497
4681
|
/** `billing` namespace dictionaries. */
|
|
4498
4682
|
/** Dictionary namespace owned by this plugin. */
|
|
@@ -4508,12 +4692,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4508
4692
|
"label.cost.input": "未命中输入 {amount}",
|
|
4509
4693
|
"label.cost.output": "输出 {amount}",
|
|
4510
4694
|
"stat.none": "暂无消耗记录",
|
|
4695
|
+
"stat.untitled": "未命名",
|
|
4511
4696
|
"state.unavailable": "额度不可用",
|
|
4512
4697
|
"action.refresh": "刷新",
|
|
4513
4698
|
"info.aria": "花费说明",
|
|
4514
4699
|
"info.hint": "仅能预估 DeepSeek 相关模型。本会话花费按每条消息的发生时刻(北京时间)所在峰谷时段单价计费:缓存命中输入、未命中输入(含缓存写入)、输出(含推理)分别计价。费率按 8 月 17 日实行的标准,高峰时段为北京时间周一至周五 9:00–12:00、14:00–18:00,周末全天按低谷价计费。",
|
|
4515
4700
|
"badge.aria": "DeepSeek 额度:{amount}",
|
|
4516
|
-
"panel.aria": "DeepSeek 额度详情"
|
|
4701
|
+
"panel.aria": "DeepSeek 额度详情",
|
|
4702
|
+
"turnCost": "本轮花费:{amount}",
|
|
4703
|
+
"label.sessionRanking": "今日会话花费",
|
|
4704
|
+
"label.sessionRanking.more": "…还有 {count} 个会话"
|
|
4517
4705
|
};
|
|
4518
4706
|
/** English dictionary, key-identical to the Chinese source of truth. */
|
|
4519
4707
|
const en = {
|
|
@@ -4526,12 +4714,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4526
4714
|
"label.cost.input": "Missed input {amount}",
|
|
4527
4715
|
"label.cost.output": "Output {amount}",
|
|
4528
4716
|
"stat.none": "No usage recorded",
|
|
4717
|
+
"stat.untitled": "Untitled",
|
|
4529
4718
|
"state.unavailable": "Balance unavailable",
|
|
4530
4719
|
"action.refresh": "Refresh",
|
|
4531
4720
|
"info.aria": "About this spend",
|
|
4532
4721
|
"info.hint": "Only DeepSeek models are estimated. This session's spend is priced per message at the rate of its Beijing-time peak/off-peak hour: cache-hit input, cache-miss input (including cache writes), and output (including reasoning) are billed separately. Rates follow the August 17 pricing; peak hours are Beijing time Monday–Friday 09:00–12:00 and 14:00–18:00, and weekends are billed at off-peak prices all day.",
|
|
4533
4722
|
"badge.aria": "DeepSeek balance {amount}",
|
|
4534
|
-
"panel.aria": "DeepSeek balance details"
|
|
4723
|
+
"panel.aria": "DeepSeek balance details",
|
|
4724
|
+
"turnCost": "This turn: {amount}",
|
|
4725
|
+
"label.sessionRanking": "Today session spend",
|
|
4726
|
+
"label.sessionRanking.more": "…{count} more sessions"
|
|
4535
4727
|
};
|
|
4536
4728
|
//#endregion
|
|
4537
4729
|
//#region lib/types/client/index.js
|
|
@@ -4542,6 +4734,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4542
4734
|
"locale",
|
|
4543
4735
|
"remote"
|
|
4544
4736
|
];
|
|
4737
|
+
/** Unwrap one Remote result into its value, reporting the endpoint on failure. */
|
|
4738
|
+
function unwrap(endpoint, result) {
|
|
4739
|
+
if (!result.ok) throw new Error(`${endpoint} failed: ${result.error.code}: ${result.error.message}`);
|
|
4740
|
+
return result.value;
|
|
4741
|
+
}
|
|
4545
4742
|
/**
|
|
4546
4743
|
* Client plugin body: mount the `billing` Remote, register the dictionaries,
|
|
4547
4744
|
* and contribute the header utility badge.
|
|
@@ -4555,30 +4752,28 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4555
4752
|
zh,
|
|
4556
4753
|
en
|
|
4557
4754
|
}), "ui-billing: dictionaries");
|
|
4558
|
-
const injected =
|
|
4559
|
-
getBalance: async () =>
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
const result = await billing.getSessionSpend(sessionId);
|
|
4566
|
-
if (!result.ok) throw new Error(`billing.getSessionSpend failed: ${result.error.code}: ${result.error.message}`);
|
|
4567
|
-
return result.value;
|
|
4568
|
-
},
|
|
4569
|
-
getTodaySpend: async (force) => {
|
|
4570
|
-
const result = await billing.getTodaySpend(force);
|
|
4571
|
-
if (!result.ok) throw new Error(`billing.getTodaySpend failed: ${result.error.code}: ${result.error.message}`);
|
|
4572
|
-
return result.value;
|
|
4573
|
-
}
|
|
4574
|
-
});
|
|
4755
|
+
const injected = {
|
|
4756
|
+
getBalance: async () => unwrap("billing.getBalance", await billing.getBalance()),
|
|
4757
|
+
getSessionSpend: async (sessionId) => unwrap("billing.getSessionSpend", await billing.getSessionSpend(sessionId)),
|
|
4758
|
+
getTodaySpend: async (force) => unwrap("billing.getTodaySpend", await billing.getTodaySpend(force)),
|
|
4759
|
+
getTodaySessionsSpend: async (force) => unwrap("billing.getTodaySessionsSpend", await billing.getTodaySessionsSpend(force)),
|
|
4760
|
+
getTurnSpend: async (sessionId, messageId) => unwrap("billing.getTurnSpend", await billing.getTurnSpend(sessionId, messageId))
|
|
4761
|
+
};
|
|
4575
4762
|
ctx.slots.inject("conversation.session.header.utilities", () => ctx.slots.register({
|
|
4576
4763
|
name: "conversation.session.header.utilities",
|
|
4577
4764
|
id: "billing-balance",
|
|
4578
4765
|
order: 30,
|
|
4579
4766
|
locale: NS,
|
|
4580
|
-
inject: injected
|
|
4767
|
+
inject: () => injected
|
|
4581
4768
|
}, BalanceBadge));
|
|
4769
|
+
const turnCostInjected = { getTurnSpend: injected.getTurnSpend };
|
|
4770
|
+
ctx.slots.inject("conversation.chat.assistant-actions", () => ctx.slots.register({
|
|
4771
|
+
name: "conversation.chat.assistant-actions",
|
|
4772
|
+
id: "billing-turn-cost",
|
|
4773
|
+
order: 20,
|
|
4774
|
+
locale: NS,
|
|
4775
|
+
inject: () => turnCostInjected
|
|
4776
|
+
}, TurnCostAction));
|
|
4582
4777
|
}
|
|
4583
4778
|
//#endregion
|
|
4584
4779
|
exports.apply = apply;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Session-header billing badge: balance plus the current conversation's billed spend. */
|
|
2
|
-
import type { DeepSeekBalance, DeepSeekSessionSpend, DeepSeekTodaySpend } from '@rayadesu/dsh-llm-billing/types';
|
|
2
|
+
import type { DeepSeekBalance, DeepSeekSessionSpend, DeepSeekTodaySessionsSpend, DeepSeekTodaySpend, DeepSeekTurnSpend } from '@rayadesu/dsh-llm-billing/types';
|
|
3
3
|
import type { SessionId } from '@deepseek-ai/dsh-session/types';
|
|
4
4
|
import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
5
5
|
import { NS } from './locales.ts';
|
|
@@ -15,9 +15,18 @@ export interface BalanceBadgeInjected {
|
|
|
15
15
|
* passes it, the turn-triggered recompute does not.
|
|
16
16
|
*/
|
|
17
17
|
getTodaySpend: (force?: boolean) => Promise<DeepSeekTodaySpend>;
|
|
18
|
+
/**
|
|
19
|
+
* Read today's billed spend per session, sorted by cost descending; rejects
|
|
20
|
+
* with the Remote error message. `force` behaves as in {@link getTodaySpend}.
|
|
21
|
+
*/
|
|
22
|
+
getTodaySessionsSpend: (force?: boolean) => Promise<DeepSeekTodaySessionsSpend>;
|
|
23
|
+
/** Read one completed Turn's billed cost, located by its closing message id. */
|
|
24
|
+
getTurnSpend: (sessionId: SessionId, messageId: string) => Promise<DeepSeekTurnSpend>;
|
|
18
25
|
}
|
|
19
26
|
/** Full props assembled by the header utilities slot renderer. */
|
|
20
27
|
export type BalanceBadgeProps = PropsRuntime<'conversation.session.header.utilities'> & InjectFace<BalanceBadgeInjected> & PropsLocale<typeof NS>;
|
|
28
|
+
/** How many per-session ranking rows the panel shows before the overflow hint. */
|
|
29
|
+
export declare const SESSION_RANKING_LIMIT = 10;
|
|
21
30
|
/**
|
|
22
31
|
* Render the billing badge in the session-header utilities row. The trigger
|
|
23
32
|
* shows the remaining balance and this conversation's billed spend and opens
|
|
@@ -34,5 +43,5 @@ export type BalanceBadgeProps = PropsRuntime<'conversation.session.header.utilit
|
|
|
34
43
|
* @param props - Remote face, locale, and the standard session-header runtime share.
|
|
35
44
|
* @returns the badge, or null until the first balance fetch settles.
|
|
36
45
|
*/
|
|
37
|
-
export declare function BalanceBadge({ getBalance, getSessionSpend, getTodaySpend, sessionId, useSession, t }: BalanceBadgeProps): import("react").JSX.Element | null;
|
|
46
|
+
export declare function BalanceBadge({ getBalance, getSessionSpend, getTodaySpend, getTodaySessionsSpend, sessionId, useSession, t }: BalanceBadgeProps): import("react").JSX.Element | null;
|
|
38
47
|
//# sourceMappingURL=BalanceBadge.d.ts.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** Per-turn billed-cost label in the closing assistant message's actions row. */
|
|
2
|
+
import type { DeepSeekTurnSpend } from '@rayadesu/dsh-llm-billing/types';
|
|
3
|
+
import type { SessionId } from '@deepseek-ai/dsh-session/types';
|
|
4
|
+
import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
5
|
+
import { NS } from './locales.ts';
|
|
6
|
+
/** Injected face: one Turn's billed cost through the billing Remote. */
|
|
7
|
+
export interface TurnCostActionInjected {
|
|
8
|
+
/** Read one completed Turn's billed cost, located by its closing message id. */
|
|
9
|
+
getTurnSpend: (sessionId: SessionId, messageId: string) => Promise<DeepSeekTurnSpend>;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Full props of the per-turn cost entry. The slot's owner share
|
|
13
|
+
* (`AssistantActionOwnerProps.messageId`) arrives from ui-conversation's
|
|
14
|
+
* declared `conversation.chat.assistant-actions` slot; the session runtime
|
|
15
|
+
* share adds `sessionId`.
|
|
16
|
+
*/
|
|
17
|
+
export type TurnCostActionProps = PropsRuntime<'conversation.chat.assistant-actions'> & InjectFace<TurnCostActionInjected> & PropsLocale<typeof NS>;
|
|
18
|
+
/**
|
|
19
|
+
* Render one Turn's billed cost as a muted label inside the closing message's
|
|
20
|
+
* actions row, between the built-in copy/branch controls and the message
|
|
21
|
+
* clock. The label appears only after the cost settles and hides again when
|
|
22
|
+
* the Turn priced to zero (no DeepSeek usage); a failed fetch stays hidden so
|
|
23
|
+
* a Remote outage never clutters the row.
|
|
24
|
+
* @param props - the closing message id, the session runtime share, and the injected cost reader.
|
|
25
|
+
* @returns the cost label, or null while loading, on failure, or for zero cost.
|
|
26
|
+
*/
|
|
27
|
+
export declare function TurnCostAction({ messageId, sessionId, getTurnSpend, t }: TurnCostActionProps): import("react").JSX.Element | null;
|
|
28
|
+
//# sourceMappingURL=TurnCostAction.d.ts.map
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import type { Context as ClientContext } from '@deepseek-ai/cordis';
|
|
3
3
|
import { type BillingKey } from './locales.ts';
|
|
4
4
|
export type { BalanceBadgeInjected, BalanceBadgeProps } from './BalanceBadge.tsx';
|
|
5
|
+
export type { TurnCostActionInjected, TurnCostActionProps } from './TurnCostAction.tsx';
|
|
5
6
|
export type { BillingKey } from './locales.ts';
|
|
6
7
|
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
7
8
|
interface LocaleNamespaceMap {
|
|
@@ -12,12 +12,16 @@ export declare const zh: {
|
|
|
12
12
|
readonly 'label.cost.input': "未命中输入 {amount}";
|
|
13
13
|
readonly 'label.cost.output': "输出 {amount}";
|
|
14
14
|
readonly 'stat.none': "暂无消耗记录";
|
|
15
|
+
readonly 'stat.untitled': "未命名";
|
|
15
16
|
readonly 'state.unavailable': "额度不可用";
|
|
16
17
|
readonly 'action.refresh': "刷新";
|
|
17
18
|
readonly 'info.aria': "花费说明";
|
|
18
19
|
readonly 'info.hint': "仅能预估 DeepSeek 相关模型。本会话花费按每条消息的发生时刻(北京时间)所在峰谷时段单价计费:缓存命中输入、未命中输入(含缓存写入)、输出(含推理)分别计价。费率按 8 月 17 日实行的标准,高峰时段为北京时间周一至周五 9:00–12:00、14:00–18:00,周末全天按低谷价计费。";
|
|
19
20
|
readonly 'badge.aria': "DeepSeek 额度:{amount}";
|
|
20
21
|
readonly 'panel.aria': "DeepSeek 额度详情";
|
|
22
|
+
readonly turnCost: "本轮花费:{amount}";
|
|
23
|
+
readonly 'label.sessionRanking': "今日会话花费";
|
|
24
|
+
readonly 'label.sessionRanking.more': "…还有 {count} 个会话";
|
|
21
25
|
};
|
|
22
26
|
/** English dictionary, key-identical to the Chinese source of truth. */
|
|
23
27
|
export declare const en: Record<BillingKey, string>;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rayadesu/dsh-client-ui-billing",
|
|
3
3
|
"description": "Session-header DeepSeek account-balance and conversation-spend badge over the billing Remote",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.4",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "lib/types/index.d.ts",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"access": "public"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
|
-
"@rayadesu/dsh-llm-billing": "^0.2.
|
|
50
|
+
"@rayadesu/dsh-llm-billing": "^0.2.4",
|
|
51
51
|
"@deepseek-ai/dsh-client-locale": "^0.1.1-rc.2",
|
|
52
52
|
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.1-rc.2",
|
|
53
53
|
"@deepseek-ai/dsh-typert-protocol": "^0.1.1-rc.2",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"@deepseek-ai/dsh-session": "^0.1.1-rc.2"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
|
-
"@rayadesu/dsh-llm-billing": "^0.2.
|
|
61
|
+
"@rayadesu/dsh-llm-billing": "^0.2.4",
|
|
62
62
|
"@deepseek-ai/dsh-client-locale": "^0.1.1-rc.2",
|
|
63
63
|
"@deepseek-ai/dsh-client-runtime": "^0.1.1-rc.2",
|
|
64
64
|
"@deepseek-ai/dsh-client-test-runtime": "^0.1.1-rc.2",
|