dsh-ds-balance 2.3.1 → 3.0.0
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.md +84 -178
- package/README_zh.md +140 -0
- package/lib/adapters/host-account.d.ts +22 -1
- package/lib/adapters/host-account.js +13 -0
- package/lib/client.js +619 -461
- package/lib/config.d.ts +28 -19
- package/lib/config.js +27 -19
- package/lib/domain/credential.d.ts +41 -0
- package/lib/domain/credential.js +52 -0
- package/lib/http/handlers.d.ts +33 -2
- package/lib/http/handlers.js +124 -31
- package/lib/http/routes.d.ts +5 -1
- package/lib/http/routes.js +2 -1
- package/lib/http/wire.d.ts +58 -1
- package/lib/http/wire.js +3 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.js +35 -6
- package/lib/ports/account.d.ts +25 -0
- package/lib/services/key-resolver.d.ts +33 -3
- package/lib/services/key-resolver.js +41 -12
- package/lib/services/source-readers.js +3 -3
- package/lib/services/source-selector.d.ts +10 -0
- package/lib/services/source-selector.js +14 -4
- package/lib/services/sources-service.d.ts +148 -0
- package/lib/services/sources-service.js +209 -0
- package/lib/types/client/api-types.d.ts +8 -0
- package/lib/types/client/data.d.ts +72 -0
- package/lib/types/client/icons/currency.d.ts +26 -0
- package/lib/types/client/icons.d.ts +92 -0
- package/lib/types/client/locales.d.ts +15 -13
- package/lib/types/client/model.d.ts +10 -0
- package/lib/types/client/settings/AccountAvatar.d.ts +27 -0
- package/lib/types/client/settings/BalanceSettingsCard.d.ts +8 -1
- package/lib/types/client/settings/fields.d.ts +70 -39
- package/lib/types/client/settings/source-status.d.ts +43 -0
- package/lib/types/client/settings/use-config-form.d.ts +1 -27
- package/lib/types/client/settings/use-sources.d.ts +38 -0
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +32 -32
- package/README_en.md +0 -237
- package/lib/types/client/settings/use-credential-state.d.ts +0 -36
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 取数途径的状态服务。
|
|
3
|
+
*
|
|
4
|
+
* **这是「连接」组唯一的事实出口**:两条途径(API Key / 账号登录)的只读状态都从这里出去,
|
|
5
|
+
* 界面不再自己拼判据、也不再跨端点拼两半。
|
|
6
|
+
*
|
|
7
|
+
* 为什么值得单独一层:
|
|
8
|
+
* - 判据本来散在三处(客户端的 `use-credential-state`、卡片的徽章映射、`data.ts` 的形状守卫),
|
|
9
|
+
* 每处都能各自漂;收敛到这里之后只剩一进一出。
|
|
10
|
+
* - 只有后端同时持有 `credentials` 与 `deepseekAccount` 两个接缝,所以只有它能一次答完。
|
|
11
|
+
* - `revision` 是**凭据世代**:凭据库变了或账号登录态变了就 +1。界面拿它当「有没有东西换了」
|
|
12
|
+
* 的判据 —— 这样密钥本身永远不必进 React 的依赖串(那是既有决策,
|
|
13
|
+
* 见 [边界记录](../../.agents/notes/2026-10-01-boundaries-left-as-is.md) 第 3 条)。
|
|
14
|
+
*
|
|
15
|
+
* **明文密钥只在本文件的生命周期内出现一次**:解析 → 折成掩码与账本标识 → 丢掉。
|
|
16
|
+
* 掩码之外的任何形态都不出这个函数,见 [domain/credential.ts](../domain/credential.ts)。
|
|
17
|
+
*
|
|
18
|
+
* **判据只有一份**:值、来源层、配没配,全部由解析链([key-resolver.ts](key-resolver.ts))
|
|
19
|
+
* 与官方 `describe()` 回答。本文件不自己再读一遍环境变量或配置。
|
|
20
|
+
* @module dsh-ds-balance/services/sources-service
|
|
21
|
+
*/
|
|
22
|
+
import { maskKey } from '../domain/credential.js';
|
|
23
|
+
import { ACCOUNT_SOURCE, HTTP_SOURCE } from './source-selector.js';
|
|
24
|
+
import { computeAccountTag } from './account-tag.js';
|
|
25
|
+
/**
|
|
26
|
+
* 两条途径的状态。
|
|
27
|
+
*
|
|
28
|
+
* 缓存策略是「按需重建」而不是定时刷新:`snapshot()` 在没有缓存时读一次,
|
|
29
|
+
* `refresh()` 强制重读。**账号身份是一次真上游请求**(宿主 `getProfile` 每次打 Platform),
|
|
30
|
+
* 所以它必须被缓存 —— 渲染一次问一次是不行的。
|
|
31
|
+
*/
|
|
32
|
+
export class SourcesService {
|
|
33
|
+
options;
|
|
34
|
+
/** 当前缓存;`undefined` 表示还没读过。 */
|
|
35
|
+
cache;
|
|
36
|
+
/** 凭据世代。凭据或登录态一变就 +1,界面据此知道「有东西换了」。 */
|
|
37
|
+
generation = 0;
|
|
38
|
+
constructor(options) {
|
|
39
|
+
this.options = options;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* 当前快照;没有缓存时先读一次。
|
|
43
|
+
*
|
|
44
|
+
* 读失败**不抛**:界面读不到状态时应当显示「未确认」而不是整张卡片消失。
|
|
45
|
+
* 所以失败会退化成一份保守快照(两条途径都「读不到」),并记一笔 debug。
|
|
46
|
+
* @returns 快照。
|
|
47
|
+
*/
|
|
48
|
+
async snapshot() {
|
|
49
|
+
if (this.cache !== undefined)
|
|
50
|
+
return this.cache;
|
|
51
|
+
return await this.refresh();
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* 强制重读一次并换掉缓存。
|
|
55
|
+
*
|
|
56
|
+
* 调用时机:卡片打开、窗口重新获得焦点、点 **Test**、以及凭据变更事件之后。
|
|
57
|
+
* @returns 新快照。
|
|
58
|
+
*/
|
|
59
|
+
async refresh() {
|
|
60
|
+
const key = await this.readKey();
|
|
61
|
+
const account = await this.readAccount();
|
|
62
|
+
const snapshot = { revision: this.generation, key, account };
|
|
63
|
+
this.cache = snapshot;
|
|
64
|
+
return snapshot;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* 现读生效的凭据引用名。
|
|
68
|
+
*
|
|
69
|
+
* 订阅方拿它判「这条事件是不是与我有关」:事件只给引用名,不给值。
|
|
70
|
+
* @returns 引用名;读不到时回空串。
|
|
71
|
+
*/
|
|
72
|
+
keyRef() {
|
|
73
|
+
return this.currentRef();
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* **这条取数路用的是哪一份凭据** —— 给余额响应标注来路的那个事实。
|
|
77
|
+
*
|
|
78
|
+
* 它跟数字一起走(`GET /api/v1/balance` 的 `sourceIdentity`),而不是让界面另外去读一次
|
|
79
|
+
* `/api/v1/sources`:两个缓存各自刷新时,**标签会先跳、数字还停在旧的**,
|
|
80
|
+
* 那就是在标错来源 —— 比不标更糟。
|
|
81
|
+
*
|
|
82
|
+
* 回的是**不可逆的掩码**(首尾片段)或账号名,不是密钥本身。
|
|
83
|
+
* 认不出这条路、或这条路没有身份时回 `null`(界面据此退回「(API Key)」那种路名标签)。
|
|
84
|
+
* @param source - 这一份数字来自哪条路。
|
|
85
|
+
* @returns 掩码 key / 账号名;没有则 null。
|
|
86
|
+
*/
|
|
87
|
+
async identityOf(source) {
|
|
88
|
+
try {
|
|
89
|
+
const snapshot = await this.snapshot();
|
|
90
|
+
if (source === HTTP_SOURCE)
|
|
91
|
+
return snapshot.key.masked === '' ? null : snapshot.key.masked;
|
|
92
|
+
if (source === ACCOUNT_SOURCE)
|
|
93
|
+
return snapshot.account.name;
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// 读不到身份只是少一个标签,绝不能连累余额响应本身。
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* 作废缓存并把世代 +1。
|
|
103
|
+
*
|
|
104
|
+
* 宿主侧订阅到凭据变更(或账号登出)时调它。**下一次读取会拿到新世代**,
|
|
105
|
+
* 所以界面靠「世代变了」就能知道该重读,不必把密钥放进依赖串。
|
|
106
|
+
* @returns 新的世代号。
|
|
107
|
+
*/
|
|
108
|
+
invalidate() {
|
|
109
|
+
this.generation += 1;
|
|
110
|
+
this.cache = undefined;
|
|
111
|
+
return this.generation;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* 读 Key 那条途径。
|
|
115
|
+
*
|
|
116
|
+
* **判据只有一份:解析链自己**(`resolveWithSource()` 一并回报值与来源层)。
|
|
117
|
+
* 这里不自己再读一遍环境变量 —— 那是第二份判据,两者迟早会漂。
|
|
118
|
+
*
|
|
119
|
+
* 掩码与账本标识取自链拿到的明文;解析失败只表示「这条路此刻没凭据」,不是错误。
|
|
120
|
+
* @returns 状态。
|
|
121
|
+
*/
|
|
122
|
+
async readKey() {
|
|
123
|
+
const ref = this.currentRef();
|
|
124
|
+
let plain = '';
|
|
125
|
+
let source = null;
|
|
126
|
+
try {
|
|
127
|
+
const resolved = await this.options.keys.resolveWithSource();
|
|
128
|
+
plain = resolved.value;
|
|
129
|
+
source = resolved.source;
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
// 解析链没取到值就是「这条路没凭据」;来源层留 null(界面显示「状态未确认」)。
|
|
133
|
+
}
|
|
134
|
+
// 链给不出值时,仍问一次 describe 判「配没配」:凭据存储里可能有值,
|
|
135
|
+
// 只是解析链因为别的原因没走到(例如引用名形状被判非法)。
|
|
136
|
+
const described = plain === '' ? await this.describe(ref) : null;
|
|
137
|
+
return {
|
|
138
|
+
ref,
|
|
139
|
+
configured: plain !== '' || described?.configured === true,
|
|
140
|
+
source: source ?? described?.source ?? null,
|
|
141
|
+
masked: maskKey(plain),
|
|
142
|
+
tag8: plain === '' ? '' : computeAccountTag(this.options.salt, plain).slice(0, 8),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
/** 现读生效的引用名;配置读不到时回落 schema 默认值。 */
|
|
146
|
+
currentRef() {
|
|
147
|
+
try {
|
|
148
|
+
const ref = this.options.keys.describeRef();
|
|
149
|
+
return ref === '' ? '' : ref;
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
return '';
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* 问官方凭据服务要来源层。
|
|
157
|
+
*
|
|
158
|
+
* 失败一律回 `null`(端口缺席、引用名非法、远程拒绝):界面据此退化成
|
|
159
|
+
* 「未确认」,而不是把整张卡片打挂。
|
|
160
|
+
* @param ref - 生效的引用名。
|
|
161
|
+
* @returns 来源层与可写性,或 `null`。
|
|
162
|
+
*/
|
|
163
|
+
async describe(ref) {
|
|
164
|
+
const credentials = this.options.credentials;
|
|
165
|
+
if (credentials === undefined || ref === '')
|
|
166
|
+
return null;
|
|
167
|
+
try {
|
|
168
|
+
const described = await credentials.describe(ref);
|
|
169
|
+
return { configured: described.configured === true, source: described.source ?? null };
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
this.options.logger?.debug('ds-balance: credential describe failed', {
|
|
173
|
+
ref,
|
|
174
|
+
error: String(error),
|
|
175
|
+
});
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* 读账号那条途径。
|
|
181
|
+
*
|
|
182
|
+
* `signedIn()` 是**本地读**(宿主只读一次存储记录,不打网络);`profile()` 才是上游请求。
|
|
183
|
+
* 所以未登录时**不问身份** —— 省掉一次注定失败的 Platform 调用。
|
|
184
|
+
* @returns 状态。
|
|
185
|
+
*/
|
|
186
|
+
async readAccount() {
|
|
187
|
+
const account = this.options.account();
|
|
188
|
+
if (account === undefined) {
|
|
189
|
+
// 宿主没装账号接缝:整条途径不存在。界面据此显示「不可用」而不是让它消失 ——
|
|
190
|
+
// 消失会让人以为是 bug。
|
|
191
|
+
return { available: false, signedIn: false, name: null, avatarUrl: null, contact: null };
|
|
192
|
+
}
|
|
193
|
+
// `signedIn()` 抛错时当未登录:这条路读不到就是「此刻用不上」,
|
|
194
|
+
// 不是「整个端点坏掉」—— 端点还得把 Key 那条途径报出去。
|
|
195
|
+
const signedIn = await account.signedIn().catch(() => false);
|
|
196
|
+
if (!signedIn) {
|
|
197
|
+
return { available: true, signedIn: false, name: null, avatarUrl: null, contact: null };
|
|
198
|
+
}
|
|
199
|
+
const identity = await account.profile().catch(() => null);
|
|
200
|
+
return {
|
|
201
|
+
available: true,
|
|
202
|
+
signedIn: true,
|
|
203
|
+
name: identity?.name ?? null,
|
|
204
|
+
avatarUrl: identity?.avatarUrl ?? null,
|
|
205
|
+
contact: identity?.contact ?? null,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
//# sourceMappingURL=sources-service.js.map
|
|
@@ -51,6 +51,14 @@ export interface BalanceResponse {
|
|
|
51
51
|
schemaVersion: number;
|
|
52
52
|
/** 这一份数字是哪条取数路给的。标题里的来源标签读它。 */
|
|
53
53
|
source: BalanceSource;
|
|
54
|
+
/**
|
|
55
|
+
* **这条路上用的凭据是哪一份**:掩码 key(首尾片段、不可逆)或账号名。
|
|
56
|
+
*
|
|
57
|
+
* **旧宿主没有这个字段**(客户端 HMR 换新、宿主重启才换),所以类型上可选,
|
|
58
|
+
* 读它必须过形状守卫 —— 见 [model.ts](model.ts) 的 `sourceIdentityOf`。
|
|
59
|
+
* 没有它时界面退回 `source` 那个路名标签(「(API Key)」),那句话仍然为真。
|
|
60
|
+
*/
|
|
61
|
+
sourceIdentity?: string | null;
|
|
54
62
|
/**
|
|
55
63
|
* 处境:界面唯一的分支入口。
|
|
56
64
|
*
|
|
@@ -16,6 +16,10 @@ export declare const BALANCE_PATH = "/api/v1/balance";
|
|
|
16
16
|
export declare const REFRESH_PATH = "/api/v1/balance/refresh";
|
|
17
17
|
/** 配置读端点。界面只从这里取「凭据可不可写」这一件事。 */
|
|
18
18
|
export declare const CONFIG_PATH = "/api/v1/config";
|
|
19
|
+
/** 两条取数途径的状态端点。「连接」组的唯一事实出口。 */
|
|
20
|
+
export declare const SOURCES_PATH = "/api/v1/sources";
|
|
21
|
+
/** 测连接端点。**真打上游**,不是本地模拟。 */
|
|
22
|
+
export declare const TEST_PATH = "/api/v1/test-connection";
|
|
19
23
|
/** 可替换的 fetch,便于测试注入。 */
|
|
20
24
|
export type FetchLike = (input: string, init?: RequestInit) => Promise<Response>;
|
|
21
25
|
/**
|
|
@@ -45,6 +49,48 @@ export interface RefreshResult {
|
|
|
45
49
|
cooldownMs: number;
|
|
46
50
|
state: BalanceState;
|
|
47
51
|
}
|
|
52
|
+
/** 一条取数途径的只读状态。与后端 `WireKeySource` 同形。 */
|
|
53
|
+
export interface KeySourceInfo {
|
|
54
|
+
ref: string;
|
|
55
|
+
configured: boolean;
|
|
56
|
+
source: string | null;
|
|
57
|
+
/** 首尾片段掩码,如 `sk-cf08d*****3b84`;未配置时是空串。 */
|
|
58
|
+
masked: string;
|
|
59
|
+
/** 账本标识前 8 位;未配置时是空串。 */
|
|
60
|
+
tag8: string;
|
|
61
|
+
}
|
|
62
|
+
/** 账号那条途径的只读状态。与后端 `WireAccountSource` 同形。 */
|
|
63
|
+
export interface AccountSourceInfo {
|
|
64
|
+
available: boolean;
|
|
65
|
+
signedIn: boolean;
|
|
66
|
+
name: string | null;
|
|
67
|
+
avatarUrl: string | null;
|
|
68
|
+
contact: string | null;
|
|
69
|
+
}
|
|
70
|
+
/** `GET /api/v1/sources` 的响应里本插件消费的那部分。 */
|
|
71
|
+
export interface SourcesResponse {
|
|
72
|
+
/** 凭据世代;变了就说明有东西换了。`-1` 表示这份快照不可信。 */
|
|
73
|
+
revision: number;
|
|
74
|
+
key: KeySourceInfo;
|
|
75
|
+
account: AccountSourceInfo;
|
|
76
|
+
}
|
|
77
|
+
/** 一次测连接的结果。 */
|
|
78
|
+
export interface TestResult {
|
|
79
|
+
source: 'deepseek-http' | 'deepseek-account';
|
|
80
|
+
ok: boolean;
|
|
81
|
+
latencyMs: number;
|
|
82
|
+
code?: string;
|
|
83
|
+
message?: string;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* 「读不到状态」时的保守快照。
|
|
87
|
+
*
|
|
88
|
+
* 两条途径都说「读不到」,世代 `-1` 让界面认得出这份不可信。
|
|
89
|
+
* **不抛错、不返回 null**:卡片读不到状态时该显示「未确认」,
|
|
90
|
+
* 而不是让整张卡片消失(与 `credential` 段同一策略)。
|
|
91
|
+
* @returns 保守快照。
|
|
92
|
+
*/
|
|
93
|
+
export declare function unknownSources(): SourcesResponse;
|
|
48
94
|
/** 端点拿不到、或回的不是 JSON 时用的错误码。 */
|
|
49
95
|
export declare const UNREACHABLE_CODE = "PLUGIN_UNREACHABLE";
|
|
50
96
|
/**
|
|
@@ -110,3 +156,29 @@ export declare function requestRefresh(options?: {
|
|
|
110
156
|
provider?: string | undefined;
|
|
111
157
|
fetchImpl?: FetchLike;
|
|
112
158
|
}): Promise<RefreshResult>;
|
|
159
|
+
/**
|
|
160
|
+
* 读一次两条途径的状态。
|
|
161
|
+
*
|
|
162
|
+
* `refresh` 为真时让后端强制重读(绕过它的缓存)—— 卡片打开、窗口重新获得焦点、
|
|
163
|
+
* 点 **Test** 时都用它。这正是修掉「在官方页换了 key,这里还显示旧的那把」的那一环。
|
|
164
|
+
* @param options - 是否强制重读,以及可注入的 fetch。
|
|
165
|
+
* @returns 两条途径的状态;形状不对时退化成保守快照。
|
|
166
|
+
* @throws 端点不可达、非 2xx 或响应不是 JSON。
|
|
167
|
+
*/
|
|
168
|
+
export declare function requestSources(options?: {
|
|
169
|
+
refresh?: boolean;
|
|
170
|
+
fetchImpl?: FetchLike;
|
|
171
|
+
}): Promise<SourcesResponse>;
|
|
172
|
+
/**
|
|
173
|
+
* 测一条途径的连接。**真打上游。**
|
|
174
|
+
*
|
|
175
|
+
* 这是对从前那个「本地字符串检查 + 800ms 假延迟」的替换:界面上那个按钮
|
|
176
|
+
* 现在读的是后端已经写好、也已经测过的真端点。
|
|
177
|
+
* @param source - 测哪条途径。
|
|
178
|
+
* @param options - 可注入的 fetch。
|
|
179
|
+
* @returns 结果;失败也是一份合法结果(`ok: false` + `code`)。
|
|
180
|
+
* @throws 端点不可达、非 2xx 或响应不是 JSON。
|
|
181
|
+
*/
|
|
182
|
+
export declare function requestTest(source: 'deepseek-http' | 'deepseek-account', options?: {
|
|
183
|
+
fetchImpl?: FetchLike;
|
|
184
|
+
}): Promise<TestResult>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 自绘的货币图标(「展示」组用)。
|
|
3
|
+
*
|
|
4
|
+
* **为什么自绘**:官方图标集里没有任何货币语义的字形 —— 逐个核对过
|
|
5
|
+
* `@deepseek-ai/dsh-client-ui-primitives` 的全部图标导出,无 Coin / Money / Currency /
|
|
6
|
+
* Wallet / Yuan / Dollar。而「展示」这一组讲的正是**显示币种**,
|
|
7
|
+
* 用 `IconDataOutlineRegular`(数据)或 `IconGaugeOutlineRegular`(量程)都是在将就。
|
|
8
|
+
*
|
|
9
|
+
* **规格逐条照官方** `ui-primitives/src/icons/index.tsx` 的写法(红线会逐值比对):
|
|
10
|
+
* - `viewBox="0 0 16 16"`(官方统一网格)
|
|
11
|
+
* - `size` 默认 **14**(官方 `Icon*Artwork` 的默认值)
|
|
12
|
+
* - `strokeWidth` 走 {@link ICON_REGULAR_STROKE}(= 1)
|
|
13
|
+
* - `stroke="currentColor"` + `fill="none"`(颜色随上下文)
|
|
14
|
+
* - `aria-hidden="true"`(装饰性字形,语义由所在行的标题承担)
|
|
15
|
+
*
|
|
16
|
+
* 字形取**双横杠的 ¥**:它在中日英三种语境下都是「货币」的第一读法,
|
|
17
|
+
* 比 `$` 更少歧义(`$` 还会被读成 shell 变量或正则锚点)。
|
|
18
|
+
* @module dsh-ds-balance/client/icons/currency
|
|
19
|
+
*/
|
|
20
|
+
import type { IconProps } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
21
|
+
/**
|
|
22
|
+
* 渲染货币图标。
|
|
23
|
+
* @param props - 尺寸与额外类名;颜色随 `currentColor`。
|
|
24
|
+
* @returns 货币字形。
|
|
25
|
+
*/
|
|
26
|
+
export declare function CurrencyOutlineIcon({ size, className }: IconProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 本插件用到的**全部图标,一处定义**。
|
|
3
|
+
*
|
|
4
|
+
* 为什么要有一张表:图标从前散在四处(卡片 JSX、`fields.tsx` 的 `icon={null}`、
|
|
5
|
+
* 选择器的 chevron、浮层的两个按钮),同一个概念在不同文件里各写一份,
|
|
6
|
+
* 改一处必漏一处 —— 而且「哪个组用哪个图标」这件事本身没有地方可查。
|
|
7
|
+
*
|
|
8
|
+
* 取值规则(本仓规则层定的):
|
|
9
|
+
* 1. **官方图标集里有语义对得上的就用官方的**(`@deepseek-ai/dsh-client-ui-primitives`
|
|
10
|
+
* 的 `Icon*OutlineRegular`)—— 本文件只做「选哪个」,不重新画;
|
|
11
|
+
* 2. 官方没有对应语义时**自绘**,且规格逐条照官方
|
|
12
|
+
* (`viewBox="0 0 16 16"`、`strokeWidth={1}`、`stroke="currentColor"`、
|
|
13
|
+
* `aria-hidden="true"`、`size` 默认 14),见 [./icons/CURRENCY_ARTWORK]。
|
|
14
|
+
* 3. 笔画粗细一律用 `Regular`(1px),与配置页其余图标同档;`Medium` 是给大字号场景的。
|
|
15
|
+
*
|
|
16
|
+
* **尺寸**:`DisclosureRow` 的 `.leading svg` 会用自己的 CSS 覆盖 svg 的 width/height
|
|
17
|
+
* (见官方 `DisclosureRow.module.css`),所以传不传 `size` 都不影响分组头;
|
|
18
|
+
* 传它是为了浮层这类直接渲染的地方。
|
|
19
|
+
* @module dsh-ds-balance/client/icons
|
|
20
|
+
*/
|
|
21
|
+
import type { ComponentType } from 'react';
|
|
22
|
+
import type { IconProps } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
23
|
+
import { CurrencyOutlineIcon } from './icons/currency.tsx';
|
|
24
|
+
/** 本插件用到的图标组件形状。 */
|
|
25
|
+
export type AppIcon = ComponentType<IconProps>;
|
|
26
|
+
/**
|
|
27
|
+
* 四个配置分组的图标。
|
|
28
|
+
*
|
|
29
|
+
* **连接用链条**(`IconLinkOutlineRegular`):这一组讲的是「接通官方」,
|
|
30
|
+
* 而它从前用的是 `IconApiOutlineRegular`(`</>`)—— 那个字形讲的是「写代码」,
|
|
31
|
+
* 语义对不上。
|
|
32
|
+
*
|
|
33
|
+
* **展示用自绘货币图标**:官方图标集里**没有**货币语义的字形
|
|
34
|
+
* (逐个核对过 `ui-primitives/src/icons/index.tsx` 的全部导出:无 Coin / Money /
|
|
35
|
+
* Currency / Wallet / Yuan / Dollar),而这一组讲的正是「显示币种」。
|
|
36
|
+
* 仪表盘(`IconGaugeOutlineRegular`)语义偏「量程」,不选。
|
|
37
|
+
*/
|
|
38
|
+
export declare const GROUP_ICONS: {
|
|
39
|
+
readonly connection: (props: IconProps) => import("react").JSX.Element;
|
|
40
|
+
readonly display: typeof CurrencyOutlineIcon;
|
|
41
|
+
readonly thresholds: (props: IconProps) => import("react").JSX.Element;
|
|
42
|
+
readonly refresh: (props: IconProps) => import("react").JSX.Element;
|
|
43
|
+
};
|
|
44
|
+
/** 分组键,与 `BalanceSettingsCard` 的分组顺序一致。 */
|
|
45
|
+
export type GroupIconKey = keyof typeof GROUP_ICONS;
|
|
46
|
+
/**
|
|
47
|
+
* 测试结果的成败图标。
|
|
48
|
+
*
|
|
49
|
+
* **用图形而不是 `✓` / `✗` 字符**:字符的基线、字重、间距都跟着字体走,
|
|
50
|
+
* 与旁边那行等宽掩码并排时会歪,粗细也完全不受我们控制。
|
|
51
|
+
* 用官方那一对**同族的纯字形**(对勾 / 叉):形状本身就说清了成与败,
|
|
52
|
+
* 所以不需要颜色去补(测试结果**不上色**)。取 `Regular` 档,与配置页其余图标同档。
|
|
53
|
+
*
|
|
54
|
+
* **这一对的选择被「声明下限」约束过**:更顺眼的 `IconCloseCircleOutlineRegular`
|
|
55
|
+
* (带圆圈的叉)在宿主更新的一档里才有,**本仓声明的那一档没有**
|
|
56
|
+
* (那一档只有 `IconCloseCircleFillRegular`,与对勾的 outline 不是一个画风)。
|
|
57
|
+
* 引官方符号前先在声明下限那一档确认它存在 —— 这条规则就是这么生效的。
|
|
58
|
+
*/
|
|
59
|
+
export declare const RESULT_ICONS: {
|
|
60
|
+
readonly ok: (props: IconProps) => import("react").JSX.Element;
|
|
61
|
+
readonly fail: (props: IconProps) => import("react").JSX.Element;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* 分组折叠头的两个箭头。
|
|
65
|
+
*
|
|
66
|
+
* **两个都常驻 DOM**,靠 CSS 的透明度互换(收起显下箭头、展开显上箭头)。
|
|
67
|
+
* 不按 `open` 条件渲染:那样悬浮换箭头时元素会被替换,过渡会闪一下、还会让
|
|
68
|
+
* `overflow: hidden` 那一行抖动。
|
|
69
|
+
*
|
|
70
|
+
* 为什么这一对要放在表里而不是写在 `fields.tsx` 里:本文件是「哪个图标用在哪儿」
|
|
71
|
+
* 的唯一出处,`fields.tsx` 只该消费它。
|
|
72
|
+
*/
|
|
73
|
+
export declare const HEADER_CHEVRONS: {
|
|
74
|
+
readonly closed: (props: IconProps) => import("react").JSX.Element;
|
|
75
|
+
readonly open: (props: IconProps) => import("react").JSX.Element;
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* 选择器 pill 右端的 chevron。
|
|
79
|
+
*
|
|
80
|
+
* 与 `Menu` 原语配套:pill 是自绘的,所以箭头也得自己放。
|
|
81
|
+
*/
|
|
82
|
+
export declare const SELECTOR_ICON: AppIcon;
|
|
83
|
+
/**
|
|
84
|
+
* 浮层里的图标。
|
|
85
|
+
*
|
|
86
|
+
* `plugins` 与宿主侧栏 Plugins 条目同字形(跨插件只走服务,但图标是平台模块,
|
|
87
|
+
* 可以照引);`refresh` 与分组头的刷新同源。
|
|
88
|
+
*/
|
|
89
|
+
export declare const POPOVER_ICONS: {
|
|
90
|
+
readonly plugins: (props: IconProps) => import("react").JSX.Element;
|
|
91
|
+
readonly refresh: (props: IconProps) => import("react").JSX.Element;
|
|
92
|
+
};
|
|
@@ -13,6 +13,7 @@ export declare const zh: {
|
|
|
13
13
|
readonly 'popover.title': "账户余额";
|
|
14
14
|
readonly 'popover.source.key': "(API Key)";
|
|
15
15
|
readonly 'popover.source.account': "(账号登录)";
|
|
16
|
+
readonly 'popover.source.identity': "({identity})";
|
|
16
17
|
readonly 'popover.total': "余额";
|
|
17
18
|
readonly 'popover.granted': "赠送";
|
|
18
19
|
readonly 'popover.toppedUp': "充值";
|
|
@@ -35,9 +36,6 @@ export declare const zh: {
|
|
|
35
36
|
readonly 'settings.group.refresh': "刷新";
|
|
36
37
|
readonly 'settings.group.display': "展示";
|
|
37
38
|
readonly 'settings.group.thresholds': "阈值";
|
|
38
|
-
readonly 'settings.field.apiKey': "API Key";
|
|
39
|
-
readonly 'settings.field.apiKeyRef': "API 密钥引用名";
|
|
40
|
-
readonly 'settings.field.baseUrl': "接口地址";
|
|
41
39
|
readonly 'settings.field.serverRefreshSeconds': "服务端刷新频率(秒)";
|
|
42
40
|
readonly 'settings.field.clientPollSeconds': "客户端轮询频率(秒)";
|
|
43
41
|
readonly 'settings.field.manualRefreshCooldownSeconds': "手动刷新冷却(秒)";
|
|
@@ -46,8 +44,20 @@ export declare const zh: {
|
|
|
46
44
|
readonly 'settings.field.cnyCritical': "CNY 告急";
|
|
47
45
|
readonly 'settings.field.usdWarn': "USD 预警";
|
|
48
46
|
readonly 'settings.field.usdCritical': "USD 告急";
|
|
49
|
-
readonly 'settings.
|
|
50
|
-
readonly 'settings.
|
|
47
|
+
readonly 'settings.sources.title': "取数途径";
|
|
48
|
+
readonly 'settings.sources.key': "API Key";
|
|
49
|
+
readonly 'settings.sources.account': "账号登录";
|
|
50
|
+
readonly 'settings.sources.groupNote': "两条途径都跟随官方:密钥用官方模型页配好的那一份,账号用宿主已登录的那个。本插件不自己保存凭据。";
|
|
51
|
+
readonly 'settings.sources.sourceSaved': "取自官方模型页保存的凭据";
|
|
52
|
+
readonly 'settings.sources.sourceEnv': "取自启动环境变量 {name}";
|
|
53
|
+
readonly 'settings.sources.sourceUnknown': "来源未确认";
|
|
54
|
+
readonly 'settings.sources.accountNoService': "本部署没有账号服务,这条途径不可用。";
|
|
55
|
+
readonly 'settings.test': "测试";
|
|
56
|
+
readonly 'settings.testing': "正在测试…";
|
|
57
|
+
readonly 'settings.test.ok': "{ms}ms";
|
|
58
|
+
readonly 'settings.test.noKey': "没有可用凭据";
|
|
59
|
+
readonly 'settings.test.notSignedIn': "尚未登录";
|
|
60
|
+
readonly 'settings.test.fail': "连接失败:{message}";
|
|
51
61
|
readonly 'settings.hint.displayCurrency': "所选币种不存在时,回落到账户实际持有的币种。";
|
|
52
62
|
readonly 'settings.hint.threshold': "后端判定;界面颜色跟随返回的严重度。";
|
|
53
63
|
readonly 'settings.hint.thresholdPair': "告急值必须低于预警值";
|
|
@@ -55,14 +65,6 @@ export declare const zh: {
|
|
|
55
65
|
readonly 'settings.hint.clientPollSeconds': "刷新显示的间隔;正常时只读缓存,读不到时才访问上游。";
|
|
56
66
|
readonly 'settings.hint.manualRefreshCooldownSeconds': "两次手动刷新之间的最短间隔。";
|
|
57
67
|
readonly 'settings.currency.auto': "自动(跟随账户)";
|
|
58
|
-
readonly 'settings.credential.configured': "已配置密钥。";
|
|
59
|
-
readonly 'settings.credential.notConfigured': "未配置密钥。";
|
|
60
|
-
readonly 'settings.hint.credential': "不写入设置文件。留空表示保持当前密钥。";
|
|
61
|
-
readonly 'settings.group.customized': "自定义设置";
|
|
62
|
-
readonly 'settings.test': "测试连接";
|
|
63
|
-
readonly 'settings.testing': "测试中…";
|
|
64
|
-
readonly 'settings.test.ok': "连接正常。";
|
|
65
|
-
readonly 'settings.test.fail': "连接失败:{message}";
|
|
66
68
|
readonly 'settings.save': "保存";
|
|
67
69
|
readonly 'settings.saving': "保存中…";
|
|
68
70
|
readonly 'settings.failed': "保存失败,请重试。";
|
|
@@ -102,6 +102,16 @@ export type SourceLabelKey = 'popover.source.key' | 'popover.source.account';
|
|
|
102
102
|
* @returns 词典键,或 `null` 表示不标。
|
|
103
103
|
*/
|
|
104
104
|
export declare function sourceLabelKeyOf(source: string | undefined | null): SourceLabelKey | null;
|
|
105
|
+
/**
|
|
106
|
+
* 读响应里那条「这份数字用的是哪一份凭据」(掩码 key / 账号名)。
|
|
107
|
+
*
|
|
108
|
+
* **形状守卫**:这个字段是后加的,而客户端半边由 HMR 立刻换新、宿主半边要重启才换 ——
|
|
109
|
+
* 于是「新客户端 + 旧宿主」是合法中间态,旧宿主根本不回这个字段。
|
|
110
|
+
* 不是非空字符串就当没有(界面退回路名标签),绝不把 `undefined` 漏进渲染。
|
|
111
|
+
* @param response - 余额响应。
|
|
112
|
+
* @returns 身份;没有或形状不对时 `null`。
|
|
113
|
+
*/
|
|
114
|
+
export declare function sourceIdentityOf(response: BalanceResponse): string | null;
|
|
105
115
|
/**
|
|
106
116
|
* 一次手动刷新之后的冷却截止时刻。
|
|
107
117
|
*
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 账号头像。
|
|
3
|
+
*
|
|
4
|
+
* 规格逐值照官方 `ui-settings-account/AccountAvatar.module.css` 与
|
|
5
|
+
* `AccountSection.module.css` 的 `.avatar`:32×32、`border-radius: 50%`、
|
|
6
|
+
* `corner-shape: round`(**必须是真的圆**,不能继承宿主的 superellipse)、
|
|
7
|
+
* `object-fit: cover`、底色 `--dsw-alias-bg-skeleton`。
|
|
8
|
+
*
|
|
9
|
+
* `referrerPolicy="no-referrer"` 与官方一致:头像 URL 由 Platform 给出,
|
|
10
|
+
* 请求它时不该把本页地址带出去。
|
|
11
|
+
*
|
|
12
|
+
* **加载失败回落成空圆底**,不显示碎图 —— 头像缺失是常态(平台可以没配),
|
|
13
|
+
* 而一个破图图标比一个安静的圆底难看得多。失败后**不再重试**(同一个 URL 重试也是失败)。
|
|
14
|
+
* @module dsh-ds-balance/client/settings/AccountAvatar
|
|
15
|
+
*/
|
|
16
|
+
import type { ReactNode } from 'react';
|
|
17
|
+
/** 属性。 */
|
|
18
|
+
export interface AccountAvatarProps {
|
|
19
|
+
/** 头像 URL;`null` 表示平台没给头像。 */
|
|
20
|
+
url: string | null;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 渲染账号头像。
|
|
24
|
+
* @param props - 头像 URL。
|
|
25
|
+
* @returns 头像元素;没有 URL 或加载失败时是一个空圆底。
|
|
26
|
+
*/
|
|
27
|
+
export declare function AccountAvatar({ url }: AccountAvatarProps): ReactNode;
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 「DeepSeek 余额」在 bundle 详情页里的配置卡片:标题与描述由页面画,卡内只有控件与保存。
|
|
3
|
-
*
|
|
3
|
+
* 只做配置与**来源状态**:不展示任何额度信息,也不按阈值给任何东西上色。
|
|
4
4
|
* 分组按使用频率排序:刷新三项有合理默认值,放最后。
|
|
5
|
+
*
|
|
6
|
+
* **「连接」组在 3.0 重做成只读状态展示**:从前它是一列输入框(一个继承官方的只读
|
|
7
|
+
* 凭据框 + 可编辑的接口地址 + 二级「自定义设置」里的引用名),但那些输入要么是假的
|
|
8
|
+
* (接口地址只服务官方、密钥框根本不能填),要么没有使用场景。现在它只回答一件事:
|
|
9
|
+
* **两条取数途径各自通不通**,以及一个真打上游的测试按钮。
|
|
10
|
+
*
|
|
11
|
+
* 判据住在后端(`GET /api/v1/sources`),本文件只渲染 —— 见 [use-sources.ts](use-sources.ts)。
|
|
5
12
|
* @module dsh-ds-balance/client/settings/BalanceSettingsCard
|
|
6
13
|
*/
|
|
7
14
|
import type { LocaleKey } from '../locales.ts';
|