dsh-modelscope-pool 0.10.2 → 0.11.0
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 +12 -4
- package/lib/index.js +122 -54
- package/lib/web.js +71 -52
- package/package.json +2 -23
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.
|
|
124
|
+
dsh plugin --profile web add dsh-modelscope-pool@0.11.0
|
|
125
125
|
```
|
|
126
126
|
|
|
127
127
|
一条命令搞定。装好后重启 App(或让插件重新加载),网关会在 `127.0.0.1:8899` 起来。
|
|
@@ -199,7 +199,7 @@ dsh plugin --profile web add /path/to/dsh-modelscope-pool
|
|
|
199
199
|
|
|
200
200
|
## 界面
|
|
201
201
|
|
|
202
|
-
插件在 dsh-go
|
|
202
|
+
插件在 dsh-go 的输入框上方放了一条常驻状态条,点它直接进「魔搭 Key 池」面板;另外注册了三个原生面板:
|
|
203
203
|
|
|
204
204
|
| 面板 | 位置 | 内容 |
|
|
205
205
|
|---|---|---|
|
|
@@ -207,7 +207,9 @@ dsh plugin --profile web add /path/to/dsh-modelscope-pool
|
|
|
207
207
|
| **Key 详情** | 侧栏 / 功能页 | 点列表任意一行进来:完整令牌、站点、额度明细、状态、失败原因,以及禁用 / 停用 / 恢复 / 删除(删除需连点两次确认) |
|
|
208
208
|
| **魔搭模型** | 对话标签 / 侧栏 / 功能页 | 按「国际版 / 国内版 / 双站」分组的模型列表,点一下设为默认,底部可重新拉取 |
|
|
209
209
|
|
|
210
|
-
|
|
210
|
+
输入框上方那条状态条显示「魔搭 93/95 · 2340 魔粒」,点一下就进面板——添加 Key、刷新额度都在面板里完成。
|
|
211
|
+
|
|
212
|
+
> 0.11.0 去掉了原先输入框上方的两个悬浮按钮:插件侧控制不了 App 给悬浮按钮加的投影,看起来像「胶囊按钮带阴影」。入口合并到状态条,一次点按到位,也不再有两层悬浮元素叠在一起。
|
|
211
213
|
|
|
212
214
|
---
|
|
213
215
|
|
|
@@ -334,7 +336,13 @@ dsh-go 里更进一步:这个插件会把自己注册成一个叫 **魔搭 Key
|
|
|
334
336
|
看看是不是被「手动禁用」了(`manual` 标记)。手动禁用不会被自动恢复流程放开,需要点「恢复」。
|
|
335
337
|
|
|
336
338
|
**点「刷新额度」提示「魔搭 Key 池没有响应」?**
|
|
337
|
-
App 要求点按在 3 秒内返回,而刷新额度要联网逐个查所有 Key 的额度,早就超过 3 秒了。0.10.
|
|
339
|
+
App 要求点按在 3 秒内返回,而刷新额度要联网逐个查所有 Key 的额度,早就超过 3 秒了。0.10.3 起这几个慢操作(刷新额度 / 测试全部 / 重新拉取模型 / 添加 Key)不再在点按里等:点一下立刻在**面板顶部**显示「正在刷新额度」,**3 秒后自动收起**(操作不会中断),跑完再把结果挂上去 3 秒。如果你还在 0.10.0 或更早,升级即可。
|
|
340
|
+
|
|
341
|
+
**一次粘贴很多个 Key,只加进去一部分?**
|
|
342
|
+
每行一个,或者用逗号、空格、分号分隔都行,重复的会自动跳过。0.11.0 起入库是本地写文件、立刻返回,认站点和查额度放到后台,粘 50 个也不会卡住,也不会挡住下一批。
|
|
343
|
+
|
|
344
|
+
**为什么模型列表里没有国内版模型?**
|
|
345
|
+
面板和模型页只显示「池里真有这个站点 Key」的模型。只有国际版 Key 时,国内版独占的模型不会出现;等加进国内版 Key 并查过额度后,它们会自动出现。
|
|
338
346
|
|
|
339
347
|
**端口被占用?**
|
|
340
348
|
改 `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
|
|
|
@@ -288,6 +330,22 @@ const modelRowOf = (entry, current, t) => {
|
|
|
288
330
|
}
|
|
289
331
|
}
|
|
290
332
|
|
|
333
|
+
/**
|
|
334
|
+
* 面板和模型页里该显示哪些模型:只显示「池里真的有这个站点的 Key」的那些。
|
|
335
|
+
* 只有国际版 Key 时就不该冒出国内版模型,反之亦然。
|
|
336
|
+
* 站点一个都还没认出来时(刚导入、还没查过额度)先全部显示。
|
|
337
|
+
*/
|
|
338
|
+
function visibleModels() {
|
|
339
|
+
const entries = (catalog?.entries ?? []).filter((entry) => isChatModel(entry.id))
|
|
340
|
+
const live = new Set((pool?.keys ?? []).map((k) => k.region).filter((r) => r === 'ai' || r === 'cn'))
|
|
341
|
+
if (live.size === 0) return entries
|
|
342
|
+
return entries.filter((entry) => {
|
|
343
|
+
const regions = entry.regions ?? []
|
|
344
|
+
if (regions.length === 0) return true
|
|
345
|
+
return regions.some((region) => live.has(region))
|
|
346
|
+
})
|
|
347
|
+
}
|
|
348
|
+
|
|
291
349
|
function buildData(locale) {
|
|
292
350
|
const t = makeT(locale)
|
|
293
351
|
const s = pool ? pool.status() : { total: 0, available: 0, cooling: 0, keys: [], stats: { requests: 0 } }
|
|
@@ -296,7 +354,7 @@ const modelRowOf = (entry, current, t) => {
|
|
|
296
354
|
const model = String(config.model ?? '')
|
|
297
355
|
const modelEntry = catalog?.resolve(model)
|
|
298
356
|
const currentModel = modelEntry?.id ?? model
|
|
299
|
-
const models = (
|
|
357
|
+
const models = visibleModels()
|
|
300
358
|
const routing = router.snapshot()
|
|
301
359
|
const routed =
|
|
302
360
|
routing.blockedPairs > 0 ? ` ${t('routed', { n: routing.blockedPairs })}` : ''
|
|
@@ -474,6 +532,7 @@ const modelRowOf = (entry, current, t) => {
|
|
|
474
532
|
s.total === 0
|
|
475
533
|
? t('keyTitleEmpty')
|
|
476
534
|
: t('keyTitlePage', { total: s.total, page: pageNo, pages: pageCount }),
|
|
535
|
+
newKey: '',
|
|
477
536
|
keyPager:
|
|
478
537
|
s.total > KEYS_PER_PAGE
|
|
479
538
|
? t('keyPager', { page: pageNo, pages: pageCount, size: KEYS_PER_PAGE })
|
|
@@ -618,6 +677,7 @@ const modelRowOf = (entry, current, t) => {
|
|
|
618
677
|
keyTitle: d.keyTitle,
|
|
619
678
|
keys: d.keys,
|
|
620
679
|
focusTitle: d.focus.title,
|
|
680
|
+
newKey: '',
|
|
621
681
|
}
|
|
622
682
|
}
|
|
623
683
|
const buildKeyData = (locale) => {
|
|
@@ -640,6 +700,20 @@ const modelRowOf = (entry, current, t) => {
|
|
|
640
700
|
.filter((t) => /^ms-[A-Za-z0-9._-]{8,}$/.test(t))
|
|
641
701
|
.filter((t, i, arr) => arr.indexOf(t) === i)
|
|
642
702
|
|
|
703
|
+
/** 表单提交回来的值形状不固定:字符串、{newKey}、{'data.newKey'} 都见过,统统认。 */
|
|
704
|
+
const pickText = (value) => {
|
|
705
|
+
if (typeof value === 'string') return value
|
|
706
|
+
if (!value || typeof value !== 'object') return ''
|
|
707
|
+
for (const key of ['newKey', 'data.newKey', 'value', 'text', 'input']) {
|
|
708
|
+
if (typeof value[key] === 'string' && value[key].trim()) return value[key]
|
|
709
|
+
}
|
|
710
|
+
for (const item of Object.values(value)) {
|
|
711
|
+
if (typeof item === 'string' && item.trim()) return item
|
|
712
|
+
}
|
|
713
|
+
return ''
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
/** 从泡里抠令牌、批量入库。入库是本地写文件,很快;认站点和查额度放后台。 */
|
|
643
717
|
const addKey = async (raw, locale) => {
|
|
644
718
|
const t = makeT(locale)
|
|
645
719
|
const text = String(raw ?? '').trim()
|
|
@@ -656,33 +730,43 @@ const modelRowOf = (entry, current, t) => {
|
|
|
656
730
|
}
|
|
657
731
|
if (added.length === 0) return { toast: skipped[0] ?? t('toastNoNew') }
|
|
658
732
|
|
|
659
|
-
//
|
|
660
|
-
const
|
|
661
|
-
|
|
662
|
-
let low = 0
|
|
663
|
-
for (const { id, token } of added) {
|
|
664
|
-
const state = await quota.refreshOne(id, token, { force: true })
|
|
665
|
-
if (!state?.ok) {
|
|
666
|
-
invalid++
|
|
667
|
-
pool.markFailure(id, '\u4ee4\u724c\u65e0\u6548', String(state?.error ?? '').slice(0, 80))
|
|
668
|
-
continue
|
|
669
|
-
}
|
|
670
|
-
if (state.region) {
|
|
671
|
-
regions[state.region]++
|
|
672
|
-
await pool.setRegion(id, state.region)
|
|
673
|
-
}
|
|
674
|
-
applyQuota(id, state)
|
|
675
|
-
if (state.available < QUOTA_FLOOR) low++
|
|
676
|
-
}
|
|
733
|
+
// 先给用户一个立刻的答复,几十个 Key 也不用等。
|
|
734
|
+
const bits = [t('addDone', { n: added.length })]
|
|
735
|
+
if (skipped.length > 0) bits.push(t('addSkipped', { n: skipped.length }))
|
|
677
736
|
refreshPanel()
|
|
678
737
|
refreshStrip()
|
|
679
738
|
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
739
|
+
// 后台逐个认站点 / 查额度:失败的不阻塞入库,也不影响下一批。
|
|
740
|
+
void (async () => {
|
|
741
|
+
let invalid = 0
|
|
742
|
+
let low = 0
|
|
743
|
+
const regions = { ai: 0, cn: 0 }
|
|
744
|
+
for (const { id, token } of added) {
|
|
745
|
+
try {
|
|
746
|
+
const state = await quota.refreshOne(id, token, { force: true })
|
|
747
|
+
if (!state?.ok) {
|
|
748
|
+
invalid++
|
|
749
|
+
pool.markFailure(id, '\u4ee4\u724c\u65e0\u6548', String(state?.error ?? '').slice(0, 80))
|
|
750
|
+
continue
|
|
751
|
+
}
|
|
752
|
+
if (state.region) {
|
|
753
|
+
regions[state.region]++
|
|
754
|
+
await pool.setRegion(id, state.region)
|
|
755
|
+
}
|
|
756
|
+
applyQuota(id, state)
|
|
757
|
+
if (state.available < QUOTA_FLOOR) low++
|
|
758
|
+
} catch {}
|
|
759
|
+
}
|
|
760
|
+
refreshPanel()
|
|
761
|
+
refreshStrip()
|
|
762
|
+
const tail = [t('addDone', { n: added.length })]
|
|
763
|
+
if (regions.ai > 0) tail.push(t('addIntl', { n: regions.ai }))
|
|
764
|
+
if (regions.cn > 0) tail.push(t('addCn', { n: regions.cn }))
|
|
765
|
+
if (low > 0) tail.push(t('addLow', { n: low }))
|
|
766
|
+
if (invalid > 0) tail.push(t('addInvalid', { n: invalid }))
|
|
767
|
+
setNotice(tail.join(t('joinArrow')))
|
|
768
|
+
})()
|
|
769
|
+
|
|
686
770
|
return { toast: bits.join(t('joinArrow')) }
|
|
687
771
|
}
|
|
688
772
|
|
|
@@ -718,14 +802,19 @@ const modelRowOf = (entry, current, t) => {
|
|
|
718
802
|
ready
|
|
719
803
|
.then(() => {
|
|
720
804
|
tick()
|
|
805
|
+
kicks.add(tick)
|
|
721
806
|
const timer = setInterval(tick, 1500)
|
|
722
807
|
if (typeof timer.unref === 'function') timer.unref()
|
|
723
|
-
stop = () =>
|
|
808
|
+
stop = () => {
|
|
809
|
+
kicks.delete(tick)
|
|
810
|
+
clearInterval(timer)
|
|
811
|
+
}
|
|
724
812
|
})
|
|
725
813
|
.catch(() => {})
|
|
726
814
|
let stop = () => {}
|
|
727
815
|
return () => {
|
|
728
816
|
stopped = true
|
|
817
|
+
kicks.delete(tick)
|
|
729
818
|
stop()
|
|
730
819
|
}
|
|
731
820
|
}
|
|
@@ -733,15 +822,6 @@ const modelRowOf = (entry, current, t) => {
|
|
|
733
822
|
// 点按必须 3 秒内返回,否则 App 会提示「没有响应」。
|
|
734
823
|
// 联网的慢操作因此不在点按里等:先把一句话挂到面板顶部(data.hint),
|
|
735
824
|
// 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
825
|
const running = new Set()
|
|
746
826
|
const runInBackground = (key, label, work) => {
|
|
747
827
|
if (running.has(key)) return { toast: label }
|
|
@@ -889,14 +969,12 @@ const modelRowOf = (entry, current, t) => {
|
|
|
889
969
|
})
|
|
890
970
|
return { toast: t('toastDeleted', { id }), refresh: true }
|
|
891
971
|
},
|
|
892
|
-
'add-form'(scope, event) {
|
|
972
|
+
async 'add-form'(scope, event) {
|
|
893
973
|
const t = makeT(event?.locale)
|
|
894
|
-
const
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
return result?.toast ?? t('toastNoNew')
|
|
899
|
-
})
|
|
974
|
+
const raw = pickText(event?.value)
|
|
975
|
+
if (!raw) return { toast: t('toastNoInput'), refresh: true }
|
|
976
|
+
const result = await addKey(raw, event?.locale)
|
|
977
|
+
return { toast: result?.toast ?? t('toastNoNew'), refresh: true }
|
|
900
978
|
},
|
|
901
979
|
'key-back'() {
|
|
902
980
|
return { open: 'panel:pool' }
|
|
@@ -950,16 +1028,6 @@ const modelRowOf = (entry, current, t) => {
|
|
|
950
1028
|
},
|
|
951
1029
|
})
|
|
952
1030
|
|
|
953
|
-
dshGo.on('addkey', (scope, event) => {
|
|
954
|
-
const t = makeT(event?.locale)
|
|
955
|
-
const text = String(scope.clipboard ?? '').trim()
|
|
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
|
-
})
|
|
961
|
-
})
|
|
962
|
-
|
|
963
1031
|
dshGo.on('open-pool', () => ({ open: 'panel:pool' }))
|
|
964
1032
|
})
|
|
965
1033
|
|
|
@@ -1027,8 +1095,8 @@ const modelRowOf = (entry, current, t) => {
|
|
|
1027
1095
|
entryId,
|
|
1028
1096
|
getPort: () => proxy?.port ?? 0,
|
|
1029
1097
|
getKey: () => config.apiKey,
|
|
1030
|
-
getModels: () =>
|
|
1031
|
-
getEntries: () =>
|
|
1098
|
+
getModels: () => visibleModels().map((entry) => entry.id),
|
|
1099
|
+
getEntries: () => visibleModels(),
|
|
1032
1100
|
logger,
|
|
1033
1101
|
})
|
|
1034
1102
|
providerState = {
|
package/lib/web.js
CHANGED
|
@@ -9,64 +9,79 @@ const HTML = [
|
|
|
9
9
|
'<!doctype html>',
|
|
10
10
|
'<html lang="zh-CN"><head>',
|
|
11
11
|
'<meta charset="utf-8">',
|
|
12
|
-
'<meta name="viewport" content="width=device-width,initial-scale=1">',
|
|
12
|
+
'<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">',
|
|
13
13
|
'<title>ModelScope key pool</title>',
|
|
14
14
|
'<style>',
|
|
15
|
-
':root{color-scheme:light dark;--bg:#fff;--fg:#
|
|
16
|
-
'@media (prefers-color-scheme:dark){:root{--bg:#
|
|
17
|
-
'*{box-sizing:border-box}',
|
|
18
|
-
'body{margin:0;
|
|
19
|
-
'
|
|
20
|
-
'
|
|
21
|
-
'
|
|
22
|
-
'.
|
|
23
|
-
'.
|
|
24
|
-
'.
|
|
25
|
-
'.
|
|
26
|
-
'.
|
|
27
|
-
'.
|
|
28
|
-
'
|
|
29
|
-
'
|
|
30
|
-
'
|
|
31
|
-
'
|
|
32
|
-
'
|
|
33
|
-
'
|
|
34
|
-
'.
|
|
35
|
-
'.
|
|
36
|
-
'
|
|
37
|
-
'
|
|
38
|
-
'
|
|
39
|
-
'
|
|
40
|
-
'
|
|
41
|
-
'
|
|
42
|
-
'
|
|
43
|
-
'
|
|
44
|
-
'
|
|
45
|
-
'
|
|
46
|
-
'
|
|
47
|
-
'
|
|
48
|
-
'
|
|
49
|
-
'
|
|
50
|
-
'
|
|
51
|
-
'
|
|
52
|
-
'
|
|
53
|
-
'
|
|
54
|
-
'
|
|
15
|
+
':root{color-scheme:light dark;--bg:#f6f7f9;--card:#fff;--fg:#14171d;--muted:#6b7280;--line:#e5e7eb;--accent:#0f766e;--accent-fg:#fff;--good:#15803d;--warn:#a16207;--bad:#b91c1c;--chip:#eef1f5}',
|
|
16
|
+
'@media (prefers-color-scheme:dark){:root{--bg:#14161a;--card:#1c1f25;--fg:#e9ebef;--muted:#9aa1ad;--line:#2b3038;--accent:#2dd4bf;--accent-fg:#0b0d10;--good:#4ade80;--warn:#fbbf24;--bad:#f87171;--chip:#242932}}',
|
|
17
|
+
'*{box-sizing:border-box;-webkit-tap-highlight-color:transparent}',
|
|
18
|
+
'body{margin:0;background:var(--bg);color:var(--fg);font:15px/1.55 -apple-system,system-ui,"Segoe UI",Roboto,"Noto Sans SC",sans-serif;padding:0 14px 40px}',
|
|
19
|
+
'.wrap{max-width:760px;margin:0 auto}',
|
|
20
|
+
'header{position:sticky;top:0;z-index:5;display:flex;align-items:center;gap:10px;padding:14px 0 10px;background:linear-gradient(var(--bg) 78%,transparent)}',
|
|
21
|
+
'header h1{margin:0;font-size:19px;font-weight:650;letter-spacing:.2px}',
|
|
22
|
+
'header .sub{margin:2px 0 0;color:var(--muted);font-size:12px}',
|
|
23
|
+
'header .grow{flex:1;min-width:0}',
|
|
24
|
+
'.ghost{background:var(--chip);border:1px solid var(--line);color:var(--fg);border-radius:999px;padding:6px 12px;font:inherit;font-size:13px}',
|
|
25
|
+
'.cards{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin:6px 0 14px}',
|
|
26
|
+
'.card{background:var(--card);border:1px solid var(--line);border-radius:14px;padding:12px 13px}',
|
|
27
|
+
'.card b{display:block;font-size:24px;line-height:1.15;font-weight:680;font-variant-numeric:tabular-nums}',
|
|
28
|
+
'.card span{display:block;margin-top:2px;color:var(--muted);font-size:12px}',
|
|
29
|
+
'.card.ok b{color:var(--good)}.card.warn b{color:var(--warn)}.card.info b{color:var(--accent)}',
|
|
30
|
+
'.magic{background:var(--card);border:1px solid var(--line);border-radius:14px;padding:12px 13px;margin-bottom:14px}',
|
|
31
|
+
'.magic .row{display:flex;justify-content:space-between;gap:10px;font-size:13px;color:var(--muted)}',
|
|
32
|
+
'.magic .row b{color:var(--fg);font-weight:600;font-size:13px}',
|
|
33
|
+
'.bar{height:9px;border-radius:99px;background:var(--chip);overflow:hidden;margin-top:9px}',
|
|
34
|
+
'.bar i{display:block;height:100%;border-radius:99px;background:linear-gradient(90deg,var(--accent),color-mix(in srgb,var(--accent) 55%,#fff));transition:width .35s ease}',
|
|
35
|
+
'.toolbar{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:14px}',
|
|
36
|
+
'button{font:inherit;border-radius:11px;border:1px solid var(--line);background:var(--card);color:var(--fg);padding:9px 14px;cursor:pointer}',
|
|
37
|
+
'button:active{transform:scale(.985)}',
|
|
38
|
+
'button.pri{background:var(--accent);border-color:var(--accent);color:var(--accent-fg);font-weight:600}',
|
|
39
|
+
'.tablewrap{background:var(--card);border:1px solid var(--line);border-radius:14px;overflow:hidden}',
|
|
40
|
+
'table{width:100%;border-collapse:collapse;font-size:13.5px}',
|
|
41
|
+
'thead th{position:sticky;top:0;background:var(--card);color:var(--muted);font-weight:500;font-size:12px;text-align:left;padding:10px 12px;border-bottom:1px solid var(--line);white-space:nowrap}',
|
|
42
|
+
'tbody td{padding:10px 12px;border-bottom:1px solid var(--line);vertical-align:middle}',
|
|
43
|
+
'tbody tr:last-child td{border-bottom:0}',
|
|
44
|
+
'tbody tr:nth-child(even){background:color-mix(in srgb,var(--chip) 45%,transparent)}',
|
|
45
|
+
'.mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12.5px}',
|
|
46
|
+
'.ok{color:var(--good)}.warn{color:var(--warn)}.bad{color:var(--bad)}.muted{color:var(--muted)}',
|
|
47
|
+
'td.ops{text-align:right;white-space:nowrap}',
|
|
48
|
+
'td.ops button{padding:5px 9px;font-size:12px;border-radius:8px;margin-left:5px;background:transparent}',
|
|
49
|
+
'td.ops button:hover{background:var(--chip)}',
|
|
50
|
+
'td.ops button.danger{color:var(--bad);border-color:color-mix(in srgb,var(--bad) 35%,var(--line))}',
|
|
51
|
+
'.pager{display:flex;align-items:center;justify-content:center;gap:12px;margin:12px 0 18px;color:var(--muted);font-size:13px}',
|
|
52
|
+
'.pager button{padding:7px 14px}',
|
|
53
|
+
'.panel{background:var(--card);border:1px solid var(--line);border-radius:14px;padding:13px;margin-bottom:14px}',
|
|
54
|
+
'.panel h2{margin:0 0 9px;font-size:14px;font-weight:600}',
|
|
55
|
+
'textarea,select{width:100%;background:var(--bg);color:var(--fg);border:1px solid var(--line);border-radius:11px;padding:10px 12px;font:inherit;font-size:14px}',
|
|
56
|
+
'textarea{min-height:86px;resize:vertical;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12.5px}',
|
|
57
|
+
'.panel .foot{display:flex;justify-content:space-between;align-items:center;gap:10px;margin-top:9px}',
|
|
58
|
+
'.panel .foot span{color:var(--muted);font-size:12px}',
|
|
59
|
+
'.toast{position:fixed;left:50%;bottom:26px;transform:translateX(-50%) translateY(8px);background:var(--fg);color:var(--bg);padding:10px 18px;border-radius:999px;font-size:13px;opacity:0;transition:opacity .18s,transform .18s;pointer-events:none;max-width:90vw;z-index:20}',
|
|
60
|
+
'.toast.on{opacity:.96;transform:translateX(-50%) translateY(0)}',
|
|
61
|
+
'@media (max-width:430px){.cards{grid-template-columns:repeat(3,1fr);gap:8px}.card{padding:10px}.card b{font-size:20px}td.ops button{padding:5px 7px;margin-left:4px}}',
|
|
62
|
+
'</style></head><body><div class="wrap">',
|
|
63
|
+
'<header><div class="grow"><h1 id="title">魔搭 Key 池</h1><p class="sub" id="sub"></p></div>',
|
|
64
|
+
'<button class="ghost" id="b-lang">English</button></header>',
|
|
65
|
+
'<section class="cards">',
|
|
66
|
+
'<div class="card ok"><b id="s-ok">–</b><span id="l-ok"></span></div>',
|
|
67
|
+
'<div class="card warn"><b id="s-cool">–</b><span id="l-cool"></span></div>',
|
|
68
|
+
'<div class="card info"><b id="s-magic">–</b><span id="l-magic"></span></div>',
|
|
69
|
+
'</section>',
|
|
70
|
+
'<section class="magic"><div class="row"><b id="magic-label"></b><span id="magic-cap"></span></div>',
|
|
71
|
+
'<div class="bar"><i id="bar" style="width:0"></i></div></section>',
|
|
72
|
+
'<div class="toolbar">',
|
|
55
73
|
'<button class="pri" data-act="refresh" id="b-refresh"></button>',
|
|
56
74
|
'<button data-act="test" id="b-test"></button>',
|
|
57
75
|
'<button data-act="restoreAll" id="b-restore"></button>',
|
|
58
|
-
'<button id="b-lang"></button>',
|
|
59
76
|
'</div>',
|
|
60
|
-
'<table><thead><tr id="head"></tr></thead><tbody id="rows"></tbody></table>',
|
|
61
|
-
'<div class="
|
|
62
|
-
'<
|
|
63
|
-
'<
|
|
64
|
-
'<div class="
|
|
65
|
-
'
|
|
66
|
-
'<details><summary id="model-sum"></summary>',
|
|
67
|
-
'<div class="row"><select id="model-sel"></select></div>',
|
|
68
|
-
'</details>',
|
|
77
|
+
'<div class="tablewrap"><table><thead><tr id="head"></tr></thead><tbody id="rows"></tbody></table></div>',
|
|
78
|
+
'<div class="pager"><button id="b-prev"></button><span id="page-info"></span><button id="b-next"></button></div>',
|
|
79
|
+
'<section class="panel"><h2 id="add-sum"></h2>',
|
|
80
|
+
'<textarea id="add-text" placeholder="ms-..."></textarea>',
|
|
81
|
+
'<div class="foot"><span id="add-hint"></span><button class="pri" id="b-add"></button></div></section>',
|
|
82
|
+
'<section class="panel"><h2 id="model-sum"></h2><select id="model-sel"></select></section>',
|
|
69
83
|
'<div class="toast" id="toast"></div>',
|
|
84
|
+
'</div>',
|
|
70
85
|
'<script>',
|
|
71
86
|
'var L=null,S=null,PAGE=0,PER=20,ARM="";',
|
|
72
87
|
'var DICT={',
|
|
@@ -109,13 +124,17 @@ const HTML = [
|
|
|
109
124
|
' document.getElementById("l-magic").textContent=L.magic;',
|
|
110
125
|
' var pct=S.magic.total>0?Math.max(0,Math.min(100,S.magic.left/S.magic.total*100)):0;',
|
|
111
126
|
' document.getElementById("bar").style.width=pct.toFixed(1)+"%";',
|
|
112
|
-
' document.getElementById("magic-
|
|
127
|
+
' document.getElementById("magic-label").textContent=L.usage;',
|
|
128
|
+
' document.getElementById("magic-cap").textContent=L.left+" "+S.magic.left+" / "+L.total+" "+S.magic.total;',
|
|
129
|
+
' document.getElementById("add-hint").textContent=L.addHint;',
|
|
113
130
|
' document.getElementById("b-refresh").textContent=L.refresh;',
|
|
114
131
|
' document.getElementById("b-test").textContent=L.test;',
|
|
115
132
|
' document.getElementById("b-restore").textContent=L.restoreAll;',
|
|
116
133
|
' document.getElementById("b-prev").textContent=L.prev;',
|
|
117
134
|
' document.getElementById("b-next").textContent=L.next;',
|
|
118
135
|
' document.getElementById("b-add").textContent=L.add;',
|
|
136
|
+
' var pg=document.getElementById("page-info");',
|
|
137
|
+
' if(pg){var total=Math.max(1,Math.ceil(S.keys.length/PER));pg.textContent=L.page.replace("{p}",String(PAGE+1)).replace("{n}",String(total))}',
|
|
119
138
|
' document.getElementById("add-sum").textContent=L.addSum;',
|
|
120
139
|
' document.getElementById("model-sum").textContent=L.modelSum;',
|
|
121
140
|
' var head=document.getElementById("head");head.innerHTML="";',
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-modelscope-pool",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "魔搭多 Key
|
|
3
|
+
"version": "0.11.0",
|
|
4
|
+
"description": "魔搭多 Key 聚合网关 + DSH 原生插件:模型不变,Key 遇限流、额度不足、无权限或令牌失效自动无感换下一个;自动识别国际版/国内版令牌;Key 池管理、魔粒用量、多语言界面;也可在网页版/桌面版管理。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Yizutt",
|
|
7
7
|
"repository": {
|
|
@@ -39,28 +39,7 @@
|
|
|
39
39
|
"en": "ModelScope keys"
|
|
40
40
|
},
|
|
41
41
|
"icon": "cloud",
|
|
42
|
-
"wants": [
|
|
43
|
-
"clipboard"
|
|
44
|
-
],
|
|
45
42
|
"contributes": {
|
|
46
|
-
"composer.actions": [
|
|
47
|
-
{
|
|
48
|
-
"id": "addkey",
|
|
49
|
-
"label": {
|
|
50
|
-
"zh": "添加 Key",
|
|
51
|
-
"en": "Add key"
|
|
52
|
-
},
|
|
53
|
-
"icon": "paste"
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
"id": "open-pool",
|
|
57
|
-
"label": {
|
|
58
|
-
"zh": "Key 池",
|
|
59
|
-
"en": "Key pool"
|
|
60
|
-
},
|
|
61
|
-
"icon": "cloud"
|
|
62
|
-
}
|
|
63
|
-
],
|
|
64
43
|
"composer.strip": [
|
|
65
44
|
{
|
|
66
45
|
"id": "pool-strip",
|