@dsh-plugins/dsh-llm-hub 1.6.0 → 1.6.1

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 CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  本项目遵循 [Semantic Versioning](https://semver.org/lang/zh-CN/)。
4
4
 
5
+ ## [1.6.1] - 2026-09-28
6
+
7
+ ### 🧹 去掉模型页底部的「热门大模型一键装配」,改为添加时按厂商匹配
8
+
9
+ - 删掉页脚常驻的预设抽屉:它和用户正在配哪一家无关,读起来像一块广告
10
+ - 改为在「添加提供方」里**选中某一家时**,草稿卡上只给匹配到的那一家一行提示:名称 · 推荐模型 · 获取 Key ↗;没匹配到的厂商不显示任何东西
11
+ - 不再提供 YAML 模板:草稿卡本身就是配置表单,而模板根键(`minimax`)与目录 id(`minimax-cn`)对不上,复制过去反而误导
12
+ - 修复:添加提供方时,只是选了下拉框就出现红字「llm-pi-ai 段里没有 "xxx" 这个 provider」。草稿态不再渲染原卡内容
13
+
5
14
  ## [1.6.0] - 2026-09-28
6
15
 
7
16
  ### ✨ 智能路由:组可排序 + 折叠摘要
package/lib/client.js CHANGED
@@ -213,10 +213,6 @@ window.__ModuleLoader__.load({
213
213
  heroFastest: '最优延迟',
214
214
  heroSecurity: '凭据防护',
215
215
  heroSecSafe: '零明文脱敏中',
216
- presetsTitle: '⚡ 热门大模型一键装配',
217
- presetsSub: '点击主流服务商快速复制 YAML 配置模板与直达控制台',
218
- presetsCopied: '已复制配置片段',
219
- presetsCopyBtn: '复制 YAML',
220
216
  presetsDocsBtn: '获取 Key ↗',
221
217
  usageTitle: '本月用量',
222
218
  usageCalls: '次调用',
@@ -367,10 +363,6 @@ window.__ModuleLoader__.load({
367
363
  heroFastest: 'Fastest ping',
368
364
  heroSecurity: 'Key security',
369
365
  heroSecSafe: 'Masked in memory',
370
- presetsTitle: '⚡ Popular LLM Quick Presets',
371
- presetsSub: 'One-click YAML config snippets and official key links',
372
- presetsCopied: 'YAML snippet copied',
373
- presetsCopyBtn: 'Copy YAML',
374
366
  presetsDocsBtn: 'Get Key ↗',
375
367
  usageTitle: 'This month',
376
368
  usageCalls: 'calls',
@@ -685,17 +677,12 @@ window.__ModuleLoader__.load({
685
677
  '.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)}',
686
678
  '.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}',
687
679
  '.dsh-llm-hub-hero__label{font-size:10.5px;color:var(--dsw-alias-label-secondary);opacity:.85}',
688
- // 热门大模型一键装配 Hub
689
- '.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}',
690
- '.dsh-llm-hub-presets__head{display:flex;align-items:center;justify-content:space-between;gap:8px;cursor:pointer;user-select:none}',
691
- '.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}',
692
- '.dsh-llm-hub-presets__grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:6px;margin-top:6px}',
693
- '.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}',
694
- '.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))}',
695
- '.dsh-llm-hub-presets__name{font-weight:600;font-size:11.5px;color:var(--dsw-alias-label-primary)}',
696
- '.dsh-llm-hub-presets__desc{font-size:10px;color:var(--dsw-alias-label-secondary);opacity:.75;line-height:1.3}',
697
- '.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}',
698
- '.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}',
680
+ // 添加提供方草稿卡上的匹配提示(1.6.0)
681
+ '.dsh-llm-hub-preset-hint{display:flex;flex-wrap:wrap;align-items:baseline;gap:4px 8px;margin:0 0 8px;padding:6px 10px;border-radius:6px;',
682
+ 'border:.5px solid var(--dsw-alias-border-l1);font-size:11px;line-height:1.5}',
683
+ '.dsh-llm-hub-preset-hint__name{font-weight:600;color:var(--dsw-alias-label-primary)}',
684
+ '.dsh-llm-hub-preset-hint__desc{color:var(--dsw-alias-label-secondary);flex:1 1 auto;min-width:0}',
685
+ '.dsh-llm-hub-preset-hint__link{color:var(--dsw-alias-brand-primary,#3b82f6);text-decoration:none;white-space:nowrap}',
699
686
  // 延迟高亮胶囊
700
687
  '.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}',
701
688
  '.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)}',
@@ -1543,45 +1530,45 @@ window.__ModuleLoader__.load({
1543
1530
  const PRESETS = [
1544
1531
  {
1545
1532
  id: 'deepseek',
1533
+ match: ['deepseek', 'llm-deepseek'],
1546
1534
  name: '🇨🇳 DeepSeek 官方',
1547
1535
  desc: 'V4.1 Flash / V4 Pro 极速官方直连',
1548
- docUrl: 'https://platform.deepseek.com/api_keys',
1549
- 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'
1536
+ docUrl: 'https://platform.deepseek.com/api_keys'
1550
1537
  },
1551
1538
  {
1552
1539
  id: 'minimax',
1540
+ match: ['minimax', 'minimax-cn'],
1553
1541
  name: '⚡ MiniMax',
1554
1542
  desc: 'MiniMax-M3 百万上下文超强大模型',
1555
- docUrl: 'https://platform.minimaxi.com/user-center/basic-information/interface-key',
1556
- 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'
1543
+ docUrl: 'https://platform.minimaxi.com/user-center/basic-information/interface-key'
1557
1544
  },
1558
1545
  {
1559
1546
  id: 'zhipu',
1547
+ match: ['zai-coding-cn', 'zai', 'zhipu'],
1560
1548
  name: '🧠 智谱清言 GLM',
1561
1549
  desc: 'GLM-5 / GLM-4.7 国内 Coding 利器',
1562
- docUrl: 'https://open.bigmodel.cn/usercenter/apikeys',
1563
- 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'
1550
+ docUrl: 'https://open.bigmodel.cn/usercenter/apikeys'
1564
1551
  },
1565
1552
  {
1566
1553
  id: 'volc',
1554
+ match: ['volcengine', 'volc', 'ark'],
1567
1555
  name: '🌋 火山方舟 Ark',
1568
1556
  desc: '字节豆包 Doubao-1.5 多模态矩阵',
1569
- docUrl: 'https://console.volcengine.com/ark/region:ark+cn-beijing/apiKey',
1570
- 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'
1557
+ docUrl: 'https://console.volcengine.com/ark/region:ark+cn-beijing/apiKey'
1571
1558
  },
1572
1559
  {
1573
1560
  id: 'siliconflow',
1561
+ match: ['siliconflow'],
1574
1562
  name: '🚀 硅基流动',
1575
1563
  desc: 'SiliconFlow 聚合开源高性价比模型',
1576
- docUrl: 'https://cloud.siliconflow.cn/account/ak',
1577
- 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'
1564
+ docUrl: 'https://cloud.siliconflow.cn/account/ak'
1578
1565
  },
1579
1566
  {
1580
1567
  id: 'openrouter',
1568
+ match: ['openrouter'],
1581
1569
  name: '🌐 OpenRouter',
1582
1570
  desc: '聚合全球 Claude / GPT / Gemini 全模型',
1583
- docUrl: 'https://openrouter.ai/keys',
1584
- 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'
1571
+ docUrl: 'https://openrouter.ai/keys'
1585
1572
  }
1586
1573
  ]
1587
1574
 
@@ -1621,58 +1608,52 @@ window.__ModuleLoader__.load({
1621
1608
  }
1622
1609
 
1623
1610
  /**
1624
- * 热门大模型一键装配抽屉
1611
+ * 添加提供方时的匹配提示(1.6.0 起取代页脚的「热门大模型一键装配」抽屉)。
1612
+ *
1613
+ * 为什么改:抽屉常驻在模型页底部,和用户正在配哪一家毫无关系,读起来像一块广告。
1614
+ * 该出现的时机是「用户在添加提供方里选中了某一家」—— 宿主此时渲染草稿卡,并以
1615
+ * `configured: false` 派发 settings.models.provider-card(见宿主 slot-contract.d.ts)。
1616
+ * 于是只在这张卡上、只给匹配到的那一家一行:名称 · 推荐模型 · 获取 Key。
1617
+ * 不再给 YAML:草稿卡本身就是配置表单,而模板根键(minimax)与目录 id(minimax-cn)
1618
+ * 对不上,复制过去反而误导。没匹配到的厂商不渲染任何东西。
1619
+ *
1620
+ * @param {unknown} provider - 宿主传入的 entry 对象或 provider id 字符串。
1621
+ * @returns {object | undefined} 命中的预设。
1625
1622
  */
1626
- function QuickPresetsCard(props) {
1627
- const t = typeof props.t === 'function' ? props.t : fallbackT
1628
- const [expanded, setExpanded] = React.useState(false)
1629
- const [selected, setSelected] = React.useState(null)
1630
- const [copied, setCopied] = React.useState(false)
1623
+ const presetFor = (provider) => {
1624
+ const id = typeof provider === 'string' ? provider : (provider && typeof provider.provider === 'string' ? provider.provider : '')
1625
+ return id === '' ? undefined : PRESETS.find((preset) => preset.match.includes(id))
1626
+ }
1631
1627
 
1628
+ function PresetHint(props) {
1629
+ const t = typeof props.t === 'function' ? props.t : fallbackT
1630
+ const preset = presetFor(props.provider)
1631
+ if (preset === undefined) return null
1632
1632
  ensureStyle()
1633
+ return h('div', { className: 'dsh-llm-hub-preset-hint' },
1634
+ h('span', { className: 'dsh-llm-hub-preset-hint__name' }, preset.name),
1635
+ h('span', { className: 'dsh-llm-hub-preset-hint__desc' }, preset.desc),
1636
+ h('a', {
1637
+ href: preset.docUrl,
1638
+ target: '_blank',
1639
+ rel: 'noreferrer',
1640
+ className: 'dsh-llm-hub-preset-hint__link'
1641
+ }, t('presetsDocsBtn')))
1642
+ }
1633
1643
 
1634
- const copyYaml = (e, yaml) => {
1635
- e.stopPropagation()
1636
- navigator.clipboard?.writeText(yaml).then(() => {
1637
- setCopied(true)
1638
- setTimeout(() => setCopied(false), 2000)
1639
- }).catch(() => {})
1640
- }
1641
-
1642
- return h('div', { className: 'dsh-llm-hub-presets' },
1643
- h('div', {
1644
- className: 'dsh-llm-hub-presets__head',
1645
- onClick: () => setExpanded((prev) => !prev)
1646
- },
1647
- h('span', { className: 'dsh-llm-hub-presets__title' },
1648
- t('presetsTitle'),
1649
- h('span', { style: { fontSize: '10px', opacity: 0.6 } }, expanded ? ' ▲ 收起' : ' ▼ 展开模版')),
1650
- h('span', { className: 'dsh-llm-hub-subtext' }, t('presetsSub'))),
1651
- expanded ? h('div', { className: 'dsh-llm-hub-presets__grid' },
1652
- PRESETS.map((p) => h('div', {
1653
- key: p.id,
1654
- className: 'dsh-llm-hub-presets__item',
1655
- onClick: () => setSelected(selected?.id === p.id ? null : p)
1656
- },
1657
- h('span', { className: 'dsh-llm-hub-presets__name' }, p.name),
1658
- h('span', { className: 'dsh-llm-hub-presets__desc' }, p.desc)))) : null,
1659
- expanded && selected !== null ? h('div', { className: 'dsh-llm-hub-presets__detail' },
1660
- h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' } },
1661
- h('span', { style: { fontWeight: '600' } }, `${selected.name} 配置模版`),
1662
- h('div', { style: { display: 'flex', gap: '6px' } },
1663
- h('button', {
1664
- type: 'button',
1665
- className: 'dsh-llm-hub-btn',
1666
- onClick: (e) => copyYaml(e, selected.yaml)
1667
- }, copied ? t('presetsCopied') : t('presetsCopyBtn')),
1668
- h('a', {
1669
- href: selected.docUrl,
1670
- target: '_blank',
1671
- rel: 'noreferrer',
1672
- className: 'dsh-llm-hub-btn',
1673
- style: { textDecoration: 'none' }
1674
- }, t('presetsDocsBtn')))),
1675
- h('pre', { className: 'dsh-llm-hub-presets__code' }, selected.yaml)) : null)
1644
+ /**
1645
+ * 给 provider-card 组件套上草稿态行为。直接调用原组件(而不是 h(Card)),
1646
+ * 保证原组件的 hooks 挂在同一个组件上、调用顺序不变 —— 草稿保存后 configured
1647
+ * 翻成 true,同一个组件实例继续渲染,hook 数量不能变。
1648
+ *
1649
+ * 草稿态(configured === false)只给匹配提示、丢掉原卡输出:原卡此时去查一个
1650
+ * 还没写进配置的 provider,必然渲染红字「llm-pi-ai 段里没有 "xxx" 这个 provider」,
1651
+ * 用户只是选了一下下拉框,看到的却像报错(2026-09-28 浏览器实测)。
1652
+ */
1653
+ const withPresetHint = (Card) => (props) => {
1654
+ const card = Card(props)
1655
+ if (props.configured !== false) return card
1656
+ return PresetHint(props)
1676
1657
  }
1677
1658
 
1678
1659
  /**
@@ -2631,13 +2612,13 @@ window.__ModuleLoader__.load({
2631
2612
  key: CARD_KEY,
2632
2613
  locale: NS,
2633
2614
  inject: () => ({ t, availability })
2634
- }, BalanceCard))
2615
+ }, withPresetHint(BalanceCard)))
2635
2616
  ctx.slots.inject('settings.models.provider-card', () => ctx.slots.register({
2636
2617
  name: 'settings.models.provider-card',
2637
2618
  key: PIAI_CARD_KEY,
2638
2619
  locale: NS,
2639
2620
  inject: () => ({ t, availability })
2640
- }, PiAiCard))
2621
+ }, withPresetHint(PiAiCard)))
2641
2622
  // settings.models.footer 的契约是 kind:'list'(见宿主 dsh-client-ui-settings-models
2642
2623
  // 的 children 声明),list 类 slot 注册**必须带 id** —— 参照宿主自己的
2643
2624
  // settings.onboarding 注册(id:'welcome-notice', order:-100)。
@@ -2651,13 +2632,6 @@ window.__ModuleLoader__.load({
2651
2632
  locale: NS,
2652
2633
  inject: () => ({ t, availability })
2653
2634
  }, HeroOverviewCard))
2654
- ctx.slots.inject('settings.models.footer', () => ctx.slots.register({
2655
- name: 'settings.models.footer',
2656
- id: 'dsh-llm-hub-presets',
2657
- order: 65,
2658
- locale: NS,
2659
- inject: () => ({ t })
2660
- }, QuickPresetsCard))
2661
2635
  ctx.slots.inject('settings.models.footer', () => ctx.slots.register({
2662
2636
  name: 'settings.models.footer',
2663
2637
  id: 'dsh-llm-hub-health',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dsh-plugins/dsh-llm-hub",
3
- "version": "1.6.0",
3
+ "version": "1.6.1",
4
4
  "description": "DSH 模型管理层:网关可达性探测、模型目录拉取、DeepSeek + MiniMax/智谱/Moonshot/StepFun 余额与配额、本月用量统计、余额预警、Provider 健康看板,再把本机装好的 codex / claude / agy 注册成子代理(装了才出现)。零运行时依赖,不改动 DSH 安装。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",