dsh-modelscope-pool 0.8.0 → 0.10.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 +65 -4
- package/lib/i18n.js +233 -0
- package/lib/index.js +450 -202
- package/lib/provider.js +8 -2
- package/lib/web.js +287 -0
- package/package.json +5 -1
package/lib/index.js
CHANGED
|
@@ -11,6 +11,8 @@ import { registerPoolProvider, isChatModel } from './provider.js'
|
|
|
11
11
|
import { createProxy, quotaLike } from './proxy.js'
|
|
12
12
|
import { createQuotaBook, REGIONS } from './quota.js'
|
|
13
13
|
import { createRouter } from './route.js'
|
|
14
|
+
import { makeT } from './i18n.js'
|
|
15
|
+
import { registerWebAdmin, WEB_ADMIN_PATH } from './web.js'
|
|
14
16
|
|
|
15
17
|
export const name = 'dsh-modelscope-pool'
|
|
16
18
|
|
|
@@ -264,27 +266,30 @@ export function apply(ctx) {
|
|
|
264
266
|
let keyFocus = ''
|
|
265
267
|
let keyPage = 0
|
|
266
268
|
let deleteArmed = ''
|
|
267
|
-
const KEYS_PER_PAGE =
|
|
269
|
+
const KEYS_PER_PAGE = 10
|
|
268
270
|
const pickedKey = () => (keyFocus && pool?.keys.some((k) => k.id === keyFocus) ? keyFocus : '')
|
|
269
271
|
|
|
270
272
|
|
|
271
|
-
|
|
272
|
-
const modelRowOf = (entry, current) => {
|
|
273
|
+
const modelRowOf = (entry, current, t) => {
|
|
273
274
|
const id = String(entry?.id ?? '')
|
|
274
275
|
const isCurrent = id === current
|
|
275
276
|
const regions = entry?.regions ?? []
|
|
276
|
-
const where =
|
|
277
|
+
const where =
|
|
278
|
+
regions.length > 1
|
|
279
|
+
? `${t('regionAi')} + ${t('regionCn')}`
|
|
280
|
+
: t.region(regions[0])
|
|
277
281
|
return {
|
|
278
282
|
title: entry?.name || id,
|
|
279
|
-
subtitle: isCurrent ?
|
|
280
|
-
trailing: isCurrent ? '
|
|
283
|
+
subtitle: isCurrent ? `${t('currentDefault')} · ${where}` : where,
|
|
284
|
+
trailing: isCurrent ? t('trailingCurrent') : t('trailingUse'),
|
|
281
285
|
icon: isCurrent ? 'check' : 'model',
|
|
282
286
|
tone: isCurrent ? 'good' : undefined,
|
|
283
287
|
action: { id: 'pick-model', args: { model: id } },
|
|
284
288
|
}
|
|
285
289
|
}
|
|
286
290
|
|
|
287
|
-
function buildData() {
|
|
291
|
+
function buildData(locale) {
|
|
292
|
+
const t = makeT(locale)
|
|
288
293
|
const s = pool ? pool.status() : { total: 0, available: 0, cooling: 0, keys: [], stats: { requests: 0 } }
|
|
289
294
|
const ids = s.keys.map((k) => k.id)
|
|
290
295
|
const q = quota ? quota.sum(ids) : { available: 0, known: 0 }
|
|
@@ -293,34 +298,74 @@ export function apply(ctx) {
|
|
|
293
298
|
const currentModel = modelEntry?.id ?? model
|
|
294
299
|
const models = (catalog?.entries ?? []).filter((entry) => isChatModel(entry.id))
|
|
295
300
|
const routing = router.snapshot()
|
|
296
|
-
const routed =
|
|
301
|
+
const routed =
|
|
302
|
+
routing.blockedPairs > 0 ? ` ${t('routed', { n: routing.blockedPairs })}` : ''
|
|
297
303
|
const hint =
|
|
298
304
|
s.total === 0
|
|
299
|
-
? '
|
|
305
|
+
? t('hintNoKeys')
|
|
300
306
|
: s.cooling > 0
|
|
301
|
-
?
|
|
307
|
+
? `${t('hintCooling')}${routed}`
|
|
302
308
|
: model
|
|
303
|
-
?
|
|
304
|
-
:
|
|
309
|
+
? `${t('hintModel', { model })}${routed}`
|
|
310
|
+
: `${t('hintIdle')}${routed}`
|
|
311
|
+
|
|
305
312
|
// 扇形图:Key 池的状态分布(可用 / 额度不足 / 令牌无效 / 其他冷却)
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
313
|
+
const order = [
|
|
314
|
+
['ready', t('bucketReady'), (k) => k.state === 'ok'],
|
|
315
|
+
[
|
|
316
|
+
'low',
|
|
317
|
+
t('bucketLow'),
|
|
318
|
+
(k) => k.reason === '额度不足' || k.reason === '额度用尽',
|
|
319
|
+
],
|
|
320
|
+
['invalid', t('bucketInvalid'), (k) => k.reason === '令牌无效'],
|
|
321
|
+
[
|
|
322
|
+
'other',
|
|
323
|
+
t('bucketOther'),
|
|
324
|
+
(k) => k.state !== 'ok' && !['额度不足', '额度用尽', '令牌无效'].includes(k.reason),
|
|
325
|
+
],
|
|
326
|
+
]
|
|
327
|
+
const counts = order.map(([key, label, test]) => ({
|
|
328
|
+
key,
|
|
329
|
+
label,
|
|
330
|
+
value: s.keys.filter(test).length,
|
|
331
|
+
}))
|
|
332
|
+
const live = counts.filter((row) => row.value > 0)
|
|
333
|
+
const lowCount = counts.find((row) => row.key === 'low')?.value ?? 0
|
|
334
|
+
const readyCount = counts.find((row) => row.key === 'ready')?.value ?? 0
|
|
315
335
|
const share = {
|
|
316
336
|
kind: 'donut',
|
|
317
|
-
categories:
|
|
318
|
-
series: [{ name: 'Key', values:
|
|
319
|
-
unit: '
|
|
337
|
+
categories: live.map((row) => row.label),
|
|
338
|
+
series: [{ name: 'Key', values: live.map((row) => row.value) }],
|
|
339
|
+
unit: t('unitKey'),
|
|
320
340
|
caption:
|
|
321
341
|
s.total === 0
|
|
322
|
-
? '
|
|
323
|
-
:
|
|
342
|
+
? t('captionNoKeys')
|
|
343
|
+
: t('captionTotal', { total: s.total, ok: readyCount, low: lowCount }),
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// 魔粒估算:每个 Key 按满额 250 计,剩余取各 Key 剩余之和(没查到的按满额算,不凭空记「已用」)。
|
|
347
|
+
const MAGIC_PER_KEY = 250
|
|
348
|
+
const magicTotal = s.total * MAGIC_PER_KEY
|
|
349
|
+
const magicUnknown = Math.max(0, s.total - q.known)
|
|
350
|
+
const magicLeft = q.known > 0 ? q.available + magicUnknown * MAGIC_PER_KEY : magicTotal
|
|
351
|
+
const magicUsed = Math.max(0, magicTotal - magicLeft)
|
|
352
|
+
const magic = {
|
|
353
|
+
total: magicTotal,
|
|
354
|
+
left: magicLeft,
|
|
355
|
+
used: magicUsed,
|
|
356
|
+
caption:
|
|
357
|
+
s.total === 0
|
|
358
|
+
? t('magicCaptionEmpty')
|
|
359
|
+
: t('magicCaption', {
|
|
360
|
+
total: magicTotal,
|
|
361
|
+
keys: s.total,
|
|
362
|
+
per: MAGIC_PER_KEY,
|
|
363
|
+
left: magicLeft,
|
|
364
|
+
used: magicUsed,
|
|
365
|
+
}),
|
|
366
|
+
brief: t('magicBarCaption', { left: magicLeft, total: magicTotal }),
|
|
367
|
+
categories: [t('magicLeft'), t('magicUsed')],
|
|
368
|
+
series: [{ name: 'Magicubes', values: [magicLeft, magicUsed] }],
|
|
324
369
|
}
|
|
325
370
|
|
|
326
371
|
const pageCount = Math.max(1, Math.ceil(s.total / KEYS_PER_PAGE))
|
|
@@ -336,84 +381,123 @@ export function apply(ctx) {
|
|
|
336
381
|
? {
|
|
337
382
|
title: `${focusRow.id} · ${focusRow.masked}`,
|
|
338
383
|
rows: [
|
|
339
|
-
['
|
|
340
|
-
['
|
|
384
|
+
[t('rowToken'), focusRaw?.key ?? '—'],
|
|
385
|
+
[t('rowSite'), t.region(focusRow.region)],
|
|
341
386
|
[
|
|
342
|
-
'
|
|
387
|
+
t('rowQuota'),
|
|
343
388
|
focusState?.ok
|
|
344
|
-
?
|
|
389
|
+
? t('quotaDetail', {
|
|
390
|
+
n: focusState.available,
|
|
391
|
+
total: focusState.total,
|
|
392
|
+
frozen: focusState.frozen,
|
|
393
|
+
})
|
|
345
394
|
: focusState
|
|
346
|
-
?
|
|
347
|
-
: '
|
|
395
|
+
? t('quotaFailed', { error: focusState.error })
|
|
396
|
+
: t('quotaNever'),
|
|
348
397
|
],
|
|
349
398
|
[
|
|
350
|
-
'
|
|
399
|
+
t('rowState'),
|
|
351
400
|
focusRow.state === 'ok'
|
|
352
|
-
? '
|
|
401
|
+
? t('stateReady')
|
|
353
402
|
: focusRow.manual
|
|
354
|
-
?
|
|
355
|
-
: `${focusRow.reason || '
|
|
403
|
+
? t('stateManual')
|
|
404
|
+
: `${t.reason(focusRow.reason) || t('hintKeyFallback')}${
|
|
405
|
+
focusRow.untilAt ? ` · ${t('coolingUntil', { time: fmtCN(focusRow.untilAt) })}` : ''
|
|
406
|
+
}`,
|
|
407
|
+
],
|
|
408
|
+
[
|
|
409
|
+
t('rowFails'),
|
|
410
|
+
t('failsDetail', {
|
|
411
|
+
n: focusRow.fails,
|
|
412
|
+
error: focusRow.lastError || t('none'),
|
|
413
|
+
used: focusRow.used ? t('usedSuffix', { n: focusRow.used }) : '',
|
|
414
|
+
}),
|
|
356
415
|
],
|
|
357
|
-
['失败', `${focusRow.fails} 次 · ${focusRow.lastError || '无'}${focusRow.used ? ` · 用过 ${focusRow.used} 次` : ''}`],
|
|
358
416
|
],
|
|
359
|
-
hint: '
|
|
417
|
+
hint: t('hintKeyDetail'),
|
|
360
418
|
}
|
|
361
|
-
: { title: '
|
|
419
|
+
: { title: t('focusEmpty'), rows: [], hint: '' }
|
|
362
420
|
|
|
363
421
|
// 模型列表按站点分组:国际版 → 国内版 → 双站,名字用短名。
|
|
364
422
|
const modelRows = []
|
|
365
|
-
const
|
|
366
|
-
for (const region of modelOrder) {
|
|
423
|
+
for (const region of ['ai', 'cn']) {
|
|
367
424
|
const group = models.filter((entry) => entry.regions?.length === 1 && entry.regions[0] === region)
|
|
368
425
|
if (group.length === 0) continue
|
|
369
|
-
modelRows.push({
|
|
370
|
-
|
|
426
|
+
modelRows.push({
|
|
427
|
+
title: t.region(region),
|
|
428
|
+
icon: 'globe',
|
|
429
|
+
tone: 'info',
|
|
430
|
+
subtitle: t('groupCount', { n: group.length }),
|
|
431
|
+
})
|
|
432
|
+
for (const entry of group) modelRows.push(modelRowOf(entry, currentModel, t))
|
|
371
433
|
}
|
|
372
434
|
const both = models.filter((entry) => (entry.regions?.length ?? 0) > 1)
|
|
373
435
|
if (both.length > 0) {
|
|
374
|
-
modelRows.push({
|
|
375
|
-
|
|
436
|
+
modelRows.push({
|
|
437
|
+
title: t('groupBoth'),
|
|
438
|
+
icon: 'globe',
|
|
439
|
+
tone: 'info',
|
|
440
|
+
subtitle: t('groupCount', { n: both.length }),
|
|
441
|
+
})
|
|
442
|
+
for (const entry of both) modelRows.push(modelRowOf(entry, currentModel, t))
|
|
376
443
|
}
|
|
377
444
|
const unknownRegion = models.filter((entry) => (entry.regions?.length ?? 0) === 0)
|
|
378
445
|
if (unknownRegion.length > 0) {
|
|
379
|
-
modelRows.push({
|
|
380
|
-
|
|
446
|
+
modelRows.push({
|
|
447
|
+
title: t('groupUnknown'),
|
|
448
|
+
icon: 'globe',
|
|
449
|
+
tone: 'info',
|
|
450
|
+
subtitle: t('groupCount', { n: unknownRegion.length }),
|
|
451
|
+
})
|
|
452
|
+
for (const entry of unknownRegion) modelRows.push(modelRowOf(entry, currentModel, t))
|
|
381
453
|
}
|
|
382
454
|
|
|
383
455
|
return {
|
|
384
456
|
available: s.available,
|
|
385
457
|
cooling: s.cooling,
|
|
386
458
|
share,
|
|
459
|
+
shareTitle: t('shareTitle'),
|
|
387
460
|
magicubes: q.known > 0 ? String(q.available) : '—',
|
|
461
|
+
magic,
|
|
388
462
|
endpoint: endpoint(),
|
|
389
463
|
hint,
|
|
390
464
|
modelTitle:
|
|
391
465
|
models.length === 0
|
|
392
|
-
? '
|
|
393
|
-
:
|
|
466
|
+
? t('modelsTitleEmpty')
|
|
467
|
+
: t('modelsTitleCount', {
|
|
468
|
+
n: models.length,
|
|
469
|
+
current: modelEntry?.name ?? model ?? t('modelNotPicked'),
|
|
470
|
+
}),
|
|
394
471
|
keyTitle:
|
|
395
472
|
s.total === 0
|
|
396
|
-
? '
|
|
397
|
-
:
|
|
398
|
-
keyPager:
|
|
473
|
+
? t('keyTitleEmpty')
|
|
474
|
+
: t('keyTitlePage', { total: s.total, page: pageNo, pages: pageCount }),
|
|
475
|
+
keyPager:
|
|
476
|
+
s.total > KEYS_PER_PAGE
|
|
477
|
+
? t('keyPager', { page: pageNo, pages: pageCount, size: KEYS_PER_PAGE })
|
|
478
|
+
: '',
|
|
399
479
|
keys: pageKeys.map((k) => {
|
|
400
480
|
const state = quota?.cached(k.id)
|
|
401
|
-
const used =
|
|
402
|
-
const money = state?.ok
|
|
403
|
-
|
|
481
|
+
const used = t('usedTimes', { n: k.used })
|
|
482
|
+
const money = state?.ok
|
|
483
|
+
? t('quotaLeft', { n: state.available })
|
|
484
|
+
: state && !state.ok
|
|
485
|
+
? t('quotaUnknown', { error: String(state.error).slice(0, 24) })
|
|
486
|
+
: t('quotaUnchecked')
|
|
487
|
+
const region = t.region(k.region)
|
|
404
488
|
return {
|
|
405
|
-
title: `${k.id} · ${k.masked}${k.id === keyFocus ? '
|
|
489
|
+
title: `${k.id} · ${k.masked}${k.id === keyFocus ? t('selectedMark') : ''}`,
|
|
406
490
|
subtitle: k.reason
|
|
407
|
-
? `${k.reason}
|
|
491
|
+
? `${t.reason(k.reason)}(${t('failTimes', { n: k.fails })})· ${money} · ${region}`
|
|
408
492
|
: `${money} · ${region} · ${used}`,
|
|
409
493
|
trailing:
|
|
410
494
|
k.state === 'ok'
|
|
411
|
-
? '
|
|
495
|
+
? t('stateReady')
|
|
412
496
|
: k.manual
|
|
413
|
-
? '
|
|
497
|
+
? t('stateDisabled')
|
|
414
498
|
: k.untilAt
|
|
415
|
-
?
|
|
416
|
-
:
|
|
499
|
+
? t('coolingUntil', { time: fmtCN(k.untilAt) })
|
|
500
|
+
: t('coolingLeft', { time: fmtLeft(k.leftMs) }),
|
|
417
501
|
icon: k.state === 'ok' ? 'cloud' : k.manual ? 'remove' : 'time',
|
|
418
502
|
tone: k.state === 'ok' ? 'good' : k.manual ? 'bad' : 'warn',
|
|
419
503
|
action: { id: 'key-open', args: { id: k.id } },
|
|
@@ -423,110 +507,129 @@ export function apply(ctx) {
|
|
|
423
507
|
models: modelRows,
|
|
424
508
|
}
|
|
425
509
|
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
type: '
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
510
|
+
const viewPool = (scope, caps) => {
|
|
511
|
+
const t = makeT(caps?.locale)
|
|
512
|
+
return {
|
|
513
|
+
v: 2,
|
|
514
|
+
type: 'stack',
|
|
515
|
+
title: t('poolTitle'),
|
|
516
|
+
accent: 'teal',
|
|
517
|
+
children: [
|
|
518
|
+
{
|
|
519
|
+
type: 'row',
|
|
520
|
+
children: [
|
|
521
|
+
{ type: 'metric', label: t('metricReady'), value: { $: 'data.available' }, tone: 'good' },
|
|
522
|
+
{ type: 'metric', label: t('metricCooling'), value: { $: 'data.cooling' }, tone: 'warn' },
|
|
523
|
+
{ type: 'metric', label: t('metricMagicubes'), value: { $: 'data.magicubes' }, tone: 'info' },
|
|
524
|
+
],
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
type: 'progress',
|
|
528
|
+
label: t('magicLabel'),
|
|
529
|
+
value: { $: 'data.magic.left' },
|
|
530
|
+
max: { $: 'data.magic.total' },
|
|
531
|
+
caption: { $: 'data.magic.brief' },
|
|
532
|
+
tone: 'good',
|
|
533
|
+
},
|
|
534
|
+
{ type: 'callout', tone: 'info', text: { $: 'data.hint' } },
|
|
535
|
+
{ type: 'text', text: { $: 'data.keyTitle' }, style: { size: 'title', weight: 'bold' } },
|
|
536
|
+
{ type: 'list', items: { $: 'data.keys' } },
|
|
537
|
+
{
|
|
538
|
+
type: 'actions',
|
|
539
|
+
items: [
|
|
540
|
+
{ label: t('btnRefresh'), icon: 'refresh', action: { id: 'refresh' } },
|
|
541
|
+
{ label: t('btnTestAll'), icon: 'bolt', action: { id: 'test-all' } },
|
|
542
|
+
{ label: t('btnRestoreAll'), icon: 'play', action: { id: 'restore-all' } },
|
|
543
|
+
],
|
|
544
|
+
},
|
|
545
|
+
{
|
|
546
|
+
type: 'actions',
|
|
547
|
+
items: [
|
|
548
|
+
{ label: t('btnPrev'), icon: 'collapse', action: { id: 'key-prev' } },
|
|
549
|
+
{ label: t('btnNext'), icon: 'expand', action: { id: 'key-next' } },
|
|
550
|
+
],
|
|
551
|
+
},
|
|
552
|
+
{
|
|
553
|
+
type: 'form',
|
|
554
|
+
submitLabel: t('formSubmit'),
|
|
555
|
+
submit: 'add-form',
|
|
556
|
+
children: [
|
|
557
|
+
{
|
|
558
|
+
type: 'textField',
|
|
559
|
+
label: t('fieldLabel'),
|
|
560
|
+
bind: 'data.newKey',
|
|
561
|
+
placeholder: t('fieldPlaceholder'),
|
|
562
|
+
},
|
|
563
|
+
],
|
|
564
|
+
},
|
|
565
|
+
],
|
|
566
|
+
}
|
|
474
567
|
}
|
|
475
568
|
|
|
476
|
-
const viewKey = {
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
type: '
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
569
|
+
const viewKey = (scope, caps) => {
|
|
570
|
+
const t = makeT(caps?.locale)
|
|
571
|
+
return {
|
|
572
|
+
v: 2,
|
|
573
|
+
type: 'stack',
|
|
574
|
+
title: t('keyPanelTitle'),
|
|
575
|
+
children: [
|
|
576
|
+
{ type: 'text', text: { $: 'data.title' }, style: { size: 'title', weight: 'bold' } },
|
|
577
|
+
{ type: 'keyValue', items: { $: 'data.rows' } },
|
|
578
|
+
{
|
|
579
|
+
type: 'actions',
|
|
580
|
+
items: [
|
|
581
|
+
{ label: t('btnDisable'), icon: 'remove', action: { id: 'key-disable' } },
|
|
582
|
+
{ label: t('btnStop'), icon: 'clock', action: { id: 'key-stop' } },
|
|
583
|
+
{ label: t('btnRestore'), icon: 'play', action: { id: 'key-restore' } },
|
|
584
|
+
{ label: t('btnDelete'), icon: 'delete', action: { id: 'key-delete' } },
|
|
585
|
+
],
|
|
586
|
+
},
|
|
587
|
+
{ type: 'callout', tone: 'warn', text: { $: 'data.hint' } },
|
|
588
|
+
{ type: 'actions', items: [{ label: t('btnBack'), icon: 'list', action: { id: 'key-back' } }] },
|
|
589
|
+
],
|
|
590
|
+
}
|
|
495
591
|
}
|
|
496
592
|
|
|
497
|
-
const viewModels = {
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
593
|
+
const viewModels = (scope, caps) => {
|
|
594
|
+
const t = makeT(caps?.locale)
|
|
595
|
+
return {
|
|
596
|
+
v: 2,
|
|
597
|
+
type: 'stack',
|
|
598
|
+
title: t('modelsPanelTitle'),
|
|
599
|
+
children: [
|
|
600
|
+
{ type: 'text', text: { $: 'data.modelTitle' }, style: { size: 'title', weight: 'bold' } },
|
|
601
|
+
{ type: 'list', items: { $: 'data.models' } },
|
|
602
|
+
{ type: 'actions', items: [{ label: t('btnReloadModels'), icon: 'refresh', action: { id: 'refresh-models' } }] },
|
|
603
|
+
],
|
|
604
|
+
}
|
|
506
605
|
}
|
|
507
606
|
|
|
508
|
-
const buildPoolData = () => {
|
|
509
|
-
const d = buildData()
|
|
607
|
+
const buildPoolData = (locale) => {
|
|
608
|
+
const d = buildData(locale)
|
|
510
609
|
return {
|
|
511
610
|
available: d.available,
|
|
512
611
|
cooling: d.cooling,
|
|
513
612
|
magicubes: d.magicubes,
|
|
514
613
|
share: d.share,
|
|
614
|
+
magic: d.magic,
|
|
515
615
|
hint: d.hint,
|
|
516
616
|
keyTitle: d.keyTitle,
|
|
517
617
|
keys: d.keys,
|
|
518
618
|
focusTitle: d.focus.title,
|
|
519
619
|
}
|
|
520
620
|
}
|
|
521
|
-
const buildKeyData = () => {
|
|
522
|
-
const d = buildData()
|
|
523
|
-
return {
|
|
621
|
+
const buildKeyData = (locale) => {
|
|
622
|
+
const d = buildData(locale)
|
|
623
|
+
return {
|
|
624
|
+
title: d.focus.title,
|
|
625
|
+
rows: d.focus.rows,
|
|
626
|
+
hint: d.focus.hint || makeT(locale)('hintKeyFallback'),
|
|
627
|
+
}
|
|
524
628
|
}
|
|
525
|
-
const buildModelsData = () => {
|
|
526
|
-
const d = buildData()
|
|
629
|
+
const buildModelsData = (locale) => {
|
|
630
|
+
const d = buildData(locale)
|
|
527
631
|
return { modelTitle: d.modelTitle, models: d.models }
|
|
528
632
|
}
|
|
529
|
-
|
|
530
633
|
/** 从粘贴的内容里抠出所有令牌:一行一个也行,逗号/空格混着也行。 */
|
|
531
634
|
const parseKeys = (raw) =>
|
|
532
635
|
String(raw ?? '')
|
|
@@ -535,11 +638,12 @@ export function apply(ctx) {
|
|
|
535
638
|
.filter((t) => /^ms-[A-Za-z0-9._-]{8,}$/.test(t))
|
|
536
639
|
.filter((t, i, arr) => arr.indexOf(t) === i)
|
|
537
640
|
|
|
538
|
-
const addKey = async (raw) => {
|
|
641
|
+
const addKey = async (raw, locale) => {
|
|
642
|
+
const t = makeT(locale)
|
|
539
643
|
const text = String(raw ?? '').trim()
|
|
540
|
-
if (!text) return { toast: '
|
|
644
|
+
if (!text) return { toast: t('toastNoInput') }
|
|
541
645
|
const tokens = parseKeys(text)
|
|
542
|
-
if (tokens.length === 0) return { toast: '
|
|
646
|
+
if (tokens.length === 0) return { toast: t('toastNotMs') }
|
|
543
647
|
|
|
544
648
|
const added = []
|
|
545
649
|
const skipped = []
|
|
@@ -548,9 +652,9 @@ export function apply(ctx) {
|
|
|
548
652
|
if (result.ok) added.push({ id: result.id, token })
|
|
549
653
|
else skipped.push(result.error)
|
|
550
654
|
}
|
|
551
|
-
if (added.length === 0) return { toast: skipped[0] ?? '
|
|
655
|
+
if (added.length === 0) return { toast: skipped[0] ?? t('toastNoNew') }
|
|
552
656
|
|
|
553
|
-
//
|
|
657
|
+
// 加进来就逐个问额度接口:既能认出站点,也能当场发现令牌无效或额度不够。
|
|
554
658
|
const regions = { ai: 0, cn: 0 }
|
|
555
659
|
let invalid = 0
|
|
556
660
|
let low = 0
|
|
@@ -558,7 +662,7 @@ export function apply(ctx) {
|
|
|
558
662
|
const state = await quota.refreshOne(id, token, { force: true })
|
|
559
663
|
if (!state?.ok) {
|
|
560
664
|
invalid++
|
|
561
|
-
pool.markFailure(id, '
|
|
665
|
+
pool.markFailure(id, '\u4ee4\u724c\u65e0\u6548', String(state?.error ?? '').slice(0, 80))
|
|
562
666
|
continue
|
|
563
667
|
}
|
|
564
668
|
if (state.region) {
|
|
@@ -571,13 +675,13 @@ export function apply(ctx) {
|
|
|
571
675
|
refreshPanel()
|
|
572
676
|
refreshStrip()
|
|
573
677
|
|
|
574
|
-
const bits = [
|
|
575
|
-
if (regions.ai > 0) bits.push(
|
|
576
|
-
if (regions.cn > 0) bits.push(
|
|
577
|
-
if (low > 0) bits.push(
|
|
578
|
-
if (invalid > 0) bits.push(
|
|
579
|
-
if (skipped.length > 0) bits.push(
|
|
580
|
-
return { toast: bits.join('
|
|
678
|
+
const bits = [t('addDone', { n: added.length })]
|
|
679
|
+
if (regions.ai > 0) bits.push(t('addIntl', { n: regions.ai }))
|
|
680
|
+
if (regions.cn > 0) bits.push(t('addCn', { n: regions.cn }))
|
|
681
|
+
if (low > 0) bits.push(t('addLow', { n: low }))
|
|
682
|
+
if (invalid > 0) bits.push(t('addInvalid', { n: invalid }))
|
|
683
|
+
if (skipped.length > 0) bits.push(t('addSkipped', { n: skipped.length }))
|
|
684
|
+
return { toast: bits.join(t('joinArrow')) }
|
|
581
685
|
}
|
|
582
686
|
|
|
583
687
|
ctx.inject(['dshGo'], (dshCtx) => {
|
|
@@ -595,14 +699,14 @@ export function apply(ctx) {
|
|
|
595
699
|
} catch {}
|
|
596
700
|
}
|
|
597
701
|
|
|
598
|
-
|
|
599
|
-
const panelData = (build) => (scope, emit) => {
|
|
702
|
+
/** 面板数据推流的通用外壳:内容变了才重绘,面板一关就停。 */
|
|
703
|
+
const panelData = (build) => (scope, emit, caps) => {
|
|
600
704
|
let stopped = false
|
|
601
705
|
let lastJson = ''
|
|
602
706
|
const tick = () => {
|
|
603
707
|
if (stopped) return
|
|
604
708
|
try {
|
|
605
|
-
const payload = build()
|
|
709
|
+
const payload = build(caps?.locale)
|
|
606
710
|
const json = JSON.stringify(payload)
|
|
607
711
|
if (json === lastJson) return // 没有变化就不重绘,界面元素保持稳定
|
|
608
712
|
lastJson = json
|
|
@@ -625,18 +729,23 @@ export function apply(ctx) {
|
|
|
625
729
|
}
|
|
626
730
|
|
|
627
731
|
const panelActions = {
|
|
628
|
-
async refresh() {
|
|
732
|
+
async refresh(scope, event) {
|
|
733
|
+
const t = makeT(event?.locale)
|
|
629
734
|
await Promise.all([refreshQuota(true), refreshCatalog(true)])
|
|
630
|
-
return { toast: '
|
|
735
|
+
return { toast: t('toastQuotaUpdated'), refresh: true }
|
|
631
736
|
},
|
|
632
737
|
async 'pick-model'(scope, event) {
|
|
738
|
+
const t = makeT(event?.locale)
|
|
633
739
|
const model = String(event?.value?.model ?? '')
|
|
634
740
|
const ok = await selectModel(model)
|
|
635
|
-
return ok
|
|
741
|
+
return ok
|
|
742
|
+
? { toast: t('toastModelSet', { model }), refresh: true }
|
|
743
|
+
: { toast: t('toastModelBad') }
|
|
636
744
|
},
|
|
637
|
-
async 'test-all'() {
|
|
745
|
+
async 'test-all'(scope, event) {
|
|
746
|
+
const t = makeT(event?.locale)
|
|
638
747
|
const keys = pool ? [...pool.keys] : []
|
|
639
|
-
if (keys.length === 0) return { toast: '
|
|
748
|
+
if (keys.length === 0) return { toast: t('toastNoKeys') }
|
|
640
749
|
// 注意不能用 /v1/models 验 Key:那个接口是公开的,什么令牌都返回 200。
|
|
641
750
|
// 真正会校验令牌的是额度接口,而且查额度不扣魔粒。
|
|
642
751
|
const rows = await quota.refresh(
|
|
@@ -661,71 +770,79 @@ export function apply(ctx) {
|
|
|
661
770
|
const ok = rows.filter((r) => r.state?.ok && r.state.available >= QUOTA_FLOOR).length
|
|
662
771
|
const invalid = rows.filter((r) => !r.state?.ok).length
|
|
663
772
|
const short = rows.length - ok - invalid
|
|
664
|
-
const parts = [
|
|
665
|
-
if (short > 0) parts.push(
|
|
666
|
-
if (invalid > 0) parts.push(
|
|
667
|
-
return { toast:
|
|
773
|
+
const parts = [t('partOk', { ok, total: keys.length })]
|
|
774
|
+
if (short > 0) parts.push(t('partShort', { n: short }))
|
|
775
|
+
if (invalid > 0) parts.push(t('partInvalid', { n: invalid }))
|
|
776
|
+
return { toast: t('toastTestDone', { parts: parts.join(' · ') }) }
|
|
668
777
|
},
|
|
669
|
-
'restore-all': async () => {
|
|
778
|
+
'restore-all': async (scope, event) => {
|
|
779
|
+
const t = makeT(event?.locale)
|
|
670
780
|
await pool.restoreAll()
|
|
671
781
|
router.clearAll()
|
|
672
782
|
refreshPanel()
|
|
673
783
|
refreshStrip()
|
|
674
|
-
return { toast: '
|
|
784
|
+
return { toast: t('toastRestored') }
|
|
675
785
|
},
|
|
676
786
|
// 单 Key 控制:先点列表里的某一行选中,再用下面四个按钮。
|
|
677
787
|
'key-open'(scope, event) {
|
|
788
|
+
const t = makeT(event?.locale)
|
|
678
789
|
const id = String(event?.value?.id ?? '')
|
|
679
|
-
if (!id) return { toast: '
|
|
790
|
+
if (!id) return { toast: t('toastNoKeyId') }
|
|
680
791
|
keyFocus = id
|
|
681
792
|
deleteArmed = ''
|
|
682
793
|
return { open: 'panel:key', refresh: true }
|
|
683
794
|
},
|
|
684
|
-
'key-prev'() {
|
|
685
|
-
|
|
795
|
+
'key-prev'(scope, event) {
|
|
796
|
+
const t = makeT(event?.locale)
|
|
797
|
+
if (keyPage <= 0) return { toast: t('toastFirstPage') }
|
|
686
798
|
keyPage--
|
|
687
799
|
return { refresh: true }
|
|
688
800
|
},
|
|
689
|
-
'key-next'() {
|
|
801
|
+
'key-next'(scope, event) {
|
|
802
|
+
const t = makeT(event?.locale)
|
|
690
803
|
const pages = Math.max(1, Math.ceil((pool?.keys.length ?? 0) / KEYS_PER_PAGE))
|
|
691
|
-
if (keyPage >= pages - 1) return { toast: '
|
|
804
|
+
if (keyPage >= pages - 1) return { toast: t('toastLastPage') }
|
|
692
805
|
keyPage++
|
|
693
806
|
return { refresh: true }
|
|
694
807
|
},
|
|
695
|
-
'key-disable'() {
|
|
808
|
+
'key-disable'(scope, event) {
|
|
809
|
+
const t = makeT(event?.locale)
|
|
696
810
|
const id = pickedKey()
|
|
697
|
-
if (!id) return { toast: '
|
|
811
|
+
if (!id) return { toast: t('toastNoPick') }
|
|
698
812
|
pool.disable(id, '手动禁用')
|
|
699
813
|
router.clearKey?.(id)
|
|
700
814
|
refreshPanel()
|
|
701
815
|
refreshStrip()
|
|
702
|
-
return { toast:
|
|
816
|
+
return { toast: t('toastDisabled', { id }), refresh: true }
|
|
703
817
|
},
|
|
704
|
-
'key-stop'() {
|
|
818
|
+
'key-stop'(scope, event) {
|
|
819
|
+
const t = makeT(event?.locale)
|
|
705
820
|
const id = pickedKey()
|
|
706
|
-
if (!id) return { toast: '
|
|
821
|
+
if (!id) return { toast: t('toastNoPick') }
|
|
707
822
|
pool.stop(id, '手动停用', nextQuotaResetAt())
|
|
708
823
|
router.clearKey?.(id)
|
|
709
824
|
refreshPanel()
|
|
710
825
|
refreshStrip()
|
|
711
|
-
return { toast:
|
|
826
|
+
return { toast: t('toastStopped', { id }), refresh: true }
|
|
712
827
|
},
|
|
713
|
-
'key-restore'() {
|
|
828
|
+
'key-restore'(scope, event) {
|
|
829
|
+
const t = makeT(event?.locale)
|
|
714
830
|
const id = pickedKey()
|
|
715
|
-
if (!id) return { toast: '
|
|
831
|
+
if (!id) return { toast: t('toastNoPick') }
|
|
716
832
|
pool.restore(id)
|
|
717
833
|
router.clearKey?.(id)
|
|
718
834
|
refreshPanel()
|
|
719
835
|
refreshStrip()
|
|
720
|
-
return { toast:
|
|
836
|
+
return { toast: t('toastRestoredOne', { id }), refresh: true }
|
|
721
837
|
},
|
|
722
|
-
'key-delete'() {
|
|
838
|
+
'key-delete'(scope, event) {
|
|
839
|
+
const t = makeT(event?.locale)
|
|
723
840
|
const id = pickedKey()
|
|
724
|
-
if (!id) return { toast: '
|
|
841
|
+
if (!id) return { toast: t('toastNoPick') }
|
|
725
842
|
// 删令牌不可撤销,要求连点两次。
|
|
726
843
|
if (deleteArmed !== id) {
|
|
727
844
|
deleteArmed = id
|
|
728
|
-
return { toast:
|
|
845
|
+
return { toast: t('toastDeleteArm', { id }), refresh: true }
|
|
729
846
|
}
|
|
730
847
|
deleteArmed = ''
|
|
731
848
|
void pool.remove(id).then(() => {
|
|
@@ -734,22 +851,22 @@ export function apply(ctx) {
|
|
|
734
851
|
refreshPanel()
|
|
735
852
|
refreshStrip()
|
|
736
853
|
})
|
|
737
|
-
return { toast:
|
|
854
|
+
return { toast: t('toastDeleted', { id }), refresh: true }
|
|
738
855
|
},
|
|
739
856
|
'add-form': async (scope, event) => {
|
|
740
857
|
const value = event?.value
|
|
741
858
|
const raw = typeof value === 'string' ? value : value?.newKey ?? value?.value ?? ''
|
|
742
|
-
const result = await addKey(raw)
|
|
859
|
+
const result = await addKey(raw, event?.locale)
|
|
743
860
|
return { ...result, refresh: true }
|
|
744
861
|
},
|
|
745
862
|
'key-back'() {
|
|
746
863
|
return { open: 'panel:pool' }
|
|
747
864
|
},
|
|
748
|
-
async 'refresh-models'() {
|
|
749
|
-
|
|
865
|
+
async 'refresh-models'(scope, event) {
|
|
866
|
+
const t = makeT(event?.locale)
|
|
750
867
|
await refreshCatalog(true)
|
|
751
868
|
refreshPanel()
|
|
752
|
-
return { toast: '
|
|
869
|
+
return { toast: t('toastModelsReloaded'), refresh: true }
|
|
753
870
|
},
|
|
754
871
|
}
|
|
755
872
|
|
|
@@ -758,7 +875,8 @@ export function apply(ctx) {
|
|
|
758
875
|
dshGo.panel('models', { view: viewModels, data: panelData(buildModelsData), actions: panelActions })
|
|
759
876
|
|
|
760
877
|
dshGo.strip('pool-strip', {
|
|
761
|
-
data(scope, emit) {
|
|
878
|
+
data(scope, emit, caps) {
|
|
879
|
+
const t = makeT(caps?.locale)
|
|
762
880
|
let stop = () => {}
|
|
763
881
|
let lastText = ''
|
|
764
882
|
const tick = () => {
|
|
@@ -767,9 +885,13 @@ export function apply(ctx) {
|
|
|
767
885
|
const q = quota ? quota.sum(ids) : { available: 0, known: 0 }
|
|
768
886
|
const payload =
|
|
769
887
|
s.total === 0
|
|
770
|
-
? { text: '
|
|
888
|
+
? { text: t('stripEmpty'), tone: 'warn' }
|
|
771
889
|
: {
|
|
772
|
-
text:
|
|
890
|
+
text: t('stripText', {
|
|
891
|
+
ok: s.available,
|
|
892
|
+
total: s.total,
|
|
893
|
+
quota: q.known > 0 ? t('stripQuota', { n: q.available }) : t('stripQuotaUnknown'),
|
|
894
|
+
}),
|
|
773
895
|
tone: s.available > 0 ? 'good' : 'bad',
|
|
774
896
|
}
|
|
775
897
|
const json = JSON.stringify(payload)
|
|
@@ -789,10 +911,10 @@ export function apply(ctx) {
|
|
|
789
911
|
},
|
|
790
912
|
})
|
|
791
913
|
|
|
792
|
-
dshGo.on('addkey', async (scope) => {
|
|
914
|
+
dshGo.on('addkey', async (scope, event) => {
|
|
793
915
|
const text = String(scope.clipboard ?? '').trim()
|
|
794
|
-
if (!text) return { toast: '
|
|
795
|
-
const result = await addKey(text)
|
|
916
|
+
if (!text) return { toast: makeT(event?.locale)('toastNoClipboard') }
|
|
917
|
+
const result = await addKey(text, event?.locale)
|
|
796
918
|
return { ...result, refresh: 'pool' }
|
|
797
919
|
})
|
|
798
920
|
|
|
@@ -859,7 +981,7 @@ export function apply(ctx) {
|
|
|
859
981
|
})()
|
|
860
982
|
const ok = await registerPoolProvider(llmCtx, {
|
|
861
983
|
providerId: 'modelscope-pool',
|
|
862
|
-
displayName: '
|
|
984
|
+
displayName: makeT()('poolTitle'),
|
|
863
985
|
entryId,
|
|
864
986
|
getPort: () => proxy?.port ?? 0,
|
|
865
987
|
getKey: () => config.apiKey,
|
|
@@ -908,7 +1030,133 @@ export function apply(ctx) {
|
|
|
908
1030
|
})
|
|
909
1031
|
|
|
910
1032
|
// 真正的网关只在引擎里起(检查器没有 webServer,也不会占端口)。
|
|
911
|
-
|
|
1033
|
+
// 网页版 / 桌面版:原生面板只在 dsh-go 里出现,这里补一个同源管理页。
|
|
1034
|
+
ctx.inject(['webServer'], (webCtx) => {
|
|
1035
|
+
const webServer = webCtx?.webServer
|
|
1036
|
+
if (webServer && typeof webServer.register === 'function') {
|
|
1037
|
+
const getState = () => {
|
|
1038
|
+
const status = pool ? pool.status() : { total: 0, available: 0, cooling: 0, keys: [] }
|
|
1039
|
+
const ids = status.keys.map((k) => k.id)
|
|
1040
|
+
const sum = quota ? quota.sum(ids) : { available: 0, known: 0 }
|
|
1041
|
+
const per = 250
|
|
1042
|
+
const total = status.total * per
|
|
1043
|
+
const unknown = Math.max(0, status.total - sum.known)
|
|
1044
|
+
const left = sum.known > 0 ? sum.available + unknown * per : total
|
|
1045
|
+
return {
|
|
1046
|
+
total: status.total,
|
|
1047
|
+
available: status.available,
|
|
1048
|
+
cooling: status.cooling,
|
|
1049
|
+
magic: { total, left, used: Math.max(0, total - left) },
|
|
1050
|
+
model: String(config.model ?? ''),
|
|
1051
|
+
models: (catalog?.entries ?? [])
|
|
1052
|
+
.filter((entry) => isChatModel(entry.id))
|
|
1053
|
+
.map((entry) => ({ id: entry.id, name: entry.name || entry.id, regions: entry.regions ?? [] })),
|
|
1054
|
+
keys: status.keys.map((k) => {
|
|
1055
|
+
const state = quota?.cached(k.id)
|
|
1056
|
+
return {
|
|
1057
|
+
id: k.id,
|
|
1058
|
+
masked: k.masked,
|
|
1059
|
+
region: k.region,
|
|
1060
|
+
state: k.state,
|
|
1061
|
+
reason: k.reason ?? '',
|
|
1062
|
+
manual: Boolean(k.manual),
|
|
1063
|
+
untilAt: k.untilAt ?? 0,
|
|
1064
|
+
used: k.used ?? 0,
|
|
1065
|
+
quota: state?.ok
|
|
1066
|
+
? { ok: true, available: state.available, total: state.total, frozen: state.frozen }
|
|
1067
|
+
: { ok: false, available: 0, total: 0, frozen: 0, error: state?.error ?? '' },
|
|
1068
|
+
}
|
|
1069
|
+
}),
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
const doAction = async (action, payload) => {
|
|
1074
|
+
const id = String(payload?.id ?? '')
|
|
1075
|
+
const after = () => {
|
|
1076
|
+
refreshPanel()
|
|
1077
|
+
refreshStrip()
|
|
1078
|
+
}
|
|
1079
|
+
switch (action) {
|
|
1080
|
+
case 'refresh':
|
|
1081
|
+
await refreshQuota(true)
|
|
1082
|
+
await refreshCatalog(true)
|
|
1083
|
+
after()
|
|
1084
|
+
return makeT()('toastQuotaUpdated')
|
|
1085
|
+
case 'test': {
|
|
1086
|
+
const keys = pool ? [...pool.keys] : []
|
|
1087
|
+
if (keys.length === 0) return makeT()('toastNoKeys')
|
|
1088
|
+
const rows = await quota.refresh(
|
|
1089
|
+
keys.map((k) => ({ id: k.id, key: k.key, region: k.region })),
|
|
1090
|
+
{ force: true },
|
|
1091
|
+
)
|
|
1092
|
+
for (const [i, row] of rows.entries()) {
|
|
1093
|
+
const key = keys[i]
|
|
1094
|
+
if (row.state?.region) await pool.setRegion(key.id, row.state.region)
|
|
1095
|
+
if (!row.state?.ok) {
|
|
1096
|
+
pool.markFailure(key.id, '令牌无效', String(row.state?.error ?? '').slice(0, 80))
|
|
1097
|
+
continue
|
|
1098
|
+
}
|
|
1099
|
+
if (row.state.available < QUOTA_FLOOR) {
|
|
1100
|
+
pool.markFailure(key.id, '额度不足', `剩余 ${row.state.available} 魔粒`, nextQuotaResetAt())
|
|
1101
|
+
continue
|
|
1102
|
+
}
|
|
1103
|
+
pool.restore(key.id)
|
|
1104
|
+
}
|
|
1105
|
+
after()
|
|
1106
|
+
const t = makeT()
|
|
1107
|
+
const ready = rows.filter((r) => r.state?.ok && r.state.available >= QUOTA_FLOOR).length
|
|
1108
|
+
return t('toastTestDone', { parts: t('partOk', { ok: ready, total: keys.length }) })
|
|
1109
|
+
}
|
|
1110
|
+
case 'restoreAll':
|
|
1111
|
+
await pool.restoreAll()
|
|
1112
|
+
router.clearAll()
|
|
1113
|
+
after()
|
|
1114
|
+
return makeT()('toastRestored')
|
|
1115
|
+
case 'disable':
|
|
1116
|
+
if (!id) return makeT()('toastNoKeyId')
|
|
1117
|
+
pool.disable(id, '手动禁用')
|
|
1118
|
+
router.clearKey?.(id)
|
|
1119
|
+
after()
|
|
1120
|
+
return makeT()('toastDisabled', { id })
|
|
1121
|
+
case 'stop':
|
|
1122
|
+
if (!id) return makeT()('toastNoKeyId')
|
|
1123
|
+
pool.stop(id, '手动停用', nextQuotaResetAt())
|
|
1124
|
+
router.clearKey?.(id)
|
|
1125
|
+
after()
|
|
1126
|
+
return makeT()('toastStopped', { id })
|
|
1127
|
+
case 'restore':
|
|
1128
|
+
if (!id) return makeT()('toastNoKeyId')
|
|
1129
|
+
pool.restore(id)
|
|
1130
|
+
router.clearKey?.(id)
|
|
1131
|
+
after()
|
|
1132
|
+
return makeT()('toastRestoredOne', { id })
|
|
1133
|
+
case 'remove':
|
|
1134
|
+
if (!id) return makeT()('toastNoKeyId')
|
|
1135
|
+
await pool.remove(id)
|
|
1136
|
+
quota?.forget?.(id)
|
|
1137
|
+
after()
|
|
1138
|
+
return makeT()('toastDeleted', { id })
|
|
1139
|
+
case 'add': {
|
|
1140
|
+
const result = await addKey(String(payload?.value ?? ''), undefined)
|
|
1141
|
+
return result?.toast ?? makeT()('toastNoNew')
|
|
1142
|
+
}
|
|
1143
|
+
case 'setModel': {
|
|
1144
|
+
const ok = await selectModel(String(payload?.value ?? ''))
|
|
1145
|
+
return ok ? makeT()('toastModelSet', { model: payload.value }) : makeT()('toastModelBad')
|
|
1146
|
+
}
|
|
1147
|
+
default:
|
|
1148
|
+
throw new Error(`unknown action ${action}`)
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
try {
|
|
1153
|
+
ctx.effect(() => registerWebAdmin(webServer, { getState, doAction, logger }))
|
|
1154
|
+
logger?.info?.(`modelscope-pool: 网页/桌面端管理页 ${WEB_ADMIN_PATH}`)
|
|
1155
|
+
} catch (error) {
|
|
1156
|
+
logger?.warn?.(`modelscope-pool: 管理页注册失败 ${String(error?.message ?? error)}`)
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
|
|
912
1160
|
ready
|
|
913
1161
|
.then(async () => {
|
|
914
1162
|
proxy = createProxy({
|