dsh-link-pulse 0.1.0 → 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 +19 -1
- package/package.json +1 -1
- package/src/client.js +229 -132
- package/src/index.js +14 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,27 @@
|
|
|
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
|
+
|
|
13
|
+
## [0.1.1] - 2026-08-25
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- 主动清理模型选择器文本中夹带的「default / 推理等级 / 模型」等 UI 标签残留,让长模型 ID(如 `opencode minimax-m3`)能正确识别为单个 token。
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- 恢复并改写侧边栏条目 hover 悬浮预览:220ms 悬停延迟后在按钮上方弹出小卡片,展示当前模型 / 供应商 / 实时延迟以及全部 Provider 链路测速对比。
|
|
20
|
+
|
|
3
21
|
## [0.1.0] - 2026-08-25
|
|
4
22
|
|
|
5
23
|
### Added
|
|
6
24
|
- 初始版本发布:支持从 `settings.yaml` 动态读取 Providers。
|
|
7
25
|
- 侧边栏常驻条目与当前会话模型动态感知。
|
|
8
26
|
- 多通道实时测速浮层看板。
|
|
9
|
-
- 遵循 Cordis@4 扁平依赖注入规范。
|
|
27
|
+
- 遵循 Cordis@4 扁平依赖注入规范。
|
package/package.json
CHANGED
package/src/client.js
CHANGED
|
@@ -62,11 +62,42 @@ window.__ModuleLoader__.load({
|
|
|
62
62
|
whiteSpace: 'nowrap',
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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) {
|
|
@@ -107,8 +138,13 @@ window.__ModuleLoader__.load({
|
|
|
107
138
|
'[data-slot="conversation.input.model"], button[class*="modelSelect"], div[class*="modelSelector"]',
|
|
108
139
|
)
|
|
109
140
|
if (modelButton && modelButton.textContent) {
|
|
110
|
-
|
|
111
|
-
|
|
141
|
+
let text = modelButton.textContent.trim()
|
|
142
|
+
// 剥离「default」「推理等级」「模型」等 UI 标签
|
|
143
|
+
text = text
|
|
144
|
+
.split(/[\n|·•]/)[0]
|
|
145
|
+
.replace(/\s*(default|推理等级|模型|model)\s*/gi, '')
|
|
146
|
+
.trim()
|
|
147
|
+
if (text && text.length < 30 && /[a-zA-Z0-9\-_.]/.test(text)) {
|
|
112
148
|
setActiveModel(text)
|
|
113
149
|
return
|
|
114
150
|
}
|
|
@@ -127,34 +163,17 @@ window.__ModuleLoader__.load({
|
|
|
127
163
|
|
|
128
164
|
function FooterEntry(props) {
|
|
129
165
|
const { wide } = props
|
|
130
|
-
const
|
|
131
|
-
const [
|
|
132
|
-
const
|
|
166
|
+
const state = useStore()
|
|
167
|
+
const [hovering, setHovering] = useState(false)
|
|
168
|
+
const hoverTimerRef = React.useRef(null)
|
|
133
169
|
|
|
134
170
|
useEffect(() => {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}, [])
|
|
138
|
-
|
|
139
|
-
const fetchData = useCallback(async () => {
|
|
140
|
-
try {
|
|
141
|
-
setLoading(true)
|
|
142
|
-
const res = await fetch('/api/link-pulse/status')
|
|
143
|
-
if (res.ok) {
|
|
144
|
-
const json = await res.json()
|
|
145
|
-
setData(json)
|
|
146
|
-
}
|
|
147
|
-
} catch {} finally {
|
|
148
|
-
setLoading(false)
|
|
149
|
-
}
|
|
150
|
-
}, [])
|
|
151
|
-
|
|
152
|
-
useEffect(() => {
|
|
153
|
-
fetchData()
|
|
154
|
-
const timer = setInterval(fetchData, 45_000)
|
|
171
|
+
fetchStatus(false)
|
|
172
|
+
const timer = setInterval(() => fetchStatus(false), 45_000)
|
|
155
173
|
return () => clearInterval(timer)
|
|
156
|
-
}, [
|
|
174
|
+
}, [])
|
|
157
175
|
|
|
176
|
+
const data = state.data
|
|
158
177
|
const defaultModel = data?.defaultModelConfig?.model || 'gemini-3.7-flash-high'
|
|
159
178
|
const activeModel = useActiveModel(defaultModel)
|
|
160
179
|
|
|
@@ -171,32 +190,30 @@ window.__ModuleLoader__.load({
|
|
|
171
190
|
const providerDisplayName = activeProviderInfo?.name || 'CPA'
|
|
172
191
|
const modelShortName = formatModelShortName(activeModel)
|
|
173
192
|
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
const lat = p.probe?.latencyMs > 0 ? `${p.probe.latencyMs}ms` : '离线'
|
|
183
|
-
lines.push(`• ${p.name}: ${lat}`)
|
|
184
|
-
}
|
|
185
|
-
return lines.join('\n')
|
|
186
|
-
}, [data, activeModel, providerDisplayName, currentLatency])
|
|
193
|
+
const onMouseEnter = useCallback(() => {
|
|
194
|
+
clearTimeout(hoverTimerRef.current)
|
|
195
|
+
hoverTimerRef.current = setTimeout(() => setHovering(true), 220)
|
|
196
|
+
}, [])
|
|
197
|
+
const onMouseLeave = useCallback(() => {
|
|
198
|
+
clearTimeout(hoverTimerRef.current)
|
|
199
|
+
setHovering(false)
|
|
200
|
+
}, [])
|
|
187
201
|
|
|
188
|
-
|
|
202
|
+
const otherProviders = (data?.providers || []).filter((p) => p.id !== activeProviderInfo?.id)
|
|
203
|
+
|
|
204
|
+
const node = h(
|
|
189
205
|
'button',
|
|
190
206
|
{
|
|
191
207
|
className: 'pulse-btn',
|
|
192
|
-
onClick: () =>
|
|
193
|
-
|
|
208
|
+
onClick: () => store.set({ open: !state.open }),
|
|
209
|
+
onMouseEnter: onMouseEnter,
|
|
210
|
+
onMouseLeave: onMouseLeave,
|
|
194
211
|
style: {
|
|
195
212
|
...btnBase,
|
|
196
213
|
width: '100%',
|
|
197
214
|
justifyContent: wide ? 'flex-start' : 'center',
|
|
198
|
-
borderColor:
|
|
199
|
-
background:
|
|
215
|
+
borderColor: state.open ? T.brand : T.border,
|
|
216
|
+
background: state.open ? 'var(--dsw-alias-interactive-bg-hover, rgba(59,130,246,.12))' : 'transparent',
|
|
200
217
|
},
|
|
201
218
|
},
|
|
202
219
|
h('span', {
|
|
@@ -246,34 +263,109 @@ window.__ModuleLoader__.load({
|
|
|
246
263
|
),
|
|
247
264
|
),
|
|
248
265
|
)
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
function OverlayPanel() {
|
|
252
|
-
const [isOpen, setIsOpen] = useState(overlayOpen)
|
|
253
|
-
const [data, setData] = useState(null)
|
|
254
|
-
const [loading, setLoading] = useState(false)
|
|
255
266
|
|
|
256
|
-
|
|
257
|
-
listeners.add(setIsOpen)
|
|
258
|
-
return () => listeners.delete(setIsOpen)
|
|
259
|
-
}, [])
|
|
267
|
+
if (!hovering || state.open) return node
|
|
260
268
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
269
|
+
return h(
|
|
270
|
+
'div',
|
|
271
|
+
{
|
|
272
|
+
style: { position: 'relative', width: '100%' },
|
|
273
|
+
onMouseEnter: onMouseEnter,
|
|
274
|
+
onMouseLeave: onMouseLeave,
|
|
275
|
+
},
|
|
276
|
+
node,
|
|
277
|
+
h(
|
|
278
|
+
'div',
|
|
279
|
+
{
|
|
280
|
+
className: 'pulse-panel-enter',
|
|
281
|
+
style: {
|
|
282
|
+
position: 'absolute',
|
|
283
|
+
left: 0,
|
|
284
|
+
right: 0,
|
|
285
|
+
bottom: 'calc(100% + 6px)',
|
|
286
|
+
background: T.bg,
|
|
287
|
+
border: `1px solid ${T.border2}`,
|
|
288
|
+
borderRadius: '10px',
|
|
289
|
+
boxShadow: '0 8px 24px rgba(0,0,0,.18)',
|
|
290
|
+
padding: '10px 12px',
|
|
291
|
+
fontSize: '11px',
|
|
292
|
+
lineHeight: '17px',
|
|
293
|
+
color: T.label,
|
|
294
|
+
whiteSpace: 'pre',
|
|
295
|
+
zIndex: 100,
|
|
296
|
+
fontFamily: 'system-ui, -apple-system, sans-serif',
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
h('div', { style: { fontWeight: 600, fontSize: '12px', marginBottom: 4 } }, `🎯 ${activeModel}`),
|
|
300
|
+
h('div', { style: { color: T.secondary } }, `🏢 ${providerDisplayName}`),
|
|
301
|
+
h(
|
|
302
|
+
'div',
|
|
303
|
+
{
|
|
304
|
+
style: {
|
|
305
|
+
color: statusColor,
|
|
306
|
+
fontWeight: 600,
|
|
307
|
+
marginTop: 2,
|
|
308
|
+
marginBottom: otherProviders.length > 0 ? 6 : 0,
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
`⚡ ${currentLatency > 0 ? currentLatency + ' ms' : '检测中'}`,
|
|
312
|
+
),
|
|
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
|
+
},
|
|
322
|
+
},
|
|
323
|
+
'🌐 其它 Provider 链路',
|
|
324
|
+
),
|
|
325
|
+
otherProviders.map((p, i) =>
|
|
326
|
+
h(
|
|
327
|
+
'div',
|
|
328
|
+
{
|
|
329
|
+
key: i,
|
|
330
|
+
style: {
|
|
331
|
+
display: 'flex',
|
|
332
|
+
justifyContent: 'space-between',
|
|
333
|
+
gap: 12,
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
h(
|
|
337
|
+
'span',
|
|
338
|
+
{ style: { display: 'flex', alignItems: 'center', gap: 4 } },
|
|
339
|
+
h('span', {
|
|
340
|
+
style: {
|
|
341
|
+
...dotStyle(getQualityColor(p.probe?.quality, p.probe?.latencyMs)),
|
|
342
|
+
width: 5,
|
|
343
|
+
height: 5,
|
|
344
|
+
},
|
|
345
|
+
}),
|
|
346
|
+
p.name,
|
|
347
|
+
),
|
|
348
|
+
h(
|
|
349
|
+
'span',
|
|
350
|
+
{
|
|
351
|
+
style: {
|
|
352
|
+
color: getQualityColor(p.probe?.quality, p.probe?.latencyMs),
|
|
353
|
+
fontWeight: 600,
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
p.probe?.latencyMs > 0 ? `${p.probe.latencyMs} ms` : '离线',
|
|
357
|
+
),
|
|
358
|
+
),
|
|
359
|
+
),
|
|
360
|
+
),
|
|
361
|
+
)
|
|
362
|
+
}
|
|
273
363
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
364
|
+
function OverlayPanel() {
|
|
365
|
+
const state = useStore()
|
|
366
|
+
const isOpen = state.open
|
|
367
|
+
const data = state.data
|
|
368
|
+
const loading = state.loading
|
|
277
369
|
|
|
278
370
|
const defaultModel = data?.defaultModelConfig?.model || 'gemini-3.7-flash-high'
|
|
279
371
|
const activeModel = useActiveModel(defaultModel)
|
|
@@ -291,6 +383,7 @@ window.__ModuleLoader__.load({
|
|
|
291
383
|
const currentLatency = activeProviderInfo?.probe?.latencyMs ?? -1
|
|
292
384
|
const currentQuality = activeProviderInfo?.probe?.quality || 'offline'
|
|
293
385
|
const statusColor = getQualityColor(currentQuality, currentLatency)
|
|
386
|
+
const otherProviders = providers.filter((p) => p.id !== activeProviderInfo?.id)
|
|
294
387
|
|
|
295
388
|
return h(
|
|
296
389
|
'div',
|
|
@@ -306,7 +399,7 @@ window.__ModuleLoader__.load({
|
|
|
306
399
|
backdropFilter: 'blur(4px)',
|
|
307
400
|
},
|
|
308
401
|
onClick: (e) => {
|
|
309
|
-
if (e.target === e.currentTarget)
|
|
402
|
+
if (e.target === e.currentTarget) store.set({ open: false })
|
|
310
403
|
},
|
|
311
404
|
},
|
|
312
405
|
h(
|
|
@@ -358,7 +451,7 @@ window.__ModuleLoader__.load({
|
|
|
358
451
|
'button',
|
|
359
452
|
{
|
|
360
453
|
className: 'pulse-btn',
|
|
361
|
-
onClick:
|
|
454
|
+
onClick: () => fetchStatus(true),
|
|
362
455
|
disabled: loading,
|
|
363
456
|
style: {
|
|
364
457
|
display: 'flex',
|
|
@@ -380,7 +473,7 @@ window.__ModuleLoader__.load({
|
|
|
380
473
|
'button',
|
|
381
474
|
{
|
|
382
475
|
className: 'pulse-btn',
|
|
383
|
-
onClick: () =>
|
|
476
|
+
onClick: () => store.set({ open: false }),
|
|
384
477
|
style: {
|
|
385
478
|
padding: '4px 8px',
|
|
386
479
|
borderRadius: '6px',
|
|
@@ -483,75 +576,79 @@ window.__ModuleLoader__.load({
|
|
|
483
576
|
marginBottom: '10px',
|
|
484
577
|
},
|
|
485
578
|
},
|
|
486
|
-
'🌐
|
|
579
|
+
'🌐 其它可用 Provider 链路测速',
|
|
487
580
|
),
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
{ style: { display: 'flex', flexDirection: 'column', gap: '8px' } },
|
|
491
|
-
providers.map((p, idx) => {
|
|
492
|
-
const lat = p.probe?.latencyMs ?? -1
|
|
493
|
-
const qual = p.probe?.quality || 'offline'
|
|
494
|
-
const col = getQualityColor(qual, lat)
|
|
495
|
-
const isCurrent = activeProviderInfo?.id === p.id
|
|
496
|
-
|
|
581
|
+
(() => {
|
|
582
|
+
if (otherProviders.length === 0) {
|
|
497
583
|
return h(
|
|
498
584
|
'div',
|
|
499
585
|
{
|
|
500
|
-
key: idx,
|
|
501
586
|
style: {
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
padding: '
|
|
506
|
-
borderRadius: '8px',
|
|
507
|
-
background: isCurrent ? 'rgba(59,130,246,.08)' : 'rgba(0,0,0,.03)',
|
|
508
|
-
border: `1px solid ${isCurrent ? 'rgba(59,130,246,.3)' : 'transparent'}`,
|
|
587
|
+
fontSize: '11px',
|
|
588
|
+
color: T.secondary,
|
|
589
|
+
textAlign: 'center',
|
|
590
|
+
padding: '12px 0',
|
|
509
591
|
},
|
|
510
592
|
},
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
fontSize: '9px',
|
|
525
|
-
color: T.brand,
|
|
526
|
-
fontWeight: 600,
|
|
527
|
-
},
|
|
528
|
-
},
|
|
529
|
-
'● 当前主力',
|
|
530
|
-
),
|
|
531
|
-
),
|
|
532
|
-
h(
|
|
533
|
-
'div',
|
|
534
|
-
{ style: { fontSize: '10px', color: T.secondary, marginTop: '2px' } },
|
|
535
|
-
p.baseURL || '官方内置通道',
|
|
536
|
-
),
|
|
537
|
-
),
|
|
538
|
-
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(
|
|
539
606
|
'div',
|
|
540
|
-
{
|
|
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
|
+
},
|
|
541
619
|
h(
|
|
542
620
|
'div',
|
|
543
|
-
|
|
544
|
-
|
|
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
|
+
),
|
|
545
633
|
),
|
|
546
634
|
h(
|
|
547
635
|
'div',
|
|
548
|
-
{ style: {
|
|
549
|
-
|
|
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
|
+
),
|
|
550
647
|
),
|
|
551
|
-
)
|
|
552
|
-
)
|
|
553
|
-
|
|
554
|
-
),
|
|
648
|
+
)
|
|
649
|
+
}),
|
|
650
|
+
)
|
|
651
|
+
})(),
|
|
555
652
|
),
|
|
556
653
|
),
|
|
557
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
|
-
|
|
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
|
}
|