dsh-link-pulse 0.1.1 → 0.1.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 CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.3] - 2026-08-25
4
+
5
+ ### Fixed
6
+ - 修复内外延迟显示不一致问题:引入全局单例状态存储与后端 25 秒探测缓存(支持 `?refresh=1` 强制刷新),确保侧栏按钮、Hover 悬浮小卡片与点击全屏大看板的延迟数据 100% 同步同源。
7
+
8
+ ## [0.1.2] - 2026-08-25
9
+
10
+ ### Changed
11
+ - 去除 Provider 链路对比列表中的当前主力通道重复展示:当前主力已在顶部重点呈现,下方列表聚焦展示「其它可用 Provider」的测速对比(Hover 浮层与全屏看板同步生效)。
12
+
3
13
  ## [0.1.1] - 2026-08-25
4
14
 
5
15
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-link-pulse",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Dynamic LLM provider gateway latency and health pulse probe for DeepSeek Harness",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
package/src/client.js CHANGED
@@ -62,11 +62,42 @@ window.__ModuleLoader__.load({
62
62
  whiteSpace: 'nowrap',
63
63
  }
64
64
 
65
- let overlayOpen = false
66
- const listeners = new Set()
67
- function setOverlay(val) {
68
- overlayOpen = val
69
- listeners.forEach((fn) => fn(val))
65
+ // ---------- 全局单例共享状态(确保内外完全同源同步) ----------
66
+ const store = {
67
+ state: {
68
+ data: null,
69
+ loading: false,
70
+ open: false,
71
+ },
72
+ listeners: new Set(),
73
+ set(patch) {
74
+ store.state = { ...store.state, ...patch }
75
+ for (const fn of store.listeners) fn()
76
+ },
77
+ subscribe(fn) {
78
+ store.listeners.add(fn)
79
+ return () => store.listeners.delete(fn)
80
+ },
81
+ }
82
+
83
+ function useStore() {
84
+ return React.useSyncExternalStore(store.subscribe, () => store.state)
85
+ }
86
+
87
+ async function fetchStatus(force = false) {
88
+ if (store.state.loading && !force) return
89
+ try {
90
+ store.set({ loading: true })
91
+ const res = await fetch(`/api/link-pulse/status${force ? '?refresh=1' : ''}`)
92
+ if (res.ok) {
93
+ const json = await res.json()
94
+ store.set({ data: json, loading: false })
95
+ } else {
96
+ store.set({ loading: false })
97
+ }
98
+ } catch {
99
+ store.set({ loading: false })
100
+ }
70
101
  }
71
102
 
72
103
  function getQualityColor(quality, latencyMs) {
@@ -108,7 +139,7 @@ window.__ModuleLoader__.load({
108
139
  )
109
140
  if (modelButton && modelButton.textContent) {
110
141
  let text = modelButton.textContent.trim()
111
- // 剥离开关/标签 UI 残留,如「default」「推理等级」「high」等
142
+ // 剥离「default」「推理等级」「模型」等 UI 标签
112
143
  text = text
113
144
  .split(/[\n|·•]/)[0]
114
145
  .replace(/\s*(default|推理等级|模型|model)\s*/gi, '')
@@ -132,36 +163,17 @@ window.__ModuleLoader__.load({
132
163
 
133
164
  function FooterEntry(props) {
134
165
  const { wide } = props
135
- const [isOpen, setIsOpen] = useState(overlayOpen)
166
+ const state = useStore()
136
167
  const [hovering, setHovering] = useState(false)
137
- const [data, setData] = useState(null)
138
- const [loading, setLoading] = useState(false)
139
168
  const hoverTimerRef = React.useRef(null)
140
169
 
141
170
  useEffect(() => {
142
- listeners.add(setIsOpen)
143
- return () => listeners.delete(setIsOpen)
144
- }, [])
145
-
146
- const fetchData = useCallback(async () => {
147
- try {
148
- setLoading(true)
149
- const res = await fetch('/api/link-pulse/status')
150
- if (res.ok) {
151
- const json = await res.json()
152
- setData(json)
153
- }
154
- } catch {} finally {
155
- setLoading(false)
156
- }
157
- }, [])
158
-
159
- useEffect(() => {
160
- fetchData()
161
- const timer = setInterval(fetchData, 45_000)
171
+ fetchStatus(false)
172
+ const timer = setInterval(() => fetchStatus(false), 45_000)
162
173
  return () => clearInterval(timer)
163
- }, [fetchData])
174
+ }, [])
164
175
 
176
+ const data = state.data
165
177
  const defaultModel = data?.defaultModelConfig?.model || 'gemini-3.7-flash-high'
166
178
  const activeModel = useActiveModel(defaultModel)
167
179
 
@@ -187,19 +199,21 @@ window.__ModuleLoader__.load({
187
199
  setHovering(false)
188
200
  }, [])
189
201
 
202
+ const otherProviders = (data?.providers || []).filter((p) => p.id !== activeProviderInfo?.id)
203
+
190
204
  const node = h(
191
205
  'button',
192
206
  {
193
207
  className: 'pulse-btn',
194
- onClick: () => setOverlay(!isOpen),
208
+ onClick: () => store.set({ open: !state.open }),
195
209
  onMouseEnter: onMouseEnter,
196
210
  onMouseLeave: onMouseLeave,
197
211
  style: {
198
212
  ...btnBase,
199
213
  width: '100%',
200
214
  justifyContent: wide ? 'flex-start' : 'center',
201
- borderColor: isOpen ? T.brand : T.border,
202
- background: isOpen ? 'var(--dsw-alias-interactive-bg-hover, rgba(59,130,246,.12))' : 'transparent',
215
+ borderColor: state.open ? T.brand : T.border,
216
+ background: state.open ? 'var(--dsw-alias-interactive-bg-hover, rgba(59,130,246,.12))' : 'transparent',
203
217
  },
204
218
  },
205
219
  h('span', {
@@ -250,9 +264,8 @@ window.__ModuleLoader__.load({
250
264
  ),
251
265
  )
252
266
 
253
- if (!hovering || isOpen) return node
267
+ if (!hovering || state.open) return node
254
268
 
255
- const providers = data?.providers || []
256
269
  return h(
257
270
  'div',
258
271
  {
@@ -279,7 +292,7 @@ window.__ModuleLoader__.load({
279
292
  lineHeight: '17px',
280
293
  color: T.label,
281
294
  whiteSpace: 'pre',
282
- z: 100,
295
+ zIndex: 100,
283
296
  fontFamily: 'system-ui, -apple-system, sans-serif',
284
297
  },
285
298
  },
@@ -292,23 +305,24 @@ window.__ModuleLoader__.load({
292
305
  color: statusColor,
293
306
  fontWeight: 600,
294
307
  marginTop: 2,
295
- marginBottom: 6,
308
+ marginBottom: otherProviders.length > 0 ? 6 : 0,
296
309
  },
297
310
  },
298
311
  `⚡ ${currentLatency > 0 ? currentLatency + ' ms' : '检测中'}`,
299
312
  ),
300
- h(
301
- 'div',
302
- {
303
- style: {
304
- borderTop: `1px dashed ${T.border}`,
305
- paddingTop: 6,
306
- color: T.secondary,
313
+ otherProviders.length > 0 &&
314
+ h(
315
+ 'div',
316
+ {
317
+ style: {
318
+ borderTop: `1px dashed ${T.border}`,
319
+ paddingTop: 6,
320
+ color: T.secondary,
321
+ },
307
322
  },
308
- },
309
- '🌐 Provider 链路一览',
310
- ),
311
- providers.map((p, i) =>
323
+ '🌐 其它 Provider 链路',
324
+ ),
325
+ otherProviders.map((p, i) =>
312
326
  h(
313
327
  'div',
314
328
  {
@@ -322,7 +336,13 @@ window.__ModuleLoader__.load({
322
336
  h(
323
337
  'span',
324
338
  { style: { display: 'flex', alignItems: 'center', gap: 4 } },
325
- h('span', { style: { ...dotStyle(getQualityColor(p.probe?.quality, p.probe?.latencyMs)), width: 5, height: 5 } }),
339
+ h('span', {
340
+ style: {
341
+ ...dotStyle(getQualityColor(p.probe?.quality, p.probe?.latencyMs)),
342
+ width: 5,
343
+ height: 5,
344
+ },
345
+ }),
326
346
  p.name,
327
347
  ),
328
348
  h(
@@ -342,31 +362,10 @@ window.__ModuleLoader__.load({
342
362
  }
343
363
 
344
364
  function OverlayPanel() {
345
- const [isOpen, setIsOpen] = useState(overlayOpen)
346
- const [data, setData] = useState(null)
347
- const [loading, setLoading] = useState(false)
348
-
349
- useEffect(() => {
350
- listeners.add(setIsOpen)
351
- return () => listeners.delete(setIsOpen)
352
- }, [])
353
-
354
- const fetchData = useCallback(async () => {
355
- try {
356
- setLoading(true)
357
- const res = await fetch('/api/link-pulse/status')
358
- if (res.ok) {
359
- const json = await res.json()
360
- setData(json)
361
- }
362
- } catch {} finally {
363
- setLoading(false)
364
- }
365
- }, [])
366
-
367
- useEffect(() => {
368
- if (isOpen) fetchData()
369
- }, [isOpen, fetchData])
365
+ const state = useStore()
366
+ const isOpen = state.open
367
+ const data = state.data
368
+ const loading = state.loading
370
369
 
371
370
  const defaultModel = data?.defaultModelConfig?.model || 'gemini-3.7-flash-high'
372
371
  const activeModel = useActiveModel(defaultModel)
@@ -384,6 +383,7 @@ window.__ModuleLoader__.load({
384
383
  const currentLatency = activeProviderInfo?.probe?.latencyMs ?? -1
385
384
  const currentQuality = activeProviderInfo?.probe?.quality || 'offline'
386
385
  const statusColor = getQualityColor(currentQuality, currentLatency)
386
+ const otherProviders = providers.filter((p) => p.id !== activeProviderInfo?.id)
387
387
 
388
388
  return h(
389
389
  'div',
@@ -399,7 +399,7 @@ window.__ModuleLoader__.load({
399
399
  backdropFilter: 'blur(4px)',
400
400
  },
401
401
  onClick: (e) => {
402
- if (e.target === e.currentTarget) setOverlay(false)
402
+ if (e.target === e.currentTarget) store.set({ open: false })
403
403
  },
404
404
  },
405
405
  h(
@@ -451,7 +451,7 @@ window.__ModuleLoader__.load({
451
451
  'button',
452
452
  {
453
453
  className: 'pulse-btn',
454
- onClick: fetchData,
454
+ onClick: () => fetchStatus(true),
455
455
  disabled: loading,
456
456
  style: {
457
457
  display: 'flex',
@@ -473,7 +473,7 @@ window.__ModuleLoader__.load({
473
473
  'button',
474
474
  {
475
475
  className: 'pulse-btn',
476
- onClick: () => setOverlay(false),
476
+ onClick: () => store.set({ open: false }),
477
477
  style: {
478
478
  padding: '4px 8px',
479
479
  borderRadius: '6px',
@@ -576,75 +576,79 @@ window.__ModuleLoader__.load({
576
576
  marginBottom: '10px',
577
577
  },
578
578
  },
579
- '🌐 已接入 Provider 链路测速对比',
579
+ '🌐 其它可用 Provider 链路测速',
580
580
  ),
581
- h(
582
- 'div',
583
- { style: { display: 'flex', flexDirection: 'column', gap: '8px' } },
584
- providers.map((p, idx) => {
585
- const lat = p.probe?.latencyMs ?? -1
586
- const qual = p.probe?.quality || 'offline'
587
- const col = getQualityColor(qual, lat)
588
- const isCurrent = activeProviderInfo?.id === p.id
589
-
581
+ (() => {
582
+ if (otherProviders.length === 0) {
590
583
  return h(
591
584
  'div',
592
585
  {
593
- key: idx,
594
586
  style: {
595
- display: 'flex',
596
- justifyContent: 'space-between',
597
- alignItems: 'center',
598
- padding: '8px 10px',
599
- borderRadius: '8px',
600
- background: isCurrent ? 'rgba(59,130,246,.08)' : 'rgba(0,0,0,.03)',
601
- border: `1px solid ${isCurrent ? 'rgba(59,130,246,.3)' : 'transparent'}`,
587
+ fontSize: '11px',
588
+ color: T.secondary,
589
+ textAlign: 'center',
590
+ padding: '12px 0',
602
591
  },
603
592
  },
604
- h(
605
- 'div',
606
- null,
607
- h(
608
- 'div',
609
- { style: { display: 'flex', alignItems: 'center', gap: '6px' } },
610
- h('span', { style: dotStyle(col) }),
611
- h('span', { style: { fontWeight: 600, fontSize: '12px' } }, p.name),
612
- isCurrent &&
613
- h(
614
- 'span',
615
- {
616
- style: {
617
- fontSize: '9px',
618
- color: T.brand,
619
- fontWeight: 600,
620
- },
621
- },
622
- '● 当前主力',
623
- ),
624
- ),
625
- h(
626
- 'div',
627
- { style: { fontSize: '10px', color: T.secondary, marginTop: '2px' } },
628
- p.baseURL || '官方内置通道',
629
- ),
630
- ),
631
- h(
593
+ '暂无其它已接入的 Provider',
594
+ )
595
+ }
596
+
597
+ return h(
598
+ 'div',
599
+ { style: { display: 'flex', flexDirection: 'column', gap: '8px' } },
600
+ otherProviders.map((p, idx) => {
601
+ const lat = p.probe?.latencyMs ?? -1
602
+ const qual = p.probe?.quality || 'offline'
603
+ const col = getQualityColor(qual, lat)
604
+
605
+ return h(
632
606
  'div',
633
- { style: { textAlign: 'right' } },
607
+ {
608
+ key: idx,
609
+ style: {
610
+ display: 'flex',
611
+ justifyContent: 'space-between',
612
+ alignItems: 'center',
613
+ padding: '8px 10px',
614
+ borderRadius: '8px',
615
+ background: 'rgba(0,0,0,.03)',
616
+ border: `1px solid transparent`,
617
+ },
618
+ },
634
619
  h(
635
620
  'div',
636
- { style: { fontWeight: 700, fontSize: '13px', color: col } },
637
- lat > 0 ? `${lat} ms` : '超时/离线',
621
+ null,
622
+ h(
623
+ 'div',
624
+ { style: { display: 'flex', alignItems: 'center', gap: '6px' } },
625
+ h('span', { style: dotStyle(col) }),
626
+ h('span', { style: { fontWeight: 600, fontSize: '12px' } }, p.name),
627
+ ),
628
+ h(
629
+ 'div',
630
+ { style: { fontSize: '10px', color: T.secondary, marginTop: '2px' } },
631
+ p.baseURL || '官方内置通道',
632
+ ),
638
633
  ),
639
634
  h(
640
635
  'div',
641
- { style: { fontSize: '9px', color: T.secondary } },
642
- lat > 0 ? (lat < 200 ? '极佳' : '良好') : '不可达',
636
+ { style: { textAlign: 'right' } },
637
+ h(
638
+ 'div',
639
+ { style: { fontWeight: 700, fontSize: '13px', color: col } },
640
+ lat > 0 ? `${lat} ms` : '超时/离线',
641
+ ),
642
+ h(
643
+ 'div',
644
+ { style: { fontSize: '9px', color: T.secondary } },
645
+ lat > 0 ? (lat < 200 ? '极佳' : '良好') : '不可达',
646
+ ),
643
647
  ),
644
- ),
645
- )
646
- }),
647
- ),
648
+ )
649
+ }),
650
+ )
651
+ })(),
648
652
  ),
649
653
  ),
650
654
  ),
package/src/index.js CHANGED
@@ -137,6 +137,9 @@ function sendJson(res, statusCode, data) {
137
137
  res.end(JSON.stringify(data))
138
138
  }
139
139
 
140
+ let cachedData = null
141
+ let lastProbeAt = 0
142
+
140
143
  export function apply(ctx) {
141
144
  ctx.effect(() =>
142
145
  ctx.webServer.register({
@@ -148,6 +151,12 @@ export function apply(ctx) {
148
151
  }
149
152
 
150
153
  try {
154
+ const isForce = req.url && req.url.includes('refresh=1')
155
+ const now = Date.now()
156
+ if (!isForce && cachedData && now - lastProbeAt < 25_000) {
157
+ return sendJson(res, 200, cachedData)
158
+ }
159
+
151
160
  const { providers, defaultModelConfig, modelToProviderMap } = loadDynamicProviders()
152
161
 
153
162
  const probePromises = providers.map(async (p) => {
@@ -160,12 +169,15 @@ export function apply(ctx) {
160
169
 
161
170
  const probedProviders = await Promise.all(probePromises)
162
171
 
163
- sendJson(res, 200, {
172
+ cachedData = {
164
173
  defaultModelConfig,
165
174
  modelToProviderMap,
166
175
  providers: probedProviders,
167
176
  timestamp: Date.now(),
168
- })
177
+ }
178
+ lastProbeAt = Date.now()
179
+
180
+ sendJson(res, 200, cachedData)
169
181
  } catch (err) {
170
182
  sendJson(res, 500, { error: String(err && err.message ? err.message : err) })
171
183
  }