dsh-modelscope-pool 0.10.1 → 0.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/lib/index.js +77 -56
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -334,7 +334,7 @@ dsh-go 里更进一步:这个插件会把自己注册成一个叫 **魔搭 Key
334
334
  看看是不是被「手动禁用」了(`manual` 标记)。手动禁用不会被自动恢复流程放开,需要点「恢复」。
335
335
 
336
336
  **点「刷新额度」提示「魔搭 Key 池没有响应」?**
337
- App 要求点按在 3 秒内返回,而刷新额度要联网逐个查所有 Key 的额度,早就超过 3 秒了。0.10.1 起这几个慢操作(刷新额度 / 测试全部 / 重新拉取模型 / 添加 Key)改成先回一个「正在刷新额度」的等待态,结果异步回来,最多 60 秒。如果你还在 0.10.0 或更早,升级即可。
337
+ App 要求点按在 3 秒内返回,而刷新额度要联网逐个查所有 Key 的额度,早就超过 3 秒了。0.10.2 起这几个慢操作(刷新额度 / 测试全部 / 重新拉取模型 / 添加 Key)不再在点按里等:点一下立刻在**面板顶部**显示「正在刷新额度」,**3 秒后自动收起**(操作不会中断),跑完再把结果挂上去 3 秒。如果你还在 0.10.0 或更早,升级即可。
338
338
 
339
339
  **端口被占用?**
340
340
  改 `config.json` 的 `port`,同时把客户端和供应商配置里的地址一起改掉。
