@dsh-plugins/dsh-llm-hub 1.3.1 → 1.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/lib/client.js +201 -10
- package/lib/index.js +53 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
本项目遵循 [Semantic Versioning](https://semver.org/lang/zh-CN/)。
|
|
4
4
|
|
|
5
|
+
## [1.3.3] - 2026-09-25
|
|
6
|
+
|
|
7
|
+
### ✨ 订阅制配额可视化进度条与重置倒计时
|
|
8
|
+
|
|
9
|
+
- **📊 订阅制可视化进度条胶囊(Subscription Quota Gauge)**:
|
|
10
|
+
- 参考 `claude-usage-statusline` 经典轨道式设计,在 Provider 卡片中引入高质感平滑迷你进度条;
|
|
11
|
+
- 动态三色渐变指示:`> 50%` 充裕翡翠绿(`#10b981`)、`15%~50%` 预警金黄(`#f59e0b`)、`<= 15%` 告急珊瑚红(`#ef4444`);
|
|
12
|
+
- 同时支持主限额(如 MiniMax 5 小时滚动配额、智谱 5h 配额)与次级周限额的双重进度条并列展示;
|
|
13
|
+
- **⏱️ 重置时刻与倒计时精准指示(Reset Time Tracking)**:
|
|
14
|
+
- 在配额进度条后实时标注重置时刻(如 `(15:40 重置)`),悬浮卡片提供完整余量与周期说明;
|
|
15
|
+
- 适配器底层自动解析服务端返回的 `current_interval_end_time` / `next_reset_time` / `expire_time` 真实重置时刻。
|
|
16
|
+
|
|
17
|
+
## [1.3.2] - 2026-09-24
|
|
18
|
+
|
|
19
|
+
### 🐛 修复与健壮性提升
|
|
20
|
+
|
|
21
|
+
- **⚡ 解决 MiniMax 等大模型临时限额导致下拉永久死锁问题**:
|
|
22
|
+
- 核心逻辑解耦:重构可用性过滤判定,严格区分「致命凭据故障(`CREDENTIAL_MISSING` / `AUTH_REJECTED`)」与「临时配额/余额状态(`QUOTA_EXHAUSTED` / `BALANCE_EMPTY` / `PAYMENT_REQUIRED`)」;
|
|
23
|
+
- 临时配额用尽(如 MiniMax 5 小时滚动配额 / 周配额达到 100% 用量)仅在设置页与状态卡片进行友好 warning 预警,**不再从 `llm.listProviders()` 下拉菜单中强行抹除**;
|
|
24
|
+
- 彻底破除「额度用尽 → 下拉消失 → 无法选择该模型发请求 → 运行期遥测恢复机制永久无法触发」的死锁链条;
|
|
25
|
+
- **🔄 后台主动保鲜与自愈机制(Auto-healing Probe)**:
|
|
26
|
+
- 在插件生命周期内新增守护探针轮询(`TTL = 5m`),结合 `.unref()` 保证无副作用后台运行;
|
|
27
|
+
- 当 MiniMax 5 小时重置窗口或余额恢复后,后台探针自动感知并重置为 `available`,无需手动重启或进入设置页刷新。
|
|
28
|
+
|
|
5
29
|
## [1.3.1] - 2026-09-23
|
|
6
30
|
|
|
7
31
|
### ✨ UI 视觉升级与极客装配 Hub
|
package/lib/client.js
CHANGED
|
@@ -187,7 +187,12 @@ window.__ModuleLoader__.load({
|
|
|
187
187
|
usageClear: '清空记录',
|
|
188
188
|
usageCleared: '已清空',
|
|
189
189
|
usageClearConfirm: '确认清空?',
|
|
190
|
-
usageNoData: '本月还没有记录'
|
|
190
|
+
usageNoData: '本月还没有记录',
|
|
191
|
+
suiteTitle: 'Webkubor DSH 扩展家族',
|
|
192
|
+
suiteDesc: '一键构建完整智能体开发环境',
|
|
193
|
+
suiteActive: '已激活',
|
|
194
|
+
suiteCopyInstall: '复制安装',
|
|
195
|
+
suiteCopied: '已复制命令'
|
|
191
196
|
},
|
|
192
197
|
en: {
|
|
193
198
|
balance: 'Balance',
|
|
@@ -297,7 +302,12 @@ window.__ModuleLoader__.load({
|
|
|
297
302
|
usageClear: 'Clear records',
|
|
298
303
|
usageCleared: 'Cleared',
|
|
299
304
|
usageClearConfirm: 'Confirm clear?',
|
|
300
|
-
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'
|
|
301
311
|
}
|
|
302
312
|
}
|
|
303
313
|
|
|
@@ -537,6 +547,37 @@ window.__ModuleLoader__.load({
|
|
|
537
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)}',
|
|
538
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)}',
|
|
539
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
|
+
// 订阅制配额可视化进度条与重置倒计时(参考 claude-usage-statusline 规范)
|
|
551
|
+
'.dsh-llm-hub-quota-group{display:inline-flex;align-items:center;gap:6px;flex-wrap:wrap;vertical-align:middle}',
|
|
552
|
+
'.dsh-llm-hub-quota-bar{display:inline-flex;align-items:center;gap:4.5px;padding:1.5px 6px;border-radius:4px;background:var(--dsw-alias-bg-layer-2,rgba(255,255,255,.03));border:.5px solid var(--dsw-alias-border-l1,rgba(255,255,255,.08));font-size:10.5px;line-height:1;transition:all .15s ease}',
|
|
553
|
+
'.dsh-llm-hub-quota-bar:hover{border-color:var(--dsw-alias-brand-primary,rgba(59,130,246,.4));background:var(--dsw-alias-bg-layer-3,rgba(255,255,255,.06))}',
|
|
554
|
+
'.dsh-llm-hub-quota-label{font-weight:600;color:var(--dsw-alias-label-primary);font-size:10px}',
|
|
555
|
+
'.dsh-llm-hub-quota-track{width:46px;height:4.5px;border-radius:2.5px;background:rgba(255,255,255,.12);overflow:hidden;position:relative;display:inline-block;vertical-align:middle}',
|
|
556
|
+
'.dsh-llm-hub-quota-fill{height:100%;border-radius:2.5px;display:block;transition:width .3s cubic-bezier(0.4,0,0.2,1)}',
|
|
557
|
+
'.dsh-llm-hub-quota-fill.is-high{background:linear-gradient(90deg,#10b981,#34d399)}',
|
|
558
|
+
'.dsh-llm-hub-quota-fill.is-mid{background:linear-gradient(90deg,#f59e0b,#fbbf24)}',
|
|
559
|
+
'.dsh-llm-hub-quota-fill.is-low{background:linear-gradient(90deg,#ef4444,#f87171)}',
|
|
560
|
+
'.dsh-llm-hub-quota-pct{font-variant-numeric:tabular-nums;font-weight:600;color:var(--dsw-alias-label-primary);font-size:10.5px}',
|
|
561
|
+
'.dsh-llm-hub-quota-time{font-size:9.5px;color:var(--dsw-alias-label-secondary);opacity:.8;font-variant-numeric:tabular-nums;display:inline-flex;align-items:center;margin-left:1px}',
|
|
562
|
+
// Webkubor 插件全家桶互导矩阵
|
|
563
|
+
'.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)}',
|
|
564
|
+
'.dsh-suite-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px;flex-wrap:wrap;gap:8px}',
|
|
565
|
+
'.dsh-suite-title{font-size:12px;font-weight:650;color:var(--dsw-alias-label-primary);margin:0;display:flex;align-items:center;gap:6px}',
|
|
566
|
+
'.dsh-suite-desc{font-size:10.5px;color:var(--dsw-alias-label-tertiary);letter-spacing:.2px}',
|
|
567
|
+
'.dsh-suite-cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:8px}',
|
|
568
|
+
'.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)}',
|
|
569
|
+
'.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)}',
|
|
570
|
+
'.dsh-suite-card-top{display:flex;align-items:center;gap:6px;margin-bottom:4px}',
|
|
571
|
+
'.dsh-suite-card-icon{font-size:15px;line-height:1}',
|
|
572
|
+
'.dsh-suite-card-name{font-size:12px;font-weight:600;color:var(--dsw-alias-label-primary);text-decoration:none}',
|
|
573
|
+
'.dsh-suite-card-desc{font-size:10.5px;color:var(--dsw-alias-label-secondary);line-height:1.4;margin-bottom:8px;flex:1}',
|
|
574
|
+
'.dsh-suite-card-bottom{display:flex;align-items:center;justify-content:space-between;gap:6px;margin-top:auto;font-size:10.5px}',
|
|
575
|
+
'.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}',
|
|
576
|
+
'.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)}',
|
|
577
|
+
'.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}',
|
|
578
|
+
'.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)}',
|
|
579
|
+
'.dsh-suite-link{color:var(--dsw-alias-label-tertiary);text-decoration:none;font-size:10px;display:inline-flex;align-items:center}',
|
|
580
|
+
'.dsh-suite-link:hover{color:var(--dsw-alias-label-primary)}'
|
|
540
581
|
].join('')
|
|
541
582
|
document.head.appendChild(style)
|
|
542
583
|
}
|
|
@@ -659,7 +700,9 @@ window.__ModuleLoader__.load({
|
|
|
659
700
|
const verdict = Array.isArray(availabilityState.providers)
|
|
660
701
|
? availabilityState.providers.find((entry) => entry.provider === DEEPSEEK_PROVIDER)
|
|
661
702
|
: undefined
|
|
662
|
-
const hiddenFromDropdown =
|
|
703
|
+
const hiddenFromDropdown = Array.isArray(availabilityState.hidden)
|
|
704
|
+
? availabilityState.hidden.includes(DEEPSEEK_PROVIDER)
|
|
705
|
+
: (verdict !== undefined && verdict.state === 'unavailable')
|
|
663
706
|
|
|
664
707
|
const load = React.useCallback(() => {
|
|
665
708
|
if (!keyConfigured) {
|
|
@@ -830,14 +873,44 @@ window.__ModuleLoader__.load({
|
|
|
830
873
|
return `${t('balance')} ${symbol}${first.amount}${voucher}`
|
|
831
874
|
}
|
|
832
875
|
const meaning = balance.meaning === 'used' ? t('used') : t('remain')
|
|
833
|
-
const
|
|
876
|
+
const isUsed = balance.meaning === 'used'
|
|
877
|
+
const bars = []
|
|
834
878
|
for (const item of items.slice(0, 2)) {
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
879
|
+
const remainPct = isUsed ? Math.max(0, 100 - (item.percent ?? 0)) : (item.percent ?? 0)
|
|
880
|
+
const fillPct = Math.min(Math.max(remainPct, 0), 100)
|
|
881
|
+
const levelClass = fillPct > 50 ? 'is-high' : (fillPct > 20 ? 'is-mid' : 'is-low')
|
|
882
|
+
const labelText = item.label || '5h'
|
|
883
|
+
const resetTimeNote = item.resetsAt ? `(${item.resetsAt} 重置)` : ''
|
|
884
|
+
bars.push(h('span', {
|
|
885
|
+
key: labelText,
|
|
886
|
+
className: 'dsh-llm-hub-quota-bar',
|
|
887
|
+
title: `${labelText} ${meaning} ${item.percent}%${resetTimeNote ? ` · ${resetTimeNote}` : ''}`
|
|
888
|
+
},
|
|
889
|
+
h('span', { className: 'dsh-llm-hub-quota-label' }, labelText),
|
|
890
|
+
h('span', { className: 'dsh-llm-hub-quota-track' },
|
|
891
|
+
h('span', { className: `dsh-llm-hub-quota-fill ${levelClass}`, style: { width: `${fillPct}%` } })
|
|
892
|
+
),
|
|
893
|
+
h('span', { className: 'dsh-llm-hub-quota-pct' }, `${item.percent}%`),
|
|
894
|
+
resetTimeNote ? h('span', { className: 'dsh-llm-hub-quota-time' }, resetTimeNote) : null
|
|
895
|
+
))
|
|
896
|
+
if (typeof item.weeklyPercent === 'number') {
|
|
897
|
+
const weeklyRemain = isUsed ? Math.max(0, 100 - item.weeklyPercent) : item.weeklyPercent
|
|
898
|
+
const weeklyFill = Math.min(Math.max(weeklyRemain, 0), 100)
|
|
899
|
+
const weeklyClass = weeklyFill > 50 ? 'is-high' : (weeklyFill > 20 ? 'is-mid' : 'is-low')
|
|
900
|
+
bars.push(h('span', {
|
|
901
|
+
key: 'week',
|
|
902
|
+
className: 'dsh-llm-hub-quota-bar',
|
|
903
|
+
title: `${t('week')} ${meaning} ${item.weeklyPercent}%`
|
|
904
|
+
},
|
|
905
|
+
h('span', { className: 'dsh-llm-hub-quota-label' }, t('week')),
|
|
906
|
+
h('span', { className: 'dsh-llm-hub-quota-track' },
|
|
907
|
+
h('span', { className: `dsh-llm-hub-quota-fill ${weeklyClass}`, style: { width: `${weeklyFill}%` } })
|
|
908
|
+
),
|
|
909
|
+
h('span', { className: 'dsh-llm-hub-quota-pct' }, `${item.weeklyPercent}%`)
|
|
910
|
+
))
|
|
911
|
+
}
|
|
838
912
|
}
|
|
839
|
-
|
|
840
|
-
return `${t('planQuota')} ${level}${parts.join(' / ')}`
|
|
913
|
+
return h('span', { className: 'dsh-llm-hub-quota-group' }, bars)
|
|
841
914
|
}
|
|
842
915
|
|
|
843
916
|
/**
|
|
@@ -881,7 +954,9 @@ window.__ModuleLoader__.load({
|
|
|
881
954
|
const verdict = Array.isArray(availabilityState.providers)
|
|
882
955
|
? availabilityState.providers.find((entry) => entry.provider === provider)
|
|
883
956
|
: undefined
|
|
884
|
-
const hiddenFromDropdown =
|
|
957
|
+
const hiddenFromDropdown = Array.isArray(availabilityState.hidden)
|
|
958
|
+
? availabilityState.hidden.includes(provider)
|
|
959
|
+
: (verdict !== undefined && verdict.state === 'unavailable')
|
|
885
960
|
|
|
886
961
|
React.useEffect(() => {
|
|
887
962
|
if (provider === '' || status === null || status.ok !== true || !status.balanceAdapter) return undefined
|
|
@@ -1632,6 +1707,115 @@ window.__ModuleLoader__.load({
|
|
|
1632
1707
|
links)
|
|
1633
1708
|
}
|
|
1634
1709
|
|
|
1710
|
+
/**
|
|
1711
|
+
* Webkubor DSH 扩展家族互导 Dock
|
|
1712
|
+
*/
|
|
1713
|
+
function WebkuborSuiteDock(props) {
|
|
1714
|
+
const t = typeof props.t === 'function' ? props.t : fallbackT
|
|
1715
|
+
const [copiedPkg, setCopiedPkg] = React.useState(null)
|
|
1716
|
+
|
|
1717
|
+
ensureStyle()
|
|
1718
|
+
|
|
1719
|
+
const suite = [
|
|
1720
|
+
{
|
|
1721
|
+
id: '@dsh-plugins/dsh-bloom-theme',
|
|
1722
|
+
aliases: ['dsh-bloom-theme', '@dsh-plugins/dsh-bloom-theme'],
|
|
1723
|
+
name: 'Bloom Theme',
|
|
1724
|
+
icon: '🌸',
|
|
1725
|
+
desc: '落霞 Bloom 极客质感毛玻璃主题与代码高亮',
|
|
1726
|
+
repo: 'https://github.com/webkubor/dsh-bloom-theme',
|
|
1727
|
+
pkg: '@dsh-plugins/dsh-bloom-theme'
|
|
1728
|
+
},
|
|
1729
|
+
{
|
|
1730
|
+
id: '@dsh-plugins/dsh-llm-hub',
|
|
1731
|
+
aliases: ['dsh-llm-hub', '@dsh-plugins/dsh-llm-hub'],
|
|
1732
|
+
name: 'LLM Hub',
|
|
1733
|
+
icon: '🎛️',
|
|
1734
|
+
desc: '多模型网关、余额配额、多Key轮换与极客大屏',
|
|
1735
|
+
repo: 'https://github.com/webkubor/dsh-llm-hub',
|
|
1736
|
+
pkg: '@dsh-plugins/dsh-llm-hub',
|
|
1737
|
+
isCurrent: true
|
|
1738
|
+
},
|
|
1739
|
+
{
|
|
1740
|
+
id: '@dsh-plugins/dsh-user-mirror',
|
|
1741
|
+
aliases: ['dsh-user-mirror', 'dsh-mirror', '@dsh-plugins/dsh-user-mirror'],
|
|
1742
|
+
name: 'User Mirror',
|
|
1743
|
+
icon: '🪞',
|
|
1744
|
+
desc: '用户角色数字画像、习惯偏好与记忆网络',
|
|
1745
|
+
repo: 'https://github.com/webkubor/dsh-mirror',
|
|
1746
|
+
pkg: '@dsh-plugins/dsh-user-mirror'
|
|
1747
|
+
},
|
|
1748
|
+
{
|
|
1749
|
+
id: '@dsh-plugins/dsh-env-inspector',
|
|
1750
|
+
aliases: ['dsh-env-inspector', '@dsh-plugins/dsh-env-inspector'],
|
|
1751
|
+
name: 'Env Inspector',
|
|
1752
|
+
icon: '🖥️',
|
|
1753
|
+
desc: '端口监听释放、CLI工具链与环境大屏',
|
|
1754
|
+
repo: 'https://github.com/webkubor/dsh-env-inspector',
|
|
1755
|
+
pkg: '@dsh-plugins/dsh-env-inspector'
|
|
1756
|
+
}
|
|
1757
|
+
]
|
|
1758
|
+
|
|
1759
|
+
const handleCopy = (pkg) => {
|
|
1760
|
+
const cmd = `dsh plugin install ${pkg}`
|
|
1761
|
+
if (navigator.clipboard) {
|
|
1762
|
+
navigator.clipboard.writeText(cmd)
|
|
1763
|
+
}
|
|
1764
|
+
setCopiedPkg(pkg)
|
|
1765
|
+
setTimeout(() => setCopiedPkg(null), 2000)
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1768
|
+
return h('div', { className: 'dsh-suite-dock' },
|
|
1769
|
+
h('div', { className: 'dsh-suite-head' },
|
|
1770
|
+
h('h3', { className: 'dsh-suite-title' },
|
|
1771
|
+
h('span', null, '🌟'),
|
|
1772
|
+
t('suiteTitle')
|
|
1773
|
+
),
|
|
1774
|
+
h('span', { className: 'dsh-suite-desc' }, t('suiteDesc'))
|
|
1775
|
+
),
|
|
1776
|
+
h('div', { className: 'dsh-suite-cards' },
|
|
1777
|
+
...suite.map((item) => {
|
|
1778
|
+
const isInstalled = item.isCurrent
|
|
1779
|
+
const isCopied = copiedPkg === item.pkg
|
|
1780
|
+
|
|
1781
|
+
return h('div', { key: item.id, className: 'dsh-suite-card' },
|
|
1782
|
+
h('div', { className: 'dsh-suite-card-top' },
|
|
1783
|
+
h('span', { className: 'dsh-suite-card-icon' }, item.icon),
|
|
1784
|
+
h('a', {
|
|
1785
|
+
href: item.repo,
|
|
1786
|
+
target: '_blank',
|
|
1787
|
+
rel: 'noopener noreferrer',
|
|
1788
|
+
className: 'dsh-suite-card-name'
|
|
1789
|
+
}, item.name)
|
|
1790
|
+
),
|
|
1791
|
+
h('div', { className: 'dsh-suite-card-desc' }, item.desc),
|
|
1792
|
+
h('div', { className: 'dsh-suite-card-bottom' },
|
|
1793
|
+
isInstalled ? (
|
|
1794
|
+
h('span', { className: 'dsh-suite-badge-active' },
|
|
1795
|
+
h('span', { className: 'dsh-suite-badge-active-dot' }),
|
|
1796
|
+
t('suiteActive')
|
|
1797
|
+
)
|
|
1798
|
+
) : (
|
|
1799
|
+
h('button', {
|
|
1800
|
+
type: 'button',
|
|
1801
|
+
className: 'dsh-suite-btn-action',
|
|
1802
|
+
onClick: () => handleCopy(item.pkg),
|
|
1803
|
+
title: `安装命令: dsh plugin install ${item.pkg}`
|
|
1804
|
+
}, isCopied ? `✓ ${t('suiteCopied')}` : `⚡ ${t('suiteCopyInstall')}`)
|
|
1805
|
+
),
|
|
1806
|
+
h('a', {
|
|
1807
|
+
href: item.repo,
|
|
1808
|
+
target: '_blank',
|
|
1809
|
+
rel: 'noopener noreferrer',
|
|
1810
|
+
className: 'dsh-suite-link'
|
|
1811
|
+
}, 'GitHub ↗')
|
|
1812
|
+
)
|
|
1813
|
+
)
|
|
1814
|
+
})
|
|
1815
|
+
)
|
|
1816
|
+
)
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1635
1819
|
/**
|
|
1636
1820
|
* 可用性判定缓存 —— 所有会话共用一份(同一个 host、同一份设置)。
|
|
1637
1821
|
*
|
|
@@ -1844,6 +2028,13 @@ window.__ModuleLoader__.load({
|
|
|
1844
2028
|
locale: NS,
|
|
1845
2029
|
inject: () => ({ t, availability })
|
|
1846
2030
|
}, HubFooter))
|
|
2031
|
+
ctx.slots.inject('settings.models.footer', () => ctx.slots.register({
|
|
2032
|
+
name: 'settings.models.footer',
|
|
2033
|
+
id: 'dsh-llm-hub-suite',
|
|
2034
|
+
order: 110,
|
|
2035
|
+
locale: NS,
|
|
2036
|
+
inject: () => ({ t })
|
|
2037
|
+
}, WebkuborSuiteDock))
|
|
1847
2038
|
}
|
|
1848
2039
|
|
|
1849
2040
|
exports.apply = apply
|
package/lib/index.js
CHANGED
|
@@ -923,10 +923,21 @@ export function apply(ctx) {
|
|
|
923
923
|
if (row === null || typeof row !== 'object') continue
|
|
924
924
|
const percent = percentage(row.current_interval_remaining_percent)
|
|
925
925
|
if (percent === undefined) continue
|
|
926
|
+
let resetsAt = undefined
|
|
927
|
+
const rawTime = row.current_interval_end_time ?? row.end_time ?? row.reset_time
|
|
928
|
+
if (typeof rawTime === 'number' && rawTime > 0) {
|
|
929
|
+
const date = new Date(rawTime > 1e11 ? rawTime : rawTime * 1000)
|
|
930
|
+
if (!isNaN(date.getTime())) {
|
|
931
|
+
resetsAt = `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`
|
|
932
|
+
}
|
|
933
|
+
} else if (typeof rawTime === 'string' && rawTime.trim() !== '') {
|
|
934
|
+
resetsAt = rawTime.trim()
|
|
935
|
+
}
|
|
926
936
|
items.push({
|
|
927
937
|
label: label(row.model_name) ?? 'plan',
|
|
928
938
|
percent,
|
|
929
|
-
weeklyPercent: percentage(row.current_weekly_remaining_percent)
|
|
939
|
+
weeklyPercent: percentage(row.current_weekly_remaining_percent),
|
|
940
|
+
resetsAt
|
|
930
941
|
})
|
|
931
942
|
}
|
|
932
943
|
return { items }
|
|
@@ -953,7 +964,17 @@ export function apply(ctx) {
|
|
|
953
964
|
if (tier === undefined) continue
|
|
954
965
|
const percent = percentage(lim.percentage)
|
|
955
966
|
if (percent === undefined) continue
|
|
956
|
-
|
|
967
|
+
let resetsAt = undefined
|
|
968
|
+
const rawTime = lim.reset_time ?? lim.next_reset_time ?? body?.data?.expire_time
|
|
969
|
+
if (typeof rawTime === 'number' && rawTime > 0) {
|
|
970
|
+
const date = new Date(rawTime > 1e11 ? rawTime : rawTime * 1000)
|
|
971
|
+
if (!isNaN(date.getTime())) {
|
|
972
|
+
resetsAt = `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`
|
|
973
|
+
}
|
|
974
|
+
} else if (typeof rawTime === 'string' && rawTime.trim() !== '') {
|
|
975
|
+
resetsAt = rawTime.trim()
|
|
976
|
+
}
|
|
977
|
+
items.push({ label: tier, percent, resetsAt })
|
|
957
978
|
}
|
|
958
979
|
return { level: label(body?.data?.level), items }
|
|
959
980
|
}
|
|
@@ -1490,11 +1511,29 @@ export function apply(ctx) {
|
|
|
1490
1511
|
return [...merged.values()].sort((left, right) => left.provider.localeCompare(right.provider))
|
|
1491
1512
|
}
|
|
1492
1513
|
|
|
1514
|
+
/**
|
|
1515
|
+
* 判定是否属于应从模型下拉彻底摘掉的严重凭据故障。
|
|
1516
|
+
*
|
|
1517
|
+
* ⚠️ 设计原则(避免死锁):
|
|
1518
|
+
* 只有确凿的凭据故障(CREDENTIAL_MISSING / AUTH_REJECTED / ACCOUNT_UNAVAILABLE)才从下拉框剔除。
|
|
1519
|
+
* 配额耗尽(QUOTA_EXHAUSTED)与现金余额为 0(BALANCE_EMPTY / PAYMENT_REQUIRED)属于临时限额/计费状态,
|
|
1520
|
+
* 绝不能从下拉框中剔除,否则会导致:
|
|
1521
|
+
* 1) 用户在界面上永久丢失模型入口;
|
|
1522
|
+
* 2) 5小时滚动配额或周配额恢复后,用户因无法选模型而无法触发运行期遥测恢复;
|
|
1523
|
+
* 3) 形成死锁。
|
|
1524
|
+
*/
|
|
1525
|
+
const isDropDeadHidden = (verdict) => {
|
|
1526
|
+
if (verdict === null || typeof verdict !== 'object') return false
|
|
1527
|
+
if (verdict.state !== 'unavailable') return false
|
|
1528
|
+
const code = verdict.code
|
|
1529
|
+
return code === 'CREDENTIAL_MISSING' || code === 'AUTH_REJECTED' || code === 'ACCOUNT_UNAVAILABLE'
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1493
1532
|
/** 当前该从下拉里摘掉的 provider 集合。 */
|
|
1494
1533
|
const hiddenProviders = () => {
|
|
1495
1534
|
const hidden = new Set()
|
|
1496
1535
|
for (const verdict of currentAvailability()) {
|
|
1497
|
-
if (verdict
|
|
1536
|
+
if (isDropDeadHidden(verdict)) hidden.add(verdict.provider)
|
|
1498
1537
|
}
|
|
1499
1538
|
return hidden
|
|
1500
1539
|
}
|
|
@@ -1530,7 +1569,7 @@ export function apply(ctx) {
|
|
|
1530
1569
|
checkedAt: lastProbeAt,
|
|
1531
1570
|
probing: probeAll !== null,
|
|
1532
1571
|
providers,
|
|
1533
|
-
hidden: providers.filter(
|
|
1572
|
+
hidden: providers.filter(isDropDeadHidden).map((verdict) => verdict.provider)
|
|
1534
1573
|
}
|
|
1535
1574
|
}
|
|
1536
1575
|
|
|
@@ -1630,8 +1669,16 @@ export function apply(ctx) {
|
|
|
1630
1669
|
const timer = setTimeout(() => {
|
|
1631
1670
|
refreshAvailability().catch(() => {})
|
|
1632
1671
|
}, 1500)
|
|
1633
|
-
|
|
1634
|
-
|
|
1672
|
+
timer.unref?.()
|
|
1673
|
+
const interval = setInterval(() => {
|
|
1674
|
+
ensureAvailabilityFresh()
|
|
1675
|
+
}, AVAILABILITY_TTL_MS)
|
|
1676
|
+
interval.unref?.()
|
|
1677
|
+
return () => {
|
|
1678
|
+
clearTimeout(timer)
|
|
1679
|
+
clearInterval(interval)
|
|
1680
|
+
}
|
|
1681
|
+
}, 'dsh-llm-hub: initial and periodic availability probe')
|
|
1635
1682
|
|
|
1636
1683
|
// 把不可用的 provider 从 llm 服务的路由表里摘掉。
|
|
1637
1684
|
//
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dsh-plugins/dsh-llm-hub",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.3",
|
|
4
4
|
"description": "DSH 模型管理层:网关可达性探测、模型目录拉取、DeepSeek + MiniMax/智谱/Moonshot/StepFun 余额与配额、本月用量统计、余额预警、Provider 健康看板,再把本机装好的 codex / claude / agy 注册成子代理(装了才出现)。零运行时依赖,不改动 DSH 安装。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|