@eddyskywalker/dsh-chatgpt-subscription 0.1.0-alpha.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.
Files changed (46) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/LICENSE +21 -0
  3. package/README.md +148 -0
  4. package/cordis.patch.yml +6 -0
  5. package/lib/client.js +673 -0
  6. package/lib/client.js.map +1 -0
  7. package/lib/index.js +1753 -0
  8. package/lib/types/client/CodexSubscriptionSection.d.ts +15 -0
  9. package/lib/types/client/CodexSubscriptionSection.d.ts.map +1 -0
  10. package/lib/types/client/api.d.ts +17 -0
  11. package/lib/types/client/api.d.ts.map +1 -0
  12. package/lib/types/client/index.d.ts +10 -0
  13. package/lib/types/client/index.d.ts.map +1 -0
  14. package/lib/types/client/locales.d.ts +103 -0
  15. package/lib/types/client/locales.d.ts.map +1 -0
  16. package/lib/types/client/styles.d.ts +2 -0
  17. package/lib/types/client/styles.d.ts.map +1 -0
  18. package/lib/types/compat.d.ts +26 -0
  19. package/lib/types/compat.d.ts.map +1 -0
  20. package/lib/types/host/adapter.d.ts +14 -0
  21. package/lib/types/host/adapter.d.ts.map +1 -0
  22. package/lib/types/host/callback-server.d.ts +22 -0
  23. package/lib/types/host/callback-server.d.ts.map +1 -0
  24. package/lib/types/host/model-catalog.d.ts +6 -0
  25. package/lib/types/host/model-catalog.d.ts.map +1 -0
  26. package/lib/types/host/oauth-service.d.ts +69 -0
  27. package/lib/types/host/oauth-service.d.ts.map +1 -0
  28. package/lib/types/host/responses-client.d.ts +21 -0
  29. package/lib/types/host/responses-client.d.ts.map +1 -0
  30. package/lib/types/host/responses-mapper.d.ts +11 -0
  31. package/lib/types/host/responses-mapper.d.ts.map +1 -0
  32. package/lib/types/host/routes.d.ts +5 -0
  33. package/lib/types/host/routes.d.ts.map +1 -0
  34. package/lib/types/host/token-store-windows.d.ts +10 -0
  35. package/lib/types/host/token-store-windows.d.ts.map +1 -0
  36. package/lib/types/host/token-store.d.ts +23 -0
  37. package/lib/types/host/token-store.d.ts.map +1 -0
  38. package/lib/types/host/usage-service.d.ts +33 -0
  39. package/lib/types/host/usage-service.d.ts.map +1 -0
  40. package/lib/types/host/wire-auth.d.ts +5 -0
  41. package/lib/types/host/wire-auth.d.ts.map +1 -0
  42. package/lib/types/index.d.ts +9 -0
  43. package/lib/types/index.d.ts.map +1 -0
  44. package/lib/types/shared/contracts.d.ts +77 -0
  45. package/lib/types/shared/contracts.d.ts.map +1 -0
  46. package/package.json +105 -0
