dsh-modelscope-pool 0.0.0-stage → 0.9.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/LICENSE +21 -0
- package/README.md +343 -2
- package/cordis.patch.yml +4 -0
- package/lib/catalog.js +105 -0
- package/lib/i18n.js +231 -0
- package/lib/index.js +1094 -0
- package/lib/pool.js +336 -0
- package/lib/provider.js +233 -0
- package/lib/proxy.js +426 -0
- package/lib/quota.js +131 -0
- package/lib/route.js +104 -0
- package/package.json +111 -4
package/lib/i18n.js
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 界面多语言:跟随 App 的语言(`caps.locale` / `event.locale`),缺省按系统语言判断。
|
|
3
|
+
*
|
|
4
|
+
* 约定:中文用「国际版 / 国内版」,其他语言用「International / China」。
|
|
5
|
+
* 站点名、失败原因这类存盘的字符串保持中文常量,显示时再翻译,
|
|
6
|
+
* 这样 keys.json / cooldowns.json 的数据不受语言影响。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const S = {
|
|
10
|
+
// 面板标题
|
|
11
|
+
poolTitle: ['魔搭 Key 池', 'ModelScope keys'],
|
|
12
|
+
keyPanelTitle: ['Key 详情', 'Key detail'],
|
|
13
|
+
modelsPanelTitle: ['魔搭模型', 'ModelScope models'],
|
|
14
|
+
|
|
15
|
+
// 概览
|
|
16
|
+
metricReady: ['可用', 'Ready'],
|
|
17
|
+
metricCooling: ['冷却', 'Cooling'],
|
|
18
|
+
metricMagicubes: ['魔粒', 'Magicubes'],
|
|
19
|
+
|
|
20
|
+
// 按钮
|
|
21
|
+
btnPrev: ['上一页', 'Prev'],
|
|
22
|
+
btnNext: ['下一页', 'Next'],
|
|
23
|
+
btnRefresh: ['刷新额度', 'Refresh quota'],
|
|
24
|
+
btnTestAll: ['测试全部', 'Test all'],
|
|
25
|
+
btnRestoreAll: ['全部恢复', 'Restore all'],
|
|
26
|
+
btnDisable: ['禁用', 'Disable'],
|
|
27
|
+
btnStop: ['停用', 'Pause'],
|
|
28
|
+
btnRestore: ['恢复', 'Restore'],
|
|
29
|
+
btnDelete: ['删除', 'Delete'],
|
|
30
|
+
btnBack: ['返回 Key 列表', 'Back to keys'],
|
|
31
|
+
btnReloadModels: ['重新拉取模型', 'Reload models'],
|
|
32
|
+
formSubmit: ['添加', 'Add'],
|
|
33
|
+
fieldLabel: ['添加 Key', 'Add key'],
|
|
34
|
+
fieldPlaceholder: ['粘贴 ms- 开头的令牌,可多行', 'Paste ms- tokens, one per line'],
|
|
35
|
+
|
|
36
|
+
// 提示
|
|
37
|
+
hintNoKeys: [
|
|
38
|
+
'还没有 Key:在魔搭复制令牌(ms- 开头)后点输入框上方的「添加 Key」,也可以在下面直接填。',
|
|
39
|
+
'No keys yet. Copy a token that starts with ms- on ModelScope, then tap "Add key" above the composer — or paste it below.',
|
|
40
|
+
],
|
|
41
|
+
hintCooling: [
|
|
42
|
+
'无感切换已开:剩余魔粒低于 2 的 Key 已自动停用到次日 00:01,避免撞 429。到点自动恢复。',
|
|
43
|
+
'Silent rotation is on. Keys with fewer than 2 magicubes are paused until 00:01 next day to avoid 429s, and resume automatically.',
|
|
44
|
+
],
|
|
45
|
+
hintModel: [
|
|
46
|
+
'无感切换已开:模型始终是 {model},Key 遇到限流、额度不足、没权限会自动换下一个;魔粒低于 2 的 Key 会提前停用。',
|
|
47
|
+
'Silent rotation is on. The model stays {model}; on rate limits, exhausted quota or missing permission the gateway moves to the next key. Keys under 2 magicubes are paused early.',
|
|
48
|
+
],
|
|
49
|
+
hintIdle: [
|
|
50
|
+
'点「刷新额度」看剩余魔粒;点下面的模型可以设为默认。',
|
|
51
|
+
'Tap "Refresh quota" to check remaining magicubes; tap a model below to set it as default.',
|
|
52
|
+
],
|
|
53
|
+
routed: [
|
|
54
|
+
'已记下 {n} 个「Key×模型」的失败组合(5 分钟内自动跳过)。',
|
|
55
|
+
'{n} key/model failures remembered and skipped for 5 minutes.',
|
|
56
|
+
],
|
|
57
|
+
hintKeyDetail: [
|
|
58
|
+
'「禁用」= 一直停到点恢复;「停用」= 停到北京时间次日 00:01;「删除」会把令牌从本机文件里去掉。',
|
|
59
|
+
'"Disable" keeps the key off until you restore it; "Pause" stops it until 00:01 next day (Beijing time); "Delete" removes the token from this device.',
|
|
60
|
+
],
|
|
61
|
+
hintKeyFallback: [
|
|
62
|
+
'回到「魔搭 Key 池」面板,点列表里任意一行就会选中它。',
|
|
63
|
+
'Go back to the key list and tap a row to select it.',
|
|
64
|
+
],
|
|
65
|
+
|
|
66
|
+
// 扇形图
|
|
67
|
+
bucketReady: ['可用', 'Ready'],
|
|
68
|
+
bucketLow: ['额度不足', 'Out of quota'],
|
|
69
|
+
bucketInvalid: ['令牌无效', 'Invalid token'],
|
|
70
|
+
bucketOther: ['其他冷却', 'Cooling (other)'],
|
|
71
|
+
unitKey: ['个', ''],
|
|
72
|
+
captionNoKeys: ['还没有 Key', 'No keys yet'],
|
|
73
|
+
shareTitle: ['Key 状态分布', 'Key status'],
|
|
74
|
+
magicLeft: ['剩余', 'Remaining'],
|
|
75
|
+
magicUsed: ['已用', 'Spent'],
|
|
76
|
+
magicCaption: [
|
|
77
|
+
'共 {total} 魔粒({keys} 个 Key × {per});剩余 {left},已用 {used}。未查到的 Key 按满额计。',
|
|
78
|
+
'{total} magicubes total ({keys} keys × {per}); {left} left, {used} spent. Keys not checked yet count as full.',
|
|
79
|
+
],
|
|
80
|
+
magicCaptionEmpty: ['还没有 Key,估不出总魔粒', 'No keys yet, so the magicube total is unknown'],
|
|
81
|
+
captionTotal: [
|
|
82
|
+
'共 {total} 个 Key:可用 {ok},额度不足 {low},其余冷却中',
|
|
83
|
+
'{total} keys: {ok} ready, {low} out of quota, the rest cooling down',
|
|
84
|
+
],
|
|
85
|
+
|
|
86
|
+
// Key 列表
|
|
87
|
+
keyTitleEmpty: ['Key 池(还没有 Key)', 'Key pool (empty)'],
|
|
88
|
+
keyTitlePage: [
|
|
89
|
+
'Key 池 {total} 个 · 第 {page}/{pages} 页(点一行看详情)',
|
|
90
|
+
'{total} keys · page {page}/{pages} (tap a row for detail)',
|
|
91
|
+
],
|
|
92
|
+
keyPager: ['第 {page}/{pages} 页 · 每页 {size} 个', 'Page {page}/{pages} · {size} per page'],
|
|
93
|
+
usedTimes: ['用过 {n} 次', 'used {n}×'],
|
|
94
|
+
quotaLeft: ['额度 {n} 魔粒', '{n} magicubes'],
|
|
95
|
+
quotaUnknown: ['额度未知({error})', 'quota unknown ({error})'],
|
|
96
|
+
quotaUnchecked: ['额度未查', 'quota not checked'],
|
|
97
|
+
selectedMark: ['(已选中)', ' (selected)'],
|
|
98
|
+
failTimes: ['失败 {n} 次', '{n} failed'],
|
|
99
|
+
stateReady: ['可用', 'Ready'],
|
|
100
|
+
stateDisabled: ['已禁用', 'Disabled'],
|
|
101
|
+
coolingUntil: ['冷却至 {time}', 'cooling until {time}'],
|
|
102
|
+
coolingLeft: ['冷却 {time}', 'cooling {time}'],
|
|
103
|
+
|
|
104
|
+
// Key 详情
|
|
105
|
+
focusEmpty: ['还没选中 Key(回「Key 列表」点一行)', 'No key selected — tap a row in the key list'],
|
|
106
|
+
rowToken: ['完整令牌', 'Full token'],
|
|
107
|
+
rowSite: ['站点', 'Site'],
|
|
108
|
+
rowQuota: ['额度', 'Quota'],
|
|
109
|
+
rowState: ['状态', 'State'],
|
|
110
|
+
rowFails: ['失败', 'Failures'],
|
|
111
|
+
quotaDetail: ['{n} 魔粒(总额 {total},冻结 {frozen})', '{n} magicubes (total {total}, frozen {frozen})'],
|
|
112
|
+
quotaFailed: ['查询失败:{error}', 'lookup failed: {error}'],
|
|
113
|
+
quotaNever: ['还没查过', 'not checked yet'],
|
|
114
|
+
stateManual: ['已禁用(点「恢复」放开)', 'Disabled — tap "Restore" to re-enable'],
|
|
115
|
+
failsDetail: ['{n} 次 · {error}{used}', '{n}× · {error}{used}'],
|
|
116
|
+
usedSuffix: [' · 用过 {n} 次', ' · used {n}×'],
|
|
117
|
+
none: ['无', 'none'],
|
|
118
|
+
|
|
119
|
+
// 模型列表
|
|
120
|
+
modelsTitleEmpty: ['模型(还没读到,点下面「重新拉取模型」)', 'Models (not loaded — tap "Reload models")'],
|
|
121
|
+
modelsTitleCount: ['{n} 个模型 · 当前 {current}', '{n} models · current {current}'],
|
|
122
|
+
modelNotPicked: ['未选', 'none'],
|
|
123
|
+
groupCount: ['{n} 个模型', '{n} models'],
|
|
124
|
+
groupBoth: ['国际版 + 国内版都有', 'On both sites'],
|
|
125
|
+
groupUnknown: ['站点未知', 'Unknown site'],
|
|
126
|
+
currentDefault: ['当前默认', 'current default'],
|
|
127
|
+
trailingCurrent: ['当前', 'Current'],
|
|
128
|
+
trailingUse: ['使用', 'Use'],
|
|
129
|
+
|
|
130
|
+
// 操作反馈
|
|
131
|
+
toastQuotaUpdated: ['额度已更新', 'Quota refreshed'],
|
|
132
|
+
toastModelSet: ['默认模型:{model}', 'Default model: {model}'],
|
|
133
|
+
toastModelBad: ['模型名不可用', 'That model name is not usable'],
|
|
134
|
+
toastNoKeys: ['池里还没有 Key', 'The pool has no keys'],
|
|
135
|
+
toastTestDone: ['测试完成:{parts}', 'Test done: {parts}'],
|
|
136
|
+
partOk: ['{ok}/{total} 个 Key 可用', '{ok}/{total} keys ready'],
|
|
137
|
+
partShort: ['{n} 个额度不足', '{n} out of quota'],
|
|
138
|
+
partInvalid: ['{n} 个令牌无效', '{n} invalid'],
|
|
139
|
+
toastRestored: ['已把全部 Key 恢复为可用', 'All keys restored'],
|
|
140
|
+
toastNoPick: ['先在上面点一行选中一个 Key', 'Select a key from the list first'],
|
|
141
|
+
toastNoKeyId: ['没认出这个 Key', 'Could not identify that key'],
|
|
142
|
+
toastDisabled: ['{id} 已禁用,点「恢复」才放开', '{id} disabled — tap "Restore" to re-enable'],
|
|
143
|
+
toastStopped: ['{id} 停用到北京时间次日 00:01', '{id} paused until 00:01 next day (Beijing time)'],
|
|
144
|
+
toastRestoredOne: ['{id} 已恢复可用', '{id} is ready again'],
|
|
145
|
+
toastDeleteArm: ['再点一次「删除」确认删掉 {id}', 'Tap "Delete" once more to remove {id}'],
|
|
146
|
+
toastDeleted: ['{id} 已从本机删除', '{id} removed from this device'],
|
|
147
|
+
toastFirstPage: ['已经是第一页了', 'Already on the first page'],
|
|
148
|
+
toastLastPage: ['已经是最后一页了', 'Already on the last page'],
|
|
149
|
+
toastModelsReloaded: ['模型列表已重新拉取', 'Model list reloaded'],
|
|
150
|
+
toastNoClipboard: ['剪贴板里没有文字', 'Nothing to read in the clipboard'],
|
|
151
|
+
toastNoInput: ['没读到 Key', 'No key found in the input'],
|
|
152
|
+
toastNotMs: ['这不像魔搭令牌(应以 ms- 开头)', 'That does not look like a ModelScope token (should start with ms-)'],
|
|
153
|
+
toastNoNew: ['没有新增 Key', 'No new keys added'],
|
|
154
|
+
|
|
155
|
+
// 添加结果
|
|
156
|
+
addDone: ['新增 {n} 个', '{n} added'],
|
|
157
|
+
addIntl: ['国际版 {n}', 'International {n}'],
|
|
158
|
+
addCn: ['国内版 {n}', 'China {n}'],
|
|
159
|
+
addLow: ['{n} 个额度不足已停用', '{n} out of quota, paused'],
|
|
160
|
+
addInvalid: ['{n} 个令牌无效', '{n} invalid'],
|
|
161
|
+
addSkipped: ['{n} 个重复跳过', '{n} duplicates skipped'],
|
|
162
|
+
joinArrow: ['|', ' · '],
|
|
163
|
+
|
|
164
|
+
// 状态条
|
|
165
|
+
stripEmpty: ['魔搭 未配置', 'ModelScope · none'],
|
|
166
|
+
stripText: ['魔搭 {ok}/{total} · {quota}', 'ModelScope {ok}/{total} · {quota}'],
|
|
167
|
+
stripQuota: ['{n} 魔粒', '{n} left'],
|
|
168
|
+
stripQuotaUnknown: ['额度?', 'quota?'],
|
|
169
|
+
|
|
170
|
+
// 站点与原因
|
|
171
|
+
regionAi: ['国际版', 'International'],
|
|
172
|
+
regionCn: ['国内版', 'China'],
|
|
173
|
+
regionUnknown: ['版本未知', 'Unknown site'],
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const REASONS = {
|
|
177
|
+
额度不足: ['额度不足', 'Out of quota'],
|
|
178
|
+
额度用尽: ['额度用尽', 'Quota exhausted'],
|
|
179
|
+
令牌无效: ['令牌无效', 'Invalid token'],
|
|
180
|
+
被限流: ['被限流', 'Rate limited'],
|
|
181
|
+
模型名不可用: ['模型名不可用', 'Model unavailable'],
|
|
182
|
+
上游超时: ['上游超时', 'Upstream timeout'],
|
|
183
|
+
读取请求失败: ['读取请求失败', 'Request failed'],
|
|
184
|
+
冷却中: ['冷却中', 'Cooling down'],
|
|
185
|
+
手动禁用: ['手动禁用', 'Disabled manually'],
|
|
186
|
+
手动停用: ['手动停用', 'Paused manually'],
|
|
187
|
+
其他冷却: ['其他冷却', 'Cooling (other)'],
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** 系统语言判断:App 没给 locale 时的兜底。 */
|
|
191
|
+
export function detectLang() {
|
|
192
|
+
try {
|
|
193
|
+
const locale = Intl.DateTimeFormat().resolvedOptions().locale
|
|
194
|
+
if (locale) return String(locale).toLowerCase().startsWith('zh') ? 'zh' : 'en'
|
|
195
|
+
} catch {}
|
|
196
|
+
const env = String(process.env.LC_ALL ?? process.env.LC_MESSAGES ?? process.env.LANG ?? '')
|
|
197
|
+
if (env) return env.toLowerCase().startsWith('zh') ? 'zh' : 'en'
|
|
198
|
+
return 'zh'
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** `zh-Hans-CN` / `en-US` / 空 → `'zh'` | `'en'`。 */
|
|
202
|
+
export function pickLang(locale) {
|
|
203
|
+
const raw = String(locale ?? '').trim()
|
|
204
|
+
if (!raw) return detectLang()
|
|
205
|
+
return raw.toLowerCase().startsWith('zh') ? 'zh' : 'en'
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* 造一个翻译函数:`t('key', { n: 3 })`,另带 `t.lang`、`t.region(r)`、`t.reason(r)`。
|
|
210
|
+
*/
|
|
211
|
+
export function makeT(locale) {
|
|
212
|
+
const lang = pickLang(locale)
|
|
213
|
+
const idx = lang === 'en' ? 1 : 0
|
|
214
|
+
const t = (key, vars) => {
|
|
215
|
+
const row = S[key]
|
|
216
|
+
let text = row ? row[idx] : key
|
|
217
|
+
if (vars) {
|
|
218
|
+
for (const k of Object.keys(vars)) text = text.split(`{${k}}`).join(String(vars[k]))
|
|
219
|
+
}
|
|
220
|
+
return text
|
|
221
|
+
}
|
|
222
|
+
t.lang = lang
|
|
223
|
+
t.region = (region) =>
|
|
224
|
+
region === 'ai' ? t('regionAi') : region === 'cn' ? t('regionCn') : t('regionUnknown')
|
|
225
|
+
t.reason = (reason) => {
|
|
226
|
+
const text = String(reason ?? '')
|
|
227
|
+
const row = REASONS[text]
|
|
228
|
+
return row ? row[idx] : text
|
|
229
|
+
}
|
|
230
|
+
return t
|
|
231
|
+
}
|