dsh-plugin-subscription-oauth 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/README.md +413 -2
- package/cordis.patch.yml +13 -0
- package/lib/auth/device-flow.d.ts +112 -0
- package/lib/auth/device-flow.js +319 -0
- package/lib/auth/device-id.d.ts +31 -0
- package/lib/auth/device-id.js +58 -0
- package/lib/auth/flow.d.ts +33 -0
- package/lib/auth/flow.js +12 -0
- package/lib/auth/glm-flow.d.ts +99 -0
- package/lib/auth/glm-flow.js +194 -0
- package/lib/auth/headers.d.ts +61 -0
- package/lib/auth/headers.js +106 -0
- package/lib/auth/pkce-flow.d.ts +78 -0
- package/lib/auth/pkce-flow.js +205 -0
- package/lib/auth/provider-controller.d.ts +241 -0
- package/lib/auth/provider-controller.js +502 -0
- package/lib/auth/registry.d.ts +91 -0
- package/lib/auth/registry.js +149 -0
- package/lib/auth/rpc.d.ts +31 -0
- package/lib/auth/rpc.js +200 -0
- package/lib/auth/store.d.ts +99 -0
- package/lib/auth/store.js +213 -0
- package/lib/auth/tokens.d.ts +76 -0
- package/lib/auth/tokens.js +103 -0
- package/lib/client/SubscriptionsSection.d.ts +34 -0
- package/lib/client/SubscriptionsSection.js +742 -0
- package/lib/client/index.d.ts +36 -0
- package/lib/client/index.js +47 -0
- package/lib/client/locales.d.ts +187 -0
- package/lib/client/locales.js +185 -0
- package/lib/client/styles.d.ts +18 -0
- package/lib/client/styles.js +257 -0
- package/lib/client/subscriptions-rpc.d.ts +24 -0
- package/lib/client/subscriptions-rpc.js +44 -0
- package/lib/client.js +2078 -0
- package/lib/client.js.map +1 -0
- package/lib/constants.d.ts +79 -0
- package/lib/constants.js +91 -0
- package/lib/errors.d.ts +28 -0
- package/lib/errors.js +34 -0
- package/lib/http.d.ts +24 -0
- package/lib/http.js +98 -0
- package/lib/index.d.ts +57 -0
- package/lib/index.js +185 -0
- package/lib/protocol.d.ts +316 -0
- package/lib/protocol.js +29 -0
- package/lib/providers/antigravity-catalog.d.ts +53 -0
- package/lib/providers/antigravity-catalog.js +269 -0
- package/lib/providers/antigravity.d.ts +331 -0
- package/lib/providers/antigravity.js +821 -0
- package/lib/providers/auto.d.ts +151 -0
- package/lib/providers/auto.js +351 -0
- package/lib/providers/catalog.d.ts +69 -0
- package/lib/providers/catalog.js +78 -0
- package/lib/providers/common.d.ts +51 -0
- package/lib/providers/common.js +169 -0
- package/lib/providers/glm-provider.d.ts +41 -0
- package/lib/providers/glm-provider.js +165 -0
- package/lib/providers/glm.d.ts +159 -0
- package/lib/providers/glm.js +445 -0
- package/lib/providers/grok.d.ts +224 -0
- package/lib/providers/grok.js +700 -0
- package/lib/providers/kimi-provider.d.ts +45 -0
- package/lib/providers/kimi-provider.js +148 -0
- package/lib/providers/kimi.d.ts +151 -0
- package/lib/providers/kimi.js +400 -0
- package/lib/providers/responses-stream.d.ts +58 -0
- package/lib/providers/responses-stream.js +115 -0
- package/lib/providers/usage.d.ts +65 -0
- package/lib/providers/usage.js +309 -0
- package/lib/settings.d.ts +122 -0
- package/lib/settings.js +333 -0
- package/lib/translate/antigravity-schema.d.ts +24 -0
- package/lib/translate/antigravity-schema.js +174 -0
- package/lib/translate/antigravity.d.ts +184 -0
- package/lib/translate/antigravity.js +419 -0
- package/lib/translate/responses.d.ts +156 -0
- package/lib/translate/responses.js +395 -0
- package/lib/translate/sse.d.ts +27 -0
- package/lib/translate/sse.js +82 -0
- package/package.json +126 -4
package/lib/client.js
ADDED
|
@@ -0,0 +1,2078 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({ id: "dsh-plugin-subscription-oauth", factory: (require) => {
|
|
2
|
+
var module = { exports: {} }; var exports = module.exports;
|
|
3
|
+
//#region rolldown:runtime
|
|
4
|
+
var __create = Object.create;
|
|
5
|
+
var __defProp = Object.defineProperty;
|
|
6
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
7
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
8
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
9
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
12
|
+
key = keys[i];
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
14
|
+
get: ((k) => from[k]).bind(null, key),
|
|
15
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
21
|
+
value: mod,
|
|
22
|
+
enumerable: true
|
|
23
|
+
}) : target, mod));
|
|
24
|
+
|
|
25
|
+
//#endregion
|
|
26
|
+
let react = require("react");
|
|
27
|
+
react = __toESM(react);
|
|
28
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
29
|
+
react_jsx_runtime = __toESM(react_jsx_runtime);
|
|
30
|
+
|
|
31
|
+
//#region src/client/locales.ts
|
|
32
|
+
/**
|
|
33
|
+
* Copy dictionaries for the Subscriptions settings page.
|
|
34
|
+
*
|
|
35
|
+
* English is the key-set source of truth; the Chinese dictionary is typed
|
|
36
|
+
* against it, so a key added to one and forgotten in the other is a compile
|
|
37
|
+
* error rather than a silent fallback.
|
|
38
|
+
*/
|
|
39
|
+
/** English strings. */
|
|
40
|
+
const en = {
|
|
41
|
+
nav: "Subscriptions",
|
|
42
|
+
intro: "Sign in with a subscription account — Kimi Code, Grok (X Premium) or Google Antigravity. DeepSeek Harness keeps the tokens on this machine and streams every request through the provider's own API.",
|
|
43
|
+
loading: "Checking subscriptions…",
|
|
44
|
+
unavailable: "The subscriptions page needs the web connection service, which this composition does not provide.",
|
|
45
|
+
statusConnected: "Connected · {count} account(s)",
|
|
46
|
+
statusSignedOut: "Not signed in",
|
|
47
|
+
statusBusy: "Waiting for browser approval",
|
|
48
|
+
statusSignIn: "Sign in to add this provider's models to the model picker.",
|
|
49
|
+
statusAuto: "Follows {count} enabled model(s) from every provider",
|
|
50
|
+
connect: "Connect",
|
|
51
|
+
edit: "Edit",
|
|
52
|
+
collapse: "Collapse",
|
|
53
|
+
addAccount: "Add account",
|
|
54
|
+
manage: "Manage",
|
|
55
|
+
cancel: "Cancel",
|
|
56
|
+
close: "Close",
|
|
57
|
+
logout: "Sign out",
|
|
58
|
+
makeDefault: "Use by default",
|
|
59
|
+
defaultBadge: "Default",
|
|
60
|
+
accountNeedsLogin: "Sign in again",
|
|
61
|
+
accountNeedsLoginHint: "The provider refused this account's grant, so its token cannot be renewed",
|
|
62
|
+
accountExpired: "token expired",
|
|
63
|
+
accountExpires: "expires {time}",
|
|
64
|
+
deviceTitle: "Approve this device",
|
|
65
|
+
devicePrompt: "Open the verification page and confirm the code below. This page updates by itself once the provider approves.",
|
|
66
|
+
deviceOpen: "Open verification page",
|
|
67
|
+
deviceCopy: "Copy code",
|
|
68
|
+
deviceCopied: "Copied",
|
|
69
|
+
deviceExpires: "The code expires at {time}.",
|
|
70
|
+
openLogin: "Open sign-in page",
|
|
71
|
+
openLoginHint: "Finish signing in in the browser tab that opened. This page updates by itself once the provider redirects back.",
|
|
72
|
+
modelsTitle: "Models",
|
|
73
|
+
modelsCount: "{count} available",
|
|
74
|
+
modelsLive: "live",
|
|
75
|
+
modelsBuiltIn: "built-in",
|
|
76
|
+
modelsShow: "Show in picker",
|
|
77
|
+
modelsHiddenCount: "· {count} hidden",
|
|
78
|
+
modelsHidden: "Hidden from the model picker",
|
|
79
|
+
modelsEmpty: "No models reported yet.",
|
|
80
|
+
modelsAdd: "Add model",
|
|
81
|
+
modelsAddHint: "Publish a model id this build does not know yet",
|
|
82
|
+
modelsAddPlaceholder: "model id, e.g. k3-next",
|
|
83
|
+
modelsAddConfirm: "Add",
|
|
84
|
+
modelsRemove: "Remove this hand-added model",
|
|
85
|
+
modelsContext: "{tokens} context",
|
|
86
|
+
modelsAuto: "Auto",
|
|
87
|
+
modelsAutoHint: "Pinned first: moves to the next model when one is unavailable",
|
|
88
|
+
modelsAutoOff: "Off — the picker offers the models directly instead",
|
|
89
|
+
modelsAutoChain: "Auto tries these, in this order — drag to re-order",
|
|
90
|
+
modelsDrag: "Drag to re-order (or press ↑ / ↓ here)",
|
|
91
|
+
modelsAutoChainOff: "Take out of Auto",
|
|
92
|
+
modelsAutoChainEmpty: "No model is enabled, so Auto has nothing to try.",
|
|
93
|
+
modelsPinned: "pinned",
|
|
94
|
+
usageTitle: "Plan and usage",
|
|
95
|
+
usagePlan: "Plan: {plan}",
|
|
96
|
+
usageRenews: "renews {time}",
|
|
97
|
+
usageReset: "resets {time}",
|
|
98
|
+
usageRefresh: "Refresh usage",
|
|
99
|
+
usageFetched: "read {time}",
|
|
100
|
+
usageNone: "This provider reports no plan quota.",
|
|
101
|
+
usageError: "Could not read usage: {message}",
|
|
102
|
+
usage_5h: "5-hour window",
|
|
103
|
+
usage_weekly: "Weekly window",
|
|
104
|
+
usage_credits: "Included credit",
|
|
105
|
+
usage_gemini: "Gemini allowance (rolling)",
|
|
106
|
+
usage_gemini_weekly: "Gemini allowance (weekly)",
|
|
107
|
+
usage_claude: "Claude allowance (rolling)",
|
|
108
|
+
usage_claude_weekly: "Claude allowance (weekly)",
|
|
109
|
+
usage_other: "Allowance",
|
|
110
|
+
refresh: "Refresh",
|
|
111
|
+
refreshing: "Refreshing…",
|
|
112
|
+
modelsRefreshed: "Model list refreshed.",
|
|
113
|
+
refreshToken: "Refresh token",
|
|
114
|
+
tokenRefreshed: "Token refreshed; valid until {time}.",
|
|
115
|
+
hint: "Authorization follows the account the browser is signed in with. To add a different account, switch accounts there first, or use a private window.",
|
|
116
|
+
manageTitle: "Manage {provider}",
|
|
117
|
+
manageAccounts: "Accounts",
|
|
118
|
+
reload: "Reload credentials",
|
|
119
|
+
reloadHint: "Use this after signing in from another DeepSeek Harness window.",
|
|
120
|
+
detail_oauthHost: "Authorization server",
|
|
121
|
+
detail_apiBase: "API base",
|
|
122
|
+
detail_credentialFile: "Credential file",
|
|
123
|
+
detail_project: "Cloud project",
|
|
124
|
+
lastError: "Last login attempt failed: {message}",
|
|
125
|
+
loginFailed: "Could not start the sign-in: {message}",
|
|
126
|
+
actionFailed: "Action failed: {message}"
|
|
127
|
+
};
|
|
128
|
+
/** Chinese strings. */
|
|
129
|
+
const zh = {
|
|
130
|
+
nav: "订阅",
|
|
131
|
+
intro: "使用订阅账号登录 —— Kimi Code、Grok (X Premium) 或 Google Antigravity。DeepSeek Harness 将令牌保存在本机,并通过各服务自己的 API 发起请求。",
|
|
132
|
+
loading: "正在检查订阅…",
|
|
133
|
+
unavailable: "订阅页面需要 Web 连接服务,当前组合未提供该服务。",
|
|
134
|
+
statusConnected: "已连接 {count} 个账号",
|
|
135
|
+
statusSignedOut: "未登录",
|
|
136
|
+
statusBusy: "等待浏览器确认",
|
|
137
|
+
statusSignIn: "登录后即可在模型选择器中使用该服务的模型。",
|
|
138
|
+
statusAuto: "依次使用来自各服务的 {count} 个已启用模型",
|
|
139
|
+
connect: "连接",
|
|
140
|
+
edit: "编辑",
|
|
141
|
+
collapse: "收起",
|
|
142
|
+
addAccount: "添加账号",
|
|
143
|
+
manage: "管理",
|
|
144
|
+
cancel: "取消",
|
|
145
|
+
close: "关闭",
|
|
146
|
+
logout: "退出登录",
|
|
147
|
+
makeDefault: "设为默认",
|
|
148
|
+
defaultBadge: "默认",
|
|
149
|
+
accountNeedsLogin: "需重新登录",
|
|
150
|
+
accountNeedsLoginHint: "服务方已拒绝该账号的授权,令牌无法自动续期",
|
|
151
|
+
accountExpired: "令牌已过期",
|
|
152
|
+
accountExpires: "过期时间 {time}",
|
|
153
|
+
deviceTitle: "确认此设备",
|
|
154
|
+
devicePrompt: "打开验证页面并确认下方代码,服务确认后本页面会自动更新。",
|
|
155
|
+
deviceOpen: "打开验证页面",
|
|
156
|
+
deviceCopy: "复制代码",
|
|
157
|
+
deviceCopied: "已复制",
|
|
158
|
+
deviceExpires: "代码将于 {time} 过期。",
|
|
159
|
+
openLogin: "打开登录页面",
|
|
160
|
+
openLoginHint: "请在打开的浏览器标签页中完成登录;服务回调后本页面会自动更新。",
|
|
161
|
+
modelsTitle: "模型",
|
|
162
|
+
modelsCount: "共 {count} 个",
|
|
163
|
+
modelsLive: "实时",
|
|
164
|
+
modelsBuiltIn: "内置",
|
|
165
|
+
modelsShow: "在选择器中显示",
|
|
166
|
+
modelsHiddenCount: "· 已隐藏 {count} 个",
|
|
167
|
+
modelsHidden: "已从模型选择器隐藏",
|
|
168
|
+
modelsEmpty: "暂未获取到模型。",
|
|
169
|
+
modelsAdd: "添加模型",
|
|
170
|
+
modelsAddHint: "手动添加一个本版本还不认识的模型 ID",
|
|
171
|
+
modelsAddPlaceholder: "模型 ID,例如 k3-next",
|
|
172
|
+
modelsAddConfirm: "添加",
|
|
173
|
+
modelsRemove: "移除这个手动添加的模型",
|
|
174
|
+
modelsContext: "{tokens} 上下文",
|
|
175
|
+
modelsAuto: "自动",
|
|
176
|
+
modelsAutoHint: "固定在首位:某个模型不可用时自动改用下一个",
|
|
177
|
+
modelsAutoOff: "已关闭 —— 选择器将直接列出各个模型",
|
|
178
|
+
modelsAutoChain: "自动会按这个顺序依次尝试(拖动可调整)",
|
|
179
|
+
modelsDrag: "拖动调整顺序(也可在此按 ↑ / ↓)",
|
|
180
|
+
modelsAutoChainOff: "从自动中移除",
|
|
181
|
+
modelsAutoChainEmpty: "没有启用任何模型,自动没有可尝试的模型。",
|
|
182
|
+
modelsPinned: "固定",
|
|
183
|
+
usageTitle: "套餐与额度",
|
|
184
|
+
usagePlan: "套餐:{plan}",
|
|
185
|
+
usageRenews: "续费时间 {time}",
|
|
186
|
+
usageReset: "重置时间 {time}",
|
|
187
|
+
usageRefresh: "刷新额度",
|
|
188
|
+
usageFetched: "读取于 {time}",
|
|
189
|
+
usageNone: "该服务未提供套餐额度信息。",
|
|
190
|
+
usageError: "额度读取失败:{message}",
|
|
191
|
+
usage_5h: "5 小时窗口",
|
|
192
|
+
usage_weekly: "每周窗口",
|
|
193
|
+
usage_credits: "包含额度",
|
|
194
|
+
usage_gemini: "Gemini 模型额度(滚动)",
|
|
195
|
+
usage_gemini_weekly: "Gemini 模型额度(每周)",
|
|
196
|
+
usage_claude: "Claude 模型额度(滚动)",
|
|
197
|
+
usage_claude_weekly: "Claude 模型额度(每周)",
|
|
198
|
+
usage_other: "额度",
|
|
199
|
+
refresh: "刷新",
|
|
200
|
+
refreshing: "正在刷新…",
|
|
201
|
+
modelsRefreshed: "模型列表已刷新。",
|
|
202
|
+
refreshToken: "刷新令牌",
|
|
203
|
+
tokenRefreshed: "令牌已刷新,有效至 {time}。",
|
|
204
|
+
hint: "授权以浏览器当前登录的账号为准;要添加不同账号,请先在浏览器里切换账号,或使用无痕窗口。",
|
|
205
|
+
manageTitle: "管理 {provider}",
|
|
206
|
+
manageAccounts: "账号",
|
|
207
|
+
reload: "重新读取凭据",
|
|
208
|
+
reloadHint: "在另一个 DeepSeek Harness 窗口中登录后,请使用此操作。",
|
|
209
|
+
detail_oauthHost: "授权服务器",
|
|
210
|
+
detail_apiBase: "API 地址",
|
|
211
|
+
detail_credentialFile: "凭据文件",
|
|
212
|
+
detail_project: "云项目",
|
|
213
|
+
lastError: "上次登录失败:{message}",
|
|
214
|
+
loginFailed: "无法开始登录:{message}",
|
|
215
|
+
actionFailed: "操作失败:{message}"
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
//#endregion
|
|
219
|
+
//#region src/client/styles.ts
|
|
220
|
+
/**
|
|
221
|
+
* Scoped stylesheet for the Subscriptions page and its manage dialog.
|
|
222
|
+
*
|
|
223
|
+
* The host loads plugin markup without its own control classes, and a bare
|
|
224
|
+
* `<button>` therefore renders with the browser's default raised chrome — which
|
|
225
|
+
* reads as a stray highlight in a dark theme. Rather than invent a look, this
|
|
226
|
+
* follows the control vocabulary of the sibling `dsh-plugin-subscriptions`
|
|
227
|
+
* plugin (same author-facing design as the shell's settings forms): a 6 px
|
|
228
|
+
* radius small control, a transparent fill, a hairline border from the theme's
|
|
229
|
+
* alias tokens, and the theme's hover/active/focus tokens for state.
|
|
230
|
+
*
|
|
231
|
+
* The tokens all carry fallbacks, so the page still renders correctly on a host
|
|
232
|
+
* build that does not define them.
|
|
233
|
+
*
|
|
234
|
+
* @module dsh-plugin-subscription-oauth/client/styles
|
|
235
|
+
*/
|
|
236
|
+
/**
|
|
237
|
+
* Everything is scoped to this plugin's own roots, so no other surface in the
|
|
238
|
+
* shell can be affected by a rule here.
|
|
239
|
+
*/
|
|
240
|
+
const scope = ":is(.dsh-subscriptions-settings, .dsh-subscription-manager)";
|
|
241
|
+
/** The page's and dialog's stylesheet. */
|
|
242
|
+
const subscriptionsCss = `
|
|
243
|
+
.dsh-subscriptions-settings,
|
|
244
|
+
.dsh-subscription-manager {
|
|
245
|
+
font-family: inherit;
|
|
246
|
+
font-size: 13px;
|
|
247
|
+
line-height: 1.5;
|
|
248
|
+
/* Native controls (checkbox, dialog) follow the host theme, not the OS. */
|
|
249
|
+
color-scheme: light;
|
|
250
|
+
}
|
|
251
|
+
body[data-ds-dark-theme] ${scope} {
|
|
252
|
+
color-scheme: dark;
|
|
253
|
+
}
|
|
254
|
+
.dsh-subscription-manager {
|
|
255
|
+
box-sizing: border-box;
|
|
256
|
+
padding: 0;
|
|
257
|
+
border: 0;
|
|
258
|
+
border-radius: var(--dsw-radius-panel, 16px);
|
|
259
|
+
background: var(--dsw-alias-bg-layer-2, var(--dsw-alias-bg-layer-1));
|
|
260
|
+
color: var(--dsw-alias-label-primary);
|
|
261
|
+
box-shadow: var(--dsw-elevation-prominent, 0 20px 70px #0004);
|
|
262
|
+
}
|
|
263
|
+
.dsh-subscription-manager::backdrop {
|
|
264
|
+
background: var(--dsw-alias-bg-mask-1, #0006);
|
|
265
|
+
backdrop-filter: var(--dsw-mask-blur, none);
|
|
266
|
+
}
|
|
267
|
+
${scope} h2 {
|
|
268
|
+
margin: 0;
|
|
269
|
+
font-size: 16px;
|
|
270
|
+
line-height: 24px;
|
|
271
|
+
font-weight: 500;
|
|
272
|
+
}
|
|
273
|
+
${scope} strong,
|
|
274
|
+
${scope} .dsh-subscription-label {
|
|
275
|
+
font-size: 13px;
|
|
276
|
+
line-height: 1.5;
|
|
277
|
+
font-weight: 500;
|
|
278
|
+
}
|
|
279
|
+
${scope} small,
|
|
280
|
+
${scope} .dsh-subscription-hint,
|
|
281
|
+
${scope} [role="status"] {
|
|
282
|
+
margin: 0;
|
|
283
|
+
font-size: 12px;
|
|
284
|
+
line-height: 1.5;
|
|
285
|
+
color: var(--dsw-alias-label-tertiary);
|
|
286
|
+
}
|
|
287
|
+
${scope} .dsh-subscription-error {
|
|
288
|
+
margin: 0;
|
|
289
|
+
font-size: 12px;
|
|
290
|
+
line-height: 1.5;
|
|
291
|
+
color: var(--dsw-alias-state-error-primary, #b42318);
|
|
292
|
+
}
|
|
293
|
+
${scope} .dsh-subscription-success {
|
|
294
|
+
margin: 0;
|
|
295
|
+
font-size: 12px;
|
|
296
|
+
line-height: 1.5;
|
|
297
|
+
color: var(--dsw-alias-state-success-primary, #067647);
|
|
298
|
+
}
|
|
299
|
+
${scope} label:has(> input[type="checkbox"]) {
|
|
300
|
+
cursor: pointer;
|
|
301
|
+
}
|
|
302
|
+
${scope} input[type="checkbox"] {
|
|
303
|
+
accent-color: var(--dsw-alias-brand-primary);
|
|
304
|
+
}
|
|
305
|
+
/*
|
|
306
|
+
* A model's on/off control is a switch, not a checkbox: it takes effect
|
|
307
|
+
* immediately and its state is what matters, not a tick. Drawn from the theme
|
|
308
|
+
* tokens with appearance: none, so it follows the host palette in both themes
|
|
309
|
+
* instead of the platform's checkbox chrome.
|
|
310
|
+
*/
|
|
311
|
+
${scope} input[type="checkbox"].dsh-subscription-switch {
|
|
312
|
+
appearance: none;
|
|
313
|
+
-webkit-appearance: none;
|
|
314
|
+
position: relative;
|
|
315
|
+
box-sizing: border-box;
|
|
316
|
+
flex-shrink: 0;
|
|
317
|
+
width: 34px;
|
|
318
|
+
height: 20px;
|
|
319
|
+
margin: 0;
|
|
320
|
+
border: 0.5px solid var(--dsw-alias-border-l3, var(--dsw-alias-border-l2));
|
|
321
|
+
border-radius: 999px;
|
|
322
|
+
background: var(--dsw-alias-bg-layer-3, var(--dsw-alias-bg-layer-2));
|
|
323
|
+
cursor: pointer;
|
|
324
|
+
transition: background 120ms ease, border-color 120ms ease;
|
|
325
|
+
}
|
|
326
|
+
${scope} input[type="checkbox"].dsh-subscription-switch::after {
|
|
327
|
+
content: '';
|
|
328
|
+
position: absolute;
|
|
329
|
+
top: 2px;
|
|
330
|
+
left: 2px;
|
|
331
|
+
width: 14px;
|
|
332
|
+
height: 14px;
|
|
333
|
+
border-radius: 50%;
|
|
334
|
+
background: var(--dsw-alias-label-primary-foreground, #fff);
|
|
335
|
+
box-shadow: 0 1px 2px #0003;
|
|
336
|
+
transition: transform 120ms ease;
|
|
337
|
+
}
|
|
338
|
+
${scope} input[type="checkbox"].dsh-subscription-switch:checked {
|
|
339
|
+
border-color: transparent;
|
|
340
|
+
background: var(--dsw-alias-state-success-primary, var(--dsw-alias-brand-primary, #067647));
|
|
341
|
+
}
|
|
342
|
+
${scope} input[type="checkbox"].dsh-subscription-switch:checked::after {
|
|
343
|
+
transform: translateX(14px);
|
|
344
|
+
}
|
|
345
|
+
${scope} input[type="checkbox"].dsh-subscription-switch:disabled {
|
|
346
|
+
opacity: 0.4;
|
|
347
|
+
cursor: default;
|
|
348
|
+
}
|
|
349
|
+
${scope} button {
|
|
350
|
+
box-sizing: border-box;
|
|
351
|
+
display: inline-flex;
|
|
352
|
+
align-items: center;
|
|
353
|
+
justify-content: center;
|
|
354
|
+
flex-shrink: 0;
|
|
355
|
+
min-height: 28px;
|
|
356
|
+
padding: 0 10px;
|
|
357
|
+
border: 0.5px solid var(--dsw-alias-border-l3, var(--dsw-alias-border-l2));
|
|
358
|
+
border-radius: var(--dsw-radius-sm, 6px);
|
|
359
|
+
background: transparent;
|
|
360
|
+
color: var(--dsw-alias-label-primary);
|
|
361
|
+
font: inherit;
|
|
362
|
+
font-size: 12px;
|
|
363
|
+
line-height: 18px;
|
|
364
|
+
cursor: pointer;
|
|
365
|
+
}
|
|
366
|
+
${scope} button:hover:not(:disabled) {
|
|
367
|
+
background: var(--dsw-alias-interactive-bg-hover, var(--dsw-alias-bg-layer-3));
|
|
368
|
+
}
|
|
369
|
+
${scope} button:active:not(:disabled) {
|
|
370
|
+
background: var(--dsw-alias-interactive-bg-active, var(--dsw-alias-bg-layer-3));
|
|
371
|
+
}
|
|
372
|
+
${scope} button:disabled {
|
|
373
|
+
opacity: 0.4;
|
|
374
|
+
cursor: default;
|
|
375
|
+
}
|
|
376
|
+
/* A control that is present but should not read as a bordered button. */
|
|
377
|
+
${scope} button.dsh-subscription-quiet {
|
|
378
|
+
min-height: 22px;
|
|
379
|
+
padding: 0 4px;
|
|
380
|
+
border-color: transparent;
|
|
381
|
+
color: var(--dsw-alias-label-secondary, var(--dsw-alias-label-primary));
|
|
382
|
+
}
|
|
383
|
+
/* The free-text field for a hand-added model id. */
|
|
384
|
+
${scope} input.dsh-subscription-input {
|
|
385
|
+
box-sizing: border-box;
|
|
386
|
+
flex: 1 1 auto;
|
|
387
|
+
min-width: 0;
|
|
388
|
+
min-height: 28px;
|
|
389
|
+
padding: 0 10px;
|
|
390
|
+
border: 0.5px solid var(--dsw-alias-border-l3, var(--dsw-alias-border-l2));
|
|
391
|
+
border-radius: var(--dsw-radius-sm, 6px);
|
|
392
|
+
background: var(--dsw-alias-bg-layer-1);
|
|
393
|
+
color: var(--dsw-alias-label-primary);
|
|
394
|
+
font: inherit;
|
|
395
|
+
font-size: 12px;
|
|
396
|
+
line-height: 18px;
|
|
397
|
+
}
|
|
398
|
+
${scope} input.dsh-subscription-input::placeholder {
|
|
399
|
+
color: var(--dsw-alias-label-tertiary);
|
|
400
|
+
}
|
|
401
|
+
${scope} input.dsh-subscription-input:focus-visible {
|
|
402
|
+
outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));
|
|
403
|
+
outline-offset: 2px;
|
|
404
|
+
}
|
|
405
|
+
/* A drag handle: no border or fill, so it reads as a grip rather than a button. */
|
|
406
|
+
${scope} button.dsh-subscription-handle {
|
|
407
|
+
min-height: 18px;
|
|
408
|
+
height: 18px;
|
|
409
|
+
padding: 0 2px;
|
|
410
|
+
border-color: transparent;
|
|
411
|
+
border-radius: var(--dsw-radius-sm, 6px);
|
|
412
|
+
background: transparent;
|
|
413
|
+
color: var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary));
|
|
414
|
+
font-size: 12px;
|
|
415
|
+
line-height: 18px;
|
|
416
|
+
cursor: grab;
|
|
417
|
+
}
|
|
418
|
+
${scope} button.dsh-subscription-handle:hover:not(:disabled) {
|
|
419
|
+
background: transparent;
|
|
420
|
+
color: var(--dsw-alias-label-primary);
|
|
421
|
+
}
|
|
422
|
+
${scope} button.dsh-subscription-handle:active:not(:disabled) { cursor: grabbing; }
|
|
423
|
+
/* Controls inside the manage dialog are one step larger, like a form footer. */
|
|
424
|
+
${scope} .dsh-subscription-dialog-actions button {
|
|
425
|
+
min-height: 34px;
|
|
426
|
+
padding: 0 14px;
|
|
427
|
+
border-radius: var(--dsw-radius-md, 8px);
|
|
428
|
+
font-size: 13px;
|
|
429
|
+
line-height: 1.5;
|
|
430
|
+
}
|
|
431
|
+
${scope} button.dsh-subscription-primary {
|
|
432
|
+
border-color: transparent;
|
|
433
|
+
background: var(--dsw-alias-button-primary-fill, var(--dsw-alias-label-primary));
|
|
434
|
+
color: var(--dsw-alias-label-primary-foreground, var(--dsw-alias-bg-layer-1));
|
|
435
|
+
font-weight: 500;
|
|
436
|
+
}
|
|
437
|
+
${scope} button.dsh-subscription-primary:hover:not(:disabled) {
|
|
438
|
+
background: var(--dsw-alias-button-primary-hover, var(--dsw-alias-label-secondary));
|
|
439
|
+
}
|
|
440
|
+
${scope} button:focus-visible,
|
|
441
|
+
${scope} input[type="checkbox"]:focus-visible,
|
|
442
|
+
${scope} .dsh-subscription-disclosure:focus-visible {
|
|
443
|
+
outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));
|
|
444
|
+
outline-offset: 2px;
|
|
445
|
+
}
|
|
446
|
+
/* The catalog heading is a summary-style disclosure, not a bordered button. */
|
|
447
|
+
${scope} .dsh-subscription-disclosure {
|
|
448
|
+
border-color: transparent;
|
|
449
|
+
border-radius: var(--dsw-radius-sm, 6px);
|
|
450
|
+
background: transparent;
|
|
451
|
+
padding: 0 4px;
|
|
452
|
+
font-size: 12px;
|
|
453
|
+
line-height: 18px;
|
|
454
|
+
font-weight: 500;
|
|
455
|
+
color: var(--dsw-alias-label-secondary, var(--dsw-alias-label-primary));
|
|
456
|
+
}
|
|
457
|
+
${scope} .dsh-subscription-disclosure:hover:not(:disabled) {
|
|
458
|
+
background: transparent;
|
|
459
|
+
color: var(--dsw-alias-label-primary);
|
|
460
|
+
}
|
|
461
|
+
/* A usage meter: the track, with the fill sized by the caller. */
|
|
462
|
+
${scope} .dsh-subscription-meter {
|
|
463
|
+
height: 6px;
|
|
464
|
+
border-radius: 999px;
|
|
465
|
+
background: var(--dsw-alias-bg-layer-3, var(--dsw-alias-bg-layer-2));
|
|
466
|
+
overflow: hidden;
|
|
467
|
+
}
|
|
468
|
+
${scope} .dsh-subscription-meter > span {
|
|
469
|
+
display: block;
|
|
470
|
+
height: 100%;
|
|
471
|
+
background: var(--dsw-alias-state-success-primary, #067647);
|
|
472
|
+
}
|
|
473
|
+
${scope} .dsh-subscription-meter > span[data-exhausted="true"] {
|
|
474
|
+
background: var(--dsw-alias-state-error-primary, #b42318);
|
|
475
|
+
}
|
|
476
|
+
`;
|
|
477
|
+
|
|
478
|
+
//#endregion
|
|
479
|
+
//#region src/protocol.ts
|
|
480
|
+
/**
|
|
481
|
+
* Wire contract shared by the plugin's node half (the RPC routes) and its
|
|
482
|
+
* browser half (the Settings page).
|
|
483
|
+
*
|
|
484
|
+
* Only this module crosses the boundary: the client bundle imports the types
|
|
485
|
+
* and the channel name, and every call travels as one JSON envelope over a
|
|
486
|
+
* `POST /api/<prefix><endpoint>` route. One status document describes every
|
|
487
|
+
* provider route the plugin serves, so the page renders one card per entry.
|
|
488
|
+
*
|
|
489
|
+
* @module dsh-plugin-subscription-oauth/protocol
|
|
490
|
+
*/
|
|
491
|
+
/** Prefix of every RPC method this plugin serves. */
|
|
492
|
+
const RPC_PREFIX = "subscription-oauth.";
|
|
493
|
+
|
|
494
|
+
//#endregion
|
|
495
|
+
//#region src/client/subscriptions-rpc.ts
|
|
496
|
+
/** The shared channel the node half's routes are mounted on. */
|
|
497
|
+
const CHANNEL = "/api";
|
|
498
|
+
/** A failed call: either the transport rejected or the endpoint answered `ok: false`. */
|
|
499
|
+
var SubscriptionsRpcError = class extends Error {
|
|
500
|
+
constructor(message, code) {
|
|
501
|
+
super(message);
|
|
502
|
+
this.code = code;
|
|
503
|
+
this.name = "SubscriptionsRpcError";
|
|
504
|
+
}
|
|
505
|
+
};
|
|
506
|
+
/** Human-readable message for any thrown value. */
|
|
507
|
+
function messageOf$1(error) {
|
|
508
|
+
return error instanceof Error ? error.message : String(error);
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Bind a caller to one connection's RPC transport.
|
|
512
|
+
* @param rpc - the browser connection's RPC caller.
|
|
513
|
+
* @returns a typed caller for this plugin's endpoints.
|
|
514
|
+
*/
|
|
515
|
+
function createSubscriptionsCall(rpc) {
|
|
516
|
+
return async function call(endpoint, payload) {
|
|
517
|
+
let result;
|
|
518
|
+
try {
|
|
519
|
+
result = await rpc.call(CHANNEL, `${RPC_PREFIX}${endpoint}`, payload);
|
|
520
|
+
} catch (error) {
|
|
521
|
+
throw new SubscriptionsRpcError(messageOf$1(error), "RPC_TRANSPORT");
|
|
522
|
+
}
|
|
523
|
+
if (!result.ok) throw new SubscriptionsRpcError(result.error.message, result.error.code);
|
|
524
|
+
return result.value;
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
//#endregion
|
|
529
|
+
//#region src/client/SubscriptionsSection.tsx
|
|
530
|
+
/** Poll cadence while a login attempt is pending. */
|
|
531
|
+
const POLL_INTERVAL_MS = 2e3;
|
|
532
|
+
/**
|
|
533
|
+
* How often an open page re-reads every signed-in route's allowance.
|
|
534
|
+
*
|
|
535
|
+
* Slow on purpose: it is fast enough that a figure shown on screen is current, and
|
|
536
|
+
* slow enough that leaving the page open costs one small request a minute per
|
|
537
|
+
* signed-in provider.
|
|
538
|
+
*/
|
|
539
|
+
const USAGE_POLL_INTERVAL_MS = 6e4;
|
|
540
|
+
/**
|
|
541
|
+
* Disclosures start folded, every time the page is opened.
|
|
542
|
+
*
|
|
543
|
+
* Nothing is remembered between visits on purpose: the section is a list of
|
|
544
|
+
* providers first, and a card left open last time should not decide what this
|
|
545
|
+
* visit looks like. A fold is a gesture within one visit, not a stored setting.
|
|
546
|
+
*/
|
|
547
|
+
/** Horizontal room the page takes inside the settings panel. */
|
|
548
|
+
const SECTION_WIDTH = 560;
|
|
549
|
+
/** Minimal translator used when the locale seat is absent (standalone render). */
|
|
550
|
+
function fallbackTranslate(key, params) {
|
|
551
|
+
const template = en[key];
|
|
552
|
+
if (params === void 0) return template;
|
|
553
|
+
return template.replace(/\{(\w+)\}/g, (match, name) => name in params ? String(params[name]) : match);
|
|
554
|
+
}
|
|
555
|
+
/** Human-readable message for any thrown value. */
|
|
556
|
+
function messageOf(error) {
|
|
557
|
+
if (error instanceof SubscriptionsRpcError) return error.message;
|
|
558
|
+
return error instanceof Error ? error.message : String(error);
|
|
559
|
+
}
|
|
560
|
+
/** Local time for one epoch-millisecond instant. */
|
|
561
|
+
function formatTime(at) {
|
|
562
|
+
try {
|
|
563
|
+
return new Date(at).toLocaleString();
|
|
564
|
+
} catch {
|
|
565
|
+
return String(at);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
/** Compact token count for a context window. */
|
|
569
|
+
function formatTokens(tokens) {
|
|
570
|
+
if (tokens >= 1e6) return `${String(Math.round(tokens / 1e5) / 10)}M`;
|
|
571
|
+
if (tokens >= 1e3) return `${String(Math.round(tokens / 1e3))}K`;
|
|
572
|
+
return String(tokens);
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Routes worth asking for allowances.
|
|
576
|
+
*
|
|
577
|
+
* A route with no account of its own — `auto` — reports itself as signed in so its
|
|
578
|
+
* card reads as usable, but it holds no allowance to read: asking it every minute
|
|
579
|
+
* spends a request to be told nothing. The routes it stands in for are each read
|
|
580
|
+
* on their own.
|
|
581
|
+
*
|
|
582
|
+
* @param providers - the routes in the current status.
|
|
583
|
+
* @returns the route keys to read usage for.
|
|
584
|
+
*/
|
|
585
|
+
function usageTargets(providers) {
|
|
586
|
+
return providers.filter((provider) => provider.loggedIn && provider.connectable !== false).map((provider) => provider.provider);
|
|
587
|
+
}
|
|
588
|
+
const styles = {
|
|
589
|
+
section: {
|
|
590
|
+
display: "flex",
|
|
591
|
+
flexDirection: "column",
|
|
592
|
+
gap: 12,
|
|
593
|
+
maxWidth: SECTION_WIDTH,
|
|
594
|
+
color: "var(--dsw-alias-label-primary)"
|
|
595
|
+
},
|
|
596
|
+
intro: {
|
|
597
|
+
margin: 0,
|
|
598
|
+
color: "var(--dsw-alias-label-tertiary)",
|
|
599
|
+
fontSize: 14,
|
|
600
|
+
lineHeight: "22px"
|
|
601
|
+
},
|
|
602
|
+
card: {
|
|
603
|
+
border: "0.5px solid var(--dsw-alias-settings-card-stroke, var(--dsw-alias-border-l4))",
|
|
604
|
+
borderRadius: "var(--dsw-radius-xl, 20px)",
|
|
605
|
+
background: "var(--dsw-alias-settings-card-fill, var(--dsw-alias-bg-layer-2))",
|
|
606
|
+
padding: "12px 14px",
|
|
607
|
+
display: "flex",
|
|
608
|
+
flexDirection: "column",
|
|
609
|
+
gap: 6
|
|
610
|
+
},
|
|
611
|
+
cardHeader: {
|
|
612
|
+
display: "flex",
|
|
613
|
+
alignItems: "center",
|
|
614
|
+
gap: 8
|
|
615
|
+
},
|
|
616
|
+
cardBody: {
|
|
617
|
+
display: "flex",
|
|
618
|
+
flexDirection: "column",
|
|
619
|
+
gap: 6
|
|
620
|
+
},
|
|
621
|
+
cardSummary: {
|
|
622
|
+
fontSize: 12,
|
|
623
|
+
lineHeight: "18px",
|
|
624
|
+
color: "var(--dsw-alias-label-tertiary)"
|
|
625
|
+
},
|
|
626
|
+
dot: {
|
|
627
|
+
width: 8,
|
|
628
|
+
height: 8,
|
|
629
|
+
borderRadius: "50%",
|
|
630
|
+
flexShrink: 0
|
|
631
|
+
},
|
|
632
|
+
name: {
|
|
633
|
+
fontWeight: 500,
|
|
634
|
+
fontSize: 14,
|
|
635
|
+
lineHeight: "22px",
|
|
636
|
+
color: "var(--dsw-alias-label-primary)"
|
|
637
|
+
},
|
|
638
|
+
providerId: {
|
|
639
|
+
fontSize: 12,
|
|
640
|
+
lineHeight: "18px",
|
|
641
|
+
color: "var(--dsw-alias-label-dimmed)"
|
|
642
|
+
},
|
|
643
|
+
statusLine: {
|
|
644
|
+
margin: 0,
|
|
645
|
+
fontSize: 12,
|
|
646
|
+
lineHeight: "18px",
|
|
647
|
+
color: "var(--dsw-alias-label-tertiary)"
|
|
648
|
+
},
|
|
649
|
+
errorLine: {
|
|
650
|
+
margin: 0,
|
|
651
|
+
fontSize: 12,
|
|
652
|
+
lineHeight: "18px",
|
|
653
|
+
color: "var(--dsw-alias-state-error-primary)"
|
|
654
|
+
},
|
|
655
|
+
successLine: {
|
|
656
|
+
margin: 0,
|
|
657
|
+
fontSize: 12,
|
|
658
|
+
lineHeight: "18px",
|
|
659
|
+
color: "var(--dsw-alias-state-success-primary)"
|
|
660
|
+
},
|
|
661
|
+
actions: {
|
|
662
|
+
display: "flex",
|
|
663
|
+
gap: 8,
|
|
664
|
+
marginTop: 4,
|
|
665
|
+
alignItems: "center",
|
|
666
|
+
flexWrap: "wrap"
|
|
667
|
+
},
|
|
668
|
+
accountRow: {
|
|
669
|
+
display: "flex",
|
|
670
|
+
flexDirection: "column",
|
|
671
|
+
gap: 6,
|
|
672
|
+
border: "0.5px solid var(--dsw-alias-border-l2)",
|
|
673
|
+
borderRadius: "var(--dsw-radius-lg, 16px)",
|
|
674
|
+
padding: "8px 10px",
|
|
675
|
+
marginTop: 4
|
|
676
|
+
},
|
|
677
|
+
accountHeader: {
|
|
678
|
+
display: "flex",
|
|
679
|
+
alignItems: "center",
|
|
680
|
+
gap: 8,
|
|
681
|
+
flexWrap: "wrap"
|
|
682
|
+
},
|
|
683
|
+
starButton: {
|
|
684
|
+
border: "none",
|
|
685
|
+
padding: 0,
|
|
686
|
+
width: 20,
|
|
687
|
+
background: "transparent",
|
|
688
|
+
font: "inherit",
|
|
689
|
+
fontSize: 14,
|
|
690
|
+
lineHeight: "20px",
|
|
691
|
+
cursor: "pointer",
|
|
692
|
+
color: "var(--dsw-alias-state-warn-label)"
|
|
693
|
+
},
|
|
694
|
+
accountName: {
|
|
695
|
+
fontSize: 13,
|
|
696
|
+
lineHeight: "20px",
|
|
697
|
+
color: "var(--dsw-alias-label-primary)",
|
|
698
|
+
userSelect: "all"
|
|
699
|
+
},
|
|
700
|
+
badge: {
|
|
701
|
+
fontSize: 11,
|
|
702
|
+
lineHeight: "16px",
|
|
703
|
+
padding: "0 6px",
|
|
704
|
+
borderRadius: 8,
|
|
705
|
+
background: "var(--dsw-alias-bg-layer-1)",
|
|
706
|
+
border: "0.5px solid var(--dsw-alias-border-l2)",
|
|
707
|
+
color: "var(--dsw-alias-label-secondary)"
|
|
708
|
+
},
|
|
709
|
+
pushRight: {
|
|
710
|
+
marginLeft: "auto",
|
|
711
|
+
flexShrink: 0
|
|
712
|
+
},
|
|
713
|
+
catalog: {
|
|
714
|
+
display: "flex",
|
|
715
|
+
flexDirection: "column",
|
|
716
|
+
gap: 6,
|
|
717
|
+
marginTop: 4,
|
|
718
|
+
borderTop: "0.5px solid var(--dsw-alias-border-l2)",
|
|
719
|
+
paddingTop: 8
|
|
720
|
+
},
|
|
721
|
+
catalogHeader: {
|
|
722
|
+
display: "flex",
|
|
723
|
+
alignItems: "center",
|
|
724
|
+
gap: 8,
|
|
725
|
+
flexWrap: "wrap"
|
|
726
|
+
},
|
|
727
|
+
catalogTitle: {
|
|
728
|
+
fontSize: 12,
|
|
729
|
+
lineHeight: "18px",
|
|
730
|
+
fontWeight: 500,
|
|
731
|
+
color: "var(--dsw-alias-label-secondary)"
|
|
732
|
+
},
|
|
733
|
+
catalogMeta: {
|
|
734
|
+
fontSize: 12,
|
|
735
|
+
lineHeight: "18px",
|
|
736
|
+
color: "var(--dsw-alias-label-tertiary)"
|
|
737
|
+
},
|
|
738
|
+
pill: {},
|
|
739
|
+
pillSmall: {},
|
|
740
|
+
disclosure: {
|
|
741
|
+
display: "inline-flex",
|
|
742
|
+
alignItems: "center",
|
|
743
|
+
gap: 4,
|
|
744
|
+
margin: 0
|
|
745
|
+
},
|
|
746
|
+
chevron: {
|
|
747
|
+
fontSize: 10,
|
|
748
|
+
lineHeight: "18px",
|
|
749
|
+
color: "var(--dsw-alias-label-tertiary)"
|
|
750
|
+
},
|
|
751
|
+
modelList: {
|
|
752
|
+
display: "flex",
|
|
753
|
+
flexDirection: "column",
|
|
754
|
+
gap: 6,
|
|
755
|
+
marginTop: 2
|
|
756
|
+
},
|
|
757
|
+
modelRowGrow: {
|
|
758
|
+
display: "flex",
|
|
759
|
+
alignItems: "flex-start",
|
|
760
|
+
gap: 8,
|
|
761
|
+
flex: "1 1 auto",
|
|
762
|
+
minWidth: 0
|
|
763
|
+
},
|
|
764
|
+
autoRow: {
|
|
765
|
+
display: "flex",
|
|
766
|
+
alignItems: "flex-start",
|
|
767
|
+
gap: 8,
|
|
768
|
+
cursor: "pointer",
|
|
769
|
+
fontSize: 13,
|
|
770
|
+
lineHeight: "20px",
|
|
771
|
+
color: "var(--dsw-alias-label-primary)"
|
|
772
|
+
},
|
|
773
|
+
chain: {
|
|
774
|
+
display: "flex",
|
|
775
|
+
flexDirection: "column",
|
|
776
|
+
gap: 3,
|
|
777
|
+
margin: "2px 0 4px 26px",
|
|
778
|
+
paddingLeft: 8,
|
|
779
|
+
borderLeft: "0.5px solid var(--dsw-alias-border-l2)"
|
|
780
|
+
},
|
|
781
|
+
chainTitle: {
|
|
782
|
+
fontSize: 11,
|
|
783
|
+
lineHeight: "16px",
|
|
784
|
+
color: "var(--dsw-alias-label-tertiary)"
|
|
785
|
+
},
|
|
786
|
+
addRow: {
|
|
787
|
+
display: "flex",
|
|
788
|
+
alignItems: "center",
|
|
789
|
+
gap: 6,
|
|
790
|
+
marginTop: 2
|
|
791
|
+
},
|
|
792
|
+
addInput: {},
|
|
793
|
+
removeButton: {
|
|
794
|
+
flexShrink: 0,
|
|
795
|
+
alignSelf: "center",
|
|
796
|
+
padding: "0 6px"
|
|
797
|
+
},
|
|
798
|
+
chainRowWrap: {
|
|
799
|
+
display: "flex",
|
|
800
|
+
alignItems: "center",
|
|
801
|
+
gap: 4
|
|
802
|
+
},
|
|
803
|
+
chainRow: {
|
|
804
|
+
display: "flex",
|
|
805
|
+
alignItems: "center",
|
|
806
|
+
gap: 6,
|
|
807
|
+
cursor: "pointer",
|
|
808
|
+
flex: "1 1 auto",
|
|
809
|
+
fontSize: 12,
|
|
810
|
+
lineHeight: "18px",
|
|
811
|
+
color: "var(--dsw-alias-label-secondary)"
|
|
812
|
+
},
|
|
813
|
+
usage: {
|
|
814
|
+
display: "flex",
|
|
815
|
+
flexDirection: "column",
|
|
816
|
+
gap: 6,
|
|
817
|
+
marginTop: 4,
|
|
818
|
+
borderTop: "0.5px solid var(--dsw-alias-border-l2)",
|
|
819
|
+
paddingTop: 8
|
|
820
|
+
},
|
|
821
|
+
usageHeader: {
|
|
822
|
+
display: "flex",
|
|
823
|
+
alignItems: "center",
|
|
824
|
+
gap: 8,
|
|
825
|
+
flexWrap: "wrap"
|
|
826
|
+
},
|
|
827
|
+
usageRow: {
|
|
828
|
+
display: "flex",
|
|
829
|
+
flexDirection: "column",
|
|
830
|
+
gap: 3
|
|
831
|
+
},
|
|
832
|
+
usageLine: {
|
|
833
|
+
display: "flex",
|
|
834
|
+
alignItems: "baseline",
|
|
835
|
+
gap: 8,
|
|
836
|
+
justifyContent: "space-between"
|
|
837
|
+
},
|
|
838
|
+
usageMeter: {
|
|
839
|
+
height: 6,
|
|
840
|
+
borderRadius: 999,
|
|
841
|
+
background: "var(--dsw-alias-bg-layer-3, var(--dsw-alias-bg-layer-2))",
|
|
842
|
+
overflow: "hidden"
|
|
843
|
+
},
|
|
844
|
+
modelRow: {
|
|
845
|
+
display: "flex",
|
|
846
|
+
alignItems: "flex-start",
|
|
847
|
+
gap: 8,
|
|
848
|
+
cursor: "pointer",
|
|
849
|
+
fontSize: 13,
|
|
850
|
+
lineHeight: "20px",
|
|
851
|
+
color: "var(--dsw-alias-label-primary)"
|
|
852
|
+
},
|
|
853
|
+
modelRowHidden: {
|
|
854
|
+
display: "flex",
|
|
855
|
+
alignItems: "flex-start",
|
|
856
|
+
gap: 8,
|
|
857
|
+
cursor: "pointer",
|
|
858
|
+
fontSize: 13,
|
|
859
|
+
lineHeight: "20px",
|
|
860
|
+
color: "var(--dsw-alias-label-dimmed)"
|
|
861
|
+
},
|
|
862
|
+
modelBody: {
|
|
863
|
+
display: "flex",
|
|
864
|
+
flexDirection: "column",
|
|
865
|
+
gap: 1,
|
|
866
|
+
minWidth: 0
|
|
867
|
+
},
|
|
868
|
+
modelName: {
|
|
869
|
+
fontSize: 13,
|
|
870
|
+
lineHeight: "20px"
|
|
871
|
+
},
|
|
872
|
+
modelMeta: {
|
|
873
|
+
fontSize: 11,
|
|
874
|
+
lineHeight: "16px",
|
|
875
|
+
color: "var(--dsw-alias-label-tertiary)",
|
|
876
|
+
userSelect: "all"
|
|
877
|
+
},
|
|
878
|
+
device: {
|
|
879
|
+
marginTop: 4,
|
|
880
|
+
display: "flex",
|
|
881
|
+
flexDirection: "column",
|
|
882
|
+
gap: 6,
|
|
883
|
+
border: "0.5px solid var(--dsw-alias-border-l2)",
|
|
884
|
+
borderRadius: "var(--dsw-radius-lg, 16px)",
|
|
885
|
+
padding: "10px 12px",
|
|
886
|
+
background: "var(--dsw-alias-bg-layer-1)"
|
|
887
|
+
},
|
|
888
|
+
deviceTitle: {
|
|
889
|
+
fontWeight: 500,
|
|
890
|
+
fontSize: 13,
|
|
891
|
+
lineHeight: "20px",
|
|
892
|
+
color: "var(--dsw-alias-label-primary)"
|
|
893
|
+
},
|
|
894
|
+
deviceCode: {
|
|
895
|
+
fontFamily: "monospace",
|
|
896
|
+
fontSize: 18,
|
|
897
|
+
lineHeight: "24px",
|
|
898
|
+
letterSpacing: 2,
|
|
899
|
+
color: "var(--dsw-alias-label-primary)",
|
|
900
|
+
userSelect: "all"
|
|
901
|
+
},
|
|
902
|
+
details: {
|
|
903
|
+
display: "flex",
|
|
904
|
+
flexDirection: "column",
|
|
905
|
+
gap: 4,
|
|
906
|
+
fontSize: 12,
|
|
907
|
+
lineHeight: "18px",
|
|
908
|
+
color: "var(--dsw-alias-label-tertiary)"
|
|
909
|
+
},
|
|
910
|
+
detailRow: {
|
|
911
|
+
display: "flex",
|
|
912
|
+
gap: 8,
|
|
913
|
+
justifyContent: "space-between",
|
|
914
|
+
alignItems: "baseline"
|
|
915
|
+
},
|
|
916
|
+
detailValue: {
|
|
917
|
+
userSelect: "all",
|
|
918
|
+
overflowWrap: "anywhere",
|
|
919
|
+
textAlign: "right"
|
|
920
|
+
},
|
|
921
|
+
modal: {
|
|
922
|
+
width: 440,
|
|
923
|
+
maxWidth: "100%",
|
|
924
|
+
maxHeight: "85vh",
|
|
925
|
+
overflowY: "auto",
|
|
926
|
+
boxSizing: "border-box",
|
|
927
|
+
display: "flex",
|
|
928
|
+
flexDirection: "column",
|
|
929
|
+
gap: 10,
|
|
930
|
+
padding: "16px 18px",
|
|
931
|
+
margin: "auto"
|
|
932
|
+
},
|
|
933
|
+
modalHeader: {
|
|
934
|
+
display: "flex",
|
|
935
|
+
alignItems: "center",
|
|
936
|
+
gap: 8
|
|
937
|
+
},
|
|
938
|
+
modalTitle: {
|
|
939
|
+
fontWeight: 500,
|
|
940
|
+
fontSize: 16,
|
|
941
|
+
lineHeight: "24px",
|
|
942
|
+
color: "var(--dsw-alias-label-primary)"
|
|
943
|
+
},
|
|
944
|
+
modalActions: {
|
|
945
|
+
display: "flex",
|
|
946
|
+
gap: 8,
|
|
947
|
+
justifyContent: "flex-end",
|
|
948
|
+
marginTop: 2
|
|
949
|
+
},
|
|
950
|
+
divider: { borderTop: "0.5px solid var(--dsw-alias-border-l2)" }
|
|
951
|
+
};
|
|
952
|
+
/** Status dot color for one provider's connection state. */
|
|
953
|
+
function dotColor(status) {
|
|
954
|
+
if (status.busy) return "var(--dsw-alias-state-warn-label)";
|
|
955
|
+
if (status.loggedIn) return "var(--dsw-alias-state-success-primary)";
|
|
956
|
+
return "var(--dsw-alias-label-dimmed)";
|
|
957
|
+
}
|
|
958
|
+
/** Localized label for one provider-owned detail row. */
|
|
959
|
+
function detailLabel(t, row) {
|
|
960
|
+
const key = `detail_${row.key}`;
|
|
961
|
+
return key in en ? t(key) : row.key;
|
|
962
|
+
}
|
|
963
|
+
/** Localized label for one metered window the provider states. */
|
|
964
|
+
function windowLabel(t, window$1) {
|
|
965
|
+
const key = `usage_${window$1.label}`;
|
|
966
|
+
return key in en ? t(key) : window$1.label;
|
|
967
|
+
}
|
|
968
|
+
/** How much of one window is spent, as a display string. */
|
|
969
|
+
function windowAmount(window$1) {
|
|
970
|
+
if (window$1.usedPercent !== void 0) return `${String(Math.round(window$1.usedPercent))}%`;
|
|
971
|
+
if (window$1.used !== void 0 && window$1.limit !== void 0) return `${String(window$1.used)}/${String(window$1.limit)}`;
|
|
972
|
+
return "—";
|
|
973
|
+
}
|
|
974
|
+
/** One account's plan and metered allowances. */
|
|
975
|
+
function UsageBlock(props) {
|
|
976
|
+
const { usage, error, t, busyAction, onRefresh } = props;
|
|
977
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
978
|
+
style: styles.usage,
|
|
979
|
+
children: [
|
|
980
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
981
|
+
style: styles.usageHeader,
|
|
982
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
983
|
+
style: styles.catalogTitle,
|
|
984
|
+
children: t("usageTitle")
|
|
985
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
986
|
+
type: "button",
|
|
987
|
+
className: "dsh-subscription-quiet",
|
|
988
|
+
style: styles.pushRight,
|
|
989
|
+
disabled: busyAction,
|
|
990
|
+
onClick: onRefresh,
|
|
991
|
+
children: busyAction ? t("refreshing") : t("usageRefresh")
|
|
992
|
+
})]
|
|
993
|
+
}),
|
|
994
|
+
error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
995
|
+
className: "dsh-subscription-error",
|
|
996
|
+
children: t("usageError", { message: error })
|
|
997
|
+
}),
|
|
998
|
+
usage.length === 0 && error === void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
999
|
+
style: styles.statusLine,
|
|
1000
|
+
children: t("usageNone")
|
|
1001
|
+
}),
|
|
1002
|
+
usage.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1003
|
+
style: styles.usageRow,
|
|
1004
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1005
|
+
style: styles.usageLine,
|
|
1006
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1007
|
+
style: styles.modelMeta,
|
|
1008
|
+
children: [entry.plan === void 0 ? entry.account : t("usagePlan", { plan: entry.plan }), entry.renewsAt === void 0 ? "" : ` · ${t("usageRenews", { time: formatTime(entry.renewsAt) })}`]
|
|
1009
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1010
|
+
style: styles.modelMeta,
|
|
1011
|
+
children: t("usageFetched", { time: formatTime(entry.fetchedAt) })
|
|
1012
|
+
})]
|
|
1013
|
+
}), entry.windows.map((window$1) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1014
|
+
style: styles.usageRow,
|
|
1015
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1016
|
+
style: styles.usageLine,
|
|
1017
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1018
|
+
style: styles.modelName,
|
|
1019
|
+
children: [
|
|
1020
|
+
windowLabel(t, window$1),
|
|
1021
|
+
" · ",
|
|
1022
|
+
windowAmount(window$1)
|
|
1023
|
+
]
|
|
1024
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1025
|
+
style: styles.modelMeta,
|
|
1026
|
+
children: window$1.resetsAt === void 0 ? "" : t("usageReset", { time: formatTime(window$1.resetsAt) })
|
|
1027
|
+
})]
|
|
1028
|
+
}), window$1.usedPercent !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1029
|
+
className: "dsh-subscription-meter",
|
|
1030
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1031
|
+
style: { width: `${String(Math.min(100, Math.max(0, window$1.usedPercent)))}%` },
|
|
1032
|
+
"data-exhausted": window$1.usedPercent >= 90 ? "true" : "false"
|
|
1033
|
+
})
|
|
1034
|
+
})]
|
|
1035
|
+
}, `${entry.account}-${window$1.label}`))]
|
|
1036
|
+
}, entry.account))
|
|
1037
|
+
]
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
1040
|
+
/** The pending-login block: a device code, or a link to the sign-in page. */
|
|
1041
|
+
function PendingLogin(props) {
|
|
1042
|
+
const { prompt, t, copied, onCopy } = props;
|
|
1043
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1044
|
+
style: styles.device,
|
|
1045
|
+
children: [
|
|
1046
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1047
|
+
style: styles.deviceTitle,
|
|
1048
|
+
children: prompt.mode === "device-code" ? t("deviceTitle") : t("openLogin")
|
|
1049
|
+
}),
|
|
1050
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1051
|
+
style: styles.statusLine,
|
|
1052
|
+
children: prompt.mode === "device-code" ? t("devicePrompt") : t("openLoginHint")
|
|
1053
|
+
}),
|
|
1054
|
+
prompt.userCode !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1055
|
+
style: styles.deviceCode,
|
|
1056
|
+
children: prompt.userCode
|
|
1057
|
+
}),
|
|
1058
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1059
|
+
style: styles.statusLine,
|
|
1060
|
+
children: t("deviceExpires", { time: formatTime(prompt.expiresAt) })
|
|
1061
|
+
}),
|
|
1062
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1063
|
+
style: styles.actions,
|
|
1064
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1065
|
+
type: "button",
|
|
1066
|
+
onClick: () => {
|
|
1067
|
+
window.open(prompt.url, "_blank", "noopener");
|
|
1068
|
+
},
|
|
1069
|
+
children: prompt.mode === "device-code" ? t("deviceOpen") : t("openLogin")
|
|
1070
|
+
}), prompt.userCode !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1071
|
+
type: "button",
|
|
1072
|
+
onClick: () => {
|
|
1073
|
+
onCopy(prompt.userCode);
|
|
1074
|
+
},
|
|
1075
|
+
children: copied ? t("deviceCopied") : t("deviceCopy")
|
|
1076
|
+
})]
|
|
1077
|
+
})
|
|
1078
|
+
]
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
1081
|
+
/** One provider's card. */
|
|
1082
|
+
function ProviderCard(props) {
|
|
1083
|
+
const { status, t, busyAction, message } = props;
|
|
1084
|
+
const [copied, setCopied] = (0, react.useState)(false);
|
|
1085
|
+
const [modelsOpen, setModelsOpen] = (0, react.useState)(false);
|
|
1086
|
+
/** Whether this provider's whole card is unfolded. */
|
|
1087
|
+
const [cardOpen, setCardOpen] = (0, react.useState)(false);
|
|
1088
|
+
const [manageOpen, setManageOpen] = (0, react.useState)(false);
|
|
1089
|
+
const mounted = (0, react.useRef)(true);
|
|
1090
|
+
const dialog = (0, react.useRef)(null);
|
|
1091
|
+
(0, react.useEffect)(() => {
|
|
1092
|
+
mounted.current = true;
|
|
1093
|
+
return () => {
|
|
1094
|
+
mounted.current = false;
|
|
1095
|
+
};
|
|
1096
|
+
}, []);
|
|
1097
|
+
(0, react.useEffect)(() => {
|
|
1098
|
+
const element = dialog.current;
|
|
1099
|
+
if (element === null || typeof element.showModal !== "function") return;
|
|
1100
|
+
if (manageOpen && !element.open) element.showModal();
|
|
1101
|
+
if (!manageOpen && element.open) element.close();
|
|
1102
|
+
}, [manageOpen]);
|
|
1103
|
+
const copyCode = (0, react.useCallback)(async (code) => {
|
|
1104
|
+
try {
|
|
1105
|
+
await navigator.clipboard.writeText(code);
|
|
1106
|
+
if (mounted.current) {
|
|
1107
|
+
setCopied(true);
|
|
1108
|
+
setTimeout(() => {
|
|
1109
|
+
if (mounted.current) setCopied(false);
|
|
1110
|
+
}, 2e3);
|
|
1111
|
+
}
|
|
1112
|
+
} catch {}
|
|
1113
|
+
}, []);
|
|
1114
|
+
const toggleModelsOpen = (0, react.useCallback)(() => {
|
|
1115
|
+
setModelsOpen((open) => !open);
|
|
1116
|
+
}, []);
|
|
1117
|
+
/** Fold or unfold this provider's card. */
|
|
1118
|
+
const toggleCardOpen = (0, react.useCallback)(() => {
|
|
1119
|
+
setCardOpen((open) => !open);
|
|
1120
|
+
}, []);
|
|
1121
|
+
const accounts = status.accounts;
|
|
1122
|
+
const models = status.models;
|
|
1123
|
+
/** Whether this route signs in at all; the auto provider does not. */
|
|
1124
|
+
const connectable = status.connectable !== false;
|
|
1125
|
+
const hiddenCount = models.filter((model) => !model.visible).length;
|
|
1126
|
+
/** Whether the add-a-model-id field is open, and what has been typed. */
|
|
1127
|
+
const [adding, setAdding] = (0, react.useState)(false);
|
|
1128
|
+
const [draft, setDraft] = (0, react.useState)("");
|
|
1129
|
+
/** The row being dragged, so it can be dimmed while it moves. */
|
|
1130
|
+
const [dragFrom, setDragFrom] = (0, react.useState)(void 0);
|
|
1131
|
+
/**
|
|
1132
|
+
* The dragged row, mirrored in a ref.
|
|
1133
|
+
*
|
|
1134
|
+
* The drop is handled by an ancestor whose handler was attached in an earlier
|
|
1135
|
+
* render, so it must not depend on state that was set during the drag.
|
|
1136
|
+
*/
|
|
1137
|
+
const dragFromRef = (0, react.useRef)(void 0);
|
|
1138
|
+
/** The row the pointer is currently over, so a drop lands where the user aims. */
|
|
1139
|
+
const dropTargetRef = (0, react.useRef)(void 0);
|
|
1140
|
+
/** The same row in state, purely so it can be highlighted while dragging. */
|
|
1141
|
+
const [dropTarget, setDropTarget] = (0, react.useState)(void 0);
|
|
1142
|
+
/**
|
|
1143
|
+
* Commit one dropped arrangement.
|
|
1144
|
+
*
|
|
1145
|
+
* The node half takes the full chain in its new order and appends whatever the
|
|
1146
|
+
* page left out, so sending just these ids is enough — and a model hidden in
|
|
1147
|
+
* another window is never dropped from the stored rank by a stale page.
|
|
1148
|
+
*/
|
|
1149
|
+
const commitOrder = (0, react.useCallback)((ids) => {
|
|
1150
|
+
props.onSetModelOrder(ids);
|
|
1151
|
+
}, [props]);
|
|
1152
|
+
/** Drag-and-drop plus its keyboard equivalent on the drag handle. */
|
|
1153
|
+
const drag = {
|
|
1154
|
+
start: (event, index) => {
|
|
1155
|
+
setDragFrom(index);
|
|
1156
|
+
dragFromRef.current = index;
|
|
1157
|
+
event.dataTransfer.effectAllowed = "move";
|
|
1158
|
+
event.dataTransfer.setData("text/plain", String(index));
|
|
1159
|
+
},
|
|
1160
|
+
over: (event, _index) => {
|
|
1161
|
+
event.preventDefault();
|
|
1162
|
+
event.dataTransfer.dropEffect = "move";
|
|
1163
|
+
const container = event.currentTarget;
|
|
1164
|
+
const to = indexAt(container, event.clientY);
|
|
1165
|
+
dropTargetRef.current = to;
|
|
1166
|
+
if (to !== void 0 && to !== dropTarget) setDropTarget(to);
|
|
1167
|
+
},
|
|
1168
|
+
drop: (event, index) => {
|
|
1169
|
+
event.preventDefault();
|
|
1170
|
+
const to = dropTargetRef.current ?? indexAt(event.currentTarget, event.clientY) ?? index;
|
|
1171
|
+
move(dragFromRef.current, to);
|
|
1172
|
+
dragFromRef.current = void 0;
|
|
1173
|
+
dropTargetRef.current = void 0;
|
|
1174
|
+
setDropTarget(void 0);
|
|
1175
|
+
setDragFrom(void 0);
|
|
1176
|
+
},
|
|
1177
|
+
dropAt: (event) => {
|
|
1178
|
+
event.preventDefault();
|
|
1179
|
+
const container = event.currentTarget;
|
|
1180
|
+
const to = dropTargetRef.current ?? indexAt(container, event.clientY);
|
|
1181
|
+
if (to !== void 0) move(dragFromRef.current, to);
|
|
1182
|
+
dragFromRef.current = void 0;
|
|
1183
|
+
dropTargetRef.current = void 0;
|
|
1184
|
+
setDropTarget(void 0);
|
|
1185
|
+
setDragFrom(void 0);
|
|
1186
|
+
},
|
|
1187
|
+
end: () => {
|
|
1188
|
+
dragFromRef.current = void 0;
|
|
1189
|
+
dropTargetRef.current = void 0;
|
|
1190
|
+
setDropTarget(void 0);
|
|
1191
|
+
setDragFrom(void 0);
|
|
1192
|
+
},
|
|
1193
|
+
key: (event, index) => {
|
|
1194
|
+
if (event.key !== "ArrowUp" && event.key !== "ArrowDown") return;
|
|
1195
|
+
event.preventDefault();
|
|
1196
|
+
move(index, index + (event.key === "ArrowUp" ? -1 : 1));
|
|
1197
|
+
}
|
|
1198
|
+
};
|
|
1199
|
+
/** Move one model row to another position and persist the result. */
|
|
1200
|
+
function move(from, to) {
|
|
1201
|
+
if (from === void 0 || from === to) return;
|
|
1202
|
+
if (from < 0 || to < 0 || from >= models.length || to >= models.length) return;
|
|
1203
|
+
const ids = models.map((model) => model.id);
|
|
1204
|
+
const [moved] = ids.splice(from, 1);
|
|
1205
|
+
if (moved === void 0) return;
|
|
1206
|
+
ids.splice(to, 0, moved);
|
|
1207
|
+
commitOrder(ids);
|
|
1208
|
+
}
|
|
1209
|
+
/**
|
|
1210
|
+
* The row index a drop at this pointer position lands on.
|
|
1211
|
+
*
|
|
1212
|
+
* The drop is handled on the whole list rather than per row: rows are tall and
|
|
1213
|
+
* separated by gaps, so a drop between two of them hits no target at all and
|
|
1214
|
+
* silently does nothing. Comparing the pointer against each row's midpoint also
|
|
1215
|
+
* makes the gesture match what the user sees.
|
|
1216
|
+
*/
|
|
1217
|
+
function indexAt(container, clientY) {
|
|
1218
|
+
const rows = Array.from(container.querySelectorAll("[data-model-index]"));
|
|
1219
|
+
if (rows.length === 0) return void 0;
|
|
1220
|
+
let closest = 0;
|
|
1221
|
+
let best = Number.POSITIVE_INFINITY;
|
|
1222
|
+
for (const row of rows) {
|
|
1223
|
+
const box = row.getBoundingClientRect();
|
|
1224
|
+
const distance = Math.abs(clientY - (box.top + box.height / 2));
|
|
1225
|
+
if (distance >= best) continue;
|
|
1226
|
+
best = distance;
|
|
1227
|
+
closest = Number(row.dataset.modelIndex);
|
|
1228
|
+
}
|
|
1229
|
+
return closest;
|
|
1230
|
+
}
|
|
1231
|
+
const busy = status.busy;
|
|
1232
|
+
const liveCatalog = (status.discovered?.length ?? 0) > 0;
|
|
1233
|
+
/**
|
|
1234
|
+
* What a folded card says about itself.
|
|
1235
|
+
*
|
|
1236
|
+
* The connection is the reason to open a card, so the folded row carries it;
|
|
1237
|
+
* `auto` has no account and counts the models it will actually try instead.
|
|
1238
|
+
*/
|
|
1239
|
+
const summaryLine = !connectable ? t("statusAuto", { count: models.filter((model) => model.visible).length }) : busy ? t("statusBusy") : status.loggedIn ? t("statusConnected", { count: accounts.length }) : t("statusSignedOut");
|
|
1240
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1241
|
+
style: styles.card,
|
|
1242
|
+
children: [
|
|
1243
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1244
|
+
style: styles.cardHeader,
|
|
1245
|
+
children: [
|
|
1246
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1247
|
+
style: {
|
|
1248
|
+
...styles.dot,
|
|
1249
|
+
background: dotColor(status)
|
|
1250
|
+
},
|
|
1251
|
+
"aria-hidden": "true"
|
|
1252
|
+
}),
|
|
1253
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1254
|
+
style: styles.name,
|
|
1255
|
+
children: status.displayName
|
|
1256
|
+
}),
|
|
1257
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1258
|
+
style: styles.providerId,
|
|
1259
|
+
children: status.provider
|
|
1260
|
+
}),
|
|
1261
|
+
!cardOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1262
|
+
style: styles.cardSummary,
|
|
1263
|
+
children: summaryLine
|
|
1264
|
+
}),
|
|
1265
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1266
|
+
type: "button",
|
|
1267
|
+
className: "dsh-subscription-quiet",
|
|
1268
|
+
style: styles.pushRight,
|
|
1269
|
+
"aria-expanded": cardOpen,
|
|
1270
|
+
"aria-controls": `dsh-subscription-card-${status.provider}`,
|
|
1271
|
+
onClick: toggleCardOpen,
|
|
1272
|
+
children: cardOpen ? t("collapse") : t("edit")
|
|
1273
|
+
})
|
|
1274
|
+
]
|
|
1275
|
+
}),
|
|
1276
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1277
|
+
id: `dsh-subscription-card-${status.provider}`,
|
|
1278
|
+
style: cardOpen ? styles.cardBody : {
|
|
1279
|
+
...styles.cardBody,
|
|
1280
|
+
display: "none"
|
|
1281
|
+
},
|
|
1282
|
+
children: [
|
|
1283
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1284
|
+
style: styles.statusLine,
|
|
1285
|
+
children: !connectable ? t("statusAuto", { count: models.filter((model) => model.visible).length }) : busy ? t("statusBusy") : status.loggedIn ? t("statusConnected", { count: accounts.length }) : t("statusSignedOut")
|
|
1286
|
+
}),
|
|
1287
|
+
connectable && !status.loggedIn && !busy && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1288
|
+
style: styles.statusLine,
|
|
1289
|
+
children: t("statusSignIn")
|
|
1290
|
+
}),
|
|
1291
|
+
status.lastError !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1292
|
+
className: "dsh-subscription-error",
|
|
1293
|
+
children: t("lastError", { message: status.lastError })
|
|
1294
|
+
}),
|
|
1295
|
+
message?.tone === "error" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1296
|
+
className: "dsh-subscription-error",
|
|
1297
|
+
children: message.text
|
|
1298
|
+
}),
|
|
1299
|
+
message?.tone === "success" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1300
|
+
className: "dsh-subscription-success",
|
|
1301
|
+
children: message.text
|
|
1302
|
+
}),
|
|
1303
|
+
accounts.map((account) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1304
|
+
style: styles.accountRow,
|
|
1305
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1306
|
+
style: styles.accountHeader,
|
|
1307
|
+
children: [
|
|
1308
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1309
|
+
type: "button",
|
|
1310
|
+
style: styles.starButton,
|
|
1311
|
+
title: account.isDefault ? t("defaultBadge") : t("makeDefault"),
|
|
1312
|
+
"aria-label": account.isDefault ? t("defaultBadge") : t("makeDefault"),
|
|
1313
|
+
disabled: busyAction || account.isDefault,
|
|
1314
|
+
onClick: () => {
|
|
1315
|
+
props.onSetDefault(account.account);
|
|
1316
|
+
},
|
|
1317
|
+
children: account.isDefault ? "★" : "☆"
|
|
1318
|
+
}),
|
|
1319
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1320
|
+
style: styles.accountName,
|
|
1321
|
+
children: account.label
|
|
1322
|
+
}),
|
|
1323
|
+
account.isDefault && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1324
|
+
style: styles.badge,
|
|
1325
|
+
children: t("defaultBadge")
|
|
1326
|
+
}),
|
|
1327
|
+
account.needsLogin === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1328
|
+
style: styles.badge,
|
|
1329
|
+
title: t("accountNeedsLoginHint"),
|
|
1330
|
+
children: t("accountNeedsLogin")
|
|
1331
|
+
}),
|
|
1332
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1333
|
+
style: styles.statusLine,
|
|
1334
|
+
children: account.expired ? t("accountExpired") : t("accountExpires", { time: formatTime(account.expiresAt) })
|
|
1335
|
+
}),
|
|
1336
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1337
|
+
type: "button",
|
|
1338
|
+
className: "dsh-subscription-quiet",
|
|
1339
|
+
style: styles.pushRight,
|
|
1340
|
+
disabled: busyAction,
|
|
1341
|
+
onClick: () => {
|
|
1342
|
+
props.onLogout(account.account);
|
|
1343
|
+
},
|
|
1344
|
+
children: t("logout")
|
|
1345
|
+
})
|
|
1346
|
+
]
|
|
1347
|
+
})
|
|
1348
|
+
}, account.account)),
|
|
1349
|
+
status.loggedIn && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1350
|
+
style: styles.catalog,
|
|
1351
|
+
children: [
|
|
1352
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1353
|
+
style: styles.catalogHeader,
|
|
1354
|
+
children: [
|
|
1355
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1356
|
+
type: "button",
|
|
1357
|
+
className: "dsh-subscription-disclosure",
|
|
1358
|
+
style: styles.disclosure,
|
|
1359
|
+
"aria-expanded": modelsOpen,
|
|
1360
|
+
"aria-controls": `dsh-subscription-models-${status.provider}`,
|
|
1361
|
+
onClick: toggleModelsOpen,
|
|
1362
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1363
|
+
style: styles.chevron,
|
|
1364
|
+
"aria-hidden": "true",
|
|
1365
|
+
children: modelsOpen ? "▾" : "▸"
|
|
1366
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1367
|
+
style: styles.catalogTitle,
|
|
1368
|
+
children: t("modelsTitle")
|
|
1369
|
+
})]
|
|
1370
|
+
}),
|
|
1371
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1372
|
+
style: styles.catalogMeta,
|
|
1373
|
+
children: [
|
|
1374
|
+
t("modelsCount", { count: models.length }),
|
|
1375
|
+
" · ",
|
|
1376
|
+
liveCatalog ? t("modelsLive") : t("modelsBuiltIn"),
|
|
1377
|
+
hiddenCount === 0 ? "" : ` ${t("modelsHiddenCount", { count: hiddenCount })}`
|
|
1378
|
+
]
|
|
1379
|
+
}),
|
|
1380
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1381
|
+
type: "button",
|
|
1382
|
+
className: "dsh-subscription-quiet",
|
|
1383
|
+
style: styles.pushRight,
|
|
1384
|
+
disabled: busyAction,
|
|
1385
|
+
onClick: props.onRefreshModels,
|
|
1386
|
+
children: busyAction ? t("refreshing") : t("refresh")
|
|
1387
|
+
}),
|
|
1388
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1389
|
+
type: "button",
|
|
1390
|
+
className: "dsh-subscription-quiet",
|
|
1391
|
+
disabled: busyAction || !connectable,
|
|
1392
|
+
title: t("modelsAddHint"),
|
|
1393
|
+
onClick: () => {
|
|
1394
|
+
setAdding((open) => !open);
|
|
1395
|
+
},
|
|
1396
|
+
children: t("modelsAdd")
|
|
1397
|
+
})
|
|
1398
|
+
]
|
|
1399
|
+
}),
|
|
1400
|
+
adding && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
|
|
1401
|
+
style: styles.addRow,
|
|
1402
|
+
onSubmit: (event) => {
|
|
1403
|
+
event.preventDefault();
|
|
1404
|
+
const id = draft.trim();
|
|
1405
|
+
if (id.length === 0) return;
|
|
1406
|
+
props.onAddModel(id);
|
|
1407
|
+
setDraft("");
|
|
1408
|
+
setAdding(false);
|
|
1409
|
+
},
|
|
1410
|
+
children: [
|
|
1411
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1412
|
+
type: "text",
|
|
1413
|
+
className: "dsh-subscription-input",
|
|
1414
|
+
style: styles.addInput,
|
|
1415
|
+
value: draft,
|
|
1416
|
+
autoFocus: true,
|
|
1417
|
+
spellCheck: false,
|
|
1418
|
+
placeholder: t("modelsAddPlaceholder"),
|
|
1419
|
+
"aria-label": t("modelsAdd"),
|
|
1420
|
+
disabled: busyAction,
|
|
1421
|
+
onChange: (event) => {
|
|
1422
|
+
setDraft(event.target.value);
|
|
1423
|
+
}
|
|
1424
|
+
}),
|
|
1425
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1426
|
+
type: "submit",
|
|
1427
|
+
disabled: busyAction || draft.trim().length === 0,
|
|
1428
|
+
children: t("modelsAddConfirm")
|
|
1429
|
+
}),
|
|
1430
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1431
|
+
type: "button",
|
|
1432
|
+
className: "dsh-subscription-quiet",
|
|
1433
|
+
onClick: () => {
|
|
1434
|
+
setAdding(false);
|
|
1435
|
+
setDraft("");
|
|
1436
|
+
},
|
|
1437
|
+
children: t("cancel")
|
|
1438
|
+
})
|
|
1439
|
+
]
|
|
1440
|
+
}),
|
|
1441
|
+
modelsOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1442
|
+
id: `dsh-subscription-models-${status.provider}`,
|
|
1443
|
+
style: styles.modelList,
|
|
1444
|
+
onDragOver: (event) => {
|
|
1445
|
+
drag.over(event, 0);
|
|
1446
|
+
},
|
|
1447
|
+
onDrop: drag.dropAt,
|
|
1448
|
+
children: [
|
|
1449
|
+
status.provider === "auto" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1450
|
+
style: styles.chainTitle,
|
|
1451
|
+
children: t("modelsAutoChain")
|
|
1452
|
+
}),
|
|
1453
|
+
models.map((model, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1454
|
+
"data-model-index": index,
|
|
1455
|
+
draggable: !busyAction,
|
|
1456
|
+
style: {
|
|
1457
|
+
...styles.modelRowWrap,
|
|
1458
|
+
...dragFrom === index ? { opacity: .5 } : {},
|
|
1459
|
+
...dropTarget === index && dragFrom !== void 0 && dragFrom !== index ? { boxShadow: "inset 0 2px 0 0 var(--dsw-alias-brand-primary)" } : {}
|
|
1460
|
+
},
|
|
1461
|
+
onDragStart: (event) => {
|
|
1462
|
+
drag.start(event, index);
|
|
1463
|
+
},
|
|
1464
|
+
onDragEnd: drag.end,
|
|
1465
|
+
children: [
|
|
1466
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1467
|
+
type: "button",
|
|
1468
|
+
className: "dsh-subscription-handle",
|
|
1469
|
+
disabled: busyAction,
|
|
1470
|
+
title: t("modelsDrag"),
|
|
1471
|
+
"aria-label": `${t("modelsDrag")} · ${model.name}`,
|
|
1472
|
+
onKeyDown: (event) => {
|
|
1473
|
+
drag.key(event, index);
|
|
1474
|
+
},
|
|
1475
|
+
children: "⠿"
|
|
1476
|
+
}),
|
|
1477
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
1478
|
+
style: model.visible ? {
|
|
1479
|
+
...styles.modelRowGrow,
|
|
1480
|
+
cursor: "pointer"
|
|
1481
|
+
} : {
|
|
1482
|
+
...styles.modelRowGrow,
|
|
1483
|
+
cursor: "pointer",
|
|
1484
|
+
color: "var(--dsw-alias-label-dimmed)"
|
|
1485
|
+
},
|
|
1486
|
+
title: model.visible ? t("modelsHidden") : t("modelsShow"),
|
|
1487
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1488
|
+
type: "checkbox",
|
|
1489
|
+
className: "dsh-subscription-switch",
|
|
1490
|
+
role: "switch",
|
|
1491
|
+
checked: model.visible,
|
|
1492
|
+
disabled: busyAction,
|
|
1493
|
+
"aria-label": `${t("modelsShow")} · ${model.name}`,
|
|
1494
|
+
onChange: (event) => {
|
|
1495
|
+
props.onSetModelVisible(model.id, event.target.checked);
|
|
1496
|
+
}
|
|
1497
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1498
|
+
style: styles.modelBody,
|
|
1499
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1500
|
+
style: styles.modelName,
|
|
1501
|
+
children: [model.name, model.source === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1502
|
+
style: styles.badge,
|
|
1503
|
+
children: model.source
|
|
1504
|
+
})]
|
|
1505
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1506
|
+
style: styles.modelMeta,
|
|
1507
|
+
children: [
|
|
1508
|
+
model.id,
|
|
1509
|
+
" · ",
|
|
1510
|
+
t("modelsContext", { tokens: formatTokens(model.contextWindow) }),
|
|
1511
|
+
model.defaultEffort === void 0 ? "" : ` · ${model.defaultEffort}`,
|
|
1512
|
+
model.inputModalities === void 0 || model.inputModalities.length === 0 ? "" : ` · ${model.inputModalities.join("/")}`
|
|
1513
|
+
]
|
|
1514
|
+
})]
|
|
1515
|
+
})]
|
|
1516
|
+
}),
|
|
1517
|
+
model.userAdded === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1518
|
+
type: "button",
|
|
1519
|
+
className: "dsh-subscription-quiet",
|
|
1520
|
+
style: styles.removeButton,
|
|
1521
|
+
disabled: busyAction,
|
|
1522
|
+
title: t("modelsRemove"),
|
|
1523
|
+
"aria-label": `${t("modelsRemove")} · ${model.name}`,
|
|
1524
|
+
onClick: () => {
|
|
1525
|
+
props.onRemoveModel(model.id);
|
|
1526
|
+
},
|
|
1527
|
+
children: "×"
|
|
1528
|
+
})
|
|
1529
|
+
]
|
|
1530
|
+
}, model.id)),
|
|
1531
|
+
models.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1532
|
+
style: styles.statusLine,
|
|
1533
|
+
children: status.provider === "auto" ? t("modelsAutoChainEmpty") : t("modelsEmpty")
|
|
1534
|
+
})
|
|
1535
|
+
]
|
|
1536
|
+
})
|
|
1537
|
+
]
|
|
1538
|
+
}),
|
|
1539
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageBlock, {
|
|
1540
|
+
usage: status.usage ?? [],
|
|
1541
|
+
...status.usageError === void 0 ? {} : { error: status.usageError },
|
|
1542
|
+
t,
|
|
1543
|
+
busyAction,
|
|
1544
|
+
onRefresh: props.onRefreshUsage
|
|
1545
|
+
}),
|
|
1546
|
+
connectable && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1547
|
+
style: styles.actions,
|
|
1548
|
+
children: [
|
|
1549
|
+
!busy && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1550
|
+
type: "button",
|
|
1551
|
+
disabled: busyAction,
|
|
1552
|
+
onClick: props.onLogin,
|
|
1553
|
+
children: accounts.length === 0 ? t("connect") : t("addAccount")
|
|
1554
|
+
}),
|
|
1555
|
+
!busy && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1556
|
+
type: "button",
|
|
1557
|
+
disabled: busyAction,
|
|
1558
|
+
onClick: () => {
|
|
1559
|
+
setManageOpen(true);
|
|
1560
|
+
},
|
|
1561
|
+
children: t("manage")
|
|
1562
|
+
}),
|
|
1563
|
+
busy && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1564
|
+
type: "button",
|
|
1565
|
+
disabled: busyAction,
|
|
1566
|
+
onClick: props.onCancel,
|
|
1567
|
+
children: t("cancel")
|
|
1568
|
+
})
|
|
1569
|
+
]
|
|
1570
|
+
}),
|
|
1571
|
+
connectable && accounts.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1572
|
+
style: styles.statusLine,
|
|
1573
|
+
children: t("hint")
|
|
1574
|
+
}),
|
|
1575
|
+
busy && status.pending !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PendingLogin, {
|
|
1576
|
+
prompt: status.pending,
|
|
1577
|
+
t,
|
|
1578
|
+
copied,
|
|
1579
|
+
onCopy: (code) => {
|
|
1580
|
+
copyCode(code);
|
|
1581
|
+
}
|
|
1582
|
+
})
|
|
1583
|
+
]
|
|
1584
|
+
}),
|
|
1585
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dialog", {
|
|
1586
|
+
ref: dialog,
|
|
1587
|
+
className: "dsh-subscription-manager",
|
|
1588
|
+
"aria-labelledby": `dsh-subscription-manage-${status.provider}`,
|
|
1589
|
+
style: manageOpen ? styles.modal : {
|
|
1590
|
+
...styles.modal,
|
|
1591
|
+
display: "none"
|
|
1592
|
+
},
|
|
1593
|
+
onClose: () => {
|
|
1594
|
+
setManageOpen(false);
|
|
1595
|
+
},
|
|
1596
|
+
onCancel: () => {
|
|
1597
|
+
setManageOpen(false);
|
|
1598
|
+
},
|
|
1599
|
+
children: [
|
|
1600
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1601
|
+
style: styles.modalHeader,
|
|
1602
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1603
|
+
style: styles.modalTitle,
|
|
1604
|
+
id: `dsh-subscription-manage-${status.provider}`,
|
|
1605
|
+
children: t("manageTitle", { provider: status.displayName })
|
|
1606
|
+
})
|
|
1607
|
+
}),
|
|
1608
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1609
|
+
style: styles.statusLine,
|
|
1610
|
+
children: t("manageAccounts")
|
|
1611
|
+
}),
|
|
1612
|
+
accounts.map((account) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1613
|
+
style: styles.accountRow,
|
|
1614
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1615
|
+
style: styles.accountHeader,
|
|
1616
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1617
|
+
style: styles.accountName,
|
|
1618
|
+
children: account.label
|
|
1619
|
+
}), account.isDefault && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1620
|
+
style: styles.badge,
|
|
1621
|
+
children: t("defaultBadge")
|
|
1622
|
+
})]
|
|
1623
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1624
|
+
style: styles.actions,
|
|
1625
|
+
children: [
|
|
1626
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1627
|
+
type: "button",
|
|
1628
|
+
disabled: busyAction,
|
|
1629
|
+
onClick: () => {
|
|
1630
|
+
props.onRefreshToken(account.account);
|
|
1631
|
+
},
|
|
1632
|
+
children: t("refreshToken")
|
|
1633
|
+
}),
|
|
1634
|
+
!account.isDefault && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1635
|
+
type: "button",
|
|
1636
|
+
disabled: busyAction,
|
|
1637
|
+
onClick: () => {
|
|
1638
|
+
props.onSetDefault(account.account);
|
|
1639
|
+
},
|
|
1640
|
+
children: t("makeDefault")
|
|
1641
|
+
}),
|
|
1642
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1643
|
+
type: "button",
|
|
1644
|
+
disabled: busyAction,
|
|
1645
|
+
onClick: () => {
|
|
1646
|
+
props.onLogout(account.account);
|
|
1647
|
+
},
|
|
1648
|
+
children: t("logout")
|
|
1649
|
+
})
|
|
1650
|
+
]
|
|
1651
|
+
})]
|
|
1652
|
+
}, account.account)),
|
|
1653
|
+
accounts.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1654
|
+
style: styles.statusLine,
|
|
1655
|
+
children: t("statusSignedOut")
|
|
1656
|
+
}),
|
|
1657
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: styles.divider }),
|
|
1658
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1659
|
+
style: styles.actions,
|
|
1660
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1661
|
+
type: "button",
|
|
1662
|
+
disabled: busyAction,
|
|
1663
|
+
onClick: props.onReload,
|
|
1664
|
+
children: t("reload")
|
|
1665
|
+
})
|
|
1666
|
+
}),
|
|
1667
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1668
|
+
style: styles.statusLine,
|
|
1669
|
+
children: t("reloadHint")
|
|
1670
|
+
}),
|
|
1671
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: styles.divider }),
|
|
1672
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1673
|
+
style: styles.details,
|
|
1674
|
+
children: [status.details.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1675
|
+
style: styles.detailRow,
|
|
1676
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: detailLabel(t, row) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1677
|
+
style: styles.detailValue,
|
|
1678
|
+
children: row.value
|
|
1679
|
+
})]
|
|
1680
|
+
}, row.key)), status.discovered !== void 0 && status.discovered.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1681
|
+
style: styles.detailRow,
|
|
1682
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("modelsLive") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1683
|
+
style: styles.detailValue,
|
|
1684
|
+
children: status.discovered.join(", ")
|
|
1685
|
+
})]
|
|
1686
|
+
})]
|
|
1687
|
+
}),
|
|
1688
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1689
|
+
className: "dsh-subscription-dialog-actions",
|
|
1690
|
+
style: styles.modalActions,
|
|
1691
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1692
|
+
type: "button",
|
|
1693
|
+
onClick: () => {
|
|
1694
|
+
setManageOpen(false);
|
|
1695
|
+
},
|
|
1696
|
+
children: t("close")
|
|
1697
|
+
})
|
|
1698
|
+
})
|
|
1699
|
+
]
|
|
1700
|
+
})
|
|
1701
|
+
]
|
|
1702
|
+
});
|
|
1703
|
+
}
|
|
1704
|
+
/**
|
|
1705
|
+
* The Subscriptions settings section.
|
|
1706
|
+
* @param props - section props (inject face plus the shell's `close`).
|
|
1707
|
+
* @returns the section content.
|
|
1708
|
+
*/
|
|
1709
|
+
function SubscriptionsSection(props) {
|
|
1710
|
+
const { call } = props;
|
|
1711
|
+
const t = props.t ?? fallbackTranslate;
|
|
1712
|
+
const [status, setStatus] = (0, react.useState)(void 0);
|
|
1713
|
+
const [error, setError] = (0, react.useState)(void 0);
|
|
1714
|
+
const [messages, setMessages] = (0, react.useState)({});
|
|
1715
|
+
const [busyAction, setBusyAction] = (0, react.useState)(false);
|
|
1716
|
+
const mounted = (0, react.useRef)(true);
|
|
1717
|
+
const timer = (0, react.useRef)(void 0);
|
|
1718
|
+
/** Fetch the current state; a failure is shown rather than swallowed. */
|
|
1719
|
+
const refresh = (0, react.useCallback)(async () => {
|
|
1720
|
+
if (call === void 0) return;
|
|
1721
|
+
try {
|
|
1722
|
+
const next = await call("status", void 0);
|
|
1723
|
+
if (!mounted.current) return;
|
|
1724
|
+
setStatus(next);
|
|
1725
|
+
setError(void 0);
|
|
1726
|
+
} catch (caught) {
|
|
1727
|
+
if (mounted.current) setError(t("actionFailed", { message: messageOf(caught) }));
|
|
1728
|
+
}
|
|
1729
|
+
}, [call, t]);
|
|
1730
|
+
const stopPolling = (0, react.useCallback)(() => {
|
|
1731
|
+
if (timer.current !== void 0) {
|
|
1732
|
+
clearInterval(timer.current);
|
|
1733
|
+
timer.current = void 0;
|
|
1734
|
+
}
|
|
1735
|
+
}, []);
|
|
1736
|
+
const startPolling = (0, react.useCallback)(() => {
|
|
1737
|
+
if (timer.current !== void 0) return;
|
|
1738
|
+
timer.current = setInterval(() => {
|
|
1739
|
+
refresh();
|
|
1740
|
+
}, POLL_INTERVAL_MS);
|
|
1741
|
+
}, [refresh]);
|
|
1742
|
+
(0, react.useEffect)(() => {
|
|
1743
|
+
mounted.current = true;
|
|
1744
|
+
refresh();
|
|
1745
|
+
return () => {
|
|
1746
|
+
mounted.current = false;
|
|
1747
|
+
stopPolling();
|
|
1748
|
+
};
|
|
1749
|
+
}, [refresh, stopPolling]);
|
|
1750
|
+
/** Every route worth reading, as of the latest render, for the usage poll. */
|
|
1751
|
+
const signedInRef = (0, react.useRef)([]);
|
|
1752
|
+
(0, react.useEffect)(() => {
|
|
1753
|
+
signedInRef.current = usageTargets(status?.providers ?? []);
|
|
1754
|
+
});
|
|
1755
|
+
/**
|
|
1756
|
+
* Read each named route's plan and allowance, applying every answer.
|
|
1757
|
+
*
|
|
1758
|
+
* A read is not forced: the node half reuses a figure only for a few seconds and
|
|
1759
|
+
* never reuses a substitute, so a steady interval refreshes the figures while
|
|
1760
|
+
* collapsing bursts into one request per route.
|
|
1761
|
+
*/
|
|
1762
|
+
const readUsage = (0, react.useCallback)(async (providers) => {
|
|
1763
|
+
if (call === void 0) return;
|
|
1764
|
+
for (const provider of providers) try {
|
|
1765
|
+
const result = await call("usage", { provider });
|
|
1766
|
+
if (!mounted.current) return;
|
|
1767
|
+
setStatus(result.status);
|
|
1768
|
+
} catch {}
|
|
1769
|
+
}, [call]);
|
|
1770
|
+
const usageRequested = (0, react.useRef)(false);
|
|
1771
|
+
(0, react.useEffect)(() => {
|
|
1772
|
+
if (call === void 0 || status === void 0 || usageRequested.current) return;
|
|
1773
|
+
const signedIn = usageTargets(status.providers);
|
|
1774
|
+
if (signedIn.length === 0) return;
|
|
1775
|
+
usageRequested.current = true;
|
|
1776
|
+
readUsage(signedIn);
|
|
1777
|
+
}, [
|
|
1778
|
+
call,
|
|
1779
|
+
status,
|
|
1780
|
+
readUsage
|
|
1781
|
+
]);
|
|
1782
|
+
(0, react.useEffect)(() => {
|
|
1783
|
+
if (call === void 0) return;
|
|
1784
|
+
const tick = () => {
|
|
1785
|
+
if (globalThis.document?.visibilityState === "hidden") return;
|
|
1786
|
+
readUsage(signedInRef.current);
|
|
1787
|
+
};
|
|
1788
|
+
const interval = setInterval(tick, USAGE_POLL_INTERVAL_MS);
|
|
1789
|
+
return () => {
|
|
1790
|
+
clearInterval(interval);
|
|
1791
|
+
};
|
|
1792
|
+
}, [call, readUsage]);
|
|
1793
|
+
const anyBusy = status?.providers.some((provider) => provider.busy) === true;
|
|
1794
|
+
(0, react.useEffect)(() => {
|
|
1795
|
+
if (anyBusy) startPolling();
|
|
1796
|
+
else stopPolling();
|
|
1797
|
+
}, [
|
|
1798
|
+
anyBusy,
|
|
1799
|
+
startPolling,
|
|
1800
|
+
stopPolling
|
|
1801
|
+
]);
|
|
1802
|
+
/** Run one action for one provider, reporting its own failure. */
|
|
1803
|
+
const act = (0, react.useCallback)(async (provider, run) => {
|
|
1804
|
+
setBusyAction(true);
|
|
1805
|
+
setMessages((current) => {
|
|
1806
|
+
const next = { ...current };
|
|
1807
|
+
delete next[provider];
|
|
1808
|
+
return next;
|
|
1809
|
+
});
|
|
1810
|
+
try {
|
|
1811
|
+
await run();
|
|
1812
|
+
} catch (caught) {
|
|
1813
|
+
if (mounted.current) setMessages((current) => ({
|
|
1814
|
+
...current,
|
|
1815
|
+
[provider]: {
|
|
1816
|
+
tone: "error",
|
|
1817
|
+
text: t("actionFailed", { message: messageOf(caught) })
|
|
1818
|
+
}
|
|
1819
|
+
}));
|
|
1820
|
+
} finally {
|
|
1821
|
+
if (mounted.current) setBusyAction(false);
|
|
1822
|
+
}
|
|
1823
|
+
}, [t]);
|
|
1824
|
+
const login = (0, react.useCallback)((provider) => {
|
|
1825
|
+
if (call === void 0) return;
|
|
1826
|
+
act(provider, async () => {
|
|
1827
|
+
const started = await call("login", { provider });
|
|
1828
|
+
if (!mounted.current) return;
|
|
1829
|
+
setStatus(started.status);
|
|
1830
|
+
if (started.prompt.mode === "open-url") window.open(started.prompt.url, "_blank", "noopener");
|
|
1831
|
+
});
|
|
1832
|
+
}, [act, call]);
|
|
1833
|
+
const cancel = (0, react.useCallback)((provider) => {
|
|
1834
|
+
if (call === void 0) return;
|
|
1835
|
+
act(provider, async () => {
|
|
1836
|
+
const next = await call("cancel", { provider });
|
|
1837
|
+
if (mounted.current) setStatus(next);
|
|
1838
|
+
});
|
|
1839
|
+
}, [act, call]);
|
|
1840
|
+
const logout = (0, react.useCallback)((provider, account) => {
|
|
1841
|
+
if (call === void 0) return;
|
|
1842
|
+
act(provider, async () => {
|
|
1843
|
+
const next = await call("logout", {
|
|
1844
|
+
provider,
|
|
1845
|
+
account
|
|
1846
|
+
});
|
|
1847
|
+
if (mounted.current) setStatus(next);
|
|
1848
|
+
});
|
|
1849
|
+
}, [act, call]);
|
|
1850
|
+
const setDefault = (0, react.useCallback)((provider, account) => {
|
|
1851
|
+
if (call === void 0) return;
|
|
1852
|
+
act(provider, async () => {
|
|
1853
|
+
const next = await call("setDefault", {
|
|
1854
|
+
provider,
|
|
1855
|
+
account
|
|
1856
|
+
});
|
|
1857
|
+
if (mounted.current) setStatus(next);
|
|
1858
|
+
});
|
|
1859
|
+
}, [act, call]);
|
|
1860
|
+
const refreshToken = (0, react.useCallback)((provider, account) => {
|
|
1861
|
+
if (call === void 0) return;
|
|
1862
|
+
act(provider, async () => {
|
|
1863
|
+
const result = await call("refresh", {
|
|
1864
|
+
provider,
|
|
1865
|
+
account
|
|
1866
|
+
});
|
|
1867
|
+
if (!mounted.current) return;
|
|
1868
|
+
setStatus(result.status);
|
|
1869
|
+
setMessages((current) => ({
|
|
1870
|
+
...current,
|
|
1871
|
+
[provider]: {
|
|
1872
|
+
tone: "success",
|
|
1873
|
+
text: t("tokenRefreshed", { time: formatTime(result.expiresAt) })
|
|
1874
|
+
}
|
|
1875
|
+
}));
|
|
1876
|
+
});
|
|
1877
|
+
}, [
|
|
1878
|
+
act,
|
|
1879
|
+
call,
|
|
1880
|
+
t
|
|
1881
|
+
]);
|
|
1882
|
+
const refreshModels = (0, react.useCallback)((provider) => {
|
|
1883
|
+
if (call === void 0) return;
|
|
1884
|
+
act(provider, async () => {
|
|
1885
|
+
const next = await call("discover", {
|
|
1886
|
+
provider,
|
|
1887
|
+
force: true
|
|
1888
|
+
});
|
|
1889
|
+
if (!mounted.current) return;
|
|
1890
|
+
setStatus(next);
|
|
1891
|
+
setMessages((current) => ({
|
|
1892
|
+
...current,
|
|
1893
|
+
[provider]: {
|
|
1894
|
+
tone: "success",
|
|
1895
|
+
text: t("modelsRefreshed")
|
|
1896
|
+
}
|
|
1897
|
+
}));
|
|
1898
|
+
});
|
|
1899
|
+
}, [
|
|
1900
|
+
act,
|
|
1901
|
+
call,
|
|
1902
|
+
t
|
|
1903
|
+
]);
|
|
1904
|
+
const setModelVisible = (0, react.useCallback)((provider, id, visible) => {
|
|
1905
|
+
if (call === void 0) return;
|
|
1906
|
+
act(provider, async () => {
|
|
1907
|
+
const next = await call("setModelVisible", {
|
|
1908
|
+
provider,
|
|
1909
|
+
id,
|
|
1910
|
+
visible
|
|
1911
|
+
});
|
|
1912
|
+
if (mounted.current) setStatus(next);
|
|
1913
|
+
});
|
|
1914
|
+
}, [act, call]);
|
|
1915
|
+
const refreshUsage = (0, react.useCallback)((provider) => {
|
|
1916
|
+
if (call === void 0) return;
|
|
1917
|
+
act(provider, async () => {
|
|
1918
|
+
const result = await call("usage", {
|
|
1919
|
+
provider,
|
|
1920
|
+
force: true
|
|
1921
|
+
});
|
|
1922
|
+
if (mounted.current) setStatus(result.status);
|
|
1923
|
+
});
|
|
1924
|
+
}, [act, call]);
|
|
1925
|
+
const setModelOrder = (0, react.useCallback)((provider, ids) => {
|
|
1926
|
+
if (call === void 0) return;
|
|
1927
|
+
act(provider, async () => {
|
|
1928
|
+
const next = await call("setModelOrder", {
|
|
1929
|
+
provider,
|
|
1930
|
+
ids: [...ids]
|
|
1931
|
+
});
|
|
1932
|
+
if (mounted.current) setStatus(next);
|
|
1933
|
+
});
|
|
1934
|
+
}, [act, call]);
|
|
1935
|
+
const addModel = (0, react.useCallback)((provider, id) => {
|
|
1936
|
+
if (call === void 0) return;
|
|
1937
|
+
act(provider, async () => {
|
|
1938
|
+
const next = await call("addModel", {
|
|
1939
|
+
provider,
|
|
1940
|
+
id
|
|
1941
|
+
});
|
|
1942
|
+
if (mounted.current) setStatus(next);
|
|
1943
|
+
});
|
|
1944
|
+
}, [act, call]);
|
|
1945
|
+
const removeModel = (0, react.useCallback)((provider, id) => {
|
|
1946
|
+
if (call === void 0) return;
|
|
1947
|
+
act(provider, async () => {
|
|
1948
|
+
const next = await call("removeModel", {
|
|
1949
|
+
provider,
|
|
1950
|
+
id
|
|
1951
|
+
});
|
|
1952
|
+
if (mounted.current) setStatus(next);
|
|
1953
|
+
});
|
|
1954
|
+
}, [act, call]);
|
|
1955
|
+
const reload = (0, react.useCallback)((provider) => {
|
|
1956
|
+
if (call === void 0) return;
|
|
1957
|
+
act(provider, async () => {
|
|
1958
|
+
const next = await call("reload", void 0);
|
|
1959
|
+
if (mounted.current) setStatus(next);
|
|
1960
|
+
});
|
|
1961
|
+
}, [act, call]);
|
|
1962
|
+
if (call === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1963
|
+
style: styles.intro,
|
|
1964
|
+
children: t("unavailable")
|
|
1965
|
+
});
|
|
1966
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1967
|
+
className: "dsh-subscriptions-settings",
|
|
1968
|
+
style: styles.section,
|
|
1969
|
+
children: [
|
|
1970
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: subscriptionsCss }),
|
|
1971
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1972
|
+
style: styles.intro,
|
|
1973
|
+
children: t("intro")
|
|
1974
|
+
}),
|
|
1975
|
+
error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1976
|
+
className: "dsh-subscription-error",
|
|
1977
|
+
children: error
|
|
1978
|
+
}),
|
|
1979
|
+
status === void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1980
|
+
style: styles.statusLine,
|
|
1981
|
+
children: t("loading")
|
|
1982
|
+
}),
|
|
1983
|
+
status?.providers.map((provider) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProviderCard, {
|
|
1984
|
+
status: provider,
|
|
1985
|
+
t,
|
|
1986
|
+
busyAction,
|
|
1987
|
+
...messages[provider.provider] === void 0 ? {} : { message: messages[provider.provider] },
|
|
1988
|
+
onLogin: () => {
|
|
1989
|
+
login(provider.provider);
|
|
1990
|
+
},
|
|
1991
|
+
onCancel: () => {
|
|
1992
|
+
cancel(provider.provider);
|
|
1993
|
+
},
|
|
1994
|
+
onLogout: (account) => {
|
|
1995
|
+
logout(provider.provider, account);
|
|
1996
|
+
},
|
|
1997
|
+
onSetDefault: (account) => {
|
|
1998
|
+
setDefault(provider.provider, account);
|
|
1999
|
+
},
|
|
2000
|
+
onRefreshToken: (account) => {
|
|
2001
|
+
refreshToken(provider.provider, account);
|
|
2002
|
+
},
|
|
2003
|
+
onRefreshModels: () => {
|
|
2004
|
+
refreshModels(provider.provider);
|
|
2005
|
+
},
|
|
2006
|
+
onSetModelVisible: (id, visible) => {
|
|
2007
|
+
setModelVisible(provider.provider, id, visible);
|
|
2008
|
+
},
|
|
2009
|
+
onSetModelOrder: (ids) => {
|
|
2010
|
+
setModelOrder(provider.provider, ids);
|
|
2011
|
+
},
|
|
2012
|
+
onAddModel: (id) => {
|
|
2013
|
+
addModel(provider.provider, id);
|
|
2014
|
+
},
|
|
2015
|
+
onRemoveModel: (id) => {
|
|
2016
|
+
removeModel(provider.provider, id);
|
|
2017
|
+
},
|
|
2018
|
+
onRefreshUsage: () => {
|
|
2019
|
+
refreshUsage(provider.provider);
|
|
2020
|
+
},
|
|
2021
|
+
onReload: () => {
|
|
2022
|
+
reload(provider.provider);
|
|
2023
|
+
}
|
|
2024
|
+
}, provider.provider))
|
|
2025
|
+
]
|
|
2026
|
+
});
|
|
2027
|
+
}
|
|
2028
|
+
|
|
2029
|
+
//#endregion
|
|
2030
|
+
//#region src/client/index.ts
|
|
2031
|
+
/** Dictionary namespace owned by this plugin. */
|
|
2032
|
+
const NS = "settings.subscriptions";
|
|
2033
|
+
/** Settings nav position: after the shipped sections, before plugin tooling. */
|
|
2034
|
+
const SECTION_ORDER = 92;
|
|
2035
|
+
/**
|
|
2036
|
+
* Required services: `slots` carries the registration seat, `connection` the
|
|
2037
|
+
* RPC caller, and `locale` the copy dictionaries.
|
|
2038
|
+
*/
|
|
2039
|
+
const inject = [
|
|
2040
|
+
"slots",
|
|
2041
|
+
"connection",
|
|
2042
|
+
"locale"
|
|
2043
|
+
];
|
|
2044
|
+
/**
|
|
2045
|
+
* Register the Subscriptions settings section.
|
|
2046
|
+
*
|
|
2047
|
+
* Registration goes through `slots.inject`, so the shell's own apply order
|
|
2048
|
+
* relative to this one is unconstrained.
|
|
2049
|
+
*
|
|
2050
|
+
* @param ctx - client root context.
|
|
2051
|
+
*/
|
|
2052
|
+
function apply(ctx) {
|
|
2053
|
+
ctx.effect(() => ctx.locale.register(NS, {
|
|
2054
|
+
zh,
|
|
2055
|
+
en
|
|
2056
|
+
}), "dsh-plugin-subscription-oauth: copy dictionaries");
|
|
2057
|
+
const connection = ctx.get("connection");
|
|
2058
|
+
const t = ctx.locale.bind(NS);
|
|
2059
|
+
const call = createSubscriptionsCall(connection.rpc);
|
|
2060
|
+
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
2061
|
+
name: "settings.section",
|
|
2062
|
+
id: "subscriptions",
|
|
2063
|
+
order: SECTION_ORDER,
|
|
2064
|
+
label: () => t("nav"),
|
|
2065
|
+
inject: () => ({
|
|
2066
|
+
call,
|
|
2067
|
+
t
|
|
2068
|
+
})
|
|
2069
|
+
}, SubscriptionsSection));
|
|
2070
|
+
}
|
|
2071
|
+
|
|
2072
|
+
//#endregion
|
|
2073
|
+
exports.SubscriptionsRpcError = SubscriptionsRpcError;
|
|
2074
|
+
exports.apply = apply;
|
|
2075
|
+
exports.createSubscriptionsCall = createSubscriptionsCall;
|
|
2076
|
+
exports.inject = inject;
|
|
2077
|
+
return module.exports; } });
|
|
2078
|
+
//# sourceMappingURL=client.js.map
|