@dshtrading/client-ui-settings 0.1.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/lib/client.js ADDED
@@ -0,0 +1,1329 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@dshtrading/client-ui-settings",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let _deepseek_ai_dsh_client_ui_slots = require("@deepseek-ai/dsh-client-ui-slots");
8
+ let react = require("react");
9
+ let react_jsx_runtime = require("react/jsx-runtime");
10
+ //#region src/client/trading-settings-controller.ts
11
+ /** provider 候选显示名与指引(词典键 + 官网/API Key 申请链接 + 环境变量 + 支持市场;值与 router PROVIDER_VOCABULARY 对齐)。 */
12
+ const PROVIDER_LABELS = [
13
+ {
14
+ id: "binance",
15
+ label: "provider.binance",
16
+ url: "https://binance.com/en/binance-api",
17
+ env: "BINANCE_API_KEY",
18
+ type: "public",
19
+ markets: ["crypto"]
20
+ },
21
+ {
22
+ id: "okx",
23
+ label: "provider.okx",
24
+ url: "https://okx.com/docs-v5/zh/",
25
+ env: "OKX_API_KEY",
26
+ type: "public",
27
+ markets: ["crypto"]
28
+ },
29
+ {
30
+ id: "bybit",
31
+ label: "provider.bybit",
32
+ url: "https://bybit.com/en/api-overview",
33
+ env: "BYBIT_API_KEY",
34
+ type: "public",
35
+ markets: ["crypto"]
36
+ },
37
+ {
38
+ id: "ccxt",
39
+ label: "provider.ccxt",
40
+ url: "https://ccxt.com",
41
+ env: "CCXT_API_KEY",
42
+ type: "public",
43
+ markets: ["crypto"]
44
+ },
45
+ {
46
+ id: "yahoo",
47
+ label: "provider.yahoo",
48
+ url: "https://finance.yahoo.com",
49
+ type: "public",
50
+ markets: ["us", "hk"]
51
+ },
52
+ {
53
+ id: "alpaca",
54
+ label: "provider.alpaca",
55
+ url: "https://alpaca.markets",
56
+ env: "ALPACA_API_KEY",
57
+ type: "commercial",
58
+ markets: ["us"]
59
+ },
60
+ {
61
+ id: "fmp",
62
+ label: "provider.fmp",
63
+ url: "https://site.financialmodelingprep.com/developer",
64
+ env: "FMP_API_KEY",
65
+ type: "commercial",
66
+ markets: ["us"]
67
+ },
68
+ {
69
+ id: "finnhub",
70
+ label: "provider.finnhub",
71
+ url: "https://finnhub.io/register",
72
+ env: "FINNHUB_API_KEY",
73
+ type: "commercial",
74
+ markets: ["us"]
75
+ },
76
+ {
77
+ id: "polygon",
78
+ label: "provider.polygon",
79
+ url: "https://polygon.io",
80
+ env: "POLYGON_API_KEY",
81
+ type: "commercial",
82
+ markets: ["us"]
83
+ },
84
+ {
85
+ id: "ibkr",
86
+ label: "provider.ibkr",
87
+ url: "https://interactivebrokers.com/campus/ibkr-api-page/cpapi/",
88
+ env: "IBKR_GATEWAY_URL",
89
+ type: "gateway",
90
+ markets: ["us", "hk"]
91
+ },
92
+ {
93
+ id: "stooq",
94
+ label: "provider.stooq",
95
+ url: "https://stooq.com",
96
+ type: "public",
97
+ markets: ["us"]
98
+ },
99
+ {
100
+ id: "tencent",
101
+ label: "provider.tencent",
102
+ url: "https://finance.qq.com",
103
+ type: "public",
104
+ markets: ["cn", "hk"]
105
+ },
106
+ {
107
+ id: "eastmoney",
108
+ label: "provider.eastmoney",
109
+ url: "https://eastmoney.com",
110
+ type: "public",
111
+ markets: ["cn", "hk"]
112
+ },
113
+ {
114
+ id: "tushare",
115
+ label: "provider.tushare",
116
+ url: "https://tushare.pro/register",
117
+ env: "TUSHARE_TOKEN",
118
+ type: "commercial",
119
+ markets: [
120
+ "cn",
121
+ "hk",
122
+ "us"
123
+ ]
124
+ },
125
+ {
126
+ id: "akshare",
127
+ label: "provider.akshare",
128
+ url: "https://akshare.xyz",
129
+ env: "AKSHARE_API_URL",
130
+ type: "public",
131
+ markets: ["cn", "hk"]
132
+ },
133
+ {
134
+ id: "qmt",
135
+ label: "provider.qmt",
136
+ url: "http://127.0.0.1:5800",
137
+ env: "QMT_GATEWAY_URL",
138
+ type: "gateway",
139
+ markets: ["cn"]
140
+ },
141
+ {
142
+ id: "futu",
143
+ label: "provider.futu",
144
+ url: "https://futunn.com/download/open-api",
145
+ env: "FUTU_HOST",
146
+ type: "gateway",
147
+ markets: [
148
+ "hk",
149
+ "us",
150
+ "cn"
151
+ ]
152
+ },
153
+ {
154
+ id: "longbridge",
155
+ label: "provider.longbridge",
156
+ url: "https://open.longportapp.com",
157
+ env: "LONGBRIDGE_APP_KEY",
158
+ type: "commercial",
159
+ markets: ["hk", "us"]
160
+ },
161
+ {
162
+ id: "tiger",
163
+ label: "provider.tiger",
164
+ url: "https://developer.itigerup.com",
165
+ env: "TIGER_ID",
166
+ type: "commercial",
167
+ markets: [
168
+ "hk",
169
+ "us",
170
+ "cn"
171
+ ]
172
+ },
173
+ {
174
+ id: "hithink",
175
+ label: "provider.hithink",
176
+ url: "https://fuyao.aicubes.cn",
177
+ env: "HITHINK_FINANCE_API_KEY",
178
+ type: "commercial",
179
+ markets: ["cn"]
180
+ }
181
+ ];
182
+ const PROVIDER_CREDENTIAL_SPECS = {
183
+ binance: [{
184
+ key: "apiKey",
185
+ label: "field.label.apiKey",
186
+ placeholder: "field.placeholder.binanceKey",
187
+ secret: true
188
+ }, {
189
+ key: "apiSecret",
190
+ label: "field.label.apiSecret",
191
+ placeholder: "field.placeholder.binanceSecret",
192
+ secret: true
193
+ }],
194
+ okx: [
195
+ {
196
+ key: "apiKey",
197
+ label: "field.label.apiKey",
198
+ placeholder: "field.placeholder.okxKey",
199
+ secret: true
200
+ },
201
+ {
202
+ key: "secretKey",
203
+ label: "field.label.secretKey",
204
+ placeholder: "field.placeholder.okxSecret",
205
+ secret: true
206
+ },
207
+ {
208
+ key: "passphrase",
209
+ label: "field.label.passphrase",
210
+ placeholder: "field.placeholder.okxPassphrase",
211
+ secret: true
212
+ }
213
+ ],
214
+ bybit: [{
215
+ key: "apiKey",
216
+ label: "field.label.apiKey",
217
+ placeholder: "field.placeholder.bybitKey",
218
+ secret: true
219
+ }, {
220
+ key: "apiSecret",
221
+ label: "field.label.apiSecret",
222
+ placeholder: "field.placeholder.bybitSecret",
223
+ secret: true
224
+ }],
225
+ ccxt: [{
226
+ key: "apiKey",
227
+ label: "field.label.apiKey",
228
+ placeholder: "field.placeholder.ccxtKey",
229
+ secret: true
230
+ }, {
231
+ key: "apiSecret",
232
+ label: "field.label.apiSecret",
233
+ placeholder: "field.placeholder.ccxtSecret",
234
+ secret: true
235
+ }],
236
+ alpaca: [{
237
+ key: "apiKey",
238
+ label: "field.label.apiKey",
239
+ placeholder: "field.placeholder.alpacaKey",
240
+ secret: true
241
+ }, {
242
+ key: "secretKey",
243
+ label: "field.label.secretKey",
244
+ placeholder: "field.placeholder.alpacaSecret",
245
+ secret: true
246
+ }],
247
+ fmp: [{
248
+ key: "apiKey",
249
+ label: "field.label.apiKey",
250
+ placeholder: "field.placeholder.fmpKey",
251
+ secret: true
252
+ }],
253
+ finnhub: [{
254
+ key: "apiKey",
255
+ label: "field.label.apiKey",
256
+ placeholder: "field.placeholder.finnhubKey",
257
+ secret: true
258
+ }],
259
+ polygon: [{
260
+ key: "apiKey",
261
+ label: "field.label.apiKey",
262
+ placeholder: "field.placeholder.polygonKey",
263
+ secret: true
264
+ }],
265
+ tushare: [{
266
+ key: "token",
267
+ label: "field.label.token",
268
+ placeholder: "field.placeholder.tushareToken",
269
+ secret: true
270
+ }],
271
+ akshare: [{
272
+ key: "apiUrl",
273
+ label: "field.label.apiUrl",
274
+ placeholder: "field.placeholder.akshareUrl"
275
+ }],
276
+ qmt: [{
277
+ key: "gatewayUrl",
278
+ label: "field.label.qmtUrl",
279
+ placeholder: "field.placeholder.qmtUrl"
280
+ }],
281
+ futu: [{
282
+ key: "host",
283
+ label: "field.label.futuHost",
284
+ placeholder: "field.placeholder.futuHost"
285
+ }, {
286
+ key: "port",
287
+ label: "field.label.futuPort",
288
+ placeholder: "field.placeholder.futuPort"
289
+ }],
290
+ ibkr: [{
291
+ key: "gatewayUrl",
292
+ label: "field.label.ibkrUrl",
293
+ placeholder: "field.placeholder.ibkrUrl"
294
+ }],
295
+ longbridge: [
296
+ {
297
+ key: "appKey",
298
+ label: "field.label.appKey",
299
+ placeholder: "field.placeholder.longbridgeAppKey",
300
+ secret: true
301
+ },
302
+ {
303
+ key: "appSecret",
304
+ label: "field.label.appSecret",
305
+ placeholder: "field.placeholder.longbridgeAppSecret",
306
+ secret: true
307
+ },
308
+ {
309
+ key: "accessToken",
310
+ label: "field.label.accessToken",
311
+ placeholder: "field.placeholder.longbridgeAccessToken",
312
+ secret: true
313
+ }
314
+ ],
315
+ tiger: [{
316
+ key: "tigerId",
317
+ label: "field.label.tigerId",
318
+ placeholder: "field.placeholder.tigerId"
319
+ }, {
320
+ key: "privateKey",
321
+ label: "field.label.tigerPrivateKey",
322
+ placeholder: "field.placeholder.tigerPrivateKey",
323
+ secret: true
324
+ }],
325
+ hithink: [{
326
+ key: "apiKey",
327
+ label: "field.label.apiKey",
328
+ placeholder: "HITHINK_FINANCE_API_KEY",
329
+ secret: true
330
+ }]
331
+ };
332
+ /** 状态转化:从 settings 快照投射为组件的可观察视图。 */
333
+ function projectSnapshot(snap) {
334
+ const value = snap.value ?? snap.base;
335
+ const user = snap.user ?? {};
336
+ const marketIds = /* @__PURE__ */ new Set([
337
+ ...Object.keys(value?.markets ?? {}),
338
+ ...Object.keys(snap.base?.markets ?? {}),
339
+ ...Object.keys(user.markets ?? {})
340
+ ]);
341
+ const resolved = {};
342
+ const overridden = {};
343
+ const baseMarkets = snap.base?.markets ?? {};
344
+ for (const marketId of marketIds) {
345
+ resolved[marketId] = value?.markets?.[marketId]?.provider ?? baseMarkets[marketId]?.provider;
346
+ overridden[marketId] = user.markets?.[marketId] !== void 0;
347
+ }
348
+ const credentials = value?.credentials ?? {};
349
+ const baseNews = snap.base?.news;
350
+ const news = value?.news ?? baseNews;
351
+ const userNews = (snap.user ?? {}).news;
352
+ return {
353
+ status: snap.status,
354
+ resolved,
355
+ overridden,
356
+ credentials,
357
+ newsKey: news?.cryptoPanicKey,
358
+ newsOverridden: userNews?.cryptoPanicKey !== void 0,
359
+ colorMode: value?.colorMode === "green-up" ? "green-up" : "red-up",
360
+ writable: snap.writable && snap.mode === "host"
361
+ };
362
+ }
363
+ /** 从 settings scope 构建 SnapshotStore(getSnapshot 稳定引用 + subscribe 转发)。 */
364
+ function createTradingSettingsStore(scope) {
365
+ let cached = projectSnapshot(scope.getSnapshot());
366
+ return {
367
+ getSnapshot: () => {
368
+ const current = projectSnapshot(scope.getSnapshot());
369
+ if (JSON.stringify(current) !== JSON.stringify(cached)) cached = current;
370
+ return cached;
371
+ },
372
+ subscribe: (listener) => scope.subscribe(listener)
373
+ };
374
+ }
375
+ //#endregion
376
+ //#region \0dsh-css:/home/runner/work/dsh-trading/dsh-trading/packages/client-ui-settings/src/client/trading-settings.module.css.mjs
377
+ const css$1 = ".zeqDYq_root{--sec-text-primary:var(--dsw-alias-text-primary,#1a1e24);--sec-text-secondary:var(--dsw-alias-text-secondary,#5f6672);--sec-text-muted:var(--dsw-alias-text-muted,#8e95a3);--sec-border:var(--dsw-alias-border-l2,#e3e6ea);--sec-bg-hover:var(--dsw-alias-fill-hover,#0000000a);--sec-bg-active:var(--dsw-alias-primary-bg,#2563eb0f);--sec-primary:var(--dsw-alias-primary,#2563eb);flex-direction:column;gap:16px;padding:4px 0 24px;font-size:13px;display:flex}body[data-ds-dark-theme] .zeqDYq_root,[data-theme=dark] .zeqDYq_root,:root[data-theme=dark] .zeqDYq_root,html.dark .zeqDYq_root,body.dark .zeqDYq_root{--sec-text-primary:var(--dsw-alias-text-primary,#f3f4f6);--sec-text-secondary:var(--dsw-alias-text-secondary,#9ca3af);--sec-text-muted:var(--dsw-alias-text-muted,#6b7280);--sec-border:var(--dsw-alias-border-l2,#ffffff1a);--sec-bg-hover:var(--dsw-alias-fill-hover,#ffffff0f);--sec-bg-active:#2563eb29;--sec-primary:#3b82f6}.zeqDYq_root{color:var(--sec-text-primary)}.zeqDYq_lead{color:var(--sec-text-secondary);margin:0;font-size:13.5px;line-height:1.6}.zeqDYq_empty{color:var(--sec-text-muted);padding:24px 0}.zeqDYq_tabList{border-bottom:1px solid var(--sec-border);gap:6px;margin-top:4px;display:flex}.zeqDYq_tab{appearance:none;color:var(--sec-text-secondary);cursor:pointer;background:0 0;border:none;border-radius:6px 6px 0 0;padding:6px 14px 8px;font-size:13px;font-weight:500;transition:color .15s,background .15s;position:relative}.zeqDYq_tab:hover{color:var(--sec-text-primary);background:var(--sec-bg-hover)}.zeqDYq_tab[data-active=true]{color:var(--sec-primary);font-weight:600}.zeqDYq_tab[data-active=true]:after{content:\"\";background:var(--sec-primary);border-radius:1px;height:2px;position:absolute;bottom:-1px;left:0;right:0}.zeqDYq_tabPanel{padding-top:8px}.zeqDYq_colorModeFieldset{border:1px solid var(--sec-border);border-radius:8px;margin:0;padding:12px 16px 14px}.zeqDYq_colorModeLabel{color:var(--sec-text-primary);padding:0 4px;font-size:13px;font-weight:600}.zeqDYq_colorModeOptions{gap:12px;margin-top:8px;display:flex}.zeqDYq_colorModeOption{border:1px solid var(--sec-border);cursor:pointer;color:var(--sec-text-secondary);border-radius:6px;align-items:center;gap:6px;padding:6px 12px;font-size:13px;transition:border-color .15s,background .15s;display:flex}.zeqDYq_colorModeOption:hover{background:var(--sec-bg-hover)}.zeqDYq_colorModeOption[data-active=true]{border-color:var(--sec-primary);color:var(--sec-text-primary);background:var(--sec-bg-active)}.zeqDYq_colorModeRadio{display:none}.zeqDYq_colorModeSwatch,.zeqDYq_colorModeSwatchDown{border-radius:2px;width:12px;height:12px;display:inline-block}";
378
+ const tagId$1 = "@dshtrading/client-ui-settings/trading-settings.module.css";
379
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
380
+ const tag = document.createElement("style");
381
+ tag.dataset.plugin = "@dshtrading/client-ui-settings";
382
+ tag.dataset.pluginCss = tagId$1;
383
+ tag.textContent = css$1;
384
+ document.head.appendChild(tag);
385
+ }
386
+ var trading_settings_module_css_default = {
387
+ "colorModeSwatch": "zeqDYq_colorModeSwatch",
388
+ "colorModeOptions": "zeqDYq_colorModeOptions",
389
+ "root": "zeqDYq_root",
390
+ "colorModeRadio": "zeqDYq_colorModeRadio",
391
+ "colorModeSwatchDown": "zeqDYq_colorModeSwatchDown",
392
+ "tabList": "zeqDYq_tabList",
393
+ "lead": "zeqDYq_lead",
394
+ "empty": "zeqDYq_empty",
395
+ "tab": "zeqDYq_tab",
396
+ "colorModeLabel": "zeqDYq_colorModeLabel",
397
+ "tabPanel": "zeqDYq_tabPanel",
398
+ "colorModeFieldset": "zeqDYq_colorModeFieldset",
399
+ "colorModeOption": "zeqDYq_colorModeOption"
400
+ };
401
+ //#endregion
402
+ //#region src/client/TradingSettingsSection.tsx
403
+ /**
404
+ * Trading settings section (tab container): the '交易' 一级菜单 host. The
405
+ * section chrome is a tab bar projected from the dshtrading.market.tab slot
406
+ * ledger; each market contributes its own panel (id = market id) and the
407
+ * section renders it through the child slot. New market = new tab registration,
408
+ * no section changes (官方 settings.plugins.tab 模式).
409
+ */
410
+ /** Render the Trading page: color mode selector + market tab bar + active market provider panel. */
411
+ function TradingSettingsSection({ t, renderSlot, useTabs, useController, setColorMode }) {
412
+ const tabsId = (0, react.useId)();
413
+ const tabRefs = (0, react.useRef)([]);
414
+ const rows = useTabs((value) => value);
415
+ const controllerState = useController((value) => value);
416
+ const [activeId, setActiveId] = (0, react.useState)();
417
+ const [visitedIds, setVisitedIds] = (0, react.useState)(() => /* @__PURE__ */ new Set());
418
+ const active = rows.find((row) => row.id === activeId)?.id ?? rows[0]?.id;
419
+ (0, react.useEffect)(() => {
420
+ if (active === void 0) return;
421
+ setVisitedIds((previous) => {
422
+ if (previous.has(active)) return previous;
423
+ return /* @__PURE__ */ new Set([...previous, active]);
424
+ });
425
+ }, [active]);
426
+ const currentColorMode = controllerState.colorMode ?? "red-up";
427
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
428
+ className: trading_settings_module_css_default.root,
429
+ children: [
430
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
431
+ className: trading_settings_module_css_default.lead,
432
+ children: t("lead")
433
+ }),
434
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("fieldset", {
435
+ className: trading_settings_module_css_default.colorModeFieldset,
436
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("legend", {
437
+ className: trading_settings_module_css_default.colorModeLabel,
438
+ children: t("colorMode.label")
439
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
440
+ className: trading_settings_module_css_default.colorModeOptions,
441
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
442
+ className: trading_settings_module_css_default.colorModeOption,
443
+ "data-active": currentColorMode === "red-up" ? "true" : void 0,
444
+ children: [
445
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
446
+ type: "radio",
447
+ name: "dshtrading-color-mode",
448
+ value: "red-up",
449
+ checked: currentColorMode === "red-up",
450
+ onChange: () => {
451
+ setColorMode("red-up");
452
+ },
453
+ className: trading_settings_module_css_default.colorModeRadio
454
+ }),
455
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
456
+ className: trading_settings_module_css_default.colorModeSwatch,
457
+ style: { background: "#e64545" }
458
+ }),
459
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
460
+ className: trading_settings_module_css_default.colorModeSwatchDown,
461
+ style: { background: "#2ba471" }
462
+ }),
463
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("colorMode.redUp") })
464
+ ]
465
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
466
+ className: trading_settings_module_css_default.colorModeOption,
467
+ "data-active": currentColorMode === "green-up" ? "true" : void 0,
468
+ children: [
469
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
470
+ type: "radio",
471
+ name: "dshtrading-color-mode",
472
+ value: "green-up",
473
+ checked: currentColorMode === "green-up",
474
+ onChange: () => {
475
+ setColorMode("green-up");
476
+ },
477
+ className: trading_settings_module_css_default.colorModeRadio
478
+ }),
479
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
480
+ className: trading_settings_module_css_default.colorModeSwatch,
481
+ style: { background: "#2ba471" }
482
+ }),
483
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
484
+ className: trading_settings_module_css_default.colorModeSwatchDown,
485
+ style: { background: "#e64545" }
486
+ }),
487
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("colorMode.greenUp") })
488
+ ]
489
+ })]
490
+ })]
491
+ }),
492
+ rows.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
493
+ className: trading_settings_module_css_default.empty,
494
+ children: t("empty")
495
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
496
+ className: trading_settings_module_css_default.tabList,
497
+ role: "tablist",
498
+ "aria-label": t("tabs"),
499
+ children: rows.map((row, index) => {
500
+ const selected = row.id === active;
501
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
502
+ ref: (element) => {
503
+ tabRefs.current[index] = element;
504
+ },
505
+ id: `${tabsId}-tab-${row.id}`,
506
+ type: "button",
507
+ role: "tab",
508
+ "aria-selected": selected,
509
+ "aria-controls": `${tabsId}-panel-${row.id}`,
510
+ "data-active": selected ? "true" : void 0,
511
+ tabIndex: selected ? 0 : -1,
512
+ className: trading_settings_module_css_default.tab,
513
+ onClick: () => {
514
+ setActiveId(row.id);
515
+ },
516
+ onKeyDown: (event) => {
517
+ let nextIndex;
518
+ switch (event.key) {
519
+ case "ArrowRight":
520
+ nextIndex = (index + 1) % rows.length;
521
+ break;
522
+ case "ArrowLeft":
523
+ nextIndex = (index - 1 + rows.length) % rows.length;
524
+ break;
525
+ case "Home":
526
+ nextIndex = 0;
527
+ break;
528
+ case "End":
529
+ nextIndex = rows.length - 1;
530
+ break;
531
+ default: return;
532
+ }
533
+ event.preventDefault();
534
+ const nextRow = rows[nextIndex];
535
+ const nextTab = tabRefs.current[nextIndex];
536
+ setActiveId(nextRow.id);
537
+ nextTab.focus();
538
+ },
539
+ children: row.label
540
+ }, row.id);
541
+ })
542
+ }), rows.filter((row) => row.id === active || visitedIds.has(row.id)).map((row) => {
543
+ const selected = row.id === active;
544
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
545
+ id: `${tabsId}-panel-${row.id}`,
546
+ role: "tabpanel",
547
+ "aria-labelledby": `${tabsId}-tab-${row.id}`,
548
+ className: trading_settings_module_css_default.tabPanel,
549
+ hidden: !selected,
550
+ children: renderSlot("dshtrading.market.tab", {}, { only: row.id })
551
+ }, row.id);
552
+ })] })
553
+ ]
554
+ });
555
+ }
556
+ //#endregion
557
+ //#region \0dsh-css:/home/runner/work/dsh-trading/dsh-trading/packages/client-ui-settings/src/client/market-provider-panel.module.css.mjs
558
+ const css = "._4qCYrG_panel{--panel-bg-card:var(--dsw-alias-bg-l1,#fff);--panel-bg-card-hover:var(--dsw-alias-fill-hover,#00000005);--panel-bg-card-selected:#2563eb0d;--panel-border-card:var(--dsw-alias-border-l2,#e3e6ea);--panel-border-card-selected:var(--dsw-alias-primary,#2563eb);--panel-text-primary:var(--dsw-alias-text-primary,#1a1e24);--panel-text-secondary:var(--dsw-alias-text-secondary,#5f6672);--panel-text-muted:var(--dsw-alias-text-muted,#8e95a3);--panel-bg-subtle:var(--dsw-alias-fill-hover,#0000000a);--panel-bg-input:var(--dsw-alias-bg-l1,#fff);--panel-border-input:var(--dsw-alias-border-l2,#e3e6ea);--panel-btn-bg:var(--dsw-alias-bg-l1,#fff);--panel-btn-border:var(--dsw-alias-border-l2,#e3e6ea);--panel-btn-text:var(--dsw-alias-text-primary,#1a1e24);flex-direction:column;gap:16px;padding-top:4px;display:flex}body[data-ds-dark-theme] ._4qCYrG_panel,[data-theme=dark] ._4qCYrG_panel,:root[data-theme=dark] ._4qCYrG_panel,html.dark ._4qCYrG_panel,body.dark ._4qCYrG_panel{--panel-bg-card:var(--dsw-alias-bg-l1,#ffffff0a);--panel-bg-card-hover:var(--dsw-alias-fill-hover,#ffffff12);--panel-bg-card-selected:#2563eb29;--panel-border-card:var(--dsw-alias-border-l2,#ffffff1a);--panel-border-card-selected:#3b82f6;--panel-text-primary:var(--dsw-alias-text-primary,#f3f4f6);--panel-text-secondary:var(--dsw-alias-text-secondary,#9ca3af);--panel-text-muted:var(--dsw-alias-text-muted,#6b7280);--panel-bg-subtle:var(--dsw-alias-fill-hover,#ffffff0f);--panel-bg-input:#00000040;--panel-border-input:var(--dsw-alias-border-l2,#ffffff26);--panel-btn-bg:#ffffff14;--panel-btn-border:#ffffff24;--panel-btn-text:var(--dsw-alias-text-primary,#e5e7eb)}._4qCYrG_currentHeader{color:var(--panel-text-secondary);align-items:baseline;gap:8px;font-size:13px;display:flex}._4qCYrG_currentBadge{color:var(--panel-text-primary);background:var(--panel-bg-subtle);border:1px solid var(--panel-border-card);border-radius:4px;padding:2px 8px;font-weight:600}._4qCYrG_grid{grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px;display:grid}._4qCYrG_card{border:1px solid var(--panel-border-card);background:var(--panel-bg-card);cursor:pointer;user-select:none;border-radius:8px;flex-direction:column;gap:8px;padding:12px 14px;transition:border-color .15s,box-shadow .15s,background .15s;display:flex}._4qCYrG_card:hover{border-color:var(--panel-border-card-selected);background:var(--panel-bg-card-hover)}._4qCYrG_card[data-selected=true]{border-color:var(--panel-border-card-selected);background:var(--panel-bg-card-selected);box-shadow:0 2px 10px #2563eb1f}._4qCYrG_cardHeader{align-items:center;gap:8px;display:flex}._4qCYrG_cardHeader input[type=radio]{accent-color:var(--panel-border-card-selected);cursor:pointer;margin:0}._4qCYrG_cardTitle{color:var(--panel-text-primary);flex:1;min-width:0;font-size:13.5px;font-weight:600}._4qCYrG_typeBadge{border:1px solid var(--panel-border-card);background:var(--panel-bg-subtle);color:var(--panel-text-secondary);white-space:nowrap;border-radius:4px;padding:2px 6px;font-size:10.5px;line-height:1}._4qCYrG_cardMeta{color:var(--panel-text-muted);border-top:1px dashed var(--panel-border-card);flex-direction:column;gap:4px;padding-top:6px;font-size:12px;display:flex}._4qCYrG_link{color:var(--dsw-alias-primary,#3b82f6);align-items:center;gap:4px;text-decoration:none;display:inline-flex}._4qCYrG_link:hover{text-decoration:underline}._4qCYrG_envBox{background:var(--panel-bg-subtle);color:var(--panel-text-secondary);word-break:break-all;border:1px solid var(--panel-border-card);border-radius:4px;padding:4px 6px;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:11.5px}._4qCYrG_actions{align-items:center;gap:8px;margin-top:4px;display:flex}._4qCYrG_saveBtn{appearance:none;background:var(--dsw-alias-primary,#2563eb);color:#fff;cursor:pointer;border:1px solid #0000;border-radius:6px;height:32px;padding:0 16px;font-size:13px;font-weight:500;transition:background .15s}._4qCYrG_saveBtn:hover:not(:disabled){background:#1d4ed8}._4qCYrG_saveBtn:disabled{opacity:.45;cursor:default}._4qCYrG_discardBtn{appearance:none;border:1px solid var(--panel-btn-border);background:var(--panel-btn-bg);color:var(--panel-btn-text);cursor:pointer;border-radius:6px;height:32px;padding:0 14px;font-size:13px;transition:background .15s}._4qCYrG_discardBtn:hover:not(:disabled){background:var(--panel-bg-card-hover)}._4qCYrG_discardBtn:disabled{opacity:.45;cursor:default}._4qCYrG_message{color:var(--panel-text-secondary);font-size:12.5px}._4qCYrG_newsSection{border-top:1px solid var(--panel-border-card);flex-direction:column;gap:8px;margin-top:8px;padding-top:16px;display:flex}._4qCYrG_newsLabel{color:var(--panel-text-primary);font-size:13px;font-weight:600}._4qCYrG_newsInput{box-sizing:border-box;border:1px solid var(--panel-border-input);background:var(--panel-bg-input);width:100%;max-width:380px;height:32px;color:var(--panel-text-primary);border-radius:6px;padding:0 10px;font-size:12.5px}._4qCYrG_newsInput:focus{border-color:var(--panel-border-card-selected);outline:none}._4qCYrG_credentialBlock{border-top:1px dashed var(--panel-border-card);flex-direction:column;gap:4px;padding-top:6px;display:flex}._4qCYrG_credentialHeader{justify-content:space-between;align-items:center;display:flex}._4qCYrG_credentialStatus{color:var(--panel-text-muted);align-items:center;gap:4px;font-size:11.5px;display:inline-flex}._4qCYrG_credentialStatus[data-configured=true]{color:#22c55e;font-weight:500}._4qCYrG_credentialToggleBtn{appearance:none;border:1px solid var(--panel-btn-border);background:var(--panel-btn-bg);color:var(--panel-btn-text);cursor:pointer;border-radius:4px;padding:3px 8px;font-size:11.5px;transition:all .15s}._4qCYrG_credentialToggleBtn:hover{border-color:var(--panel-border-card-selected);color:var(--panel-border-card-selected)}._4qCYrG_credentialDrawer{background:var(--panel-bg-subtle);border:1px solid var(--panel-border-card);cursor:default;border-radius:6px;flex-direction:column;gap:8px;margin-top:4px;padding:10px;display:flex}._4qCYrG_credentialFields{flex-direction:column;gap:6px;display:flex}._4qCYrG_fieldRow{flex-direction:column;gap:3px;display:flex}._4qCYrG_fieldLabel{color:var(--panel-text-secondary);font-size:11.5px;font-weight:500}._4qCYrG_inputWrapper{align-items:center;display:flex;position:relative}._4qCYrG_credInput{border:1px solid var(--panel-border-input);background:var(--panel-bg-input);width:100%;height:28px;color:var(--panel-text-primary);border-radius:4px;padding:0 28px 0 8px;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:12px}._4qCYrG_credInput:focus{border-color:var(--panel-border-card-selected);outline:none}._4qCYrG_eyeBtn{appearance:none;cursor:pointer;opacity:.7;background:0 0;border:none;padding:2px;font-size:13px;position:absolute;right:4px}._4qCYrG_eyeBtn:hover{opacity:1}._4qCYrG_credActions{align-items:center;gap:6px;margin-top:4px;display:flex}._4qCYrG_credSaveBtn{appearance:none;background:var(--dsw-alias-primary,#2563eb);color:#fff;cursor:pointer;border:none;border-radius:4px;height:26px;padding:0 10px;font-size:11.5px;font-weight:500}._4qCYrG_credSaveBtn:hover:not(:disabled){background:#1d4ed8}._4qCYrG_credSaveBtn:disabled{opacity:.45;cursor:default}._4qCYrG_credDeleteBtn{appearance:none;color:#ef4444;cursor:pointer;background:#ef44441f;border:1px solid #ef444459;border-radius:4px;height:26px;padding:0 8px;font-size:11.5px}._4qCYrG_credDeleteBtn:hover:not(:disabled){background:#ef444433}._4qCYrG_credDeleteBtn:disabled{opacity:.45;cursor:default}._4qCYrG_credMsg{color:#22c55e;font-size:11.5px}";
559
+ const tagId = "@dshtrading/client-ui-settings/market-provider-panel.module.css";
560
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
561
+ const tag = document.createElement("style");
562
+ tag.dataset.plugin = "@dshtrading/client-ui-settings";
563
+ tag.dataset.pluginCss = tagId;
564
+ tag.textContent = css;
565
+ document.head.appendChild(tag);
566
+ }
567
+ var market_provider_panel_module_css_default = {
568
+ "cardTitle": "_4qCYrG_cardTitle",
569
+ "credInput": "_4qCYrG_credInput",
570
+ "cardMeta": "_4qCYrG_cardMeta",
571
+ "credMsg": "_4qCYrG_credMsg",
572
+ "fieldRow": "_4qCYrG_fieldRow",
573
+ "discardBtn": "_4qCYrG_discardBtn",
574
+ "envBox": "_4qCYrG_envBox",
575
+ "currentHeader": "_4qCYrG_currentHeader",
576
+ "typeBadge": "_4qCYrG_typeBadge",
577
+ "newsLabel": "_4qCYrG_newsLabel",
578
+ "credentialHeader": "_4qCYrG_credentialHeader",
579
+ "credActions": "_4qCYrG_credActions",
580
+ "inputWrapper": "_4qCYrG_inputWrapper",
581
+ "eyeBtn": "_4qCYrG_eyeBtn",
582
+ "fieldLabel": "_4qCYrG_fieldLabel",
583
+ "link": "_4qCYrG_link",
584
+ "currentBadge": "_4qCYrG_currentBadge",
585
+ "credDeleteBtn": "_4qCYrG_credDeleteBtn",
586
+ "card": "_4qCYrG_card",
587
+ "credentialBlock": "_4qCYrG_credentialBlock",
588
+ "credentialDrawer": "_4qCYrG_credentialDrawer",
589
+ "credSaveBtn": "_4qCYrG_credSaveBtn",
590
+ "actions": "_4qCYrG_actions",
591
+ "grid": "_4qCYrG_grid",
592
+ "newsSection": "_4qCYrG_newsSection",
593
+ "credentialToggleBtn": "_4qCYrG_credentialToggleBtn",
594
+ "credentialStatus": "_4qCYrG_credentialStatus",
595
+ "panel": "_4qCYrG_panel",
596
+ "newsInput": "_4qCYrG_newsInput",
597
+ "credentialFields": "_4qCYrG_credentialFields",
598
+ "saveBtn": "_4qCYrG_saveBtn",
599
+ "message": "_4qCYrG_message",
600
+ "cardHeader": "_4qCYrG_cardHeader"
601
+ };
602
+ //#endregion
603
+ //#region src/client/MarketProviderPanel.tsx
604
+ const TYPE_LABEL = {
605
+ public: "type.public",
606
+ gateway: "type.gateway",
607
+ commercial: "type.commercial"
608
+ };
609
+ function ProviderCredentialCard(props) {
610
+ const { providerId, spec, currentValues = {}, writable, onSave, onDelete, t } = props;
611
+ const [open, setOpen] = (0, react.useState)(false);
612
+ const [fields, setFields] = (0, react.useState)(() => ({ ...currentValues }));
613
+ const [showSecret, setShowSecret] = (0, react.useState)({});
614
+ const [saving, setSaving] = (0, react.useState)(false);
615
+ const [msg, setMsg] = (0, react.useState)(null);
616
+ (0, react.useEffect)(() => {
617
+ setFields({ ...currentValues });
618
+ }, [currentValues]);
619
+ const isConfigured = Object.values(currentValues).some((v) => Boolean(v && v.trim()));
620
+ const isDirty = spec.some((s) => (fields[s.key] ?? "") !== (currentValues[s.key] ?? ""));
621
+ const handleSave = async (e) => {
622
+ e.stopPropagation();
623
+ setSaving(true);
624
+ setMsg(null);
625
+ try {
626
+ await onSave(fields);
627
+ setMsg(t("credential.saved"));
628
+ } catch (err) {
629
+ setMsg(`${t("credential.saveFailed")}: ${String(err)}`);
630
+ } finally {
631
+ setSaving(false);
632
+ }
633
+ };
634
+ const handleDelete = async (e) => {
635
+ e.stopPropagation();
636
+ setSaving(true);
637
+ setMsg(null);
638
+ try {
639
+ await onDelete();
640
+ setFields({});
641
+ setMsg(t("credential.deleted"));
642
+ } catch (err) {
643
+ setMsg(`${t("credential.deleteFailed")}: ${String(err)}`);
644
+ } finally {
645
+ setSaving(false);
646
+ }
647
+ };
648
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
649
+ className: market_provider_panel_module_css_default.credentialBlock,
650
+ onClick: (e) => e.stopPropagation(),
651
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
652
+ className: market_provider_panel_module_css_default.credentialHeader,
653
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
654
+ className: market_provider_panel_module_css_default.credentialStatus,
655
+ "data-configured": isConfigured ? "true" : "false",
656
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: isConfigured ? "●" : "○" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: isConfigured ? t("credential.configured") : t("credential.notConfigured") })]
657
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
658
+ type: "button",
659
+ className: market_provider_panel_module_css_default.credentialToggleBtn,
660
+ onClick: () => {
661
+ setOpen(!open);
662
+ setMsg(null);
663
+ },
664
+ children: open ? t("credential.btnFold") : t("credential.btn")
665
+ })]
666
+ }), open && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
667
+ className: market_provider_panel_module_css_default.credentialDrawer,
668
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
669
+ className: market_provider_panel_module_css_default.credentialFields,
670
+ children: spec.map((field) => {
671
+ const isPass = field.secret && !showSecret[field.key];
672
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
673
+ className: market_provider_panel_module_css_default.fieldRow,
674
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
675
+ className: market_provider_panel_module_css_default.fieldLabel,
676
+ children: t(field.label)
677
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
678
+ className: market_provider_panel_module_css_default.inputWrapper,
679
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
680
+ type: isPass ? "password" : "text",
681
+ className: market_provider_panel_module_css_default.credInput,
682
+ value: fields[field.key] ?? "",
683
+ placeholder: field.placeholder !== void 0 ? t(field.placeholder) : void 0,
684
+ disabled: !writable || saving,
685
+ onChange: (e) => setFields({
686
+ ...fields,
687
+ [field.key]: e.target.value
688
+ })
689
+ }), field.secret && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
690
+ type: "button",
691
+ className: market_provider_panel_module_css_default.eyeBtn,
692
+ onClick: () => setShowSecret((prev) => ({
693
+ ...prev,
694
+ [field.key]: !prev[field.key]
695
+ })),
696
+ title: showSecret[field.key] ? t("field.action.hide") : t("field.action.show"),
697
+ children: showSecret[field.key] ? "🙈" : "👁️"
698
+ })]
699
+ })]
700
+ }, field.key);
701
+ })
702
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
703
+ className: market_provider_panel_module_css_default.credActions,
704
+ children: [
705
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
706
+ type: "button",
707
+ className: market_provider_panel_module_css_default.credSaveBtn,
708
+ disabled: !isDirty || saving || !writable,
709
+ onClick: handleSave,
710
+ children: t("credential.save")
711
+ }),
712
+ isConfigured && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
713
+ type: "button",
714
+ className: market_provider_panel_module_css_default.credDeleteBtn,
715
+ disabled: saving || !writable,
716
+ onClick: handleDelete,
717
+ children: t("credential.delete")
718
+ }),
719
+ msg && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
720
+ className: market_provider_panel_module_css_default.credMsg,
721
+ children: msg
722
+ })
723
+ ]
724
+ })]
725
+ })]
726
+ });
727
+ }
728
+ /** Render one market's provider radio group with save/reset (+ WS2c news key, crypto only). */
729
+ function MarketProviderPanel({ t: tProp, useController, market, setProvider, resetProvider, setCredential, deleteCredential, setNewsKey, resetNewsKey }) {
730
+ const t = tProp;
731
+ const state = useController((value) => value);
732
+ const resolved = state.resolved[market];
733
+ const overridden = state.overridden[market];
734
+ const [draft, setDraft] = (0, react.useState)(void 0);
735
+ const [saving, setSaving] = (0, react.useState)(false);
736
+ const [message, setMessage] = (0, react.useState)(void 0);
737
+ (0, react.useEffect)(() => {
738
+ setDraft(resolved);
739
+ setMessage(void 0);
740
+ }, [market, resolved]);
741
+ const dirty = (0, react.useMemo)(() => {
742
+ const chosen = draft ?? resolved;
743
+ if (chosen === void 0) return overridden;
744
+ return chosen !== resolved;
745
+ }, [
746
+ draft,
747
+ resolved,
748
+ overridden
749
+ ]);
750
+ const writable = state.writable;
751
+ const [newsDraft, setNewsDraft] = (0, react.useState)(void 0);
752
+ const [newsSaving, setNewsSaving] = (0, react.useState)(false);
753
+ const [newsMessage, setNewsMessage] = (0, react.useState)(void 0);
754
+ (0, react.useEffect)(() => {
755
+ if (newsDraft === void 0 && state.status === "ready") setNewsDraft(state.newsKey);
756
+ }, [state.status, state.newsKey]);
757
+ async function saveNews() {
758
+ setNewsSaving(true);
759
+ setNewsMessage(void 0);
760
+ try {
761
+ const next = (newsDraft ?? "").trim();
762
+ if (next !== (state.newsKey ?? "")) await setNewsKey(next);
763
+ else if (state.newsOverridden) await resetNewsKey();
764
+ setNewsMessage(t("newsSaved"));
765
+ } catch (error) {
766
+ setNewsMessage(`${t("newsSaveFailed")}: ${String(error?.message ?? error)}`);
767
+ } finally {
768
+ setNewsSaving(false);
769
+ }
770
+ }
771
+ const newsDirty = (newsDraft === void 0 ? state.newsKey ?? "" : newsDraft.trim()) !== (state.newsKey ?? "");
772
+ const options = (0, react.useMemo)(() => {
773
+ const matched = PROVIDER_LABELS.filter((p) => p.markets.includes(market));
774
+ const known = new Set(matched.map((p) => p.id));
775
+ const extras = [];
776
+ for (const slug of [resolved, draft]) if (slug !== void 0 && !known.has(slug) && !extras.some((e) => e.id === slug)) extras.push({
777
+ id: slug,
778
+ label: t("custom", { provider: slug })
779
+ });
780
+ return [...matched, ...extras];
781
+ }, [
782
+ market,
783
+ resolved,
784
+ draft,
785
+ t
786
+ ]);
787
+ async function save() {
788
+ setSaving(true);
789
+ setMessage(void 0);
790
+ try {
791
+ const chosen = draft ?? resolved;
792
+ if (chosen === void 0) {
793
+ if (overridden) await resetProvider(market);
794
+ } else if (chosen !== resolved || !overridden) await setProvider(market, chosen);
795
+ setMessage(t("saved"));
796
+ } catch (error) {
797
+ setMessage(`${t("saveFailed")}: ${String(error?.message ?? error)}`);
798
+ } finally {
799
+ setSaving(false);
800
+ }
801
+ }
802
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
803
+ className: market_provider_panel_module_css_default.panel,
804
+ children: [
805
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
806
+ className: market_provider_panel_module_css_default.currentHeader,
807
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("current", { provider: "" }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
808
+ className: market_provider_panel_module_css_default.currentBadge,
809
+ children: resolved ?? t("default")
810
+ })]
811
+ }),
812
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
813
+ className: market_provider_panel_module_css_default.grid,
814
+ children: options.map((provider) => {
815
+ const selected = draft === void 0 ? resolved === provider.id : draft === provider.id;
816
+ const credSpec = PROVIDER_CREDENTIAL_SPECS[provider.id];
817
+ const currentCreds = state.credentials?.[provider.id];
818
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
819
+ className: market_provider_panel_module_css_default.card,
820
+ "data-selected": selected ? "true" : void 0,
821
+ onClick: () => {
822
+ if (writable && !saving) setDraft(provider.id);
823
+ },
824
+ children: [
825
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
826
+ className: market_provider_panel_module_css_default.cardHeader,
827
+ children: [
828
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
829
+ type: "radio",
830
+ name: `provider-${market}`,
831
+ checked: selected,
832
+ disabled: !writable || saving,
833
+ onChange: () => setDraft(provider.id)
834
+ }),
835
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
836
+ className: market_provider_panel_module_css_default.cardTitle,
837
+ children: t(provider.label)
838
+ }),
839
+ provider.type && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
840
+ className: market_provider_panel_module_css_default.typeBadge,
841
+ children: t(TYPE_LABEL[provider.type] ?? provider.type)
842
+ })
843
+ ]
844
+ }),
845
+ (provider.url || provider.env) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
846
+ className: market_provider_panel_module_css_default.cardMeta,
847
+ children: [provider.url && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("a", {
848
+ href: provider.url,
849
+ target: "_blank",
850
+ rel: "noreferrer",
851
+ className: market_provider_panel_module_css_default.link,
852
+ onClick: (e) => e.stopPropagation(),
853
+ children: [t("provider.docsLink"), " ↗"]
854
+ }) }), provider.env && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
855
+ className: market_provider_panel_module_css_default.envBox,
856
+ children: [t("provider.envPrefix"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: provider.env })]
857
+ })]
858
+ }),
859
+ credSpec && credSpec.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProviderCredentialCard, {
860
+ providerId: provider.id,
861
+ spec: credSpec,
862
+ currentValues: currentCreds,
863
+ writable,
864
+ onSave: (fields) => setCredential(provider.id, fields),
865
+ onDelete: () => deleteCredential(provider.id),
866
+ t
867
+ })
868
+ ]
869
+ }, `${market}-${provider.id}`);
870
+ })
871
+ }),
872
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
873
+ className: market_provider_panel_module_css_default.actions,
874
+ children: [
875
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
876
+ type: "button",
877
+ className: market_provider_panel_module_css_default.saveBtn,
878
+ disabled: !dirty || saving || !writable,
879
+ onClick: () => void save(),
880
+ children: t("save")
881
+ }),
882
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
883
+ type: "button",
884
+ className: market_provider_panel_module_css_default.discardBtn,
885
+ disabled: saving || !dirty,
886
+ onClick: () => {
887
+ setDraft(void 0);
888
+ setMessage(void 0);
889
+ },
890
+ children: t("discard")
891
+ }),
892
+ message !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
893
+ className: market_provider_panel_module_css_default.message,
894
+ children: message
895
+ }) : null
896
+ ]
897
+ }),
898
+ market === "crypto" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
899
+ className: market_provider_panel_module_css_default.newsSection,
900
+ children: [
901
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
902
+ className: market_provider_panel_module_css_default.newsLabel,
903
+ children: t("newsKeyLabel")
904
+ }),
905
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
906
+ type: "password",
907
+ className: market_provider_panel_module_css_default.newsInput,
908
+ value: newsDraft ?? "",
909
+ disabled: !writable || newsSaving,
910
+ onChange: (event) => setNewsDraft(event.target.value),
911
+ placeholder: t("newsKeyPlaceholder")
912
+ }),
913
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
914
+ className: market_provider_panel_module_css_default.actions,
915
+ children: [
916
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
917
+ type: "button",
918
+ className: market_provider_panel_module_css_default.saveBtn,
919
+ disabled: !newsDirty || newsSaving || !writable,
920
+ onClick: () => void saveNews(),
921
+ children: t("save")
922
+ }),
923
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
924
+ type: "button",
925
+ className: market_provider_panel_module_css_default.discardBtn,
926
+ disabled: newsSaving || !newsDirty,
927
+ onClick: () => {
928
+ setNewsDraft(void 0);
929
+ setNewsMessage(void 0);
930
+ },
931
+ children: t("discard")
932
+ }),
933
+ newsMessage !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
934
+ className: market_provider_panel_module_css_default.message,
935
+ children: newsMessage
936
+ }) : null
937
+ ]
938
+ })
939
+ ]
940
+ })
941
+ ]
942
+ });
943
+ }
944
+ //#endregion
945
+ //#region src/client/locales.ts
946
+ const zh = {
947
+ "nav": "交易",
948
+ "lead": "选择每个市场使用的数据/交易所提供方。行情面板保存即生效;Agent 会话于新建会话生效(切换不中断当前会话)。",
949
+ "tabs": "市场",
950
+ "empty": "没有可配置的市场。",
951
+ "save": "保存",
952
+ "discard": "放弃",
953
+ "saved": "已保存",
954
+ "saveFailed": "保存失败",
955
+ "current": "当前:{provider}",
956
+ "default": "默认",
957
+ "custom": "自定义({provider},由第三方连接器提供)",
958
+ "newsKeyLabel": "CryptoPanic API Key(可选)——新闻工具 crypto_get_news 的 B 增强源;留空则用公共源。",
959
+ "newsKeyPlaceholder": "粘贴 CryptoPanic free API token(私钥,仅本地存储)",
960
+ "newsSaved": "已保存",
961
+ "newsSaveFailed": "保存失败",
962
+ "colorMode.label": "涨跌配色",
963
+ "colorMode.redUp": "红涨绿跌(国内习惯)",
964
+ "colorMode.greenUp": "绿涨红跌(国际习惯)",
965
+ "market.crypto": "加密货币",
966
+ "market.us": "美国股票",
967
+ "market.cn": "中国 A 股",
968
+ "market.hk": "香港股票",
969
+ "credential.btn": "配置 API 凭证",
970
+ "credential.btnFold": "收起配置",
971
+ "credential.configured": "已配置凭证",
972
+ "credential.notConfigured": "未配置",
973
+ "credential.save": "保存凭证",
974
+ "credential.delete": "清除/删除凭证",
975
+ "credential.saved": "API 凭证已保存",
976
+ "credential.deleted": "API 凭证已删除",
977
+ "credential.saveFailed": "凭证保存失败",
978
+ "credential.deleteFailed": "凭证清除失败",
979
+ "type.public": "免密公共源",
980
+ "type.gateway": "本地网关",
981
+ "type.commercial": "商业 API",
982
+ "field.action.hide": "隐藏",
983
+ "field.action.show": "显示",
984
+ "provider.docsLink": "官方指引与文档",
985
+ "provider.envPrefix": "环境变量:",
986
+ "provider.binance": "Binance (币安)",
987
+ "provider.okx": "OKX (欧易)",
988
+ "provider.bybit": "Bybit",
989
+ "provider.ccxt": "CCXT (跨所聚合 100+)",
990
+ "provider.yahoo": "Yahoo Finance",
991
+ "provider.alpaca": "Alpaca",
992
+ "provider.fmp": "FMP (Financial Modeling Prep)",
993
+ "provider.finnhub": "Finnhub",
994
+ "provider.polygon": "Polygon.io (Massive)",
995
+ "provider.ibkr": "IBKR (盈透证券)",
996
+ "provider.stooq": "Stooq",
997
+ "provider.tencent": "腾讯 (Tencent)",
998
+ "provider.eastmoney": "东方财富 (Eastmoney)",
999
+ "provider.tushare": "Tushare Pro",
1000
+ "provider.akshare": "AkShare (宏观/另类量化)",
1001
+ "provider.qmt": "MiniQMT (迅投券商实盘)",
1002
+ "provider.futu": "Futu (富途 OpenD)",
1003
+ "provider.longbridge": "Longbridge (长桥)",
1004
+ "provider.tiger": "Tiger Trade (老虎证券)",
1005
+ "field.label.apiKey": "API Key",
1006
+ "field.label.apiSecret": "API Secret",
1007
+ "field.label.secretKey": "Secret Key",
1008
+ "field.label.passphrase": "Passphrase",
1009
+ "field.label.token": "Pro Token",
1010
+ "field.label.apiUrl": "HTTP 服务地址",
1011
+ "field.label.qmtUrl": "MiniQMT 服务地址",
1012
+ "field.label.futuHost": "OpenD IP",
1013
+ "field.label.futuPort": "OpenD 端口",
1014
+ "field.label.ibkrUrl": "CP Gateway 地址",
1015
+ "field.label.appKey": "App Key",
1016
+ "field.label.appSecret": "App Secret",
1017
+ "field.label.accessToken": "Access Token",
1018
+ "field.label.tigerId": "Tiger ID",
1019
+ "field.label.tigerPrivateKey": "RSA 私钥",
1020
+ "field.placeholder.binanceKey": "BINANCE_API_KEY (留空走免密公共行情)",
1021
+ "field.placeholder.binanceSecret": "BINANCE_API_SECRET",
1022
+ "field.placeholder.okxKey": "OKX_API_KEY (留空走免密公共行情)",
1023
+ "field.placeholder.okxSecret": "OKX_SECRET_KEY",
1024
+ "field.placeholder.okxPassphrase": "OKX_PASSPHRASE",
1025
+ "field.placeholder.bybitKey": "BYBIT_API_KEY (留空走免密公共行情)",
1026
+ "field.placeholder.bybitSecret": "BYBIT_API_SECRET",
1027
+ "field.placeholder.ccxtKey": "CCXT_API_KEY",
1028
+ "field.placeholder.ccxtSecret": "CCXT_API_SECRET",
1029
+ "field.placeholder.alpacaKey": "ALPACA_API_KEY",
1030
+ "field.placeholder.alpacaSecret": "ALPACA_SECRET_KEY",
1031
+ "field.placeholder.fmpKey": "FMP_API_KEY",
1032
+ "field.placeholder.finnhubKey": "FINNHUB_API_KEY",
1033
+ "field.placeholder.polygonKey": "POLYGON_API_KEY",
1034
+ "field.placeholder.tushareToken": "TUSHARE_TOKEN",
1035
+ "field.placeholder.akshareUrl": "http://127.0.0.1:8080 (默认内置)",
1036
+ "field.placeholder.qmtUrl": "http://127.0.0.1:5800",
1037
+ "field.placeholder.futuHost": "127.0.0.1",
1038
+ "field.placeholder.futuPort": "11111",
1039
+ "field.placeholder.ibkrUrl": "https://localhost:5000",
1040
+ "field.placeholder.longbridgeAppKey": "LONGBRIDGE_APP_KEY",
1041
+ "field.placeholder.longbridgeAppSecret": "LONGBRIDGE_APP_SECRET",
1042
+ "field.placeholder.longbridgeAccessToken": "LONGBRIDGE_ACCESS_TOKEN",
1043
+ "field.placeholder.tigerId": "TIGER_ID",
1044
+ "field.placeholder.tigerPrivateKey": "TIGER_PRIVATE_KEY"
1045
+ };
1046
+ const en = {
1047
+ "nav": "Trading",
1048
+ "lead": "Choose the data/exchange provider for each market. Quote panels take effect immediately; agent sessions pick it up in new sessions (running sessions are not interrupted).",
1049
+ "tabs": "Markets",
1050
+ "empty": "No configurable markets.",
1051
+ "save": "Save",
1052
+ "discard": "Discard",
1053
+ "saved": "Saved",
1054
+ "saveFailed": "Save failed",
1055
+ "current": "Current: {provider}",
1056
+ "default": "default",
1057
+ "custom": "Custom ({provider}, provided by a third-party connector)",
1058
+ "newsKeyLabel": "CryptoPanic API key (optional) — B-source enrichment for the crypto_get_news tool; leave empty to use public sources.",
1059
+ "newsKeyPlaceholder": "Paste a CryptoPanic free API token (stored locally only)",
1060
+ "newsSaved": "Saved",
1061
+ "newsSaveFailed": "Save failed",
1062
+ "colorMode.label": "Price Color Scheme",
1063
+ "colorMode.redUp": "Red Up / Green Down (Chinese)",
1064
+ "colorMode.greenUp": "Green Up / Red Down (International)",
1065
+ "market.crypto": "Crypto",
1066
+ "market.us": "US Stocks",
1067
+ "market.cn": "China A-shares",
1068
+ "market.hk": "Hong Kong",
1069
+ "credential.btn": "Configure API Credentials",
1070
+ "credential.btnFold": "Hide Configuration",
1071
+ "credential.configured": "Configured",
1072
+ "credential.notConfigured": "Not Configured",
1073
+ "credential.save": "Save Credentials",
1074
+ "credential.delete": "Clear / Delete Credentials",
1075
+ "credential.saved": "API Credentials saved",
1076
+ "credential.deleted": "API Credentials cleared",
1077
+ "credential.saveFailed": "Failed to save credentials",
1078
+ "credential.deleteFailed": "Failed to clear credentials",
1079
+ "type.public": "Public source",
1080
+ "type.gateway": "Local gateway",
1081
+ "type.commercial": "Commercial API",
1082
+ "field.action.hide": "Hide",
1083
+ "field.action.show": "Show",
1084
+ "provider.docsLink": "Official docs & guides",
1085
+ "provider.envPrefix": "Env var: ",
1086
+ "provider.binance": "Binance",
1087
+ "provider.okx": "OKX",
1088
+ "provider.bybit": "Bybit",
1089
+ "provider.ccxt": "CCXT (100+ exchanges)",
1090
+ "provider.yahoo": "Yahoo Finance",
1091
+ "provider.alpaca": "Alpaca",
1092
+ "provider.fmp": "FMP (Financial Modeling Prep)",
1093
+ "provider.finnhub": "Finnhub",
1094
+ "provider.polygon": "Polygon.io (Massive)",
1095
+ "provider.ibkr": "IBKR (Interactive Brokers)",
1096
+ "provider.stooq": "Stooq",
1097
+ "provider.tencent": "Tencent",
1098
+ "provider.eastmoney": "Eastmoney",
1099
+ "provider.tushare": "Tushare Pro",
1100
+ "provider.akshare": "AkShare (macro / alt-data)",
1101
+ "provider.qmt": "MiniQMT (broker gateway)",
1102
+ "provider.futu": "Futu (OpenD)",
1103
+ "provider.longbridge": "Longbridge",
1104
+ "provider.tiger": "Tiger Trade",
1105
+ "field.label.apiKey": "API Key",
1106
+ "field.label.apiSecret": "API Secret",
1107
+ "field.label.secretKey": "Secret Key",
1108
+ "field.label.passphrase": "Passphrase",
1109
+ "field.label.token": "Pro Token",
1110
+ "field.label.apiUrl": "HTTP endpoint",
1111
+ "field.label.qmtUrl": "MiniQMT endpoint",
1112
+ "field.label.futuHost": "OpenD IP",
1113
+ "field.label.futuPort": "OpenD port",
1114
+ "field.label.ibkrUrl": "CP Gateway URL",
1115
+ "field.label.appKey": "App Key",
1116
+ "field.label.appSecret": "App Secret",
1117
+ "field.label.accessToken": "Access Token",
1118
+ "field.label.tigerId": "Tiger ID",
1119
+ "field.label.tigerPrivateKey": "RSA private key",
1120
+ "field.placeholder.binanceKey": "BINANCE_API_KEY (leave empty for public quotes)",
1121
+ "field.placeholder.binanceSecret": "BINANCE_API_SECRET",
1122
+ "field.placeholder.okxKey": "OKX_API_KEY (leave empty for public quotes)",
1123
+ "field.placeholder.okxSecret": "OKX_SECRET_KEY",
1124
+ "field.placeholder.okxPassphrase": "OKX_PASSPHRASE",
1125
+ "field.placeholder.bybitKey": "BYBIT_API_KEY (leave empty for public quotes)",
1126
+ "field.placeholder.bybitSecret": "BYBIT_API_SECRET",
1127
+ "field.placeholder.ccxtKey": "CCXT_API_KEY",
1128
+ "field.placeholder.ccxtSecret": "CCXT_API_SECRET",
1129
+ "field.placeholder.alpacaKey": "ALPACA_API_KEY",
1130
+ "field.placeholder.alpacaSecret": "ALPACA_SECRET_KEY",
1131
+ "field.placeholder.fmpKey": "FMP_API_KEY",
1132
+ "field.placeholder.finnhubKey": "FINNHUB_API_KEY",
1133
+ "field.placeholder.polygonKey": "POLYGON_API_KEY",
1134
+ "field.placeholder.tushareToken": "TUSHARE_TOKEN",
1135
+ "field.placeholder.akshareUrl": "http://127.0.0.1:8080 (built-in default)",
1136
+ "field.placeholder.qmtUrl": "http://127.0.0.1:5800",
1137
+ "field.placeholder.futuHost": "127.0.0.1",
1138
+ "field.placeholder.futuPort": "11111",
1139
+ "field.placeholder.ibkrUrl": "https://localhost:5000",
1140
+ "field.placeholder.longbridgeAppKey": "LONGBRIDGE_APP_KEY",
1141
+ "field.placeholder.longbridgeAppSecret": "LONGBRIDGE_APP_SECRET",
1142
+ "field.placeholder.longbridgeAccessToken": "LONGBRIDGE_ACCESS_TOKEN",
1143
+ "field.placeholder.tigerId": "TIGER_ID",
1144
+ "field.placeholder.tigerPrivateKey": "TIGER_PRIVATE_KEY"
1145
+ };
1146
+ //#endregion
1147
+ //#region src/client/index.ts
1148
+ /** 本面板/字符串翻译的 locale namespace。 */
1149
+ const NS = "dshtrading.settings";
1150
+ /** Required services (cordis fiber inject). */
1151
+ const inject = [
1152
+ "slots",
1153
+ "locale",
1154
+ "settingsScope"
1155
+ ];
1156
+ /** 市场 tab 注册清单(id = market slug;新市场 = 加一行 + 加 slot 注册,section 零改)。 */
1157
+ const MARKET_TABS = [
1158
+ {
1159
+ id: "crypto",
1160
+ order: 0,
1161
+ key: "crypto"
1162
+ },
1163
+ {
1164
+ id: "us",
1165
+ order: 1,
1166
+ key: "us"
1167
+ },
1168
+ {
1169
+ id: "cn",
1170
+ order: 2,
1171
+ key: "cn"
1172
+ },
1173
+ {
1174
+ id: "hk",
1175
+ order: 3,
1176
+ key: "hk"
1177
+ }
1178
+ ];
1179
+ /** 注册『交易』设置一级菜单(tab 容器)+ 每市场面板。 */
1180
+ function apply(ctx) {
1181
+ const t = ctx.locale.bind(NS);
1182
+ ctx.effect(() => ctx.locale.register(NS, {
1183
+ zh,
1184
+ en
1185
+ }), "dsh-trading-settings: dictionaries");
1186
+ const scope = ctx.settingsScope.bind({ namespace: "dshtrading" });
1187
+ const store = createTradingSettingsStore(scope);
1188
+ const actions = {
1189
+ async setProvider(market, provider) {
1190
+ const rev = scope.getSnapshot().revision;
1191
+ await scope.mutate([{
1192
+ op: "set",
1193
+ path: [
1194
+ "markets",
1195
+ market,
1196
+ "provider"
1197
+ ],
1198
+ value: provider
1199
+ }], rev);
1200
+ },
1201
+ async resetProvider(market) {
1202
+ const rev = scope.getSnapshot().revision;
1203
+ await scope.mutate([{
1204
+ op: "unset",
1205
+ path: [
1206
+ "markets",
1207
+ market,
1208
+ "provider"
1209
+ ]
1210
+ }], rev);
1211
+ },
1212
+ async setCredential(provider, fields) {
1213
+ const rev = scope.getSnapshot().revision;
1214
+ await scope.mutate([{
1215
+ op: "set",
1216
+ path: ["credentials", provider],
1217
+ value: fields
1218
+ }], rev);
1219
+ },
1220
+ async deleteCredential(provider) {
1221
+ const rev = scope.getSnapshot().revision;
1222
+ await scope.mutate([{
1223
+ op: "unset",
1224
+ path: ["credentials", provider]
1225
+ }], rev);
1226
+ },
1227
+ async setNewsKey(value) {
1228
+ const rev = scope.getSnapshot().revision;
1229
+ const op = value.trim() ? {
1230
+ op: "set",
1231
+ path: ["news", "cryptoPanicKey"],
1232
+ value: value.trim()
1233
+ } : {
1234
+ op: "unset",
1235
+ path: ["news", "cryptoPanicKey"]
1236
+ };
1237
+ await scope.mutate([op], rev);
1238
+ },
1239
+ async resetNewsKey() {
1240
+ const rev = scope.getSnapshot().revision;
1241
+ await scope.mutate([{
1242
+ op: "unset",
1243
+ path: ["news", "cryptoPanicKey"]
1244
+ }], rev);
1245
+ },
1246
+ async setColorMode(mode) {
1247
+ const rev = scope.getSnapshot().revision;
1248
+ await scope.mutate([{
1249
+ op: "set",
1250
+ path: ["colorMode"],
1251
+ value: mode
1252
+ }], rev);
1253
+ try {
1254
+ localStorage.setItem("dshtrading.color_mode.v1", JSON.stringify(mode));
1255
+ } catch {}
1256
+ try {
1257
+ window.dispatchEvent(new Event("dshtrading-color-mode-changed"));
1258
+ } catch {}
1259
+ }
1260
+ };
1261
+ let tabsVersion = -1;
1262
+ let tabsRevision = -1;
1263
+ let tabs = [];
1264
+ const sectionInjected = () => ({ hooks: { tabs: {
1265
+ getSnapshot: () => {
1266
+ const version = ctx.slots.getVersion("dshtrading.market.tab");
1267
+ const revision = ctx.locale.getSnapshot().revision;
1268
+ if (version !== tabsVersion || revision !== tabsRevision) {
1269
+ tabsVersion = version;
1270
+ tabsRevision = revision;
1271
+ tabs = ctx.slots.entries("dshtrading.market.tab").map((entry) => ({
1272
+ id: entry.options.id ?? "",
1273
+ order: entry.options.order ?? 0,
1274
+ label: (0, _deepseek_ai_dsh_client_ui_slots.resolveSlotLabel)(entry.options.label) ?? ""
1275
+ })).sort((a, b) => a.order - b.order);
1276
+ }
1277
+ return tabs;
1278
+ },
1279
+ subscribe: (listener) => {
1280
+ const offLedger = ctx.slots.subscribe("dshtrading.market.tab", listener);
1281
+ const offLocale = ctx.locale.subscribe(listener);
1282
+ return () => {
1283
+ offLedger();
1284
+ offLocale();
1285
+ };
1286
+ }
1287
+ } } });
1288
+ ctx.slots.inject("settings.section", () => ctx.slots.register({
1289
+ name: "settings.section",
1290
+ id: "trading",
1291
+ order: 8,
1292
+ label: () => t("nav"),
1293
+ locale: NS,
1294
+ inject: () => ({
1295
+ ...sectionInjected(),
1296
+ hooks: {
1297
+ ...sectionInjected().hooks,
1298
+ controller: store
1299
+ },
1300
+ setColorMode: actions.setColorMode
1301
+ }),
1302
+ children: { "dshtrading.market.tab": {
1303
+ kind: "list",
1304
+ scope: "root"
1305
+ } }
1306
+ }, TradingSettingsSection));
1307
+ ctx.slots.inject("dshtrading.market.tab", function* () {
1308
+ for (const market of MARKET_TABS) yield ctx.slots.register({
1309
+ name: "dshtrading.market.tab",
1310
+ id: market.id,
1311
+ order: market.order,
1312
+ label: () => t("market." + market.key),
1313
+ locale: NS,
1314
+ inject: () => ({
1315
+ hooks: { controller: store },
1316
+ market: market.id,
1317
+ ...actions
1318
+ })
1319
+ }, MarketProviderPanel);
1320
+ });
1321
+ }
1322
+ //#endregion
1323
+ exports.apply = apply;
1324
+ exports.inject = inject;
1325
+ return module.exports;
1326
+ }
1327
+ });
1328
+
1329
+ //# sourceMappingURL=client.js.map