@goodandready/dsh-subscriptions 0.7.44 → 0.7.46

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,27 @@ 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",
303
+ "compact": "Compact",
304
+ "heatmap": "Heatmap · last 24h",
305
+ "editModels": "Edit models",
306
+ "hidden": "hidden",
286
307
  "tabRouting": "Routing",
287
308
  "tabModels": "Models",
288
309
  "tabVault": "Vault",
@@ -691,6 +712,27 @@ window.__ModuleLoader__.load({
691
712
  "providerGallery": "添加服务商",
692
713
  "tabOverview": "概览",
693
714
  "tabAccounts": "账户",
715
+ "tabDashboard": "仪表盘",
716
+ "accountsDashboard": "账户仪表盘",
717
+ "sortRisk": "风险",
718
+ "sortQuota": "配额",
719
+ "sortName": "名称",
720
+ "speedSubagent": "子代理速度",
721
+ "statusHealthy": "正常",
722
+ "statusCooling": "冷却中",
723
+ "statusQuarantined": "隔离中",
724
+ "quotaUsed": "已用配额",
725
+ "requests": "请求",
726
+ "errors": "错误",
727
+ "loadUsage": "加载用量",
728
+ "modelEditor": "模型编辑器",
729
+ "discovered": "已发现",
730
+ "shown": "已显示",
731
+ "searchModels": "按名称或 ID 搜索模型",
732
+ "compact": "紧凑模式",
733
+ "heatmap": "热力图 · 最近 24 小时",
734
+ "editModels": "编辑模型",
735
+ "hidden": "隐藏",
694
736
  "tabRouting": "路由",
695
737
  "tabModels": "模型",
696
738
  "tabVault": "Vault",
@@ -1046,6 +1088,7 @@ window.__ModuleLoader__.load({
1046
1088
  const VaultSnapshotsPanel = lazyPanel('VaultSnapshotsPanel')
1047
1089
  const ProviderPacksPanel = lazyPanel('ProviderPacksPanel')
1048
1090
  const SourcesPanel = lazyPanel('SourcesPanel')
1091
+ const AccountsDashboardPanel = lazyPanel('AccountsDashboardPanel')
1049
1092
 
1050
1093
  // #839 model-list helpers
1051
1094
  function modelListSearchText(model) {
@@ -1955,6 +1998,7 @@ window.__ModuleLoader__.load({
1955
1998
  [
1956
1999
  ['overview', t('tabOverview')],
1957
2000
  ['accounts', t('tabAccounts')],
2001
+ ['dashboard', t('tabDashboard')],
1958
2002
  ['routing', t('tabRouting')],
1959
2003
  ['models', t('tabModels')],
1960
2004
  ['vault', t('tabVault')],
@@ -2221,6 +2265,19 @@ h('div', { className: 'dsub-row' },
2221
2265
  h('summary', { className: 'dsub-groupHead' }, t('sources')),
2222
2266
  h(SourcesPanel, { t }),
2223
2267
  ),
2268
+ h('div', { className: 'dsub-group', 'data-pane': 'dashboard' },
2269
+ h(AccountsDashboardPanel, {
2270
+ t,
2271
+ labels: {
2272
+ accountsDashboard: t('accountsDashboard'),
2273
+ sortRisk: t('sortRisk'), sortQuota: t('sortQuota'), sortName: t('sortName'),
2274
+ speedSubagent: t('speedSubagent'),
2275
+ statusHealthy: t('statusHealthy'), statusCooling: t('statusCooling'), statusQuarantined: t('statusQuarantined'),
2276
+ quotaUsed: t('quotaUsed'), requests: t('requests'), errors: t('errors'), loadUsage: t('loadUsage'),
2277
+ modelEditor: t('modelEditor'), discovered: t('discovered'), shown: t('shown'), searchModels: t('searchModels'),
2278
+ },
2279
+ }),
2280
+ ),
2224
2281
  h('details', { className: 'dsub-group', 'data-pane': 'models' },
2225
2282
  h('summary', { className: 'dsub-groupHead' }, t('providerPacks')),
2226
2283
  h(ProviderPacksPanel, { t }),
@@ -182,6 +182,161 @@ 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 [analytics, setAnalytics] = React.useState(null)
195
+ const [compact, setCompact] = React.useState(false)
196
+ const [editing, setEditing] = React.useState(false)
197
+ const [draft, setDraft] = React.useState({})
198
+ const usageRef = React.useRef({ inflight: {}, gen: {} })
199
+ const pull = () => fetch('/dsh-subscriptions/config', { cache: 'no-store' })
200
+ .then((res) => res.json())
201
+ .then((cfg) => {
202
+ setData(cfg)
203
+ setSpeed((cfg.config && cfg.config.codexSubagentSpeed) || 'standard')
204
+ setDraft(JSON.parse(JSON.stringify((cfg.config && cfg.config.modelOverrides) || {})))
205
+ if (!provider && cfg.accounts && cfg.accounts.length) setProvider(cfg.accounts[0].provider)
206
+ })
207
+ .catch(() => {})
208
+ React.useEffect(() => {
209
+ pull()
210
+ fetch('/dsh-subscriptions/analytics', { cache: 'no-store' })
211
+ .then((res) => res.json())
212
+ .then((out) => setAnalytics((out && out.analytics) || null))
213
+ .catch(() => {})
214
+ }, [])
215
+ const saveOverrides = () => {
216
+ if (!data || !data.config) return
217
+ fetch('/dsh-subscriptions/config', {
218
+ method: 'PUT',
219
+ headers: { 'content-type': 'application/json' },
220
+ body: JSON.stringify({ config: Object.assign({}, data.config, { modelOverrides: draft }), revision: data.revision }),
221
+ }).then(() => { setEditing(false); pull() }).catch(() => {})
222
+ }
223
+ const cancelOverrides = () => {
224
+ setDraft(JSON.parse(JSON.stringify((data && data.config && data.config.modelOverrides) || {})))
225
+ setEditing(false)
226
+ }
227
+ const loadUsage = (row) => {
228
+ const key = row.provider + ':' + row.index
229
+ if (usageRef.current.inflight[key]) return
230
+ const gen = (usageRef.current.gen[key] || 0) + 1
231
+ usageRef.current.gen[key] = gen
232
+ usageRef.current.inflight[key] = true
233
+ setUsage((m) => Object.assign({}, m, { [key]: { loading: true } }))
234
+ fetch('/dsh-subscriptions/usage?provider=' + encodeURIComponent(row.provider) + '&index=' + encodeURIComponent(row.index), { cache: 'no-store' })
235
+ .then((res) => res.json())
236
+ .then((out) => {
237
+ if (usageRef.current.gen[key] !== gen) return
238
+ setUsage((m) => Object.assign({}, m, { [key]: { value: out, loading: false } }))
239
+ })
240
+ .catch((e) => {
241
+ if (usageRef.current.gen[key] === gen) setUsage((m) => Object.assign({}, m, { [key]: { loading: false, error: String(e && e.message || e) } }))
242
+ })
243
+ .finally(() => { usageRef.current.inflight[key] = false })
244
+ }
245
+ const saveSpeed = (value) => {
246
+ setSpeed(value)
247
+ if (!data || !data.config) return
248
+ fetch('/dsh-subscriptions/config', {
249
+ method: 'PUT',
250
+ headers: { 'content-type': 'application/json' },
251
+ body: JSON.stringify({ config: Object.assign({}, data.config, { codexSubagentSpeed: value }), revision: data.revision }),
252
+ }).then(pull).catch(() => {})
253
+ }
254
+ const accounts = (data && data.accounts) || []
255
+ const selected = provider || (accounts[0] && accounts[0].provider) || ''
256
+ const rows = accounts.filter((row) => row.provider === selected).sort((a, b) => {
257
+ if (sort === 'name') return String(a.label || a.email || a.ref).localeCompare(String(b.label || b.email || b.ref))
258
+ if (sort === 'quota') return (b.usagePercent || 0) - (a.usagePercent || 0)
259
+ return (b.usagePercent || 0) + (b.errors || 0) * 5 - ((a.usagePercent || 0) + (a.errors || 0) * 5)
260
+ })
261
+ const models = ((data && data.modelsCatalog) || []).filter((m) => m.provider === selected)
262
+ const q = query.trim().toLowerCase()
263
+ const shown = models.filter((m) => !q || [m.id, m.name, m.alias].some((v) => String(v || '').toLowerCase().includes(q)))
264
+ const providers = Array.from(new Set(accounts.map((row) => row.provider).filter(Boolean)))
265
+ return h('div', { className: 'dsub-section-card' },
266
+ h('div', { className: 'dsub-section-title' }, t('accountsDashboard'), h('button', { type: 'button', className: 'dsub-mini', onClick: pull }, '↻')),
267
+ h('div', { className: 'dsub-row' },
268
+ h('select', { className: 'dsub-select', value: selected, onChange: (e) => setProvider(e.target.value) },
269
+ providers.map((id) => h('option', { key: id, value: id }, id))),
270
+ h('select', { className: 'dsub-select', value: sort, onChange: (e) => setSort(e.target.value) },
271
+ ['risk', 'quota', 'name'].map((id) => h('option', { key: id, value: id }, t('sort' + id[0].toUpperCase() + id.slice(1))))),
272
+ h('span', { className: 'dsub-sub' }, t('speedSubagent') + ':'),
273
+ h('select', { className: 'dsub-select', value: speed, onChange: (e) => saveSpeed(e.target.value) },
274
+ h('option', { value: 'standard' }, 'Standard'), h('option', { value: 'fast' }, 'Fast')),
275
+ ),
276
+ rows.map((row, i) => {
277
+ const key = row.provider + ':' + row.index
278
+ const u = usage[key]
279
+ return h('div', { key: key + ':' + i, className: 'dsub-group', style: { padding: '8px' } },
280
+ h('div', { className: 'dsub-row' },
281
+ h('span', { className: 'dsub-h' }, '#' + row.index + ' ' + (row.label || row.email || row.ref)),
282
+ h('span', { className: 'dsub-pillTag' }, row.quarantineUntil > Date.now() ? t('statusQuarantined') : (row.cooldownUntil > Date.now() ? t('statusCooling') : t('statusHealthy')))),
283
+ h('div', { className: 'dsub-sub' }, t('quotaUsed') + ': ' + (row.usagePercent == null ? '—' : row.usagePercent + '%') + ' · ' + t('requests') + ' ' + (row.requests || 0) + ' · ' + t('errors') + ' ' + (row.errors || 0)),
284
+ h('div', { className: 'dsub-row' },
285
+ h('button', { type: 'button', className: 'dsub-mini', onClick: () => loadUsage(row) }, u && u.loading ? '…' : t('loadUsage')),
286
+ u && u.error ? h('span', { className: 'dsub-bad' }, u.error) : null,
287
+ u && u.value ? h('span', { className: 'dsub-dim' }, JSON.stringify(u.value).slice(0, 180)) : null),
288
+ )
289
+ }),
290
+ h('div', { className: 'dsub-row' },
291
+ h('label', { className: 'dsub-sub' }, h('input', { type: 'checkbox', checked: compact, onChange: (e) => setCompact(e.target.checked) }), ' ' + t('compact')),
292
+ ),
293
+ h('div', { className: 'dsub-h' }, t('heatmap')),
294
+ h('div', { className: 'dsub-row', style: { gap: 2 } },
295
+ ((analytics && analytics.hourly) || []).slice(-24).map((hour, i) => h('span', {
296
+ key: i,
297
+ title: (hour && hour.requests || 0) + ' req',
298
+ style: { display: 'inline-block', width: 8, height: 18, background: 'rgba(88,166,255,' + Math.min(1, ((hour && hour.requests) || 0) / 10 + 0.1) + ')' },
299
+ }))),
300
+ h('div', { className: 'dsub-row' },
301
+ h('span', { className: 'dsub-h' }, t('modelEditor') + ' · ' + t('discovered') + ' ' + models.length + ' · ' + t('shown') + ' ' + shown.length),
302
+ h('button', { type: 'button', className: 'dsub-mini', onClick: () => setEditing(!editing) }, t('editModels')),
303
+ editing ? h('button', { type: 'button', className: 'dsub-mini', onClick: saveOverrides }, t('save')) : null,
304
+ editing ? h('button', { type: 'button', className: 'dsub-mini', onClick: cancelOverrides }, t('cancel')) : null,
305
+ ),
306
+ h('input', { className: 'dsub-grow', value: query, placeholder: t('searchModels'), onChange: (e) => setQuery(e.target.value) }),
307
+ shown.slice(0, 40).map((m) => h('div', { key: m.id, className: 'dsub-row' },
308
+ h('span', { className: 'dsub-h' }, m.independent ? '★ ' : ''),
309
+ editing
310
+ ? h('input', {
311
+ className: 'dsub-grow',
312
+ value: (draft[m.id] && draft[m.id].name) || '',
313
+ placeholder: m.name || m.id,
314
+ onChange: (e) => setDraft((d) => Object.assign({}, d, { [m.id]: Object.assign({}, d[m.id], { name: e.target.value }) })),
315
+ })
316
+ : h('span', { className: 'dsub-sub' }, (draft[m.id] && draft[m.id].name) || m.name || m.id),
317
+ editing ? h('input', {
318
+ type: 'number',
319
+ className: 'dsub-inp',
320
+ style: { width: '90px', minWidth: '90px' },
321
+ value: (draft[m.id] && draft[m.id].contextWindow) || '',
322
+ placeholder: 'ctx',
323
+ onChange: (e) => setDraft((d) => Object.assign({}, d, { [m.id]: Object.assign({}, d[m.id], { contextWindow: Number(e.target.value) || undefined }) })),
324
+ }) : null,
325
+ editing ? h('select', {
326
+ className: 'dsub-select',
327
+ value: (draft[m.id] && draft[m.id].reasoningEffort) || '',
328
+ onChange: (e) => setDraft((d) => Object.assign({}, d, { [m.id]: Object.assign({}, d[m.id], { reasoningEffort: e.target.value || undefined }) })),
329
+ }, ['', 'low', 'medium', 'high'].map((level) => h('option', { key: level || 'default', value: level }, level || 'effort'))) : null,
330
+ editing ? h('label', { className: 'dsub-dim' },
331
+ h('input', {
332
+ type: 'checkbox',
333
+ checked: !!(draft[m.id] && draft[m.id].hidden),
334
+ onChange: (e) => setDraft((d) => Object.assign({}, d, { [m.id]: Object.assign({}, d[m.id], { hidden: e.target.checked }) })),
335
+ }), ' ' + t('hidden')) : null,
336
+ h('span', { className: 'dsub-dim' }, m.id))),
337
+ )
338
+ }
339
+
340
+ return { WebhookTestButton, VaultSnapshotsPanel, ProviderPacksPanel, SourcesPanel, AccountsDashboardPanel }
186
341
  }
187
342
  })
@@ -171,6 +171,8 @@ export const Config = z.object({
171
171
  .description('#788 Per-provider model glob rules. First match wins; account may be a ref, label or index.'),
172
172
  tiers: z.array(z.any()).volatile().default([])
173
173
  .description('#817 Named picker tiers: { id, name, members: [accountRef] }. No cross-tier fallback.'),
174
+ modelOverrides: z.any().volatile().default({})
175
+ .description('#807 Per-model editor overrides: display name, hidden, context window, default reasoning effort.'),
174
176
  rotationStrategy: z.string().volatile().default('round-robin')
175
177
  .description('Load balancing strategy among available accounts: round-robin (balanced), least-loaded (fair-share quota), quota-aware (urgent quota first), or priority (sequential).'),
176
178
  poolEnabled: z.boolean().volatile().default(true)
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.46",
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",