@goodandready/dsh-subscriptions 0.4.18 → 0.4.19
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 +16 -337
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -64,27 +64,7 @@ window.__ModuleLoader__.load({
|
|
|
64
64
|
'.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
65
|
'.dsub-verify{font-size:12px;color:var(--dsw-alias-state-warning-primary)}' +
|
|
66
66
|
'.dsub-verify a{color:var(--dsw-alias-brand-primary)}' +
|
|
67
|
-
'.dsub-hud{pointer-events:auto;position:fixed;z-index:10010;width:64px;height:64px;transition:left .2s,top .2s}' +
|
|
68
|
-
'.dsub-hud[data-dragging=true]{transition:none}' +
|
|
69
|
-
'.dsub-hudBtn{position:relative;width:64px;height:64px;padding:0;border:0;border-radius:50%;cursor:grab;display:grid;place-items:center;user-select:none;touch-action:none;' +
|
|
70
67
|
'background:var(--dsw-alias-bg-layer-3);box-shadow:0 0 0 1px var(--dsw-alias-border-l2),0 8px 24px rgba(0,0,0,.25);backdrop-filter:blur(18px)}' +
|
|
71
|
-
'.dsub-hudBtn:hover{transform:scale(1.05)}' +
|
|
72
|
-
'.dsub-hudBtn:active{cursor:grabbing}' +
|
|
73
|
-
'.dsub-hudRing{position:absolute;inset:4px;width:56px;height:56px;transform:rotate(-90deg)}' +
|
|
74
|
-
'.dsub-hudTrack{fill:none;stroke:var(--dsw-alias-border-l2);stroke-width:4}' +
|
|
75
|
-
'.dsub-hudVal{fill:none;stroke:var(--dsub-tone,var(--dsw-alias-state-success-primary));stroke-width:4;stroke-linecap:round;transition:stroke-dashoffset .4s}' +
|
|
76
|
-
'.dsub-hudCore{position:relative;z-index:2;font-size:14px;font-weight:700;font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-primary)}' +
|
|
77
|
-
'.dsub-hud[data-collapsed=true] .dsub-hudBtn{opacity:.45}' +
|
|
78
|
-
'.dsub-hud[data-collapsed=true]:hover .dsub-hudBtn{opacity:1}' +
|
|
79
|
-
'.dsub-hudPanel{pointer-events:none;position:absolute;width:280px;padding:12px;border-radius:14px;background:var(--dsw-alias-bg-layer-3);border:1px solid var(--dsw-alias-border-l2);box-shadow:0 18px 48px rgba(0,0,0,.35);backdrop-filter:blur(28px);opacity:0;transform:translateY(6px);transition:opacity .16s,transform .16s}' +
|
|
80
|
-
'.dsub-hud:hover .dsub-hudPanel{opacity:1;transform:none}' +
|
|
81
|
-
'.dsub-hud[data-dragging=true] .dsub-hudPanel{opacity:0}' +
|
|
82
|
-
'.dsub-hudRow{display:flex;align-items:center;gap:8px;margin-top:8px;font-size:11.5px}' +
|
|
83
|
-
'.dsub-hudRow .dsub-bar{min-width:70px}' +
|
|
84
|
-
'.dsub-hudName{font-weight:600;min-width:52px}' +
|
|
85
|
-
'.dsub-hudPct{margin-left:auto;font-variant-numeric:tabular-nums}' +
|
|
86
|
-
'.dsub-hudHead{font-size:12px;font-weight:600;color:var(--dsw-alias-label-primary)}' +
|
|
87
|
-
'.dsub-hudHint{margin-top:10px;font-size:10.5px;color:var(--dsw-alias-label-tertiary)}' +
|
|
88
68
|
'.dsub-pill{display:inline-flex;align-items:center;gap:6px;height:26px;padding:0 10px;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:11.5px;font-weight:600;cursor:pointer;letter-spacing:.06em}' +
|
|
89
69
|
'.dsub-pill:hover{border-color:var(--dsw-alias-label-dimmed)}' +
|
|
90
70
|
'.dsub-led{width:7px;height:7px;border-radius:50%;flex:none}' +
|
|
@@ -346,23 +326,7 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
346
326
|
}
|
|
347
327
|
const Chevron = ChevronIcon || FallbackChevron
|
|
348
328
|
|
|
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
|
-
}
|
|
329
|
+
|
|
366
330
|
|
|
367
331
|
function SubsSection(props) {
|
|
368
332
|
// Переводчик приходит от слота, потому что в его записи указан locale.
|
|
@@ -1120,7 +1084,7 @@ function PluginCard(props) {
|
|
|
1120
1084
|
// модальной консолью аккаунтов (закрытие: крестик, Escape, клик мимо).
|
|
1121
1085
|
function SubsPill(props) {
|
|
1122
1086
|
const t = (props && props.t) || ((k) => k)
|
|
1123
|
-
const [state, setState] = React.useState({ logged: [], usage: 0, accounts: [], open: false })
|
|
1087
|
+
const [state, setState] = React.useState({ logged: [], usage: 0, accounts: [], open: false, active: null })
|
|
1124
1088
|
React.useEffect(() => {
|
|
1125
1089
|
let alive = true
|
|
1126
1090
|
const pull = () => {
|
|
@@ -1131,13 +1095,13 @@ function PluginCard(props) {
|
|
|
1131
1095
|
.then((res) => res.json())
|
|
1132
1096
|
.then((cfg) => {
|
|
1133
1097
|
if (!alive) return
|
|
1134
|
-
setState({ logged, usage: r.usage, accounts: (cfg && cfg.accounts) || [], open: false })
|
|
1098
|
+
setState({ logged, usage: r.usage, accounts: (cfg && cfg.accounts) || [], open: false, active: r.active })
|
|
1135
1099
|
})
|
|
1136
|
-
.catch(() => { if (alive) setState({ logged, usage: r.usage, accounts: [], open: false }) })
|
|
1100
|
+
.catch(() => { if (alive) setState({ logged, usage: r.usage, accounts: [], open: false, active: r.active }) })
|
|
1137
1101
|
}).catch(() => {})
|
|
1138
1102
|
}
|
|
1139
1103
|
pull()
|
|
1140
|
-
const id = setInterval(pull,
|
|
1104
|
+
const id = setInterval(pull, 20 * 1000)
|
|
1141
1105
|
return () => { alive = false; clearInterval(id) }
|
|
1142
1106
|
}, [])
|
|
1143
1107
|
React.useEffect(() => {
|
|
@@ -1149,6 +1113,14 @@ function PluginCard(props) {
|
|
|
1149
1113
|
const n = state.logged.length
|
|
1150
1114
|
const led = n === 0 ? 'dsub-ledOff' : (state.usage >= 90 ? 'dsub-ledBad' : (state.usage >= 50 ? 'dsub-ledWarn' : 'dsub-ledOk'))
|
|
1151
1115
|
const now = Date.now()
|
|
1116
|
+
let pillText = t('subsPill') + ' (' + n + ')'
|
|
1117
|
+
if (state.active) {
|
|
1118
|
+
const a = state.active
|
|
1119
|
+
const wins = Array.isArray(a.windows) ? a.windows : []
|
|
1120
|
+
const winStr = wins.map((w) => (w.label || w.id) + ' ' + Math.round(w.usedPercent) + '%').join(' ')
|
|
1121
|
+
const provName = a.provider ? (a.provider.charAt(0).toUpperCase() + a.provider.slice(1)) : ''
|
|
1122
|
+
pillText = (a.provider === 'codex' && a.fastMode ? '⚡ ' : '') + provName + (winStr ? ' · ' + winStr : (a.usagePercent != null ? ' · ' + Math.round(a.usagePercent) + '%' : ''))
|
|
1123
|
+
}
|
|
1152
1124
|
const rows = state.accounts.map((a, i) => {
|
|
1153
1125
|
const isLogged = state.logged.includes(a.provider)
|
|
1154
1126
|
const cooled = a.cooldownUntil && a.cooldownUntil > now
|
|
@@ -1167,10 +1139,11 @@ function PluginCard(props) {
|
|
|
1167
1139
|
return React.createElement(React.Fragment, null,
|
|
1168
1140
|
React.createElement('button', {
|
|
1169
1141
|
type: 'button', className: 'dsub-pill', 'aria-expanded': state.open ? 'true' : 'false',
|
|
1142
|
+
title: t('subsPill') + ': ' + (n > 0 ? (n + ' ' + t('connected')) : t('notConnected')),
|
|
1170
1143
|
onClick: () => setState((s) => Object.assign({}, s, { open: !s.open })),
|
|
1171
1144
|
},
|
|
1172
1145
|
React.createElement('span', { className: 'dsub-led ' + led }),
|
|
1173
|
-
|
|
1146
|
+
pillText,
|
|
1174
1147
|
),
|
|
1175
1148
|
state.open ? React.createElement('div', {
|
|
1176
1149
|
className: 'dsub-modalWrap',
|
|
@@ -1182,7 +1155,7 @@ function PluginCard(props) {
|
|
|
1182
1155
|
React.createElement('button', {
|
|
1183
1156
|
type: 'button', className: 'dsub-mini',
|
|
1184
1157
|
onClick: () => setState((s) => Object.assign({}, s, { open: false })),
|
|
1185
|
-
}, t('subsModalClose') + '
|
|
1158
|
+
}, t('subsModalClose') + ' ×'),
|
|
1186
1159
|
),
|
|
1187
1160
|
rows.length ? rows : React.createElement('div', { className: 'dsub-sub' }, t('subsNotLogged')),
|
|
1188
1161
|
React.createElement('div', { className: 'dsub-row', style: { marginTop: 12 } },
|
|
@@ -1190,7 +1163,6 @@ function PluginCard(props) {
|
|
|
1190
1163
|
className: 'dsub-mini', href: '#', onClick: (e) => {
|
|
1191
1164
|
e.preventDefault()
|
|
1192
1165
|
setState((s) => Object.assign({}, s, { open: false }))
|
|
1193
|
-
try { document.querySelector('[data-slot-key=\"settings.plugin.item\"],[data-dsub-open]') } catch {}
|
|
1194
1166
|
try {
|
|
1195
1167
|
const btn = Array.from(document.querySelectorAll('button')).find((b) => (b.textContent || '').includes(t('title')))
|
|
1196
1168
|
if (btn) btn.click()
|
|
@@ -1202,162 +1174,11 @@ function PluginCard(props) {
|
|
|
1202
1174
|
) : null,
|
|
1203
1175
|
)
|
|
1204
1176
|
}
|
|
1205
|
-
|
|
1206
1177
|
// #82: перетаскиваемый HUD-бабл (shell.overlay) с кольцевой шкалой
|
|
1207
1178
|
// остатка активной подписки, прилипанием к краям и frosted-панелью.
|
|
1208
1179
|
const HUD_KEY = 'dsh-subscriptions.hud'
|
|
1209
1180
|
const HUD_SIZE = 64
|
|
1210
1181
|
|
|
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
1182
|
|
|
1362
1183
|
function registerSubsPill(ctx) {
|
|
1363
1184
|
ctx.effect(() => {
|
|
@@ -1391,145 +1212,6 @@ function PluginCard(props) {
|
|
|
1391
1212
|
}, 'dsh-subscriptions: composer quota')
|
|
1392
1213
|
}
|
|
1393
1214
|
|
|
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
1215
|
function registerSlashCommands(ctx) {
|
|
1534
1216
|
ctx.effect(() => {
|
|
1535
1217
|
const triggers = ctx.get('inputTriggers')
|
|
@@ -1595,11 +1277,8 @@ function PluginCard(props) {
|
|
|
1595
1277
|
exports.apply = function apply(ctx) {
|
|
1596
1278
|
setT(ctx.locale.bind(NS))
|
|
1597
1279
|
registerSettings(ctx)
|
|
1598
|
-
registerUsageBubble(ctx)
|
|
1599
1280
|
registerSubsPill(ctx)
|
|
1600
1281
|
registerComposerQuota(ctx)
|
|
1601
|
-
registerHeaderChip(ctx)
|
|
1602
|
-
registerComposerSwitch(ctx)
|
|
1603
1282
|
registerSlashCommands(ctx)
|
|
1604
1283
|
}
|
|
1605
1284
|
return module.exports
|
package/package.json
CHANGED