dsh-mesh-ui 0.0.11 → 0.0.12

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/lib/client.js CHANGED
@@ -678,7 +678,7 @@ body.light .dm-edge-label { stroke: rgba(255,255,255,.92); }
678
678
  h('span', null, '⋯')
679
679
  )
680
680
  ),
681
- h('div', { className: 'dm-hint' }, '拓扑 v21 · 内容 ' + Math.round(layout.W) + '×' + Math.round(layout.H) + 'px · 卡 ' + Math.round(layout.cardW) + '×' + Math.round(layout.cardH) + ' · 缩放 ' + Math.round(view.scale * 100) + '% · 滚轮缩放 / 拖拽平移')
681
+ h('div', { className: 'dm-hint' }, '拓扑 v23 · 内容 ' + Math.round(layout.W) + '×' + Math.round(layout.H) + 'px · 卡 ' + Math.round(layout.cardW) + '×' + Math.round(layout.cardH) + ' · 缩放 ' + Math.round(view.scale * 100) + '% · 滚轮缩放 / 拖拽平移')
682
682
  )
683
683
  }
684
684
 
@@ -1453,6 +1453,9 @@ function deviceLinkState(data, peer, lanMap) {
1453
1453
  // 2026-11-10:真刷新 —— 点击后等待后台采集完成再渲染;本地冷却 + 忙锁防止连点刷爆请求
1454
1454
  var s6 = useState(""); var refreshHint = s6[0]; var setRefreshHint = s6[1]
1455
1455
  var forceRef = useRef({ at: 0, busy: false })
1456
+ // 2026-11-10:verdict 防抖 —— 单次探测失败(远端忙/超时)只显示 degraded,连续 2 次才显示 down
1457
+ var verdictState = useState(''); var verdictShown = verdictState[0]; var setVerdictShown = verdictState[1]
1458
+ var verdictRef = useRef({ raw: '', streak: 0 })
1456
1459
  var s5 = useState('local'); var selectedKey = s5[0]; var setSelectedKey = s5[1]
1457
1460
 
1458
1461
  function load(force) {
@@ -1515,6 +1518,24 @@ function deviceLinkState(data, peer, lanMap) {
1515
1518
  }, [])
1516
1519
 
1517
1520
  useEffect(function () { if (tick > 0 && !forceRef.current.busy) load(false) }, [tick])
1521
+ useEffect(function () {
1522
+ var raw = String((data && data.verdict) || '')
1523
+ if (!raw) return
1524
+ var st = verdictRef.current
1525
+ var at = String((data && data.at) || '')
1526
+ var fresh = Boolean(at) && at !== st.lastAt
1527
+ if (raw === 'down') {
1528
+ if (fresh && !st.downAt) st.downAt = Date.now()
1529
+ st.lastAt = at
1530
+ var persisted = Boolean(st.downAt) && Date.now() - st.downAt >= 15000
1531
+ setVerdictShown(persisted ? 'down' : 'degraded')
1532
+ } else {
1533
+ st.downAt = 0
1534
+ st.lastAt = at
1535
+ setVerdictShown(raw)
1536
+ }
1537
+ st.raw = raw
1538
+ }, [data])
1518
1539
 
1519
1540
  function copyJson() {
1520
1541
  var text = JSON.stringify(data || {}, null, 2)
@@ -1540,7 +1561,11 @@ function deviceLinkState(data, peer, lanMap) {
1540
1561
  h('style', null, CSS),
1541
1562
  h('div', { className: 'dm-head' },
1542
1563
  h('span', { className: 'dm-title' }, 'AI 网络 · dsh-mesh'),
1543
- h('span', { className: 'dm-pill ' + (data.warming ? 'muted' : tone(data.verdict)) }, data.warming ? '预热中' : (data.verdict || '?')),
1564
+ h('span', {
1565
+ className: 'dm-pill ' + (data.warming ? 'muted' : tone(verdictShown || data.verdict)),
1566
+ title: data.warming ? '' : (verdictShown === 'degraded' ? '探测波动:需持续 ≥15s 且新快照仍 down 才显示 down' : ''),
1567
+ style: verdictShown === 'degraded' ? { opacity: 0.75 } : undefined
1568
+ }, data.warming ? '预热中' : (verdictShown || data.verdict || '?')),
1544
1569
  h('span', { className: 'dm-sub' }, (data.meshVersion ? 'mesh ' + data.meshVersion + ' · ' : '') + (data.at ? ago(data.at) : '')),
1545
1570
  h('span', { style: { flex: 1 } }),
1546
1571
  h('a', { className: 'dm-btn', href: API, target: '_blank', rel: 'noreferrer', style: { textDecoration: 'none' } }, '原始 JSON'),
@@ -2265,12 +2290,22 @@ body[data-ds-dark-theme] .dm-dg-fs-item.active { background:rgba(88,166,255,.28)
2265
2290
  var button = buttons[i]
2266
2291
  var text = (button.textContent || '').trim()
2267
2292
  if (text !== 'AI 网络' && text !== 'AI 网络(dsh-mesh)') continue
2268
- if (button.querySelector('svg[data-dsh-mesh-nav-icon]')) continue
2269
- var gear = button.querySelector('svg')
2270
- var className = gear ? gear.getAttribute('class') || '' : ''
2271
- var icon = navMeshIconDom(className)
2272
- if (gear && gear.parentNode === button) button.replaceChild(icon, gear)
2273
- else button.insertBefore(icon, button.firstChild)
2293
+ // 0.2.2 的默认图标可能包在 span 里;在默认图标的父容器里原地替换,
2294
+ // 保留间距/对齐,并清掉所有旧 SVG,避免齿轮 + 自绘图标双图标。
2295
+ var existingIcon = button.querySelector('svg[data-dsh-mesh-nav-icon]')
2296
+ var old = []
2297
+ var all = button.querySelectorAll('svg')
2298
+ for (var j = 0; j < all.length; j++) if (all[j] !== existingIcon) old.push(all[j])
2299
+ var reference = old[0] || null
2300
+ var parent = reference ? reference.parentNode : button
2301
+ var className = reference ? reference.getAttribute('class') || '' : ''
2302
+ if (!existingIcon) {
2303
+ var icon = navMeshIconDom(className)
2304
+ if (reference) parent.insertBefore(icon, reference)
2305
+ else if (button.firstChild) button.insertBefore(icon, button.firstChild)
2306
+ else button.appendChild(icon)
2307
+ }
2308
+ for (var k = 0; k < old.length; k++) if (old[k].parentNode) old[k].parentNode.removeChild(old[k])
2274
2309
  }
2275
2310
  } catch (error) {
2276
2311
  /* ignore */
@@ -2279,10 +2314,16 @@ body[data-ds-dark-theme] .dm-dg-fs-item.active { background:rgba(88,166,255,.28)
2279
2314
 
2280
2315
  function apply(ctx) {
2281
2316
  var disposers = []
2317
+ // 2026-11-10:AI 网络从「设置」迁到左侧栏独立页面,使用更大的主区域。
2282
2318
  try {
2283
- disposers.push(ctx.slots.inject('settings.section', function () { return ctx.slots.register(SECTION, MeshStatusSection) }))
2319
+ disposers.push(ctx.slots.inject('sidebar.panellist', function () {
2320
+ return ctx.slots.register({ name: 'sidebar.panellist', id: 'mesh-network', order: 54, label: function () { return 'AI 网络' } }, meshPanelIcon)
2321
+ }))
2322
+ disposers.push(ctx.slots.inject('main', function () {
2323
+ return ctx.slots.register({ name: 'main', key: 'mesh-network', inject: function () { return {} } }, MeshStatusSection)
2324
+ }))
2284
2325
  } catch (error) {
2285
- console.warn('[dsh-mesh-ui] settings section registration failed:', error)
2326
+ console.warn('[dsh-mesh-ui] sidebar network page registration failed:', error)
2286
2327
  }
2287
2328
  // 2026-11-07 方案 A:左侧栏「委托」面板(sidebar.panellist + main)
2288
2329
  try {
package/lib/collect.js CHANGED
@@ -15,10 +15,11 @@
15
15
 
16
16
  import { execFile } from 'node:child_process'
17
17
  import { existsSync, readdirSync, readFileSync, statfsSync, writeFileSync } from 'node:fs'
18
+ import { createRequire } from 'node:module'
18
19
  import { lookup as dnsLookup } from 'node:dns/promises'
19
20
  import { connect as netConnect } from 'node:net'
20
21
  import { arch as osArch, cpus as osCpus, freemem as osFreemem, homedir, platform as osPlatform, release as osRelease, totalmem as osTotalmem } from 'node:os'
21
- import { join } from 'node:path'
22
+ import { dirname, join } from 'node:path'
22
23
 
23
24
  const CLI_TIMEOUT_MS = 25_000
24
25
  const ALERT_HISTORY_MAX = 12
@@ -29,9 +30,19 @@ export function meshHome() {
29
30
  }
30
31
 
31
32
  export function resolveRepo() {
33
+ // 1) 显式 DSH_MESH_REPO / 常见开发目录(源码/本地仓库)
32
34
  const candidates = [process.env.DSH_MESH_REPO, join(homedir(), 'Code', 'dsh-mesh'), join(homedir(), 'Desktop', 'dsh-mesh')].filter(Boolean)
33
35
  for (const candidate of candidates) {
34
- if (existsSync(join(candidate, 'scripts', 'mesh-health.mjs'))) return candidate
36
+ if (existsSync(join(candidate, 'scripts', 'mesh-health.mjs')) && existsSync(join(candidate, 'src', 'cli.js'))) return candidate
37
+ }
38
+ // 2) 用户正常安装路径:dsh-mesh-ui 声明了 dsh-mesh-node 依赖,
39
+ // 直接解析已安装的 npm 包(随包带 scripts/mesh-health.mjs)。
40
+ try {
41
+ const requireFromPlugin = createRequire(import.meta.url)
42
+ const nodeRoot = dirname(requireFromPlugin.resolve('dsh-mesh-node/package.json'))
43
+ if (existsSync(join(nodeRoot, 'scripts', 'mesh-health.mjs')) && existsSync(join(nodeRoot, 'src', 'cli.js'))) return nodeRoot
44
+ } catch {
45
+ /* package not installed next to this plugin */
35
46
  }
36
47
  return null
37
48
  }
@@ -391,7 +402,7 @@ async function overviewData(repo, force = false) {
391
402
  */
392
403
  export async function collect(repo) {
393
404
  const generatedAt = new Date().toISOString()
394
- if (!repo) return { ok: false, error: 'dsh-mesh repo not found (set DSH_MESH_REPO)', generatedAt }
405
+ if (!repo) return { ok: false, error: 'dsh-mesh-node is not installed for dsh-mesh-ui (install it with `npm i -g dsh-mesh-node`, or set DSH_MESH_REPO to a dsh-mesh checkout)', generatedAt }
395
406
 
396
407
  const meshPromise = meshHealthJson(repo)
397
408
  const overviewPromise = overviewData(repo)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-mesh-ui",
3
- "version": "0.0.11",
4
- "description": "DSH settings-page panel for dsh-mesh: AI network topology, peer health, and delegation panel. Read-only; data is collected from the local mesh CLI.",
3
+ "version": "0.0.12",
4
+ "description": "DSH sidebar page for dsh-mesh: AI network topology, peer health, and delegation panel. Read-only; data is collected from the installed dsh-mesh-node package.",
5
5
  "icon": "icon.svg",
6
6
  "type": "module",
7
7
  "engines": {
@@ -27,6 +27,9 @@
27
27
  "platform": "web"
28
28
  }
29
29
  },
30
+ "dependencies": {
31
+ "dsh-mesh-node": "0.0.12"
32
+ },
30
33
  "peerDependencies": {
31
34
  "@deepseek-ai/dsh": ">=0.2.0-rc.2 <0.3.0",
32
35
  "react": "^18.2.0",