dsh-packycode-api 1.2.6 → 1.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/README.en.md +24 -12
- package/README.md +24 -12
- package/lib/client.js +451 -130
- package/lib/index.js +348 -26
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -44,7 +44,6 @@ window.__ModuleLoader__.load({
|
|
|
44
44
|
imagesBalanceFailed: "图片余额读取失败",
|
|
45
45
|
imagesComposerLabel: "图片剩余额度",
|
|
46
46
|
imagesBalanceKey: "当前图片 Key",
|
|
47
|
-
imagesBalanceReadAt: "更新于 {time}",
|
|
48
47
|
imagesBalanceHint: "仅汇总图片 Key 的额度,每分钟自动更新;也可手动刷新。与对话 Key 的余额分开统计。",
|
|
49
48
|
imagesIntro: "使用独立图片 Key 调用 PackyCode 图片接口。生成新图或上传图片后描述修改要求;图片模型独立于对话模型。原始 PNG 保存在本机,聊天中显示图片预览。",
|
|
50
49
|
imagesEnabled: "图片生成与编辑",
|
|
@@ -88,6 +87,8 @@ window.__ModuleLoader__.load({
|
|
|
88
87
|
totalStale: "其中 {count} 个 Key 本次读取失败,显示的是上次读到的余额。",
|
|
89
88
|
staleNote: "上次读取于 {time},本次读取失败:{error}",
|
|
90
89
|
empty: "还没有 Key。添加一个即可在模型选择器里看到 PackyCode 的模型。",
|
|
90
|
+
inviteLead: "还没有 PackyCode 的 API Key?可以通过邀请链接注册:",
|
|
91
|
+
inviteLink: "www.packyapi.ai/register ↗",
|
|
91
92
|
keysHint: "按顺序使用:当前 Key 不可用时自动切到下一个;「切到此 Key」指定优先使用的 Key。",
|
|
92
93
|
keyActive: "使用中",
|
|
93
94
|
keyDisabled: "已停用",
|
|
@@ -137,9 +138,11 @@ window.__ModuleLoader__.load({
|
|
|
137
138
|
apiHost: "模型接口地址",
|
|
138
139
|
statusHost: "余额接口地址",
|
|
139
140
|
composerLabel: "PackyCode 剩余额度",
|
|
141
|
+
composerKey: "当前 Key",
|
|
140
142
|
composerNoKey: "无可用 Key",
|
|
141
143
|
composerUnread: "余额读取失败",
|
|
142
144
|
composerStale: "上次读取于 {time},本次读取失败",
|
|
145
|
+
quotaReadAt: "更新于 {time}",
|
|
143
146
|
cliTitle: "Claude Code CLI",
|
|
144
147
|
cliChecking: "正在检测本机的 Claude Code CLI…",
|
|
145
148
|
cliInstalled: "已安装",
|
|
@@ -182,6 +185,20 @@ window.__ModuleLoader__.load({
|
|
|
182
185
|
versionRestartHint: "插件无法安全地重启桌面应用。请完全退出 DSH,再重新打开。",
|
|
183
186
|
versionRefresh: "刷新界面",
|
|
184
187
|
versionLater: "稍后",
|
|
188
|
+
diagTitle: "诊断信息",
|
|
189
|
+
diagIntro: "遇到回答被截断(已达到输出 token 上限)、请求反复失败等问题时,选择出问题的会话,生成诊断信息并复制给插件作者。",
|
|
190
|
+
diagSession: "会话",
|
|
191
|
+
diagGenerate: "生成诊断信息",
|
|
192
|
+
diagGenerating: "正在生成…",
|
|
193
|
+
diagReport: "诊断信息内容",
|
|
194
|
+
diagCopy: "复制",
|
|
195
|
+
diagCopied: "已复制",
|
|
196
|
+
diagSelected: "已选中全部内容,请按 ⌘C 或 Ctrl+C 复制。",
|
|
197
|
+
diagPrivacy: "只包含模型、推理档位、输出上限、每一步的 token 用量和错误码,不含对话内容、文件路径和 Key。",
|
|
198
|
+
diagNoSessions: "还没有会话。",
|
|
199
|
+
diagLive: "进行中",
|
|
200
|
+
diagNoFolder: "无目录",
|
|
201
|
+
diagError: "生成诊断信息失败",
|
|
185
202
|
searchTitle: "网页搜索",
|
|
186
203
|
searchScope: "DSH 全局设置",
|
|
187
204
|
searchIntro: "DSH 的 web_search 和 Claude CLI 的 MCP 搜索可使用这里选择的 DeepSeek/GPT 后端。每次搜索是独立的模型调用,用插件保存的 Key 计费。",
|
|
@@ -226,7 +243,6 @@ window.__ModuleLoader__.load({
|
|
|
226
243
|
imagesBalanceFailed: "Could not read image balances",
|
|
227
244
|
imagesComposerLabel: "Image balance",
|
|
228
245
|
imagesBalanceKey: "Current image key",
|
|
229
|
-
imagesBalanceReadAt: "Updated at {time}",
|
|
230
246
|
imagesBalanceHint: "Only image key quotas are counted here. Updates every minute, or use Refresh. Conversation key balances are separate.",
|
|
231
247
|
imagesIntro: "Use a dedicated image key with PackyCode’s Images API. Describe a new image or attach images with editing instructions. The image engine is independent of the conversation model. Original PNGs are saved locally and previews appear in chat.",
|
|
232
248
|
imagesEnabled: "Generate and edit images",
|
|
@@ -270,6 +286,8 @@ window.__ModuleLoader__.load({
|
|
|
270
286
|
totalStale: "{count} of these keys failed this read and show their last read balance.",
|
|
271
287
|
staleNote: "Last read at {time}; this read failed: {error}",
|
|
272
288
|
empty: "No keys yet. Add one and the PackyCode models appear in the model picker.",
|
|
289
|
+
inviteLead: "No PackyCode API key yet? You can register through this invite link:",
|
|
290
|
+
inviteLink: "www.packyapi.ai/register ↗",
|
|
273
291
|
keysHint: "Used in order: when the active key cannot serve a request, the next one takes over. “Use this key” picks the preferred key.",
|
|
274
292
|
keyActive: "Active",
|
|
275
293
|
keyDisabled: "Disabled",
|
|
@@ -311,9 +329,11 @@ window.__ModuleLoader__.load({
|
|
|
311
329
|
apiHost: "Model endpoint",
|
|
312
330
|
statusHost: "Balance endpoint",
|
|
313
331
|
composerLabel: "PackyCode remaining balance",
|
|
332
|
+
composerKey: "Current key",
|
|
314
333
|
composerNoKey: "No usable key",
|
|
315
334
|
composerUnread: "Balance unavailable",
|
|
316
|
-
composerStale: "
|
|
335
|
+
composerStale: "Last read at {time}; this read failed",
|
|
336
|
+
quotaReadAt: "Updated at {time}",
|
|
317
337
|
cliTitle: "Claude Code CLI",
|
|
318
338
|
cliChecking: "Checking this device for the Claude Code CLI…",
|
|
319
339
|
cliInstalled: "Installed",
|
|
@@ -356,6 +376,20 @@ window.__ModuleLoader__.load({
|
|
|
356
376
|
versionRestartHint: "The plugin cannot safely restart the desktop application. Quit DSH completely, then open it again.",
|
|
357
377
|
versionRefresh: "Refresh page",
|
|
358
378
|
versionLater: "Later",
|
|
379
|
+
diagTitle: "Diagnostics",
|
|
380
|
+
diagIntro: "If replies are cut off (\"output token limit reached\") or requests keep failing, pick the affected session, generate its diagnostics and send them to the plugin author.",
|
|
381
|
+
diagSession: "Session",
|
|
382
|
+
diagGenerate: "Generate diagnostics",
|
|
383
|
+
diagGenerating: "Generating…",
|
|
384
|
+
diagReport: "Diagnostics text",
|
|
385
|
+
diagCopy: "Copy",
|
|
386
|
+
diagCopied: "Copied",
|
|
387
|
+
diagSelected: "All text is selected; press ⌘C or Ctrl+C to copy.",
|
|
388
|
+
diagPrivacy: "Contains only models, reasoning effort, output limits, per-step token usage and error codes — no conversation text, file paths or keys.",
|
|
389
|
+
diagNoSessions: "No sessions yet.",
|
|
390
|
+
diagLive: "open",
|
|
391
|
+
diagNoFolder: "no folder",
|
|
392
|
+
diagError: "Could not generate diagnostics",
|
|
359
393
|
searchTitle: "Web search",
|
|
360
394
|
searchScope: "DSH-wide setting",
|
|
361
395
|
searchIntro: "DSH’s web_search and Claude CLI’s MCP search can use the DeepSeek/GPT backend selected here. Each search is a separate model call billed to the plugin-stored keys.",
|
|
@@ -400,6 +434,8 @@ window.__ModuleLoader__.load({
|
|
|
400
434
|
.pkc-head{display:flex;align-items:center;justify-content:space-between;gap:12px;min-height:28px}
|
|
401
435
|
.pkc-title{display:flex;align-items:center;gap:8px;margin:0;font-size:15px;font-weight:600;line-height:22px}
|
|
402
436
|
.pkc-note{margin:0;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}
|
|
437
|
+
.pkc-link{color:var(--dsw-alias-link,#3964fe);font-weight:500;text-decoration:none;word-break:break-all}
|
|
438
|
+
.pkc-link:hover,.pkc-link:focus{outline:none;text-decoration:underline dotted var(--dsw-alias-link,#3964fe);text-underline-offset:3px}
|
|
403
439
|
.pkc-warn{margin:0;color:var(--dsw-alias-state-warn-label,#dd8629);font-size:12px;line-height:18px}
|
|
404
440
|
.pkc-error{margin:0;color:var(--dsw-alias-state-error-primary,#ec1313);font-size:12px;line-height:18px}
|
|
405
441
|
.pkc-label{color:var(--dsw-alias-label-secondary);font-size:12px;font-weight:500;line-height:18px}
|
|
@@ -421,13 +457,16 @@ window.__ModuleLoader__.load({
|
|
|
421
457
|
.pkc-input:focus{border-color:var(--dsw-alias-state-business-primary)}
|
|
422
458
|
.pkc-input::placeholder{color:var(--dsw-alias-label-caption)}
|
|
423
459
|
.pkc-input:disabled{opacity:.5}
|
|
460
|
+
.pkc-diag-session{flex:1 1 260px;max-width:480px;cursor:pointer}
|
|
461
|
+
.pkc-diag-text{width:100%;height:auto;min-height:220px;padding:8px 10px;line-height:18px;resize:vertical;white-space:pre;overflow:auto}
|
|
424
462
|
.pkc-image-choice{flex:none;width:100%;max-width:300px;cursor:pointer}
|
|
425
463
|
.pkc-image-shortcut{display:inline-flex;align-items:center;gap:8px}
|
|
426
|
-
.pkc-
|
|
427
|
-
.pkc-
|
|
428
|
-
.pkc-
|
|
429
|
-
.pkc-
|
|
430
|
-
.pkc-
|
|
464
|
+
.pkc-quota{position:relative;display:inline-flex;align-items:center}
|
|
465
|
+
.pkc-quota-value{cursor:help;border-radius:var(--dsw-radius-xs,4px)}
|
|
466
|
+
.pkc-quota-value:focus-visible{outline:1px solid var(--dsw-alias-state-business-primary);outline-offset:2px}
|
|
467
|
+
.pkc-quota-tooltip{position:absolute;bottom:calc(100% + 8px);left:0;z-index:100;box-sizing:border-box;width:max-content;max-width:min(320px,calc(100vw - 32px));padding:10px 12px;border:.5px solid var(--dsw-alias-border-l3);border-radius:var(--dsw-radius-md,12px);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);box-shadow:var(--dsw-shadow-lv1);font-size:12px;line-height:20px;white-space:pre-line;overflow-wrap:anywhere;visibility:hidden;opacity:0;pointer-events:none}
|
|
468
|
+
.pkc-quota[data-align=end]>.pkc-quota-tooltip{left:auto;right:0}
|
|
469
|
+
.pkc-quota:hover>.pkc-quota-tooltip,.pkc-quota:focus-within>.pkc-quota-tooltip{visibility:visible;opacity:1}
|
|
431
470
|
.pkc-image-result,.pkc-image-chat{display:flex;flex-direction:column;gap:10px;padding:8px 0}
|
|
432
471
|
.pkc-generated-image{display:grid;place-items:center;align-self:flex-start;max-width:100%;width:320px;min-height:80px;padding:0;overflow:hidden;border:.5px solid var(--dsw-alias-border-l3);border-radius:var(--dsw-radius-md,12px);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-secondary);cursor:zoom-in}
|
|
433
472
|
.pkc-generated-image img{display:block;max-width:100%;max-height:480px;object-fit:contain}
|
|
@@ -720,6 +759,54 @@ window.__ModuleLoader__.load({
|
|
|
720
759
|
})
|
|
721
760
|
] });
|
|
722
761
|
}
|
|
762
|
+
/**
|
|
763
|
+
* A composer quota figure with the same details for either key ring. The help
|
|
764
|
+
* cursor marks it; hovering or focusing it shows the key, its account, the
|
|
765
|
+
* remaining, used and total quota and when it was read, and asks the caller
|
|
766
|
+
* for an unforced read.
|
|
767
|
+
* @param props.copy - the ring's own lines: `title`, `key`, `failed`, `loading`.
|
|
768
|
+
* @param props.empty - what the figure shows while the remaining quota is unknown.
|
|
769
|
+
* @param props.align - `end` opens the details leftward, for the composer's right side.
|
|
770
|
+
*/
|
|
771
|
+
function QuotaHover({ row, fetchedAt, loading = false, failed = false, empty, copy, align, onRefresh, t }) {
|
|
772
|
+
const tooltipId = react.useId();
|
|
773
|
+
const usage = row?.usage;
|
|
774
|
+
const unlimited = usage?.unlimitedQuota === true;
|
|
775
|
+
const remaining = unlimited ? t("keyUnlimited") : formatUsd(usage?.availableUsd);
|
|
776
|
+
const stale = usage !== void 0 && (failed || row.stale !== void 0);
|
|
777
|
+
const display = remaining ?? empty;
|
|
778
|
+
const details = [
|
|
779
|
+
copy.title,
|
|
780
|
+
row?.label ? `${copy.key}: ${row.label}` : void 0,
|
|
781
|
+
usage?.name ? `${t("keyName")}: ${usage.name}` : void 0,
|
|
782
|
+
`${t("keyRemaining")}: ${remaining ?? "—"}`,
|
|
783
|
+
`${t("keyUsed")}: ${formatUsd(usage?.usedUsd) ?? "—"}`,
|
|
784
|
+
`${t("accountLimit")}: ${unlimited ? t("keyUnlimited") : formatUsd(quotaTotalUsd(usage)) ?? "—"}`,
|
|
785
|
+
stale ? fill(t("composerStale"), { time: readTime(row.stale?.fetchedAt ?? fetchedAt) }) : failed || row?.error ? copy.failed : loading ? copy.loading : fetchedAt === void 0 ? void 0 : fill(t("quotaReadAt"), { time: readTime(fetchedAt) })
|
|
786
|
+
].filter(Boolean).join("\n");
|
|
787
|
+
const refresh = () => {
|
|
788
|
+
onRefresh?.();
|
|
789
|
+
};
|
|
790
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
791
|
+
className: "pkc-quota",
|
|
792
|
+
"data-align": align,
|
|
793
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
794
|
+
className: "pkc-composer pkc-quota-value",
|
|
795
|
+
role: "status",
|
|
796
|
+
tabIndex: 0,
|
|
797
|
+
"aria-label": `${copy.title}: ${stale ? "≈" : ""}${display}`,
|
|
798
|
+
"aria-describedby": tooltipId,
|
|
799
|
+
onMouseEnter: refresh,
|
|
800
|
+
onFocus: refresh,
|
|
801
|
+
children: stale ? `≈${display}` : display
|
|
802
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
803
|
+
className: "pkc-quota-tooltip",
|
|
804
|
+
id: tooltipId,
|
|
805
|
+
role: "tooltip",
|
|
806
|
+
children: details
|
|
807
|
+
})]
|
|
808
|
+
});
|
|
809
|
+
}
|
|
723
810
|
/** Only this card's keys contribute; account-wide billing is never added. */
|
|
724
811
|
function BalanceTotals({ rows, t }) {
|
|
725
812
|
const totals = sumBalances(rows);
|
|
@@ -779,7 +866,6 @@ window.__ModuleLoader__.load({
|
|
|
779
866
|
function ImageShortcutBalance({ call, t }) {
|
|
780
867
|
const [view, setView] = react.useState({ loading: true });
|
|
781
868
|
const refresh = react.useRef(() => {});
|
|
782
|
-
const tooltipId = react.useId();
|
|
783
869
|
react.useEffect(() => {
|
|
784
870
|
let live = true, generation = 0, pending = false;
|
|
785
871
|
const read = async (force = false, reset = false) => {
|
|
@@ -823,42 +909,22 @@ window.__ModuleLoader__.load({
|
|
|
823
909
|
window.removeEventListener("packycode-images-updated", changed);
|
|
824
910
|
};
|
|
825
911
|
}, [call]);
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
className: "pkc-image-balance",
|
|
843
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
844
|
-
className: "pkc-composer pkc-image-balance-value",
|
|
845
|
-
role: "status",
|
|
846
|
-
tabIndex: 0,
|
|
847
|
-
"aria-label": `${t("imagesComposerLabel")}: ${stale ? "≈" : ""}${display}`,
|
|
848
|
-
"aria-describedby": tooltipId,
|
|
849
|
-
onMouseEnter: () => {
|
|
850
|
-
refresh.current();
|
|
851
|
-
},
|
|
852
|
-
onFocus: () => {
|
|
853
|
-
refresh.current();
|
|
854
|
-
},
|
|
855
|
-
children: stale ? `≈${display}` : display
|
|
856
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
857
|
-
className: "pkc-image-balance-tooltip",
|
|
858
|
-
id: tooltipId,
|
|
859
|
-
role: "tooltip",
|
|
860
|
-
children: details
|
|
861
|
-
})]
|
|
912
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QuotaHover, {
|
|
913
|
+
row: currentImageKey(view.balance),
|
|
914
|
+
fetchedAt: view.balance?.fetchedAt,
|
|
915
|
+
loading: view.loading,
|
|
916
|
+
failed: view.failed === true,
|
|
917
|
+
empty: view.loading ? "…" : "—",
|
|
918
|
+
copy: {
|
|
919
|
+
title: t("imagesComposerLabel"),
|
|
920
|
+
key: t("imagesBalanceKey"),
|
|
921
|
+
failed: t("imagesBalanceFailed"),
|
|
922
|
+
loading: t("imagesBalanceLoading")
|
|
923
|
+
},
|
|
924
|
+
onRefresh: () => {
|
|
925
|
+
refresh.current();
|
|
926
|
+
},
|
|
927
|
+
t
|
|
862
928
|
});
|
|
863
929
|
}
|
|
864
930
|
//#endregion
|
|
@@ -1116,6 +1182,79 @@ window.__ModuleLoader__.load({
|
|
|
1116
1182
|
}, image.revision);
|
|
1117
1183
|
}
|
|
1118
1184
|
//#endregion
|
|
1185
|
+
//#region src/client-key-rename.jsx
|
|
1186
|
+
/**
|
|
1187
|
+
* Renaming a stored key, the same way in the conversation and image key
|
|
1188
|
+
* cards: the name becomes a text field; Enter or Save stores it, Escape or
|
|
1189
|
+
* Cancel keeps the old one. Only the key's id and the new label reach the host.
|
|
1190
|
+
*
|
|
1191
|
+
* @module dsh-packycode-api/client-key-rename
|
|
1192
|
+
*/
|
|
1193
|
+
/**
|
|
1194
|
+
* The rename state a key card keeps.
|
|
1195
|
+
* @param save - `({ id, label }) => Promise`; the field closes once it settles.
|
|
1196
|
+
* @returns `{ renaming, start, change, cancel, submit }`.
|
|
1197
|
+
*/
|
|
1198
|
+
function useKeyRename(save) {
|
|
1199
|
+
const [renaming, setRenaming] = react.useState();
|
|
1200
|
+
return {
|
|
1201
|
+
renaming,
|
|
1202
|
+
start: (entry) => setRenaming({
|
|
1203
|
+
id: entry.id,
|
|
1204
|
+
label: entry.label
|
|
1205
|
+
}),
|
|
1206
|
+
change: (label) => setRenaming((current) => ({
|
|
1207
|
+
...current,
|
|
1208
|
+
label
|
|
1209
|
+
})),
|
|
1210
|
+
cancel: () => setRenaming(void 0),
|
|
1211
|
+
submit: () => {
|
|
1212
|
+
if (renaming === void 0 || renaming.label.trim() === "") return;
|
|
1213
|
+
save({
|
|
1214
|
+
id: renaming.id,
|
|
1215
|
+
label: renaming.label
|
|
1216
|
+
}).then(() => setRenaming(void 0));
|
|
1217
|
+
}
|
|
1218
|
+
};
|
|
1219
|
+
}
|
|
1220
|
+
/** The field and buttons that stand in for a key's name while it is renamed. */
|
|
1221
|
+
function KeyRenameForm({ rename, busy, t }) {
|
|
1222
|
+
const label = rename.renaming.label;
|
|
1223
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
|
|
1224
|
+
className: "pkc-form",
|
|
1225
|
+
onSubmit: (event) => {
|
|
1226
|
+
event.preventDefault();
|
|
1227
|
+
rename.submit();
|
|
1228
|
+
},
|
|
1229
|
+
children: [
|
|
1230
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1231
|
+
className: "pkc-input",
|
|
1232
|
+
type: "text",
|
|
1233
|
+
maxLength: 60,
|
|
1234
|
+
autoFocus: true,
|
|
1235
|
+
placeholder: t("keyRenamePlaceholder"),
|
|
1236
|
+
value: label,
|
|
1237
|
+
onChange: (event) => rename.change(event.target.value),
|
|
1238
|
+
onKeyDown: (event) => event.key === "Escape" && rename.cancel()
|
|
1239
|
+
}),
|
|
1240
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1241
|
+
type: "submit",
|
|
1242
|
+
className: "pkc-btn",
|
|
1243
|
+
"data-variant": "primary",
|
|
1244
|
+
disabled: busy || label.trim() === "",
|
|
1245
|
+
children: t("keyRenameSave")
|
|
1246
|
+
}),
|
|
1247
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1248
|
+
type: "button",
|
|
1249
|
+
className: "pkc-btn",
|
|
1250
|
+
"data-variant": "ghost",
|
|
1251
|
+
onClick: rename.cancel,
|
|
1252
|
+
children: t("keyRenameCancel")
|
|
1253
|
+
})
|
|
1254
|
+
]
|
|
1255
|
+
});
|
|
1256
|
+
}
|
|
1257
|
+
//#endregion
|
|
1119
1258
|
//#region src/client-images.jsx
|
|
1120
1259
|
const imageModelLabel = (id) => id.replace("gpt-image-", "GPT Image ").replace("-flare", " Flare").replace("-sunburst", " Sunburst");
|
|
1121
1260
|
/** Only projections come back from the host; the password draft is cleared on success. */
|
|
@@ -1182,6 +1321,10 @@ window.__ModuleLoader__.load({
|
|
|
1182
1321
|
if (mounted.current) setBusy(false);
|
|
1183
1322
|
}
|
|
1184
1323
|
};
|
|
1324
|
+
const rename = useKeyRename(({ id, label }) => mutate("images/keys/rename", {
|
|
1325
|
+
id,
|
|
1326
|
+
label
|
|
1327
|
+
}));
|
|
1185
1328
|
const disabled = busy || state?.writable !== true;
|
|
1186
1329
|
const models = state?.models ?? [];
|
|
1187
1330
|
const qualities = models.find((model) => model.id === state?.imageModel)?.qualities ?? [];
|
|
@@ -1291,7 +1434,11 @@ window.__ModuleLoader__.load({
|
|
|
1291
1434
|
className: "pkc-row-top",
|
|
1292
1435
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1293
1436
|
className: "pkc-row-name",
|
|
1294
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
1437
|
+
children: [rename.renaming?.id === entry.id ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(KeyRenameForm, {
|
|
1438
|
+
rename,
|
|
1439
|
+
busy,
|
|
1440
|
+
t
|
|
1441
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1295
1442
|
className: "pkc-name",
|
|
1296
1443
|
children: entry.label
|
|
1297
1444
|
}), entry.active && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
@@ -1299,26 +1446,37 @@ window.__ModuleLoader__.load({
|
|
|
1299
1446
|
"data-tone": "success",
|
|
1300
1447
|
children: t("keyActive")
|
|
1301
1448
|
})]
|
|
1302
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1449
|
+
}), rename.renaming?.id !== entry.id && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1303
1450
|
className: "pkc-actions",
|
|
1304
|
-
children: [
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1451
|
+
children: [
|
|
1452
|
+
!entry.active && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1453
|
+
className: "pkc-btn",
|
|
1454
|
+
type: "button",
|
|
1455
|
+
disabled: busy,
|
|
1456
|
+
onClick: () => {
|
|
1457
|
+
mutate("images/keys/select", { id: entry.id });
|
|
1458
|
+
},
|
|
1459
|
+
children: t("keyUse")
|
|
1460
|
+
}),
|
|
1461
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1462
|
+
className: "pkc-btn",
|
|
1463
|
+
"data-variant": "ghost",
|
|
1464
|
+
type: "button",
|
|
1465
|
+
disabled: busy,
|
|
1466
|
+
onClick: () => rename.start(entry),
|
|
1467
|
+
children: t("keyRename")
|
|
1468
|
+
}),
|
|
1469
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1470
|
+
className: "pkc-btn",
|
|
1471
|
+
"data-variant": "danger",
|
|
1472
|
+
type: "button",
|
|
1473
|
+
disabled: busy,
|
|
1474
|
+
onClick: () => {
|
|
1475
|
+
mutate("images/keys/remove", { id: entry.id });
|
|
1476
|
+
},
|
|
1477
|
+
children: t("keyRemove")
|
|
1478
|
+
})
|
|
1479
|
+
]
|
|
1322
1480
|
})]
|
|
1323
1481
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(KeyBalance, {
|
|
1324
1482
|
row: rows.find((row) => row.id === entry.id),
|
|
@@ -1593,6 +1751,173 @@ window.__ModuleLoader__.load({
|
|
|
1593
1751
|
});
|
|
1594
1752
|
}
|
|
1595
1753
|
//#endregion
|
|
1754
|
+
//#region src/client-diagnostics.jsx
|
|
1755
|
+
/**
|
|
1756
|
+
* The settings card that turns one session into a numbers-only diagnosis a
|
|
1757
|
+
* user can copy into a bug report. The host builds the text; this card only
|
|
1758
|
+
* picks the session, shows the result and copies it.
|
|
1759
|
+
*
|
|
1760
|
+
* @module dsh-packycode-api/client-diagnostics
|
|
1761
|
+
*/
|
|
1762
|
+
const pad = (value) => String(value).padStart(2, "0");
|
|
1763
|
+
/** The picker's local date and time; the report itself keeps UTC ISO times. */
|
|
1764
|
+
function localTime(iso) {
|
|
1765
|
+
const date = new Date(iso);
|
|
1766
|
+
if (Number.isNaN(date.getTime())) return "—";
|
|
1767
|
+
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
|
1768
|
+
}
|
|
1769
|
+
const messageOf = (failure) => failure instanceof Error ? failure.message : String(failure);
|
|
1770
|
+
function DiagnosticsCard({ call, t }) {
|
|
1771
|
+
const [sessions, setSessions] = react.useState();
|
|
1772
|
+
const [selected, setSelected] = react.useState("");
|
|
1773
|
+
const [report, setReport] = react.useState();
|
|
1774
|
+
const [busy, setBusy] = react.useState(false);
|
|
1775
|
+
const [error, setError] = react.useState();
|
|
1776
|
+
const [copied, setCopied] = react.useState();
|
|
1777
|
+
const area = react.useRef(null);
|
|
1778
|
+
const mounted = react.useRef(true);
|
|
1779
|
+
react.useEffect(() => () => {
|
|
1780
|
+
mounted.current = false;
|
|
1781
|
+
}, []);
|
|
1782
|
+
const loadSessions = react.useCallback(() => {
|
|
1783
|
+
setError(void 0);
|
|
1784
|
+
return call("diagnostics/sessions").then((value) => {
|
|
1785
|
+
if (!mounted.current) return;
|
|
1786
|
+
const list = Array.isArray(value?.sessions) ? value.sessions : [];
|
|
1787
|
+
setSessions(list);
|
|
1788
|
+
setSelected((current) => list.some((item) => item.id === current) ? current : list[0]?.id ?? "");
|
|
1789
|
+
}).catch((failure) => {
|
|
1790
|
+
if (mounted.current) setError(messageOf(failure));
|
|
1791
|
+
});
|
|
1792
|
+
}, [call]);
|
|
1793
|
+
react.useEffect(() => {
|
|
1794
|
+
loadSessions();
|
|
1795
|
+
}, [loadSessions]);
|
|
1796
|
+
const generate = () => {
|
|
1797
|
+
if (selected === "") return;
|
|
1798
|
+
setBusy(true);
|
|
1799
|
+
setError(void 0);
|
|
1800
|
+
setCopied(void 0);
|
|
1801
|
+
call("diagnostics/session", { sessionId: selected }).then((value) => {
|
|
1802
|
+
if (mounted.current) setReport(JSON.stringify(value, null, 2));
|
|
1803
|
+
}).catch((failure) => {
|
|
1804
|
+
if (mounted.current) setError(messageOf(failure));
|
|
1805
|
+
}).finally(() => {
|
|
1806
|
+
if (mounted.current) setBusy(false);
|
|
1807
|
+
});
|
|
1808
|
+
};
|
|
1809
|
+
/** Copy through the clipboard API, or select everything for a manual copy. */
|
|
1810
|
+
const copy = () => {
|
|
1811
|
+
if (report === void 0) return;
|
|
1812
|
+
const selectAll = () => {
|
|
1813
|
+
area.current?.focus?.();
|
|
1814
|
+
area.current?.select?.();
|
|
1815
|
+
if (mounted.current) setCopied("selected");
|
|
1816
|
+
};
|
|
1817
|
+
const clipboard = globalThis.navigator?.clipboard;
|
|
1818
|
+
if (typeof clipboard?.writeText !== "function") {
|
|
1819
|
+
selectAll();
|
|
1820
|
+
return;
|
|
1821
|
+
}
|
|
1822
|
+
clipboard.writeText(report).then(() => {
|
|
1823
|
+
if (mounted.current) setCopied("copied");
|
|
1824
|
+
}, selectAll);
|
|
1825
|
+
};
|
|
1826
|
+
const label = (item) => [
|
|
1827
|
+
localTime(item.createdAt),
|
|
1828
|
+
item.folder ?? t("diagNoFolder"),
|
|
1829
|
+
item.live ? t("diagLive") : void 0
|
|
1830
|
+
].filter(Boolean).join(" · ");
|
|
1831
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1832
|
+
className: "pkc-card",
|
|
1833
|
+
children: [
|
|
1834
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
|
|
1835
|
+
className: "pkc-head",
|
|
1836
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
1837
|
+
className: "pkc-title",
|
|
1838
|
+
children: t("diagTitle")
|
|
1839
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1840
|
+
className: "pkc-actions",
|
|
1841
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1842
|
+
className: "pkc-btn",
|
|
1843
|
+
type: "button",
|
|
1844
|
+
disabled: busy,
|
|
1845
|
+
onClick: () => {
|
|
1846
|
+
loadSessions();
|
|
1847
|
+
},
|
|
1848
|
+
children: t("refresh")
|
|
1849
|
+
})
|
|
1850
|
+
})]
|
|
1851
|
+
}),
|
|
1852
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1853
|
+
className: "pkc-note",
|
|
1854
|
+
children: t("diagIntro")
|
|
1855
|
+
}),
|
|
1856
|
+
sessions !== void 0 && sessions.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1857
|
+
className: "pkc-note",
|
|
1858
|
+
children: t("diagNoSessions")
|
|
1859
|
+
}),
|
|
1860
|
+
sessions !== void 0 && sessions.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1861
|
+
className: "pkc-form",
|
|
1862
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
1863
|
+
className: "pkc-input pkc-diag-session",
|
|
1864
|
+
"aria-label": t("diagSession"),
|
|
1865
|
+
value: selected,
|
|
1866
|
+
disabled: busy,
|
|
1867
|
+
onChange: (event) => {
|
|
1868
|
+
setSelected(event.target.value);
|
|
1869
|
+
setReport(void 0);
|
|
1870
|
+
setCopied(void 0);
|
|
1871
|
+
},
|
|
1872
|
+
children: sessions.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1873
|
+
value: item.id,
|
|
1874
|
+
children: label(item)
|
|
1875
|
+
}, item.id))
|
|
1876
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1877
|
+
className: "pkc-btn",
|
|
1878
|
+
"data-variant": "primary",
|
|
1879
|
+
"data-size": "md",
|
|
1880
|
+
type: "button",
|
|
1881
|
+
disabled: busy || selected === "",
|
|
1882
|
+
onClick: generate,
|
|
1883
|
+
children: busy ? t("diagGenerating") : t("diagGenerate")
|
|
1884
|
+
})]
|
|
1885
|
+
}),
|
|
1886
|
+
report !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1887
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
1888
|
+
ref: area,
|
|
1889
|
+
className: "pkc-input pkc-mono pkc-diag-text",
|
|
1890
|
+
readOnly: true,
|
|
1891
|
+
rows: 14,
|
|
1892
|
+
value: report,
|
|
1893
|
+
"aria-label": t("diagReport")
|
|
1894
|
+
}),
|
|
1895
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1896
|
+
className: "pkc-form",
|
|
1897
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1898
|
+
className: "pkc-btn",
|
|
1899
|
+
type: "button",
|
|
1900
|
+
onClick: copy,
|
|
1901
|
+
children: copied === "copied" ? t("diagCopied") : t("diagCopy")
|
|
1902
|
+
}), copied === "selected" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1903
|
+
className: "pkc-note",
|
|
1904
|
+
children: t("diagSelected")
|
|
1905
|
+
})]
|
|
1906
|
+
}),
|
|
1907
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1908
|
+
className: "pkc-note",
|
|
1909
|
+
children: t("diagPrivacy")
|
|
1910
|
+
})
|
|
1911
|
+
] }),
|
|
1912
|
+
error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1913
|
+
className: "pkc-error",
|
|
1914
|
+
role: "alert",
|
|
1915
|
+
children: `${t("diagError")}: ${error}`
|
|
1916
|
+
})
|
|
1917
|
+
]
|
|
1918
|
+
});
|
|
1919
|
+
}
|
|
1920
|
+
//#endregion
|
|
1596
1921
|
//#region src/client.jsx
|
|
1597
1922
|
/**
|
|
1598
1923
|
* Browser half: the PackyCode settings page and the composer balance chip.
|
|
@@ -1607,6 +1932,8 @@ window.__ModuleLoader__.load({
|
|
|
1607
1932
|
const PROVIDER = "packycode";
|
|
1608
1933
|
const CHANNEL = "/packycode-api";
|
|
1609
1934
|
const REFRESH_MS = 6e4;
|
|
1935
|
+
/** PackyCode registration through the author's invite link, offered only until a key is stored. */
|
|
1936
|
+
const INVITE_URL = "https://www.packyapi.ai/register?aff=Im3l";
|
|
1610
1937
|
const INSTALL_POLL_MS = 2e3;
|
|
1611
1938
|
/** How long after a search change the panel re-reads what DSH applied (the patch hot-reloads). */
|
|
1612
1939
|
const SEARCH_SETTLE_MS = 1500;
|
|
@@ -1748,8 +2075,8 @@ window.__ModuleLoader__.load({
|
|
|
1748
2075
|
})
|
|
1749
2076
|
] });
|
|
1750
2077
|
}
|
|
1751
|
-
function KeyRow({ entry, row, group, descriptions, groupsLoading, busy,
|
|
1752
|
-
const editing = renaming?.id === entry.id;
|
|
2078
|
+
function KeyRow({ entry, row, group, descriptions, groupsLoading, busy, rename, onAction, t }) {
|
|
2079
|
+
const editing = rename.renaming?.id === entry.id;
|
|
1753
2080
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1754
2081
|
className: "pkc-row",
|
|
1755
2082
|
children: [
|
|
@@ -1758,35 +2085,10 @@ window.__ModuleLoader__.load({
|
|
|
1758
2085
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1759
2086
|
className: "pkc-row-name",
|
|
1760
2087
|
children: [
|
|
1761
|
-
editing ? /* @__PURE__ */ (0, react_jsx_runtime.
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
onRename.submit();
|
|
1766
|
-
},
|
|
1767
|
-
children: [
|
|
1768
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1769
|
-
className: "pkc-input",
|
|
1770
|
-
type: "text",
|
|
1771
|
-
maxLength: 60,
|
|
1772
|
-
autoFocus: true,
|
|
1773
|
-
placeholder: t("keyRenamePlaceholder"),
|
|
1774
|
-
value: renaming.label,
|
|
1775
|
-
onChange: (event) => onRename.change(event.target.value),
|
|
1776
|
-
onKeyDown: (event) => event.key === "Escape" && onRename.cancel()
|
|
1777
|
-
}),
|
|
1778
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
1779
|
-
type: "submit",
|
|
1780
|
-
variant: "primary",
|
|
1781
|
-
disabled: busy || renaming.label.trim() === "",
|
|
1782
|
-
children: t("keyRenameSave")
|
|
1783
|
-
}),
|
|
1784
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
1785
|
-
variant: "ghost",
|
|
1786
|
-
onClick: onRename.cancel,
|
|
1787
|
-
children: t("keyRenameCancel")
|
|
1788
|
-
})
|
|
1789
|
-
]
|
|
2088
|
+
editing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(KeyRenameForm, {
|
|
2089
|
+
rename,
|
|
2090
|
+
busy,
|
|
2091
|
+
t
|
|
1790
2092
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1791
2093
|
className: "pkc-name",
|
|
1792
2094
|
children: entry.label
|
|
@@ -1818,7 +2120,7 @@ window.__ModuleLoader__.load({
|
|
|
1818
2120
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
1819
2121
|
variant: "ghost",
|
|
1820
2122
|
disabled: busy,
|
|
1821
|
-
onClick: () =>
|
|
2123
|
+
onClick: () => rename.start(entry),
|
|
1822
2124
|
children: t("keyRename")
|
|
1823
2125
|
}),
|
|
1824
2126
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
@@ -1843,6 +2145,27 @@ window.__ModuleLoader__.load({
|
|
|
1843
2145
|
]
|
|
1844
2146
|
});
|
|
1845
2147
|
}
|
|
2148
|
+
/**
|
|
2149
|
+
* Where to get a key, for a profile that has none. A new-window link: the
|
|
2150
|
+
* desktop shell hands http(s) window opens to the system browser, and a web
|
|
2151
|
+
* profile opens a new tab.
|
|
2152
|
+
*/
|
|
2153
|
+
function RegisterInvite({ t }) {
|
|
2154
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
2155
|
+
className: "pkc-note",
|
|
2156
|
+
children: [
|
|
2157
|
+
t("inviteLead"),
|
|
2158
|
+
" ",
|
|
2159
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
2160
|
+
className: "pkc-link",
|
|
2161
|
+
href: INVITE_URL,
|
|
2162
|
+
target: "_blank",
|
|
2163
|
+
rel: "noopener noreferrer",
|
|
2164
|
+
children: t("inviteLink")
|
|
2165
|
+
})
|
|
2166
|
+
]
|
|
2167
|
+
});
|
|
2168
|
+
}
|
|
1846
2169
|
function AccountsCard({ call, t }) {
|
|
1847
2170
|
const [status, setStatus] = react.useState();
|
|
1848
2171
|
const [balance, setBalance] = react.useState();
|
|
@@ -1854,7 +2177,6 @@ window.__ModuleLoader__.load({
|
|
|
1854
2177
|
key: "",
|
|
1855
2178
|
label: ""
|
|
1856
2179
|
});
|
|
1857
|
-
const [renaming, setRenaming] = react.useState();
|
|
1858
2180
|
const generation = react.useRef(0);
|
|
1859
2181
|
const readGroups = react.useCallback((force = false) => {
|
|
1860
2182
|
setGroupsLoading(true);
|
|
@@ -1901,24 +2223,10 @@ window.__ModuleLoader__.load({
|
|
|
1901
2223
|
label: ""
|
|
1902
2224
|
}));
|
|
1903
2225
|
};
|
|
1904
|
-
const rename = {
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
}),
|
|
1909
|
-
change: (label) => setRenaming((current) => ({
|
|
1910
|
-
...current,
|
|
1911
|
-
label
|
|
1912
|
-
})),
|
|
1913
|
-
cancel: () => setRenaming(void 0),
|
|
1914
|
-
submit: () => {
|
|
1915
|
-
if (renaming === void 0 || renaming.label.trim() === "") return;
|
|
1916
|
-
mutate("keys/rename", {
|
|
1917
|
-
id: renaming.id,
|
|
1918
|
-
label: renaming.label
|
|
1919
|
-
}).then(() => setRenaming(void 0));
|
|
1920
|
-
}
|
|
1921
|
-
};
|
|
2226
|
+
const rename = useKeyRename(({ id, label }) => mutate("keys/rename", {
|
|
2227
|
+
id,
|
|
2228
|
+
label
|
|
2229
|
+
}));
|
|
1922
2230
|
const keys = status?.keys ?? [];
|
|
1923
2231
|
const rows = balance?.keys ?? [];
|
|
1924
2232
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, {
|
|
@@ -1937,10 +2245,10 @@ window.__ModuleLoader__.load({
|
|
|
1937
2245
|
})]
|
|
1938
2246
|
}),
|
|
1939
2247
|
children: [
|
|
1940
|
-
keys.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2248
|
+
keys.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1941
2249
|
className: "pkc-note",
|
|
1942
2250
|
children: t("empty")
|
|
1943
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
2251
|
+
}), status !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RegisterInvite, { t })] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
1944
2252
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(BalanceTotals, {
|
|
1945
2253
|
rows,
|
|
1946
2254
|
t
|
|
@@ -1954,8 +2262,7 @@ window.__ModuleLoader__.load({
|
|
|
1954
2262
|
descriptions: groups?.descriptions,
|
|
1955
2263
|
groupsLoading,
|
|
1956
2264
|
busy,
|
|
1957
|
-
|
|
1958
|
-
onRename: rename,
|
|
2265
|
+
rename,
|
|
1959
2266
|
onAction: mutate,
|
|
1960
2267
|
t
|
|
1961
2268
|
}, entry.id))
|
|
@@ -2626,6 +2933,10 @@ window.__ModuleLoader__.load({
|
|
|
2626
2933
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(VersionCard, {
|
|
2627
2934
|
call,
|
|
2628
2935
|
t
|
|
2936
|
+
}),
|
|
2937
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DiagnosticsCard, {
|
|
2938
|
+
call,
|
|
2939
|
+
t
|
|
2629
2940
|
})
|
|
2630
2941
|
]
|
|
2631
2942
|
});
|
|
@@ -2634,38 +2945,48 @@ window.__ModuleLoader__.load({
|
|
|
2634
2945
|
const call = useCaller(rpc);
|
|
2635
2946
|
const enabled = react.useSyncExternalStore((listener) => directory.subscribe(listener), () => directory.getSnapshot())?.current?.provider === PROVIDER;
|
|
2636
2947
|
const [balance, setBalance] = react.useState();
|
|
2948
|
+
const refresh = react.useRef(() => {});
|
|
2637
2949
|
react.useEffect(() => {
|
|
2638
2950
|
if (!enabled) {
|
|
2639
2951
|
setBalance(void 0);
|
|
2640
2952
|
return;
|
|
2641
2953
|
}
|
|
2642
2954
|
let live = true;
|
|
2955
|
+
let pending = false;
|
|
2643
2956
|
const tick = () => {
|
|
2957
|
+
if (pending) return;
|
|
2958
|
+
pending = true;
|
|
2644
2959
|
call("balance").then((value) => {
|
|
2645
2960
|
if (live) setBalance(value);
|
|
2646
2961
|
}).catch(() => {
|
|
2647
2962
|
if (live) setBalance(void 0);
|
|
2963
|
+
}).finally(() => {
|
|
2964
|
+
pending = false;
|
|
2648
2965
|
});
|
|
2649
2966
|
};
|
|
2967
|
+
refresh.current = tick;
|
|
2650
2968
|
tick();
|
|
2651
2969
|
const timer = window.setInterval(tick, REFRESH_MS);
|
|
2652
2970
|
return () => {
|
|
2653
2971
|
live = false;
|
|
2972
|
+
refresh.current = () => {};
|
|
2654
2973
|
window.clearInterval(timer);
|
|
2655
2974
|
};
|
|
2656
2975
|
}, [call, enabled]);
|
|
2657
2976
|
if (!enabled || balance === void 0) return null;
|
|
2658
2977
|
const row = activeUsage(balance);
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2978
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QuotaHover, {
|
|
2979
|
+
row,
|
|
2980
|
+
fetchedAt: balance.fetchedAt,
|
|
2981
|
+
empty: row?.error === void 0 ? t("composerNoKey") : t("composerUnread"),
|
|
2982
|
+
copy: {
|
|
2983
|
+
title: t("composerLabel"),
|
|
2984
|
+
key: t("composerKey"),
|
|
2985
|
+
failed: t("composerUnread")
|
|
2986
|
+
},
|
|
2987
|
+
align: "end",
|
|
2988
|
+
onRefresh: () => refresh.current(),
|
|
2989
|
+
t
|
|
2669
2990
|
});
|
|
2670
2991
|
}
|
|
2671
2992
|
const inject = [
|