dsh-router-core 0.4.8 → 0.4.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/docs/suppliers.md +1 -1
- package/lib/client-registry.js +88 -26
- package/lib/client.js +88 -26
- package/lib/types/router/types.d.ts +11 -0
- package/lib/types/shared.d.ts +2 -0
- package/package.json +1 -1
- package/src/client/CombosTab.tsx +29 -8
- package/src/client/SupplierDetail.tsx +11 -5
- package/src/client/model-label.test.ts +33 -0
- package/src/client/model-label.ts +48 -0
- package/src/client/router.css +21 -58
- package/src/router/models-cache.test.ts +16 -0
- package/src/router/types.ts +11 -0
- package/src/shared.ts +2 -0
package/lib/types/shared.d.ts
CHANGED
|
@@ -218,6 +218,8 @@ export interface RouterExtResponse {
|
|
|
218
218
|
/** 供应商模型(含启用状态)。 */
|
|
219
219
|
export interface RouterSupplierModel {
|
|
220
220
|
id: string;
|
|
221
|
+
/** 可读名(上游写的,只用于显示;缺省 = id)。见 `ModelInfo.name`。 */
|
|
222
|
+
name?: string;
|
|
221
223
|
context_length?: number;
|
|
222
224
|
enabled: boolean;
|
|
223
225
|
/** 是否为自定义(手动添加 / 拉取)模型,可删除。 */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-router-core",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.9",
|
|
4
4
|
"description": "DSH plugin: a simplified 9router (AI routing gateway). Adds a 路由系统 (Routing System) sidebar entry beside 记忆系统; the panel shows suppliers, account pool, combos, and endpoint/API keys.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
package/src/client/CombosTab.tsx
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
type RouterComboAlias,
|
|
11
11
|
type RouterComboSupplierGroup,
|
|
12
12
|
} from '../shared.ts'
|
|
13
|
+
import { modelLabel, modelMatches } from './model-label.ts'
|
|
13
14
|
import { Modal } from './Modal.tsx'
|
|
14
15
|
|
|
15
16
|
/* ---------------- SVG 图标 ---------------- */
|
|
@@ -67,6 +68,22 @@ export function CombosTab({ combos, aliases, onRefresh }: CombosTabProps): JSX.E
|
|
|
67
68
|
?? groups.find(g => g.models.some(m => m.id === stored))
|
|
68
69
|
return hit === undefined ? stored : `${hit.supplier.alias}/${stored}`
|
|
69
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* 存储值 → 悬停说明。组合里存的、请求要发的都是 `alias/modelId`(上面那行),
|
|
73
|
+
* 但上游 key 往往不是人能读的(`qmodel_38max`),所以把上游的可读名
|
|
74
|
+
* (`Qwen3.8-Max`)放进 title 里,不占行宽也不改变「发什么」。
|
|
75
|
+
*/
|
|
76
|
+
const comboTip = (stored: string): string => {
|
|
77
|
+
const name = displayName(stored)
|
|
78
|
+
const comma = stored.indexOf(',')
|
|
79
|
+
const mid = comma > 0 ? stored.slice(comma + 1) : stored
|
|
80
|
+
const sid = comma > 0 ? stored.slice(0, comma) : ''
|
|
81
|
+
const hit = (sid === '' ? groups : groups.filter(g => g.supplier.id === sid))
|
|
82
|
+
.flatMap(g => g.models)
|
|
83
|
+
.find(m => m.id === mid)
|
|
84
|
+
const readable = (hit?.name ?? '').trim()
|
|
85
|
+
return readable === '' || readable === mid ? name : `${readable}\n${name}(请求里用这个)`
|
|
86
|
+
}
|
|
70
87
|
const [showForm, setShowForm] = useState(false)
|
|
71
88
|
const [editing, setEditing] = useState<RouterCombo | null>(null)
|
|
72
89
|
const [form, setForm] = useState<ComboForm>({ name: '', models: [] })
|
|
@@ -289,7 +306,7 @@ export function CombosTab({ combos, aliases, onRefresh }: CombosTabProps): JSX.E
|
|
|
289
306
|
: (
|
|
290
307
|
<>
|
|
291
308
|
{combo.models.slice(0, 4).map((model, i) => (
|
|
292
|
-
<span key={`${model}-${i}`} className="dshr-comboChip">
|
|
309
|
+
<span key={`${model}-${i}`} className="dshr-comboChip" title={comboTip(model)}>
|
|
293
310
|
<span className="dshr-comboChipNum">{i + 1}</span>
|
|
294
311
|
<span className="dshr-comboChipSupplier">{displayName(model)}</span>
|
|
295
312
|
</span>
|
|
@@ -349,7 +366,7 @@ export function CombosTab({ combos, aliases, onRefresh }: CombosTabProps): JSX.E
|
|
|
349
366
|
>
|
|
350
367
|
<span className="dshr-comboGrip" title="拖拽排序"><Icon d={I.grip} size={14} /></span>
|
|
351
368
|
<span className="dshr-comboStepNum">{index + 1}</span>
|
|
352
|
-
<code className="dshr-comboSelectedModel">{displayName(model)}</code>
|
|
369
|
+
<code className="dshr-comboSelectedModel" title={comboTip(model)}>{displayName(model)}</code>
|
|
353
370
|
<button type="button" className="dshr-iconBtn dshr-iconBtn-sm dshr-comboStepRemove" title="移除" onClick={() => removeModel(index)}>
|
|
354
371
|
<Icon d={I.delete} size={14} />
|
|
355
372
|
</button>
|
|
@@ -379,16 +396,20 @@ export function CombosTab({ combos, aliases, onRefresh }: CombosTabProps): JSX.E
|
|
|
379
396
|
: groups.map(g => {
|
|
380
397
|
const items = g.models.filter(m =>
|
|
381
398
|
!form.models.includes(`${g.supplier.id},${m.id}`) &&
|
|
382
|
-
(modelQuery === '' ||
|
|
399
|
+
(modelQuery === '' || modelMatches(modelQuery, m)))
|
|
383
400
|
if (items.length === 0) return null
|
|
384
401
|
return (
|
|
385
402
|
<div key={g.supplier.id} className="dshr-comboPickGroup">
|
|
386
403
|
<p className="dshr-comboPickGroupTitle">{g.supplier.name} · {g.supplier.alias}</p>
|
|
387
|
-
{items.map(m =>
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
404
|
+
{items.map(m => {
|
|
405
|
+
const lab = modelLabel(m)
|
|
406
|
+
return (
|
|
407
|
+
<button key={m.id} type="button" className="dshr-comboPickItem" title={lab.title} onClick={() => toggleModel(g.supplier.id, m.id)}>
|
|
408
|
+
<span className="dshr-comboPickName">{lab.label}</span>
|
|
409
|
+
{lab.showId && <span className="dshr-comboPickKey">{m.id}</span>}
|
|
410
|
+
</button>
|
|
411
|
+
)
|
|
412
|
+
})}
|
|
392
413
|
</div>
|
|
393
414
|
)
|
|
394
415
|
})}
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
type RouterSupplierModelsResponse,
|
|
14
14
|
} from '../shared.ts'
|
|
15
15
|
import { statusBadge } from './account-badge.ts'
|
|
16
|
+
import { modelLabel, modelMatches } from './model-label.ts'
|
|
16
17
|
import { Modal } from './Modal.tsx'
|
|
17
18
|
|
|
18
19
|
/* ---------------- SVG 图标 ---------------- */
|
|
@@ -550,7 +551,7 @@ export function SupplierDetail({ supplier, accounts, statusLoading, onBack, onRe
|
|
|
550
551
|
return out
|
|
551
552
|
})()
|
|
552
553
|
const q = modelQuery.trim().toLowerCase()
|
|
553
|
-
const matchQuery = (m: RouterSupplierModel): boolean => q
|
|
554
|
+
const matchQuery = (m: RouterSupplierModel): boolean => modelMatches(q, m)
|
|
554
555
|
const enabledModels = models?.filter(m => m.enabled && matchQuery(m)) ?? []
|
|
555
556
|
const disabledModels = models?.filter(m => !m.enabled && matchQuery(m)) ?? []
|
|
556
557
|
|
|
@@ -763,19 +764,24 @@ export function SupplierDetail({ supplier, accounts, statusLoading, onBack, onRe
|
|
|
763
764
|
{enabledModels.map(model => {
|
|
764
765
|
const status = testResults[model.id]
|
|
765
766
|
const chipCls = status === 'ok' ? ' dshr-modelChip-ok' : status === 'error' ? ' dshr-modelChip-err' : ''
|
|
767
|
+
const lab = modelLabel(model)
|
|
766
768
|
return (
|
|
767
769
|
<div key={model.id} className={`dshr-modelChipWrap${chipCls}`}>
|
|
768
770
|
<button
|
|
769
771
|
type="button"
|
|
770
772
|
className="dshr-modelChip"
|
|
771
|
-
title=
|
|
773
|
+
title={`${lab.title}\n点击禁用`}
|
|
772
774
|
disabled={toggling}
|
|
773
775
|
onClick={() => void toggleModel(model.id, false)}
|
|
774
776
|
>
|
|
775
777
|
<span className="dshr-modelChipStatus">
|
|
776
778
|
<Icon d={status === 'ok' ? I.checkCircle : status === 'error' ? I.cancel : I.robot} size={13} />
|
|
777
779
|
</span>
|
|
778
|
-
<span className="dshr-
|
|
780
|
+
<span className="dshr-modelChipName">{lab.label}</span>
|
|
781
|
+
{lab.showId && <span className="dshr-modelChipKey">{model.id}</span>}
|
|
782
|
+
{typeof model.context_length === 'number' && model.context_length > 0 && (
|
|
783
|
+
<span className="dshr-modelChipCtx">{model.context_length}K</span>
|
|
784
|
+
)}
|
|
779
785
|
</button>
|
|
780
786
|
<div className="dshr-modelChipOps">
|
|
781
787
|
<button
|
|
@@ -819,11 +825,11 @@ export function SupplierDetail({ supplier, accounts, statusLoading, onBack, onRe
|
|
|
819
825
|
key={model.id}
|
|
820
826
|
type="button"
|
|
821
827
|
className="dshr-modelChip dshr-modelChip-off"
|
|
822
|
-
title=
|
|
828
|
+
title={`${modelLabel(model).title}\n点击恢复`}
|
|
823
829
|
disabled={toggling}
|
|
824
830
|
onClick={() => void toggleModel(model.id, true)}
|
|
825
831
|
>
|
|
826
|
-
<span className="dshr-
|
|
832
|
+
<span className="dshr-modelChipName">{modelLabel(model).label}</span>
|
|
827
833
|
<span className="dshr-modelChipRestore">+ 恢复</span>
|
|
828
834
|
</button>
|
|
829
835
|
))}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 模型标签/搜索判据测试。
|
|
3
|
+
*
|
|
4
|
+
* 为什么要有:这两条判据错了都不会报错,只会让人在面板上做错事 ——
|
|
5
|
+
* 标签退化成 key(用户还是看不懂),或搜索只认 key(记不清 key 的人搜不到)。
|
|
6
|
+
*/
|
|
7
|
+
import test from 'node:test'
|
|
8
|
+
import assert from 'node:assert/strict'
|
|
9
|
+
import { modelLabel, modelMatches } from './model-label.ts'
|
|
10
|
+
|
|
11
|
+
test('有可读名:名字在前,key 作为副标签', () => {
|
|
12
|
+
const l = modelLabel({ id: 'qmodel_38max', name: 'Qwen3.8-Max' })
|
|
13
|
+
assert.equal(l.label, 'Qwen3.8-Max')
|
|
14
|
+
assert.equal(l.showId, true)
|
|
15
|
+
assert.match(l.title, /qmodel_38max/)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test('没有可读名 / 名字与 key 相同:只显示 key,不重复', () => {
|
|
19
|
+
assert.deepEqual(
|
|
20
|
+
{ ...modelLabel({ id: 'gpt-4o' }), title: undefined },
|
|
21
|
+
{ label: 'gpt-4o', showId: false, title: undefined },
|
|
22
|
+
)
|
|
23
|
+
assert.equal(modelLabel({ id: 'gpt-4o', name: 'gpt-4o' }).showId, false)
|
|
24
|
+
assert.equal(modelLabel({ id: 'gpt-4o', name: ' ' }).label, 'gpt-4o')
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
test('搜索:可读名与 key 都能命中', () => {
|
|
28
|
+
const m = { id: 'kmodel_latest', name: 'Kimi-K3.1-Pro' }
|
|
29
|
+
assert.equal(modelMatches('kimi', m), true)
|
|
30
|
+
assert.equal(modelMatches('KMODEL', m), true)
|
|
31
|
+
assert.equal(modelMatches(' ', m), true)
|
|
32
|
+
assert.equal(modelMatches('glm', m), false)
|
|
33
|
+
})
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 模型 chip 的标签 —— 「可读名 + 上游 key」两段。
|
|
3
|
+
*
|
|
4
|
+
* 为什么需要:有些上游的模型 key 是内部标识(Qoder 的 `qmodel_38max`、
|
|
5
|
+
* `kmodel_latest`、`mmodel`),面板上只剩一串猜不出含义的字母;而上游目录里
|
|
6
|
+
* 其实同时下发了可读名(`Qwen3.8-Max`、`Kimi-K3.1-Pro`)。丢掉它,用户点
|
|
7
|
+
* 「获取模型」之后仍然不知道自己勾的是什么。
|
|
8
|
+
*
|
|
9
|
+
* 但两段不能互相替代:**能发给上游的只有 key**。所以显示上可读名在前、key 在后
|
|
10
|
+
* (两者相同时只显示一次),复制按钮复制 key,请求/组合存的也是 key。
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** 一个模型在面板上的呈现。 */
|
|
14
|
+
export interface ModelLabel {
|
|
15
|
+
/** 主标签:上游给的可读名,没有(或与 key 相同)就用 key。 */
|
|
16
|
+
label: string
|
|
17
|
+
/** 是否还要把 key 作为副标签显示 —— 与主标签相同就不重复。 */
|
|
18
|
+
showId: boolean
|
|
19
|
+
/** 悬停提示:两个都给出来,并点明请求要用哪个。 */
|
|
20
|
+
title: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 算出模型的展示标签。
|
|
25
|
+
* @param m 至少要有 `id`;`name` 是可读名(可缺、可空串)
|
|
26
|
+
*/
|
|
27
|
+
export function modelLabel(m: { id: string; name?: string }): ModelLabel {
|
|
28
|
+
const name = (m.name ?? '').trim()
|
|
29
|
+
const label = name === '' || name === m.id ? m.id : name
|
|
30
|
+
const showId = label !== m.id
|
|
31
|
+
return {
|
|
32
|
+
label,
|
|
33
|
+
showId,
|
|
34
|
+
title: showId ? `${label}\n${m.id}(请求与组合里用这个)` : m.id,
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 搜索匹配:可读名与 key **都**参与。
|
|
40
|
+
*
|
|
41
|
+
* 两种说法都要能搜到:记得模型叫什么的人搜 `kimi`,记得上游 key 的人搜
|
|
42
|
+
* `kmodel`;只匹配一个等于把另一半人的搜索框作废。
|
|
43
|
+
*/
|
|
44
|
+
export function modelMatches(query: string, m: { id: string; name?: string }): boolean {
|
|
45
|
+
const q = query.trim().toLowerCase()
|
|
46
|
+
if (q === '') return true
|
|
47
|
+
return m.id.toLowerCase().includes(q) || (m.name ?? '').toLowerCase().includes(q)
|
|
48
|
+
}
|
package/src/client/router.css
CHANGED
|
@@ -1900,9 +1900,16 @@ html[data-dsh-router-active]:not([data-dsh-taskboard-active]):not(
|
|
|
1900
1900
|
cursor: default;
|
|
1901
1901
|
}
|
|
1902
1902
|
|
|
1903
|
-
|
|
1903
|
+
/* 主标签 = 上游的可读名(缺省回落成 key),正文字号;key 用等宽淡字跟在后面,
|
|
1904
|
+
两者相同时不渲染 key(判据见 model-label.ts) */
|
|
1905
|
+
.dshr-modelChipName {
|
|
1906
|
+
font-size: 12px;
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
.dshr-modelChipKey {
|
|
1904
1910
|
font-family: var(--rs-mono);
|
|
1905
|
-
font-size:
|
|
1911
|
+
font-size: 10px;
|
|
1912
|
+
color: var(--rs-faint);
|
|
1906
1913
|
}
|
|
1907
1914
|
|
|
1908
1915
|
.dshr-modelChipCtx {
|
|
@@ -2047,61 +2054,6 @@ html[data-dsh-router-active]:not([data-dsh-taskboard-active]):not(
|
|
|
2047
2054
|
opacity: 1;
|
|
2048
2055
|
}
|
|
2049
2056
|
|
|
2050
|
-
/* 旧 .dshr-modelChip 独立样式(disabled 区仍用) */
|
|
2051
|
-
.dshr-modelChip {
|
|
2052
|
-
display: inline-flex;
|
|
2053
|
-
align-items: center;
|
|
2054
|
-
gap: 6px;
|
|
2055
|
-
padding: 6px 10px;
|
|
2056
|
-
border: 1px solid var(--rs-line);
|
|
2057
|
-
border-radius: 8px;
|
|
2058
|
-
color: var(--rs-text);
|
|
2059
|
-
background: var(--rs-layer-1);
|
|
2060
|
-
font-size: 12px;
|
|
2061
|
-
cursor: pointer;
|
|
2062
|
-
transition:
|
|
2063
|
-
border-color 0.12s ease,
|
|
2064
|
-
background 0.12s ease,
|
|
2065
|
-
color 0.12s ease;
|
|
2066
|
-
}
|
|
2067
|
-
|
|
2068
|
-
.dshr-modelChip:hover {
|
|
2069
|
-
border-color: var(--rs-accent);
|
|
2070
|
-
background: color-mix(in srgb, var(--rs-accent) 6%, transparent);
|
|
2071
|
-
}
|
|
2072
|
-
|
|
2073
|
-
.dshr-modelChip:disabled {
|
|
2074
|
-
opacity: 0.6;
|
|
2075
|
-
cursor: default;
|
|
2076
|
-
}
|
|
2077
|
-
|
|
2078
|
-
.dshr-modelChipId {
|
|
2079
|
-
font-family: var(--rs-mono);
|
|
2080
|
-
font-size: 11px;
|
|
2081
|
-
}
|
|
2082
|
-
|
|
2083
|
-
.dshr-modelChipCtx {
|
|
2084
|
-
flex: none;
|
|
2085
|
-
color: var(--rs-faint);
|
|
2086
|
-
font-size: 10px;
|
|
2087
|
-
}
|
|
2088
|
-
|
|
2089
|
-
.dshr-modelChip-off {
|
|
2090
|
-
border-style: dashed;
|
|
2091
|
-
color: var(--rs-faint);
|
|
2092
|
-
opacity: 0.8;
|
|
2093
|
-
}
|
|
2094
|
-
|
|
2095
|
-
.dshr-modelChip-off:hover {
|
|
2096
|
-
color: var(--rs-accent);
|
|
2097
|
-
border-color: var(--rs-accent);
|
|
2098
|
-
}
|
|
2099
|
-
|
|
2100
|
-
.dshr-modelChipRestore {
|
|
2101
|
-
color: var(--rs-accent);
|
|
2102
|
-
font-size: 10px;
|
|
2103
|
-
}
|
|
2104
|
-
|
|
2105
2057
|
/* 计数徽标跟在标题/操作按钮右侧(模型卡片) */
|
|
2106
2058
|
.dshr-cardMetaAfter {
|
|
2107
2059
|
margin-left: 0;
|
|
@@ -2484,9 +2436,20 @@ html[data-dsh-router-active]:not([data-dsh-taskboard-active]):not(
|
|
|
2484
2436
|
}
|
|
2485
2437
|
|
|
2486
2438
|
.dshr-comboPickName {
|
|
2487
|
-
flex: 1;
|
|
2439
|
+
flex: 0 1 auto;
|
|
2488
2440
|
min-width: 0;
|
|
2441
|
+
overflow: hidden;
|
|
2442
|
+
text-overflow: ellipsis;
|
|
2443
|
+
white-space: nowrap;
|
|
2444
|
+
}
|
|
2445
|
+
|
|
2446
|
+
/* 挑选项里的上游 key:可读名之后的小号等宽淡字,两者相同时不渲染 */
|
|
2447
|
+
.dshr-comboPickKey {
|
|
2448
|
+
flex: none;
|
|
2449
|
+
margin-left: auto;
|
|
2489
2450
|
font-family: var(--rs-mono);
|
|
2451
|
+
font-size: 10px;
|
|
2452
|
+
color: var(--rs-faint);
|
|
2490
2453
|
overflow: hidden;
|
|
2491
2454
|
text-overflow: ellipsis;
|
|
2492
2455
|
white-space: nowrap;
|
|
@@ -752,3 +752,19 @@ test('正常供应商(listModels 全量返回):列表内容与启用状态
|
|
|
752
752
|
assert.deepEqual(models.map((m) => m.id).sort(), ['a-1', 'a-2'])
|
|
753
753
|
assert.deepEqual(models.map((m) => m.enabled).sort(), [false, true], '启用状态仍由核心合并')
|
|
754
754
|
})
|
|
755
|
+
|
|
756
|
+
test('可读名原样透传到面板,且 id 不被它替换', async () => {
|
|
757
|
+
const router = new Router('')
|
|
758
|
+
// Qoder 的形状:id 是上游内部 key,可读名在 name 里
|
|
759
|
+
const a = supplier('a', [{ id: 'qmodel_38max', name: 'Qwen3.8-Max', context_length: 200 }])
|
|
760
|
+
addSupplier(router, a.s)
|
|
761
|
+
|
|
762
|
+
const models = await router.modelsOf('a')
|
|
763
|
+
assert.deepEqual(models, [{
|
|
764
|
+
id: 'qmodel_38max',
|
|
765
|
+
name: 'Qwen3.8-Max',
|
|
766
|
+
context_length: 200,
|
|
767
|
+
enabled: true,
|
|
768
|
+
custom: undefined,
|
|
769
|
+
}], '两个字段各自都必须在:拿 name 去请求上游会 404,只剩 id 则面板上看不懂')
|
|
770
|
+
})
|
package/src/router/types.ts
CHANGED
|
@@ -35,7 +35,18 @@ export interface SupplierStatus {
|
|
|
35
35
|
|
|
36
36
|
/** OpenAI 模型条目。 */
|
|
37
37
|
export interface ModelInfo {
|
|
38
|
+
/** 上游的模型 key(**唯一标识**:组合存它、路由认它、上游收它)。 */
|
|
38
39
|
id: string
|
|
40
|
+
/**
|
|
41
|
+
* 可读名(上游自己写的,如 `Qwen3.8-Max`)。**只用于显示**,缺省 = `id`。
|
|
42
|
+
*
|
|
43
|
+
* 为什么需要它:有些上游的 key 是内部标识(Qoder 的 `qmodel_38max`、
|
|
44
|
+
* `kmodel_latest`),面板上只剩一串猜不出含义的字母。既然上游在目录里
|
|
45
|
+
* 同时下发了 `display_name`,丢掉它就是把可读信息换成噪声。
|
|
46
|
+
* 反过来,**凡是"发给上游/存下来"的地方都得继续用 `id`**——用名字去请求
|
|
47
|
+
* 上游只会拿到 404。
|
|
48
|
+
*/
|
|
49
|
+
name?: string
|
|
39
50
|
context_length?: number
|
|
40
51
|
}
|
|
41
52
|
|
package/src/shared.ts
CHANGED
|
@@ -227,6 +227,8 @@ export interface RouterExtResponse {
|
|
|
227
227
|
/** 供应商模型(含启用状态)。 */
|
|
228
228
|
export interface RouterSupplierModel {
|
|
229
229
|
id: string
|
|
230
|
+
/** 可读名(上游写的,只用于显示;缺省 = id)。见 `ModelInfo.name`。 */
|
|
231
|
+
name?: string
|
|
230
232
|
context_length?: number
|
|
231
233
|
enabled: boolean
|
|
232
234
|
/** 是否为自定义(手动添加 / 拉取)模型,可删除。 */
|