dsh-link-pulse 0.1.0 → 0.1.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
@@ -1,9 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.1] - 2026-08-25
4
+
5
+ ### Fixed
6
+ - 主动清理模型选择器文本中夹带的「default / 推理等级 / 模型」等 UI 标签残留,让长模型 ID(如 `opencode minimax-m3`)能正确识别为单个 token。
7
+
8
+ ### Added
9
+ - 恢复并改写侧边栏条目 hover 悬浮预览:220ms 悬停延迟后在按钮上方弹出小卡片,展示当前模型 / 供应商 / 实时延迟以及全部 Provider 链路测速对比。
10
+
3
11
  ## [0.1.0] - 2026-08-25
4
12
 
5
13
  ### Added
6
14
  - 初始版本发布:支持从 `settings.yaml` 动态读取 Providers。
7
15
  - 侧边栏常驻条目与当前会话模型动态感知。
8
16
  - 多通道实时测速浮层看板。
9
- - 遵循 Cordis@4 扁平依赖注入规范。
17
+ - 遵循 Cordis@4 扁平依赖注入规范。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-link-pulse",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
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
@@ -107,8 +107,13 @@ window.__ModuleLoader__.load({
107
107
  '[data-slot="conversation.input.model"], button[class*="modelSelect"], div[class*="modelSelector"]',
108
108
  )
109
109
  if (modelButton && modelButton.textContent) {
110
- const text = modelButton.textContent.trim()
111
- if (text && text.length < 30) {
110
+ let text = modelButton.textContent.trim()
111
+ // 剥离开关/标签 UI 残留,如「default」「推理等级」「high」等
112
+ text = text
113
+ .split(/[\n|·•]/)[0]
114
+ .replace(/\s*(default|推理等级|模型|model)\s*/gi, '')
115
+ .trim()
116
+ if (text && text.length < 30 && /[a-zA-Z0-9\-_.]/.test(text)) {
112
117
  setActiveModel(text)
113
118
  return
114
119
  }
@@ -128,8 +133,10 @@ window.__ModuleLoader__.load({
128
133
  function FooterEntry(props) {
129
134
  const { wide } = props
130
135
  const [isOpen, setIsOpen] = useState(overlayOpen)
136
+ const [hovering, setHovering] = useState(false)
131
137
  const [data, setData] = useState(null)
132
138
  const [loading, setLoading] = useState(false)
139
+ const hoverTimerRef = React.useRef(null)
133
140
 
134
141
  useEffect(() => {
135
142
  listeners.add(setIsOpen)
@@ -171,26 +178,22 @@ window.__ModuleLoader__.load({
171
178
  const providerDisplayName = activeProviderInfo?.name || 'CPA'
172
179
  const modelShortName = formatModelShortName(activeModel)
173
180
 
174
- const tooltipText = useMemo(() => {
175
- if (!data?.providers) return '链路测速中...'
176
- const lines = [`🎯 当前对话模型: ${activeModel}`]
177
- lines.push(`🏢 归属供应商: ${providerDisplayName}`)
178
- lines.push(`⚡ 实时往返延迟: ${currentLatency > 0 ? currentLatency + ' ms' : '检测中'}`)
179
- lines.push('────────────────────────')
180
- lines.push('🌐 已接入 Provider 链路一览:')
181
- for (const p of data.providers) {
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])
181
+ const onMouseEnter = useCallback(() => {
182
+ clearTimeout(hoverTimerRef.current)
183
+ hoverTimerRef.current = setTimeout(() => setHovering(true), 220)
184
+ }, [])
185
+ const onMouseLeave = useCallback(() => {
186
+ clearTimeout(hoverTimerRef.current)
187
+ setHovering(false)
188
+ }, [])
187
189
 
188
- return h(
190
+ const node = h(
189
191
  'button',
190
192
  {
191
193
  className: 'pulse-btn',
192
194
  onClick: () => setOverlay(!isOpen),
193
- title: tooltipText,
195
+ onMouseEnter: onMouseEnter,
196
+ onMouseLeave: onMouseLeave,
194
197
  style: {
195
198
  ...btnBase,
196
199
  width: '100%',
@@ -246,6 +249,96 @@ window.__ModuleLoader__.load({
246
249
  ),
247
250
  ),
248
251
  )
252
+
253
+ if (!hovering || isOpen) return node
254
+
255
+ const providers = data?.providers || []
256
+ return h(
257
+ 'div',
258
+ {
259
+ style: { position: 'relative', width: '100%' },
260
+ onMouseEnter: onMouseEnter,
261
+ onMouseLeave: onMouseLeave,
262
+ },
263
+ node,
264
+ h(
265
+ 'div',
266
+ {
267
+ className: 'pulse-panel-enter',
268
+ style: {
269
+ position: 'absolute',
270
+ left: 0,
271
+ right: 0,
272
+ bottom: 'calc(100% + 6px)',
273
+ background: T.bg,
274
+ border: `1px solid ${T.border2}`,
275
+ borderRadius: '10px',
276
+ boxShadow: '0 8px 24px rgba(0,0,0,.18)',
277
+ padding: '10px 12px',
278
+ fontSize: '11px',
279
+ lineHeight: '17px',
280
+ color: T.label,
281
+ whiteSpace: 'pre',
282
+ z: 100,
283
+ fontFamily: 'system-ui, -apple-system, sans-serif',
284
+ },
285
+ },
286
+ h('div', { style: { fontWeight: 600, fontSize: '12px', marginBottom: 4 } }, `🎯 ${activeModel}`),
287
+ h('div', { style: { color: T.secondary } }, `🏢 ${providerDisplayName}`),
288
+ h(
289
+ 'div',
290
+ {
291
+ style: {
292
+ color: statusColor,
293
+ fontWeight: 600,
294
+ marginTop: 2,
295
+ marginBottom: 6,
296
+ },
297
+ },
298
+ `⚡ ${currentLatency > 0 ? currentLatency + ' ms' : '检测中'}`,
299
+ ),
300
+ h(
301
+ 'div',
302
+ {
303
+ style: {
304
+ borderTop: `1px dashed ${T.border}`,
305
+ paddingTop: 6,
306
+ color: T.secondary,
307
+ },
308
+ },
309
+ '🌐 Provider 链路一览',
310
+ ),
311
+ providers.map((p, i) =>
312
+ h(
313
+ 'div',
314
+ {
315
+ key: i,
316
+ style: {
317
+ display: 'flex',
318
+ justifyContent: 'space-between',
319
+ gap: 12,
320
+ },
321
+ },
322
+ h(
323
+ 'span',
324
+ { style: { display: 'flex', alignItems: 'center', gap: 4 } },
325
+ h('span', { style: { ...dotStyle(getQualityColor(p.probe?.quality, p.probe?.latencyMs)), width: 5, height: 5 } }),
326
+ p.name,
327
+ ),
328
+ h(
329
+ 'span',
330
+ {
331
+ style: {
332
+ color: getQualityColor(p.probe?.quality, p.probe?.latencyMs),
333
+ fontWeight: 600,
334
+ },
335
+ },
336
+ p.probe?.latencyMs > 0 ? `${p.probe.latencyMs} ms` : '离线',
337
+ ),
338
+ ),
339
+ ),
340
+ ),
341
+ )
249
342
  }
250
343
 
251
344
  function OverlayPanel() {