@dsh-plugins/dsh-llm-hub 1.3.0 → 1.3.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.
- package/CHANGELOG.md +25 -0
- package/README.md +11 -4
- package/lib/client.js +838 -404
- package/lib/index.js +48 -31
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -105,6 +105,12 @@ window.__ModuleLoader__.load({
|
|
|
105
105
|
configuredModels: '已配',
|
|
106
106
|
modelsUnit: '个模型',
|
|
107
107
|
keyMissing: '未配 Key',
|
|
108
|
+
copiedKey: '已复制 Key',
|
|
109
|
+
showKey: '查看原密码',
|
|
110
|
+
hideKey: '收起脱敏',
|
|
111
|
+
showKeyHint: '点击复制完整 Key · 眼睛图标查看原密码',
|
|
112
|
+
hideKeyHint: '点击复制完整 Key · 眼睛图标收起脱敏',
|
|
113
|
+
showKeyUnavailable: '点击复制(当前凭据受安全保护仅展示脱敏标识)',
|
|
108
114
|
noBaseURLHint: '没填服务地址,探测不了;模型只能手填',
|
|
109
115
|
pullCatalog: '拉取目录',
|
|
110
116
|
pick: '选择要用的模型',
|
|
@@ -154,6 +160,19 @@ window.__ModuleLoader__.load({
|
|
|
154
160
|
aliasesMissing: '尚未配置别名',
|
|
155
161
|
keypoolTitle: '多账号 key 轮换',
|
|
156
162
|
keypoolRound: 'round-robin',
|
|
163
|
+
keypoolActiveTip: '当前优先使用',
|
|
164
|
+
keypoolNextTip: '下次轮换目标',
|
|
165
|
+
heroTitle: 'LLM 矩阵概览',
|
|
166
|
+
heroProviders: '活跃提供方',
|
|
167
|
+
heroModels: '可用模型',
|
|
168
|
+
heroFastest: '最优延迟',
|
|
169
|
+
heroSecurity: '凭据防护',
|
|
170
|
+
heroSecSafe: '零明文脱敏中',
|
|
171
|
+
presetsTitle: '⚡ 热门大模型一键装配',
|
|
172
|
+
presetsSub: '点击主流服务商快速复制 YAML 配置模板与直达控制台',
|
|
173
|
+
presetsCopied: '已复制配置片段',
|
|
174
|
+
presetsCopyBtn: '复制 YAML',
|
|
175
|
+
presetsDocsBtn: '获取 Key ↗',
|
|
157
176
|
usageTitle: '本月用量',
|
|
158
177
|
usageCalls: '次调用',
|
|
159
178
|
usageInput: '输入',
|
|
@@ -168,7 +187,12 @@ window.__ModuleLoader__.load({
|
|
|
168
187
|
usageClear: '清空记录',
|
|
169
188
|
usageCleared: '已清空',
|
|
170
189
|
usageClearConfirm: '确认清空?',
|
|
171
|
-
usageNoData: '本月还没有记录'
|
|
190
|
+
usageNoData: '本月还没有记录',
|
|
191
|
+
suiteTitle: 'Webkubor DSH 扩展家族',
|
|
192
|
+
suiteDesc: '一键构建完整智能体开发环境',
|
|
193
|
+
suiteActive: '已激活',
|
|
194
|
+
suiteCopyInstall: '复制安装',
|
|
195
|
+
suiteCopied: '已复制命令'
|
|
172
196
|
},
|
|
173
197
|
en: {
|
|
174
198
|
balance: 'Balance',
|
|
@@ -196,6 +220,12 @@ window.__ModuleLoader__.load({
|
|
|
196
220
|
configuredModels: 'configured',
|
|
197
221
|
modelsUnit: 'models',
|
|
198
222
|
keyMissing: 'No key',
|
|
223
|
+
copiedKey: 'Key copied',
|
|
224
|
+
showKey: 'Show key',
|
|
225
|
+
hideKey: 'Mask key',
|
|
226
|
+
showKeyHint: 'Click to copy full key · Eye icon to reveal',
|
|
227
|
+
hideKeyHint: 'Click to copy full key · Eye icon to mask',
|
|
228
|
+
showKeyUnavailable: 'Click to copy masked key',
|
|
199
229
|
noBaseURLHint: 'No service address configured — probing unavailable, models are hand-typed',
|
|
200
230
|
pullCatalog: 'Fetch catalog',
|
|
201
231
|
pick: 'Pick models',
|
|
@@ -245,6 +275,19 @@ window.__ModuleLoader__.load({
|
|
|
245
275
|
aliasesMissing: 'no aliases configured',
|
|
246
276
|
keypoolTitle: 'Key rotation',
|
|
247
277
|
keypoolRound: 'round-robin',
|
|
278
|
+
keypoolActiveTip: 'Currently active',
|
|
279
|
+
keypoolNextTip: 'Next target',
|
|
280
|
+
heroTitle: 'LLM Matrix Overview',
|
|
281
|
+
heroProviders: 'Active providers',
|
|
282
|
+
heroModels: 'Models',
|
|
283
|
+
heroFastest: 'Fastest ping',
|
|
284
|
+
heroSecurity: 'Key security',
|
|
285
|
+
heroSecSafe: 'Masked in memory',
|
|
286
|
+
presetsTitle: '⚡ Popular LLM Quick Presets',
|
|
287
|
+
presetsSub: 'One-click YAML config snippets and official key links',
|
|
288
|
+
presetsCopied: 'YAML snippet copied',
|
|
289
|
+
presetsCopyBtn: 'Copy YAML',
|
|
290
|
+
presetsDocsBtn: 'Get Key ↗',
|
|
248
291
|
usageTitle: 'This month',
|
|
249
292
|
usageCalls: 'calls',
|
|
250
293
|
usageInput: 'in',
|
|
@@ -259,7 +302,12 @@ window.__ModuleLoader__.load({
|
|
|
259
302
|
usageClear: 'Clear records',
|
|
260
303
|
usageCleared: 'Cleared',
|
|
261
304
|
usageClearConfirm: 'Confirm clear?',
|
|
262
|
-
usageNoData: 'no calls this month yet'
|
|
305
|
+
usageNoData: 'no calls this month yet',
|
|
306
|
+
suiteTitle: 'Webkubor DSH Plugin Suite',
|
|
307
|
+
suiteDesc: 'One-click full agent development environment',
|
|
308
|
+
suiteActive: 'Active',
|
|
309
|
+
suiteCopyInstall: 'Copy install',
|
|
310
|
+
suiteCopied: 'Command copied'
|
|
263
311
|
}
|
|
264
312
|
}
|
|
265
313
|
|
|
@@ -315,150 +363,312 @@ window.__ModuleLoader__.load({
|
|
|
315
363
|
const style = document.createElement('style')
|
|
316
364
|
style.id = id
|
|
317
365
|
style.textContent = [
|
|
366
|
+
'@keyframes dsh-llm-hub-spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}',
|
|
367
|
+
'.dsh-llm-hub-spin{animation:dsh-llm-hub-spin 1s linear infinite;display:inline-block}',
|
|
368
|
+
// 清爽无边框 List Card 容器:自然融入宿主,呼吸感通透
|
|
369
|
+
'.dsh-llm-hub-card{display:flex;flex-direction:column;gap:6px;margin-top:4px;',
|
|
370
|
+
'padding:4px 0 2px 0;border:none;background:transparent;font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary)}',
|
|
371
|
+
'.dsh-llm-hub-card__meta{display:flex;align-items:center;justify-content:space-between;gap:8px;flex-wrap:wrap;min-width:0}',
|
|
372
|
+
'.dsh-llm-hub-card__tags{display:flex;align-items:center;gap:6px;flex-wrap:wrap;min-width:0}',
|
|
373
|
+
'.dsh-llm-hub-meta-sep{opacity:.35;font-size:10px;user-select:none;margin:0 1px}',
|
|
374
|
+
'.dsh-llm-hub-meta-text{font-size:11.5px;color:var(--dsw-alias-label-secondary);white-space:nowrap}',
|
|
375
|
+
'.dsh-llm-hub-card__actions{display:flex;align-items:center;justify-content:space-between;gap:8px;flex-wrap:wrap;padding-top:2px}',
|
|
376
|
+
'.dsh-llm-hub-card__btn-group{display:flex;align-items:center;gap:5px;flex-wrap:wrap}',
|
|
377
|
+
'.dsh-llm-hub-card__status-group{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-left:auto}',
|
|
378
|
+
// 兼容旧 balance 容器类名
|
|
318
379
|
'.dsh-llm-hub-balance{display:flex;align-items:center;gap:8px;flex-wrap:wrap;',
|
|
319
|
-
'margin-top:
|
|
320
|
-
'border:.5px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-2);',
|
|
380
|
+
'margin-top:4px;padding:4px 0;border:none;background:transparent;',
|
|
321
381
|
'font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary)}',
|
|
322
|
-
'.dsh-llm-hub-
|
|
323
|
-
'
|
|
324
|
-
'.dsh-llm-hub-
|
|
325
|
-
'
|
|
382
|
+
'.dsh-llm-hub-balance--stack{flex-direction:column;align-items:stretch;gap:6px}',
|
|
383
|
+
'.dsh-llm-hub-balance__facts{display:flex;align-items:center;gap:6px;flex-wrap:wrap;min-width:0}',
|
|
384
|
+
'.dsh-llm-hub-balance-label{font-size:12px;color:var(--dsw-alias-label-secondary);font-weight:500}',
|
|
385
|
+
'.dsh-llm-hub-amount{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary);',
|
|
386
|
+
'font-variant-numeric:tabular-nums;letter-spacing:-0.2px}',
|
|
387
|
+
'.dsh-llm-hub-amount.is-low{color:var(--dsw-alias-state-error-primary)}',
|
|
388
|
+
'.dsh-llm-hub-subtext{font-size:11px;color:var(--dsw-alias-label-secondary);opacity:.8}',
|
|
389
|
+
'.dsh-llm-hub-warn{color:var(--dsw-alias-state-warn-primary);font-size:11px}',
|
|
390
|
+
'.dsh-llm-hub-error{color:var(--dsw-alias-state-error-primary);font-size:11px}',
|
|
391
|
+
// 去掉粗糙灰底框,改用轻量排版标签
|
|
392
|
+
'.dsh-llm-hub-tag{display:inline-flex;align-items:center;gap:3px;padding:0 2px;',
|
|
393
|
+
'border:none;background:transparent;',
|
|
394
|
+
'color:var(--dsw-alias-label-secondary);font-size:11.5px;white-space:nowrap;line-height:16px}',
|
|
395
|
+
'.dsh-llm-hub-tag--muted{opacity:.65}',
|
|
396
|
+
'.dsh-llm-hub-warn-badge{display:inline-flex;align-items:center;gap:3px;padding:1px 5px;border-radius:3px;',
|
|
397
|
+
'border:.5px solid var(--dsw-alias-state-warn-primary);background:rgba(234,179,8,.08);',
|
|
398
|
+
'color:var(--dsw-alias-state-warn-primary);font-size:10.5px}',
|
|
399
|
+
// 现代 Ghost 微操作按钮:去硬框、低视觉干扰
|
|
400
|
+
'.dsh-llm-hub-btn{box-sizing:border-box;display:inline-flex;align-items:center;gap:3px;',
|
|
401
|
+
'height:22px;padding:0 6px;font:inherit;font-size:11px;line-height:1;color:var(--dsw-alias-label-secondary);',
|
|
402
|
+
'background:transparent;cursor:pointer;border:.5px solid var(--dsw-alias-border-l1);',
|
|
403
|
+
'border-radius:4px;white-space:nowrap;transition:all .15s ease}',
|
|
404
|
+
'.dsh-llm-hub-btn:hover:not(:disabled){color:var(--dsw-alias-label-primary);',
|
|
405
|
+
'background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-border-l2)}',
|
|
406
|
+
'.dsh-llm-hub-btn:disabled{cursor:default;opacity:.45}',
|
|
407
|
+
// API Key 徽章:透明极简底色,精致内联 SVG
|
|
408
|
+
'.dsh-llm-hub-key-badge{box-sizing:border-box;display:inline-flex;align-items:center;',
|
|
409
|
+
'height:20px;padding:0 3px 0 6px;gap:3px;border-radius:4px;flex:0 0 auto;',
|
|
410
|
+
'border:.5px solid var(--dsw-alias-border-l1);background:transparent;',
|
|
411
|
+
'color:var(--dsw-alias-label-secondary);font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;',
|
|
412
|
+
'font-size:10.5px;cursor:pointer;user-select:none;transition:all .15s ease;max-width:260px}',
|
|
413
|
+
'.dsh-llm-hub-quota-bar{display:flex;align-items:center;gap:6px;flex-wrap:wrap}',
|
|
414
|
+
'.dsh-llm-hub-key-badge:hover{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);',
|
|
326
415
|
'background:var(--dsw-alias-bg-layer-2)}',
|
|
327
|
-
'.dsh-llm-hub-
|
|
328
|
-
'
|
|
416
|
+
'.dsh-llm-hub-key-badge.is-copied{border-color:var(--dsw-alias-state-success-primary,#10b981);',
|
|
417
|
+
'color:var(--dsw-alias-state-success-primary,#10b981);background:rgba(16,185,129,.08)}',
|
|
418
|
+
'.dsh-llm-hub-key-badge__text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1}',
|
|
419
|
+
'.dsh-llm-hub-key-badge__copied{display:inline-flex;align-items:center;gap:3px;font-weight:500}',
|
|
420
|
+
'.dsh-llm-hub-key-badge__btn{display:inline-flex;align-items:center;justify-content:center;',
|
|
421
|
+
'width:16px;height:16px;padding:0;border:0;background:0 0;color:inherit;cursor:pointer;',
|
|
422
|
+
'border-radius:3px;opacity:.65;transition:all .12s ease;flex:none}',
|
|
423
|
+
'.dsh-llm-hub-key-badge__btn:hover{opacity:1;background:var(--dsw-alias-interactive-bg-hover)}',
|
|
424
|
+
'.dsh-llm-hub-key-badge__btn.is-active{opacity:1;color:var(--dsw-alias-brand-primary)}',
|
|
425
|
+
// 直接融入 Provider 行的健康与延迟微徽章
|
|
426
|
+
'.dsh-llm-hub-health-badge{display:inline-flex;align-items:center;gap:4.5px;font-size:11px;',
|
|
427
|
+
'font-variant-numeric:tabular-nums;line-height:1;color:var(--dsw-alias-label-secondary)}',
|
|
428
|
+
'.dsh-llm-hub-health-badge.is-ok{color:var(--dsw-alias-state-success-primary,#10b981)}',
|
|
429
|
+
'.dsh-llm-hub-health-badge.is-err{color:var(--dsw-alias-state-error-primary,#ef4444)}',
|
|
430
|
+
'.dsh-llm-hub-health-dot{width:5px;height:5px;border-radius:50%;flex:none}',
|
|
431
|
+
'.dsh-llm-hub-health-dot.is-ok{background:var(--dsw-alias-state-success-primary,#10b981);box-shadow:0 0 4px rgba(16,185,129,.4)}',
|
|
432
|
+
'.dsh-llm-hub-health-dot.is-err{background:var(--dsw-alias-state-error-primary,#ef4444);box-shadow:0 0 4px rgba(239,68,68,.4)}',
|
|
433
|
+
// 状态指示条与小圆点(保留以兼容历史与测试)
|
|
434
|
+
'.dsh-llm-hub-status-text{display:inline-flex;align-items:center;gap:5px;font-size:11px;',
|
|
435
|
+
'color:var(--dsw-alias-label-secondary)}',
|
|
436
|
+
'.dsh-llm-hub-status-text.is-err{color:var(--dsw-alias-state-error-primary)}',
|
|
437
|
+
'.dsh-llm-hub-status-dot{width:6px;height:6px;border-radius:50%;background:var(--dsw-alias-label-secondary);flex:none}',
|
|
438
|
+
'.dsh-llm-hub-status-dot.is-ok{background:var(--dsw-alias-state-success-primary,#10b981);',
|
|
439
|
+
'box-shadow:0 0 5px rgba(16,185,129,.5)}',
|
|
440
|
+
'.dsh-llm-hub-status-dot.is-err{background:var(--dsw-alias-state-error-primary);',
|
|
441
|
+
'box-shadow:0 0 5px rgba(239,68,68,.5)}',
|
|
442
|
+
'.dsh-llm-hub-dot-sep{opacity:.35;margin:0 1px}',
|
|
443
|
+
// harness
|
|
444
|
+
'.dsh-llm-hub-harness{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-top:6px;',
|
|
445
|
+
'font-size:11.5px;line-height:16px;color:var(--dsw-alias-label-secondary)}',
|
|
446
|
+
'.dsh-llm-hub-harness__chip{display:inline-flex;align-items:center;gap:4px;',
|
|
447
|
+
'padding:2px 7px;border-radius:999px;border:.5px solid var(--dsw-alias-border-l1);',
|
|
448
|
+
'background:transparent}',
|
|
449
|
+
'.dsh-llm-hub-harness__chip--missing{opacity:.45}',
|
|
450
|
+
'.dsh-llm-hub-harness__dot{width:5px;height:5px;border-radius:50%;',
|
|
329
451
|
'background:var(--dsw-alias-label-secondary);flex:none}',
|
|
330
|
-
'.dsh-llm-hub-harness__dot--ready{background:var(--dsw-alias-state-success-primary,#
|
|
452
|
+
'.dsh-llm-hub-harness__dot--ready{background:var(--dsw-alias-state-success-primary,#10b981)}',
|
|
331
453
|
'.dsh-llm-hub-harness__name{color:var(--dsw-alias-label-primary)}',
|
|
332
454
|
'.dsh-llm-hub-harness__state{opacity:.75}',
|
|
333
|
-
|
|
334
|
-
'.dsh-llm-hub-
|
|
335
|
-
'font-variant-numeric:tabular-nums}',
|
|
336
|
-
'.dsh-llm-hub-balance__amount--low{color:var(--dsw-alias-state-error-primary)}',
|
|
337
|
-
'.dsh-llm-hub-balance__breakdown{color:var(--dsw-alias-label-secondary);opacity:.8}',
|
|
338
|
-
'.dsh-llm-hub-balance__warn{color:var(--dsw-alias-state-warn-primary)}',
|
|
339
|
-
'.dsh-llm-hub-balance__error{color:var(--dsw-alias-state-error-primary)}',
|
|
340
|
-
'.dsh-llm-hub-balance__spacer{flex:1 1 auto}',
|
|
341
|
-
// 一张卡一个盒子:事实行 + 动作条。原来是两个盒子,白吃一行高度。
|
|
342
|
-
'.dsh-llm-hub-balance--stack{flex-direction:column;align-items:stretch;gap:6px}',
|
|
343
|
-
'.dsh-llm-hub-balance__facts{display:flex;align-items:center;gap:8px;flex-wrap:wrap;min-width:0}',
|
|
344
|
-
'.dsh-llm-hub-actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap}',
|
|
345
|
-
'.dsh-llm-hub-actions__button{box-sizing:border-box;height:24px;padding:0 10px;font:inherit;',
|
|
346
|
-
'font-size:12px;line-height:16px;color:var(--dsw-alias-label-primary);background:0 0;cursor:pointer;',
|
|
347
|
-
'border:.5px solid var(--dsw-alias-border-l3);border-radius:12px;white-space:nowrap}',
|
|
348
|
-
'.dsh-llm-hub-actions__button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}',
|
|
349
|
-
'.dsh-llm-hub-actions__button:disabled{cursor:default;opacity:.5}',
|
|
350
|
-
// 目录选择器:沿用卡片本身的 token,不引入新配色
|
|
351
|
-
'.dsh-llm-hub-picker{margin-top:6px;padding:8px 10px;border-radius:8px;',
|
|
455
|
+
// 目录选择器 Drawer
|
|
456
|
+
'.dsh-llm-hub-picker{margin-top:6px;padding:8px 10px;border-radius:6px;',
|
|
352
457
|
'border:.5px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-2)}',
|
|
353
458
|
'.dsh-llm-hub-picker__head{display:flex;align-items:center;justify-content:space-between;',
|
|
354
|
-
'gap:8px;font-size:
|
|
355
|
-
'.dsh-llm-hub-picker__list{max-height:
|
|
459
|
+
'gap:8px;font-size:11.5px;color:var(--dsw-alias-label-secondary);margin-bottom:6px}',
|
|
460
|
+
'.dsh-llm-hub-picker__list{max-height:200px;overflow-y:auto;display:flex;',
|
|
356
461
|
'flex-direction:column;gap:2px}',
|
|
357
|
-
'.dsh-llm-hub-picker__item{display:flex;align-items:center;gap:6px;font-size:
|
|
462
|
+
'.dsh-llm-hub-picker__item{display:flex;align-items:center;gap:6px;font-size:11.5px;',
|
|
358
463
|
'line-height:20px;color:var(--dsw-alias-label-primary);cursor:pointer}',
|
|
359
464
|
'.dsh-llm-hub-picker__id{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}',
|
|
360
|
-
'.dsh-llm-hub-
|
|
361
|
-
'
|
|
362
|
-
|
|
465
|
+
'.dsh-llm-hub-picker__tag{font-size:10.5px;padding:0 4px;border-radius:3px;',
|
|
466
|
+
'color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-3)}',
|
|
467
|
+
// 页脚
|
|
468
|
+
'.dsh-llm-hub-foot{display:flex;align-items:center;gap:10px;margin-top:10px;',
|
|
469
|
+
'padding:2px 2px;font-size:11px;color:var(--dsw-alias-label-secondary);opacity:.75}',
|
|
470
|
+
'.dsh-llm-hub-foot__name{font-variant-numeric:tabular-nums;opacity:.9}',
|
|
363
471
|
'.dsh-llm-hub-foot__link{color:var(--dsw-alias-label-link);text-decoration:none;',
|
|
364
|
-
'border:0;background:0 0;padding:0;font:inherit;cursor:pointer}',
|
|
472
|
+
'border:0;background:0 0;padding:0;font:inherit;font-size:11px;cursor:pointer}',
|
|
365
473
|
'.dsh-llm-hub-foot__link:hover{text-decoration:underline}',
|
|
366
|
-
'.dsh-llm-hub-
|
|
367
|
-
'
|
|
368
|
-
'
|
|
369
|
-
'font-size:12px;color:var(--dsw-alias-brand-primary)}',
|
|
370
|
-
'.dsh-llm-hub-balance__button:disabled{cursor:default;opacity:.5}',
|
|
371
|
-
'.dsh-llm-hub-balance__tag{padding:1px 6px;border-radius:6px;',
|
|
372
|
-
'border:.5px solid var(--dsw-alias-border-l1);',
|
|
373
|
-
'color:var(--dsw-alias-label-secondary);white-space:nowrap}',
|
|
374
|
-
// 被摘掉的 provider:在它自己的卡片动作条里标一枚小片,不另开面板
|
|
375
|
-
// (2026-09-16 owner:「这不是很多余吗,上面不都是显示了吗」)。
|
|
376
|
-
'.dsh-llm-hub-chip{box-sizing:border-box;display:inline-flex;align-items:center;height:24px;padding:0 8px;',
|
|
377
|
-
'border-radius:12px;font-size:12px;line-height:16px;white-space:nowrap;',
|
|
378
|
-
'border:.5px solid var(--dsw-alias-border-l3);color:var(--dsw-alias-label-secondary)}',
|
|
474
|
+
'.dsh-llm-hub-chip{box-sizing:border-box;display:inline-flex;align-items:center;height:20px;padding:0 6px;',
|
|
475
|
+
'border-radius:10px;font-size:10.5px;line-height:14px;white-space:nowrap;',
|
|
476
|
+
'border:.5px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary)}',
|
|
379
477
|
'.dsh-llm-hub-chip--hidden{border-color:var(--dsw-alias-state-error-primary);',
|
|
380
478
|
'color:var(--dsw-alias-state-error-primary)}',
|
|
381
479
|
'.dsh-llm-hub-foot__hidden{color:var(--dsw-alias-state-error-primary)}',
|
|
382
|
-
//
|
|
480
|
+
// 用量卡:紧凑通透,不再沉重
|
|
383
481
|
'.dsh-llm-hub-usage{display:flex;flex-direction:column;gap:6px;margin-top:10px;',
|
|
384
|
-
'padding:8px 10px;border-radius:
|
|
385
|
-
'border:.5px solid var(--dsw-alias-border-l1);background:
|
|
386
|
-
'font-size:
|
|
387
|
-
'.dsh-llm-hub-usage__head{display:flex;align-items:center;gap:8px;flex-wrap:wrap}',
|
|
482
|
+
'padding:8px 10px;border-radius:6px;',
|
|
483
|
+
'border:.5px solid var(--dsw-alias-border-l1);background:transparent;',
|
|
484
|
+
'font-size:11.5px;line-height:16px;color:var(--dsw-alias-label-secondary)}',
|
|
485
|
+
'.dsh-llm-hub-usage__head{display:flex;align-items:center;justify-content:space-between;gap:8px;flex-wrap:wrap}',
|
|
388
486
|
'.dsh-llm-hub-usage__title{color:var(--dsw-alias-label-primary);font-weight:600}',
|
|
389
|
-
'.dsh-llm-hub-usage__facts{display:flex;align-items:center;gap:
|
|
390
|
-
'.dsh-llm-hub-usage__tag{display:inline-flex;align-items:center;gap:
|
|
391
|
-
'border-radius:
|
|
392
|
-
'background:var(--dsw-alias-bg-layer-3)}',
|
|
487
|
+
'.dsh-llm-hub-usage__facts{display:flex;align-items:center;gap:6px;flex-wrap:wrap}',
|
|
488
|
+
'.dsh-llm-hub-usage__tag{display:inline-flex;align-items:center;gap:3px;padding:0 5px;',
|
|
489
|
+
'border-radius:3px;border:.5px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-2);font-size:10.5px}',
|
|
393
490
|
'.dsh-llm-hub-usage__top{display:flex;flex-direction:column;gap:2px;margin-top:2px}',
|
|
394
491
|
'.dsh-llm-hub-usage__top-row{display:flex;justify-content:space-between;gap:8px;',
|
|
395
|
-
'color:var(--dsw-alias-label-primary)}',
|
|
492
|
+
'color:var(--dsw-alias-label-primary);font-size:11px}',
|
|
396
493
|
'.dsh-llm-hub-usage__top-row small{color:var(--dsw-alias-label-secondary);opacity:.8}',
|
|
397
|
-
'.dsh-llm-hub-usage__actions{display:flex;gap:
|
|
398
|
-
//
|
|
399
|
-
'.dsh-llm-hub-health{display:
|
|
400
|
-
|
|
401
|
-
'border:.5px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-2);',
|
|
402
|
-
'font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary)}',
|
|
403
|
-
'.dsh-llm-hub-health__head{display:flex;align-items:center;gap:8px;flex-wrap:wrap}',
|
|
404
|
-
'.dsh-llm-hub-health__title{color:var(--dsw-alias-label-primary);font-weight:600}',
|
|
405
|
-
'.dsh-llm-hub-health__rows{display:flex;flex-direction:column;gap:2px}',
|
|
406
|
-
'.dsh-llm-hub-health__row{display:flex;align-items:center;gap:6px;flex-wrap:wrap;',
|
|
407
|
-
'padding:2px 4px;border-radius:6px}',
|
|
408
|
-
'.dsh-llm-hub-health__name{color:var(--dsw-alias-label-primary);font-weight:600;',
|
|
409
|
-
'min-width:80px}',
|
|
410
|
-
'.dsh-llm-hub-health__chip{display:inline-flex;align-items:center;height:20px;padding:0 8px;',
|
|
411
|
-
'border-radius:999px;border:.5px solid var(--dsw-alias-border-l3);',
|
|
412
|
-
'font-size:11px;line-height:16px;color:var(--dsw-alias-label-secondary)}',
|
|
413
|
-
'.dsh-llm-hub-health__chip--available{border-color:var(--dsw-alias-state-success-primary,#3fb950);',
|
|
414
|
-
'color:var(--dsw-alias-state-success-primary,#3fb950)}',
|
|
415
|
-
'.dsh-llm-hub-health__chip--unavailable{border-color:var(--dsw-alias-state-error-primary);',
|
|
416
|
-
'color:var(--dsw-alias-state-error-primary)}',
|
|
417
|
-
'.dsh-llm-hub-health__chip--unknown{border-color:var(--dsw-alias-border-l3);',
|
|
418
|
-
'color:var(--dsw-alias-label-secondary)}',
|
|
419
|
-
'.dsh-llm-hub-health__metric{font-size:11px;opacity:.85}',
|
|
420
|
-
'.dsh-llm-hub-health__metric--time{margin-left:auto}',
|
|
421
|
-
'.dsh-llm-hub-health__reason{color:var(--dsw-alias-state-warn-primary);',
|
|
422
|
-
'max-width:240px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
|
|
423
|
-
// 当前路由卡:放在 health 70 与 usage 80 之间(order 75)
|
|
494
|
+
'.dsh-llm-hub-usage__actions{display:flex;gap:10px;flex-wrap:wrap;padding-top:4px;border-top:.5px solid var(--dsw-alias-border-l1)}',
|
|
495
|
+
// 健康看板:样式保留以兼容
|
|
496
|
+
'.dsh-llm-hub-health{display:none}',
|
|
497
|
+
// 当前路由卡
|
|
424
498
|
'.dsh-llm-hub-routing{display:flex;flex-direction:column;gap:6px;margin-top:10px;',
|
|
425
|
-
'padding:8px 10px;border-radius:
|
|
426
|
-
'border:.5px solid var(--dsw-alias-border-l1);background:
|
|
427
|
-
'font-size:
|
|
499
|
+
'padding:8px 10px;border-radius:6px;',
|
|
500
|
+
'border:.5px solid var(--dsw-alias-border-l1);background:transparent;',
|
|
501
|
+
'font-size:11.5px;line-height:16px;color:var(--dsw-alias-label-secondary)}',
|
|
428
502
|
'.dsh-llm-hub-routing__head{display:flex;align-items:center;gap:8px;flex-wrap:wrap}',
|
|
429
503
|
'.dsh-llm-hub-routing__title{color:var(--dsw-alias-label-primary);font-weight:600}',
|
|
430
504
|
'.dsh-llm-hub-routing__active{color:var(--dsw-alias-label-primary)}',
|
|
431
505
|
'.dsh-llm-hub-routing__rows{display:flex;flex-direction:column;gap:2px}',
|
|
432
|
-
'.dsh-llm-hub-routing__row{display:flex;align-items:center;gap:6px;flex-wrap:wrap;',
|
|
433
|
-
'padding:
|
|
434
|
-
'
|
|
435
|
-
'
|
|
436
|
-
'background:var(--dsw-alias-bg-layer-3);font-size:11px;color:var(--dsw-alias-label-secondary)}',
|
|
506
|
+
'.dsh-llm-hub-routing__row{display:flex;align-items:center;gap:6px;flex-wrap:wrap;padding:2px 4px}',
|
|
507
|
+
'.dsh-llm-hub-routing__role{display:inline-flex;align-items:center;height:16px;padding:0 5px;',
|
|
508
|
+
'border-radius:3px;border:.5px solid var(--dsw-alias-border-l2);',
|
|
509
|
+
'background:transparent;font-size:10px;color:var(--dsw-alias-label-secondary)}',
|
|
437
510
|
'.dsh-llm-hub-routing__name{color:var(--dsw-alias-label-primary);font-weight:600}',
|
|
438
|
-
'.dsh-llm-hub-routing__suggestion{color:var(--dsw-alias-state-warn-primary);',
|
|
439
|
-
'padding-top:
|
|
440
|
-
|
|
441
|
-
'padding-top:4px}',
|
|
442
|
-
// 多账号 key 轮换:放在 routing 75 之前(order 73)
|
|
511
|
+
'.dsh-llm-hub-routing__suggestion{color:var(--dsw-alias-state-warn-primary);padding-top:2px}',
|
|
512
|
+
'.dsh-llm-hub-routing__reason{color:var(--dsw-alias-state-error-primary);padding-top:2px}',
|
|
513
|
+
// 多账号 key 轮换
|
|
443
514
|
'.dsh-llm-hub-keypool{display:flex;flex-direction:column;gap:6px;margin-top:10px;',
|
|
444
|
-
'padding:8px 10px;border-radius:
|
|
445
|
-
'border:.5px solid var(--dsw-alias-border-l1);background:
|
|
446
|
-
'font-size:
|
|
447
|
-
'.dsh-llm-hub-keypool__head{display:flex;align-items:center;gap:8px;flex-wrap:wrap}',
|
|
448
|
-
'.dsh-llm-hub-keypool__title{color:var(--dsw-alias-label-primary);font-weight:600}',
|
|
449
|
-
'.dsh-llm-hub-keypool__rows{display:flex;flex-direction:column;gap:
|
|
450
|
-
'.dsh-llm-hub-keypool__row{display:flex;align-items:center;gap:8px;flex-wrap:wrap;',
|
|
451
|
-
'
|
|
452
|
-
'.dsh-llm-hub-keypool__name{color:var(--dsw-alias-label-primary);font-weight:600;min-width:120px}',
|
|
453
|
-
'.dsh-llm-hub-keypool__meta{font-size:
|
|
515
|
+
'padding:8px 10px;border-radius:6px;',
|
|
516
|
+
'border:.5px solid var(--dsw-alias-border-l1);background:transparent;',
|
|
517
|
+
'font-size:11.5px;line-height:16px;color:var(--dsw-alias-label-secondary)}',
|
|
518
|
+
'.dsh-llm-hub-keypool__head{display:flex;align-items:center;justify-content:space-between;gap:8px;flex-wrap:wrap}',
|
|
519
|
+
'.dsh-llm-hub-keypool__title{color:var(--dsw-alias-label-primary);font-weight:600;display:inline-flex;align-items:center;gap:5px}',
|
|
520
|
+
'.dsh-llm-hub-keypool__rows{display:flex;flex-direction:column;gap:3px}',
|
|
521
|
+
'.dsh-llm-hub-keypool__row{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:4px 6px;border-radius:4px;background:var(--dsw-alias-bg-layer-2,#1c1f24);border:.5px solid var(--dsw-alias-border-l1)}',
|
|
522
|
+
'.dsh-llm-hub-keypool__row.is-active{border-color:var(--dsw-alias-state-success-primary,#10b981);box-shadow:0 0 8px rgba(16,185,129,.15)}',
|
|
523
|
+
'.dsh-llm-hub-keypool__name{color:var(--dsw-alias-label-primary);font-weight:600;min-width:120px;font-family:ui-monospace,SFMono-Regular,monospace}',
|
|
524
|
+
'.dsh-llm-hub-keypool__meta{font-size:10.5px;opacity:.7}',
|
|
454
525
|
'.dsh-llm-hub-keypool__chosen{display:flex;align-items:baseline;gap:4px;',
|
|
455
526
|
'color:var(--dsw-alias-label-primary)}',
|
|
456
|
-
'.dsh-llm-hub-keypool__chosen small{color:var(--dsw-alias-label-secondary);opacity:.75;font-size:
|
|
527
|
+
'.dsh-llm-hub-keypool__chosen small{color:var(--dsw-alias-label-secondary);opacity:.75;font-size:10.5px}',
|
|
457
528
|
'.dsh-llm-hub-keypool__pending{color:var(--dsw-alias-label-secondary);opacity:.6}',
|
|
529
|
+
// Hero 全景统计大横条
|
|
530
|
+
'.dsh-llm-hub-hero{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:6px;margin:12px 0 6px 0}',
|
|
531
|
+
'.dsh-llm-hub-hero__card{display:flex;flex-direction:column;gap:2px;padding:8px 10px;border-radius:6px;border:.5px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-2,rgba(255,255,255,.02));backdrop-filter:blur(6px)}',
|
|
532
|
+
'.dsh-llm-hub-hero__num{font-size:15px;font-weight:700;color:var(--dsw-alias-label-primary);font-family:ui-monospace,SFMono-Regular,monospace;letter-spacing:-0.3px;line-height:1.2}',
|
|
533
|
+
'.dsh-llm-hub-hero__label{font-size:10.5px;color:var(--dsw-alias-label-secondary);opacity:.85}',
|
|
534
|
+
// 热门大模型一键装配 Hub
|
|
535
|
+
'.dsh-llm-hub-presets{display:flex;flex-direction:column;gap:6px;margin-top:10px;padding:8px 10px;border-radius:6px;border:.5px solid var(--dsw-alias-border-l1);background:transparent}',
|
|
536
|
+
'.dsh-llm-hub-presets__head{display:flex;align-items:center;justify-content:space-between;gap:8px;cursor:pointer;user-select:none}',
|
|
537
|
+
'.dsh-llm-hub-presets__title{color:var(--dsw-alias-label-primary);font-weight:600;font-size:12px;display:inline-flex;align-items:center;gap:5px}',
|
|
538
|
+
'.dsh-llm-hub-presets__grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:6px;margin-top:6px}',
|
|
539
|
+
'.dsh-llm-hub-presets__item{display:flex;flex-direction:column;gap:3px;padding:6px 8px;border-radius:5px;border:.5px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-2,rgba(255,255,255,.02));cursor:pointer;transition:all .15s ease}',
|
|
540
|
+
'.dsh-llm-hub-presets__item:hover{border-color:var(--dsw-alias-brand-primary,#3b82f6);transform:translateY(-1px);background:var(--dsw-alias-bg-layer-3,rgba(255,255,255,.05))}',
|
|
541
|
+
'.dsh-llm-hub-presets__name{font-weight:600;font-size:11.5px;color:var(--dsw-alias-label-primary)}',
|
|
542
|
+
'.dsh-llm-hub-presets__desc{font-size:10px;color:var(--dsw-alias-label-secondary);opacity:.75;line-height:1.3}',
|
|
543
|
+
'.dsh-llm-hub-presets__detail{margin-top:6px;padding:8px;border-radius:4px;background:var(--dsw-alias-bg-layer-3,rgba(0,0,0,.2));border:.5px solid var(--dsw-alias-border-l1);font-size:11px}',
|
|
544
|
+
'.dsh-llm-hub-presets__code{font-family:ui-monospace,SFMono-Regular,monospace;font-size:10px;white-space:pre-wrap;color:var(--dsw-alias-label-secondary);margin:4px 0}',
|
|
545
|
+
// 延迟高亮胶囊
|
|
546
|
+
'.dsh-llm-hub-latency-badge{display:inline-flex;align-items:center;gap:3.5px;padding:1px 5px;border-radius:3px;font-size:10.5px;font-variant-numeric:tabular-nums;line-height:1}',
|
|
547
|
+
'.dsh-llm-hub-latency-badge.is-fast{background:rgba(16,185,129,.08);color:var(--dsw-alias-state-success-primary,#10b981);border:.5px solid rgba(16,185,129,.2)}',
|
|
548
|
+
'.dsh-llm-hub-latency-badge.is-normal{background:rgba(234,179,8,.08);color:var(--dsw-alias-state-warn-primary,#eab308);border:.5px solid rgba(234,179,8,.2)}',
|
|
549
|
+
'.dsh-llm-hub-latency-badge.is-slow{background:rgba(239,68,68,.08);color:var(--dsw-alias-state-error-primary,#ef4444);border:.5px solid rgba(239,68,68,.2)}',
|
|
550
|
+
// Webkubor 插件全家桶互导矩阵
|
|
551
|
+
'.dsh-suite-dock{margin-top:14px;padding:14px 16px;background:var(--dsw-alias-bg-layer-2,rgba(255,255,255,0.03));border:1px solid var(--dsw-alias-border-l1,rgba(255,255,255,0.08));border-radius:10px;backdrop-filter:blur(10px)}',
|
|
552
|
+
'.dsh-suite-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px;flex-wrap:wrap;gap:8px}',
|
|
553
|
+
'.dsh-suite-title{font-size:12px;font-weight:650;color:var(--dsw-alias-label-primary);margin:0;display:flex;align-items:center;gap:6px}',
|
|
554
|
+
'.dsh-suite-desc{font-size:10.5px;color:var(--dsw-alias-label-tertiary);letter-spacing:.2px}',
|
|
555
|
+
'.dsh-suite-cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:8px}',
|
|
556
|
+
'.dsh-suite-card{display:flex;flex-direction:column;justify-content:space-between;padding:10px 12px;border-radius:8px;background:var(--dsw-alias-bg-layer-3,rgba(255,255,255,0.02));border:1px solid var(--dsw-alias-border-l2,rgba(255,255,255,0.05));transition:all .2s cubic-bezier(0.16,1,0.3,1)}',
|
|
557
|
+
'.dsh-suite-card:hover{transform:translateY(-1px);border-color:var(--dsw-alias-brand-primary,#3b82f6);box-shadow:0 4px 14px rgba(0,0,0,0.15)}',
|
|
558
|
+
'.dsh-suite-card-top{display:flex;align-items:center;gap:6px;margin-bottom:4px}',
|
|
559
|
+
'.dsh-suite-card-icon{font-size:15px;line-height:1}',
|
|
560
|
+
'.dsh-suite-card-name{font-size:12px;font-weight:600;color:var(--dsw-alias-label-primary);text-decoration:none}',
|
|
561
|
+
'.dsh-suite-card-desc{font-size:10.5px;color:var(--dsw-alias-label-secondary);line-height:1.4;margin-bottom:8px;flex:1}',
|
|
562
|
+
'.dsh-suite-card-bottom{display:flex;align-items:center;justify-content:space-between;gap:6px;margin-top:auto;font-size:10.5px}',
|
|
563
|
+
'.dsh-suite-badge-active{display:inline-flex;align-items:center;gap:4px;color:var(--dsw-alias-state-success-primary,#10b981);font-weight:600;font-size:10.5px}',
|
|
564
|
+
'.dsh-suite-badge-active-dot{width:5px;height:5px;border-radius:50%;background:#10b981;box-shadow:0 0 6px rgba(16,185,129,0.6)}',
|
|
565
|
+
'.dsh-suite-btn-action{cursor:pointer;padding:2px 8px;border-radius:4px;font-size:10px;font-weight:550;background:var(--dsw-alias-interactive-bg-subtle,rgba(255,255,255,0.06));border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);transition:all .15s;text-decoration:none;display:inline-flex;align-items:center;gap:3px}',
|
|
566
|
+
'.dsh-suite-btn-action:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(255,255,255,0.12));border-color:var(--dsw-alias-border-l1)}',
|
|
567
|
+
'.dsh-suite-link{color:var(--dsw-alias-label-tertiary);text-decoration:none;font-size:10px;display:inline-flex;align-items:center}',
|
|
568
|
+
'.dsh-suite-link:hover{color:var(--dsw-alias-label-primary)}'
|
|
458
569
|
].join('')
|
|
459
570
|
document.head.appendChild(style)
|
|
460
571
|
}
|
|
461
572
|
|
|
573
|
+
/** 矢量 SVG 线条图标集合(彻底杜绝系统 emoji) */
|
|
574
|
+
function EyeIcon() {
|
|
575
|
+
return h('svg', { width: 12, height: 12, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 1.8, strokeLinecap: 'round', strokeLinejoin: 'round' },
|
|
576
|
+
h('path', { d: 'M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z' }),
|
|
577
|
+
h('circle', { cx: 12, cy: 12, r: 3 })
|
|
578
|
+
)
|
|
579
|
+
}
|
|
580
|
+
function EyeOffIcon() {
|
|
581
|
+
return h('svg', { width: 12, height: 12, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 1.8, strokeLinecap: 'round', strokeLinejoin: 'round' },
|
|
582
|
+
h('path', { d: 'M9.88 9.88a3 3 0 1 0 4.24 4.24' }),
|
|
583
|
+
h('path', { d: 'M10.73 5.08A10.43 10.43 0 0 1 12 5c7 0 10 7 10 7a13.16 13.16 0 0 1-1.67 2.68' }),
|
|
584
|
+
h('path', { d: 'M6.61 6.61A13.526 13.526 0 0 0 2 12s3 7 10 7a9.74 9.74 0 0 0 5.39-1.61' }),
|
|
585
|
+
h('line', { x1: 2, y1: 2, x2: 22, y2: 22 })
|
|
586
|
+
)
|
|
587
|
+
}
|
|
588
|
+
function CopyIcon() {
|
|
589
|
+
return h('svg', { width: 11, height: 11, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 1.8, strokeLinecap: 'round', strokeLinejoin: 'round' },
|
|
590
|
+
h('rect', { width: 14, height: 14, x: 8, y: 8, rx: 2, ry: 2 }),
|
|
591
|
+
h('path', { d: 'M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2' })
|
|
592
|
+
)
|
|
593
|
+
}
|
|
594
|
+
function CheckIcon() {
|
|
595
|
+
return h('svg', { width: 11, height: 11, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 2.2, strokeLinecap: 'round', strokeLinejoin: 'round' },
|
|
596
|
+
h('polyline', { points: '20 6 9 17 4 12' })
|
|
597
|
+
)
|
|
598
|
+
}
|
|
599
|
+
function RefreshIcon(props) {
|
|
600
|
+
return h('svg', { width: 11, height: 11, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 1.8, strokeLinecap: 'round', strokeLinejoin: 'round', className: props?.spinning ? 'dsh-llm-hub-spin' : undefined },
|
|
601
|
+
h('path', { d: 'M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8' }),
|
|
602
|
+
h('path', { d: 'M3 3v5h5' }),
|
|
603
|
+
h('path', { d: 'M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16' }),
|
|
604
|
+
h('path', { d: 'M16 21h5v-5' })
|
|
605
|
+
)
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* API Key 脱敏展示与一键复制徽章。
|
|
610
|
+
* - 默认展示首尾可见的脱敏串(如 mgk_li····8cV9)
|
|
611
|
+
* - 矢量 Eye 图标切换查看/收起完整明文
|
|
612
|
+
* - 点击徽章或 Copy 图标一键复制完整 Key,并有绿色无缝状态反馈
|
|
613
|
+
*/
|
|
614
|
+
function ApiKeyBadge(props) {
|
|
615
|
+
const apiKey = typeof props.apiKey === 'string' && props.apiKey.trim() !== '' ? props.apiKey.trim() : null
|
|
616
|
+
const keyMasked = typeof props.keyMasked === 'string' && props.keyMasked.trim() !== '' ? props.keyMasked.trim() : null
|
|
617
|
+
const t = typeof props.t === 'function' ? props.t : fallbackT
|
|
618
|
+
const [revealed, setRevealed] = React.useState(false)
|
|
619
|
+
const [copied, setCopied] = React.useState(false)
|
|
620
|
+
|
|
621
|
+
if (apiKey === null && keyMasked === null) {
|
|
622
|
+
return h('span', { className: 'dsh-llm-hub-tag dsh-llm-hub-tag--muted' }, t('keyMissing'))
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
const rawDisplay = revealed ? (apiKey ?? keyMasked) : (keyMasked ?? '••••••••')
|
|
626
|
+
const display = rawDisplay.replace(/•+/g, '····')
|
|
627
|
+
|
|
628
|
+
const copyKey = (e) => {
|
|
629
|
+
e.stopPropagation()
|
|
630
|
+
const textToCopy = apiKey ?? keyMasked
|
|
631
|
+
if (!textToCopy) return
|
|
632
|
+
navigator.clipboard?.writeText(textToCopy)
|
|
633
|
+
.then(() => {
|
|
634
|
+
setCopied(true)
|
|
635
|
+
setTimeout(() => setCopied(false), 1600)
|
|
636
|
+
})
|
|
637
|
+
.catch(() => {})
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
const toggleReveal = (e) => {
|
|
641
|
+
e.stopPropagation()
|
|
642
|
+
if (apiKey === null) return
|
|
643
|
+
setRevealed((prev) => !prev)
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
return h('div', {
|
|
647
|
+
className: 'dsh-llm-hub-key-badge' + (copied ? ' is-copied' : ''),
|
|
648
|
+
title: copied ? t('copiedKey') : (apiKey === null ? t('showKeyUnavailable') : (revealed ? t('hideKeyHint') : t('showKeyHint'))),
|
|
649
|
+
onClick: copyKey
|
|
650
|
+
},
|
|
651
|
+
copied
|
|
652
|
+
? h('span', { className: 'dsh-llm-hub-key-badge__copied' },
|
|
653
|
+
h(CheckIcon, null),
|
|
654
|
+
h('span', null, t('copiedKey'))
|
|
655
|
+
)
|
|
656
|
+
: h('span', { className: 'dsh-llm-hub-key-badge__text' }, display),
|
|
657
|
+
!copied && apiKey !== null ? h('button', {
|
|
658
|
+
type: 'button',
|
|
659
|
+
className: 'dsh-llm-hub-key-badge__btn' + (revealed ? ' is-active' : ''),
|
|
660
|
+
title: revealed ? t('hideKey') : t('showKey'),
|
|
661
|
+
onClick: toggleReveal
|
|
662
|
+
}, revealed ? h(EyeOffIcon, null) : h(EyeIcon, null)) : null,
|
|
663
|
+
!copied ? h('button', {
|
|
664
|
+
type: 'button',
|
|
665
|
+
className: 'dsh-llm-hub-key-badge__btn',
|
|
666
|
+
title: t('copiedKey'),
|
|
667
|
+
onClick: copyKey
|
|
668
|
+
}, h(CopyIcon, null)) : null
|
|
669
|
+
)
|
|
670
|
+
}
|
|
671
|
+
|
|
462
672
|
/**
|
|
463
673
|
* 余额卡:挂载即查,可手动刷新。
|
|
464
674
|
* @param props - owner props(provider/configured/keyConfigured)+ 注入的 `t`。
|
|
@@ -469,6 +679,7 @@ window.__ModuleLoader__.load({
|
|
|
469
679
|
const keyConfigured = props.keyConfigured !== false
|
|
470
680
|
const [state, setState] = React.useState({ status: 'idle' })
|
|
471
681
|
const [warning, setWarning] = React.useState(null)
|
|
682
|
+
const [isRefreshing, setIsRefreshing] = React.useState(false)
|
|
472
683
|
// 官方直连也可能被判不可用(key 被拒 / 账户欠费)。同一个状态片,一致的处理。
|
|
473
684
|
const availability = props.availability
|
|
474
685
|
const availabilityState = availability === undefined
|
|
@@ -477,14 +688,17 @@ window.__ModuleLoader__.load({
|
|
|
477
688
|
const verdict = Array.isArray(availabilityState.providers)
|
|
478
689
|
? availabilityState.providers.find((entry) => entry.provider === DEEPSEEK_PROVIDER)
|
|
479
690
|
: undefined
|
|
480
|
-
const hiddenFromDropdown =
|
|
691
|
+
const hiddenFromDropdown = Array.isArray(availabilityState.hidden)
|
|
692
|
+
? availabilityState.hidden.includes(DEEPSEEK_PROVIDER)
|
|
693
|
+
: (verdict !== undefined && verdict.state === 'unavailable')
|
|
481
694
|
|
|
482
695
|
const load = React.useCallback(() => {
|
|
483
696
|
if (!keyConfigured) {
|
|
484
697
|
setState({ status: 'nokey' })
|
|
485
698
|
return
|
|
486
699
|
}
|
|
487
|
-
setState({ status: 'loading' })
|
|
700
|
+
setState((prev) => (prev.status === 'ok' ? prev : { status: 'loading' }))
|
|
701
|
+
setIsRefreshing(true)
|
|
488
702
|
fetch(BALANCE_URL, { headers: { accept: 'application/json' } })
|
|
489
703
|
.then(async (response) => {
|
|
490
704
|
let body
|
|
@@ -496,59 +710,31 @@ window.__ModuleLoader__.load({
|
|
|
496
710
|
if (body == null || body.ok !== true) {
|
|
497
711
|
throw new Error(typeof body?.error === 'string' ? body.error : `HTTP ${response.status}`)
|
|
498
712
|
}
|
|
499
|
-
setState({
|
|
713
|
+
setState({
|
|
714
|
+
status: 'ok',
|
|
715
|
+
isAvailable: body.isAvailable === true,
|
|
716
|
+
balances: Array.isArray(body.balances) ? body.balances : [],
|
|
717
|
+
apiKey: typeof body.apiKey === 'string' ? body.apiKey : undefined,
|
|
718
|
+
keyMasked: typeof body.keyMasked === 'string' ? body.keyMasked : undefined
|
|
719
|
+
})
|
|
500
720
|
})
|
|
501
721
|
.catch((error) => {
|
|
502
722
|
setState({ status: 'error', error: error instanceof Error ? error.message : String(error) })
|
|
503
723
|
})
|
|
724
|
+
.finally(() => {
|
|
725
|
+
setIsRefreshing(false)
|
|
726
|
+
})
|
|
504
727
|
}, [keyConfigured])
|
|
505
728
|
|
|
506
729
|
React.useEffect(() => {
|
|
507
730
|
load()
|
|
508
731
|
}, [load])
|
|
509
732
|
|
|
510
|
-
const
|
|
511
|
-
children.push(h('span', { key: 'label', className: 'dsh-llm-hub-balance__label' }, t('balance')))
|
|
512
|
-
if (hiddenFromDropdown) {
|
|
513
|
-
children.push(h('span', {
|
|
514
|
-
key: 'hidden-chip',
|
|
515
|
-
className: 'dsh-llm-hub-chip dsh-llm-hub-chip--hidden',
|
|
516
|
-
title: typeof verdict.reason === 'string' ? verdict.reason : ''
|
|
517
|
-
}, t('availabilityUnavailable')))
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
if (state.status === 'nokey') {
|
|
521
|
-
children.push(h('span', { key: 'nokey', className: 'dsh-llm-hub-balance__warn' }, t('noKey')))
|
|
522
|
-
children.push(h('span', { key: 'hint', className: 'dsh-llm-hub-balance__breakdown' }, t('noKeyHint')))
|
|
523
|
-
return h('div', { className: 'dsh-llm-hub-balance' }, children)
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
if (state.status === 'loading' || state.status === 'idle') {
|
|
527
|
-
children.push(h('span', { key: 'loading', className: 'dsh-llm-hub-balance__breakdown' }, t('loading')))
|
|
528
|
-
return h('div', { className: 'dsh-llm-hub-balance' }, children)
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
if (state.status === 'error') {
|
|
532
|
-
children.push(h('span', { key: 'error', className: 'dsh-llm-hub-balance__error' }, `${t('failed')}: ${state.error}`))
|
|
533
|
-
children.push(h('span', { key: 'spacer', className: 'dsh-llm-hub-balance__spacer' }))
|
|
534
|
-
children.push(h('button', {
|
|
535
|
-
key: 'retry',
|
|
536
|
-
type: 'button',
|
|
537
|
-
className: 'dsh-llm-hub-balance__button',
|
|
538
|
-
onClick: load
|
|
539
|
-
}, t('retry')))
|
|
540
|
-
return h('div', { className: 'dsh-llm-hub-balance' }, children)
|
|
541
|
-
}
|
|
733
|
+
const primary = state.status === 'ok' && Array.isArray(state.balances) ? state.balances[0] : undefined
|
|
542
734
|
|
|
543
|
-
//
|
|
544
|
-
const primary = state.balances[0]
|
|
545
|
-
if (primary === undefined) {
|
|
546
|
-
children.push(h('span', { key: 'empty', className: 'dsh-llm-hub-balance__breakdown' }, t('loading')))
|
|
547
|
-
return h('div', { className: 'dsh-llm-hub-balance' }, children)
|
|
548
|
-
}
|
|
549
|
-
// 余额预警:拿到数字后顺手问 host 是否低于阈值。状态独立:
|
|
550
|
-
// warning=null 表示还没回(不显示标签)/ level='low' 才上色 + 提示。
|
|
735
|
+
// 余额预警:无条件在最外层声明 Hook,遵从 Rules of Hooks!
|
|
551
736
|
React.useEffect(() => {
|
|
737
|
+
if (!primary || typeof primary.total !== 'string') return
|
|
552
738
|
fetch(WARNING_URL, {
|
|
553
739
|
method: 'POST',
|
|
554
740
|
headers: { 'content-type': 'application/json', accept: 'application/json' },
|
|
@@ -566,35 +752,85 @@ window.__ModuleLoader__.load({
|
|
|
566
752
|
}
|
|
567
753
|
})
|
|
568
754
|
.catch(() => setWarning(null))
|
|
569
|
-
}, [primary
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
className: 'dsh-llm-hub-balance__warn',
|
|
579
|
-
title: warning.text
|
|
580
|
-
}, '⚠️'))
|
|
755
|
+
}, [primary?.total, primary?.currency])
|
|
756
|
+
|
|
757
|
+
if (state.status === 'nokey') {
|
|
758
|
+
return h('div', { className: 'dsh-llm-hub-card' },
|
|
759
|
+
h('div', { className: 'dsh-llm-hub-card__meta' },
|
|
760
|
+
h('span', { className: 'dsh-llm-hub-warn' }, t('noKey')),
|
|
761
|
+
h('span', { className: 'dsh-llm-hub-subtext' }, t('noKeyHint'))
|
|
762
|
+
)
|
|
763
|
+
)
|
|
581
764
|
}
|
|
582
|
-
|
|
765
|
+
|
|
766
|
+
if (state.status === 'loading' || state.status === 'idle') {
|
|
767
|
+
return h('div', { className: 'dsh-llm-hub-card' },
|
|
768
|
+
h('div', { className: 'dsh-llm-hub-card__meta' },
|
|
769
|
+
h('span', { className: 'dsh-llm-hub-subtext' }, t('loading'))
|
|
770
|
+
)
|
|
771
|
+
)
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
if (state.status === 'error') {
|
|
775
|
+
return h('div', { className: 'dsh-llm-hub-card' },
|
|
776
|
+
h('div', { className: 'dsh-llm-hub-card__meta' },
|
|
777
|
+
h('span', { className: 'dsh-llm-hub-error' }, `${t('failed')}: ${state.error}`),
|
|
778
|
+
h('button', {
|
|
779
|
+
type: 'button',
|
|
780
|
+
className: 'dsh-llm-hub-btn',
|
|
781
|
+
onClick: load
|
|
782
|
+
}, h(RefreshIcon, { spinning: isRefreshing }), t('retry'))
|
|
783
|
+
)
|
|
784
|
+
)
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
if (!primary) {
|
|
788
|
+
return h('div', { className: 'dsh-llm-hub-card' },
|
|
789
|
+
h('div', { className: 'dsh-llm-hub-card__meta' },
|
|
790
|
+
h('span', { className: 'dsh-llm-hub-subtext' }, t('loading'))
|
|
791
|
+
)
|
|
792
|
+
)
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
const symbol = primary.currency === 'CNY' ? '¥' : primary.currency === 'USD' ? '$' : `${primary.currency} `
|
|
583
796
|
const breakdown = []
|
|
584
797
|
if (primary.granted !== '0' && primary.granted !== '0.00') breakdown.push(`${t('granted')} ${symbol}${primary.granted}`)
|
|
585
798
|
if (primary.toppedUp !== '0' && primary.toppedUp !== '0.00') breakdown.push(`${t('toppedUp')} ${symbol}${primary.toppedUp}`)
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
799
|
+
|
|
800
|
+
return h('div', { className: 'dsh-llm-hub-card' },
|
|
801
|
+
h('div', { className: 'dsh-llm-hub-card__meta' },
|
|
802
|
+
h('div', { className: 'dsh-llm-hub-card__tags' },
|
|
803
|
+
h('span', { className: 'dsh-llm-hub-balance-label' }, t('balance')),
|
|
804
|
+
h('span', { className: 'dsh-llm-hub-amount' + (warning?.level === 'low' ? ' is-low' : '') }, `${symbol}${primary.total}`),
|
|
805
|
+
h('span', { className: 'dsh-llm-hub-meta-sep' }, '·'),
|
|
806
|
+
h('span', { className: 'dsh-llm-hub-meta-text' }, '官方直连'),
|
|
807
|
+
h('span', { className: 'dsh-llm-hub-meta-sep' }, '·'),
|
|
808
|
+
h('span', {
|
|
809
|
+
className: 'dsh-llm-hub-health-badge ' + (state.isAvailable ? 'is-ok' : 'is-err'),
|
|
810
|
+
title: state.isAvailable ? '官方接口连通正常' : t('unavailable')
|
|
811
|
+
},
|
|
812
|
+
h('span', { className: 'dsh-llm-hub-health-dot ' + (state.isAvailable ? 'is-ok' : 'is-err') }),
|
|
813
|
+
state.isAvailable ? '连通正常' : t('unavailable')
|
|
814
|
+
),
|
|
815
|
+
breakdown.length > 0 ? h('span', { className: 'dsh-llm-hub-subtext' }, `(${breakdown.join(' · ')})`) : null,
|
|
816
|
+
hiddenFromDropdown ? h('span', { className: 'dsh-llm-hub-chip dsh-llm-hub-chip--hidden' }, t('availabilityUnavailable')) : null
|
|
817
|
+
),
|
|
818
|
+
h('div', { className: 'dsh-llm-hub-card__btn-group' },
|
|
819
|
+
h(ApiKeyBadge, {
|
|
820
|
+
apiKey: state.apiKey,
|
|
821
|
+
keyMasked: state.keyMasked,
|
|
822
|
+
t
|
|
823
|
+
}),
|
|
824
|
+
h('button', {
|
|
825
|
+
type: 'button',
|
|
826
|
+
className: 'dsh-llm-hub-btn',
|
|
827
|
+
disabled: isRefreshing,
|
|
828
|
+
title: t('refresh'),
|
|
829
|
+
onClick: load
|
|
830
|
+
}, h(RefreshIcon, { spinning: isRefreshing }))
|
|
831
|
+
)
|
|
832
|
+
)
|
|
833
|
+
)
|
|
598
834
|
}
|
|
599
835
|
|
|
600
836
|
/**
|
|
@@ -676,7 +912,9 @@ window.__ModuleLoader__.load({
|
|
|
676
912
|
const verdict = Array.isArray(availabilityState.providers)
|
|
677
913
|
? availabilityState.providers.find((entry) => entry.provider === provider)
|
|
678
914
|
: undefined
|
|
679
|
-
const hiddenFromDropdown =
|
|
915
|
+
const hiddenFromDropdown = Array.isArray(availabilityState.hidden)
|
|
916
|
+
? availabilityState.hidden.includes(provider)
|
|
917
|
+
: (verdict !== undefined && verdict.state === 'unavailable')
|
|
680
918
|
|
|
681
919
|
React.useEffect(() => {
|
|
682
920
|
if (provider === '' || status === null || status.ok !== true || !status.balanceAdapter) return undefined
|
|
@@ -778,90 +1016,108 @@ window.__ModuleLoader__.load({
|
|
|
778
1016
|
// 2026-09-15 owner 报「为啥都显示状态读取失败」—— 页面一打开、或 DSH 刚重启
|
|
779
1017
|
// 导致这次 fetch 断掉时,三行全是这句,看着像插件坏了,其实只是还没加载完。
|
|
780
1018
|
if (status === null) {
|
|
781
|
-
return h('div', { className: 'dsh-llm-hub-
|
|
782
|
-
h('
|
|
783
|
-
|
|
1019
|
+
return h('div', { className: 'dsh-llm-hub-card' },
|
|
1020
|
+
h('div', { className: 'dsh-llm-hub-card__meta' },
|
|
1021
|
+
h('span', { className: 'dsh-llm-hub-subtext' }, t('statusLoading'))))
|
|
784
1022
|
}
|
|
785
1023
|
if (status.ok !== true) {
|
|
786
|
-
return h('div', { className: 'dsh-llm-hub-
|
|
787
|
-
h('
|
|
788
|
-
|
|
1024
|
+
return h('div', { className: 'dsh-llm-hub-card' },
|
|
1025
|
+
h('div', { className: 'dsh-llm-hub-card__meta' },
|
|
1026
|
+
h('span', { className: 'dsh-llm-hub-error' }, typeof status.error === 'string' ? status.error : t('statusFail'))))
|
|
789
1027
|
}
|
|
790
1028
|
|
|
791
|
-
//
|
|
792
|
-
const
|
|
793
|
-
|
|
794
|
-
facts.push(h('span', { key: 'name', className: 'dsh-llm-hub-balance__amount' }, status.displayName))
|
|
795
|
-
facts.push(h('span', { key: 'count', className: 'dsh-llm-hub-balance__tag' }, `${t('configuredModels')} ${status.modelCount} ${t('modelsUnit')}`))
|
|
796
|
-
facts.push(h('span', { key: 'key', className: 'dsh-llm-hub-balance__tag' }, status.keyConfigured === true ? 'Key ✓' : t('keyMissing')))
|
|
797
|
-
// 协议与接入地址(2026-09-14 owner:「接入的协议和 apiurl 也需要可以看」)。
|
|
798
|
-
// 这两项决定了这个 provider 到底连去哪、用哪套报文;出问题时第一眼要看的就是它们,
|
|
799
|
-
// 之前只能去翻 settings.yaml。地址只显示域名,完整 URL 放 title —— 一行放不下,
|
|
800
|
-
// 而域名已经够回答「连的是不是我以为的那个网关」。
|
|
1029
|
+
// 元信息标签组(不重复展示外层已有的大标题,只呈现核心事实)
|
|
1030
|
+
const tags = []
|
|
1031
|
+
tags.push(h('span', { key: 'count', className: 'dsh-llm-hub-tag' }, `${t('configuredModels')} ${status.modelCount} ${t('modelsUnit')}`))
|
|
801
1032
|
if (typeof status.api === 'string' && status.api !== '') {
|
|
802
|
-
|
|
1033
|
+
const shortApi = status.api.replace('-messages', '').replace('-completions', '')
|
|
1034
|
+
tags.push(h('span', { key: 'api', className: 'dsh-llm-hub-tag', title: `${t('protocol')}: ${status.api}` }, shortApi))
|
|
803
1035
|
}
|
|
804
1036
|
if (typeof status.baseURL === 'string' && status.baseURL !== '') {
|
|
805
1037
|
let host = status.baseURL
|
|
806
1038
|
try { host = new URL(status.baseURL).host } catch {}
|
|
807
|
-
|
|
1039
|
+
tags.push(h('span', { key: 'sep2', className: 'dsh-llm-hub-meta-sep' }, '·'))
|
|
1040
|
+
tags.push(h('span', {
|
|
808
1041
|
key: 'base',
|
|
809
|
-
className: 'dsh-llm-hub-
|
|
1042
|
+
className: 'dsh-llm-hub-meta-text',
|
|
810
1043
|
title: `${t('endpoint')}: ${status.baseURL}`
|
|
811
1044
|
}, host))
|
|
812
1045
|
}
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
1046
|
+
|
|
1047
|
+
// 健康与延迟直接融入 Provider 行(消除冗余下方看板)
|
|
1048
|
+
const probing = probe !== null && probe.phase === 'loading'
|
|
1049
|
+
const probeDone = probe !== null && probe.phase === 'done'
|
|
1050
|
+
const latency = probeDone && typeof probe.body?.latencyMs === 'number'
|
|
1051
|
+
? probe.body.latencyMs
|
|
1052
|
+
: (typeof verdict?.latencyMs === 'number' ? verdict.latencyMs : null)
|
|
1053
|
+
const isReachable = probeDone
|
|
1054
|
+
? (probe.body?.ok === true && probe.body?.reachable === true)
|
|
1055
|
+
: (verdict === undefined || verdict.state !== 'unavailable')
|
|
1056
|
+
const errMsg = probeDone
|
|
1057
|
+
? (probe.body?.error ?? '')
|
|
1058
|
+
: (verdict?.state === 'unavailable' ? (verdict.reason ?? t('availabilityUnavailable')) : '')
|
|
1059
|
+
|
|
1060
|
+
tags.push(h('span', { key: 'sep3', className: 'dsh-llm-hub-meta-sep' }, '·'))
|
|
1061
|
+
if (probing) {
|
|
1062
|
+
tags.push(h('span', { key: 'health', className: 'dsh-llm-hub-health-badge' }, h(RefreshIcon, { spinning: true }), t('probing')))
|
|
1063
|
+
} else if (isReachable) {
|
|
1064
|
+
const latencyClass = latency === null ? 'is-fast' : (latency < 300 ? 'is-fast' : (latency < 800 ? 'is-normal' : 'is-slow'))
|
|
1065
|
+
tags.push(h('span', {
|
|
1066
|
+
key: 'health',
|
|
1067
|
+
className: 'dsh-llm-hub-health-badge is-ok',
|
|
1068
|
+
title: latency !== null ? `连通正常 · 延迟 ${latency}ms` : '连通正常'
|
|
1069
|
+
},
|
|
1070
|
+
h('span', { className: 'dsh-llm-hub-health-dot is-ok' }),
|
|
1071
|
+
latency !== null
|
|
1072
|
+
? h('span', { className: `dsh-llm-hub-latency-badge ${latencyClass}` }, `${latency}ms`)
|
|
1073
|
+
: '正常'
|
|
1074
|
+
))
|
|
1075
|
+
} else {
|
|
1076
|
+
tags.push(h('span', {
|
|
1077
|
+
key: 'health',
|
|
1078
|
+
className: 'dsh-llm-hub-health-badge is-err dsh-llm-hub-chip--hidden',
|
|
1079
|
+
title: errMsg || t('availabilityUnavailable')
|
|
1080
|
+
},
|
|
1081
|
+
h('span', { className: 'dsh-llm-hub-health-dot is-err' }),
|
|
1082
|
+
t('availabilityUnavailable')
|
|
1083
|
+
))
|
|
821
1084
|
}
|
|
822
1085
|
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
actions.push(h('span', {
|
|
831
|
-
key: 'hidden-chip',
|
|
832
|
-
className: 'dsh-llm-hub-chip dsh-llm-hub-chip--hidden',
|
|
833
|
-
title: typeof verdict.reason === 'string' ? verdict.reason : ''
|
|
834
|
-
}, t('availabilityUnavailable')))
|
|
1086
|
+
const balanceText = renderBalance(balance, t)
|
|
1087
|
+
if (balanceText !== null) {
|
|
1088
|
+
tags.push(h('span', { key: 'sep4', className: 'dsh-llm-hub-meta-sep' }, '·'))
|
|
1089
|
+
tags.push(h('span', { key: 'bal', className: 'dsh-llm-hub-meta-text' }, balanceText))
|
|
1090
|
+
if (warning !== null && warning.level === 'low') {
|
|
1091
|
+
tags.push(h('span', { key: 'warn', className: 'dsh-llm-hub-warn-badge', title: warning.text }, '⚠️ 额度偏低'))
|
|
1092
|
+
}
|
|
835
1093
|
}
|
|
1094
|
+
|
|
1095
|
+
// 收拢的操作按钮(Ghost 风格)
|
|
836
1096
|
const noBaseURL = status.baseURL === undefined || status.baseURL === ''
|
|
837
|
-
const
|
|
1097
|
+
const probeBtn = (key, label, busy, spinning, onClick) => h('button', {
|
|
838
1098
|
key,
|
|
839
1099
|
type: 'button',
|
|
840
|
-
className: 'dsh-llm-hub-
|
|
1100
|
+
className: 'dsh-llm-hub-btn',
|
|
841
1101
|
disabled: busy,
|
|
842
1102
|
onClick
|
|
843
|
-
}, label)
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
// 目录拉取原先只对 modelgo 开放(它走 anthropic-messages,官方发现天然失效)。
|
|
858
|
-
// 但「网关上到底有哪些模型」对每个 provider 都有用 —— 智谱手填 8 个、网关在售
|
|
859
|
-
// 16 个,不拉一次根本不知道漏了什么。改为所有 pi-ai provider 都能拉。
|
|
860
|
-
// 探测不到的(没 baseURL 又不在已知网关表里)点了会得到明确提示,不会静默。
|
|
1103
|
+
}, spinning ? h(RefreshIcon, { spinning: true }) : null, label)
|
|
1104
|
+
|
|
1105
|
+
const btnGroup = [
|
|
1106
|
+
h(ApiKeyBadge, {
|
|
1107
|
+
key: 'key',
|
|
1108
|
+
apiKey: status.apiKey,
|
|
1109
|
+
keyMasked: status.keyMasked,
|
|
1110
|
+
t
|
|
1111
|
+
})
|
|
1112
|
+
]
|
|
1113
|
+
|
|
1114
|
+
if (!noBaseURL) {
|
|
861
1115
|
const pulling = catalog !== null && catalog.phase === 'loading'
|
|
862
|
-
|
|
1116
|
+
btnGroup.push(probeBtn('probe', probing ? t('probing') : t('probe'), probing, probing, runProbe))
|
|
1117
|
+
btnGroup.push(probeBtn('catalog', pulling ? t('pulling') : t('pullCatalog'), pulling, pulling, pullCatalog))
|
|
863
1118
|
}
|
|
864
|
-
|
|
1119
|
+
|
|
1120
|
+
// 配额不可用时的上游原因不进正文,只留在 title 里
|
|
865
1121
|
const quotaNote = balance !== null && typeof balance === 'object' && balance.ok === true
|
|
866
1122
|
&& balance.available === false && typeof balance.reason === 'string' && balance.reason.length > 0
|
|
867
1123
|
? balance.reason
|
|
@@ -871,12 +1127,8 @@ window.__ModuleLoader__.load({
|
|
|
871
1127
|
if (!noBaseURL && catalog !== null && catalog.phase === 'done') {
|
|
872
1128
|
const body = catalog.body
|
|
873
1129
|
if (body?.ok === true && Array.isArray(body.models)) {
|
|
874
|
-
|
|
875
|
-
actions.push(probeButton('copy', copied ? t('copied') : t('copyIds'), false, copyIds))
|
|
1130
|
+
btnGroup.push(probeBtn('copy', copied ? t('copied') : t('copyIds'), false, false, copyIds))
|
|
876
1131
|
|
|
877
|
-
// 拉到目录就把它摆出来。之前这里只报一个数字加「复制全部 id」,
|
|
878
|
-
// 等于让人把 71 个 id 粘到配置文件里自己挑 —— 最后一公里留给了人。
|
|
879
|
-
// 已配置的默认勾上,人只需要动增量。
|
|
880
1132
|
const configured = new Set(Array.isArray(status.modelIds) ? status.modelIds : [])
|
|
881
1133
|
const picked = selected === null ? configured : selected
|
|
882
1134
|
const toggle = (id) => {
|
|
@@ -889,14 +1141,13 @@ window.__ModuleLoader__.load({
|
|
|
889
1141
|
picker = h('div', { className: 'dsh-llm-hub-picker' },
|
|
890
1142
|
h('div', { className: 'dsh-llm-hub-picker__head' },
|
|
891
1143
|
h('span', null, `${t('pick')}(${picked.size}/${body.models.length})`),
|
|
892
|
-
|
|
1144
|
+
probeBtn('save', saveState === 'saving' ? t('saving') : saveState === 'done' ? t('saved') : saveState === 'fail' ? t('saveFail') : t('save'), saveState === 'saving', saveState === 'saving', saveModels)),
|
|
893
1145
|
h('div', { className: 'dsh-llm-hub-picker__list' },
|
|
894
1146
|
body.models.map((model) => {
|
|
895
1147
|
const alias = aliasesStore.aliasFor(provider, model.id)
|
|
896
1148
|
return h('label', {
|
|
897
1149
|
key: model.id,
|
|
898
1150
|
className: 'dsh-llm-hub-picker__item',
|
|
899
|
-
// 完整 id 永远在 title 里 —— 复制 model id 粘配置用。
|
|
900
1151
|
title: model.id
|
|
901
1152
|
},
|
|
902
1153
|
h('input', { type: 'checkbox', checked: picked.has(model.id), onChange: () => toggle(model.id) }),
|
|
@@ -904,25 +1155,32 @@ window.__ModuleLoader__.load({
|
|
|
904
1155
|
configured.has(model.id) ? h('span', { className: 'dsh-llm-hub-picker__tag' }, t('configured')) : null)
|
|
905
1156
|
})))
|
|
906
1157
|
} else {
|
|
907
|
-
|
|
1158
|
+
btnGroup.push(h('span', { key: 'cat-bad', className: 'dsh-llm-hub-error' }, `${t('failed')}: ${body?.error ?? ''}`))
|
|
908
1159
|
}
|
|
909
1160
|
}
|
|
1161
|
+
|
|
1162
|
+
const actionsRight = []
|
|
1163
|
+
if (hiddenFromDropdown) {
|
|
1164
|
+
actionsRight.push(h('span', {
|
|
1165
|
+
key: 'hidden-chip',
|
|
1166
|
+
className: 'dsh-llm-hub-chip dsh-llm-hub-chip--hidden',
|
|
1167
|
+
title: typeof verdict?.reason === 'string' ? verdict.reason : ''
|
|
1168
|
+
}, t('availabilityUnavailable')))
|
|
1169
|
+
}
|
|
1170
|
+
|
|
910
1171
|
return h('div', null,
|
|
911
|
-
h('div', { className: 'dsh-llm-hub-
|
|
912
|
-
h('div', { key: '
|
|
913
|
-
|
|
1172
|
+
h('div', { className: 'dsh-llm-hub-card', title: quotaNote },
|
|
1173
|
+
h('div', { key: 'meta', className: 'dsh-llm-hub-card__meta' },
|
|
1174
|
+
h('div', { className: 'dsh-llm-hub-card__tags' }, tags),
|
|
1175
|
+
h('div', { className: 'dsh-llm-hub-card__btn-group dsh-llm-hub-actions' },
|
|
1176
|
+
btnGroup,
|
|
1177
|
+
actionsRight.length > 0 ? actionsRight : null
|
|
1178
|
+
)
|
|
1179
|
+
)
|
|
1180
|
+
),
|
|
914
1181
|
picker)
|
|
915
1182
|
}
|
|
916
1183
|
|
|
917
|
-
/**
|
|
918
|
-
* 模型页页脚:插件版本 + 仓库 + 反馈入口。
|
|
919
|
-
*
|
|
920
|
-
* 2026-09-15 owner:「人家写的插件都有,你也加上,不然别人没有反馈,没法闭环」。
|
|
921
|
-
* 放在 settings.models.footer 而不是每张 provider 卡片上 —— 卡片有几个就会重复
|
|
922
|
-
* 几次,而这行信息整页只需要一份。
|
|
923
|
-
* @param props - 注入的 `t`。
|
|
924
|
-
* @returns 页脚节点;元信息读不到时返回 null(不占位、不报错)。
|
|
925
|
-
*/
|
|
926
1184
|
/**
|
|
927
1185
|
* 外部 harness 一览。
|
|
928
1186
|
*
|
|
@@ -974,101 +1232,153 @@ window.__ModuleLoader__.load({
|
|
|
974
1232
|
}
|
|
975
1233
|
|
|
976
1234
|
/**
|
|
977
|
-
*
|
|
978
|
-
*
|
|
979
|
-
*
|
|
980
|
-
* 不复用 availability payload:那个为下拉隐藏服务,shape 不一样;新加
|
|
981
|
-
* `/api/dsh-llm-hub/health` 路由返回带 latencyMs / status 的全字段。
|
|
982
|
-
* 状态 chip 与隐藏下拉那枚 chip 共用样式变量。
|
|
983
|
-
*
|
|
984
|
-
* 「重新探测」按钮 = availability 路由的 recheck(共用同一条底层 refresh
|
|
985
|
-
* 流程,避免双探),触发后乐观更新 checkedAt 让用户看见反馈。
|
|
986
|
-
*
|
|
987
|
-
* @param props - 注入的 `t` 和 `availability`(recheck 用)。
|
|
988
|
-
* @returns 健康看板;探测数据为空时返回 null(启动竞态)。
|
|
1235
|
+
* 健康看板卡:健康度已直接融合进 Provider 列表,下方不再冗余平铺整张大表。
|
|
1236
|
+
* 保持组件注册以保证向前兼容。
|
|
989
1237
|
*/
|
|
990
1238
|
function HealthCard(props) {
|
|
1239
|
+
return null
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
/**
|
|
1243
|
+
* 热门大模型预设库定义
|
|
1244
|
+
*/
|
|
1245
|
+
const PRESETS = [
|
|
1246
|
+
{
|
|
1247
|
+
id: 'deepseek',
|
|
1248
|
+
name: '🇨🇳 DeepSeek 官方',
|
|
1249
|
+
desc: 'V4.1 Flash / V4 Pro 极速官方直连',
|
|
1250
|
+
docUrl: 'https://platform.deepseek.com/api_keys',
|
|
1251
|
+
yaml: 'llm-deepseek:\n keyPool:\n llm-deepseek:\n - name: "主账号"\n env: DEEPSEEK_API_KEY\n models:\n - id: deepseek-flash\n name: DeepSeek-V4.1-Flash\n - id: deepseek-v4-pro\n name: DeepSeek-V4-Pro'
|
|
1252
|
+
},
|
|
1253
|
+
{
|
|
1254
|
+
id: 'minimax',
|
|
1255
|
+
name: '⚡ MiniMax',
|
|
1256
|
+
desc: 'MiniMax-M3 百万上下文超强大模型',
|
|
1257
|
+
docUrl: 'https://platform.minimaxi.com/user-center/basic-information/interface-key',
|
|
1258
|
+
yaml: 'minimax:\n displayName: MiniMax\n api: openai-completions\n baseURL: https://api.minimaxi.com/v1\n apiKeyEnv: MINIMAX_API_KEY\n models:\n - id: MiniMax-M3\n name: MiniMax-M3\n - id: MiniMax-M2.7\n name: MiniMax-M2.7'
|
|
1259
|
+
},
|
|
1260
|
+
{
|
|
1261
|
+
id: 'zhipu',
|
|
1262
|
+
name: '🧠 智谱清言 GLM',
|
|
1263
|
+
desc: 'GLM-5 / GLM-4.7 国内 Coding 利器',
|
|
1264
|
+
docUrl: 'https://open.bigmodel.cn/usercenter/apikeys',
|
|
1265
|
+
yaml: 'zai-coding-cn:\n displayName: 智谱 GLM\n api: openai-completions\n baseURL: https://open.bigmodel.cn/api/paas/v4\n apiKeyEnv: ZHIPU_API_KEY\n models:\n - id: glm-5-turbo\n - id: glm-4.7'
|
|
1266
|
+
},
|
|
1267
|
+
{
|
|
1268
|
+
id: 'volc',
|
|
1269
|
+
name: '🌋 火山方舟 Ark',
|
|
1270
|
+
desc: '字节豆包 Doubao-1.5 多模态矩阵',
|
|
1271
|
+
docUrl: 'https://console.volcengine.com/ark/region:ark+cn-beijing/apiKey',
|
|
1272
|
+
yaml: 'volcengine:\n displayName: 火山方舟\n api: openai-completions\n baseURL: https://ark.cn-beijing.volces.com/api/v3\n apiKeyEnv: ARK_API_KEY\n models:\n - id: doubao-1.5-pro-32k\n - id: doubao-1.5-lite-32k'
|
|
1273
|
+
},
|
|
1274
|
+
{
|
|
1275
|
+
id: 'siliconflow',
|
|
1276
|
+
name: '🚀 硅基流动',
|
|
1277
|
+
desc: 'SiliconFlow 聚合开源高性价比模型',
|
|
1278
|
+
docUrl: 'https://cloud.siliconflow.cn/account/ak',
|
|
1279
|
+
yaml: 'siliconflow:\n displayName: 硅基流动\n api: openai-completions\n baseURL: https://api.siliconflow.cn/v1\n apiKeyEnv: SILICONFLOW_API_KEY\n models:\n - id: deepseek-ai/DeepSeek-V3\n - id: Qwen/Qwen2.5-72B-Instruct'
|
|
1280
|
+
},
|
|
1281
|
+
{
|
|
1282
|
+
id: 'openrouter',
|
|
1283
|
+
name: '🌐 OpenRouter',
|
|
1284
|
+
desc: '聚合全球 Claude / GPT / Gemini 全模型',
|
|
1285
|
+
docUrl: 'https://openrouter.ai/keys',
|
|
1286
|
+
yaml: 'openrouter:\n displayName: OpenRouter\n api: openai-completions\n baseURL: https://openrouter.ai/api/v1\n apiKeyEnv: OPENROUTER_API_KEY\n models:\n - id: anthropic/claude-3.7-sonnet\n - id: deepseek/deepseek-r1'
|
|
1287
|
+
}
|
|
1288
|
+
]
|
|
1289
|
+
|
|
1290
|
+
/**
|
|
1291
|
+
* 顶部 Hero 统计横条:4 核心极客 KPI
|
|
1292
|
+
*/
|
|
1293
|
+
function HeroOverviewCard(props) {
|
|
991
1294
|
const t = typeof props.t === 'function' ? props.t : fallbackT
|
|
992
1295
|
const availability = props.availability
|
|
993
|
-
const [report, setReport] = React.useState(null)
|
|
994
|
-
const [busy, setBusy] = React.useState(false)
|
|
995
|
-
const load = React.useCallback(() => {
|
|
996
|
-
fetch(HEALTH_URL, { headers: { accept: 'application/json' } })
|
|
997
|
-
.then((res) => res.json())
|
|
998
|
-
.then((body) => { setReport(body !== null && typeof body === 'object' ? body : { ok: false }) })
|
|
999
|
-
.catch(() => setReport({ ok: false }))
|
|
1000
|
-
}, [])
|
|
1001
|
-
React.useEffect(() => { load() }, [load])
|
|
1002
|
-
// availability store 推 probing=true 时也刷一次 —— 探测完成后数据更全
|
|
1003
1296
|
const availabilityState = availability === undefined
|
|
1004
|
-
? {
|
|
1297
|
+
? { providers: [] }
|
|
1005
1298
|
: React.useSyncExternalStore(availability.subscribe, availability.snapshot)
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
h('div', { className: 'dsh-llm-hub-health__head' },
|
|
1016
|
-
h('span', { className: 'dsh-llm-hub-health__title' }, t('healthTitle')),
|
|
1017
|
-
h('span', null, t('healthNoData'))))
|
|
1018
|
-
}
|
|
1299
|
+
|
|
1300
|
+
const list = Array.isArray(availabilityState.providers) ? availabilityState.providers : []
|
|
1301
|
+
const totalProviders = list.length
|
|
1302
|
+
const readyProviders = list.filter((p) => p.state !== 'unavailable').length
|
|
1303
|
+
const totalModels = list.reduce((acc, p) => acc + (typeof p.modelCount === 'number' ? p.modelCount : 0), 0)
|
|
1304
|
+
|
|
1305
|
+
const latencies = list.map((p) => p.latencyMs).filter((l) => typeof l === 'number' && l > 0)
|
|
1306
|
+
const minLatency = latencies.length > 0 ? Math.min(...latencies) : null
|
|
1307
|
+
|
|
1019
1308
|
ensureStyle()
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1309
|
+
|
|
1310
|
+
return h('div', { className: 'dsh-llm-hub-hero' },
|
|
1311
|
+
h('div', { className: 'dsh-llm-hub-hero__card' },
|
|
1312
|
+
h('span', { className: 'dsh-llm-hub-hero__num' }, totalProviders > 0 ? `${readyProviders} / ${totalProviders}` : '—'),
|
|
1313
|
+
h('span', { className: 'dsh-llm-hub-hero__label' }, t('heroProviders'))),
|
|
1314
|
+
h('div', { className: 'dsh-llm-hub-hero__card' },
|
|
1315
|
+
h('span', { className: 'dsh-llm-hub-hero__num' }, totalModels > 0 ? `${totalModels}` : '—'),
|
|
1316
|
+
h('span', { className: 'dsh-llm-hub-hero__label' }, t('heroModels'))),
|
|
1317
|
+
h('div', { className: 'dsh-llm-hub-hero__card' },
|
|
1318
|
+
h('span', { className: 'dsh-llm-hub-hero__num' }, minLatency !== null ? `${minLatency}ms` : '—'),
|
|
1319
|
+
h('span', { className: 'dsh-llm-hub-hero__label' }, t('heroFastest'))),
|
|
1320
|
+
h('div', { className: 'dsh-llm-hub-hero__card' },
|
|
1321
|
+
h('span', { className: 'dsh-llm-hub-hero__num' }, '🛡️ 100%'),
|
|
1322
|
+
h('span', { className: 'dsh-llm-hub-hero__label' }, t('heroSecSafe'))))
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
/**
|
|
1326
|
+
* 热门大模型一键装配抽屉
|
|
1327
|
+
*/
|
|
1328
|
+
function QuickPresetsCard(props) {
|
|
1329
|
+
const t = typeof props.t === 'function' ? props.t : fallbackT
|
|
1330
|
+
const [expanded, setExpanded] = React.useState(false)
|
|
1331
|
+
const [selected, setSelected] = React.useState(null)
|
|
1332
|
+
const [copied, setCopied] = React.useState(false)
|
|
1333
|
+
|
|
1334
|
+
ensureStyle()
|
|
1335
|
+
|
|
1336
|
+
const copyYaml = (e, yaml) => {
|
|
1337
|
+
e.stopPropagation()
|
|
1338
|
+
navigator.clipboard?.writeText(yaml).then(() => {
|
|
1339
|
+
setCopied(true)
|
|
1340
|
+
setTimeout(() => setCopied(false), 2000)
|
|
1341
|
+
}).catch(() => {})
|
|
1037
1342
|
}
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
h('
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
className: 'dsh-llm-hub-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1343
|
+
|
|
1344
|
+
return h('div', { className: 'dsh-llm-hub-presets' },
|
|
1345
|
+
h('div', {
|
|
1346
|
+
className: 'dsh-llm-hub-presets__head',
|
|
1347
|
+
onClick: () => setExpanded((prev) => !prev)
|
|
1348
|
+
},
|
|
1349
|
+
h('span', { className: 'dsh-llm-hub-presets__title' },
|
|
1350
|
+
t('presetsTitle'),
|
|
1351
|
+
h('span', { style: { fontSize: '10px', opacity: 0.6 } }, expanded ? ' ▲ 收起' : ' ▼ 展开模版')),
|
|
1352
|
+
h('span', { className: 'dsh-llm-hub-subtext' }, t('presetsSub'))),
|
|
1353
|
+
expanded ? h('div', { className: 'dsh-llm-hub-presets__grid' },
|
|
1354
|
+
PRESETS.map((p) => h('div', {
|
|
1355
|
+
key: p.id,
|
|
1356
|
+
className: 'dsh-llm-hub-presets__item',
|
|
1357
|
+
onClick: () => setSelected(selected?.id === p.id ? null : p)
|
|
1358
|
+
},
|
|
1359
|
+
h('span', { className: 'dsh-llm-hub-presets__name' }, p.name),
|
|
1360
|
+
h('span', { className: 'dsh-llm-hub-presets__desc' }, p.desc)))) : null,
|
|
1361
|
+
expanded && selected !== null ? h('div', { className: 'dsh-llm-hub-presets__detail' },
|
|
1362
|
+
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' } },
|
|
1363
|
+
h('span', { style: { fontWeight: '600' } }, `${selected.name} 配置模版`),
|
|
1364
|
+
h('div', { style: { display: 'flex', gap: '6px' } },
|
|
1365
|
+
h('button', {
|
|
1366
|
+
type: 'button',
|
|
1367
|
+
className: 'dsh-llm-hub-btn',
|
|
1368
|
+
onClick: (e) => copyYaml(e, selected.yaml)
|
|
1369
|
+
}, copied ? t('presetsCopied') : t('presetsCopyBtn')),
|
|
1370
|
+
h('a', {
|
|
1371
|
+
href: selected.docUrl,
|
|
1372
|
+
target: '_blank',
|
|
1373
|
+
rel: 'noreferrer',
|
|
1374
|
+
className: 'dsh-llm-hub-btn',
|
|
1375
|
+
style: { textDecoration: 'none' }
|
|
1376
|
+
}, t('presetsDocsBtn')))),
|
|
1377
|
+
h('pre', { className: 'dsh-llm-hub-presets__code' }, selected.yaml)) : null)
|
|
1059
1378
|
}
|
|
1060
1379
|
|
|
1061
1380
|
/**
|
|
1062
|
-
* 多账号 key
|
|
1063
|
-
*
|
|
1064
|
-
* 设计为调试 / 可见性卡:用户想知道「我现在用的是哪一把 key」「下一把是谁」
|
|
1065
|
-
* 「上次失败用的是哪个 env」。**不**显示 apiKey 实际值,只显示 name + env 名。
|
|
1066
|
-
*
|
|
1067
|
-
* 暂不做「下一把」按钮 —— host 半 round-robin 已经在每次解析时推进,用户
|
|
1068
|
-
* 想强制换的话只要刷一次页面(或重探 health)就轮到下一个。
|
|
1069
|
-
*
|
|
1070
|
-
* @param props - 注入的 `t`。
|
|
1071
|
-
* @returns 轮换卡;没配 keyPool 时返 null。
|
|
1381
|
+
* 多账号 key 轮换卡:展示当前轮换池状态与活跃 Key
|
|
1072
1382
|
*/
|
|
1073
1383
|
function KeyPoolCard(props) {
|
|
1074
1384
|
const t = typeof props.t === 'function' ? props.t : fallbackT
|
|
@@ -1092,7 +1402,12 @@ window.__ModuleLoader__.load({
|
|
|
1092
1402
|
}
|
|
1093
1403
|
const rows = providers.map((entry) => {
|
|
1094
1404
|
const chosen = entry.lastChosen
|
|
1095
|
-
|
|
1405
|
+
const isActive = chosen !== null
|
|
1406
|
+
return h('div', {
|
|
1407
|
+
key: entry.provider,
|
|
1408
|
+
className: 'dsh-llm-hub-keypool__row' + (isActive ? ' is-active' : '')
|
|
1409
|
+
},
|
|
1410
|
+
h('span', { className: 'dsh-llm-hub-health-dot ' + (isActive ? 'is-ok' : '') }),
|
|
1096
1411
|
h('span', { className: 'dsh-llm-hub-keypool__name' }, entry.provider),
|
|
1097
1412
|
h('span', { className: 'dsh-llm-hub-keypool__meta' }, `${entry.total} keys`),
|
|
1098
1413
|
chosen === null
|
|
@@ -1103,8 +1418,8 @@ window.__ModuleLoader__.load({
|
|
|
1103
1418
|
})
|
|
1104
1419
|
return h('div', { className: 'dsh-llm-hub-keypool' },
|
|
1105
1420
|
h('div', { className: 'dsh-llm-hub-keypool__head' },
|
|
1106
|
-
h('span', { className: 'dsh-llm-hub-keypool__title' }, t('keypoolTitle')),
|
|
1107
|
-
h('span',
|
|
1421
|
+
h('span', { className: 'dsh-llm-hub-keypool__title' }, '🔄 ' + t('keypoolTitle')),
|
|
1422
|
+
h('span', { className: 'dsh-llm-hub-subtext' }, t('keypoolRound'))),
|
|
1108
1423
|
h('div', { className: 'dsh-llm-hub-keypool__rows' }, rows))
|
|
1109
1424
|
}
|
|
1110
1425
|
|
|
@@ -1152,11 +1467,7 @@ window.__ModuleLoader__.load({
|
|
|
1152
1467
|
if (report.ok !== true) return null
|
|
1153
1468
|
const candidates = Array.isArray(report.candidates) ? report.candidates : []
|
|
1154
1469
|
if (candidates.length === 0) {
|
|
1155
|
-
|
|
1156
|
-
return h('div', { className: 'dsh-llm-hub-routing' },
|
|
1157
|
-
h('div', { className: 'dsh-llm-hub-routing__head' },
|
|
1158
|
-
h('span', { className: 'dsh-llm-hub-routing__title' }, t('routingTitle')),
|
|
1159
|
-
h('span', null, t('routingUnconfigured'))))
|
|
1470
|
+
return null
|
|
1160
1471
|
}
|
|
1161
1472
|
ensureStyle()
|
|
1162
1473
|
const active = report.active
|
|
@@ -1169,21 +1480,22 @@ window.__ModuleLoader__.load({
|
|
|
1169
1480
|
h('span', { className: 'dsh-llm-hub-routing__title' }, t('routingTitle')),
|
|
1170
1481
|
active !== null
|
|
1171
1482
|
? h('span', { className: 'dsh-llm-hub-routing__active' },
|
|
1172
|
-
`${t('routingActive')}: ${
|
|
1173
|
-
h('span', { className: chipClass(active.state),
|
|
1174
|
-
: null)
|
|
1175
|
-
const rows = candidates.map((entry
|
|
1176
|
-
h('span', { className: 'dsh-llm-hub-routing__role' },
|
|
1483
|
+
`${t('routingActive')}: ${activeKey}`,
|
|
1484
|
+
h('span', { className: chipClass(active.state) }, active.state === 'available' ? '可用' : '不可用'))
|
|
1485
|
+
: h('span', null, t('routingUnconfigured')))
|
|
1486
|
+
const rows = candidates.map((entry) => h('div', { key: `${entry.provider}/${entry.model}`, className: 'dsh-llm-hub-routing__row' },
|
|
1487
|
+
h('span', { className: 'dsh-llm-hub-routing__role' }, entry.role === 'primary' ? t('routingPrimary') : t('routingFallback')),
|
|
1177
1488
|
h('span', { className: 'dsh-llm-hub-routing__name' }, `${entry.provider}/${entry.model}`),
|
|
1178
|
-
h('span', { className: chipClass(entry.state) }, entry.state)))
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1489
|
+
h('span', { className: chipClass(entry.state) }, entry.state === 'available' ? '可用' : '不可用')))
|
|
1490
|
+
return h('div', { className: 'dsh-llm-hub-routing' },
|
|
1491
|
+
head,
|
|
1492
|
+
h('div', { className: 'dsh-llm-hub-routing__rows' }, rows),
|
|
1493
|
+
showSuggestion
|
|
1494
|
+
? h('div', { className: 'dsh-llm-hub-routing__suggestion' }, `${t('routingSuggestion')}: ${recKey}`)
|
|
1495
|
+
: null,
|
|
1496
|
+
recommendation === null && candidates.length > 0
|
|
1497
|
+
? h('div', { className: 'dsh-llm-hub-routing__reason' }, t('routingNoRecommendation'))
|
|
1185
1498
|
: null)
|
|
1186
|
-
return h('div', { className: 'dsh-llm-hub-routing' }, head, h('div', { className: 'dsh-llm-hub-routing__rows' }, rows), footer)
|
|
1187
1499
|
}
|
|
1188
1500
|
|
|
1189
1501
|
/**
|
|
@@ -1198,14 +1510,7 @@ window.__ModuleLoader__.load({
|
|
|
1198
1510
|
}
|
|
1199
1511
|
|
|
1200
1512
|
/**
|
|
1201
|
-
*
|
|
1202
|
-
*
|
|
1203
|
-
* 显示 input / output / cache tokens + 调用次数 + 前 5 个最常用模型。
|
|
1204
|
-
* 不算钱 —— open source DSH 没收 chat 文本定价接口,每个 provider 写硬编码价目表
|
|
1205
|
-
* 既过时又快塌。把 token 数留给用户对照官方价目表,导出按钮拿整张 CSV。
|
|
1206
|
-
*
|
|
1207
|
-
* @param props - 注入的 `t`。
|
|
1208
|
-
* @returns 月度用量卡;统计未启用时返回 null(harness 一览照常显示)。
|
|
1513
|
+
* 用量卡 —— 本月调用与 token 统计。
|
|
1209
1514
|
*/
|
|
1210
1515
|
function UsageCard(props) {
|
|
1211
1516
|
const t = typeof props.t === 'function' ? props.t : fallbackT
|
|
@@ -1222,15 +1527,11 @@ window.__ModuleLoader__.load({
|
|
|
1222
1527
|
}, [])
|
|
1223
1528
|
if (report === null) return null
|
|
1224
1529
|
if (report.ready !== true) {
|
|
1225
|
-
|
|
1226
|
-
ensureStyle()
|
|
1227
|
-
return h('div', { className: 'dsh-llm-hub-usage' },
|
|
1228
|
-
h('div', { className: 'dsh-llm-hub-usage__head' },
|
|
1229
|
-
h('span', { className: 'dsh-llm-hub-usage__title' }, t('usageTitle')),
|
|
1230
|
-
h('span', null, t('usageDisabled'))))
|
|
1530
|
+
return null
|
|
1231
1531
|
}
|
|
1232
1532
|
ensureStyle()
|
|
1233
1533
|
const current = report.current
|
|
1534
|
+
const hasCalls = (current?.calls ?? 0) > 0
|
|
1234
1535
|
const topModels = Array.isArray(current?.topModels) ? current.topModels : []
|
|
1235
1536
|
const exportCsv = () => {
|
|
1236
1537
|
setBusy(true)
|
|
@@ -1258,8 +1559,8 @@ window.__ModuleLoader__.load({
|
|
|
1258
1559
|
.finally(() => setBusy(false))
|
|
1259
1560
|
}
|
|
1260
1561
|
const facts = []
|
|
1261
|
-
if (
|
|
1262
|
-
facts.push(h('span', { key: 'calls', className: 'dsh-llm-hub-usage__tag' }, `${current.calls
|
|
1562
|
+
if (hasCalls) {
|
|
1563
|
+
facts.push(h('span', { key: 'calls', className: 'dsh-llm-hub-usage__tag' }, `${current.calls} ${t('usageCalls')}`))
|
|
1263
1564
|
facts.push(h('span', { key: 'in', className: 'dsh-llm-hub-usage__tag' }, `${t('usageInput')} ${formatTokens(current.inputTokens)}`))
|
|
1264
1565
|
facts.push(h('span', { key: 'out', className: 'dsh-llm-hub-usage__tag' }, `${t('usageOutput')} ${formatTokens(current.outputTokens)}`))
|
|
1265
1566
|
if ((current.cacheReadTokens ?? 0) > 0 || (current.cacheWriteTokens ?? 0) > 0) {
|
|
@@ -1267,31 +1568,34 @@ window.__ModuleLoader__.load({
|
|
|
1267
1568
|
}
|
|
1268
1569
|
facts.push(h('span', { key: 'total', className: 'dsh-llm-hub-usage__tag' }, `${t('usageTotal')} ${formatTokens(current.totalTokens)}`))
|
|
1269
1570
|
}
|
|
1270
|
-
const topSection = topModels.length > 0
|
|
1271
|
-
? h('div', { className: 'dsh-llm-hub-usage__top' },
|
|
1272
|
-
h('div', { className: 'dsh-llm-hub-usage__title' }, t('usageTopModels')),
|
|
1273
|
-
topModels.map((row, index) => h('div', { key: `${row.provider}/${row.model}/${index}`, className: 'dsh-llm-hub-usage__top-row' },
|
|
1274
|
-
h('span', null, `${row.provider}/${row.model}`),
|
|
1275
|
-
h('small', null, `${row.calls} ${t('usageCalls')}`))))
|
|
1276
|
-
: h('div', null, t('usageNoData'))
|
|
1277
1571
|
return h('div', { className: 'dsh-llm-hub-usage' },
|
|
1278
1572
|
h('div', { className: 'dsh-llm-hub-usage__head' },
|
|
1279
1573
|
h('span', { className: 'dsh-llm-hub-usage__title' }, t('usageTitle')),
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
className: 'dsh-llm-hub-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1574
|
+
hasCalls
|
|
1575
|
+
? h('div', { className: 'dsh-llm-hub-usage__facts' }, facts)
|
|
1576
|
+
: h('span', { className: 'dsh-llm-hub-subtext' }, t('usageNoData'))),
|
|
1577
|
+
hasCalls && topModels.length > 0
|
|
1578
|
+
? h('div', { className: 'dsh-llm-hub-usage__top' },
|
|
1579
|
+
h('div', { className: 'dsh-llm-hub-usage__title' }, t('usageTopModels')),
|
|
1580
|
+
topModels.map((row, index) => h('div', { key: `${row.provider}/${row.model}/${index}`, className: 'dsh-llm-hub-usage__top-row' },
|
|
1581
|
+
h('span', null, `${row.provider}/${row.model}`),
|
|
1582
|
+
h('small', null, `${row.calls} ${t('usageCalls')}`))))
|
|
1583
|
+
: null,
|
|
1584
|
+
hasCalls
|
|
1585
|
+
? h('div', { className: 'dsh-llm-hub-usage__actions' },
|
|
1586
|
+
h('button', {
|
|
1587
|
+
type: 'button',
|
|
1588
|
+
className: 'dsh-llm-hub-foot__link',
|
|
1589
|
+
disabled: busy,
|
|
1590
|
+
onClick: exportCsv
|
|
1591
|
+
}, feedback === 'ok' ? t('usageExported') : feedback === 'fail' ? t('usageExportFail') : t('usageExport')),
|
|
1592
|
+
h('button', {
|
|
1593
|
+
type: 'button',
|
|
1594
|
+
className: 'dsh-llm-hub-foot__link',
|
|
1595
|
+
disabled: busy,
|
|
1596
|
+
onClick: clearRecords
|
|
1597
|
+
}, t('usageClear')))
|
|
1598
|
+
: null)
|
|
1295
1599
|
}
|
|
1296
1600
|
|
|
1297
1601
|
function HubFooter(props) {
|
|
@@ -1361,6 +1665,115 @@ window.__ModuleLoader__.load({
|
|
|
1361
1665
|
links)
|
|
1362
1666
|
}
|
|
1363
1667
|
|
|
1668
|
+
/**
|
|
1669
|
+
* Webkubor DSH 扩展家族互导 Dock
|
|
1670
|
+
*/
|
|
1671
|
+
function WebkuborSuiteDock(props) {
|
|
1672
|
+
const t = typeof props.t === 'function' ? props.t : fallbackT
|
|
1673
|
+
const [copiedPkg, setCopiedPkg] = React.useState(null)
|
|
1674
|
+
|
|
1675
|
+
ensureStyle()
|
|
1676
|
+
|
|
1677
|
+
const suite = [
|
|
1678
|
+
{
|
|
1679
|
+
id: '@dsh-plugins/dsh-bloom-theme',
|
|
1680
|
+
aliases: ['dsh-bloom-theme', '@dsh-plugins/dsh-bloom-theme'],
|
|
1681
|
+
name: 'Bloom Theme',
|
|
1682
|
+
icon: '🌸',
|
|
1683
|
+
desc: '落霞 Bloom 极客质感毛玻璃主题与代码高亮',
|
|
1684
|
+
repo: 'https://github.com/webkubor/dsh-bloom-theme',
|
|
1685
|
+
pkg: '@dsh-plugins/dsh-bloom-theme'
|
|
1686
|
+
},
|
|
1687
|
+
{
|
|
1688
|
+
id: '@dsh-plugins/dsh-llm-hub',
|
|
1689
|
+
aliases: ['dsh-llm-hub', '@dsh-plugins/dsh-llm-hub'],
|
|
1690
|
+
name: 'LLM Hub',
|
|
1691
|
+
icon: '🎛️',
|
|
1692
|
+
desc: '多模型网关、余额配额、多Key轮换与极客大屏',
|
|
1693
|
+
repo: 'https://github.com/webkubor/dsh-llm-hub',
|
|
1694
|
+
pkg: '@dsh-plugins/dsh-llm-hub',
|
|
1695
|
+
isCurrent: true
|
|
1696
|
+
},
|
|
1697
|
+
{
|
|
1698
|
+
id: '@dsh-plugins/dsh-user-mirror',
|
|
1699
|
+
aliases: ['dsh-user-mirror', 'dsh-mirror', '@dsh-plugins/dsh-user-mirror'],
|
|
1700
|
+
name: 'User Mirror',
|
|
1701
|
+
icon: '🪞',
|
|
1702
|
+
desc: '用户角色数字画像、习惯偏好与记忆网络',
|
|
1703
|
+
repo: 'https://github.com/webkubor/dsh-mirror',
|
|
1704
|
+
pkg: '@dsh-plugins/dsh-user-mirror'
|
|
1705
|
+
},
|
|
1706
|
+
{
|
|
1707
|
+
id: '@dsh-plugins/dsh-env-inspector',
|
|
1708
|
+
aliases: ['dsh-env-inspector', '@dsh-plugins/dsh-env-inspector'],
|
|
1709
|
+
name: 'Env Inspector',
|
|
1710
|
+
icon: '🖥️',
|
|
1711
|
+
desc: '端口监听释放、CLI工具链与环境大屏',
|
|
1712
|
+
repo: 'https://github.com/webkubor/dsh-env-inspector',
|
|
1713
|
+
pkg: '@dsh-plugins/dsh-env-inspector'
|
|
1714
|
+
}
|
|
1715
|
+
]
|
|
1716
|
+
|
|
1717
|
+
const handleCopy = (pkg) => {
|
|
1718
|
+
const cmd = `dsh plugin install ${pkg}`
|
|
1719
|
+
if (navigator.clipboard) {
|
|
1720
|
+
navigator.clipboard.writeText(cmd)
|
|
1721
|
+
}
|
|
1722
|
+
setCopiedPkg(pkg)
|
|
1723
|
+
setTimeout(() => setCopiedPkg(null), 2000)
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
return h('div', { className: 'dsh-suite-dock' },
|
|
1727
|
+
h('div', { className: 'dsh-suite-head' },
|
|
1728
|
+
h('h3', { className: 'dsh-suite-title' },
|
|
1729
|
+
h('span', null, '🌟'),
|
|
1730
|
+
t('suiteTitle')
|
|
1731
|
+
),
|
|
1732
|
+
h('span', { className: 'dsh-suite-desc' }, t('suiteDesc'))
|
|
1733
|
+
),
|
|
1734
|
+
h('div', { className: 'dsh-suite-cards' },
|
|
1735
|
+
...suite.map((item) => {
|
|
1736
|
+
const isInstalled = item.isCurrent
|
|
1737
|
+
const isCopied = copiedPkg === item.pkg
|
|
1738
|
+
|
|
1739
|
+
return h('div', { key: item.id, className: 'dsh-suite-card' },
|
|
1740
|
+
h('div', { className: 'dsh-suite-card-top' },
|
|
1741
|
+
h('span', { className: 'dsh-suite-card-icon' }, item.icon),
|
|
1742
|
+
h('a', {
|
|
1743
|
+
href: item.repo,
|
|
1744
|
+
target: '_blank',
|
|
1745
|
+
rel: 'noopener noreferrer',
|
|
1746
|
+
className: 'dsh-suite-card-name'
|
|
1747
|
+
}, item.name)
|
|
1748
|
+
),
|
|
1749
|
+
h('div', { className: 'dsh-suite-card-desc' }, item.desc),
|
|
1750
|
+
h('div', { className: 'dsh-suite-card-bottom' },
|
|
1751
|
+
isInstalled ? (
|
|
1752
|
+
h('span', { className: 'dsh-suite-badge-active' },
|
|
1753
|
+
h('span', { className: 'dsh-suite-badge-active-dot' }),
|
|
1754
|
+
t('suiteActive')
|
|
1755
|
+
)
|
|
1756
|
+
) : (
|
|
1757
|
+
h('button', {
|
|
1758
|
+
type: 'button',
|
|
1759
|
+
className: 'dsh-suite-btn-action',
|
|
1760
|
+
onClick: () => handleCopy(item.pkg),
|
|
1761
|
+
title: `安装命令: dsh plugin install ${item.pkg}`
|
|
1762
|
+
}, isCopied ? `✓ ${t('suiteCopied')}` : `⚡ ${t('suiteCopyInstall')}`)
|
|
1763
|
+
),
|
|
1764
|
+
h('a', {
|
|
1765
|
+
href: item.repo,
|
|
1766
|
+
target: '_blank',
|
|
1767
|
+
rel: 'noopener noreferrer',
|
|
1768
|
+
className: 'dsh-suite-link'
|
|
1769
|
+
}, 'GitHub ↗')
|
|
1770
|
+
)
|
|
1771
|
+
)
|
|
1772
|
+
})
|
|
1773
|
+
)
|
|
1774
|
+
)
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1364
1777
|
/**
|
|
1365
1778
|
* 可用性判定缓存 —— 所有会话共用一份(同一个 host、同一份设置)。
|
|
1366
1779
|
*
|
|
@@ -1517,6 +1930,20 @@ window.__ModuleLoader__.load({
|
|
|
1517
1930
|
// 第一版漏了 id,注册被静默丢弃:接口通、组件在、页面上什么都没有。
|
|
1518
1931
|
// harness 一览排在 footer 那行之上(order 更小):它讲的是「能派给谁」,
|
|
1519
1932
|
// 属于能力信息;footer 那行是插件自身的版本与链接,属于元信息。
|
|
1933
|
+
ctx.slots.inject('settings.models.footer', () => ctx.slots.register({
|
|
1934
|
+
name: 'settings.models.footer',
|
|
1935
|
+
id: 'dsh-llm-hub-hero',
|
|
1936
|
+
order: 60,
|
|
1937
|
+
locale: NS,
|
|
1938
|
+
inject: () => ({ t, availability })
|
|
1939
|
+
}, HeroOverviewCard))
|
|
1940
|
+
ctx.slots.inject('settings.models.footer', () => ctx.slots.register({
|
|
1941
|
+
name: 'settings.models.footer',
|
|
1942
|
+
id: 'dsh-llm-hub-presets',
|
|
1943
|
+
order: 65,
|
|
1944
|
+
locale: NS,
|
|
1945
|
+
inject: () => ({ t })
|
|
1946
|
+
}, QuickPresetsCard))
|
|
1520
1947
|
ctx.slots.inject('settings.models.footer', () => ctx.slots.register({
|
|
1521
1948
|
name: 'settings.models.footer',
|
|
1522
1949
|
id: 'dsh-llm-hub-health',
|
|
@@ -1559,6 +1986,13 @@ window.__ModuleLoader__.load({
|
|
|
1559
1986
|
locale: NS,
|
|
1560
1987
|
inject: () => ({ t, availability })
|
|
1561
1988
|
}, HubFooter))
|
|
1989
|
+
ctx.slots.inject('settings.models.footer', () => ctx.slots.register({
|
|
1990
|
+
name: 'settings.models.footer',
|
|
1991
|
+
id: 'dsh-llm-hub-suite',
|
|
1992
|
+
order: 110,
|
|
1993
|
+
locale: NS,
|
|
1994
|
+
inject: () => ({ t })
|
|
1995
|
+
}, WebkuborSuiteDock))
|
|
1562
1996
|
}
|
|
1563
1997
|
|
|
1564
1998
|
exports.apply = apply
|