@goodandready/dsh-subscriptions 0.7.44 → 0.7.45

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 CHANGED
@@ -283,6 +283,23 @@ window.__ModuleLoader__.load({
283
283
  "providerGallery": "Add provider",
284
284
  "tabOverview": "Overview",
285
285
  "tabAccounts": "Accounts",
286
+ "tabDashboard": "Dashboard",
287
+ "accountsDashboard": "Accounts dashboard",
288
+ "sortRisk": "Risk",
289
+ "sortQuota": "Quota",
290
+ "sortName": "Name",
291
+ "speedSubagent": "Subagent speed",
292
+ "statusHealthy": "healthy",
293
+ "statusCooling": "cooling",
294
+ "statusQuarantined": "quarantined",
295
+ "quotaUsed": "quota used",
296
+ "requests": "requests",
297
+ "errors": "errors",
298
+ "loadUsage": "Load usage",
299
+ "modelEditor": "Model editor",
300
+ "discovered": "discovered",
301
+ "shown": "shown",
302
+ "searchModels": "Search models by name or id",
286
303
  "tabRouting": "Routing",
287
304
  "tabModels": "Models",
288
305
  "tabVault": "Vault",
@@ -691,6 +708,23 @@ window.__ModuleLoader__.load({
691
708
  "providerGallery": "添加服务商",
692
709
  "tabOverview": "概览",
693
710
  "tabAccounts": "账户",
711
+ "tabDashboard": "仪表盘",
712
+ "accountsDashboard": "账户仪表盘",
713
+ "sortRisk": "风险",
714
+ "sortQuota": "配额",
715
+ "sortName": "名称",
716
+ "speedSubagent": "子代理速度",
717
+ "statusHealthy": "正常",
718
+ "statusCooling": "冷却中",
719
+ "statusQuarantined": "隔离中",
720
+ "quotaUsed": "已用配额",
721
+ "requests": "请求",
722
+ "errors": "错误",
723
+ "loadUsage": "加载用量",
724
+ "modelEditor": "模型编辑器",
725
+ "discovered": "已发现",
726
+ "shown": "已显示",
727
+ "searchModels": "按名称或 ID 搜索模型",
694
728
  "tabRouting": "路由",
695
729
  "tabModels": "模型",
696
730
  "tabVault": "Vault",
@@ -1046,6 +1080,7 @@ window.__ModuleLoader__.load({
1046
1080
  const VaultSnapshotsPanel = lazyPanel('VaultSnapshotsPanel')
1047
1081
  const ProviderPacksPanel = lazyPanel('ProviderPacksPanel')
1048
1082
  const SourcesPanel = lazyPanel('SourcesPanel')
1083
+ const AccountsDashboardPanel = lazyPanel('AccountsDashboardPanel')
1049
1084
 
1050
1085
  // #839 model-list helpers
1051
1086
  function modelListSearchText(model) {
@@ -1955,6 +1990,7 @@ window.__ModuleLoader__.load({
1955
1990
  [
1956
1991
  ['overview', t('tabOverview')],
1957
1992
  ['accounts', t('tabAccounts')],
1993
+ ['dashboard', t('tabDashboard')],
1958
1994
  ['routing', t('tabRouting')],
1959
1995
  ['models', t('tabModels')],
1960
1996
  ['vault', t('tabVault')],
@@ -2221,6 +2257,19 @@ h('div', { className: 'dsub-row' },
2221
2257
  h('summary', { className: 'dsub-groupHead' }, t('sources')),
2222
2258
  h(SourcesPanel, { t }),
2223
2259
  ),
2260
+ h('div', { className: 'dsub-group', 'data-pane': 'dashboard' },
2261
+ h(AccountsDashboardPanel, {
2262
+ t,
2263
+ labels: {
2264
+ accountsDashboard: t('accountsDashboard'),
2265
+ sortRisk: t('sortRisk'), sortQuota: t('sortQuota'), sortName: t('sortName'),
2266
+ speedSubagent: t('speedSubagent'),
2267
+ statusHealthy: t('statusHealthy'), statusCooling: t('statusCooling'), statusQuarantined: t('statusQuarantined'),
2268
+ quotaUsed: t('quotaUsed'), requests: t('requests'), errors: t('errors'), loadUsage: t('loadUsage'),
2269
+ modelEditor: t('modelEditor'), discovered: t('discovered'), shown: t('shown'), searchModels: t('searchModels'),
2270
+ },
2271
+ }),
2272
+ ),
2224
2273
  h('details', { className: 'dsub-group', 'data-pane': 'models' },
2225
2274
  h('summary', { className: 'dsub-groupHead' }, t('providerPacks')),
2226
2275
  h(ProviderPacksPanel, { t }),
@@ -182,6 +182,95 @@ window.__ModuleLoader__.load({
182
182
  }
183
183
 
184
184
 
185
- return { WebhookTestButton, VaultSnapshotsPanel, ProviderPacksPanel, SourcesPanel }
185
+ // #789/#807/#812/#802: account dashboard, model editor, lazy usage and speed controls.
186
+ function AccountsDashboardPanel(props) {
187
+ const t = (props && props.t) || ((k) => k)
188
+ const [data, setData] = React.useState(null)
189
+ const [provider, setProvider] = React.useState('')
190
+ const [query, setQuery] = React.useState('')
191
+ const [sort, setSort] = React.useState('risk')
192
+ const [usage, setUsage] = React.useState({})
193
+ const [speed, setSpeed] = React.useState('standard')
194
+ const usageRef = React.useRef({ inflight: {}, gen: {} })
195
+ const pull = () => fetch('/dsh-subscriptions/config', { cache: 'no-store' })
196
+ .then((res) => res.json())
197
+ .then((cfg) => {
198
+ setData(cfg)
199
+ setSpeed((cfg.config && cfg.config.codexSubagentSpeed) || 'standard')
200
+ if (!provider && cfg.accounts && cfg.accounts.length) setProvider(cfg.accounts[0].provider)
201
+ })
202
+ .catch(() => {})
203
+ React.useEffect(() => { pull() }, [])
204
+ const loadUsage = (row) => {
205
+ const key = row.provider + ':' + row.index
206
+ if (usageRef.current.inflight[key]) return
207
+ const gen = (usageRef.current.gen[key] || 0) + 1
208
+ usageRef.current.gen[key] = gen
209
+ usageRef.current.inflight[key] = true
210
+ setUsage((m) => Object.assign({}, m, { [key]: { loading: true } }))
211
+ fetch('/dsh-subscriptions/usage?provider=' + encodeURIComponent(row.provider) + '&index=' + encodeURIComponent(row.index), { cache: 'no-store' })
212
+ .then((res) => res.json())
213
+ .then((out) => {
214
+ if (usageRef.current.gen[key] !== gen) return
215
+ setUsage((m) => Object.assign({}, m, { [key]: { value: out, loading: false } }))
216
+ })
217
+ .catch((e) => {
218
+ if (usageRef.current.gen[key] === gen) setUsage((m) => Object.assign({}, m, { [key]: { loading: false, error: String(e && e.message || e) } }))
219
+ })
220
+ .finally(() => { usageRef.current.inflight[key] = false })
221
+ }
222
+ const saveSpeed = (value) => {
223
+ setSpeed(value)
224
+ if (!data || !data.config) return
225
+ fetch('/dsh-subscriptions/config', {
226
+ method: 'PUT',
227
+ headers: { 'content-type': 'application/json' },
228
+ body: JSON.stringify({ config: Object.assign({}, data.config, { codexSubagentSpeed: value }), revision: data.revision }),
229
+ }).then(pull).catch(() => {})
230
+ }
231
+ const accounts = (data && data.accounts) || []
232
+ const selected = provider || (accounts[0] && accounts[0].provider) || ''
233
+ const rows = accounts.filter((row) => row.provider === selected).sort((a, b) => {
234
+ if (sort === 'name') return String(a.label || a.email || a.ref).localeCompare(String(b.label || b.email || b.ref))
235
+ if (sort === 'quota') return (b.usagePercent || 0) - (a.usagePercent || 0)
236
+ return (b.usagePercent || 0) + (b.errors || 0) * 5 - ((a.usagePercent || 0) + (a.errors || 0) * 5)
237
+ })
238
+ const models = ((data && data.modelsCatalog) || []).filter((m) => m.provider === selected)
239
+ const q = query.trim().toLowerCase()
240
+ const shown = models.filter((m) => !q || [m.id, m.name, m.alias].some((v) => String(v || '').toLowerCase().includes(q)))
241
+ const providers = Array.from(new Set(accounts.map((row) => row.provider).filter(Boolean)))
242
+ return h('div', { className: 'dsub-section-card' },
243
+ h('div', { className: 'dsub-section-title' }, t('accountsDashboard'), h('button', { type: 'button', className: 'dsub-mini', onClick: pull }, '↻')),
244
+ h('div', { className: 'dsub-row' },
245
+ h('select', { className: 'dsub-select', value: selected, onChange: (e) => setProvider(e.target.value) },
246
+ providers.map((id) => h('option', { key: id, value: id }, id))),
247
+ h('select', { className: 'dsub-select', value: sort, onChange: (e) => setSort(e.target.value) },
248
+ ['risk', 'quota', 'name'].map((id) => h('option', { key: id, value: id }, t('sort' + id[0].toUpperCase() + id.slice(1))))),
249
+ h('span', { className: 'dsub-sub' }, t('speedSubagent') + ':'),
250
+ h('select', { className: 'dsub-select', value: speed, onChange: (e) => saveSpeed(e.target.value) },
251
+ h('option', { value: 'standard' }, 'Standard'), h('option', { value: 'fast' }, 'Fast')),
252
+ ),
253
+ rows.map((row, i) => {
254
+ const key = row.provider + ':' + row.index
255
+ const u = usage[key]
256
+ return h('div', { key: key + ':' + i, className: 'dsub-group', style: { padding: '8px' } },
257
+ h('div', { className: 'dsub-row' },
258
+ h('span', { className: 'dsub-h' }, '#' + row.index + ' ' + (row.label || row.email || row.ref)),
259
+ h('span', { className: 'dsub-pillTag' }, row.quarantineUntil > Date.now() ? t('statusQuarantined') : (row.cooldownUntil > Date.now() ? t('statusCooling') : t('statusHealthy')))),
260
+ h('div', { className: 'dsub-sub' }, t('quotaUsed') + ': ' + (row.usagePercent == null ? '—' : row.usagePercent + '%') + ' · ' + t('requests') + ' ' + (row.requests || 0) + ' · ' + t('errors') + ' ' + (row.errors || 0)),
261
+ h('div', { className: 'dsub-row' },
262
+ h('button', { type: 'button', className: 'dsub-mini', onClick: () => loadUsage(row) }, u && u.loading ? '…' : t('loadUsage')),
263
+ u && u.error ? h('span', { className: 'dsub-bad' }, u.error) : null,
264
+ u && u.value ? h('span', { className: 'dsub-dim' }, JSON.stringify(u.value).slice(0, 180)) : null),
265
+ )
266
+ }),
267
+ h('div', { className: 'dsub-h' }, t('modelEditor') + ' · ' + t('discovered') + ' ' + models.length + ' · ' + t('shown') + ' ' + shown.length),
268
+ h('input', { className: 'dsub-grow', value: query, placeholder: t('searchModels'), onChange: (e) => setQuery(e.target.value) }),
269
+ shown.slice(0, 40).map((m) => h('div', { key: m.id, className: 'dsub-row' },
270
+ h('span', { className: 'dsub-h' }, m.independent ? '★ ' : ''), h('span', { className: 'dsub-sub' }, m.name || m.id), h('span', { className: 'dsub-dim' }, m.id))),
271
+ )
272
+ }
273
+
274
+ return { WebhookTestButton, VaultSnapshotsPanel, ProviderPacksPanel, SourcesPanel, AccountsDashboardPanel }
186
275
  }
187
276
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-subscriptions",
3
- "version": "0.7.44",
3
+ "version": "0.7.45",
4
4
  "description": "Use ChatGPT Codex, Claude, Grok, Antigravity, Kimi, GLM, Cursor, Kiro, Copilot, Qwen, ERNIE, Spark, JetBrains, Perplexity, Replit, and Cody subscriptions as DeepSeek Harness LLM providers via OAuth.",
5
5
  "license": "MIT",
6
6
  "type": "module",