dsh-link-pulse 0.1.6 → 0.1.8
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 +13 -0
- package/package.json +1 -1
- package/src/client.js +90 -43
- package/src/index.js +57 -27
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.1.8] - 2026-08-30
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- **官方 DeepSeek V4 供应商误显为 CPA**:DSH 内置官方路由的运行时 provider id 为 `deepseek-official`(`dsh-llm-deepseek` 中 `PROVIDER` 常量),而插件兜底官方节点注册的 id 是 `deepseek`,导致客户端按当前会话 provider 查找落空后错误兜底到 provider 列表第一项(CPA)。兜底节点 id 修正为 `deepseek-official`,并同步注册 `KNOWN_PROVIDER_DEFAULTS` 映射。
|
|
7
|
+
- **未识别 Provider 张冠李戴**:客户端在 providerMap 查找失败时不再冒名显示列表第一个供应商的名称与延迟,改为展示该 provider 的真实 id(大写),延迟显示为探测中。
|
|
8
|
+
|
|
9
|
+
## [0.1.7] - 2026-08-29
|
|
10
|
+
|
|
11
|
+
### Fixed & Refactored
|
|
12
|
+
- **权威状态订阅重构**:彻底移除客户端定时器轮询与 DOM 抓取,接入 DSH 原生 `sessions` 与 `modelDirectories` 服务,实现模型切换时 0 毫秒即选即切,无需发消息触发。
|
|
13
|
+
- **Provider 网关与同名模型隔离**:将各 Provider 独立隔离(官方 `deepseek` 对应 `https://api.deepseek.com`,`opencode-go` 对应 `https://opencode.ai`),解决 DeepSeek V4 系列在官方与 OpenCode GO 之间的命名空间冲突与错误归属问题。
|
|
14
|
+
|
|
15
|
+
|
|
3
16
|
## [0.1.6] - 2026-08-25
|
|
4
17
|
|
|
5
18
|
### Docs
|
package/package.json
CHANGED
package/src/client.js
CHANGED
|
@@ -7,6 +7,8 @@ window.__ModuleLoader__.load({
|
|
|
7
7
|
const h = React.createElement
|
|
8
8
|
const { useEffect, useState, useCallback, useMemo } = React
|
|
9
9
|
|
|
10
|
+
let globalCtx = null
|
|
11
|
+
|
|
10
12
|
const T = {
|
|
11
13
|
bg: 'var(--dsw-alias-bg-layer-2, #ffffff)',
|
|
12
14
|
layer: 'var(--dsw-alias-bg-layer-2, #ffffff)',
|
|
@@ -62,7 +64,7 @@ window.__ModuleLoader__.load({
|
|
|
62
64
|
whiteSpace: 'nowrap',
|
|
63
65
|
}
|
|
64
66
|
|
|
65
|
-
// ----------
|
|
67
|
+
// ---------- 全局单例共享状态(网络延迟数据) ----------
|
|
66
68
|
const store = {
|
|
67
69
|
state: {
|
|
68
70
|
data: null,
|
|
@@ -128,37 +130,65 @@ window.__ModuleLoader__.load({
|
|
|
128
130
|
return modelId.split(/[-_]/)[0] || modelId
|
|
129
131
|
}
|
|
130
132
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
+
/**
|
|
134
|
+
* 权威实时抓取:直接从 DSH 官方 sessions 与 modelDirectories 服务订阅当前正在对话的模型与供应商
|
|
135
|
+
*/
|
|
136
|
+
function useCurrentActiveModel(defaultConfig) {
|
|
137
|
+
const [selection, setSelection] = useState(() => ({
|
|
138
|
+
provider: defaultConfig?.provider || 'cpa',
|
|
139
|
+
model: defaultConfig?.model || 'gemini-3.7-flash-high',
|
|
140
|
+
}))
|
|
133
141
|
|
|
134
142
|
useEffect(() => {
|
|
135
|
-
|
|
143
|
+
if (!globalCtx) return
|
|
144
|
+
|
|
145
|
+
const getSessions = () => (globalCtx.get ? globalCtx.get('sessions') : globalCtx.sessions)
|
|
146
|
+
const getModelDirs = () => (globalCtx.get ? globalCtx.get('modelDirectories') : globalCtx.modelDirectories)
|
|
147
|
+
|
|
148
|
+
let dirUnsub = null
|
|
149
|
+
|
|
150
|
+
const sync = () => {
|
|
136
151
|
try {
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
)
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
152
|
+
const sessions = getSessions()
|
|
153
|
+
const modelDirs = getModelDirs()
|
|
154
|
+
if (!sessions || !modelDirs) return
|
|
155
|
+
|
|
156
|
+
const curSessionId = sessions.list?.getSnapshot?.()?.current
|
|
157
|
+
if (!curSessionId) return
|
|
158
|
+
|
|
159
|
+
const dir = modelDirs.directoryFor?.(curSessionId)
|
|
160
|
+
if (!dir) return
|
|
161
|
+
|
|
162
|
+
if (dirUnsub) dirUnsub()
|
|
163
|
+
dirUnsub = dir.store?.subscribe ? dir.store.subscribe(applyDirectorySnapshot) : null
|
|
164
|
+
|
|
165
|
+
function applyDirectorySnapshot() {
|
|
166
|
+
const snap = dir.store?.getSnapshot?.()
|
|
167
|
+
if (snap?.current?.provider && snap?.current?.model) {
|
|
168
|
+
setSelection({
|
|
169
|
+
provider: snap.current.provider,
|
|
170
|
+
model: snap.current.model,
|
|
171
|
+
reasoningEffort: snap.current.reasoningEffort,
|
|
172
|
+
})
|
|
150
173
|
}
|
|
151
174
|
}
|
|
175
|
+
|
|
176
|
+
applyDirectorySnapshot()
|
|
152
177
|
} catch {}
|
|
153
|
-
if (defaultModel) setActiveModel(defaultModel)
|
|
154
178
|
}
|
|
155
179
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
180
|
+
sync()
|
|
181
|
+
|
|
182
|
+
const sessions = getSessions()
|
|
183
|
+
const unsubSessions = sessions?.list?.subscribe ? sessions.list.subscribe(sync) : null
|
|
160
184
|
|
|
161
|
-
|
|
185
|
+
return () => {
|
|
186
|
+
if (unsubSessions) unsubSessions()
|
|
187
|
+
if (dirUnsub) dirUnsub()
|
|
188
|
+
}
|
|
189
|
+
}, [defaultConfig])
|
|
190
|
+
|
|
191
|
+
return selection
|
|
162
192
|
}
|
|
163
193
|
|
|
164
194
|
function FooterEntry(props) {
|
|
@@ -174,21 +204,28 @@ window.__ModuleLoader__.load({
|
|
|
174
204
|
}, [])
|
|
175
205
|
|
|
176
206
|
const data = state.data
|
|
177
|
-
const
|
|
178
|
-
const
|
|
207
|
+
const defaultModelConfig = data?.defaultModelConfig
|
|
208
|
+
const activeSelection = useCurrentActiveModel(defaultModelConfig)
|
|
179
209
|
|
|
180
210
|
const activeProviderInfo = useMemo(() => {
|
|
181
|
-
if (!data
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
211
|
+
if (!data) return null
|
|
212
|
+
const pId = activeSelection.provider || data.defaultModelConfig?.provider || 'cpa'
|
|
213
|
+
if (data.providerMap && data.providerMap[pId]) {
|
|
214
|
+
return data.providerMap[pId]
|
|
215
|
+
}
|
|
216
|
+
if (data.providers) {
|
|
217
|
+
const found = data.providers.find((p) => p.id === pId)
|
|
218
|
+
if (found) return found
|
|
219
|
+
}
|
|
220
|
+
// 未识别的 provider:展示其真实 id,绝不冒名顶替列表中的第一个供应商
|
|
221
|
+
return { id: pId, name: String(pId).toUpperCase(), baseURL: '', probe: null }
|
|
222
|
+
}, [data, activeSelection.provider])
|
|
186
223
|
|
|
187
224
|
const currentLatency = activeProviderInfo?.probe?.latencyMs ?? -1
|
|
188
225
|
const currentQuality = activeProviderInfo?.probe?.quality || 'offline'
|
|
189
226
|
const statusColor = getQualityColor(currentQuality, currentLatency)
|
|
190
|
-
const providerDisplayName = activeProviderInfo?.name ||
|
|
191
|
-
const modelShortName = formatModelShortName(
|
|
227
|
+
const providerDisplayName = activeProviderInfo?.name || activeSelection.provider.toUpperCase()
|
|
228
|
+
const modelShortName = formatModelShortName(activeSelection.model)
|
|
192
229
|
|
|
193
230
|
const onMouseEnter = useCallback(() => {
|
|
194
231
|
clearTimeout(hoverTimerRef.current)
|
|
@@ -297,7 +334,7 @@ window.__ModuleLoader__.load({
|
|
|
297
334
|
fontFamily: 'system-ui, -apple-system, sans-serif',
|
|
298
335
|
},
|
|
299
336
|
},
|
|
300
|
-
h('div', { style: { fontWeight: 600, fontSize: '12px', marginBottom: 4 } }, `🎯 ${
|
|
337
|
+
h('div', { style: { fontWeight: 600, fontSize: '12px', marginBottom: 4 } }, `🎯 ${activeSelection.model}`),
|
|
301
338
|
h('div', { style: { color: T.secondary } }, `🏢 ${providerDisplayName}`),
|
|
302
339
|
h(
|
|
303
340
|
'div',
|
|
@@ -368,15 +405,22 @@ window.__ModuleLoader__.load({
|
|
|
368
405
|
const data = state.data
|
|
369
406
|
const loading = state.loading
|
|
370
407
|
|
|
371
|
-
const
|
|
372
|
-
const
|
|
408
|
+
const defaultModelConfig = data?.defaultModelConfig
|
|
409
|
+
const activeSelection = useCurrentActiveModel(defaultModelConfig)
|
|
373
410
|
|
|
374
411
|
const activeProviderInfo = useMemo(() => {
|
|
375
|
-
if (!data
|
|
376
|
-
const
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
412
|
+
if (!data) return null
|
|
413
|
+
const pId = activeSelection.provider || data.defaultModelConfig?.provider || 'cpa'
|
|
414
|
+
if (data.providerMap && data.providerMap[pId]) {
|
|
415
|
+
return data.providerMap[pId]
|
|
416
|
+
}
|
|
417
|
+
if (data.providers) {
|
|
418
|
+
const found = data.providers.find((p) => p.id === pId)
|
|
419
|
+
if (found) return found
|
|
420
|
+
}
|
|
421
|
+
// 未识别的 provider:展示其真实 id,绝不冒名顶替列表中的第一个供应商
|
|
422
|
+
return { id: pId, name: String(pId).toUpperCase(), baseURL: '', probe: null }
|
|
423
|
+
}, [data, activeSelection.provider])
|
|
380
424
|
|
|
381
425
|
if (!isOpen) return null
|
|
382
426
|
|
|
@@ -385,6 +429,7 @@ window.__ModuleLoader__.load({
|
|
|
385
429
|
const currentQuality = activeProviderInfo?.probe?.quality || 'offline'
|
|
386
430
|
const statusColor = getQualityColor(currentQuality, currentLatency)
|
|
387
431
|
const otherProviders = providers.filter((p) => p.id !== activeProviderInfo?.id)
|
|
432
|
+
const providerDisplayName = activeProviderInfo?.name || activeSelection.provider.toUpperCase()
|
|
388
433
|
|
|
389
434
|
return h(
|
|
390
435
|
'div',
|
|
@@ -441,7 +486,7 @@ window.__ModuleLoader__.load({
|
|
|
441
486
|
h(
|
|
442
487
|
'div',
|
|
443
488
|
{ style: { fontSize: '11px', color: T.secondary } },
|
|
444
|
-
'
|
|
489
|
+
'实时订阅当前对话供应商与模型状态',
|
|
445
490
|
),
|
|
446
491
|
),
|
|
447
492
|
),
|
|
@@ -523,7 +568,7 @@ window.__ModuleLoader__.load({
|
|
|
523
568
|
'div',
|
|
524
569
|
{ style: { display: 'flex', alignItems: 'center', gap: '8px' } },
|
|
525
570
|
h('span', { style: dotStyle(statusColor) }),
|
|
526
|
-
h('span', { style: { fontWeight: 600, fontSize: '14px' } },
|
|
571
|
+
h('span', { style: { fontWeight: 600, fontSize: '14px' } }, activeSelection.model),
|
|
527
572
|
h(
|
|
528
573
|
'span',
|
|
529
574
|
{
|
|
@@ -536,7 +581,7 @@ window.__ModuleLoader__.load({
|
|
|
536
581
|
fontWeight: 600,
|
|
537
582
|
},
|
|
538
583
|
},
|
|
539
|
-
`供应商: ${
|
|
584
|
+
`供应商: ${providerDisplayName}`,
|
|
540
585
|
),
|
|
541
586
|
),
|
|
542
587
|
h(
|
|
@@ -657,6 +702,8 @@ window.__ModuleLoader__.load({
|
|
|
657
702
|
}
|
|
658
703
|
|
|
659
704
|
function apply(ctx) {
|
|
705
|
+
globalCtx = ctx
|
|
706
|
+
|
|
660
707
|
ctx.effect(() => {
|
|
661
708
|
const el = document.createElement('style')
|
|
662
709
|
el.id = 'dsh-link-pulse-styles'
|
package/src/index.js
CHANGED
|
@@ -8,6 +8,37 @@ import jsYaml from 'js-yaml'
|
|
|
8
8
|
export const name = 'link-pulse'
|
|
9
9
|
export const inject = ['webServer']
|
|
10
10
|
|
|
11
|
+
const KNOWN_PROVIDER_DEFAULTS = {
|
|
12
|
+
deepseek: {
|
|
13
|
+
name: 'DeepSeek 官方',
|
|
14
|
+
baseURL: 'https://api.deepseek.com',
|
|
15
|
+
},
|
|
16
|
+
'deepseek-official': {
|
|
17
|
+
name: 'DeepSeek 官方',
|
|
18
|
+
baseURL: 'https://api.deepseek.com',
|
|
19
|
+
},
|
|
20
|
+
'opencode-go': {
|
|
21
|
+
name: 'OpenCode GO',
|
|
22
|
+
baseURL: 'https://opencode.ai',
|
|
23
|
+
},
|
|
24
|
+
openai: {
|
|
25
|
+
name: 'OpenAI 官方',
|
|
26
|
+
baseURL: 'https://api.openai.com',
|
|
27
|
+
},
|
|
28
|
+
anthropic: {
|
|
29
|
+
name: 'Anthropic 官方',
|
|
30
|
+
baseURL: 'https://api.anthropic.com',
|
|
31
|
+
},
|
|
32
|
+
google: {
|
|
33
|
+
name: 'Google Gemini',
|
|
34
|
+
baseURL: 'https://generativelanguage.googleapis.com',
|
|
35
|
+
},
|
|
36
|
+
openrouter: {
|
|
37
|
+
name: 'OpenRouter',
|
|
38
|
+
baseURL: 'https://openrouter.ai',
|
|
39
|
+
},
|
|
40
|
+
}
|
|
41
|
+
|
|
11
42
|
function getSettingsPath() {
|
|
12
43
|
const dshHome = process.env.DSH_HOME || path.join(os.homedir(), '.dsh')
|
|
13
44
|
return path.join(dshHome, 'settings.yaml')
|
|
@@ -30,52 +61,47 @@ export function loadDynamicProviders() {
|
|
|
30
61
|
}
|
|
31
62
|
|
|
32
63
|
const providers = []
|
|
33
|
-
const
|
|
64
|
+
const providerMap = {}
|
|
34
65
|
|
|
35
66
|
for (const [providerId, pConfig] of Object.entries(providersConfig)) {
|
|
36
67
|
const rawModels = pConfig.models || []
|
|
37
68
|
const modelList = rawModels.map((m) => (typeof m === 'string' ? m : m.id || m.name)).filter(Boolean)
|
|
38
69
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
70
|
+
const known = KNOWN_PROVIDER_DEFAULTS[providerId] || {}
|
|
71
|
+
const targetBaseUrl = pConfig.baseURL || known.baseURL || ''
|
|
72
|
+
const displayName = pConfig.name || known.name || providerId.toUpperCase()
|
|
42
73
|
|
|
43
|
-
|
|
44
|
-
if (!targetBaseUrl && providerId === 'opencode-go') {
|
|
45
|
-
targetBaseUrl = 'https://api.deepseek.com'
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
providers.push({
|
|
74
|
+
const item = {
|
|
49
75
|
id: providerId,
|
|
50
|
-
name:
|
|
76
|
+
name: displayName,
|
|
51
77
|
baseURL: targetBaseUrl,
|
|
52
78
|
api: pConfig.api || 'openai-responses',
|
|
53
79
|
models: modelList,
|
|
54
80
|
isDefault: defaultModelConfig.provider === providerId,
|
|
55
|
-
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
providers.push(item)
|
|
84
|
+
providerMap[providerId] = item
|
|
56
85
|
}
|
|
57
86
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
modelToProviderMap[m] = 'deepseek'
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
providers.push({
|
|
66
|
-
id: 'deepseek',
|
|
87
|
+
// 如果未配置官方 deepseek(DSH 内置官方路由的 provider id 为 deepseek-official),补充作为独立官方探测节点
|
|
88
|
+
if (!providerMap['deepseek-official']) {
|
|
89
|
+
const dsItem = {
|
|
90
|
+
id: 'deepseek-official',
|
|
67
91
|
name: 'DeepSeek 官方',
|
|
68
92
|
baseURL: 'https://api.deepseek.com',
|
|
69
93
|
api: 'deepseek-native',
|
|
70
|
-
models:
|
|
71
|
-
isDefault: defaultModelConfig.provider === 'deepseek',
|
|
72
|
-
}
|
|
94
|
+
models: ['deepseek-v4-flash', 'deepseek-v4-pro'],
|
|
95
|
+
isDefault: defaultModelConfig.provider === 'deepseek-official',
|
|
96
|
+
}
|
|
97
|
+
providers.push(dsItem)
|
|
98
|
+
providerMap['deepseek-official'] = dsItem
|
|
73
99
|
}
|
|
74
100
|
|
|
75
101
|
return {
|
|
76
102
|
providers,
|
|
103
|
+
providerMap,
|
|
77
104
|
defaultModelConfig,
|
|
78
|
-
modelToProviderMap,
|
|
79
105
|
}
|
|
80
106
|
}
|
|
81
107
|
|
|
@@ -213,7 +239,7 @@ export function apply(ctx) {
|
|
|
213
239
|
return sendJson(res, 200, cachedData)
|
|
214
240
|
}
|
|
215
241
|
|
|
216
|
-
const { providers,
|
|
242
|
+
const { providers, providerMap, defaultModelConfig } = loadDynamicProviders()
|
|
217
243
|
|
|
218
244
|
const probePromises = providers.map(async (p) => {
|
|
219
245
|
const probeResult = await probeLatency(p.baseURL)
|
|
@@ -224,11 +250,15 @@ export function apply(ctx) {
|
|
|
224
250
|
})
|
|
225
251
|
|
|
226
252
|
const probedProviders = await Promise.all(probePromises)
|
|
253
|
+
const probedMap = {}
|
|
254
|
+
for (const item of probedProviders) {
|
|
255
|
+
probedMap[item.id] = item
|
|
256
|
+
}
|
|
227
257
|
|
|
228
258
|
cachedData = {
|
|
229
259
|
defaultModelConfig,
|
|
230
|
-
modelToProviderMap,
|
|
231
260
|
providers: probedProviders,
|
|
261
|
+
providerMap: probedMap,
|
|
232
262
|
timestamp: Date.now(),
|
|
233
263
|
}
|
|
234
264
|
lastProbeAt = Date.now()
|