dsh-links-panel 0.78.1 → 0.79.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/lib/client.js +61 -32
- package/lib/index.js +1 -1
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -1172,7 +1172,7 @@ window.__ModuleLoader__.load({
|
|
|
1172
1172
|
* only from successful web_fetch results; nothing is fetched by the plugin itself.
|
|
1173
1173
|
*/
|
|
1174
1174
|
const FAVICON_LIBRARY_KEY = 'dsh-links.favicons.v1'
|
|
1175
|
-
const FAVICON_LIBRARY_LIMIT =
|
|
1175
|
+
const FAVICON_LIBRARY_LIMIT = 150
|
|
1176
1176
|
|
|
1177
1177
|
/** @returns {Record<string, string>} favicon URL by host or domain. */
|
|
1178
1178
|
function loadFaviconLibrary() {
|
|
@@ -2464,6 +2464,21 @@ window.__ModuleLoader__.load({
|
|
|
2464
2464
|
*/
|
|
2465
2465
|
/** Cached luminance analysis of one icon data URL. */
|
|
2466
2466
|
const ICON_TONE_CACHE = new Map()
|
|
2467
|
+
const ICON_TONE_CACHE_LIMIT = 300
|
|
2468
|
+
|
|
2469
|
+
/**
|
|
2470
|
+
* Remember one analysis without letting the cache grow unbounded.
|
|
2471
|
+
* @param {string} src - data URL key.
|
|
2472
|
+
* @param {object | null} tone - analysis result.
|
|
2473
|
+
* @returns {void}
|
|
2474
|
+
*/
|
|
2475
|
+
function rememberIconTone(src, tone) {
|
|
2476
|
+
if (ICON_TONE_CACHE.size >= ICON_TONE_CACHE_LIMIT) {
|
|
2477
|
+
const oldest = ICON_TONE_CACHE.keys().next()
|
|
2478
|
+
if (!oldest.done) ICON_TONE_CACHE.delete(oldest.value)
|
|
2479
|
+
}
|
|
2480
|
+
ICON_TONE_CACHE.set(src, tone)
|
|
2481
|
+
}
|
|
2467
2482
|
|
|
2468
2483
|
/**
|
|
2469
2484
|
* Average luminance and saturation of an icon, so a monochrome mark that would
|
|
@@ -2506,7 +2521,7 @@ window.__ModuleLoader__.load({
|
|
|
2506
2521
|
count += 1
|
|
2507
2522
|
}
|
|
2508
2523
|
const result = count === 0 ? null : { luma: luma / count, saturation: saturation / count }
|
|
2509
|
-
|
|
2524
|
+
rememberIconTone(src, result)
|
|
2510
2525
|
resolve(result)
|
|
2511
2526
|
} catch (error) {
|
|
2512
2527
|
resolve(null)
|
|
@@ -2608,6 +2623,38 @@ window.__ModuleLoader__.load({
|
|
|
2608
2623
|
return trf('time.daysAgo', { count: Math.floor(delta / 86400000) })
|
|
2609
2624
|
}
|
|
2610
2625
|
|
|
2626
|
+
/**
|
|
2627
|
+
* Throttle an event-stream snapshot so a streaming answer does not re-aggregate the
|
|
2628
|
+
* whole conversation on every token. Leading call is immediate; later ones land at
|
|
2629
|
+
* most once per `wait` milliseconds, and the last value always arrives.
|
|
2630
|
+
* @param {{entries: Array, hasMore: boolean}} snapshot - live session snapshot.
|
|
2631
|
+
* @param {number} wait - minimum interval in milliseconds.
|
|
2632
|
+
* @returns {{entries: Array, hasMore: boolean}} throttled snapshot.
|
|
2633
|
+
*/
|
|
2634
|
+
function useThrottledSnapshot(snapshot, wait) {
|
|
2635
|
+
const [value, setValue] = React.useState(snapshot)
|
|
2636
|
+
const stateRef = React.useRef({ last: 0, timer: null, pending: snapshot })
|
|
2637
|
+
React.useEffect(() => {
|
|
2638
|
+
const state = stateRef.current
|
|
2639
|
+
state.pending = snapshot
|
|
2640
|
+
const flush = () => {
|
|
2641
|
+
state.timer = null
|
|
2642
|
+
state.last = Date.now()
|
|
2643
|
+
setValue(state.pending)
|
|
2644
|
+
}
|
|
2645
|
+
const elapsed = Date.now() - state.last
|
|
2646
|
+
if (elapsed >= wait) flush()
|
|
2647
|
+
else if (state.timer === null) state.timer = setTimeout(flush, wait - elapsed)
|
|
2648
|
+
}, [snapshot, wait])
|
|
2649
|
+
React.useEffect(
|
|
2650
|
+
() => () => {
|
|
2651
|
+
if (stateRef.current.timer !== null) clearTimeout(stateRef.current.timer)
|
|
2652
|
+
},
|
|
2653
|
+
[],
|
|
2654
|
+
)
|
|
2655
|
+
return value
|
|
2656
|
+
}
|
|
2657
|
+
|
|
2611
2658
|
/**
|
|
2612
2659
|
* One mention row inside the “mentioned in N turns” dialog.
|
|
2613
2660
|
* @param {{item: object, active: boolean, onHover: Function, onSelect: Function}} props - row props.
|
|
@@ -3045,12 +3092,15 @@ window.__ModuleLoader__.load({
|
|
|
3045
3092
|
scanStoreRef.current = { cache: new Map(), callNames: new Map() }
|
|
3046
3093
|
}
|
|
3047
3094
|
// Rows = conversation items table (cached rows ∪ current window scan).
|
|
3095
|
+
// The stream is throttled: a long answer emits many events, and re-aggregating
|
|
3096
|
+
// on each of them is what made the panel feel heavy while the agent typed.
|
|
3097
|
+
const liveSnapshot = useThrottledSnapshot(snapshot, 300)
|
|
3048
3098
|
const all = React.useMemo(() => {
|
|
3049
3099
|
const store = scanStoreRef.current
|
|
3050
|
-
const scanned = aggregate(
|
|
3100
|
+
const scanned = aggregate(liveSnapshot.entries, store.cache, store.callNames, faviconHintsRef.current)
|
|
3051
3101
|
let newest = scannedSeqRef.current
|
|
3052
3102
|
let lowest = scannedMinSeqRef.current
|
|
3053
|
-
for (const entry of
|
|
3103
|
+
for (const entry of liveSnapshot.entries || []) {
|
|
3054
3104
|
const seq = entry && entry.event ? entry.event.seq : null
|
|
3055
3105
|
if (typeof seq !== 'number') continue
|
|
3056
3106
|
if (seq > newest) newest = seq
|
|
@@ -3061,7 +3111,7 @@ window.__ModuleLoader__.load({
|
|
|
3061
3111
|
if (!snapshot.hasMore) return scanned
|
|
3062
3112
|
if (linksCache.rows.length === 0) return scanned
|
|
3063
3113
|
return mergeRows(linksCache.rows, scanned)
|
|
3064
|
-
}, [
|
|
3114
|
+
}, [liveSnapshot, linksCache])
|
|
3065
3115
|
|
|
3066
3116
|
// Persist the favicon cache (debounced) and flush it when the panel unmounts, so
|
|
3067
3117
|
// switching conversations never loses icons that were just fetched.
|
|
@@ -3145,15 +3195,11 @@ window.__ModuleLoader__.load({
|
|
|
3145
3195
|
const candidates = [
|
|
3146
3196
|
`https://${target.host}/favicon.ico`,
|
|
3147
3197
|
`https://${target.host}/apple-touch-icon.png`,
|
|
3148
|
-
`https://${target.host}/favicon.png`,
|
|
3149
|
-
`https://${target.host}/favicon.svg`,
|
|
3150
3198
|
]
|
|
3151
3199
|
if (target.domain !== target.host) {
|
|
3152
3200
|
candidates.push(
|
|
3153
3201
|
`https://${target.domain}/favicon.ico`,
|
|
3154
3202
|
`https://${target.domain}/apple-touch-icon.png`,
|
|
3155
|
-
`https://${target.domain}/favicon.png`,
|
|
3156
|
-
`https://${target.domain}/favicon.svg`,
|
|
3157
3203
|
)
|
|
3158
3204
|
}
|
|
3159
3205
|
let found = ''
|
|
@@ -3290,34 +3336,14 @@ window.__ModuleLoader__.load({
|
|
|
3290
3336
|
}
|
|
3291
3337
|
const run = async () => {
|
|
3292
3338
|
try {
|
|
3293
|
-
const base = new URL('/dsh-links/meta', document.baseURI)
|
|
3294
|
-
base.searchParams.set('urls', JSON.stringify([reference.url]))
|
|
3295
|
-
const response = await fetch(base)
|
|
3296
|
-
let iconUrl = ''
|
|
3297
|
-
if (response && response.ok) {
|
|
3298
|
-
const payload = await response.json()
|
|
3299
|
-
const page = payload && payload.pages && payload.pages[reference.url]
|
|
3300
|
-
iconUrl = page && typeof page.icon === 'string' ? page.icon : ''
|
|
3301
|
-
}
|
|
3302
|
-
if (iconUrl !== '') {
|
|
3303
|
-
const dataUrl = await requestFavicon(iconUrl)
|
|
3304
|
-
if (dataUrl !== '') {
|
|
3305
|
-
setFaviconLibrary((current) => (current[host] ? current : { ...current, [host]: dataUrl }))
|
|
3306
|
-
return
|
|
3307
|
-
}
|
|
3308
|
-
}
|
|
3309
3339
|
const candidates = [
|
|
3310
3340
|
`https://${host}/favicon.ico`,
|
|
3311
3341
|
`https://${host}/apple-touch-icon.png`,
|
|
3312
|
-
`https://${host}/favicon.png`,
|
|
3313
|
-
`https://${host}/favicon.svg`,
|
|
3314
3342
|
]
|
|
3315
3343
|
if (domain !== host) {
|
|
3316
3344
|
candidates.push(
|
|
3317
3345
|
`https://${domain}/favicon.ico`,
|
|
3318
3346
|
`https://${domain}/apple-touch-icon.png`,
|
|
3319
|
-
`https://${domain}/favicon.png`,
|
|
3320
|
-
`https://${domain}/favicon.svg`,
|
|
3321
3347
|
)
|
|
3322
3348
|
}
|
|
3323
3349
|
for (const candidate of candidates) {
|
|
@@ -4115,7 +4141,7 @@ window.__ModuleLoader__.load({
|
|
|
4115
4141
|
const inject = ['slots', 'sidebarRightTabs', 'sessions', 'locale']
|
|
4116
4142
|
|
|
4117
4143
|
/** Build stamp shown in the footer, so a stale bundle is visible at a glance. */
|
|
4118
|
-
const BUILD = 'v0.
|
|
4144
|
+
const BUILD = 'v0.79.1'
|
|
4119
4145
|
/**
|
|
4120
4146
|
* Translate one key and fill `{name}` placeholders.
|
|
4121
4147
|
* @param {string} key - dictionary key.
|
|
@@ -4427,7 +4453,7 @@ window.__ModuleLoader__.load({
|
|
|
4427
4453
|
}
|
|
4428
4454
|
}
|
|
4429
4455
|
visit(snapshot, 0)
|
|
4430
|
-
return [...ids].slice(0,
|
|
4456
|
+
return [...ids].slice(0, 3)
|
|
4431
4457
|
}
|
|
4432
4458
|
|
|
4433
4459
|
const attach = (sessionId) => {
|
|
@@ -4467,7 +4493,10 @@ window.__ModuleLoader__.load({
|
|
|
4467
4493
|
}
|
|
4468
4494
|
const schedule = () => {
|
|
4469
4495
|
if (tracker.timer !== null) return
|
|
4470
|
-
tracker
|
|
4496
|
+
// Long debounce: the tracker only needs to be current when the panel
|
|
4497
|
+
// opens, not on every streamed token, and synchronous merges+storage
|
|
4498
|
+
// writes on the UI thread are what make streaming feel janky.
|
|
4499
|
+
tracker.timer = setTimeout(flush, 1500)
|
|
4471
4500
|
}
|
|
4472
4501
|
schedule()
|
|
4473
4502
|
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.
|
|
40
|
+
export const BUILD = 'v0.79.1'
|
|
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.
|
|
3
|
+
"version": "0.79.1",
|
|
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",
|