dsh-long-plugins 2.2.2 → 2.3.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/client/client.js CHANGED
@@ -1961,7 +1961,9 @@ window.__ModuleLoader__.load({
1961
1961
  if (!cancelled) setBalance(null);
1962
1962
  }
1963
1963
  };
1964
- load();
1964
+ // 首次拉取延迟到浏览器空闲(减轻首屏网络风暴), 60s 轮询不变
1965
+ const schedule = () => { try { if (window.requestIdleCallback) window.requestIdleCallback(() => load(), { timeout: 300 }); else setTimeout(load, 300) } catch (_) { load() } };
1966
+ schedule();
1965
1967
  const interval = setInterval(load, 60000);
1966
1968
  return () => {
1967
1969
  cancelled = true;
@@ -2003,7 +2005,9 @@ window.__ModuleLoader__.load({
2003
2005
  if (!cancelled) setCost(null);
2004
2006
  }
2005
2007
  };
2006
- load();
2008
+ // 首次拉取延迟到空闲(减轻首屏网络风暴), 60s 轮询不变
2009
+ const schedule = () => { try { if (window.requestIdleCallback) window.requestIdleCallback(() => load(), { timeout: 300 }); else setTimeout(load, 300) } catch (_) { load() } };
2010
+ schedule();
2007
2011
  const interval = setInterval(load, 60000);
2008
2012
  return () => {
2009
2013
  cancelled = true;
@@ -3133,24 +3137,51 @@ window.__ModuleLoader__.load({
3133
3137
  color: #ffffff !important;
3134
3138
  text-shadow: 0 1px 1px rgba(0,0,0,.5);
3135
3139
  }
3140
+ /* (移除 color/text-shadow 过渡: 会在收尾重渲染时短暂贴默认色导致"闪黑") */
3136
3141
  /* DSH 弹窗/浮层(提问/确认/重命名等):给不透明底+主题文字, 杜绝透明与会话内容重叠 */
3137
3142
  html[data-dsh-glass="on"] [class*="Radix"] [class*="Content"],
3138
3143
  html[data-dsh-glass="on"] [class*="DialogContent"],
3139
3144
  html[data-dsh-glass="on"] [class*="ModalContent"],
3140
3145
  html[data-dsh-glass="on"] [class*="overlay"] [class*="content"],
3141
3146
  html[data-dsh-glass="on"] [class*="_content"][class*="dialog"] {
3142
- background-color: var(--dsw-specific-input-major, #e8edf3) !important;
3147
+ background-color: color-mix(in srgb, var(--dsw-specific-input-major, #e8edf3) 76%, transparent) !important;
3143
3148
  color: var(--dsw-alias-label-primary) !important;
3144
- backdrop-filter: blur(8px) !important;
3145
- -webkit-backdrop-filter: blur(8px) !important;
3149
+ backdrop-filter: blur(var(--dsh-glass-blur, 16px)) !important;
3150
+ -webkit-backdrop-filter: blur(var(--dsh-glass-blur, 16px)) !important;
3146
3151
  }
3147
- /* DSH 提问/弹窗卡(M8wy4a_card):不透明底+原生文字, 避免透明重叠、白字 */
3148
- html[data-dsh-glass="on"] [class*="M8wy4a_card"],
3149
- html[data-dsh-glass="on"] [data-chain-overlay-fallback] [class*="card"] {
3150
- background-color: var(--dsw-specific-input-major, #e8edf3) !important;
3152
+ /* DSH 提问/弹窗卡(M8wy4a_card):不透明底+原生文字, 避免透明重叠、白字。
3153
+ 注意: 不能再用 [data-chain-overlay-fallback] [class*=card](会误匹配输入框 uV2eYG_card 加 blur 造成重影) */
3154
+ html[data-dsh-glass="on"] [class*="M8wy4a_card"] {
3155
+ background-color: color-mix(in srgb, var(--dsw-specific-input-major, #e8edf3) 76%, transparent) !important;
3156
+ color: var(--dsw-alias-label-primary) !important;
3157
+ backdrop-filter: blur(var(--dsh-glass-blur, 16px)) !important;
3158
+ -webkit-backdrop-filter: blur(var(--dsh-glass-blur, 16px)) !important;
3159
+ }
3160
+ /* 任务/todo 面板(lXshSW_root, 在 conversation.input.dock 内):不透明底+可读文字, 不透明与会话重叠 */
3161
+ html[data-dsh-glass="on"] [class*="lXshSW_root"],
3162
+ html[data-dsh-glass="on"] [class*="lXshSW_body"] {
3163
+ background-color: color-mix(in srgb, var(--dsw-specific-input-major, #e8edf3) 76%, transparent) !important;
3164
+ color: var(--dsw-alias-label-primary) !important;
3165
+ backdrop-filter: blur(var(--dsh-glass-blur, 16px)) !important;
3166
+ -webkit-backdrop-filter: blur(var(--dsh-glass-blur, 16px)) !important;
3167
+ }
3168
+ html[data-dsh-glass="on"] [class*="lXshSW_header"] [class*="lXshSW_title"],
3169
+ html[data-dsh-glass="on"] [class*="lXshSW_header"] [class*="lXshSW_progress"],
3170
+ html[data-dsh-glass="on"] [class*="lXshSW_item"] {
3171
+ color: var(--dsw-alias-label-primary) !important;
3172
+ }
3173
+ /* 排队消息/等待任务面板(_7yHdaG_dock/panel, 在 conversation.input.dock 内):不透明/磨砂底+可读文字 */
3174
+ html[data-dsh-glass="on"] [class*="_7yHdaG_panel"],
3175
+ html[data-dsh-glass="on"] [class*="_7yHdaG_dock"] {
3176
+ background-color: color-mix(in srgb, var(--dsw-specific-input-major, #e8edf3) 88%, transparent) !important;
3177
+ color: var(--dsw-alias-label-primary) !important;
3178
+ backdrop-filter: blur(var(--dsh-glass-blur, 16px)) !important;
3179
+ -webkit-backdrop-filter: blur(var(--dsh-glass-blur, 16px)) !important;
3180
+ }
3181
+ html[data-dsh-glass="on"] [class*="_7yHdaG_header"],
3182
+ html[data-dsh-glass="on"] [class*="_7yHdaG_preview"],
3183
+ html[data-dsh-glass="on"] [class*="_7yHdaG_row"] {
3151
3184
  color: var(--dsw-alias-label-primary) !important;
3152
- backdrop-filter: blur(8px) !important;
3153
- -webkit-backdrop-filter: blur(8px) !important;
3154
3185
  }
3155
3186
  /* 轮次/历史提问预览:默认隐藏(点开才显示),并去掉头部底色,避免底部露出白条 */
3156
3187
  .dsh-turn-preview:not(.open){display:none !important;}
@@ -3206,23 +3237,32 @@ window.__ModuleLoader__.load({
3206
3237
  }
3207
3238
  /* 输入框下拉/悬浮菜单(如 / 命令、@ 提及):背景给不透明随主题色,避免叠在会话磨砂卡上重叠看不清 */
3208
3239
  html[data-dsh-glass="on"] #root [class*="_menu"][class*="_menu"][class*="_menu"][class*="_menu"] {
3209
- background-color: var(--dsw-specific-input-major, #e8edf3) !important;
3240
+ background-color: color-mix(in srgb, var(--dsw-specific-input-major, #e8edf3) 76%, transparent) !important;
3210
3241
  background-color: color-mix(in srgb, var(--dsw-specific-input-major, #e8edf3) 97%, transparent) !important;
3211
3242
  backdrop-filter: none !important;
3212
3243
  -webkit-backdrop-filter: none !important;
3213
3244
  }
3214
- /* 输入框旁权限/功能选择下拉(盾牌 Full access 等 Radix 风格菜单):与 / 命令菜单同样处理 */
3215
- html[data-dsh-glass="on"] #root [class*="uV2eYG_card"] [class*="_root_"]:has([class*="_list_"]) {
3245
+ /* 输入框旁权限/功能选择下拉(盾牌 Full access 等):给不透明底+可读文字(高优先级, 压过 DSH 透明覆盖) */
3246
+ html[data-dsh-glass="on"] #root [class*="uV2eYG_card"] [class*="_root_"] [class*="_list_"],
3247
+ html[data-dsh-glass="on"] #root [class*="uV2eYG_card"] [class*="_root_"] [class*="_list_"] [class*="_viewport_"],
3248
+ html[data-dsh-glass="on"] #root [class*="uV2eYG_card"] [class*="_list_"] [class*="_viewport_"] [class*="_itemWrap_"] {
3216
3249
  background-color: var(--dsw-specific-input-major, #e8edf3) !important;
3217
- background-color: color-mix(in srgb, var(--dsw-specific-input-major, #e8edf3) 97%, transparent) !important;
3218
3250
  backdrop-filter: none !important;
3219
3251
  -webkit-backdrop-filter: none !important;
3220
3252
  }
3221
- html[data-dsh-glass="on"] #root [class*="uV2eYG_card"] [class*="_list_"] {
3253
+ html[data-dsh-glass="on"] #root [class*="uV2eYG_card"] [class*="_item_"],
3254
+ html[data-dsh-glass="on"] #root [class*="uV2eYG_card"] [class*="_itemLabel_"] {
3255
+ color: var(--dsw-alias-label-primary) !important;
3256
+ }
3257
+ /* 兜底: Radix 风格菜单/下拉(可能被渲染到 body 顶层) */
3258
+ html[data-dsh-glass="on"] [class*="Radix"] [class*="Menu"],
3259
+ html[data-dsh-glass="on"] [class*="radix-menu"],
3260
+ html[data-dsh-glass="on"] [class*="menuitem"],
3261
+ html[data-dsh-glass="on"] [role="menu"] {
3222
3262
  background-color: var(--dsw-specific-input-major, #e8edf3) !important;
3223
- background-color: color-mix(in srgb, var(--dsw-specific-input-major, #e8edf3) 97%, transparent) !important;
3224
3263
  backdrop-filter: none !important;
3225
3264
  -webkit-backdrop-filter: none !important;
3265
+ color: var(--dsw-alias-label-primary) !important;
3226
3266
  }
3227
3267
  /* 玻璃开启 + 浅色系统主题:加深弱化标签/元信息文字,避免浅灰字叠在浅磨砂卡上看不清 */
3228
3268
  @media (prefers-color-scheme: light) {
@@ -3307,14 +3347,16 @@ window.__ModuleLoader__.load({
3307
3347
  )
3308
3348
  }
3309
3349
  const GlassSettingsSection = () => {
3310
- const [state, setState] = React.useState({ loading: true, saving: false, error: '', cfg: null, bgImage: '' })
3350
+ const [state, setState] = React.useState({ loading: true, saving: false, error: '', cfg: null, bgImage: '', bgList: [] })
3311
3351
  const load = React.useCallback(async () => {
3312
3352
  setState((s) => ({ ...s, loading: true, error: '' }))
3313
3353
  try {
3314
3354
  const r = await fetch('/api/dsh-uploads/glass-config', { cache: 'no-store' })
3315
3355
  const b = await r.json()
3316
3356
  if (!r.ok) throw new Error(b.error || ('HTTP ' + r.status))
3317
- setState((s) => ({ ...s, loading: false, cfg: b.cfg, bgImage: b.bgImage || '' }))
3357
+ let list = []
3358
+ try { const rb = await fetch('/api/dsh-uploads/glass-backgrounds', { cache: 'no-store' }); const jb = await rb.json(); list = jb.list || [] } catch (_) {}
3359
+ setState((s) => ({ ...s, loading: false, cfg: b.cfg, bgImage: b.bgImageUrl || '', bgList: list }))
3318
3360
  } catch (e) { setState((s) => ({ ...s, loading: false, error: glassText(e) })) }
3319
3361
  }, [])
3320
3362
  React.useEffect(() => { load(); }, [load])
@@ -3337,9 +3379,16 @@ window.__ModuleLoader__.load({
3337
3379
  try {
3338
3380
  const cfg = state.cfg || {}
3339
3381
  const num = (v, fb) => ((typeof v === 'number' && Number.isFinite(v) && v >= 0) ? v : fb)
3382
+ // 背景图: 仅当用户上传了新图(data-URI)才传; 未改(URL)省略以保持引用; 清空传 '' 清除
3383
+ const sBg = state.bgImage
3384
+ let bgImageOut
3385
+ if (sBg === '') bgImageOut = ''
3386
+ else if (typeof sBg === 'string' && sBg.startsWith('data:image/')) bgImageOut = sBg
3387
+ else bgImageOut = undefined
3340
3388
  const payload = {
3341
3389
  enabled: !!cfg.enabled, blur: num(cfg.blur, 0),
3342
- bgImage: state.bgImage || cfg.bgImage || '', bgMask: num(cfg.bgMask, 0.28), bgColor: cfg.bgColor || '#1a2332', bgBlur: num(cfg.bgBlur, 0), bgOpacity: num(cfg.bgOpacity, 1),
3390
+ bgMask: num(cfg.bgMask, 0.28), bgColor: cfg.bgColor || '#1a2332', bgBlur: num(cfg.bgBlur, 0), bgOpacity: num(cfg.bgOpacity, 1),
3391
+ ...(bgImageOut !== undefined ? { bgImage: bgImageOut } : {}),
3343
3392
  bgTint: {
3344
3393
  light: { color: (cfg.bgTint?.light?.color) || '#1a2332', mask: num(cfg.bgTint?.light?.mask, 0.28) },
3345
3394
  dark: { color: (cfg.bgTint?.dark?.color) || '#1a2332', mask: num(cfg.bgTint?.dark?.mask, 0.28) },
@@ -3356,8 +3405,9 @@ window.__ModuleLoader__.load({
3356
3405
  const r = await fetch('/api/dsh-uploads/glass-config', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(payload) })
3357
3406
  const b = await r.json()
3358
3407
  if (!r.ok) throw new Error(b.error || ('HTTP ' + r.status))
3359
- window.__dshGlassApply && window.__dshGlassApply(payload)
3360
- setState((s) => ({ ...s, saving: false, cfg: { ...(s.cfg || {}), ...payload }, bgImage: payload.bgImage, error: '' }))
3408
+ const applyBg = (payload.bgImage && payload.bgImage.startsWith('data:')) ? payload.bgImage : ((state.bgImage && state.bgImage.startsWith('/')) ? state.bgImage : '')
3409
+ window.__dshGlassApply && window.__dshGlassApply({ ...payload, bgImage: applyBg })
3410
+ setState((s) => ({ ...s, saving: false, cfg: { ...(s.cfg || {}), ...payload }, bgImage: applyBg, error: '' }))
3361
3411
  } catch (e) { setState((s) => ({ ...s, saving: false, error: glassText(e) })) }
3362
3412
  }, [state])
3363
3413
  const cfg = state.cfg || {}
@@ -3370,13 +3420,12 @@ window.__ModuleLoader__.load({
3370
3420
  state.error ? React.createElement('div', { className: 'dsh-glass-error' }, state.error) : null,
3371
3421
  React.createElement('div', { className: 'dsh-glass-card' },
3372
3422
  React.createElement('div', { className: 'dsh-glass-row' },
3373
- React.createElement('label', null, '磨砂模糊强度'),
3374
- React.createElement('input', { type: 'range', className: 'dsh-glass-range', min: 0, max: 80, value: cfg.blur ?? 20, onChange: (e) => patch({ blur: Number(e.target.value) }) }),
3375
- React.createElement('span', { className: 'dsh-glass-val' }, `${Number(cfg.blur ?? 20)} px`),
3376
- ),
3377
- React.createElement('div', { className: 'dsh-glass-row' },
3378
- React.createElement('label', null, '共用背景图'),
3423
+ React.createElement('label', null, '背景图'),
3379
3424
  React.createElement('div', { className: 'dsh-glass-bg' }, glassImageInput(state.bgImage, (v) => setState((s) => ({ ...s, bgImage: v })))),
3425
+ state.bgList && state.bgList.length ? React.createElement('select', { className: 'dsh-glass-file', value: '', style: { marginLeft: 8 }, onChange: async (e) => {
3426
+ const name = e.target.value; if (!name) return
3427
+ try { const rb = await fetch('/api/dsh-uploads/glass-backgrounds?name=' + encodeURIComponent(name), { cache: 'no-store' }); const uri = await rb.text(); if (uri && uri.startsWith('data:image/')) setState((s) => ({ ...s, bgImage: uri })) } catch (_) {}
3428
+ } }, React.createElement('option', { value: '' }, '选择旧图…'), state.bgList.map((n, i) => React.createElement('option', { key: n, value: n }, '背景图 ' + (i + 1))) ) : null,
3380
3429
  ),
3381
3430
  React.createElement('div', { className: 'dsh-glass-card' },
3382
3431
  React.createElement('div', { className: 'dsh-glass-zone-title' }, '背景罩'),
@@ -3403,9 +3452,18 @@ window.__ModuleLoader__.load({
3403
3452
  React.createElement('span', { className: 'dsh-glass-val' }, `${Number(cfg.bgBlur ?? 0)} px`),
3404
3453
  ),
3405
3454
  React.createElement('div', { className: 'dsh-glass-row' },
3406
- React.createElement('label', null, '背景图透明度'),
3407
- React.createElement('input', { type: 'range', className: 'dsh-glass-range', min: 0, max: 1, step: 0.01, value: cfg.bgOpacity ?? 1, onChange: (e) => patch({ bgOpacity: Number(e.target.value) }) }),
3408
- React.createElement('span', { className: 'dsh-glass-val' }, `${Math.round((Number(cfg.bgOpacity ?? 1)) * 100)}%`),
3455
+ React.createElement('label', null, '背景透明度'),
3456
+ React.createElement('input', { type: 'range', className: 'dsh-glass-range', min: 0, max: 100, value: Math.round((1 - (Number(cfg.bgOpacity ?? 1))) * 100), onChange: (e) => patch({ bgOpacity: 1 - Number(e.target.value) / 100 }) }),
3457
+ React.createElement('span', { className: 'dsh-glass-val' }, `${Math.round((1 - (Number(cfg.bgOpacity ?? 1))) * 100)}%`),
3458
+ ),
3459
+ React.createElement('div', { className: 'dsh-glass-card' },
3460
+ React.createElement('div', { className: 'dsh-glass-zone-title' }, '面板'),
3461
+ React.createElement('div', { className: 'dsh-glass-row' },
3462
+ React.createElement('label', null, '面板磨砂'),
3463
+ React.createElement('input', { type: 'range', className: 'dsh-glass-range', min: 0, max: 80, value: cfg.blur ?? 20, onChange: (e) => patch({ blur: Number(e.target.value) }) }),
3464
+ React.createElement('span', { className: 'dsh-glass-val' }, `${Number(cfg.blur ?? 20)} px`),
3465
+ ),
3466
+ React.createElement('p', { className: 'dsh-glass-note' }, '作用于弹窗/任务卡等面板的磨砂玻璃强度(0 = 关闭磨砂)。'),
3409
3467
  ),
3410
3468
  React.createElement('div', { className: 'dsh-glass-card' },
3411
3469
  React.createElement('div', { className: 'dsh-glass-zone-title' }, '输入框'),
@@ -3419,13 +3477,13 @@ window.__ModuleLoader__.load({
3419
3477
  React.createElement('button', { type: 'button', style: { marginLeft: 8, fontSize: 12 }, onClick: () => patchInput(t, { color: '', opacity: 1 }) }, '原生'),
3420
3478
  ),
3421
3479
  React.createElement('div', { className: 'dsh-glass-row' },
3422
- React.createElement('label', null, '不透明度'),
3423
- React.createElement('input', { type: 'range', className: 'dsh-glass-range', min: 0, max: 1, step: 0.01, value: (typeof tb.opacity === 'number') ? tb.opacity : 1, onChange: (e) => patchInput(t, { opacity: Number(e.target.value) }) }),
3424
- React.createElement('span', { className: 'dsh-glass-val' }, `${Math.round((typeof tb.opacity === 'number' ? tb.opacity : 1) * 100)}%`),
3480
+ React.createElement('label', null, '透明度'),
3481
+ React.createElement('input', { type: 'range', className: 'dsh-glass-range', min: 0, max: 100, value: Math.round((1 - (typeof tb.opacity === 'number' ? tb.opacity : 1)) * 100), onChange: (e) => patchInput(t, { opacity: 1 - Number(e.target.value) / 100 }) }),
3482
+ React.createElement('span', { className: 'dsh-glass-val' }, `${Math.round((1 - (typeof tb.opacity === 'number' ? tb.opacity : 1)) * 100)}%`),
3425
3483
  ),
3426
3484
  )
3427
3485
  }),
3428
- React.createElement('p', { className: 'dsh-glass-note' }, '浅色主题与深色主题可分别设置。100% = 实底不透明,越低越透出背景图;"原生" = 跟随该主题的 DSH 原生色。文字颜色按背景亮度自动配深/浅,保证可读。'),
3486
+ React.createElement('p', { className: 'dsh-glass-note' }, '浅色主题与深色主题可分别设置。0% = 全不透明,100% = 全透明;"原生" = 跟随该主题的 DSH 原生色。文字颜色按背景亮度自动配深/浅,保证可读。'),
3429
3487
  ),
3430
3488
  React.createElement('p', { className: 'dsh-glass-note' }, '提示:背景图铺满整页(可加背景图罩);背景图模糊/透明度单独可调;会话区使用自己的罩色、罩强度与透明度;左栏/顶部透出背景图;输入框颜色/不透明度可单独设置。'),
3431
3489
  React.createElement('div', { className: 'dsh-glass-actions' },
@@ -3563,18 +3621,15 @@ window.__ModuleLoader__.load({
3563
3621
  if (isInSkip(root)) return
3564
3622
  root.querySelectorAll('div,span,button,a,label,p,li,h1,h2,h3,h4').forEach((el) => {
3565
3623
  if (isInSkip(el)) return
3566
- const tag = el.tagName
3567
- const hasText = el.textContent && el.textContent.trim()
3568
- if (!hasText) return
3624
+ if (el.dataset && el.dataset.dshgl) return // 已处理过, 跳过
3569
3625
  const ownText = Array.from(el.childNodes).some((n) => n.nodeType === 3 && n.textContent.trim())
3570
- const textLike = /^(BUTTON|A|SPAN|LABEL|LI|P|H1|H2|H3|H4)$/.test(tag)
3626
+ const textLike = /^(BUTTON|A|SPAN|LABEL|LI|P|H1|H2|H3|H4)$/.test(el.tagName)
3571
3627
  if (!textLike && !ownText) return
3572
- // 跳过有盒子背景的元素(badge/徽章/药丸等, deepseek 的 HARNESS 徽章)
3573
- // 这类文字颜色应随其盒子背景走主题, 不强制白字(否则白底徽章+白字不可见)
3574
- const bgbg = getComputedStyle(el).backgroundColor
3575
- if (bgbg && bgbg !== 'rgba(0, 0, 0, 0)' && bgbg !== 'transparent') return
3628
+ // 跳过有盒子背景的元素(用类名判断, 不再 getComputedStyle 以免每元素强制样式重算导致卡顿)
3629
+ if (/bubble|badge|chip|tag|pill|code-block|md-code|n_block|n_banner|n_plain|button/i.test(el.className)) return
3576
3630
  el.style.setProperty('color', '#ffffff', 'important')
3577
3631
  el.style.setProperty('text-shadow', '0 1px 1px rgba(0,0,0,.5)')
3632
+ if (el.dataset) el.dataset.dshgl = '1'
3578
3633
  })
3579
3634
  // 图标也变白(随 currentColor),用于工作区文件夹等单色图标;保留显式品牌色
3580
3635
  root.querySelectorAll('svg, [class*="icon"], [class*="Icon"]').forEach((el) => {
@@ -3599,15 +3654,14 @@ window.__ModuleLoader__.load({
3599
3654
  if (!scroll) return
3600
3655
  scroll.querySelectorAll('div,span,p,li,pre,code,button,a,label,h1,h2,h3,h4').forEach((el) => {
3601
3656
  if (isInSkip(el)) return
3602
- const hasText = el.textContent && el.textContent.trim()
3603
- if (!hasText) return
3657
+ if (el.dataset && el.dataset.dshgl) return // 已处理过, 跳过(只处理新内容)
3604
3658
  const ownText = Array.from(el.childNodes).some((n) => n.nodeType === 3 && n.textContent.trim())
3605
3659
  const textLike = /^(BUTTON|A|SPAN|LABEL|LI|P|H1|H2|H3|H4|PRE|CODE)$/.test(el.tagName)
3606
3660
  if (!textLike && !ownText) return
3607
- const bgbg = getComputedStyle(el).backgroundColor
3608
- if (bgbg && bgbg !== 'rgba(0, 0, 0, 0)' && bgbg !== 'transparent') return
3661
+ if (/bubble|badge|chip|tag|pill|code-block|md-code|n_block|n_banner|n_plain/i.test(el.className)) return
3609
3662
  el.style.setProperty('color', '#ffffff', 'important')
3610
3663
  el.style.setProperty('text-shadow', '0 1px 1px rgba(0,0,0,.5)')
3664
+ if (el.dataset) el.dataset.dshgl = '1'
3611
3665
  })
3612
3666
  // 在白字底上, 给过程内容配色区分, Error 用红色。
3613
3667
  // 文字常在标签的子元素里(白字已把它设白), 故连同文本子元素一起上色。
@@ -3695,13 +3749,16 @@ window.__ModuleLoader__.load({
3695
3749
  const fg = dark ? '#f2f6fa' : '#1a2332'
3696
3750
  // 只匹配提问/弹窗卡(M8wy4a_card),避免误匹配输入框 uV2eYG_card(含"card")导致 blur 重影
3697
3751
  document.querySelectorAll('[class*="M8wy4a_card"]').forEach((el) => {
3698
- el.style.setProperty('background-color', bg, 'important')
3752
+ const rgb = bg
3753
+ el.style.setProperty('background-color', `color-mix(in srgb, ${rgb} 76%, transparent)`, 'important')
3699
3754
  el.style.setProperty('color', fg, 'important')
3700
- el.style.setProperty('backdrop-filter', 'blur(8px)')
3755
+ el.style.setProperty('backdrop-filter', 'blur(var(--dsh-glass-blur, 20px))')
3701
3756
  })
3702
3757
  }
3703
3758
  // 主题相关的东西统一重算:背景罩 + 输入框。切主题时由 observer/mq 触发。
3704
3759
  // 写入前先断开观察器,写完再重连,避免自触发造成死循环。
3760
+ // 把非关键的"内容配色/白字"延迟到浏览器空闲时执行, 让页面先渲染出来(优化刷新加载时间)
3761
+ const defer = (fn) => { try { if (window.requestIdleCallback) window.requestIdleCallback(fn, { timeout: 120 }); else setTimeout(fn, 0) } catch (_) { fn() } }
3705
3762
  const applyTheme = () => {
3706
3763
  const obs = window.__dshGlassStyleObs
3707
3764
  if (obs) obs.disconnect()
@@ -3712,9 +3769,7 @@ window.__ModuleLoader__.load({
3712
3769
  const v = `rgba(${hexToRgb(bt.color)},${bt.mask})`
3713
3770
  root.style.setProperty('--dsh-glass-bg-zone', v)
3714
3771
  applyInputCard()
3715
- applyChromeText()
3716
- applySessionText()
3717
- fixDialogs()
3772
+ defer(() => { applyChromeText(); applySessionText(); fixDialogs() })
3718
3773
  } finally {
3719
3774
  if (obs) obs.observe(document.documentElement, { attributes: true, attributeFilter: ['style', 'class'] })
3720
3775
  }
@@ -3733,6 +3788,8 @@ window.__ModuleLoader__.load({
3733
3788
  bgimg.style.backgroundSize = 'cover'
3734
3789
  bgimg.style.backgroundPosition = 'center'
3735
3790
  bgimg.style.backgroundRepeat = 'no-repeat'
3791
+ // 背景图模糊 = 背景图模糊(bgBlur) + 磨砂模糊强度(blur),两者叠加让背景图一起磨砂
3792
+ // 背景图模糊 = 仅背景图模糊(bgBlur);面板磨砂(blur)作用于弹窗/任务面板的 backdrop-filter
3736
3793
  const bblur = Math.max(0, Math.min(80, Number.isFinite(Number(c.bgBlur)) ? Number(c.bgBlur) : 0))
3737
3794
  bgimg.style.filter = bblur > 0 ? `blur(${bblur}px)` : 'none'
3738
3795
  const bop = Math.max(0, Math.min(1, Number.isFinite(Number(c.bgOpacity)) ? Number(c.bgOpacity) : 1))
@@ -3752,7 +3809,7 @@ window.__ModuleLoader__.load({
3752
3809
  // 侧边栏/顶栏 DOM 变化(会话列表更新等)时重算左栏/顶栏文字,防抖 120ms
3753
3810
  let chromeTimer = null
3754
3811
  const chromeRoots = [...document.querySelectorAll('#root [class*="sidebarCol"],#root [class*="wSkVaW_header"]')]
3755
- const chromeObs = new MutationObserver(() => { if (chromeTimer) clearTimeout(chromeTimer); chromeTimer = setTimeout(applyChromeText, 120) })
3812
+ const chromeObs = new MutationObserver(() => { if (chromeTimer) clearTimeout(chromeTimer); chromeTimer = setTimeout(applyChromeText, 30) })
3756
3813
  chromeRoots.forEach((r) => chromeObs.observe(r, { childList: true, subtree: true, characterData: true }))
3757
3814
  // 输入框卡片(uV2eYG_card/root)可能在启动后才挂载(会话/输入区加载晚),
3758
3815
  // 启动那一刻 applyInputCard 常找不到框 → 需刷新才生效。监听新增卡片节点时重算,防抖 100ms。
@@ -3770,7 +3827,7 @@ window.__ModuleLoader__.load({
3770
3827
  const sessionObs = new MutationObserver((muts) => {
3771
3828
  let hit = false
3772
3829
  for (const m of muts) { for (const node of m.addedNodes) { if (node.nodeType === 1 && inScroll(node)) { hit = true; break } } if (hit) break }
3773
- if (hit) { if (sessionTimer) clearTimeout(sessionTimer); sessionTimer = setTimeout(() => { applySessionText(); fixDialogs() }, 150) }
3830
+ if (hit) { if (sessionTimer) clearTimeout(sessionTimer); sessionTimer = setTimeout(() => { applySessionText(); fixDialogs() }, 20) }
3774
3831
  })
3775
3832
  sessionObs.observe(document.body, { childList: true, subtree: true })
3776
3833
  }
@@ -3791,9 +3848,11 @@ window.__ModuleLoader__.load({
3791
3848
  }, 'dsh-long-plugins: glass applier')
3792
3849
  ctx.slots.inject('settings.section', () => ctx.slots.register({ name: 'settings.section', id: 'glass-ui', order: 40, label: 'RA-Span' }, GlassSettingsSection))
3793
3850
  ctx.effect(() => {
3794
- fetch('/api/dsh-uploads/glass-config', { cache: 'no-store' }).then((r) => r.json()).then((b) => {
3795
- if (b && b.cfg) { window.__dshGlassApply && window.__dshGlassApply({ ...b.cfg, bgImage: b.bgImage }) }
3796
- }).catch(() => {})
3851
+ // 玻璃配置/背景图历史拉取延迟到空闲, 减轻首屏网络风暴(背景没用玻璃时几乎零成本)
3852
+ const loadGlass = () => { fetch('/api/dsh-uploads/glass-config', { cache: 'no-store' }).then((r) => r.json()).then((b) => {
3853
+ if (b && b.cfg) { window.__dshGlassApply && window.__dshGlassApply({ ...b.cfg, bgImage: b.bgImageUrl || '' }) }
3854
+ }).catch(() => {}) }
3855
+ try { if (window.requestIdleCallback) window.requestIdleCallback(loadGlass, { timeout: 500 }); else setTimeout(loadGlass, 500) } catch (_) { loadGlass() }
3797
3856
  }, 'dsh-long-plugins: glass boot')
3798
3857
  },
3799
3858
  }
package/dsh.plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-long-plugins",
3
3
  "description": "Merged DSH web plugins: upload manager (drag-and-drop attach any file to the message), workspace output files, skill docs, account balance, Markdown-to-Word (md2docx), and polished file preview (Word & PowerPoint real preview with zoom/pan, rendered Markdown), plus an auto-repair install for DSH core patches (reverse-proxy WebSocket heartbeat, upgrade relink). | 一个插件整合 DSH Web 的常用增强:上传管理(拖放上传:拖任意文件到会话框直接附加)、工作区「输出文件」面板、技能文档浏览、账户余额显示、Markdown 转 Word(md2docx)、Word/PowerPoint 真实预览(可缩放、翻页);并自带修复安装,自动重连 DSH 核心补丁(反代 WebSocket 心跳、升级重连)。",
4
- "version": "2.2.2",
4
+ "version": "2.3.0",
5
5
  "entry": {
6
6
  "name": "dsh-long-plugins",
7
7
  "inject": [
package/lib/index.js CHANGED
@@ -1631,15 +1631,49 @@ window.addEventListener('resize',function(){ try{ if(fitMode) zoomFit(); else ap
1631
1631
  };
1632
1632
  const glassDir = resolve(homedir(), ".dsh-long-plugins");
1633
1633
  const glassFile = join(glassDir, "glass.json");
1634
+ const bgDir = join(glassDir, "backgrounds");
1635
+ const currentBgFile = join(bgDir, "current.uri");
1636
+ // 背景图文件引用: 配置里 bgImage 为 "current"(存 bgDir/current.uri) 或 ""(无) ;不再内嵌大图 data-URI
1637
+ const decodeDataUri = (s) => { const m = /^data:(image\/[a-zA-Z0-9.+-]+);base64,([A-Za-z0-9+/=]+)$/.exec(String(s || "")); return m ? { mime: m[1], base64: m[2] } : null; };
1638
+ const bgImageUrl = (cfg) => (cfg.bgImage === "current") ? "/api/dsh-uploads/glass-background" : "";
1639
+ // 只保留当前背景图(current.uri), 删除其它历史图片文件(旧图清理)
1640
+ async function pruneBackgrounds() {
1641
+ try {
1642
+ const names = await readdir(bgDir);
1643
+ for (const n of names) {
1644
+ if (n === "current.uri" || !n.endsWith(".uri")) continue;
1645
+ try { await unlink(join(bgDir, n)); } catch (_) {}
1646
+ }
1647
+ } catch (_) {}
1648
+ }
1634
1649
  async function readGlassJSON() {
1635
- try { const raw = JSON.parse(await readFile(glassFile, "utf8")); return { ...GLASS_DEFAULTS, ...(raw && typeof raw === "object" ? raw : {}) }; }
1636
- catch { return { ...GLASS_DEFAULTS }; }
1650
+ try {
1651
+ const raw = JSON.parse(await readFile(glassFile, "utf8"));
1652
+ const cfg = { ...GLASS_DEFAULTS, ...(raw && typeof raw === "object" ? raw : {}) };
1653
+ // 迁移旧版: bgImage 是内嵌 data-URI(大图) → 存成文件 + 改引用
1654
+ if (typeof cfg.bgImage === "string" && cfg.bgImage.startsWith("data:image/")) {
1655
+ try {
1656
+ await mkdir(bgDir, { recursive: true, mode: 0o700 });
1657
+ await writeFile(currentBgFile, cfg.bgImage, "utf8");
1658
+ cfg.bgImage = "current";
1659
+ await writeFile(glassFile, JSON.stringify(cfg, null, 2), "utf8");
1660
+ } catch (_) {}
1661
+ }
1662
+ return cfg;
1663
+ } catch { return { ...GLASS_DEFAULTS }; }
1637
1664
  }
1638
1665
  const glassConfig = async (req, res) => {
1639
1666
  try {
1640
1667
  requireTrusted(req);
1641
1668
  if (req.method === "GET" || req.method === "HEAD") {
1642
1669
  const cfg = await readGlassJSON();
1670
+ // 背景图只返回"是否存在 + 图片 URL", 不再返回内嵌大图 data-URI(减小响应)
1671
+ let imgMeta = { present: false, bytes: 0 };
1672
+ let imgUrl = "";
1673
+ if (cfg.bgImage === "current") {
1674
+ imgUrl = "/api/dsh-uploads/glass-background";
1675
+ try { imgMeta = { present: true, bytes: (await stat(currentBgFile)).size || 0 }; } catch (_) { imgMeta = { present: true, bytes: 0 }; }
1676
+ }
1643
1677
  sendJson(res, 200, {
1644
1678
  found: true,
1645
1679
  cfg: {
@@ -1649,9 +1683,11 @@ window.addEventListener('resize',function(){ try{ if(fitMode) zoomFit(); else ap
1649
1683
  inputBox: cfg.inputBox || { light: { color: "", opacity: 1 }, dark: { color: "", opacity: 1 } },
1650
1684
  bgTint: cfg.bgTint || { light: { color: "#1a2332", mask: 0.28 }, dark: { color: "#1a2332", mask: 0.28 } },
1651
1685
  bgColor: cfg.bgColor || "#1a2332", bgMask: cfg.bgMask ?? 0.28,
1652
- bgImage: cfg.bgImage ? { present: true, bytes: Math.round((cfg.bgImage.length * 3) / 4) } : { present: false },
1686
+ bgBlur: cfg.bgBlur ?? 0, bgOpacity: cfg.bgOpacity ?? 1,
1687
+ bgImage: imgMeta,
1653
1688
  },
1654
- bgImage: cfg.bgImage,
1689
+ bgImage: imgMeta,
1690
+ bgImageUrl: imgUrl,
1655
1691
  });
1656
1692
  return;
1657
1693
  }
@@ -1659,6 +1695,19 @@ window.addEventListener('resize',function(){ try{ if(fitMode) zoomFit(); else ap
1659
1695
  const body = await readJsonBody(req, 4 * 1024 * 1024);
1660
1696
  const current = await readGlassJSON();
1661
1697
  const next = { ...current, ...sanitizeGlass(typeof body === "object" && body !== null ? body : {}) };
1698
+ // 背景图改为"文件 + 引用": 用户上传的 data-URI 存入 current.uri, 配置只存 "current" 引用
1699
+ if (typeof next.bgImage === "string" && next.bgImage.startsWith("data:image/")) {
1700
+ try {
1701
+ await mkdir(bgDir, { recursive: true, mode: 0o700 });
1702
+ await writeFile(currentBgFile, next.bgImage, "utf8");
1703
+ next.bgImage = "current";
1704
+ } catch (_) { next.bgImage = ""; }
1705
+ } else if ("bgImage" in body && body.bgImage === "") {
1706
+ next.bgImage = ""; // 显式清除
1707
+ try { await unlink(currentBgFile); } catch (_) {}
1708
+ }
1709
+ // 只保留当前背景图, 删除历史旧图
1710
+ await pruneBackgrounds();
1662
1711
  await mkdir(glassDir, { recursive: true, mode: 0o700 });
1663
1712
  await writeFile(glassFile, JSON.stringify(next, null, 2), "utf8");
1664
1713
  sendJson(res, 200, { ok: true, saved: true });
@@ -1669,8 +1718,44 @@ window.addEventListener('resize',function(){ try{ if(fitMode) zoomFit(); else ap
1669
1718
  sendError(res, error, onError);
1670
1719
  }
1671
1720
  };
1721
+ // 背景图历史(供设置面板"选择旧图"):GET 列出,GET ?name= 返回对应 data-URI
1722
+ const glassBackgrounds = async (req, res) => {
1723
+ try {
1724
+ requireTrusted(req);
1725
+ const url = new URL(req.url, "http://x");
1726
+ const name = url.searchParams.get("name");
1727
+ if (name) {
1728
+ if (!/^[\w.:-]+\.uri$/.test(name)) { sendError(res, new Error("bad name"), onError); return; }
1729
+ try { const buf = await readFile(join(bgDir, name)); res.writeHead(200, { "content-type": "text/plain; charset=utf-8", "cache-control": "no-store" }); res.end(buf); return; }
1730
+ catch { sendJson(res, 404, { error: "not found" }); return; }
1731
+ }
1732
+ let list = [];
1733
+ // 只保留当前背景图, 删除历史旧图; 列表排除当前图
1734
+ try { await pruneBackgrounds(); } catch (_) {}
1735
+ try { list = (await readdir(bgDir)).filter((n) => n.endsWith(".uri") && n !== "current.uri").sort().reverse().slice(0, 30); } catch (_) {}
1736
+ sendJson(res, 200, { list });
1737
+ } catch (error) {
1738
+ sendError(res, error, onError);
1739
+ }
1740
+ };
1741
+ // 返回当前背景图(文件存的 data-URI 解码为真实图片)
1742
+ const glassBackground = async (req, res) => {
1743
+ try {
1744
+ requireTrusted(req);
1745
+ const cfg = await readGlassJSON();
1746
+ if (cfg.bgImage !== "current") { sendJson(res, 404, { error: "no background" }); return; }
1747
+ const buf = await readFile(currentBgFile, "utf8");
1748
+ const d = decodeDataUri(buf);
1749
+ if (!d) { sendJson(res, 404, { error: "bad image" }); return; }
1750
+ const bytes = Buffer.from(d.base64, "base64");
1751
+ res.writeHead(200, { "content-type": d.mime, "cache-control": "no-store", "x-content-type-options": "nosniff" });
1752
+ res.end(bytes);
1753
+ } catch (error) {
1754
+ sendError(res, error, onError);
1755
+ }
1756
+ };
1672
1757
 
1673
- return { root, maxFileBytes, totalMaxBytes, api, download, preview, workspaceList, workspaceFile, workspacePreview, workspaceBrowse, workspaceDelete, workspaceRename, workspaceSave, docxPreviewPage, docxPreviewAsset, pptxPreviewPage, xlsxPreviewPage, xlsxPreviewAsset, glassConfig };
1758
+ return { root, maxFileBytes, totalMaxBytes, api, download, preview, workspaceList, workspaceFile, workspacePreview, workspaceBrowse, workspaceDelete, workspaceRename, workspaceSave, docxPreviewPage, docxPreviewAsset, pptxPreviewPage, xlsxPreviewPage, xlsxPreviewAsset, glassConfig, glassBackgrounds, glassBackground };
1674
1759
  }
1675
1760
 
1676
1761
  /** 人类可读文件大小。 */
@@ -2575,6 +2660,18 @@ export async function apply(ctx, config = {}) {
2575
2660
  handler: handlers.glassConfig,
2576
2661
  }), "dsh-long-plugins: glass-ui config route");
2577
2662
 
2663
+ ctx.effect(() => ctx.webServer.register({
2664
+ kind: "exact",
2665
+ path: "/api/dsh-uploads/glass-backgrounds",
2666
+ handler: handlers.glassBackgrounds,
2667
+ }), "dsh-long-plugins: glass-ui backgrounds route");
2668
+
2669
+ ctx.effect(() => ctx.webServer.register({
2670
+ kind: "exact",
2671
+ path: "/api/dsh-uploads/glass-background",
2672
+ handler: handlers.glassBackground,
2673
+ }), "dsh-long-plugins: glass-ui background image route");
2674
+
2578
2675
  ctx.effect(() => ctx.webServer.register({
2579
2676
  kind: "exact",
2580
2677
  path: "/api/dsh-uploads/workspace-browse",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-long-plugins",
3
- "version": "2.2.2",
3
+ "version": "2.3.0",
4
4
  "description": "Merged DSH web plugins: upload manager (drag-and-drop attach any file to the message), workspace output files, skill docs, account balance, Markdown-to-Word (md2docx), and polished file preview (Word & PowerPoint real preview with zoom/pan, rendered Markdown), plus an auto-repair install for DSH core patches (reverse-proxy WebSocket heartbeat, upgrade relink). | 一个插件整合 DSH Web 的常用增强:上传管理(拖放上传:拖任意文件到会话框直接附加)、工作区「输出文件」面板、技能文档浏览、账户余额显示、Markdown 转 Word(md2docx)、Word/PowerPoint 真实预览(可缩放、翻页);并自带修复安装,自动重连 DSH 核心补丁(反代 WebSocket 心跳、升级重连)。",
5
5
  "type": "module",
6
6
  "license": "MIT",