package/lib/index.js CHANGED
@@ -300,14 +300,16 @@ const modelRowOf = (entry, current, t) => {
300
300
  const routing = router.snapshot()
301
301
  const routed =
302
302
  routing.blockedPairs > 0 ? ` ${t('routed', { n: routing.blockedPairs })}` : ''
303
+ const busy = noticeNow()
303
304
  const hint =
304
- s.total === 0
305
+ busy ||
306
+ (s.total === 0
305
307
  ? t('hintNoKeys')
306
308
  : s.cooling > 0
307
309
  ? `${t('hintCooling')}${routed}`
308
310
  : model
309
311
  ? `${t('hintModel', { model })}${routed}`
310
- : `${t('hintIdle')}${routed}`
312
+ : `${t('hintIdle')}${routed}`)
311
313
 
312
314
  // 扇形图:Key 池的状态分布(可用 / 额度不足 / 令牌无效 / 其他冷却)
313
315
  const order = [
@@ -515,6 +517,7 @@ const modelRowOf = (entry, current, t) => {
515
517
  title: t('poolTitle'),
516
518
  accent: 'teal',
517
519
  children: [
520
+ { type: 'callout', tone: 'info', text: { $: 'data.hint' } },
518
521
  {
519
522
  type: 'row',
520
523
  children: [
@@ -531,7 +534,6 @@ const modelRowOf = (entry, current, t) => {
531
534
  caption: { $: 'data.magic.brief' },
532
535
  tone: 'good',
533
536
  },
534
- { type: 'callout', tone: 'info', text: { $: 'data.hint' } },
535
537
  { type: 'text', text: { $: 'data.keyTitle' }, style: { size: 'title', weight: 'bold' } },
536
538
  { type: 'list', items: { $: 'data.keys' } },
537
539
  {
@@ -728,25 +730,44 @@ const modelRowOf = (entry, current, t) => {
728
730
  }
729
731
  }
730
732
 
731
- // 点按必须在 3 秒内返回,否则 App 会提示「没有响应」。
732
- // 刷新额度 / 测试全部 / 拉模型 / 校验 Key 都要联网,所以先回 pending,结果走延后效果。
733
- const defer = (label, work) => ({
734
- pending: { label, timeoutMs: 60_000 },
735
- result: async () => {
736
- const value = await work()
737
- refreshPanel()
738
- refreshStrip()
739
- return value
740
- },
741
- })
733
+ // 点按必须 3 秒内返回,否则 App 会提示「没有响应」。
734
+ // 联网的慢操作因此不在点按里等:先把一句话挂到面板顶部(data.hint),
735
+ // 3 秒后自动收起,真正的活儿在后台继续跑,跑完再挂一句结果,同样 3 秒收起。
736
+ let notice = ''
737
+ let noticeUntil = 0
738
+ const setNotice = (text, ms = 3000) => {
739
+ notice = String(text ?? '')
740
+ noticeUntil = notice ? Date.now() + ms : 0
741
+ refreshPanel()
742
+ refreshStrip()
743
+ }
744
+ const noticeNow = () => (noticeUntil > Date.now() ? notice : '')
745
+ const running = new Set()
746
+ const runInBackground = (key, label, work) => {
747
+ if (running.has(key)) return { toast: label }
748
+ running.add(key)
749
+ setNotice(label)
750
+ void (async () => {
751
+ let message = ''
752
+ try {
753
+ message = String((await work()) ?? '')
754
+ } catch (error) {
755
+ message = String(error?.message ?? error ?? '')
756
+ } finally {
757
+ running.delete(key)
758
+ }
759
+ setNotice(message)
760
+ })()
761
+ return { refresh: true }
762
+ }
742
763
 
743
764
  const panelActions = {
744
765
  refresh(scope, event) {
745
766
  const t = makeT(event?.locale)
746
- return defer(t('pendingRefresh'), async () => {
767
+ return runInBackground('refresh', t('pendingRefresh'), async () => {
747
768
  await refreshQuota(true)
748
769
  await refreshCatalog(true)
749
- return { toast: t('toastQuotaUpdated') }
770
+ return t('toastQuotaUpdated')
750
771
  })
751
772
  },
752
773
  async 'pick-model'(scope, event) {
@@ -759,37 +780,35 @@ const modelRowOf = (entry, current, t) => {
759
780
  },
760
781
  'test-all'(scope, event) {
761
782
  const t = makeT(event?.locale)
762
- const keys = pool ? [...pool.keys] : []
763
- if (keys.length === 0) return { toast: t('toastNoKeys') }
764
- // 注意不能用 /v1/models 验 Key:那个接口是公开的,什么令牌都返回 200。
765
- // 真正会校验令牌的是额度接口,而且查额度不扣魔粒。
766
- return defer(t('pendingTest'), async () => {
767
- const rows = await quota.refresh(
768
- keys.map((k) => ({ id: k.id, key: k.key, region: k.region })),
769
- { force: true },
770
- )
771
- for (const [i, row] of rows.entries()) {
772
- const k = keys[i]
773
- if (row.state?.region) await pool.setRegion(k.id, row.state.region)
774
- if (!row.state?.ok) {
775
- pool.markFailure(k.id, '令牌无效', String(row.state?.error ?? '').slice(0, 80))
776
- continue
777
- }
778
- if (row.state.available < QUOTA_FLOOR) {
779
- pool.markFailure(k.id, '额度不足', `剩余 ${row.state.available} 魔粒`, nextQuotaResetAt())
780
- continue
783
+ return runInBackground('test', t('pendingTest'), async () => {
784
+ const keys = pool ? [...pool.keys] : []
785
+ if (keys.length === 0) return t('toastNoKeys')
786
+ // 注意不能用 /v1/models 验 Key:那个接口是公开的,什么令牌都返回 200。
787
+ // 真正会校验令牌的是额度接口,而且查额度不扣魔粒。
788
+ const rows = await quota.refresh(
789
+ keys.map((k) => ({ id: k.id, key: k.key, region: k.region })),
790
+ { force: true },
791
+ )
792
+ for (const [i, row] of rows.entries()) {
793
+ const k = keys[i]
794
+ if (row.state?.region) await pool.setRegion(k.id, row.state.region)
795
+ if (!row.state?.ok) {
796
+ pool.markFailure(k.id, '令牌无效', String(row.state?.error ?? '').slice(0, 80))
797
+ continue
798
+ }
799
+ if (row.state.available < QUOTA_FLOOR) {
800
+ pool.markFailure(k.id, '额度不足', `剩余 ${row.state.available} 魔粒`, nextQuotaResetAt())
801
+ continue
802
+ }
803
+ pool.restore(k.id)
781
804
  }
782
- pool.restore(k.id)
783
- }
784
- refreshPanel()
785
- refreshStrip()
786
- const ok = rows.filter((r) => r.state?.ok && r.state.available >= QUOTA_FLOOR).length
787
- const invalid = rows.filter((r) => !r.state?.ok).length
788
- const short = rows.length - ok - invalid
789
- const parts = [t('partOk', { ok, total: keys.length })]
790
- if (short > 0) parts.push(t('partShort', { n: short }))
791
- if (invalid > 0) parts.push(t('partInvalid', { n: invalid }))
792
- return { toast: t('toastTestDone', { parts: parts.join(' · ') }) }
805
+ const ok = rows.filter((r) => r.state?.ok && r.state.available >= QUOTA_FLOOR).length
806
+ const invalid = rows.filter((r) => !r.state?.ok).length
807
+ const short = rows.length - ok - invalid
808
+ const parts = [t('partOk', { ok, total: keys.length })]
809
+ if (short > 0) parts.push(t('partShort', { n: short }))
810
+ if (invalid > 0) parts.push(t('partInvalid', { n: invalid }))
811
+ return t('toastTestDone', { parts: parts.join(' · ') })
793
812
  })
794
813
  },
795
814
  'restore-all': async (scope, event) => {
@@ -870,13 +889,13 @@ const modelRowOf = (entry, current, t) => {
870
889
  })
871
890
  return { toast: t('toastDeleted', { id }), refresh: true }
872
891
  },
873
- 'add-form': (scope, event) => {
892
+ 'add-form'(scope, event) {
874
893
  const t = makeT(event?.locale)
875
894
  const value = event?.value
876
895
  const raw = typeof value === 'string' ? value : value?.newKey ?? value?.value ?? ''
877
- return defer(t('pendingAdd'), async () => {
896
+ return runInBackground('add', t('pendingAdd'), async () => {
878
897
  const result = await addKey(raw, event?.locale)
879
- return { toast: result?.toast ?? t('toastNoNew') }
898
+ return result?.toast ?? t('toastNoNew')
880
899
  })
881
900
  },
882
901
  'key-back'() {
@@ -884,13 +903,12 @@ const modelRowOf = (entry, current, t) => {
884
903
  },
885
904
  'refresh-models'(scope, event) {
886
905
  const t = makeT(event?.locale)
887
- return defer(t('pendingModels'), async () => {
906
+ return runInBackground('models', t('pendingModels'), async () => {
888
907
  await refreshCatalog(true)
889
- return { toast: t('toastModelsReloaded') }
908
+ return t('toastModelsReloaded')
890
909
  })
891
910
  },
892
911
  }
893
-
894
912
  dshGo.panel('pool', { view: viewPool, data: panelData(buildPoolData), actions: panelActions })
895
913
  dshGo.panel('key', { view: viewKey, data: panelData(buildKeyData), actions: panelActions })
896
914
  dshGo.panel('models', { view: viewModels, data: panelData(buildModelsData), actions: panelActions })
@@ -932,11 +950,14 @@ const modelRowOf = (entry, current, t) => {
932
950
  },
933
951
  })
934
952
 
935
- dshGo.on('addkey', async (scope, event) => {
953
+ dshGo.on('addkey', (scope, event) => {
954
+ const t = makeT(event?.locale)
936
955
  const text = String(scope.clipboard ?? '').trim()
937
- if (!text) return { toast: makeT(event?.locale)('toastNoClipboard') }
938
- const result = await addKey(text, event?.locale)
939
- return { ...result, refresh: 'pool' }
956
+ if (!text) return { toast: t('toastNoClipboard') }
957
+ return runInBackground('add', t('pendingAdd'), async () => {
958
+ const result = await addKey(text, event?.locale)
959
+ return result?.toast ?? t('toastNoNew')
960
+ })
940
961
  })
941
962
 
942
963
  dshGo.on('open-pool', () => ({ open: 'panel:pool' }))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-modelscope-pool",
3
- "version": "0.10.1",
3
+ "version": "0.10.2",
4
4
  "description": "魔搭多 Key 聚合网关:无感路由(模型不变,Key 遇限流/额度不足/无权限/不存在自动换下一个);自动识别国际版/国内版令牌;单个 Key 可禁用/停用/恢复/删除/查看;全部密钥分页浏览;模型实际拉取国际版+国内版合并成简化名、按站点分组;额度低于 2 停用到次日 00:01、多 Key 轮换、限流临时失败冷却 5 分钟,并注册为 DSH 模型供应商",
5
5
  "license": "MIT",
6
6
  "author": "Yizutt",