dsh-links-panel 0.79.0 → 0.80.0

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
@@ -3172,7 +3172,8 @@ window.__ModuleLoader__.load({
3172
3172
  domain: rootDomain(reference.host),
3173
3173
  }))
3174
3174
  .find((item) => {
3175
- if (faviconLibrary[item.host] !== undefined || faviconLibrary[item.domain] !== undefined) return false
3175
+ const cached = [faviconLibrary[item.host], faviconLibrary[item.domain]]
3176
+ if (cached.some((value) => typeof value === 'string' && value.indexOf('data:') === 0)) return false
3176
3177
  if (faviconQueueRef.current.has(item.host)) return false
3177
3178
  const failedAt = faviconFailedRef.current.get(item.host)
3178
3179
  return failedAt === undefined || Date.now() - failedAt >= 10 * 60 * 1000
@@ -3321,7 +3322,8 @@ window.__ModuleLoader__.load({
3321
3322
  (reference) => {
3322
3323
  const host = reference.host.replace(/^www\./, '')
3323
3324
  const domain = rootDomain(reference.host)
3324
- if (faviconLibrary[host] !== undefined || faviconLibrary[domain] !== undefined) return
3325
+ const cached = [faviconLibrary[host], faviconLibrary[domain]]
3326
+ if (cached.some((value) => typeof value === 'string' && value.indexOf('data:') === 0)) return
3325
3327
  const requestFavicon = async (iconUrl) => {
3326
3328
  try {
3327
3329
  const url = new URL('/dsh-links/favicon', document.baseURI)
@@ -3365,17 +3367,27 @@ window.__ModuleLoader__.load({
3365
3367
  const openReference = React.useCallback(
3366
3368
  (reference, event) => {
3367
3369
  const external = !!(event && (event.metaKey || event.ctrlKey || event.altKey))
3368
- if (!external && tab && tab.actions && typeof tab.actions.openTab === 'function') {
3370
+ if (!external) {
3369
3371
  try {
3370
3372
  warmFavicon(reference)
3371
3373
  } catch (error) {
3372
3374
  /* caching is best-effort */
3373
3375
  }
3374
- try {
3375
- tab.actions.openTab(BROWSER_KIND, { params: { url: reference.url } })
3376
- return
3377
- } catch (error) {
3378
- /* fall through to the system browser */
3376
+ if (tab && tab.actions && typeof tab.actions.openTab === 'function') {
3377
+ try {
3378
+ tab.actions.openTab(BROWSER_KIND, { params: { url: reference.url } })
3379
+ return
3380
+ } catch (error) {
3381
+ /* fall through to the sidebar service */
3382
+ }
3383
+ }
3384
+ if (ctx && ctx.sidebarRight && typeof ctx.sidebarRight.openTab === 'function') {
3385
+ try {
3386
+ ctx.sidebarRight.openTab(BROWSER_KIND, { params: { url: reference.url } })
3387
+ return
3388
+ } catch (error) {
3389
+ /* fall through to the system browser */
3390
+ }
3379
3391
  }
3380
3392
  }
3381
3393
  try {
@@ -3384,7 +3396,7 @@ window.__ModuleLoader__.load({
3384
3396
  window.location.href = reference.url
3385
3397
  }
3386
3398
  },
3387
- [tab, warmFavicon],
3399
+ [tab, warmFavicon, ctx],
3388
3400
  )
3389
3401
 
3390
3402
  const copyList = React.useCallback(() => {
@@ -3448,6 +3460,56 @@ window.__ModuleLoader__.load({
3448
3460
  [],
3449
3461
  )
3450
3462
 
3463
+ // Manual icon refresh: same fetch path the automatic queue uses, but with a
3464
+ // visible spinner state, so "is the chain working?" is answerable in one click.
3465
+ const refreshIcon = React.useCallback(
3466
+ async (reference) => {
3467
+ const host = reference.host.replace(/^www\./, '')
3468
+ const domain = rootDomain(reference.host)
3469
+ setRefreshingUrls((current) => new Set(current).add(reference.url))
3470
+ try {
3471
+ const requestIcon = async (iconUrl) => {
3472
+ try {
3473
+ const url = new URL('/dsh-links/favicon', document.baseURI)
3474
+ url.searchParams.set('url', iconUrl)
3475
+ const response = await fetch(url)
3476
+ if (!response || !response.ok) return ''
3477
+ const payload = await response.json()
3478
+ return payload && typeof payload.dataUrl === 'string' && payload.dataUrl.indexOf('data:') === 0 ? payload.dataUrl : ''
3479
+ } catch (error) {
3480
+ return ''
3481
+ }
3482
+ }
3483
+ const candidates = [
3484
+ `https://${host}/favicon.ico`,
3485
+ `https://${host}/apple-touch-icon.png`,
3486
+ ]
3487
+ if (domain !== host) {
3488
+ candidates.push(`https://${domain}/favicon.ico`, `https://${domain}/apple-touch-icon.png`)
3489
+ }
3490
+ let dataUrl = ''
3491
+ for (const candidate of candidates) {
3492
+ dataUrl = await requestIcon(candidate)
3493
+ if (dataUrl !== '') break
3494
+ }
3495
+ if (dataUrl !== '') {
3496
+ setFaviconLibrary((current) => ({ ...current, [host]: dataUrl }))
3497
+ showNotice(tr('notice.iconUpdated'))
3498
+ } else {
3499
+ showNotice(tr('notice.iconMissing'))
3500
+ }
3501
+ } finally {
3502
+ setRefreshingUrls((current) => {
3503
+ if (!current.has(reference.url)) return current
3504
+ const next = new Set(current)
3505
+ next.delete(reference.url)
3506
+ return next
3507
+ })
3508
+ }
3509
+ },
3510
+ [showNotice],
3511
+ )
3512
+
3451
3513
  const copyReference = React.useCallback((reference) => {
3452
3514
  copyText(reference.url).then((ok) => {
3453
3515
  showNotice(ok ? '已复制链接' : '复制失败:浏览器没有授予剪贴板权限')
@@ -3918,6 +3980,7 @@ window.__ModuleLoader__.load({
3918
3980
  }),
3919
3981
  menuSeparator('sep-1'),
3920
3982
  h(MenuItem, { key: 'rename', label: tr('menu.rename'), onSelect: closeThen(openTitleEditor) }),
3983
+ h(MenuItem, { key: 'refresh-icon', label: tr('menu.refreshIcon'), onSelect: closeThen(refreshIcon) }),
3921
3984
  menuSeparator('sep-2'),
3922
3985
  h(MenuItem, { key: 'ignore-link', label: tr('menu.hideLink'), onSelect: closeThen(hideLinkOnly) }),
3923
3986
  h(MenuItem, {
@@ -4138,10 +4201,10 @@ window.__ModuleLoader__.load({
4138
4201
  // ------------------------------------------------------------------
4139
4202
 
4140
4203
  /** Required browser services: the tab registry and the keyed seat. */
4141
- const inject = ['slots', 'sidebarRightTabs', 'sessions', 'locale']
4204
+ const inject = ['slots', 'sidebarRightTabs', 'sidebarRight', 'sessions', 'locale']
4142
4205
 
4143
4206
  /** Build stamp shown in the footer, so a stale bundle is visible at a glance. */
4144
- const BUILD = 'v0.79.0'
4207
+ const BUILD = 'v0.80.0'
4145
4208
  /**
4146
4209
  * Translate one key and fill `{name}` placeholders.
4147
4210
  * @param {string} key - dictionary key.
@@ -4208,6 +4271,9 @@ window.__ModuleLoader__.load({
4208
4271
  'notice.unpinned': '已取消置顶',
4209
4272
  'notice.loadOlderFailed': '加载更早的消息失败',
4210
4273
  'notice.jumpMissing': '第 {turn} 轮还没加载出来,先向上滚动加载更早的历史',
4274
+ 'notice.iconUpdated': '图标已更新',
4275
+ 'notice.iconMissing': '没有取到这个网站的图标',
4276
+ 'menu.refreshIcon': '刷新图标',
4211
4277
  'button.close': '关闭',
4212
4278
  'row.clickToView': '点击查看',
4213
4279
  'row.turnLabel': '第 {turn} 轮对话',
@@ -4267,6 +4333,9 @@ window.__ModuleLoader__.load({
4267
4333
  'notice.unpinned': 'Unpinned',
4268
4334
  'notice.loadOlderFailed': 'Failed to load older messages',
4269
4335
  'notice.jumpMissing': 'Turn {turn} is not loaded yet — scroll up to load older history',
4336
+ 'notice.iconUpdated': 'Icon updated',
4337
+ 'notice.iconMissing': 'No icon found for this site',
4338
+ 'menu.refreshIcon': 'Refresh icon',
4270
4339
  'button.close': 'Close',
4271
4340
  'row.clickToView': 'Click to view',
4272
4341
  'row.turnLabel': 'Turn {turn}',
@@ -4453,7 +4522,7 @@ window.__ModuleLoader__.load({
4453
4522
  }
4454
4523
  }
4455
4524
  visit(snapshot, 0)
4456
- return [...ids].slice(0, 5)
4525
+ return [...ids].slice(0, 3)
4457
4526
  }
4458
4527
 
4459
4528
  const attach = (sessionId) => {
@@ -4493,7 +4562,10 @@ window.__ModuleLoader__.load({
4493
4562
  }
4494
4563
  const schedule = () => {
4495
4564
  if (tracker.timer !== null) return
4496
- tracker.timer = setTimeout(flush, 400)
4565
+ // Long debounce: the tracker only needs to be current when the panel
4566
+ // opens, not on every streamed token, and synchronous merges+storage
4567
+ // writes on the UI thread are what make streaming feel janky.
4568
+ tracker.timer = setTimeout(flush, 1500)
4497
4569
  }
4498
4570
  schedule()
4499
4571
  tracker.dispose = source.subscribe(schedule)
package/lib/index.js CHANGED
@@ -37,7 +37,7 @@ function withOutboundLock(task) {
37
37
  }
38
38
 
39
39
  /** Host build stamp, echoed to the panel so a stale bundle is detectable. */
40
- export const BUILD = 'v0.79.0'
40
+ export const BUILD = 'v0.80.0'
41
41
  /**
42
42
  * The desktop browser reaches the network through the system proxy; the Host's own
43
43
  * Node requests bypass it. Detect that proxy so probes can use the same route.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-links-panel",
3
- "version": "0.79.0",
3
+ "version": "0.80.0",
4
4
  "description": "Reference links panel for DeepSeek Harness: collects every URL of a conversation into the right sidebar with pins, turn counts, metadata and favicon caching. DSH 右栏「应用链接」面板。",
5
5
  "license": "MIT",
6
6
  "type": "module",