dsh-modelscope-pool 0.10.1 → 0.10.3
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/README.md +2 -2
- package/lib/index.js +116 -57
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -121,7 +121,7 @@
|
|
|
121
121
|
### 方式一:从 npm 安装(推荐)
|
|
122
122
|
|
|
123
123
|
```bash
|
|
124
|
-
dsh plugin --profile web add dsh-modelscope-pool@0.10.
|
|
124
|
+
dsh plugin --profile web add dsh-modelscope-pool@0.10.3
|
|
125
125
|
```
|
|
126
126
|
|
|
127
127
|
一条命令搞定。装好后重启 App(或让插件重新加载),网关会在 `127.0.0.1:8899` 起来。
|
|
@@ -334,7 +334,7 @@ dsh-go 里更进一步:这个插件会把自己注册成一个叫 **魔搭 Key
|
|
|
334
334
|
看看是不是被「手动禁用」了(`manual` 标记)。手动禁用不会被自动恢复流程放开,需要点「恢复」。
|
|
335
335
|
|
|
336
336
|
**点「刷新额度」提示「魔搭 Key 池没有响应」?**
|
|
337
|
-
App 要求点按在 3 秒内返回,而刷新额度要联网逐个查所有 Key 的额度,早就超过 3 秒了。0.10.
|
|
337
|
+
App 要求点按在 3 秒内返回,而刷新额度要联网逐个查所有 Key 的额度,早就超过 3 秒了。0.10.3 起这几个慢操作(刷新额度 / 测试全部 / 重新拉取模型 / 添加 Key)不再在点按里等:点一下立刻在**面板顶部**显示「正在刷新额度」,**3 秒后自动收起**(操作不会中断),跑完再把结果挂上去 3 秒。如果你还在 0.10.0 或更早,升级即可。
|
|
338
338
|
|
|
339
339
|
**端口被占用?**
|
|
340
340
|
改 `config.json` 的 `port`,同时把客户端和供应商配置里的地址一起改掉。
|
package/lib/index.js
CHANGED
|
@@ -144,6 +144,48 @@ export function apply(ctx) {
|
|
|
144
144
|
})
|
|
145
145
|
let refreshPanel = () => {}
|
|
146
146
|
let refreshStrip = () => {}
|
|
147
|
+
|
|
148
|
+
// 慢操作的顶部提示:写进 data.hint,3 秒后自动收起(后台任务不中断)。
|
|
149
|
+
// 必须放在这一层:buildData 在外面读它,放进 inject 回调里会变成未定义变量。
|
|
150
|
+
let notice = ''
|
|
151
|
+
let noticeUntil = 0
|
|
152
|
+
const noticeNow = () => (noticeUntil > Date.now() ? notice : '')
|
|
153
|
+
let noticeTimer = null
|
|
154
|
+
const kicks = new Set()
|
|
155
|
+
const setNotice = (text, ms = 3000) => {
|
|
156
|
+
notice = String(text ?? '')
|
|
157
|
+
noticeUntil = notice ? Date.now() + ms : 0
|
|
158
|
+
try {
|
|
159
|
+
refreshPanel()
|
|
160
|
+
} catch {}
|
|
161
|
+
try {
|
|
162
|
+
refreshStrip()
|
|
163
|
+
} catch {}
|
|
164
|
+
// 立刻推一次:点下就能看到,不用等下一次心跳。
|
|
165
|
+
for (const kick of kicks) {
|
|
166
|
+
try {
|
|
167
|
+
kick()
|
|
168
|
+
} catch {}
|
|
169
|
+
}
|
|
170
|
+
// 到点主动推一次,让提示正好在 3 秒收起,而不是等下一次 1.5 秒心跳。
|
|
171
|
+
if (noticeTimer) clearTimeout(noticeTimer)
|
|
172
|
+
noticeTimer = null
|
|
173
|
+
if (noticeUntil > 0) {
|
|
174
|
+
noticeTimer = setTimeout(() => {
|
|
175
|
+
noticeTimer = null
|
|
176
|
+
if (noticeUntil > Date.now()) return
|
|
177
|
+
try {
|
|
178
|
+
refreshPanel()
|
|
179
|
+
} catch {}
|
|
180
|
+
for (const kick of kicks) {
|
|
181
|
+
try {
|
|
182
|
+
kick()
|
|
183
|
+
} catch {}
|
|
184
|
+
}
|
|
185
|
+
}, ms + 60)
|
|
186
|
+
if (typeof noticeTimer.unref === 'function') noticeTimer.unref()
|
|
187
|
+
}
|
|
188
|
+
}
|
|
147
189
|
// Key 级无感路由:模型不变,只换 Key。
|
|
148
190
|
const router = createRouter({ logger })
|
|
149
191
|
|
|
@@ -300,14 +342,16 @@ const modelRowOf = (entry, current, t) => {
|
|
|
300
342
|
const routing = router.snapshot()
|
|
301
343
|
const routed =
|
|
302
344
|
routing.blockedPairs > 0 ? ` ${t('routed', { n: routing.blockedPairs })}` : ''
|
|
345
|
+
const busy = noticeNow()
|
|
303
346
|
const hint =
|
|
304
|
-
|
|
347
|
+
busy ||
|
|
348
|
+
(s.total === 0
|
|
305
349
|
? t('hintNoKeys')
|
|
306
350
|
: s.cooling > 0
|
|
307
351
|
? `${t('hintCooling')}${routed}`
|
|
308
352
|
: model
|
|
309
353
|
? `${t('hintModel', { model })}${routed}`
|
|
310
|
-
: `${t('hintIdle')}${routed}`
|
|
354
|
+
: `${t('hintIdle')}${routed}`)
|
|
311
355
|
|
|
312
356
|
// 扇形图:Key 池的状态分布(可用 / 额度不足 / 令牌无效 / 其他冷却)
|
|
313
357
|
const order = [
|
|
@@ -515,6 +559,7 @@ const modelRowOf = (entry, current, t) => {
|
|
|
515
559
|
title: t('poolTitle'),
|
|
516
560
|
accent: 'teal',
|
|
517
561
|
children: [
|
|
562
|
+
{ type: 'callout', tone: 'info', text: { $: 'data.hint' } },
|
|
518
563
|
{
|
|
519
564
|
type: 'row',
|
|
520
565
|
children: [
|
|
@@ -531,7 +576,6 @@ const modelRowOf = (entry, current, t) => {
|
|
|
531
576
|
caption: { $: 'data.magic.brief' },
|
|
532
577
|
tone: 'good',
|
|
533
578
|
},
|
|
534
|
-
{ type: 'callout', tone: 'info', text: { $: 'data.hint' } },
|
|
535
579
|
{ type: 'text', text: { $: 'data.keyTitle' }, style: { size: 'title', weight: 'bold' } },
|
|
536
580
|
{ type: 'list', items: { $: 'data.keys' } },
|
|
537
581
|
{
|
|
@@ -716,37 +760,52 @@ const modelRowOf = (entry, current, t) => {
|
|
|
716
760
|
ready
|
|
717
761
|
.then(() => {
|
|
718
762
|
tick()
|
|
763
|
+
kicks.add(tick)
|
|
719
764
|
const timer = setInterval(tick, 1500)
|
|
720
765
|
if (typeof timer.unref === 'function') timer.unref()
|
|
721
|
-
stop = () =>
|
|
766
|
+
stop = () => {
|
|
767
|
+
kicks.delete(tick)
|
|
768
|
+
clearInterval(timer)
|
|
769
|
+
}
|
|
722
770
|
})
|
|
723
771
|
.catch(() => {})
|
|
724
772
|
let stop = () => {}
|
|
725
773
|
return () => {
|
|
726
774
|
stopped = true
|
|
775
|
+
kicks.delete(tick)
|
|
727
776
|
stop()
|
|
728
777
|
}
|
|
729
778
|
}
|
|
730
779
|
|
|
731
|
-
//
|
|
732
|
-
//
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
780
|
+
// 点按必须 3 秒内返回,否则 App 会提示「没有响应」。
|
|
781
|
+
// 联网的慢操作因此不在点按里等:先把一句话挂到面板顶部(data.hint),
|
|
782
|
+
// 3 秒后自动收起,真正的活儿在后台继续跑,跑完再挂一句结果,同样 3 秒收起。
|
|
783
|
+
const running = new Set()
|
|
784
|
+
const runInBackground = (key, label, work) => {
|
|
785
|
+
if (running.has(key)) return { toast: label }
|
|
786
|
+
running.add(key)
|
|
787
|
+
setNotice(label)
|
|
788
|
+
void (async () => {
|
|
789
|
+
let message = ''
|
|
790
|
+
try {
|
|
791
|
+
message = String((await work()) ?? '')
|
|
792
|
+
} catch (error) {
|
|
793
|
+
message = String(error?.message ?? error ?? '')
|
|
794
|
+
} finally {
|
|
795
|
+
running.delete(key)
|
|
796
|
+
}
|
|
797
|
+
setNotice(message)
|
|
798
|
+
})()
|
|
799
|
+
return { refresh: true }
|
|
800
|
+
}
|
|
742
801
|
|
|
743
802
|
const panelActions = {
|
|
744
803
|
refresh(scope, event) {
|
|
745
804
|
const t = makeT(event?.locale)
|
|
746
|
-
return
|
|
805
|
+
return runInBackground('refresh', t('pendingRefresh'), async () => {
|
|
747
806
|
await refreshQuota(true)
|
|
748
807
|
await refreshCatalog(true)
|
|
749
|
-
return
|
|
808
|
+
return t('toastQuotaUpdated')
|
|
750
809
|
})
|
|
751
810
|
},
|
|
752
811
|
async 'pick-model'(scope, event) {
|
|
@@ -759,37 +818,35 @@ const modelRowOf = (entry, current, t) => {
|
|
|
759
818
|
},
|
|
760
819
|
'test-all'(scope, event) {
|
|
761
820
|
const t = makeT(event?.locale)
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
821
|
+
return runInBackground('test', t('pendingTest'), async () => {
|
|
822
|
+
const keys = pool ? [...pool.keys] : []
|
|
823
|
+
if (keys.length === 0) return t('toastNoKeys')
|
|
824
|
+
// 注意不能用 /v1/models 验 Key:那个接口是公开的,什么令牌都返回 200。
|
|
825
|
+
// 真正会校验令牌的是额度接口,而且查额度不扣魔粒。
|
|
826
|
+
const rows = await quota.refresh(
|
|
827
|
+
keys.map((k) => ({ id: k.id, key: k.key, region: k.region })),
|
|
828
|
+
{ force: true },
|
|
829
|
+
)
|
|
830
|
+
for (const [i, row] of rows.entries()) {
|
|
831
|
+
const k = keys[i]
|
|
832
|
+
if (row.state?.region) await pool.setRegion(k.id, row.state.region)
|
|
833
|
+
if (!row.state?.ok) {
|
|
834
|
+
pool.markFailure(k.id, '令牌无效', String(row.state?.error ?? '').slice(0, 80))
|
|
835
|
+
continue
|
|
836
|
+
}
|
|
837
|
+
if (row.state.available < QUOTA_FLOOR) {
|
|
838
|
+
pool.markFailure(k.id, '额度不足', `剩余 ${row.state.available} 魔粒`, nextQuotaResetAt())
|
|
839
|
+
continue
|
|
840
|
+
}
|
|
841
|
+
pool.restore(k.id)
|
|
781
842
|
}
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
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(' · ') }) }
|
|
843
|
+
const ok = rows.filter((r) => r.state?.ok && r.state.available >= QUOTA_FLOOR).length
|
|
844
|
+
const invalid = rows.filter((r) => !r.state?.ok).length
|
|
845
|
+
const short = rows.length - ok - invalid
|
|
846
|
+
const parts = [t('partOk', { ok, total: keys.length })]
|
|
847
|
+
if (short > 0) parts.push(t('partShort', { n: short }))
|
|
848
|
+
if (invalid > 0) parts.push(t('partInvalid', { n: invalid }))
|
|
849
|
+
return t('toastTestDone', { parts: parts.join(' · ') })
|
|
793
850
|
})
|
|
794
851
|
},
|
|
795
852
|
'restore-all': async (scope, event) => {
|
|
@@ -870,13 +927,13 @@ const modelRowOf = (entry, current, t) => {
|
|
|
870
927
|
})
|
|
871
928
|
return { toast: t('toastDeleted', { id }), refresh: true }
|
|
872
929
|
},
|
|
873
|
-
'add-form'
|
|
930
|
+
'add-form'(scope, event) {
|
|
874
931
|
const t = makeT(event?.locale)
|
|
875
932
|
const value = event?.value
|
|
876
933
|
const raw = typeof value === 'string' ? value : value?.newKey ?? value?.value ?? ''
|
|
877
|
-
return
|
|
934
|
+
return runInBackground('add', t('pendingAdd'), async () => {
|
|
878
935
|
const result = await addKey(raw, event?.locale)
|
|
879
|
-
return
|
|
936
|
+
return result?.toast ?? t('toastNoNew')
|
|
880
937
|
})
|
|
881
938
|
},
|
|
882
939
|
'key-back'() {
|
|
@@ -884,13 +941,12 @@ const modelRowOf = (entry, current, t) => {
|
|
|
884
941
|
},
|
|
885
942
|
'refresh-models'(scope, event) {
|
|
886
943
|
const t = makeT(event?.locale)
|
|
887
|
-
return
|
|
944
|
+
return runInBackground('models', t('pendingModels'), async () => {
|
|
888
945
|
await refreshCatalog(true)
|
|
889
|
-
return
|
|
946
|
+
return t('toastModelsReloaded')
|
|
890
947
|
})
|
|
891
948
|
},
|
|
892
949
|
}
|
|
893
|
-
|
|
894
950
|
dshGo.panel('pool', { view: viewPool, data: panelData(buildPoolData), actions: panelActions })
|
|
895
951
|
dshGo.panel('key', { view: viewKey, data: panelData(buildKeyData), actions: panelActions })
|
|
896
952
|
dshGo.panel('models', { view: viewModels, data: panelData(buildModelsData), actions: panelActions })
|
|
@@ -932,11 +988,14 @@ const modelRowOf = (entry, current, t) => {
|
|
|
932
988
|
},
|
|
933
989
|
})
|
|
934
990
|
|
|
935
|
-
dshGo.on('addkey',
|
|
991
|
+
dshGo.on('addkey', (scope, event) => {
|
|
992
|
+
const t = makeT(event?.locale)
|
|
936
993
|
const text = String(scope.clipboard ?? '').trim()
|
|
937
|
-
if (!text) return { toast:
|
|
938
|
-
|
|
939
|
-
|
|
994
|
+
if (!text) return { toast: t('toastNoClipboard') }
|
|
995
|
+
return runInBackground('add', t('pendingAdd'), async () => {
|
|
996
|
+
const result = await addKey(text, event?.locale)
|
|
997
|
+
return result?.toast ?? t('toastNoNew')
|
|
998
|
+
})
|
|
940
999
|
})
|
|
941
1000
|
|
|
942
1001
|
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.
|
|
3
|
+
"version": "0.10.3",
|
|
4
4
|
"description": "魔搭多 Key 聚合网关:无感路由(模型不变,Key 遇限流/额度不足/无权限/不存在自动换下一个);自动识别国际版/国内版令牌;单个 Key 可禁用/停用/恢复/删除/查看;全部密钥分页浏览;模型实际拉取国际版+国内版合并成简化名、按站点分组;额度低于 2 停用到次日 00:01、多 Key 轮换、限流临时失败冷却 5 分钟,并注册为 DSH 模型供应商",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Yizutt",
|