@goodandready/dsh-subscriptions 0.4.18 → 0.4.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.js +248 -416
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -13,7 +13,6 @@ window.__ModuleLoader__.load({
|
|
|
13
13
|
const setT = (fn) => { t = fn }
|
|
14
14
|
|
|
15
15
|
const CSS =
|
|
16
|
-
// card — same structure as core PluginCard (bash, agent-loop, web-search)
|
|
17
16
|
'.dsub-card{list-style:none;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;transition:border-color .16s,background .16s}' +
|
|
18
17
|
'.dsub-card:hover{border-color:var(--dsw-alias-label-dimmed)}' +
|
|
19
18
|
'.dsub-cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}' +
|
|
@@ -24,38 +23,22 @@ window.__ModuleLoader__.load({
|
|
|
24
23
|
'.dsub-description{color:var(--dsw-alias-label-secondary);font-size:13px}' +
|
|
25
24
|
'.dsub-chev{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}' +
|
|
26
25
|
'.dsub-chevOpen{transform:rotate(180deg)}' +
|
|
27
|
-
'.dsub-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:
|
|
28
|
-
|
|
29
|
-
'.dsub-
|
|
30
|
-
'.dsub-
|
|
31
|
-
'.dsub-h{font-size:14px;font-weight:600;color:var(--dsw-alias-label-primary)}' +
|
|
26
|
+
'.dsub-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:12px}' +
|
|
27
|
+
'.dsub-block{margin-top:10px}' +
|
|
28
|
+
'.dsub-row{display:flex;align-items:center;gap:8px;margin-top:8px}' +
|
|
29
|
+
'.dsub-h{font-size:13.5px;font-weight:600;color:var(--dsw-alias-label-primary)}' +
|
|
32
30
|
'.dsub-sub{font-size:12px;color:var(--dsw-alias-label-secondary)}' +
|
|
33
|
-
'.dsub-
|
|
34
|
-
'.dsub-
|
|
35
|
-
'.dsub-
|
|
36
|
-
'.dsub-field{display:flex;flex-direction:column;gap:6px;padding:12px 0}' +
|
|
37
|
-
'.dsub-input{height:34px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px}' +
|
|
38
|
-
'.dsub-mini{border:1px solid var(--dsw-alias-border-l2);background:transparent;color:var(--dsw-alias-label-primary);border-radius:6px;height:28px;padding:0 8px;cursor:pointer}' +
|
|
39
|
-
'.dsub-foot{border-top:1px solid var(--dsw-alias-border-l2);display:flex;justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px}' +
|
|
40
|
-
'.dsub-save{appearance:none;font:inherit;cursor:pointer;border:1px solid transparent;border-radius:8px;padding:5px 14px;font-size:13px;background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}' +
|
|
31
|
+
'.dsub-dim{font-size:11.5px;color:var(--dsw-alias-label-tertiary)}' +
|
|
32
|
+
'.dsub-mini{appearance:none;font:inherit;font-size:12px;cursor:pointer;border:1px solid var(--dsw-alias-border-l2);border-radius:6px;padding:3px 8px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);transition:all .15s}' +
|
|
33
|
+
'.dsub-mini:hover{background:var(--dsw-alias-bg-layer-1);border-color:var(--dsw-alias-label-dimmed)}' +
|
|
41
34
|
'.dsub-ok{font-size:12px;color:var(--dsw-alias-state-success-primary)}' +
|
|
42
35
|
'.dsub-bad{font-size:12px;color:var(--dsw-alias-state-error-primary)}' +
|
|
43
|
-
'.dsub-
|
|
44
|
-
'.dsub-
|
|
45
|
-
'.dsub-
|
|
46
|
-
'.dsub-
|
|
47
|
-
'.dsub-
|
|
48
|
-
'.dsub-
|
|
49
|
-
'.dsub-chipDanger{color:var(--dsw-alias-state-error-primary);border-color:currentColor}' +
|
|
50
|
-
'.dsub-chipDot{width:6px;height:6px;border-radius:999px;background:currentColor;flex:none}' +
|
|
51
|
-
'.dsub-chipActive{display:inline-flex;align-items:center;gap:6px;height:26px;padding:0 10px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2);font-size:12px;font-weight:600;line-height:1;white-space:nowrap;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-3)}' +
|
|
52
|
-
'.dsub-chipActiveWarn{color:var(--dsw-alias-state-warning-primary);border-color:currentColor}' +
|
|
53
|
-
'.dsub-chipActiveDanger{color:var(--dsw-alias-state-error-primary);border-color:currentColor}' +
|
|
54
|
-
'.dsub-chipActiveOk{color:var(--dsw-alias-state-success-primary);border-color:currentColor}' +
|
|
55
|
-
'.dsub-chipActiveBar{height:3px;width:24px;background:var(--dsw-alias-bg-layer-1);border-radius:2px;flex:none;overflow:hidden}' +
|
|
56
|
-
'.dsub-chipActiveFill{height:100%;border-radius:2px;background:currentColor}' +
|
|
57
|
-
|
|
58
|
-
'.dsub-link{background:none;border:none;color:var(--dsw-alias-label-secondary);cursor:pointer;font-size:12px;padding:0}' +
|
|
36
|
+
'.dsub-btn{appearance:none;font:inherit;cursor:pointer;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:5px 12px;font-size:12.5px;font-weight:500;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);transition:all .15s}' +
|
|
37
|
+
'.dsub-btn:hover{background:var(--dsw-alias-bg-layer-1);border-color:var(--dsw-alias-label-dimmed)}' +
|
|
38
|
+
'.dsub-inp{height:30px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);border-radius:6px;padding:0 8px;font-size:12px;min-width:180px}' +
|
|
39
|
+
'.dsub-inp:focus{outline:none;border-color:var(--dsw-alias-brand-primary)}' +
|
|
40
|
+
'.dsub-box{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:10px;margin-top:8px;background:var(--dsw-alias-bg-layer-2)}' +
|
|
41
|
+
'.dsub-cooldown{display:inline-block;padding:2px 6px;border-radius:4px;background:var(--dsw-alias-state-warning-primary);color:#000;font-size:11px;font-weight:600}' +
|
|
59
42
|
'.dsub-bar{position:relative;height:6px;border-radius:3px;background:var(--dsw-alias-bg-layer-1);overflow:hidden;flex:1;min-width:80px}' +
|
|
60
43
|
'.dsub-barFill{height:100%;border-radius:3px;background:var(--dsw-alias-state-success-primary);transition:width .2s}' +
|
|
61
44
|
'.dsub-barWarn .dsub-barFill{background:var(--dsw-alias-state-warning-primary)}' +
|
|
@@ -64,40 +47,53 @@ window.__ModuleLoader__.load({
|
|
|
64
47
|
'.dsub-manual{display:flex;flex-direction:column;gap:6px;margin-top:4px;padding-top:10px;border-top:1px solid var(--dsw-alias-border-l2)}' +
|
|
65
48
|
'.dsub-verify{font-size:12px;color:var(--dsw-alias-state-warning-primary)}' +
|
|
66
49
|
'.dsub-verify a{color:var(--dsw-alias-brand-primary)}' +
|
|
67
|
-
'.dsub-
|
|
68
|
-
'.dsub-
|
|
69
|
-
'.dsub-
|
|
70
|
-
'background:var(--dsw-alias-bg-layer-
|
|
71
|
-
'.dsub-
|
|
72
|
-
'.dsub-
|
|
73
|
-
'.dsub-
|
|
74
|
-
'.dsub-
|
|
75
|
-
'.dsub-
|
|
76
|
-
'.dsub-
|
|
77
|
-
'.dsub-
|
|
78
|
-
'
|
|
79
|
-
'
|
|
80
|
-
'.dsub-
|
|
81
|
-
'.dsub-
|
|
82
|
-
'.dsub-
|
|
83
|
-
'.dsub-
|
|
84
|
-
'.dsub-
|
|
85
|
-
'.dsub-
|
|
86
|
-
'.dsub-
|
|
87
|
-
'.dsub-
|
|
88
|
-
'.dsub-
|
|
89
|
-
'.dsub-
|
|
90
|
-
'.dsub-
|
|
91
|
-
'.dsub-
|
|
92
|
-
'.dsub-
|
|
93
|
-
'.dsub-
|
|
94
|
-
'.dsub-
|
|
95
|
-
'.dsub-
|
|
96
|
-
'.dsub-
|
|
97
|
-
'.dsub-
|
|
98
|
-
'.dsub-
|
|
99
|
-
'.dsub-
|
|
100
|
-
'.dsub-
|
|
50
|
+
'.dsub-pill{display:inline-flex;align-items:center;gap:7px;height:28px;padding:0 11px;border:1px solid var(--dsw-alias-border-l2);border-radius:999px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font-size:12px;font-weight:600;cursor:pointer;box-shadow:0 1px 3px rgba(0,0,0,.15),inset 0 1px 0 rgba(255,255,255,.06);transition:all .16s ease;user-select:none}' +
|
|
51
|
+
'.dsub-pill:hover{background:var(--dsw-alias-bg-layer-3);border-color:var(--dsw-alias-label-tertiary);transform:translateY(-1px);box-shadow:0 3px 8px rgba(0,0,0,.25),inset 0 1px 0 rgba(255,255,255,.1)}' +
|
|
52
|
+
'.dsub-pill:active{transform:translateY(0)}' +
|
|
53
|
+
'.dsub-pillTag{padding:1px 5px;border-radius:4px;font-size:10.5px;font-weight:700;letter-spacing:.02em;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary)}' +
|
|
54
|
+
'.dsub-led{width:7px;height:7px;border-radius:50%;flex:none;transition:all .2s ease}' +
|
|
55
|
+
'.dsub-ledOk{background:var(--dsw-alias-state-success-primary,#10b981);box-shadow:0 0 6px rgba(16,185,129,.7)}' +
|
|
56
|
+
'.dsub-ledWarn{background:var(--dsw-alias-state-warning-primary,#f59e0b);box-shadow:0 0 6px rgba(245,158,11,.7)}' +
|
|
57
|
+
'.dsub-ledBad{background:var(--dsw-alias-state-error-primary,#ef4444);box-shadow:0 0 6px rgba(239,68,68,.7)}' +
|
|
58
|
+
'.dsub-ledOff{background:var(--dsw-alias-label-tertiary,#6b7280)}' +
|
|
59
|
+
'.dsub-modalWrap{position:fixed;inset:0;z-index:10020;display:grid;place-items:center;background:rgba(0,0,0,.65);backdrop-filter:blur(10px);animation:dsubFadeIn .16s ease-out}' +
|
|
60
|
+
'.dsub-modal{width:min(520px,94vw);max-height:min(85vh,720px);overflow-y:auto;border:1px solid var(--dsw-alias-border-l1);border-radius:16px;background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary);box-shadow:0 24px 64px rgba(0,0,0,.6),0 0 0 1px rgba(255,255,255,.07);padding:20px;animation:dsubScaleIn .18s cubic-bezier(.16,1,.3,1)}' +
|
|
61
|
+
'@keyframes dsubFadeIn{from{opacity:0}to{opacity:1}}' +
|
|
62
|
+
'@keyframes dsubScaleIn{from{opacity:0;transform:scale(.96)}to{opacity:1;transform:scale(1)}}' +
|
|
63
|
+
'.dsub-modalHead{display:flex;align-items:center;justify-content:space-between;gap:12px;padding-bottom:14px;border-bottom:1px solid var(--dsw-alias-border-l2)}' +
|
|
64
|
+
'.dsub-modalTitleWrap{display:flex;align-items:center;gap:10px}' +
|
|
65
|
+
'.dsub-modalIcon{width:30px;height:30px;border-radius:8px;background:var(--dsw-alias-bg-layer-1);display:grid;place-items:center;color:var(--dsw-alias-brand-primary);font-size:14px}' +
|
|
66
|
+
'.dsub-modalBadge{font-size:11px;font-weight:600;padding:2px 7px;border-radius:10px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary)}' +
|
|
67
|
+
'.dsub-heroCard{margin-top:14px;padding:14px 16px;border-radius:12px;background:linear-gradient(135deg,var(--dsw-alias-bg-layer-2) 0%,var(--dsw-alias-bg-layer-3) 100%);border:1px solid var(--dsw-alias-border-l1);box-shadow:inset 0 1px 0 rgba(255,255,255,.05)}' +
|
|
68
|
+
'.dsub-heroHead{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}' +
|
|
69
|
+
'.dsub-heroTitle{font-size:14px;font-weight:700;display:flex;align-items:center;gap:6px}' +
|
|
70
|
+
'.dsub-heroModel{font-size:11.5px;color:var(--dsw-alias-label-secondary);font-family:ui-monospace,monospace}' +
|
|
71
|
+
'.dsub-heroBars{display:flex;flex-direction:column;gap:8px;margin-top:10px}' +
|
|
72
|
+
'.dsub-barLabelRow{display:flex;justify-content:space-between;font-size:11.5px;color:var(--dsw-alias-label-secondary);margin-bottom:3px}' +
|
|
73
|
+
'.dsub-barTrack{height:6px;border-radius:3px;background:var(--dsw-alias-bg-layer-1);overflow:hidden}' +
|
|
74
|
+
'.dsub-barFillGrad{height:100%;border-radius:3px;transition:width .3s ease}' +
|
|
75
|
+
'.dsub-poolTitle{margin-top:16px;margin-bottom:8px;font-size:11.5px;font-weight:700;color:var(--dsw-alias-label-secondary);text-transform:uppercase;letter-spacing:.05em}' +
|
|
76
|
+
'.dsub-accountCard{display:flex;align-items:center;gap:12px;padding:10px 14px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-2);margin-top:6px;transition:border-color .15s,background .15s}' +
|
|
77
|
+
'.dsub-accountCard:hover{background:var(--dsw-alias-bg-layer-3);border-color:var(--dsw-alias-label-tertiary)}' +
|
|
78
|
+
'.dsub-brandBadge{width:32px;height:32px;border-radius:8px;display:grid;place-items:center;font-size:12px;font-weight:700;flex-shrink:0}' +
|
|
79
|
+
'.dsub-brandCodex{background:rgba(16,185,129,.15);color:#10b981;border:1px solid rgba(16,185,129,.3)}' +
|
|
80
|
+
'.dsub-brandClaude{background:rgba(245,158,11,.15);color:#f59e0b;border:1px solid rgba(245,158,11,.3)}' +
|
|
81
|
+
'.dsub-brandGrok{background:rgba(168,85,247,.15);color:#c084fc;border:1px solid rgba(168,85,247,.3)}' +
|
|
82
|
+
'.dsub-brandAgy{background:rgba(59,130,246,.15);color:#60a5fa;border:1px solid rgba(59,130,246,.3)}' +
|
|
83
|
+
'.dsub-brandOllama{background:rgba(107,114,128,.15);color:#9ca3af;border:1px solid rgba(107,114,128,.3)}' +
|
|
84
|
+
'.dsub-accInfo{flex:1;min-width:0;display:flex;flex-direction:column;gap:3px}' +
|
|
85
|
+
'.dsub-accNameRow{display:flex;align-items:center;justify-content:space-between;gap:8px}' +
|
|
86
|
+
'.dsub-accName{font-size:13px;font-weight:600}' +
|
|
87
|
+
'.dsub-accStatusTag{font-size:10.5px;font-weight:600;padding:1px 6px;border-radius:4px}' +
|
|
88
|
+
'.dsub-statusOk{background:rgba(16,185,129,.15);color:#34d399}' +
|
|
89
|
+
'.dsub-statusWarn{background:rgba(245,158,11,.15);color:#fbbf24}' +
|
|
90
|
+
'.dsub-statusBad{background:rgba(239,68,68,.15);color:#f87171}' +
|
|
91
|
+
'.dsub-statusOff{background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-tertiary)}' +
|
|
92
|
+
'.dsub-modalFoot{display:flex;align-items:center;justify-content:space-between;margin-top:16px;padding-top:12px;border-top:1px solid var(--dsw-alias-border-l2)}' +
|
|
93
|
+
'.dsub-btnPrimary{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;border-radius:8px;font-size:12px;font-weight:600;background:var(--dsw-alias-brand-primary,#3b82f6);color:#fff;border:0;cursor:pointer;transition:opacity .15s}' +
|
|
94
|
+
'.dsub-btnPrimary:hover{opacity:.9}' +
|
|
95
|
+
'.dsub-btnSec{display:inline-flex;align-items:center;gap:5px;padding:5px 10px;border-radius:7px;font-size:11.5px;font-weight:500;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;text-decoration:none;transition:all .15s}' +
|
|
96
|
+
'.dsub-btnSec:hover{background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-tertiary)}' +
|
|
101
97
|
'.dsub-cq{display:inline-flex;align-items:center;gap:6px;font-size:12px;color:var(--dsw-alias-label-secondary);padding:0 6px}' +
|
|
102
98
|
'.dsub-cqBar{position:relative;width:40px;height:5px;border-radius:3px;background:var(--dsw-alias-bg-layer-1);overflow:hidden}' +
|
|
103
99
|
'.dsub-cqBarFill{height:100%;border-radius:3px;background:var(--dsw-alias-state-success-primary)}' +
|
|
@@ -152,7 +148,15 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
152
148
|
'diagIssues': 'Open issue tracker',
|
|
153
149
|
'diagHint': 'The report has no tokens, emails, proxy addresses or personal data.',
|
|
154
150
|
'subsPill': 'SUBS',
|
|
155
|
-
'subsModalTitle': 'Subscription
|
|
151
|
+
'subsModalTitle': 'Subscription Hub',
|
|
152
|
+
'subsActiveHero': 'Active Subscription',
|
|
153
|
+
'subsPoolTitle': 'Provider Accounts',
|
|
154
|
+
'subsRefresh': 'Refresh',
|
|
155
|
+
'subsOpenSettings': 'All Settings →',
|
|
156
|
+
'subsFastMode': 'Fast Mode 1.5x',
|
|
157
|
+
'subsHealthy': 'Healthy',
|
|
158
|
+
'subsFree': 'free',
|
|
159
|
+
'subsUsed': 'used',
|
|
156
160
|
'subsModalClose': 'Close',
|
|
157
161
|
'subsLogged': 'connected',
|
|
158
162
|
'subsNotLogged': 'not connected',
|
|
@@ -253,6 +257,14 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
253
257
|
'diagHint': 'В отчёте нет токенов, email, адресов прокси и персональных данных.',
|
|
254
258
|
'subsPill': 'SUBS',
|
|
255
259
|
'subsModalTitle': 'Консоль подписок',
|
|
260
|
+
'subsActiveHero': 'Активная подписка',
|
|
261
|
+
'subsPoolTitle': 'Пул аккаунтов',
|
|
262
|
+
'subsRefresh': 'Обновить',
|
|
263
|
+
'subsOpenSettings': 'Все настройки →',
|
|
264
|
+
'subsFastMode': 'Режим Fast 1.5x',
|
|
265
|
+
'subsHealthy': 'Исправен',
|
|
266
|
+
'subsFree': 'свободно',
|
|
267
|
+
'subsUsed': 'израсходовано',
|
|
256
268
|
'subsModalClose': 'Закрыть',
|
|
257
269
|
'subsLogged': 'подключен',
|
|
258
270
|
'subsNotLogged': 'не подключен',
|
|
@@ -346,23 +358,7 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
346
358
|
}
|
|
347
359
|
const Chevron = ChevronIcon || FallbackChevron
|
|
348
360
|
|
|
349
|
-
|
|
350
|
-
const t = (props && props.t) || ((key) => key)
|
|
351
|
-
const [open, setOpen] = React.useState(false)
|
|
352
|
-
return React.createElement('div', { className: 'dsub-block' },
|
|
353
|
-
React.createElement('div', { className: 'dsub-row' },
|
|
354
|
-
React.createElement('span', { className: 'dsub-h' }, t('title')),
|
|
355
|
-
React.createElement('span', { className: 'dsub-sub' }, t('cardIntro')),
|
|
356
|
-
React.createElement('span', { style: { flex: 1 } }),
|
|
357
|
-
React.createElement('button', {
|
|
358
|
-
type: 'button',
|
|
359
|
-
className: 'dsub-mini',
|
|
360
|
-
onClick: () => setOpen(!open),
|
|
361
|
-
}, open ? t('hide') : t('show')),
|
|
362
|
-
),
|
|
363
|
-
open ? React.createElement(SubsSection, props) : null,
|
|
364
|
-
)
|
|
365
|
-
}
|
|
361
|
+
|
|
366
362
|
|
|
367
363
|
function SubsSection(props) {
|
|
368
364
|
// Переводчик приходит от слота, потому что в его записи указан locale.
|
|
@@ -1118,59 +1114,174 @@ function PluginCard(props) {
|
|
|
1118
1114
|
|
|
1119
1115
|
// #83: пилл SUBS(N) в шапке сессии со светодиодом здоровья пула и
|
|
1120
1116
|
// модальной консолью аккаунтов (закрытие: крестик, Escape, клик мимо).
|
|
1117
|
+
function brandBadge(prov) {
|
|
1118
|
+
const p = String(prov || '').toLowerCase()
|
|
1119
|
+
if (p.includes('codex') || p.includes('chatgpt') || p.includes('openai')) return { label: 'OpenAI', cls: 'dsub-brandCodex', icon: '⚡' }
|
|
1120
|
+
if (p.includes('claude') || p.includes('anthropic')) return { label: 'Claude', cls: 'dsub-brandClaude', icon: '✳' }
|
|
1121
|
+
if (p.includes('grok') || p.includes('xai')) return { label: 'Grok', cls: 'dsub-brandGrok', icon: '✦' }
|
|
1122
|
+
if (p.includes('antigravity') || p.includes('google') || p.includes('gemini')) return { label: 'AGY', cls: 'dsub-brandAgy', icon: '◆' }
|
|
1123
|
+
if (p.includes('ollama')) return { label: 'Ollama', cls: 'dsub-brandOllama', icon: '🦙' }
|
|
1124
|
+
return { label: (p.charAt(0).toUpperCase() || 'P'), cls: 'dsub-brandCodex', icon: '●' }
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1121
1127
|
function SubsPill(props) {
|
|
1122
1128
|
const t = (props && props.t) || ((k) => k)
|
|
1123
|
-
const [state, setState] = React.useState({ logged: [], usage: 0, accounts: [], open: false })
|
|
1129
|
+
const [state, setState] = React.useState({ logged: [], usage: 0, accounts: [], open: false, active: null, loading: false })
|
|
1130
|
+
|
|
1131
|
+
const pull = () => {
|
|
1132
|
+
setState((s) => Object.assign({}, s, { loading: true }))
|
|
1133
|
+
refreshLoggedIn().then((r) => {
|
|
1134
|
+
const logged = r.logged || []
|
|
1135
|
+
fetch('/dsh-subscriptions/config', { cache: 'no-store' })
|
|
1136
|
+
.then((res) => res.json())
|
|
1137
|
+
.then((cfg) => {
|
|
1138
|
+
setState((s) => Object.assign({}, s, { logged, usage: r.usage, accounts: (cfg && cfg.accounts) || [], active: r.active, loading: false }))
|
|
1139
|
+
})
|
|
1140
|
+
.catch(() => {
|
|
1141
|
+
setState((s) => Object.assign({}, s, { logged, usage: r.usage, accounts: [], active: r.active, loading: false }))
|
|
1142
|
+
})
|
|
1143
|
+
}).catch(() => {
|
|
1144
|
+
setState((s) => Object.assign({}, s, { loading: false }))
|
|
1145
|
+
})
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1124
1148
|
React.useEffect(() => {
|
|
1125
|
-
let alive = true
|
|
1126
|
-
const pull = () => {
|
|
1127
|
-
refreshLoggedIn().then((r) => {
|
|
1128
|
-
if (!alive) return
|
|
1129
|
-
const logged = r.logged || []
|
|
1130
|
-
fetch('/dsh-subscriptions/config', { cache: 'no-store' })
|
|
1131
|
-
.then((res) => res.json())
|
|
1132
|
-
.then((cfg) => {
|
|
1133
|
-
if (!alive) return
|
|
1134
|
-
setState({ logged, usage: r.usage, accounts: (cfg && cfg.accounts) || [], open: false })
|
|
1135
|
-
})
|
|
1136
|
-
.catch(() => { if (alive) setState({ logged, usage: r.usage, accounts: [], open: false }) })
|
|
1137
|
-
}).catch(() => {})
|
|
1138
|
-
}
|
|
1139
1149
|
pull()
|
|
1140
|
-
const id = setInterval(pull,
|
|
1141
|
-
return () =>
|
|
1150
|
+
const id = setInterval(pull, 25 * 1000)
|
|
1151
|
+
return () => clearInterval(id)
|
|
1142
1152
|
}, [])
|
|
1153
|
+
|
|
1143
1154
|
React.useEffect(() => {
|
|
1144
1155
|
if (!state.open) return undefined
|
|
1145
1156
|
const onKey = (e) => { if (e.key === 'Escape') setState((s) => Object.assign({}, s, { open: false })) }
|
|
1146
1157
|
document.addEventListener('keydown', onKey)
|
|
1147
1158
|
return () => document.removeEventListener('keydown', onKey)
|
|
1148
1159
|
}, [state.open])
|
|
1160
|
+
|
|
1149
1161
|
const n = state.logged.length
|
|
1162
|
+
const total = state.accounts.length || n
|
|
1150
1163
|
const led = n === 0 ? 'dsub-ledOff' : (state.usage >= 90 ? 'dsub-ledBad' : (state.usage >= 50 ? 'dsub-ledWarn' : 'dsub-ledOk'))
|
|
1151
1164
|
const now = Date.now()
|
|
1165
|
+
|
|
1166
|
+
// Header Pill Label
|
|
1167
|
+
let pillText = t('subsPill') + ' (' + n + ')'
|
|
1168
|
+
if (state.active) {
|
|
1169
|
+
const a = state.active
|
|
1170
|
+
const wins = Array.isArray(a.windows) ? a.windows : []
|
|
1171
|
+
const winStr = wins.map((w) => (w.label || w.id) + ' ' + Math.round(w.usedPercent) + '%').join(' ')
|
|
1172
|
+
const provName = a.provider ? (a.provider.charAt(0).toUpperCase() + a.provider.slice(1)) : ''
|
|
1173
|
+
pillText = (a.provider === 'codex' && a.fastMode ? '⚡ ' : '') + provName + (winStr ? ' · ' + winStr : (a.usagePercent != null ? ' · ' + Math.round(a.usagePercent) + '%' : ''))
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
// Active Hero Card Renderer
|
|
1177
|
+
const renderActiveHero = () => {
|
|
1178
|
+
if (!state.active) return null
|
|
1179
|
+
const a = state.active
|
|
1180
|
+
const b = brandBadge(a.provider)
|
|
1181
|
+
const wins = Array.isArray(a.windows) ? a.windows : []
|
|
1182
|
+
const primaryWin = wins.find((w) => (w.label || w.id) === '5h') || wins[0]
|
|
1183
|
+
const secWin = wins.find((w) => (w.label || w.id) === '7d') || wins[1]
|
|
1184
|
+
const primPct = primaryWin && primaryWin.usedPercent != null ? Math.round(primaryWin.usedPercent) : (a.usagePercent != null ? Math.round(a.usagePercent) : 0)
|
|
1185
|
+
const secPct = secWin && secWin.usedPercent != null ? Math.round(secWin.usedPercent) : null
|
|
1186
|
+
|
|
1187
|
+
return React.createElement('div', { className: 'dsub-heroCard' },
|
|
1188
|
+
React.createElement('div', { className: 'dsub-heroHead' },
|
|
1189
|
+
React.createElement('div', { className: 'dsub-heroTitle' },
|
|
1190
|
+
React.createElement('span', { className: 'dsub-brandBadge ' + b.cls, style: { width: 24, height: 24, fontSize: 11 } }, b.icon),
|
|
1191
|
+
React.createElement('span', null, (a.provider ? a.provider.toUpperCase() : 'LLM') + (a.index ? ' #' + a.index : '')),
|
|
1192
|
+
a.fastMode ? React.createElement('span', { className: 'dsub-pillTag', style: { background: 'rgba(245,158,11,.15)', color: '#fbbf24' } }, '⚡ FAST 1.5x') : null,
|
|
1193
|
+
),
|
|
1194
|
+
React.createElement('span', { className: 'dsub-heroModel' }, a.model || 'active model'),
|
|
1195
|
+
),
|
|
1196
|
+
React.createElement('div', { className: 'dsub-heroBars' },
|
|
1197
|
+
React.createElement('div', null,
|
|
1198
|
+
React.createElement('div', { className: 'dsub-barLabelRow' },
|
|
1199
|
+
React.createElement('span', { style: { fontWeight: 600 } }, (primaryWin && primaryWin.label) ? primaryWin.label + ' window' : t('subs5hWindow')),
|
|
1200
|
+
React.createElement('span', { style: { fontVariantNumeric: 'tabular-nums', fontWeight: 600 } }, primPct + '% ' + t('subsUsed') + ' (' + (100 - primPct) + '% ' + t('subsFree') + ')'),
|
|
1201
|
+
),
|
|
1202
|
+
React.createElement('div', { className: 'dsub-barTrack' },
|
|
1203
|
+
React.createElement('div', {
|
|
1204
|
+
className: 'dsub-barFillGrad',
|
|
1205
|
+
style: {
|
|
1206
|
+
width: Math.min(100, Math.max(0, primPct)) + '%',
|
|
1207
|
+
background: primPct >= 90 ? 'var(--dsw-alias-state-error-primary,#ef4444)' : (primPct >= 50 ? 'var(--dsw-alias-state-warning-primary,#f59e0b)' : 'var(--dsw-alias-state-success-primary,#10b981)'),
|
|
1208
|
+
},
|
|
1209
|
+
}),
|
|
1210
|
+
),
|
|
1211
|
+
),
|
|
1212
|
+
secWin ? React.createElement('div', null,
|
|
1213
|
+
React.createElement('div', { className: 'dsub-barLabelRow' },
|
|
1214
|
+
React.createElement('span', null, (secWin.label || '7d') + ' window'),
|
|
1215
|
+
React.createElement('span', { style: { fontVariantNumeric: 'tabular-nums' } }, secPct + '%'),
|
|
1216
|
+
),
|
|
1217
|
+
React.createElement('div', { className: 'dsub-barTrack' },
|
|
1218
|
+
React.createElement('div', {
|
|
1219
|
+
className: 'dsub-barFillGrad',
|
|
1220
|
+
style: {
|
|
1221
|
+
width: Math.min(100, Math.max(0, secPct)) + '%',
|
|
1222
|
+
background: secPct >= 90 ? '#ef4444' : (secPct >= 50 ? '#f59e0b' : '#3b82f6'),
|
|
1223
|
+
},
|
|
1224
|
+
}),
|
|
1225
|
+
),
|
|
1226
|
+
) : null,
|
|
1227
|
+
),
|
|
1228
|
+
)
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
// Accounts list
|
|
1152
1232
|
const rows = state.accounts.map((a, i) => {
|
|
1153
1233
|
const isLogged = state.logged.includes(a.provider)
|
|
1154
1234
|
const cooled = a.cooldownUntil && a.cooldownUntil > now
|
|
1155
1235
|
const pct = a.usagePercent != null ? Math.round(a.usagePercent) : null
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1236
|
+
const b = brandBadge(a.provider)
|
|
1237
|
+
const isActiveThis = state.active && state.active.provider === a.provider && state.active.index === a.index
|
|
1238
|
+
|
|
1239
|
+
let statusClass = 'dsub-statusOff'
|
|
1240
|
+
let statusText = t('subsNotLogged')
|
|
1241
|
+
if (cooled) {
|
|
1242
|
+
statusClass = 'dsub-statusWarn'
|
|
1243
|
+
statusText = t('subsCooldown')
|
|
1244
|
+
} else if (isLogged) {
|
|
1245
|
+
if (pct >= 90) { statusClass = 'dsub-statusBad'; statusText = '90%+ ' + t('subsUsed') }
|
|
1246
|
+
else if (pct >= 50) { statusClass = 'dsub-statusWarn'; statusText = pct + '% ' + t('subsUsed') }
|
|
1247
|
+
else { statusClass = 'dsub-statusOk'; statusText = (pct != null ? pct + '% · ' : '') + t('subsLogged') }
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
return React.createElement('div', { className: 'dsub-accountCard', key: i },
|
|
1251
|
+
React.createElement('div', { className: 'dsub-brandBadge ' + b.cls }, b.icon),
|
|
1252
|
+
React.createElement('div', { className: 'dsub-accInfo' },
|
|
1253
|
+
React.createElement('div', { className: 'dsub-accNameRow' },
|
|
1254
|
+
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 6 } },
|
|
1255
|
+
React.createElement('span', { className: 'dsub-accName' }, a.provider.toUpperCase() + (a.index ? ' #' + a.index : '')),
|
|
1256
|
+
isActiveThis ? React.createElement('span', { className: 'dsub-pillTag', style: { background: 'rgba(16,185,129,.15)', color: '#34d399' } }, 'ACTIVE') : null,
|
|
1257
|
+
a.proxy ? React.createElement('span', { className: 'dsub-dim', title: a.proxy }, '🌐 proxy') : null,
|
|
1258
|
+
),
|
|
1259
|
+
React.createElement('span', { className: 'dsub-accStatusTag ' + statusClass }, statusText),
|
|
1260
|
+
),
|
|
1261
|
+
(isLogged && pct != null) ? React.createElement('div', { className: 'dsub-barTrack', style: { marginTop: 4, height: 4 } },
|
|
1262
|
+
React.createElement('div', {
|
|
1263
|
+
className: 'dsub-barFillGrad',
|
|
1264
|
+
style: {
|
|
1265
|
+
width: Math.min(100, Math.max(0, pct)) + '%',
|
|
1266
|
+
background: pct >= 90 ? '#ef4444' : (pct >= 50 ? '#f59e0b' : '#10b981'),
|
|
1267
|
+
},
|
|
1268
|
+
}),
|
|
1269
|
+
) : null,
|
|
1164
1270
|
),
|
|
1165
1271
|
)
|
|
1166
1272
|
})
|
|
1273
|
+
|
|
1167
1274
|
return React.createElement(React.Fragment, null,
|
|
1168
1275
|
React.createElement('button', {
|
|
1169
|
-
type: 'button',
|
|
1276
|
+
type: 'button',
|
|
1277
|
+
className: 'dsub-pill',
|
|
1278
|
+
'aria-expanded': state.open ? 'true' : 'false',
|
|
1279
|
+
title: t('subsPill') + ': ' + (n > 0 ? (n + ' ' + t('connected')) : t('notConnected')),
|
|
1170
1280
|
onClick: () => setState((s) => Object.assign({}, s, { open: !s.open })),
|
|
1171
1281
|
},
|
|
1172
1282
|
React.createElement('span', { className: 'dsub-led ' + led }),
|
|
1173
|
-
|
|
1283
|
+
React.createElement('span', null, pillText),
|
|
1284
|
+
React.createElement('span', { style: { fontSize: 9, opacity: 0.6, marginLeft: 2 } }, '▼'),
|
|
1174
1285
|
),
|
|
1175
1286
|
state.open ? React.createElement('div', {
|
|
1176
1287
|
className: 'dsub-modalWrap',
|
|
@@ -1178,19 +1289,38 @@ function PluginCard(props) {
|
|
|
1178
1289
|
},
|
|
1179
1290
|
React.createElement('div', { className: 'dsub-modal', role: 'dialog', 'aria-label': t('subsModalTitle') },
|
|
1180
1291
|
React.createElement('div', { className: 'dsub-modalHead' },
|
|
1181
|
-
React.createElement('
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1292
|
+
React.createElement('div', { className: 'dsub-modalTitleWrap' },
|
|
1293
|
+
React.createElement('div', { className: 'dsub-modalIcon' }, '⚡'),
|
|
1294
|
+
React.createElement('div', null,
|
|
1295
|
+
React.createElement('div', { style: { fontWeight: 700, fontSize: 15 } }, t('subsModalTitle')),
|
|
1296
|
+
React.createElement('div', { className: 'dsub-dim' }, n + ' of ' + total + ' ' + t('connected')),
|
|
1297
|
+
),
|
|
1298
|
+
),
|
|
1299
|
+
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 6 } },
|
|
1300
|
+
React.createElement('button', {
|
|
1301
|
+
type: 'button',
|
|
1302
|
+
className: 'dsub-mini',
|
|
1303
|
+
title: t('subsRefresh'),
|
|
1304
|
+
onClick: () => pull(),
|
|
1305
|
+
}, state.loading ? '…' : '🔄'),
|
|
1306
|
+
React.createElement('button', {
|
|
1307
|
+
type: 'button',
|
|
1308
|
+
className: 'dsub-mini',
|
|
1309
|
+
onClick: () => setState((s) => Object.assign({}, s, { open: false })),
|
|
1310
|
+
}, '✕'),
|
|
1311
|
+
),
|
|
1186
1312
|
),
|
|
1187
|
-
|
|
1188
|
-
React.createElement('div', { className: 'dsub-
|
|
1313
|
+
renderActiveHero(),
|
|
1314
|
+
React.createElement('div', { className: 'dsub-poolTitle' }, t('subsPoolTitle')),
|
|
1315
|
+
rows.length ? rows : React.createElement('div', { className: 'dsub-sub', style: { padding: '12px 0' } }, t('subsNotLogged')),
|
|
1316
|
+
React.createElement('div', { className: 'dsub-modalFoot' },
|
|
1317
|
+
React.createElement('span', { className: 'dsub-dim' }, 'DSH Subscriptions'),
|
|
1189
1318
|
React.createElement('a', {
|
|
1190
|
-
className: 'dsub-
|
|
1319
|
+
className: 'dsub-btnSec',
|
|
1320
|
+
href: '#',
|
|
1321
|
+
onClick: (e) => {
|
|
1191
1322
|
e.preventDefault()
|
|
1192
1323
|
setState((s) => Object.assign({}, s, { open: false }))
|
|
1193
|
-
try { document.querySelector('[data-slot-key=\"settings.plugin.item\"],[data-dsub-open]') } catch {}
|
|
1194
1324
|
try {
|
|
1195
1325
|
const btn = Array.from(document.querySelectorAll('button')).find((b) => (b.textContent || '').includes(t('title')))
|
|
1196
1326
|
if (btn) btn.click()
|
|
@@ -1203,162 +1333,6 @@ function PluginCard(props) {
|
|
|
1203
1333
|
)
|
|
1204
1334
|
}
|
|
1205
1335
|
|
|
1206
|
-
// #82: перетаскиваемый HUD-бабл (shell.overlay) с кольцевой шкалой
|
|
1207
|
-
// остатка активной подписки, прилипанием к краям и frosted-панелью.
|
|
1208
|
-
const HUD_KEY = 'dsh-subscriptions.hud'
|
|
1209
|
-
const HUD_SIZE = 64
|
|
1210
|
-
|
|
1211
|
-
function hudClamp(pos) {
|
|
1212
|
-
const w = window.innerWidth, h = window.innerHeight
|
|
1213
|
-
return {
|
|
1214
|
-
left: Math.min(Math.max(8, pos.left), Math.max(8, w - HUD_SIZE - 8)),
|
|
1215
|
-
top: Math.min(Math.max(8, pos.top), Math.max(8, h - HUD_SIZE - 8)),
|
|
1216
|
-
}
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
|
-
function hudLoad() {
|
|
1220
|
-
try {
|
|
1221
|
-
const raw = JSON.parse(localStorage.getItem(HUD_KEY) || 'null')
|
|
1222
|
-
if (raw && typeof raw.left === 'number' && typeof raw.top === 'number') return hudClamp(raw)
|
|
1223
|
-
} catch {}
|
|
1224
|
-
return hudClamp({ left: window.innerWidth - HUD_SIZE - 24, top: window.innerHeight - HUD_SIZE - 120 })
|
|
1225
|
-
}
|
|
1226
|
-
|
|
1227
|
-
function hudDock(pos) {
|
|
1228
|
-
const w = window.innerWidth, h = window.innerHeight
|
|
1229
|
-
const dist = { left: pos.left, right: w - (pos.left + HUD_SIZE), top: pos.top, bottom: h - (pos.top + HUD_SIZE) }
|
|
1230
|
-
const TH = 24
|
|
1231
|
-
let side = null, best = Infinity
|
|
1232
|
-
for (const k of ['left', 'right', 'top', 'bottom']) {
|
|
1233
|
-
if (dist[k] <= TH && dist[k] < best) { best = dist[k]; side = k }
|
|
1234
|
-
}
|
|
1235
|
-
if (!side) return pos
|
|
1236
|
-
const next = { left: pos.left, top: pos.top }
|
|
1237
|
-
if (side === 'left') next.left = 0
|
|
1238
|
-
if (side === 'right') next.left = Math.max(0, w - HUD_SIZE)
|
|
1239
|
-
if (side === 'top') next.top = 0
|
|
1240
|
-
if (side === 'bottom') next.top = Math.max(0, h - HUD_SIZE)
|
|
1241
|
-
return next
|
|
1242
|
-
}
|
|
1243
|
-
|
|
1244
|
-
function UsageBubble(props) {
|
|
1245
|
-
const t = (props && props.t) || ((k) => k)
|
|
1246
|
-
const [pos, setPos] = React.useState(() => (typeof window === 'undefined' ? { left: 0, top: 0 } : hudLoad()))
|
|
1247
|
-
const [accounts, setAccounts] = React.useState([])
|
|
1248
|
-
const [active, setActive] = React.useState(null)
|
|
1249
|
-
const [dragging, setDragging] = React.useState(false)
|
|
1250
|
-
const [busy, setBusy] = React.useState(false)
|
|
1251
|
-
const drag = React.useRef(null)
|
|
1252
|
-
React.useEffect(() => {
|
|
1253
|
-
let alive = true
|
|
1254
|
-
const pull = () => {
|
|
1255
|
-
refreshLoggedIn().then((r) => {
|
|
1256
|
-
if (!alive) return
|
|
1257
|
-
setActive(r.active)
|
|
1258
|
-
fetch('/dsh-subscriptions/config', { cache: 'no-store' })
|
|
1259
|
-
.then((res) => res.json())
|
|
1260
|
-
.then((cfg) => { if (alive) setAccounts((cfg && cfg.accounts) || []) })
|
|
1261
|
-
.catch(() => {})
|
|
1262
|
-
}).catch(() => {})
|
|
1263
|
-
}
|
|
1264
|
-
pull()
|
|
1265
|
-
const id = setInterval(pull, 60 * 1000)
|
|
1266
|
-
return () => { alive = false; clearInterval(id) }
|
|
1267
|
-
}, [])
|
|
1268
|
-
const onPointerDown = (e) => {
|
|
1269
|
-
if (e.button !== 0) return
|
|
1270
|
-
e.preventDefault()
|
|
1271
|
-
e.currentTarget.setPointerCapture(e.pointerId)
|
|
1272
|
-
drag.current = { id: e.pointerId, sx: e.clientX, sy: e.clientY, left: pos.left, top: pos.top, moved: false }
|
|
1273
|
-
}
|
|
1274
|
-
const onPointerMove = (e) => {
|
|
1275
|
-
const d = drag.current
|
|
1276
|
-
if (!d || e.pointerId !== d.id) return
|
|
1277
|
-
if (Math.abs(e.clientX - d.sx) > 3 || Math.abs(e.clientY - d.sy) > 3) { d.moved = true; setDragging(true) }
|
|
1278
|
-
if (!d.moved) return
|
|
1279
|
-
setPos(hudClamp({ left: d.left + (e.clientX - d.sx), top: d.top + (e.clientY - d.sy) }))
|
|
1280
|
-
}
|
|
1281
|
-
const onPointerUp = (e) => {
|
|
1282
|
-
const d = drag.current
|
|
1283
|
-
if (!d || e.pointerId !== d.id) return
|
|
1284
|
-
drag.current = null
|
|
1285
|
-
setDragging(false)
|
|
1286
|
-
setPos((cur) => {
|
|
1287
|
-
const next = d.moved ? hudDock(hudClamp(cur)) : cur
|
|
1288
|
-
try { localStorage.setItem(HUD_KEY, JSON.stringify(next)) } catch {}
|
|
1289
|
-
return next
|
|
1290
|
-
})
|
|
1291
|
-
if (!d.moved) {
|
|
1292
|
-
setBusy(true)
|
|
1293
|
-
refreshLoggedIn().then((r) => {
|
|
1294
|
-
setActive(r.active)
|
|
1295
|
-
return fetch('/dsh-subscriptions/config', { cache: 'no-store' }).then((res) => res.json())
|
|
1296
|
-
}).then((cfg) => setAccounts((cfg && cfg.accounts) || [])).catch(() => {}).finally(() => setBusy(false))
|
|
1297
|
-
}
|
|
1298
|
-
}
|
|
1299
|
-
// Остаток активной подписки: 100 - usagePercent.
|
|
1300
|
-
const remaining = active && active.usagePercent != null ? Math.max(0, 100 - active.usagePercent) : null
|
|
1301
|
-
const tone = remaining == null ? 'var(--dsw-alias-label-tertiary)'
|
|
1302
|
-
: (remaining < 10 ? 'var(--dsw-alias-state-error-primary)'
|
|
1303
|
-
: (remaining < 30 ? 'var(--dsw-alias-state-warning-primary)' : 'var(--dsw-alias-state-success-primary)'))
|
|
1304
|
-
const R = 26
|
|
1305
|
-
const CIRC = 2 * Math.PI * R
|
|
1306
|
-
const offset = CIRC * (1 - (remaining == null ? 0 : Math.min(100, remaining)) / 100)
|
|
1307
|
-
const rows = accounts.map((a, i) => {
|
|
1308
|
-
const pct = a.usagePercent != null ? Math.round(a.usagePercent) : null
|
|
1309
|
-
return React.createElement('div', { className: 'dsub-hudRow', key: i },
|
|
1310
|
-
React.createElement('span', { className: 'dsub-hudName' }, a.provider + (a.index ? ' #' + a.index : '')),
|
|
1311
|
-
React.createElement('span', { className: 'dsub-bar' },
|
|
1312
|
-
React.createElement('span', { className: 'dsub-barFill', style: { width: (pct == null ? 0 : Math.min(100, pct)) + '%' } })),
|
|
1313
|
-
React.createElement('span', { className: 'dsub-hudPct' }, pct == null ? '—' : pct + '%'),
|
|
1314
|
-
)
|
|
1315
|
-
})
|
|
1316
|
-
if (!createPortal) return null
|
|
1317
|
-
return createPortal(React.createElement('div', {
|
|
1318
|
-
className: 'dsub-hud',
|
|
1319
|
-
'data-dragging': dragging ? 'true' : undefined,
|
|
1320
|
-
style: { left: pos.left + 'px', top: pos.top + 'px' },
|
|
1321
|
-
},
|
|
1322
|
-
React.createElement('button', {
|
|
1323
|
-
type: 'button', className: 'dsub-hudBtn', 'aria-label': t('hudTitle'),
|
|
1324
|
-
style: { '--dsub-tone': tone },
|
|
1325
|
-
onPointerDown, onPointerMove, onPointerUp, onPointerCancel: onPointerUp,
|
|
1326
|
-
},
|
|
1327
|
-
React.createElement('svg', { className: 'dsub-hudRing', viewBox: '0 0 56 56', 'aria-hidden': 'true' },
|
|
1328
|
-
React.createElement('circle', { className: 'dsub-hudTrack', cx: 28, cy: 28, r: R }),
|
|
1329
|
-
React.createElement('circle', {
|
|
1330
|
-
className: 'dsub-hudVal', cx: 28, cy: 28, r: R,
|
|
1331
|
-
strokeDasharray: String(CIRC), strokeDashoffset: String(offset),
|
|
1332
|
-
}),
|
|
1333
|
-
),
|
|
1334
|
-
React.createElement('span', { className: 'dsub-hudCore' },
|
|
1335
|
-
remaining == null ? '—' : (busy ? '\u2026' : Math.round(remaining) + '%'),
|
|
1336
|
-
),
|
|
1337
|
-
),
|
|
1338
|
-
React.createElement('div', { className: 'dsub-hudPanel' },
|
|
1339
|
-
React.createElement('div', { className: 'dsub-hudHead' }, t('hudTitle')),
|
|
1340
|
-
rows.length ? rows : React.createElement('div', { className: 'dsub-sub' }, '\u2014'),
|
|
1341
|
-
React.createElement('div', { className: 'dsub-hudHint' }, t('hudHint')),
|
|
1342
|
-
),
|
|
1343
|
-
), document.body)
|
|
1344
|
-
}
|
|
1345
|
-
|
|
1346
|
-
function registerUsageBubble(ctx) {
|
|
1347
|
-
ctx.effect(() => {
|
|
1348
|
-
ctx.slots.inject('shell.overlay', () =>
|
|
1349
|
-
ctx.slots.register(
|
|
1350
|
-
{
|
|
1351
|
-
name: 'shell.overlay',
|
|
1352
|
-
id: 'dsh-subscriptions-usage-bubble',
|
|
1353
|
-
order: 40,
|
|
1354
|
-
locale: NS,
|
|
1355
|
-
},
|
|
1356
|
-
(props) => React.createElement(UsageBubble, { t }),
|
|
1357
|
-
),
|
|
1358
|
-
)
|
|
1359
|
-
}, 'dsh-subscriptions: usage bubble')
|
|
1360
|
-
}
|
|
1361
|
-
|
|
1362
1336
|
function registerSubsPill(ctx) {
|
|
1363
1337
|
ctx.effect(() => {
|
|
1364
1338
|
ctx.slots.inject('conversation.session.header.actions', () =>
|
|
@@ -1391,145 +1365,6 @@ function PluginCard(props) {
|
|
|
1391
1365
|
}, 'dsh-subscriptions: composer quota')
|
|
1392
1366
|
}
|
|
1393
1367
|
|
|
1394
|
-
// #52: компактный чип в шапке сессии со статусом подключённых подписок.
|
|
1395
|
-
function HeaderChip(props) {
|
|
1396
|
-
const [state, setState] = React.useState({ loading: true, count: 0, usage: 0, expiry: null, active: null })
|
|
1397
|
-
const t = (props && props.t) || ((k) => k)
|
|
1398
|
-
React.useEffect(() => {
|
|
1399
|
-
const pull = () => {
|
|
1400
|
-
refreshLoggedIn().then((r) => {
|
|
1401
|
-
// #67: ближайшее окончание подписки в пределах expiryNotifyDays.
|
|
1402
|
-
let expiry = null
|
|
1403
|
-
const now = Date.now()
|
|
1404
|
-
const days = r.expiryNotifyDays || 7
|
|
1405
|
-
for (const p of r.logged) {
|
|
1406
|
-
const at = r.expiresAt[p]
|
|
1407
|
-
if (!at) continue
|
|
1408
|
-
const msLeft = at - now
|
|
1409
|
-
if (msLeft > 0 && msLeft <= days * 24 * 60 * 60 * 1000) {
|
|
1410
|
-
if (!expiry || at < expiry.at) expiry = { at, label: r.labels[p] || p }
|
|
1411
|
-
}
|
|
1412
|
-
}
|
|
1413
|
-
setState({ loading: false, count: r.logged.length, usage: r.usage, expiry, active: r.active })
|
|
1414
|
-
}).catch(() => setState({ loading: false, count: 0, usage: 0, expiry: null, active: null }))
|
|
1415
|
-
}
|
|
1416
|
-
pull()
|
|
1417
|
-
const id = setInterval(pull, 60 * 1000)
|
|
1418
|
-
return () => clearInterval(id)
|
|
1419
|
-
}, [])
|
|
1420
|
-
// #69/#72: если есть активная подписка — показываем её (провайдер + индекс + windows с процентами).
|
|
1421
|
-
if (state.active) {
|
|
1422
|
-
const a = state.active
|
|
1423
|
-
const wins = Array.isArray(a.windows) ? a.windows : []
|
|
1424
|
-
// макс-процент среди окон для цвета полоски
|
|
1425
|
-
let maxPct = -1
|
|
1426
|
-
for (const w of wins) {
|
|
1427
|
-
if (w && typeof w.usedPercent === 'number' && w.usedPercent > maxPct) maxPct = w.usedPercent
|
|
1428
|
-
}
|
|
1429
|
-
if (maxPct < 0 && typeof a.usagePercent === 'number') maxPct = a.usagePercent
|
|
1430
|
-
const u = maxPct >= 0 ? maxPct : null
|
|
1431
|
-
let acls = 'dsub-chipActive'
|
|
1432
|
-
if (u != null) acls += u >= 100 ? ' dsub-chipActiveDanger' : (u >= 80 ? ' dsub-chipActiveDanger' : (u >= 50 ? ' dsub-chipActiveWarn' : ' dsub-chipActiveOk'))
|
|
1433
|
-
const idx = a.index != null ? ('#' + a.index) : ''
|
|
1434
|
-
const plan = a.plan ? (' ' + a.plan) : ''
|
|
1435
|
-
// #77: без provider-префикса
|
|
1436
|
-
const label = (a.provider === 'codex' && state.fastMode ? '\u26a1' : '') + idx + plan
|
|
1437
|
-
// windows: "5h 12% 7d 8%"
|
|
1438
|
-
const winLabels = wins.map((w) => (w.label || w.id) + ' ' + Math.round(w.usedPercent) + '%').join(' ')
|
|
1439
|
-
return React.createElement('span', {
|
|
1440
|
-
className: acls,
|
|
1441
|
-
title: a.model ? (prov + idx + ' · ' + a.model) : (prov + idx),
|
|
1442
|
-
},
|
|
1443
|
-
React.createElement('span', { className: 'dsub-chipDot' }),
|
|
1444
|
-
label,
|
|
1445
|
-
u != null
|
|
1446
|
-
? React.createElement('span', { className: 'dsub-chipActiveBar' },
|
|
1447
|
-
React.createElement('span', { className: 'dsub-chipActiveFill', style: { width: Math.min(100, Math.max(0, u)) + '%' } }))
|
|
1448
|
-
: null,
|
|
1449
|
-
winLabels || null,
|
|
1450
|
-
)
|
|
1451
|
-
}
|
|
1452
|
-
const on = state.count > 0
|
|
1453
|
-
// #66: цвет чипа по максимальному usagePercent (50/80/100).
|
|
1454
|
-
const u = state.usage
|
|
1455
|
-
let cls = 'dsub-chip'
|
|
1456
|
-
if (on) cls += u >= 100 ? ' dsub-chipDanger' : (u >= 80 ? ' dsub-chipDanger' : (u >= 50 ? ' dsub-chipWarn' : ' dsub-chipOn'))
|
|
1457
|
-
// #67: надпись об окончании подписки.
|
|
1458
|
-
let text = on ? String(state.count) : '—'
|
|
1459
|
-
if (state.expiry) {
|
|
1460
|
-
const d = new Date(state.expiry.at).toLocaleDateString()
|
|
1461
|
-
text = t('expiryLabel') + ': ' + state.expiry.label + ' ' + d
|
|
1462
|
-
}
|
|
1463
|
-
return React.createElement('span', {
|
|
1464
|
-
className: cls,
|
|
1465
|
-
title: on ? t('chipActive') + ': ' + state.count : t('none'),
|
|
1466
|
-
},
|
|
1467
|
-
React.createElement('span', { className: 'dsub-chipDot' }),
|
|
1468
|
-
text,
|
|
1469
|
-
)
|
|
1470
|
-
}
|
|
1471
|
-
|
|
1472
|
-
function registerHeaderChip(ctx) {
|
|
1473
|
-
ctx.effect(() => {
|
|
1474
|
-
ctx.slots.inject('conversation.session.header.utilities', () =>
|
|
1475
|
-
ctx.slots.register(
|
|
1476
|
-
{
|
|
1477
|
-
name: 'conversation.session.header.utilities',
|
|
1478
|
-
id: 'dsh-subscriptions-header-chip',
|
|
1479
|
-
order: 5,
|
|
1480
|
-
locale: NS,
|
|
1481
|
-
},
|
|
1482
|
-
(props) => React.createElement(HeaderChip, { t }),
|
|
1483
|
-
),
|
|
1484
|
-
)
|
|
1485
|
-
}, 'dsh-subscriptions: header chip')
|
|
1486
|
-
}
|
|
1487
|
-
|
|
1488
|
-
function registerComposerSwitch(ctx) {
|
|
1489
|
-
ctx.effect(() => {
|
|
1490
|
-
const render = () => {
|
|
1491
|
-
if (!labelEl) return
|
|
1492
|
-
refreshLoggedIn().then((logged) => {
|
|
1493
|
-
if (!labelEl) return
|
|
1494
|
-
const current = logged.length ? logged[activeIdx % logged.length] : null
|
|
1495
|
-
labelEl.textContent = t('switchLabel') + ': ' + (current || t('none'))
|
|
1496
|
-
}).catch(() => {})
|
|
1497
|
-
}
|
|
1498
|
-
ctx.slots.inject('composer.action', () =>
|
|
1499
|
-
ctx.slots.register(
|
|
1500
|
-
{
|
|
1501
|
-
name: 'composer.action',
|
|
1502
|
-
id: 'dsh-subscriptions-provider-switch',
|
|
1503
|
-
order: 31,
|
|
1504
|
-
label: () => {
|
|
1505
|
-
// label используется ядром до монтирования; держим актуальным
|
|
1506
|
-
setTimeout(render, 0)
|
|
1507
|
-
return t('switchLabel')
|
|
1508
|
-
},
|
|
1509
|
-
},
|
|
1510
|
-
(props) => {
|
|
1511
|
-
const el = React.createElement('button', {
|
|
1512
|
-
onClick: async () => {
|
|
1513
|
-
const logged = await refreshLoggedIn()
|
|
1514
|
-
if (!logged.length) return
|
|
1515
|
-
activeIdx = (activeIdx + 1) % logged.length
|
|
1516
|
-
render()
|
|
1517
|
-
},
|
|
1518
|
-
}, '')
|
|
1519
|
-
// Обновить текст в смонтированном элементе
|
|
1520
|
-
setTimeout(() => {
|
|
1521
|
-
labelEl = el
|
|
1522
|
-
render()
|
|
1523
|
-
}, 0)
|
|
1524
|
-
return el
|
|
1525
|
-
},
|
|
1526
|
-
),
|
|
1527
|
-
)
|
|
1528
|
-
render()
|
|
1529
|
-
}, 'dsh-subscriptions: composer provider switch')
|
|
1530
|
-
}
|
|
1531
|
-
|
|
1532
|
-
// #43: slash-команды входа/выхода/статуса прямо из чата.
|
|
1533
1368
|
function registerSlashCommands(ctx) {
|
|
1534
1369
|
ctx.effect(() => {
|
|
1535
1370
|
const triggers = ctx.get('inputTriggers')
|
|
@@ -1595,11 +1430,8 @@ function PluginCard(props) {
|
|
|
1595
1430
|
exports.apply = function apply(ctx) {
|
|
1596
1431
|
setT(ctx.locale.bind(NS))
|
|
1597
1432
|
registerSettings(ctx)
|
|
1598
|
-
registerUsageBubble(ctx)
|
|
1599
1433
|
registerSubsPill(ctx)
|
|
1600
1434
|
registerComposerQuota(ctx)
|
|
1601
|
-
registerHeaderChip(ctx)
|
|
1602
|
-
registerComposerSwitch(ctx)
|
|
1603
1435
|
registerSlashCommands(ctx)
|
|
1604
1436
|
}
|
|
1605
1437
|
return module.exports
|
package/package.json
CHANGED