package/lib/client.js ADDED
@@ -0,0 +1,673 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@eddyskywalker/dsh-chatgpt-subscription",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let react = require("react");
8
+ let react_jsx_runtime = require("react/jsx-runtime");
9
+ const ROUTE_PREFIX = "/api/dsh-chatgpt-subscription";
10
+ //#endregion
11
+ //#region src/client/api.ts
12
+ var SubscriptionApi = class {
13
+ status() {
14
+ return request(`${ROUTE_PREFIX}/status`);
15
+ }
16
+ startLogin() {
17
+ return post(`${ROUTE_PREFIX}/login/start`, {});
18
+ }
19
+ cancelLogin(loginId) {
20
+ return post(`${ROUTE_PREFIX}/login/cancel`, { loginId });
21
+ }
22
+ logout() {
23
+ return post(`${ROUTE_PREFIX}/logout`, {});
24
+ }
25
+ refresh() {
26
+ return post(`${ROUTE_PREFIX}/token/refresh`, {});
27
+ }
28
+ refreshQuota() {
29
+ return post(`${ROUTE_PREFIX}/quota/refresh`, {});
30
+ }
31
+ testConnection() {
32
+ return post(`${ROUTE_PREFIX}/connection/test`, {});
33
+ }
34
+ events(loginId) {
35
+ return new EventSource(`${ROUTE_PREFIX}/login/events?loginId=${encodeURIComponent(loginId)}`);
36
+ }
37
+ };
38
+ async function post(url, body) {
39
+ return request(url, {
40
+ method: "POST",
41
+ headers: { "content-type": "application/json" },
42
+ body: JSON.stringify(body)
43
+ });
44
+ }
45
+ async function request(url, init) {
46
+ const response = await fetch(url, {
47
+ ...init,
48
+ credentials: "same-origin"
49
+ });
50
+ const envelope = await response.json();
51
+ if (!response.ok || !envelope.ok) throw new Error(envelope.ok ? `Request failed (${response.status})` : envelope.error.message);
52
+ return envelope.value;
53
+ }
54
+ function parseLoginEvent(event) {
55
+ try {
56
+ const value = JSON.parse(event.data);
57
+ return typeof value === "object" && value !== null && typeof value.type === "string" ? value : null;
58
+ } catch {
59
+ return null;
60
+ }
61
+ }
62
+ //#endregion
63
+ //#region src/client/CodexSubscriptionSection.tsx
64
+ const MODELS = [
65
+ "gpt-5.6-sol",
66
+ "gpt-5.6-terra",
67
+ "gpt-5.6-luna",
68
+ "gpt-5.5",
69
+ "gpt-5.4",
70
+ "gpt-5.4-mini",
71
+ "gpt-5.2"
72
+ ];
73
+ function CodexSubscriptionSection({ t }) {
74
+ const apiRef = (0, react.useRef)(new SubscriptionApi());
75
+ const eventSourceRef = (0, react.useRef)(null);
76
+ const [status, setStatus] = (0, react.useState)(null);
77
+ const [busy, setBusy] = (0, react.useState)(null);
78
+ const [error, setError] = (0, react.useState)(null);
79
+ const [authUrl, setAuthUrl] = (0, react.useState)(null);
80
+ const [popupBlocked, setPopupBlocked] = (0, react.useState)(false);
81
+ const [connection, setConnection] = (0, react.useState)(null);
82
+ const load = (0, react.useCallback)(async (quiet = false) => {
83
+ if (!quiet) setError(null);
84
+ try {
85
+ const next = await apiRef.current.status();
86
+ setStatus(next);
87
+ if (next.error !== void 0) setError(next.error.message);
88
+ } catch (cause) {
89
+ if (!quiet) setError(messageOf(cause));
90
+ }
91
+ }, []);
92
+ (0, react.useEffect)(() => {
93
+ load();
94
+ const refreshWhenVisible = () => {
95
+ if (document.visibilityState === "visible") load(true);
96
+ };
97
+ document.addEventListener("visibilitychange", refreshWhenVisible);
98
+ const timer = window.setInterval(refreshWhenVisible, 6e4);
99
+ return () => {
100
+ window.clearInterval(timer);
101
+ document.removeEventListener("visibilitychange", refreshWhenVisible);
102
+ eventSourceRef.current?.close();
103
+ };
104
+ }, [load]);
105
+ const watchLogin = (0, react.useCallback)((loginId) => {
106
+ eventSourceRef.current?.close();
107
+ const source = apiRef.current.events(loginId);
108
+ eventSourceRef.current = source;
109
+ const finish = async (message) => {
110
+ source.close();
111
+ eventSourceRef.current = null;
112
+ setBusy(null);
113
+ setAuthUrl(null);
114
+ if (message !== void 0) setError(message);
115
+ await load(true);
116
+ };
117
+ source.addEventListener("completed", (event) => {
118
+ if (parseLoginEvent(event)?.type === "completed") finish();
119
+ });
120
+ source.addEventListener("cancelled", () => void finish());
121
+ source.addEventListener("failed", (event) => {
122
+ const parsed = parseLoginEvent(event);
123
+ finish(parsed?.type === "failed" ? parsed.error.message : "ChatGPT sign-in failed.");
124
+ });
125
+ }, [load]);
126
+ (0, react.useEffect)(() => {
127
+ const loginId = status?.login.active ? status.login.loginId : null;
128
+ if (loginId !== null && loginId !== void 0 && eventSourceRef.current === null) watchLogin(loginId);
129
+ }, [
130
+ status?.login.active,
131
+ status?.login.loginId,
132
+ watchLogin
133
+ ]);
134
+ const startLogin = async () => {
135
+ setBusy("login");
136
+ setError(null);
137
+ setPopupBlocked(false);
138
+ const popup = window.open("about:blank", "dsh-chatgpt-oauth", "popup,width=560,height=760");
139
+ try {
140
+ const login = await apiRef.current.startLogin();
141
+ setAuthUrl(login.authUrl);
142
+ if (popup === null) setPopupBlocked(true);
143
+ else popup.location.replace(login.authUrl);
144
+ watchLogin(login.loginId);
145
+ await load(true);
146
+ } catch (cause) {
147
+ popup?.close();
148
+ setBusy(null);
149
+ setError(messageOf(cause));
150
+ }
151
+ };
152
+ const cancelLogin = async () => {
153
+ const loginId = status?.login.loginId;
154
+ if (loginId === null || loginId === void 0) return;
155
+ setBusy("login");
156
+ try {
157
+ await apiRef.current.cancelLogin(loginId);
158
+ eventSourceRef.current?.close();
159
+ eventSourceRef.current = null;
160
+ setAuthUrl(null);
161
+ await load();
162
+ } catch (cause) {
163
+ setError(messageOf(cause));
164
+ } finally {
165
+ setBusy(null);
166
+ }
167
+ };
168
+ const refreshToken = async () => run("token", async () => {
169
+ setStatus(await apiRef.current.refresh());
170
+ });
171
+ const refreshQuota = async () => run("quota", async () => {
172
+ const quota = await apiRef.current.refreshQuota();
173
+ setStatus((current) => current === null ? current : {
174
+ ...current,
175
+ quota
176
+ });
177
+ });
178
+ const testConnection = async () => run("test", async () => {
179
+ const result = await apiRef.current.testConnection();
180
+ setConnection({
181
+ latencyMs: result.latencyMs,
182
+ checkedAt: result.checkedAt
183
+ });
184
+ });
185
+ const logout = async () => run("logout", async () => {
186
+ await apiRef.current.logout();
187
+ eventSourceRef.current?.close();
188
+ eventSourceRef.current = null;
189
+ setAuthUrl(null);
190
+ setConnection(null);
191
+ await load();
192
+ });
193
+ const run = async (action, task) => {
194
+ setBusy(action);
195
+ setError(null);
196
+ try {
197
+ await task();
198
+ } catch (cause) {
199
+ setError(messageOf(cause));
200
+ } finally {
201
+ setBusy(null);
202
+ }
203
+ };
204
+ const account = status?.account;
205
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
206
+ className: "dsh-codex-page",
207
+ "aria-labelledby": "dsh-codex-title",
208
+ children: [
209
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
210
+ id: "dsh-codex-title",
211
+ className: "dsh-codex-title",
212
+ children: t("title")
213
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
214
+ className: "dsh-codex-intro",
215
+ children: t("intro")
216
+ })] }),
217
+ error !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
218
+ className: "dsh-codex-errorbar",
219
+ role: "alert",
220
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: error }), status === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
221
+ disabled: busy !== null,
222
+ onClick: () => load(),
223
+ children: t("retry")
224
+ }) : null]
225
+ }) : null,
226
+ status === null && error === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Skeleton, { label: t("loading") }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
227
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Section, {
228
+ title: t("account"),
229
+ children: [
230
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InfoRow, {
231
+ label: status?.authenticated ? t("signedIn") : t("signedOut"),
232
+ value: account?.email ?? "—"
233
+ }),
234
+ status?.authenticated ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
235
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InfoRow, {
236
+ label: t("plan"),
237
+ value: account?.planType ?? t("unknown")
238
+ }),
239
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InfoRow, {
240
+ label: t("accountId"),
241
+ value: account?.accountIdSuffix ?? "—"
242
+ }),
243
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InfoRow, {
244
+ label: t("expires"),
245
+ value: formatDate(account?.tokenExpiresAt)
246
+ })
247
+ ] }) : null,
248
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InfoRow, {
249
+ label: t("storage"),
250
+ value: t("storageValue")
251
+ }),
252
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
253
+ className: "dsh-codex-notice",
254
+ children: t("securityNotice")
255
+ }),
256
+ status?.login.active ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
257
+ className: "dsh-codex-muted",
258
+ role: "status",
259
+ children: t("pending")
260
+ }) : null,
261
+ popupBlocked ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
262
+ className: "dsh-codex-error",
263
+ children: t("popupBlocked")
264
+ }) : null,
265
+ authUrl !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
266
+ className: "dsh-codex-link",
267
+ href: authUrl,
268
+ target: "_blank",
269
+ rel: "noreferrer",
270
+ children: t("continueLogin")
271
+ }) : null,
272
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
273
+ className: "dsh-codex-actions",
274
+ children: [status?.login.active ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
275
+ disabled: busy !== null,
276
+ onClick: cancelLogin,
277
+ children: t("cancel")
278
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
279
+ primary: true,
280
+ disabled: busy !== null,
281
+ onClick: startLogin,
282
+ children: status?.authenticated ? t("signInAgain") : t("signIn")
283
+ }), status?.authenticated ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
284
+ disabled: busy !== null,
285
+ onClick: refreshToken,
286
+ children: t("refreshToken")
287
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
288
+ disabled: busy !== null,
289
+ onClick: logout,
290
+ children: t("signOut")
291
+ })] }) : null]
292
+ })
293
+ ]
294
+ }),
295
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Section, {
296
+ title: t("connection"),
297
+ children: [
298
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InfoRow, {
299
+ label: t("provider"),
300
+ value: "Codex(ChatGPT 订阅) · codex-chatgpt"
301
+ }),
302
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InfoRow, {
303
+ label: t("connectionState"),
304
+ value: connection === null ? t("untested") : t("connected")
305
+ }),
306
+ connection !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InfoRow, {
307
+ label: t("latency"),
308
+ value: `${connection.latencyMs} ms · ${formatDate(connection.checkedAt)}`
309
+ }) : null,
310
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
311
+ className: "dsh-codex-models",
312
+ "aria-label": t("models"),
313
+ children: MODELS.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", { children: model }, model))
314
+ }),
315
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
316
+ className: "dsh-codex-actions",
317
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
318
+ disabled: !status?.authenticated || busy !== null,
319
+ onClick: testConnection,
320
+ children: busy === "test" ? t("testing") : t("testConnection")
321
+ })
322
+ })
323
+ ]
324
+ }),
325
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Section, {
326
+ title: t("quota"),
327
+ aside: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
328
+ disabled: !status?.authenticated || busy !== null,
329
+ onClick: refreshQuota,
330
+ children: busy === "quota" ? t("refreshing") : t("refreshQuota")
331
+ }),
332
+ children: [
333
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
334
+ className: "dsh-codex-muted",
335
+ children: t("quotaIntro")
336
+ }),
337
+ status?.quota.state === "signed-out" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
338
+ className: "dsh-codex-empty",
339
+ children: t("quotaSignedOut")
340
+ }) : null,
341
+ status?.quota.buckets.map((bucket) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QuotaBucket, {
342
+ bucket,
343
+ t
344
+ }, bucket.id)),
345
+ status?.quota.state === "empty" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
346
+ className: "dsh-codex-empty",
347
+ children: t("noQuota")
348
+ }) : null,
349
+ status?.quota.stale ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
350
+ className: "dsh-codex-warning",
351
+ role: "status",
352
+ children: t("stale")
353
+ }) : null,
354
+ status?.quota.error ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
355
+ className: "dsh-codex-error",
356
+ role: "alert",
357
+ children: status.quota.error.message
358
+ }) : null,
359
+ status?.quota.fetchedAt ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
360
+ className: "dsh-codex-timestamp",
361
+ children: [
362
+ t("updated"),
363
+ ": ",
364
+ formatDate(status.quota.fetchedAt)
365
+ ]
366
+ }) : null
367
+ ]
368
+ })
369
+ ] }),
370
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
371
+ className: "dsh-codex-sr",
372
+ "aria-live": "polite",
373
+ children: busy === null ? "" : busy
374
+ })
375
+ ]
376
+ });
377
+ }
378
+ function Section({ title, aside, children }) {
379
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
380
+ className: "dsh-codex-group",
381
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
382
+ className: "dsh-codex-grouphead",
383
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: title }), aside]
384
+ }), children]
385
+ });
386
+ }
387
+ function Button({ primary = false, disabled, onClick, children }) {
388
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
389
+ className: `dsh-codex-button${primary ? " dsh-codex-button-primary" : ""}`,
390
+ type: "button",
391
+ disabled,
392
+ onClick: () => void onClick(),
393
+ children
394
+ });
395
+ }
396
+ function InfoRow({ label, value }) {
397
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
398
+ className: "dsh-codex-row",
399
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
400
+ className: "dsh-codex-label",
401
+ children: label
402
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
403
+ className: "dsh-codex-value",
404
+ children: value
405
+ })]
406
+ });
407
+ }
408
+ function QuotaBucket({ bucket, t }) {
409
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
410
+ className: "dsh-codex-quota-card",
411
+ children: [
412
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
413
+ className: "dsh-codex-quota-title",
414
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: bucket.name }), bucket.planType ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: bucket.planType }) : null]
415
+ }),
416
+ bucket.primary ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QuotaBar, {
417
+ label: windowLabel(bucket.primary.windowDurationMins, t),
418
+ window: bucket.primary,
419
+ t
420
+ }) : null,
421
+ bucket.secondary ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QuotaBar, {
422
+ label: windowLabel(bucket.secondary.windowDurationMins, t),
423
+ window: bucket.secondary,
424
+ t
425
+ }) : null
426
+ ]
427
+ });
428
+ }
429
+ function QuotaBar({ label, window, t }) {
430
+ const percent = window.usedPercent;
431
+ const level = percent >= 95 ? "danger" : percent >= 80 ? "warning" : "normal";
432
+ const remaining = Math.max(0, 100 - percent);
433
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
434
+ className: "dsh-codex-meter-wrap",
435
+ children: [
436
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
437
+ className: "dsh-codex-meter-label",
438
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: label }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: formatPercent(percent) })]
439
+ }),
440
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
441
+ className: `dsh-codex-meter dsh-codex-meter-${level}`,
442
+ role: "progressbar",
443
+ "aria-label": `${label}: ${formatPercent(percent)} ${t("used")}`,
444
+ "aria-valuemin": 0,
445
+ "aria-valuemax": 100,
446
+ "aria-valuenow": percent,
447
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: { width: `${percent}%` } })
448
+ }),
449
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
450
+ className: "dsh-codex-meter-meta",
451
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: percent >= 100 ? `${t("exhausted")} · ${formatPercent(remaining)} ${t("remaining")}` : `${formatPercent(percent)} ${t("used")} · ${formatPercent(remaining)} ${t("remaining")}` }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: window.resetsAt === null ? "—" : `${t("resets")}: ${formatReset(window.resetsAt)}` })]
452
+ })
453
+ ]
454
+ });
455
+ }
456
+ function Skeleton({ label }) {
457
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
458
+ className: "dsh-codex-skeleton",
459
+ role: "status",
460
+ "aria-label": label,
461
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {})]
462
+ });
463
+ }
464
+ function windowLabel(minutes, t) {
465
+ if (minutes === null) return t("limitWindow");
466
+ const [value, unit] = minutes >= 1440 && minutes % 1440 === 0 ? [minutes / 1440, "day"] : minutes >= 60 && minutes % 60 === 0 ? [minutes / 60, "hour"] : [Math.round(minutes), "minute"];
467
+ return `${new Intl.NumberFormat(void 0, {
468
+ style: "unit",
469
+ unit,
470
+ unitDisplay: "long"
471
+ }).format(value)} ${t("limitWindow")}`;
472
+ }
473
+ function formatPercent(value) {
474
+ return `${new Intl.NumberFormat(void 0, { maximumFractionDigits: 1 }).format(value)}%`;
475
+ }
476
+ function formatDate(seconds) {
477
+ if (seconds === void 0) return "—";
478
+ return new Intl.DateTimeFormat(void 0, {
479
+ dateStyle: "medium",
480
+ timeStyle: "short"
481
+ }).format(seconds * 1e3);
482
+ }
483
+ function formatReset(seconds) {
484
+ const absolute = formatDate(seconds);
485
+ const diff = seconds * 1e3 - Date.now();
486
+ const abs = Math.abs(diff);
487
+ const [amount, unit] = abs >= 864e5 ? [Math.round(diff / 864e5), "day"] : abs >= 36e5 ? [Math.round(diff / 36e5), "hour"] : [Math.round(diff / 6e4), "minute"];
488
+ return `${absolute} (${new Intl.RelativeTimeFormat(void 0, { numeric: "auto" }).format(amount, unit)})`;
489
+ }
490
+ function messageOf(error) {
491
+ return error instanceof Error ? error.message : String(error);
492
+ }
493
+ //#endregion
494
+ //#region src/client/locales.ts
495
+ const NS = "dsh-chatgpt-subscription";
496
+ const dictionaries = {
497
+ zh: {
498
+ title: "Codex 订阅",
499
+ intro: "使用 ChatGPT 账号登录,在 DSH 中使用订阅可用的 Codex 模型。",
500
+ account: "账号",
501
+ signedOut: "尚未登录",
502
+ signedIn: "已登录",
503
+ plan: "套餐",
504
+ accountId: "账号 ID",
505
+ expires: "令牌到期",
506
+ storage: "凭据存储",
507
+ storageValue: "Windows DPAPI(当前用户加密)",
508
+ securityNotice: "令牌仅保存在 Host 端的 DPAPI 加密文件中,不会进入浏览器、settings.yaml 或日志。",
509
+ signIn: "使用 ChatGPT 登录",
510
+ signInAgain: "重新登录",
511
+ cancel: "取消登录",
512
+ signOut: "注销",
513
+ refreshToken: "刷新凭据",
514
+ pending: "请在浏览器中完成登录。",
515
+ popupBlocked: "浏览器拦截了登录窗口,请使用下面的链接继续。",
516
+ continueLogin: "打开 ChatGPT 登录页",
517
+ loading: "正在读取状态…",
518
+ connection: "连接",
519
+ provider: "Provider",
520
+ connectionState: "连接状态",
521
+ connected: "可用",
522
+ untested: "尚未测试",
523
+ testConnection: "测试连接",
524
+ testing: "测试中…",
525
+ latency: "最近延迟",
526
+ models: "可用模型",
527
+ quota: "用量与限额",
528
+ quotaIntro: "数据来自 ChatGPT Codex 用量服务。页面可见时最多每 60 秒刷新一次。",
529
+ refreshQuota: "刷新用量",
530
+ refreshing: "刷新中…",
531
+ noQuota: "当前套餐未返回可显示的限额窗口。",
532
+ quotaSignedOut: "登录后可查看订阅限额。",
533
+ stale: "显示的是上次成功获取的数据。",
534
+ updated: "更新时间",
535
+ primary: "主要窗口",
536
+ secondary: "次要窗口",
537
+ limitWindow: "额度",
538
+ used: "已使用",
539
+ remaining: "剩余",
540
+ exhausted: "额度已用尽",
541
+ resets: "重置",
542
+ retry: "重试",
543
+ unknown: "未知"
544
+ },
545
+ en: {
546
+ title: "Codex subscription",
547
+ intro: "Sign in with ChatGPT to use Codex models available to your subscription in DSH.",
548
+ account: "Account",
549
+ signedOut: "Not signed in",
550
+ signedIn: "Signed in",
551
+ plan: "Plan",
552
+ accountId: "Account ID",
553
+ expires: "Token expires",
554
+ storage: "Credential storage",
555
+ storageValue: "Windows DPAPI (current-user encrypted)",
556
+ securityNotice: "Tokens stay in a Host-side DPAPI-encrypted file and never enter the browser, settings.yaml, or logs.",
557
+ signIn: "Sign in with ChatGPT",
558
+ signInAgain: "Sign in again",
559
+ cancel: "Cancel sign-in",
560
+ signOut: "Sign out",
561
+ refreshToken: "Refresh credentials",
562
+ pending: "Complete sign-in in your browser.",
563
+ popupBlocked: "The sign-in window was blocked. Use the link below to continue.",
564
+ continueLogin: "Open ChatGPT sign-in",
565
+ loading: "Reading status…",
566
+ connection: "Connection",
567
+ provider: "Provider",
568
+ connectionState: "Connection status",
569
+ connected: "Available",
570
+ untested: "Not tested",
571
+ testConnection: "Test connection",
572
+ testing: "Testing…",
573
+ latency: "Last latency",
574
+ models: "Available models",
575
+ quota: "Usage and limits",
576
+ quotaIntro: "Data comes from the ChatGPT Codex usage service and refreshes at most once per minute while visible.",
577
+ refreshQuota: "Refresh usage",
578
+ refreshing: "Refreshing…",
579
+ noQuota: "Your plan did not return any displayable limit windows.",
580
+ quotaSignedOut: "Sign in to view subscription limits.",
581
+ stale: "Showing the last successfully fetched data.",
582
+ updated: "Updated",
583
+ primary: "Primary window",
584
+ secondary: "Secondary window",
585
+ limitWindow: "limit",
586
+ used: "used",
587
+ remaining: "remaining",
588
+ exhausted: "Quota exhausted",
589
+ resets: "Resets",
590
+ retry: "Retry",
591
+ unknown: "Unknown"
592
+ }
593
+ };
594
+ //#endregion
595
+ //#region src/client/styles.ts
596
+ const STYLE_ID = "@eddyskywalker/dsh-chatgpt-subscription/main";
597
+ const CSS = `
598
+ .dsh-codex-page{box-sizing:border-box;color:var(--dsw-alias-label-primary);display:flex;flex-direction:column;gap:20px;max-width:780px;min-width:0;padding:2px 0 30px}
599
+ .dsh-codex-page *{box-sizing:border-box}
600
+ .dsh-codex-title{font-size:15px;font-weight:650;line-height:1.4;margin:0 0 5px}
601
+ .dsh-codex-intro,.dsh-codex-muted{color:var(--dsw-alias-label-secondary);font-size:13px;line-height:1.55;margin:0}
602
+ .dsh-codex-group{border-top:1px solid var(--dsw-alias-border-l2);min-width:0}
603
+ .dsh-codex-grouphead{align-items:center;display:flex;gap:12px;justify-content:space-between;min-height:48px}
604
+ .dsh-codex-grouphead h3{font-size:14px;font-weight:650;margin:0}
605
+ .dsh-codex-row{align-items:center;border-bottom:1px solid var(--dsw-alias-border-l2);display:flex;gap:20px;justify-content:space-between;min-height:44px;padding:8px 0}
606
+ .dsh-codex-label{color:var(--dsw-alias-label-secondary);font-size:13px;flex:0 0 auto}
607
+ .dsh-codex-value{font-size:13px;min-width:0;overflow-wrap:anywhere;text-align:right}
608
+ .dsh-codex-actions{display:flex;flex-wrap:wrap;gap:8px;justify-content:flex-end;padding-top:12px}
609
+ .dsh-codex-button{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:999px;color:var(--dsw-alias-label-primary);cursor:pointer;font:inherit;font-size:13px;line-height:1;padding:8px 13px;white-space:nowrap}
610
+ .dsh-codex-button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}
611
+ .dsh-codex-button:focus-visible,.dsh-codex-link:focus-visible{outline:2px solid var(--dsw-alias-button-info-fill,#397ee8);outline-offset:2px}
612
+ .dsh-codex-button:disabled{cursor:default;opacity:.5}
613
+ .dsh-codex-button-primary{background:var(--dsw-alias-button-info-fill,#397ee8);border-color:transparent;color:var(--dsw-alias-button-info-label,#fff)}
614
+ .dsh-codex-notice{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:7px;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.55;margin:12px 0 0;padding:10px 12px}
615
+ .dsh-codex-error,.dsh-codex-warning{font-size:12px;line-height:1.5;margin:8px 0 0}
616
+ .dsh-codex-error{color:var(--dsw-alias-label-danger,#d94b4b)}
617
+ .dsh-codex-warning{color:var(--dsw-alias-label-warning,#c77a18)}
618
+ .dsh-codex-errorbar{align-items:center;background:color-mix(in srgb,var(--dsw-alias-label-danger,#d94b4b) 9%,transparent);border:1px solid color-mix(in srgb,var(--dsw-alias-label-danger,#d94b4b) 28%,transparent);border-radius:7px;color:var(--dsw-alias-label-danger,#d94b4b);display:flex;font-size:13px;gap:12px;justify-content:space-between;padding:10px 12px}
619
+ .dsh-codex-link{color:var(--dsw-alias-label-link,#3278d4);display:inline-block;font-size:13px;margin-top:8px}
620
+ .dsh-codex-models{display:flex;flex-wrap:wrap;gap:6px;padding-top:12px}
621
+ .dsh-codex-models code{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:5px;font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:11px;padding:4px 6px}
622
+ .dsh-codex-quota-card{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;margin-top:12px;padding:12px}
623
+ .dsh-codex-quota-title{align-items:center;display:flex;font-size:13px;gap:8px;justify-content:space-between}
624
+ .dsh-codex-quota-title span{color:var(--dsw-alias-label-tertiary);font-size:11px;text-transform:uppercase}
625
+ .dsh-codex-meter-wrap{margin-top:13px}
626
+ .dsh-codex-meter-label,.dsh-codex-meter-meta{display:flex;gap:10px;justify-content:space-between}
627
+ .dsh-codex-meter-label{font-size:12px;margin-bottom:6px}
628
+ .dsh-codex-meter-meta{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:1.45;margin-top:6px}
629
+ .dsh-codex-meter{background:var(--dsw-alias-bg-layer-1,rgba(127,127,127,.15));border-radius:999px;height:7px;overflow:hidden;width:100%}
630
+ .dsh-codex-meter>span{background:var(--dsw-alias-button-info-fill,#397ee8);border-radius:inherit;display:block;height:100%;max-width:100%;min-width:0;transition:width .25s ease}
631
+ .dsh-codex-meter-warning>span{background:var(--dsw-alias-label-warning,#d58a24)}
632
+ .dsh-codex-meter-danger>span{background:var(--dsw-alias-label-danger,#d94b4b)}
633
+ .dsh-codex-empty{border:1px dashed var(--dsw-alias-border-l2);border-radius:7px;color:var(--dsw-alias-label-tertiary);font-size:12px;margin:12px 0 0;padding:16px;text-align:center}
634
+ .dsh-codex-timestamp{color:var(--dsw-alias-label-tertiary);font-size:11px;margin:10px 0 0;text-align:right}
635
+ .dsh-codex-skeleton{display:grid;gap:9px;padding-top:10px}
636
+ .dsh-codex-skeleton span{animation:dsh-codex-pulse 1.4s ease-in-out infinite;background:var(--dsw-alias-bg-layer-2);border-radius:5px;height:42px}
637
+ .dsh-codex-skeleton span:nth-child(2){animation-delay:.12s}.dsh-codex-skeleton span:nth-child(3){animation-delay:.24s}
638
+ .dsh-codex-sr{height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px;clip:rect(0 0 0 0);white-space:nowrap}
639
+ @keyframes dsh-codex-pulse{0%,100%{opacity:.55}50%{opacity:1}}
640
+ @media(max-width:560px){.dsh-codex-row{align-items:flex-start;flex-direction:column;gap:3px}.dsh-codex-value{text-align:left}.dsh-codex-actions{justify-content:flex-start}.dsh-codex-grouphead{align-items:flex-start;flex-direction:column;gap:0;padding:12px 0}.dsh-codex-meter-meta{align-items:flex-start;flex-direction:column;gap:2px}.dsh-codex-errorbar{align-items:flex-start;flex-direction:column}}
641
+ @media(prefers-reduced-motion:reduce){.dsh-codex-meter>span{transition:none}.dsh-codex-skeleton span{animation:none}}
642
+ `;
643
+ function installStyles() {
644
+ if (document.querySelector(`style[data-plugin-css="${STYLE_ID}"]`) !== null) return () => void 0;
645
+ const element = document.createElement("style");
646
+ element.dataset.plugin = "@eddyskywalker/dsh-chatgpt-subscription";
647
+ element.dataset.pluginCss = STYLE_ID;
648
+ element.textContent = CSS;
649
+ document.head.appendChild(element);
650
+ return () => element.remove();
651
+ }
652
+ //#endregion
653
+ //#region src/client/index.tsx
654
+ const inject = ["slots", "locale"];
655
+ function apply(ctx) {
656
+ ctx.effect(() => ctx.locale.register(NS, dictionaries), "dsh-chatgpt-subscription: dictionaries");
657
+ ctx.effect(() => installStyles(), "dsh-chatgpt-subscription: styles");
658
+ ctx.slots.inject("settings.section", () => ctx.slots.register({
659
+ name: "settings.section",
660
+ id: "codex-subscription",
661
+ order: 45,
662
+ label: "Codex 订阅",
663
+ locale: NS
664
+ }, CodexSubscriptionSection));
665
+ }
666
+ //#endregion
667
+ exports.apply = apply;
668
+ exports.inject = inject;
669
+ return module.exports;
670
+ }
671
+ });
672
+
673
+ //# sourceMappingURL=client.js.map