dsh-long-plugins 2.2.0 → 2.2.2

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/README.md CHANGED
@@ -124,6 +124,24 @@ MIT
124
124
 
125
125
  ## Changelog / 更新记录
126
126
 
127
+ ### v2.2.1
128
+
129
+ 修复输入框实底、浅色主题可读性问题,并为输入框、背景罩新增浅色/深色主题的分套设置。
130
+
131
+ 🐛 Bug 修复
132
+
133
+ - 输入框实底跟随主题:此前用操作系统深色偏好判断,导致浅色主题下黑底黑字。改为读取 DSH 真实主题(`<html>` 上的 `color-scheme`),浅色 = 白底深字、深色 = 深底浅字,切换主题自动实时重算。
134
+ - 浅色主题可读性:统一桌面浅色下,气泡、工具调用名(Bash 等)、推理(think)标签、底部状态/统计行文字在背景图上过浅看不清。已按主题提高对比度并加描边;状态/统计行改用稳定的 `data-slot` 槽位定位(不再依赖会变的混淆类名),提高升级稳定性。
135
+
136
+ ✨ 新增功能
137
+
138
+ - 输入框设置(设置面板新增"输入框"区):浅色主题、深色主题可分别设置背景颜色与不透明度,支持一键"恢复原生"。
139
+ - 背景罩设置(设置面板新增"背景罩"区):浅色主题、深色主题可分别设置罩色与罩强度,切换主题自动应用对应的一套。
140
+
141
+ 🎨 视觉 / 交互
142
+
143
+ - 左栏「新会话」按钮去掉白/黑实底边框,文字改为跟随侧边栏的白字+描边,更好融入背景。
144
+
127
145
  ### v2.2.0
128
146
 
129
147
  界面优化与重构
package/client/client.js CHANGED
@@ -974,8 +974,6 @@ window.__ModuleLoader__.load({
974
974
  const CSS = `
975
975
  @font-face{font-family:DshChipCellInput;src:url(data:font/ttf;base64,AAEAAAAKAIAAAwAgT1MvMkT8SmIAAAEoAAAAYGNtYXAADQBPAAABkAAAADRnbHlmAAAAAAAAAcwAAAABaGVhZCwtPGoAAACsAAAANmhoZWEDIg7bAAAA5AAAACRobXR4EZQAAAAAAYgAAAAIbG9jYQAAAAAAAAHEAAAABm1heHAAAwACAAABCAAAACBuYW1lvljk2gAAAdAAAABscG9zdNNweNQAAAI8AAAALQABAAAAAQAAdia1tV8PPPUAAwPoAAAAAOaLfcUAAAAA5ot9xQAAAAAAAAAAAAAAAwACAAAAAAAAAAEAAAMg/zgAAA+gAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAACAAEAAAACAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAwjKAZAABQAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAPz8/PwAA//z//AMg/zgAAAMgAMgAAAAAAAAAAAAAAAAAAAAgAAAB9AAAAAAAAAAAAAIAAAADAAAAFAADAAEAAAAUAAQAIAAAAAQABAABAAD//P//AAD//P//AAUAAQAAAAAAAAAAAAAAAAAAAAAAAAAEADYAAQAAAAAAAQALAAAAAQAAAAAAAgAHAAsAAwABBAkAAQAWABIAAwABBAkAAgAOAChEc2hDaGlwQ2VsbFJlZ3VsYXIARABzAGgAQwBoAGkAcABDAGUAbABsAFIAZQBnAHUAbABhAHIAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAABAgZvYmpyZXAAAAA=)format("truetype")}
976
976
  .uV2eYG_input,.uV2eYG_mirror{font-family:"DshChipCellInput",var(--dsw-font-family)!important}
977
- /* 输入区随系统深/浅自动切换(原生系统色,文字/背景同源始终匹配),避免深色下白字配浅底 */
978
- .uV2eYG_input{color-scheme:light dark;background-color:Canvas!important;color:CanvasText!important}
979
977
  [data-decoration="chip"][title^="${HIDDEN_LABEL}"]{display:inline-block!important;width:0!important;height:0!important;overflow:hidden!important;background:transparent!important;border:none!important;box-shadow:none!important;margin:0!important;padding:0!important;font-size:0!important;line-height:0!important}
980
978
  [data-decoration="chip"][title^="${HIDDEN_LABEL}"]:before,[data-decoration="chip"][title^="${HIDDEN_LABEL}"]>*{display:none!important}
981
979
  .dsh-upload-control{display:flex;align-items:center;min-width:0}
@@ -3055,6 +3053,18 @@ window.__ModuleLoader__.load({
3055
3053
  backdrop-filter: none;
3056
3054
  -webkit-backdrop-filter: none;
3057
3055
  }
3056
+ /* 左栏顶部「新会话」按钮:去掉白/黑实底与边框,透出背景图;文字颜色交由下方统一的侧边栏/顶栏可读文字规则决定 */
3057
+ html[data-dsh-glass="on"] #root [class*="newSession"] {
3058
+ background-color: transparent !important;
3059
+ background-image: none !important;
3060
+ border: none !important;
3061
+ border-radius: 0 !important;
3062
+ box-shadow: none !important;
3063
+ backdrop-filter: none !important;
3064
+ -webkit-backdrop-filter: none !important;
3065
+ }
3066
+ /* 左栏/顶栏文字可读性改用 JS 处理(见 glassPlugin.apply 的 applyChromeText),
3067
+ 只作用于主界面左栏/顶栏、并跳过设置/弹窗/浮层,避免波及设置面板 */
3058
3068
  /* 左栏内部根 / 底部容器也用透明(这些是 DSH 当前版本哈希类名;背景图要透到左栏与底部) */
3059
3069
  html[data-dsh-glass="on"] #root [class*="hHd-Xa_root"],
3060
3070
  html[data-dsh-glass="on"] #root [class*="FJxK0a_root"] {
@@ -3074,7 +3084,7 @@ window.__ModuleLoader__.load({
3074
3084
  margin: 0 !important;
3075
3085
  }
3076
3086
  /* 背景图独立层:铺满整页、位于内容之下,可单独设背景图模糊与透明度,不影响内容清晰度 */
3077
- .dsh-glass-bgimg{position:fixed;inset:0;z-index:-1;pointer-events:none;background-size:cover;background-position:center;background-repeat:no-repeat;display:none;transform:translateZ(0);}
3087
+ .dsh-glass-bgimg{position:fixed;inset:0;z-index:-1;pointer-events:none;background-size:cover;background-position:center;background-repeat:no-repeat;display:none;}
3078
3088
  /* 滚动条:细、半透明、圆角,且左移贴在内容区(不占卡片最右边缘),让右边缘过渡柔和 */
3079
3089
  html[data-dsh-glass="on"] #root [class*="wSkVaW_scrollBody"]::-webkit-scrollbar { width: 8px; }
3080
3090
  html[data-dsh-glass="on"] #root [class*="wSkVaW_scrollBody"]::-webkit-scrollbar-track { background: transparent; }
@@ -3089,7 +3099,7 @@ window.__ModuleLoader__.load({
3089
3099
  border-radius: 0 !important;
3090
3100
  box-shadow: none !important;
3091
3101
  }
3092
- /* 输入区外围(composer根/位置座)透明透出背景;输入框盒子保留原生白/黑 */
3102
+ /* 输入区外围(composerSeat/uV2eYG_root)透明,白块去掉、透出背景 */
3093
3103
  html[data-dsh-glass="on"] [class*="wSkVaW_composerSeat"],
3094
3104
  html[data-dsh-glass="on"] [class*="uV2eYG_root"] {
3095
3105
  background-color: transparent !important;
@@ -3098,21 +3108,49 @@ window.__ModuleLoader__.load({
3098
3108
  backdrop-filter: none !important;
3099
3109
  -webkit-backdrop-filter: none !important;
3100
3110
  }
3101
- /* 输入框盒子(uV2eYG_card):只在外层铺淡色底(可调);里层输入区透明避免拼缝白条;文字随主题色 */
3111
+ /* 输入框盒子(uV2eYG_card):实底(浅色=白/深色=深)并在背景图之上,文字随主题,保证可读。
3112
+ 加 #root 提高优先级,压过 DSH 自身把 card 背景设透明的规则(与 v2.2.1 一致) */
3113
+ html[data-dsh-glass="on"] #root [class*="uV2eYG_card"],
3102
3114
  html[data-dsh-glass="on"] [class*="uV2eYG_card"] {
3103
- background: linear-gradient(var(--dsh-glass-input-white, rgba(255,255,255,0.3)), var(--dsh-glass-input-white, rgba(255,255,255,0.3))) var(--dsh-glass-input-bg, rgba(26,35,50,0.6)) !important;
3104
- color: var(--dsw-alias-label-primary, #111827) !important;
3115
+ position: relative !important;
3116
+ z-index: 0 !important;
3117
+ background-color: #ffffff !important;
3118
+ background-image: none !important;
3119
+ color: #1a2332 !important;
3105
3120
  box-shadow: none !important;
3106
3121
  backdrop-filter: none !important;
3107
3122
  -webkit-backdrop-filter: none !important;
3108
3123
  }
3109
- html[data-dsh-glass="on"] [class*="uV2eYG_input"] {
3110
- background-color: transparent !important;
3111
- color: var(--dsw-alias-label-primary, #111827) !important;
3112
- }
3113
3124
  @media (prefers-color-scheme: dark) {
3114
- html[data-dsh-glass="on"] [class*="uV2eYG_card"],
3115
- html[data-dsh-glass="on"] [class*="uV2eYG_input"] { color: var(--dsw-alias-label-primary, #e5e7eb) !important; }
3125
+ html[data-dsh-glass="on"] #root [class*="uV2eYG_card"],
3126
+ html[data-dsh-glass="on"] [class*="uV2eYG_card"] { background: #1c2530 !important; color: #f5f7fa !important; }
3127
+ }
3128
+ /* 会话语流"耗时统计"(turn-tail: 12:38/用时/首token/tok/s):白字+深投影,避免淡字叠在背景图上看不见 */
3129
+ html[data-dsh-glass="on"] [class*="osXY9a_root"],
3130
+ html[data-dsh-glass="on"] [class*="osXY9a_root"] span,
3131
+ html[data-dsh-glass="on"] [class*="p-xYUq_timeEnd"],
3132
+ html[data-dsh-glass="on"] [class*="p-xYUq_"] span {
3133
+ color: #ffffff !important;
3134
+ text-shadow: 0 1px 1px rgba(0,0,0,.5);
3135
+ }
3136
+ /* DSH 弹窗/浮层(提问/确认/重命名等):给不透明底+主题文字, 杜绝透明与会话内容重叠 */
3137
+ html[data-dsh-glass="on"] [class*="Radix"] [class*="Content"],
3138
+ html[data-dsh-glass="on"] [class*="DialogContent"],
3139
+ html[data-dsh-glass="on"] [class*="ModalContent"],
3140
+ html[data-dsh-glass="on"] [class*="overlay"] [class*="content"],
3141
+ html[data-dsh-glass="on"] [class*="_content"][class*="dialog"] {
3142
+ background-color: var(--dsw-specific-input-major, #e8edf3) !important;
3143
+ color: var(--dsw-alias-label-primary) !important;
3144
+ backdrop-filter: blur(8px) !important;
3145
+ -webkit-backdrop-filter: blur(8px) !important;
3146
+ }
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;
3151
+ color: var(--dsw-alias-label-primary) !important;
3152
+ backdrop-filter: blur(8px) !important;
3153
+ -webkit-backdrop-filter: blur(8px) !important;
3116
3154
  }
3117
3155
  /* 轮次/历史提问预览:默认隐藏(点开才显示),并去掉头部底色,避免底部露出白条 */
3118
3156
  .dsh-turn-preview:not(.open){display:none !important;}
@@ -3122,11 +3160,36 @@ window.__ModuleLoader__.load({
3122
3160
  html[data-dsh-glass="off"] #root [class*="header"],
3123
3161
  html[data-dsh-glass="off"] #root [class*="centerCol"],
3124
3162
  html[data-dsh-glass="off"] #root [class*="wSkVaW_root"],
3125
- html[data-dsh-glass="off"] #root [class*="wSkVaW_scrollBody"],
3126
- html[data-dsh-glass="off"] #root [class*="uV2eYG_card"] { background-color: transparent !important; backdrop-filter: none; }
3127
- /* 用户消息气泡:浅色半透明底(随主题),与 AI 消息(透明/磨砂)区分 */
3128
- html[data-dsh-glass="on"] #root [class*="ngdEzaw_bubble"]:not([class*="button"]) {
3129
- background-color: color-mix(in srgb, var(--dsw-specific-input-major, #e8edf3) 62%, transparent) !important;
3163
+ html[data-dsh-glass="off"] #root [class*="wSkVaW_scrollBody"] { background-color: transparent !important; backdrop-filter: none; }
3164
+ /* 会话消息气泡:半透明底(随主题, 60%透明=40%不透明),与 AI 消息(透明/磨砂)区分、融入背景 */
3165
+ html[data-dsh-glass="on"] #root [class*="_bubble"]:not([class*="button"]) {
3166
+ background-color: color-mix(in srgb, var(--dsw-specific-input-major, #e8edf3) 40%, transparent) !important;
3167
+ }
3168
+ /* 统一桌面 + 浅色主题:DSH 的 secondary/tertiary 文字在浅色磨砂上太浅,统一加深,覆盖状态栏/工具标签等 */
3169
+ html[data-dsh-glass="on"][data-dsh-theme="light"] {
3170
+ --dsw-alias-label-secondary: #33414f !important;
3171
+ --dsw-alias-label-tertiary: #4a5866 !important;
3172
+ }
3173
+ /* 工具调用名(Bash/Shell等)与推理(think)标签:浅色下再显式压深,保证可读 */
3174
+ html[data-dsh-glass="on"][data-dsh-theme="light"] #root [class*="CY-8Ka_title"],
3175
+ html[data-dsh-glass="on"][data-dsh-theme="light"] #root [class*="QWLzLg_title"] {
3176
+ color: #2f3d4a !important;
3177
+ }
3178
+ /* 输入框下方状态/统计行(余额+步数/时长/速度等)都在 composer.dock 槽里:
3179
+ 背景是会变的背景图, 单色难保证可读;给整块加半透明深色衬底+亮字, 任意背景都可读。
3180
+ 槽位属性可能是 data-slot-conversation 或 data-slot(两种都锁) */
3181
+ html[data-dsh-glass="on"] [data-slot-conversation="conversation.composer.dock"],
3182
+ html[data-dsh-glass="on"] [data-slot="conversation.composer.dock"] {
3183
+ background-color: rgba(12,20,30,.55) !important;
3184
+ border-radius: 8px !important;
3185
+ padding: 1px 10px !important;
3186
+ backdrop-filter: blur(2px);
3187
+ -webkit-backdrop-filter: blur(2px);
3188
+ }
3189
+ html[data-dsh-glass="on"] [data-slot-conversation="conversation.composer.dock"] *,
3190
+ html[data-dsh-glass="on"] [data-slot="conversation.composer.dock"] * {
3191
+ color: #f0f4f9 !important;
3192
+ text-shadow: 0 1px 1px rgba(0,0,0,.5);
3130
3193
  }
3131
3194
  /* 会话区所有内容元素背景透明化:去掉"反底色"小块衬底,统一融入磨砂卡片 */
3132
3195
  html[data-dsh-glass="on"] #root [class*="wSkVaW_scrollBody"] *:not([class*="button"]):not([class*="bubble"]):not([class*="_menu"]) {
@@ -3141,10 +3204,6 @@ window.__ModuleLoader__.load({
3141
3204
  html[data-dsh-glass="on"] #root [class*="wSkVaW_scrollBody"] [class*="md-code-block"] {
3142
3205
  background-color: transparent !important;
3143
3206
  }
3144
- /* uV2eYG_add: composer 里随内容变化的白色衬底,玻璃开启时透明化 */
3145
- html[data-dsh-glass="on"] #root [class*="uV2eYG_add"] {
3146
- background-color: transparent !important;
3147
- }
3148
3207
  /* 输入框下拉/悬浮菜单(如 / 命令、@ 提及):背景给不透明随主题色,避免叠在会话磨砂卡上重叠看不清 */
3149
3208
  html[data-dsh-glass="on"] #root [class*="_menu"][class*="_menu"][class*="_menu"][class*="_menu"] {
3150
3209
  background-color: var(--dsw-specific-input-major, #e8edf3) !important;
@@ -3181,6 +3240,9 @@ window.__ModuleLoader__.load({
3181
3240
  .dsh-glass-switch input{width:34px;height:20px;accent-color:var(--dsw-alias-state-business-primary);cursor:pointer}
3182
3241
  .dsh-glass-card{display:grid;gap:12px;padding:14px 16px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-specific-input-major)}
3183
3242
  .dsh-glass-zone-title{font-size:14px;font-weight:600;color:var(--dsw-alias-label-primary)}
3243
+ .dsh-glass-input-theme{border-top:1px dashed var(--dsw-alias-border-l2);padding-top:12px;display:grid;gap:12px}
3244
+ .dsh-glass-input-theme:first-of-type{border-top:none;padding-top:0}
3245
+ .dsh-glass-theme-label{font-size:12px;font-weight:600;color:var(--dsw-alias-label-secondary)}
3184
3246
  .dsh-glass-row{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}
3185
3247
  .dsh-glass-row label{font-size:13px;color:var(--dsw-alias-label-primary);flex:none}
3186
3248
  .dsh-glass-range{flex:1;min-width:160px;max-width:320px;accent-color:var(--dsw-alias-state-business-primary)}
@@ -3258,6 +3320,18 @@ window.__ModuleLoader__.load({
3258
3320
  React.useEffect(() => { load(); }, [load])
3259
3321
  const patch = React.useCallback((p) => setState((s) => ({ ...s, cfg: { ...(s.cfg || {}), ...p } })), [])
3260
3322
  const patchZone = React.useCallback((key, p) => setState((s) => ({ ...s, cfg: { ...(s.cfg || {}), [key]: p } })), [])
3323
+ // 输入框设置:按主题(theme)合并进 cfg.inputBox[theme](不整体覆盖、不丢另一主题)
3324
+ const patchInput = React.useCallback((theme, p) => setState((s) => {
3325
+ const box = { light: { color: '', opacity: 1 }, dark: { color: '', opacity: 1 }, ...((s.cfg && s.cfg.inputBox) || {}) }
3326
+ box[theme] = { ...((box[theme]) || { color: '', opacity: 1 }), ...p }
3327
+ return { ...s, cfg: { ...(s.cfg || {}), inputBox: box } }
3328
+ }), [])
3329
+ // 背景罩设置:按主题合并进 cfg.bgTint[theme]
3330
+ const patchBgTint = React.useCallback((theme, p) => setState((s) => {
3331
+ const box = { light: { color: '#1a2332', mask: 0.28 }, dark: { color: '#1a2332', mask: 0.28 }, ...((s.cfg && s.cfg.bgTint) || {}) }
3332
+ box[theme] = { ...((box[theme]) || { color: '#1a2332', mask: 0.28 }), ...p }
3333
+ return { ...s, cfg: { ...(s.cfg || {}), bgTint: box } }
3334
+ }), [])
3261
3335
  const save = React.useCallback(async () => {
3262
3336
  setState((s) => ({ ...s, saving: true, error: '' }))
3263
3337
  try {
@@ -3266,6 +3340,14 @@ window.__ModuleLoader__.load({
3266
3340
  const payload = {
3267
3341
  enabled: !!cfg.enabled, blur: num(cfg.blur, 0),
3268
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),
3343
+ bgTint: {
3344
+ light: { color: (cfg.bgTint?.light?.color) || '#1a2332', mask: num(cfg.bgTint?.light?.mask, 0.28) },
3345
+ dark: { color: (cfg.bgTint?.dark?.color) || '#1a2332', mask: num(cfg.bgTint?.dark?.mask, 0.28) },
3346
+ },
3347
+ inputBox: {
3348
+ light: { color: (cfg.inputBox?.light?.color) || '', opacity: num(cfg.inputBox?.light?.opacity, 1) },
3349
+ dark: { color: (cfg.inputBox?.dark?.color) || '', opacity: num(cfg.inputBox?.dark?.opacity, 1) },
3350
+ },
3269
3351
  zone: {
3270
3352
  session: { enabled: cfg.session && cfg.session.enabled !== false, color: (cfg.session && cfg.session.color) || '#1a2332', mask: num(cfg.session && cfg.session.mask, 0.45), opacity: num(cfg.session && cfg.session.opacity, 0.5) },
3271
3353
  input: { color: (cfg.input && cfg.input.color) || '#1a2332', mask: num(cfg.input && cfg.input.mask, 0.6), opacity: num(cfg.input && cfg.input.opacity, 0.55) },
@@ -3282,7 +3364,7 @@ window.__ModuleLoader__.load({
3282
3364
  if (state.loading) return React.createElement('div', { className: 'dsh-glass-section' }, '加载中…')
3283
3365
  return React.createElement('div', { className: 'dsh-glass-section' },
3284
3366
  React.createElement('div', { className: 'dsh-glass-head' },
3285
- React.createElement('div', null, React.createElement('h2', null, '毛玻璃界面'), React.createElement('p', null, '共用一张背景图铺满整页,仅消息滚动区做居中磨砂玻璃卡片,其余区域透出背景图。')),
3367
+ React.createElement('div', null, React.createElement('h2', null, 'RA-Span'), React.createElement('p', null, '共用一张背景图铺满整页(RA-Span):背景图、背景罩层颜色、背景模糊/透明度可调;会话区/输入区按需透明或实底。')),
3286
3368
  React.createElement('label', { className: 'dsh-glass-switch' }, React.createElement('input', { type: 'checkbox', checked: !!cfg.enabled, onChange: (e) => patch({ enabled: e.target.checked }) }), cfg.enabled ? '已开启' : '已关闭'),
3287
3369
  ),
3288
3370
  state.error ? React.createElement('div', { className: 'dsh-glass-error' }, state.error) : null,
@@ -3296,14 +3378,24 @@ window.__ModuleLoader__.load({
3296
3378
  React.createElement('label', null, '共用背景图'),
3297
3379
  React.createElement('div', { className: 'dsh-glass-bg' }, glassImageInput(state.bgImage, (v) => setState((s) => ({ ...s, bgImage: v })))),
3298
3380
  ),
3299
- React.createElement('div', { className: 'dsh-glass-row' },
3300
- React.createElement('label', null, '背景图罩'),
3301
- React.createElement('input', { type: 'range', className: 'dsh-glass-range', min: 0, max: 0.95, step: 0.01, value: cfg.bgMask ?? 0.28, onChange: (e) => patch({ bgMask: Number(e.target.value) }) }),
3302
- React.createElement('span', { className: 'dsh-glass-val' }, `${Math.round((Number(cfg.bgMask ?? 0.28)) * 100)}%`),
3303
- ),
3304
- React.createElement('div', { className: 'dsh-glass-row' },
3305
- React.createElement('label', null, '背景罩层颜色'),
3306
- React.createElement('label', { className: 'dsh-glass-colorpicker' }, '色盘', React.createElement('input', { type: 'color', value: cfg.bgColor || '#1a2332', onChange: (e) => patch({ bgColor: e.target.value }) })),
3381
+ React.createElement('div', { className: 'dsh-glass-card' },
3382
+ React.createElement('div', { className: 'dsh-glass-zone-title' }, '背景罩'),
3383
+ [['light', '浅色主题', '#1a2332'], ['dark', '深色主题', '#1a2332']].map(([t, label, native]) => {
3384
+ const bt = (cfg.bgTint && cfg.bgTint[t]) || { color: native, mask: 0.28 }
3385
+ return React.createElement('div', { key: t, className: 'dsh-glass-input-theme' },
3386
+ React.createElement('div', { className: 'dsh-glass-theme-label' }, label),
3387
+ React.createElement('div', { className: 'dsh-glass-row' },
3388
+ React.createElement('label', null, '罩色'),
3389
+ React.createElement('label', { className: 'dsh-glass-colorpicker' }, '色盘', React.createElement('input', { type: 'color', value: bt.color || native, onChange: (e) => patchBgTint(t, { color: e.target.value }) })),
3390
+ ),
3391
+ React.createElement('div', { className: 'dsh-glass-row' },
3392
+ React.createElement('label', null, '罩强度'),
3393
+ React.createElement('input', { type: 'range', className: 'dsh-glass-range', min: 0, max: 0.95, step: 0.01, value: (typeof bt.mask === 'number') ? bt.mask : 0.28, onChange: (e) => patchBgTint(t, { mask: Number(e.target.value) }) }),
3394
+ React.createElement('span', { className: 'dsh-glass-val' }, `${Math.round((typeof bt.mask === 'number' ? bt.mask : 0.28) * 100)}%`),
3395
+ ),
3396
+ )
3397
+ }),
3398
+ React.createElement('p', { className: 'dsh-glass-note' }, '浅色主题与深色主题可分别设置背景罩颜色与罩强度,切换主题时自动应用对应的一套。'),
3307
3399
  ),
3308
3400
  React.createElement('div', { className: 'dsh-glass-row' },
3309
3401
  React.createElement('label', null, '背景图模糊'),
@@ -3315,7 +3407,27 @@ window.__ModuleLoader__.load({
3315
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) }) }),
3316
3408
  React.createElement('span', { className: 'dsh-glass-val' }, `${Math.round((Number(cfg.bgOpacity ?? 1)) * 100)}%`),
3317
3409
  ),
3318
- React.createElement('p', { className: 'dsh-glass-note' }, '提示:背景图铺满整页(可加背景图罩);背景图模糊/透明度单独可调;会话区使用自己的罩色、罩强度与透明度;左栏/顶部透出背景图;输入框保持系统原生样式。'),
3410
+ React.createElement('div', { className: 'dsh-glass-card' },
3411
+ React.createElement('div', { className: 'dsh-glass-zone-title' }, '输入框'),
3412
+ [['light', '浅色主题', '#ffffff'], ['dark', '深色主题', '#1c2530']].map(([t, label, native]) => {
3413
+ const tb = (cfg.inputBox && cfg.inputBox[t]) || { color: '', opacity: 1 }
3414
+ return React.createElement('div', { key: t, className: 'dsh-glass-input-theme' },
3415
+ React.createElement('div', { className: 'dsh-glass-theme-label' }, label),
3416
+ React.createElement('div', { className: 'dsh-glass-row' },
3417
+ React.createElement('label', null, '输入框颜色'),
3418
+ React.createElement('label', { className: 'dsh-glass-colorpicker' }, '色盘', React.createElement('input', { type: 'color', value: tb.color || native, onChange: (e) => patchInput(t, { color: e.target.value }) })),
3419
+ React.createElement('button', { type: 'button', style: { marginLeft: 8, fontSize: 12 }, onClick: () => patchInput(t, { color: '', opacity: 1 }) }, '原生'),
3420
+ ),
3421
+ 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)}%`),
3425
+ ),
3426
+ )
3427
+ }),
3428
+ React.createElement('p', { className: 'dsh-glass-note' }, '浅色主题与深色主题可分别设置。100% = 实底不透明,越低越透出背景图;"原生" = 跟随该主题的 DSH 原生色。文字颜色按背景亮度自动配深/浅,保证可读。'),
3429
+ ),
3430
+ React.createElement('p', { className: 'dsh-glass-note' }, '提示:背景图铺满整页(可加背景图罩);背景图模糊/透明度单独可调;会话区使用自己的罩色、罩强度与透明度;左栏/顶部透出背景图;输入框颜色/不透明度可单独设置。'),
3319
3431
  React.createElement('div', { className: 'dsh-glass-actions' },
3320
3432
  React.createElement('button', { type: 'button', onClick: load }, '重置'),
3321
3433
  React.createElement('button', { type: 'button', className: 'primary', disabled: state.saving, onClick: save }, state.saving ? '保存中…' : '保存生效'),
@@ -3351,6 +3463,15 @@ window.__ModuleLoader__.load({
3351
3463
  }
3352
3464
  window.addEventListener('resize', centerScroll)
3353
3465
  window.__dshGlassCenter = centerScroll
3466
+ // 主题切换监听:观察 <html> 的 style/class;只创建一次(自触发的写入在 applyTheme 内先断开再重连,杜绝死循环)
3467
+ window.__dshGlassStyleObs = null
3468
+ if (!window.__dshGlassStyleObs) {
3469
+ window.__dshGlassStyleObs = new MutationObserver(() => window.__dshGlassApplyTheme && window.__dshGlassApplyTheme())
3470
+ window.__dshGlassStyleObs.observe(document.documentElement, { attributes: true, attributeFilter: ['style', 'class'] })
3471
+ }
3472
+ const themeMq = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)')
3473
+ const themeMqHandler = () => window.__dshGlassApplyTheme && window.__dshGlassApplyTheme()
3474
+ if (themeMq) themeMq.addEventListener('change', themeMqHandler)
3354
3475
  // win 风格文字:给左栏/顶栏内的可见文字元素加白色+深色描边;跳过弹窗(modal/overlay/dialog/settings/panel)内元素。
3355
3476
 
3356
3477
  window.__dshGlassApply = (cfg) => {
@@ -3370,6 +3491,15 @@ window.__ModuleLoader__.load({
3370
3491
  const bgEl = document.querySelector('.dsh-glass-bgimg'); if (bgEl) { bgEl.style.display = 'none' }
3371
3492
  return
3372
3493
  }
3494
+ // 读 DSH 真实主题(DSH 用 inline `color-scheme` 写在 <html style> 上,非 OS 偏好)
3495
+ const dshDark = () => /dark/i.test(getComputedStyle(document.documentElement).colorScheme)
3496
+ // 颜色亮度(0~1):用来自动决定文字用深色还是浅色,保证可读
3497
+ const relLum = (hex) => {
3498
+ const m = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(String(hex || ''))
3499
+ if (!m) return 0.5
3500
+ const map = (v) => { const s = parseInt(v, 16) / 255; return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4) }
3501
+ return 0.2126 * map(m[1]) + 0.7152 * map(m[2]) + 0.0722 * map(m[3])
3502
+ }
3373
3503
  root.setAttribute('data-dsh-glass', c.enabled ? 'on' : 'off')
3374
3504
  const sessOn = !(c.zone && c.zone.session && c.zone.session.enabled === false)
3375
3505
  root.setAttribute('data-dsh-session-glass', sessOn ? 'on' : 'off')
@@ -3386,20 +3516,220 @@ window.__ModuleLoader__.load({
3386
3516
  root.style.setProperty('--dsh-glass-session-white', `color-mix(in srgb, var(--dsw-specific-input-major, #e8edf3) ${Math.round((1 - sop) * 100)}%, transparent)`)
3387
3517
  const iop = Math.min(1, Math.max(0, Number.isFinite(Number(zi.opacity)) ? Number(zi.opacity) : 0.55))
3388
3518
  root.style.setProperty('--dsh-glass-input-white', `color-mix(in srgb, var(--dsw-specific-input-major, #e8edf3) ${Math.round((1 - iop) * 100)}%, transparent)`)
3389
- // 背景图罩:整页背景图上叠一层半透明罩色
3390
- const bgc = c.bgColor || '#1a2332'
3391
- const bgmn = Number(c.bgMask); const bgm = Math.min(0.95, Math.max(0, Number.isFinite(bgmn) ? bgmn : 0.28))
3392
- root.style.setProperty('--dsh-glass-bg-zone', `rgba(${hexToRgb(bgc)},${bgm})`)
3519
+
3520
+ // 背景图罩(整页背景图上叠一层半透明罩色):浅/深主题各一套 color+mask;
3521
+ // 兼容旧版单套 bgColor/bgMask
3522
+ const bgTint = (tk) => {
3523
+ const t = (c.bgTint && c.bgTint[tk]) || { color: c.bgColor || '#1a2332', mask: c.bgMask ?? 0.28 }
3524
+ const mask = Math.min(0.95, Math.max(0, Number.isFinite(Number(t && t.mask)) ? Number(t.mask) : 0.28))
3525
+ return { color: (t && t.color) || '#1a2332', mask }
3526
+ }
3527
+ // 给输入框卡片设内联实底+文字色,且用 !important,
3528
+ // 因为 DSH 核心样式用 background-color: transparent !important 压过普通内联样式;
3529
+ // 内联 !important 优先级最高,一定覆盖。颜色/不透明度来自配置 inputBox。
3530
+ const applyInputCard = () => {
3531
+ const inputCard = document.querySelector('#root [class*="uV2eYG_card"]')
3532
+ if (!inputCard) return
3533
+ const dark = dshDark()
3534
+ const ib = (c.inputBox && typeof c.inputBox === 'object') ? c.inputBox : {}
3535
+ // 浅色/深色各一套配置;兼容旧版单套 inputBox:{color,opacity}
3536
+ const tb = (ib.light && ib.dark) ? (dark ? ib.dark : ib.light) : ib
3537
+ const defBg = dark ? '#1c2530' : '#ffffff'
3538
+ // color: 空串/非法 => 用主题原生色;否则用用户指定的颜色
3539
+ const hasColor = tb && typeof tb.color === 'string' && /^#/.test(tb.color)
3540
+ const bgHex = hasColor ? tb.color : defBg
3541
+ const alpha = (tb && typeof tb.opacity === 'number' && Number.isFinite(tb.opacity)) ? Math.min(1, Math.max(0, tb.opacity)) : 1
3542
+ const bg = `rgba(${hexToRgb(bgHex)},${alpha})`
3543
+ // 背景/文字同步:按背景底色亮度决定文字色(亮底=深字/暗底=浅字),确保可读;与 v2.2.1 一致
3544
+ const fg = relLum(bgHex) > 0.5 ? '#1a2332' : '#f5f7fa'
3545
+ inputCard.style.setProperty('background-color', bg, 'important')
3546
+ inputCard.style.setProperty('color', fg, 'important')
3547
+ // 子层(scroll/row)也是透明的,继承卡片文字色即可,确保可读
3548
+ inputCard.querySelectorAll('[class*="uV2eYG_scroll"],[class*="uV2eYG_row"]').forEach(el => {
3549
+ el.style.setProperty('color', fg, 'important')
3550
+ })
3551
+ }
3552
+ // 主界面左栏/顶栏可见文字:白字+单层轻投影(Windows 桌面图标风格, 不加粗)。
3553
+ // 显式跳过设置/弹窗/浮层(modal/overlay/dialog/settings/panel/drawer/Radix/menu), 避免波及设置面板/弹出菜单。
3554
+ const SKIP_SEL = ':is([class*="settings"],[class*="Dialog"],[class*="dialog"],[class*="modal"],[class*="overlay"],[class*="_panel"],[class*="panel"],[class*="drawer"],[class*="Radix"],[class*="_menu"],[class*="brand"],[data-chain-overlay-fallback],[class*="M8wy4a_"])'
3555
+ const isInSkip = (el) => { let p = el; while (p && p !== document.documentElement) { if (p.matches && p.matches(SKIP_SEL)) return true; p = p.parentElement } return false }
3556
+ const applyChromeText = () => {
3557
+ if (!c.enabled) return
3558
+ // 只处理主界面左栏/顶栏内带文字的元素;跳过图标/纯容器。
3559
+ // 文字检测放宽:button/a/span/li/p/标题等直接算,纯 div 容器需自身带文本才算,
3560
+ // 这样底部「设置/已归档」等包在子元素里的按钮也能命中。
3561
+ const roots = [...document.querySelectorAll('#root [class*="sidebarCol"],#root [class*="wSkVaW_header"]')]
3562
+ roots.forEach((root) => {
3563
+ if (isInSkip(root)) return
3564
+ root.querySelectorAll('div,span,button,a,label,p,li,h1,h2,h3,h4').forEach((el) => {
3565
+ if (isInSkip(el)) return
3566
+ const tag = el.tagName
3567
+ const hasText = el.textContent && el.textContent.trim()
3568
+ if (!hasText) return
3569
+ 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)
3571
+ 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
3576
+ el.style.setProperty('color', '#ffffff', 'important')
3577
+ el.style.setProperty('text-shadow', '0 1px 1px rgba(0,0,0,.5)')
3578
+ })
3579
+ // 图标也变白(随 currentColor),用于工作区文件夹等单色图标;保留显式品牌色
3580
+ root.querySelectorAll('svg, [class*="icon"], [class*="Icon"]').forEach((el) => {
3581
+ if (isInSkip(el)) return
3582
+ el.style.setProperty('color', '#ffffff', 'important')
3583
+ el.querySelectorAll('path,use,rect,circle,line,polyline,ellipse').forEach((p) => {
3584
+ const f = (p.getAttribute('fill') || '').toLowerCase()
3585
+ if (f && f !== 'none' && f !== 'currentcolor') return
3586
+ p.style.setProperty('fill', 'currentColor', 'important')
3587
+ const s = (p.getAttribute('stroke') || '').toLowerCase()
3588
+ if (s && s !== 'none' && s !== 'currentcolor') return
3589
+ p.style.setProperty('stroke', 'currentColor', 'important')
3590
+ })
3591
+ })
3592
+ })
3593
+ }
3594
+ // 会话区内容(尤其 AI 过程内容:Bash/Think 标签、思考、工具输出等浅色灰字)
3595
+ // 在透明背景图上看不清;用同侧边栏「白字+深投影」处理,跳过有盒子背景的元素(用户气泡/代码块衬底等)。
3596
+ const applySessionText = () => {
3597
+ if (!c.enabled) return
3598
+ const scroll = document.querySelector('#root [class*="wSkVaW_scrollBody"]')
3599
+ if (!scroll) return
3600
+ scroll.querySelectorAll('div,span,p,li,pre,code,button,a,label,h1,h2,h3,h4').forEach((el) => {
3601
+ if (isInSkip(el)) return
3602
+ const hasText = el.textContent && el.textContent.trim()
3603
+ if (!hasText) return
3604
+ const ownText = Array.from(el.childNodes).some((n) => n.nodeType === 3 && n.textContent.trim())
3605
+ const textLike = /^(BUTTON|A|SPAN|LABEL|LI|P|H1|H2|H3|H4|PRE|CODE)$/.test(el.tagName)
3606
+ if (!textLike && !ownText) return
3607
+ const bgbg = getComputedStyle(el).backgroundColor
3608
+ if (bgbg && bgbg !== 'rgba(0, 0, 0, 0)' && bgbg !== 'transparent') return
3609
+ el.style.setProperty('color', '#ffffff', 'important')
3610
+ el.style.setProperty('text-shadow', '0 1px 1px rgba(0,0,0,.5)')
3611
+ })
3612
+ // 在白字底上, 给过程内容配色区分, Error 用红色。
3613
+ // 文字常在标签的子元素里(白字已把它设白), 故连同文本子元素一起上色。
3614
+ const paint = (el, color) => {
3615
+ if (!el) return
3616
+ const set = (d) => { if (d.textContent && d.textContent.trim()) d.style.setProperty('color', color, 'important') }
3617
+ set(el)
3618
+ el.querySelectorAll('span,div,p,li,a').forEach(set)
3619
+ }
3620
+ // 错误/失败单行文本 → 红(只判短行、以 Error/错误/失败/异常 开头, 避免整段误标红)
3621
+ const isErrText = (d) => {
3622
+ const t = (d.textContent || '').trim()
3623
+ if (!t || t.length > 120) return false
3624
+ return /^(error|错误|失败|异常|fail)\b/i.test(t) || /^(error|错误|失败|异常)\s*[::]/i.test(t) || /^File not found/i.test(t)
3625
+ }
3626
+ scroll.querySelectorAll('div,span,p,li,pre,code').forEach((el) => {
3627
+ if (!isErrText(el)) return
3628
+ const ownText = Array.from(el.childNodes).some((n) => n.nodeType === 3 && n.textContent.trim())
3629
+ if (ownText) el.style.setProperty('color', '#ff6b6b', 'important')
3630
+ })
3631
+ // 工具名标签(o3BgMG_title):按工具类型配色(Bash/Shell=绿, Read=蓝, Edit=橙, Write=紫, 默认蓝)
3632
+ const toolColor = (text) => {
3633
+ const t = (text || '').toLowerCase()
3634
+ if (/bash|shell|zsh|terminal|powershell|cmd|sh\b/i.test(t)) return '#5ec26b'
3635
+ if (/edit|modify/i.test(t)) return '#f0a860'
3636
+ if (/write|create/i.test(t)) return '#c9a0ff'
3637
+ return '#8ccfff'
3638
+ }
3639
+ scroll.querySelectorAll('[class*="o3BgMG_title"],[class*="CY-8Ka_title"]').forEach((title) => {
3640
+ paint(title, toolColor(title.textContent))
3641
+ // 从标签向上找最近一个"含链接/代码/更多文本"的容器作为工具行,找不到就用父级
3642
+ let box = title.parentElement
3643
+ for (let i = 0; i < 4 && box; i++) {
3644
+ if (box.querySelector && box.querySelector('a, code, [class*="desc"], [class*="path"], [class*="cmd"], [class*="arg"]')) break
3645
+ box = box.parentElement
3646
+ }
3647
+ if (!box) box = title.parentElement
3648
+ box.querySelectorAll('div,span,a,p,li,pre,code').forEach((el) => {
3649
+ if (el === title || el.querySelector('[class*="o3BgMG_title"]')) return
3650
+ const ownText = Array.from(el.childNodes).some((n) => n.nodeType === 3 && n.textContent.trim())
3651
+ if (!ownText || isErrText(el)) return
3652
+ paint(el, '#6fb89a')
3653
+ })
3654
+ })
3655
+ // 工具输出/路径(o3BgMG_summary):成功 → 水绿;errorSummary 保持红
3656
+ scroll.querySelectorAll('[class*="o3BgMG_summary"]').forEach((el) => {
3657
+ if (/errorSummary/i.test(String(el.className))) return
3658
+ paint(el, '#6fb89a')
3659
+ })
3660
+ // 路径/文件链接(o3BgMG_fileLink) → 水绿(与白字标签区分)
3661
+ scroll.querySelectorAll('[class*="o3BgMG_fileLink"]').forEach((el) => paint(el, '#6fb89a'))
3662
+ // 思考正文(think 容器) → 暖琥珀;再覆盖 Think 标签为琥珀。
3663
+ // 全文档查找,不受 scrollBody 位置/加载时序影响(仍跳过设置/菜单/浮层)
3664
+ const paintThinkBody = (root) => {
3665
+ if (!root) return
3666
+ root.querySelectorAll('div,span,p,li').forEach((el) => {
3667
+ const ownText = Array.from(el.childNodes).some((n) => n.nodeType === 3 && n.textContent.trim())
3668
+ if (ownText && !isErrText(el) && !isInSkip(el)) paint(el, '#e6c583')
3669
+ })
3670
+ }
3671
+ const thinkRoots = [...document.querySelectorAll('[class*="QWLzLg_root"]')].filter((r) => !isInSkip(r))
3672
+ thinkRoots.forEach(paintThinkBody)
3673
+ document.querySelectorAll('[class*="QWLzLg_title"]').forEach((el) => {
3674
+ if (!isInSkip(el)) paint(el, '#f2c566')
3675
+ })
3676
+ // 会话区工具调用等图标:改为白色清晰显示(单色图标随 currentColor 变白, 保留品牌色)
3677
+ scroll.querySelectorAll('svg, [class*="icon"], [class*="Icon"]').forEach((el) => {
3678
+ if (isInSkip(el)) return
3679
+ el.style.setProperty('color', '#ffffff', 'important')
3680
+ el.querySelectorAll('path,use,rect,circle,line,polyline,ellipse').forEach((p) => {
3681
+ const f = (p.getAttribute('fill') || '').toLowerCase()
3682
+ if (f && f !== 'none' && f !== 'currentcolor') return
3683
+ p.style.setProperty('fill', 'currentColor', 'important')
3684
+ const s = (p.getAttribute('stroke') || '').toLowerCase()
3685
+ if (s && s !== 'none' && s !== 'currentcolor') return
3686
+ p.style.setProperty('stroke', 'currentColor', 'important')
3687
+ })
3688
+ })
3689
+ }
3690
+ // DSH 提问/弹窗卡(M8wy4a_card 等):内联强制不透明底+原生文字色, 压过 DSH 重渲染/透明与白字
3691
+ const fixDialogs = () => {
3692
+ if (!c.enabled) return
3693
+ const dark = dshDark()
3694
+ const bg = (getComputedStyle(document.documentElement).getPropertyValue('--dsw-specific-input-major') || '').trim() || '#e8edf3'
3695
+ const fg = dark ? '#f2f6fa' : '#1a2332'
3696
+ // 只匹配提问/弹窗卡(M8wy4a_card),避免误匹配输入框 uV2eYG_card(含"card")导致 blur 重影
3697
+ document.querySelectorAll('[class*="M8wy4a_card"]').forEach((el) => {
3698
+ el.style.setProperty('background-color', bg, 'important')
3699
+ el.style.setProperty('color', fg, 'important')
3700
+ el.style.setProperty('backdrop-filter', 'blur(8px)')
3701
+ })
3702
+ }
3703
+ // 主题相关的东西统一重算:背景罩 + 输入框。切主题时由 observer/mq 触发。
3704
+ // 写入前先断开观察器,写完再重连,避免自触发造成死循环。
3705
+ const applyTheme = () => {
3706
+ const obs = window.__dshGlassStyleObs
3707
+ if (obs) obs.disconnect()
3708
+ try {
3709
+ const tk = dshDark() ? 'dark' : 'light'
3710
+ root.setAttribute('data-dsh-theme', tk)
3711
+ const bt = bgTint(tk)
3712
+ const v = `rgba(${hexToRgb(bt.color)},${bt.mask})`
3713
+ root.style.setProperty('--dsh-glass-bg-zone', v)
3714
+ applyInputCard()
3715
+ applyChromeText()
3716
+ applySessionText()
3717
+ fixDialogs()
3718
+ } finally {
3719
+ if (obs) obs.observe(document.documentElement, { attributes: true, attributeFilter: ['style', 'class'] })
3720
+ }
3721
+ }
3393
3722
 
3394
3723
  const body = document.body
3395
3724
  const frame = document.querySelector('#root > div > div') || document.querySelector('#root > div')
3396
- // 背景图独立层:铺满整页、位于内容之下;背景图模糊/透明度单独可调(不影响内容清晰度)
3725
+ // 背景图独立层:铺满整页、位于内容之下;背景图模糊/透明度单独可调(不影响内容清晰度)。
3726
+ // 背景罩用 CSS 变量 var(--dsh-glass-bg-zone),随主题切换自动刷新。
3397
3727
  let bgimg = document.querySelector('.dsh-glass-bgimg')
3398
3728
  if (!bgimg) { bgimg = document.createElement('div'); bgimg.className = 'dsh-glass-bgimg'; document.body.appendChild(bgimg) }
3399
- const gz = root.style.getPropertyValue('--dsh-glass-bg-zone') || `rgba(${hexToRgb(c.bgColor || '#1a2332')},0.28)`
3729
+ const gzv = root.style.getPropertyValue('--dsh-glass-bg-zone') || 'rgba(26,35,50,0.28)'
3400
3730
  if (c.enabled && c.bgImage) {
3401
3731
  bgimg.style.display = 'block'
3402
- bgimg.style.backgroundImage = `linear-gradient(${gz},${gz}), url('${c.bgImage}')`
3732
+ bgimg.style.backgroundImage = `linear-gradient(var(--dsh-glass-bg-zone, ${gzv}), var(--dsh-glass-bg-zone, ${gzv})), url('${c.bgImage}')`
3403
3733
  bgimg.style.backgroundSize = 'cover'
3404
3734
  bgimg.style.backgroundPosition = 'center'
3405
3735
  bgimg.style.backgroundRepeat = 'no-repeat'
@@ -3414,15 +3744,52 @@ window.__ModuleLoader__.load({
3414
3744
  body.style.backgroundImage = 'none'
3415
3745
  if (c.enabled && c.bgImage) { body.style.backgroundColor = 'transparent' } else { body.style.backgroundColor = '' }
3416
3746
  if (frame) { frame.style.backgroundImage = 'none'; frame.style.backgroundColor = 'transparent' }
3747
+
3748
+ applyTheme()
3749
+ // 记录当前主题应用函数,供唯一观察器/mq 调用
3750
+ window.__dshGlassApplyTheme = applyTheme
3417
3751
  centerScroll()
3752
+ // 侧边栏/顶栏 DOM 变化(会话列表更新等)时重算左栏/顶栏文字,防抖 120ms
3753
+ let chromeTimer = null
3754
+ const chromeRoots = [...document.querySelectorAll('#root [class*="sidebarCol"],#root [class*="wSkVaW_header"]')]
3755
+ const chromeObs = new MutationObserver(() => { if (chromeTimer) clearTimeout(chromeTimer); chromeTimer = setTimeout(applyChromeText, 120) })
3756
+ chromeRoots.forEach((r) => chromeObs.observe(r, { childList: true, subtree: true, characterData: true }))
3757
+ // 输入框卡片(uV2eYG_card/root)可能在启动后才挂载(会话/输入区加载晚),
3758
+ // 启动那一刻 applyInputCard 常找不到框 → 需刷新才生效。监听新增卡片节点时重算,防抖 100ms。
3759
+ let cardTimer = null
3760
+ const hasInputNode = (node) => (node.querySelector && (node.querySelector('[class*="uV2eYG_card"]') || node.querySelector('[class*="uV2eYG_root"]'))) || (node.matches && (node.matches('[class*="uV2eYG_card"]') || node.matches('[class*="uV2eYG_root"]')))
3761
+ const cardObs = new MutationObserver((muts) => {
3762
+ let hit = false
3763
+ for (const m of muts) { for (const node of m.addedNodes) { if (node && node.nodeType === 1 && hasInputNode(node)) { hit = true; break } } if (hit) break }
3764
+ if (hit) { if (cardTimer) clearTimeout(cardTimer); cardTimer = setTimeout(() => applyTheme(), 100) }
3765
+ })
3766
+ cardObs.observe(document.body, { childList: true, subtree: true })
3767
+ // 会话区内容随流式更新会持续变,重算会话区文字(防抖 150ms, 只监听新增/移除节点)
3768
+ let sessionTimer = null
3769
+ const inScroll = (node) => { let p = node; while (p && p !== document.documentElement) { if (p.matches && p.matches('[class*="wSkVaW_scrollBody"]')) return true; p = p.parentElement } return false }
3770
+ const sessionObs = new MutationObserver((muts) => {
3771
+ let hit = false
3772
+ 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) }
3774
+ })
3775
+ sessionObs.observe(document.body, { childList: true, subtree: true })
3418
3776
  }
3419
3777
  return () => {
3420
3778
  window.__dshGlassApply = undefined
3421
3779
  window.removeEventListener('resize', centerScroll)
3422
3780
  window.__dshGlassCenter = undefined
3781
+ if (window.__dshGlassStyleObs) { window.__dshGlassStyleObs.disconnect(); window.__dshGlassStyleObs = null }
3782
+ if (themeMq) themeMq.removeEventListener('change', themeMqHandler)
3783
+ window.__dshGlassApplyTheme = undefined
3784
+ if (chromeObs) chromeObs.disconnect()
3785
+ if (chromeTimer) clearTimeout(chromeTimer)
3786
+ if (cardObs) cardObs.disconnect()
3787
+ if (cardTimer) clearTimeout(cardTimer)
3788
+ if (sessionObs) sessionObs.disconnect()
3789
+ if (sessionTimer) clearTimeout(sessionTimer)
3423
3790
  }
3424
3791
  }, 'dsh-long-plugins: glass applier')
3425
- ctx.slots.inject('settings.section', () => ctx.slots.register({ name: 'settings.section', id: 'glass-ui', order: 40, label: '毛玻璃界面' }, GlassSettingsSection))
3792
+ ctx.slots.inject('settings.section', () => ctx.slots.register({ name: 'settings.section', id: 'glass-ui', order: 40, label: 'RA-Span' }, GlassSettingsSection))
3426
3793
  ctx.effect(() => {
3427
3794
  fetch('/api/dsh-uploads/glass-config', { cache: 'no-store' }).then((r) => r.json()).then((b) => {
3428
3795
  if (b && b.cfg) { window.__dshGlassApply && window.__dshGlassApply({ ...b.cfg, bgImage: b.bgImage }) }
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.0",
4
+ "version": "2.2.2",
5
5
  "entry": {
6
6
  "name": "dsh-long-plugins",
7
7
  "inject": [
package/lib/index.js CHANGED
@@ -683,6 +683,36 @@ function sanitizeGlass(input) {
683
683
  if ("bgMask" in input) { const n = Number(input.bgMask); out.bgMask = Number.isFinite(n) ? Math.max(0, Math.min(0.95, n)) : undefined; }
684
684
  if ("bgBlur" in input) { const n = Number(input.bgBlur); out.bgBlur = Number.isFinite(n) ? Math.max(0, Math.min(80, n)) : undefined; }
685
685
  if ("bgOpacity" in input) { const n = Number(input.bgOpacity); out.bgOpacity = Number.isFinite(n) ? Math.max(0, Math.min(1, n)) : undefined; }
686
+ // 背景罩(整页背景图上叠的罩色):浅/深主题各一套 color + mask(罩强度)
687
+ if (input.bgTint && typeof input.bgTint === "object") {
688
+ const bt = {};
689
+ for (const key of ["light", "dark"]) {
690
+ const z = input.bgTint[key];
691
+ if (z && typeof z === "object") {
692
+ const o = {};
693
+ if ("color" in z) o.color = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(String(z.color)) ? String(z.color) : "#1a2332";
694
+ if ("mask" in z) { const n = Number(z.mask); o.mask = Number.isFinite(n) ? Math.max(0, Math.min(0.95, n)) : 0.28; }
695
+ bt[key] = Object.fromEntries(Object.entries(o).filter(([, v]) => v !== undefined));
696
+ }
697
+ }
698
+ if (Object.keys(bt).length) out.bgTint = bt;
699
+ }
700
+ // 输入框(独立于 zone.input 的 DSH 玻璃变量):浅/深主题各一套 custom color + opacity(1=实底)。color 空串=用主题原生色
701
+ if (input.inputBox && typeof input.inputBox === "object") {
702
+ const ib = {};
703
+ for (const key of ["light", "dark"]) {
704
+ const z = input.inputBox[key];
705
+ if (z && typeof z === "object") {
706
+ const o = {};
707
+ const c = z.color;
708
+ if (c === "" || c === null || c === undefined) o.color = "";
709
+ else o.color = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(String(c)) ? String(c) : "";
710
+ if ("opacity" in z) { const n = Number(z.opacity); o.opacity = Number.isFinite(n) ? Math.max(0, Math.min(1, n)) : 1; }
711
+ ib[key] = Object.fromEntries(Object.entries(o).filter(([, v]) => v !== undefined));
712
+ }
713
+ }
714
+ if (Object.keys(ib).length) out.inputBox = ib;
715
+ }
686
716
  // 会话区/输入区各自独立罩色+罩强度
687
717
  if (input.zone && typeof input.zone === "object") {
688
718
  const zo = {};
@@ -1595,6 +1625,8 @@ window.addEventListener('resize',function(){ try{ if(fitMode) zoomFit(); else ap
1595
1625
  // color=叠加/罩色;bgImage=共用背景图。顶栏/左栏的"罩"与整页共用 mask+color(不再单独存,简化)。
1596
1626
  const GLASS_DEFAULTS = {
1597
1627
  enabled: false, blur: 20, bgImage: "", bgColor: "#1a2332", bgMask: 0.28,
1628
+ bgTint: { light: { color: "#1a2332", mask: 0.28 }, dark: { color: "#1a2332", mask: 0.28 } },
1629
+ inputBox: { light: { color: "", opacity: 1 }, dark: { color: "", opacity: 1 } },
1598
1630
  zone: { session: { color: "#1a2332", mask: 0.45, opacity: 0.5 }, input: { color: "#1a2332", mask: 0.6, opacity: 0.55 } },
1599
1631
  };
1600
1632
  const glassDir = resolve(homedir(), ".dsh-long-plugins");
@@ -1614,6 +1646,8 @@ window.addEventListener('resize',function(){ try{ if(fitMode) zoomFit(); else ap
1614
1646
  enabled: cfg.enabled, blur: cfg.blur,
1615
1647
  session: cfg.zone?.session || { enabled: true, color: "#1a2332", mask: 0.45, opacity: 0.5 },
1616
1648
  input: cfg.zone?.input || { color: "#1a2332", mask: 0.6, opacity: 0.55 },
1649
+ inputBox: cfg.inputBox || { light: { color: "", opacity: 1 }, dark: { color: "", opacity: 1 } },
1650
+ bgTint: cfg.bgTint || { light: { color: "#1a2332", mask: 0.28 }, dark: { color: "#1a2332", mask: 0.28 } },
1617
1651
  bgColor: cfg.bgColor || "#1a2332", bgMask: cfg.bgMask ?? 0.28,
1618
1652
  bgImage: cfg.bgImage ? { present: true, bytes: Math.round((cfg.bgImage.length * 3) / 4) } : { present: false },
1619
1653
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-long-plugins",
3
- "version": "2.2.0",
3
+ "version": "2.2.2",
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",
@@ -73,6 +73,34 @@ cd "$DSH_HOME/profiles/web" && pnpm install
73
73
  4. 提问/审批窗口:弹出一个问题,等 60s+ 不再自己消失(心跳补丁生效)。
74
74
  5. 插件设置面板「上传文件」「输出文件」「技能文档」都在。
75
75
  6. `grep -q WEBSOCKET_HEARTBEAT_MS <核心解压路径>/dsh-client-connection/lib/index.js` → 心跳在位。
76
+ 7. **RA-Span 玻璃观感**:左栏/顶栏文字图标、会话区过程内容配色、输入框、提问卡、耗时统计等是否仍正常(见下文"RA-Span 升级后需重新校准")。
77
+
78
+ ## RA-Span(统一桌面/玻璃)升级后需重新校准 ⚠️
79
+
80
+ dsh-long-plugins 的「RA-Span(统一桌面/玻璃)」**大量依赖 DSH 内部混淆类名**(`o3BgMG_*`/`CY-8Ka_*`/`QWLzLg_*`/`M8wy4a_*`/`uV2eYG_*`/`wSkVaW_*`/`osXY9a_*`/`p-xYUq_*` 等)与 DSH 内部 DOM 结构。DSH 升级重打包后**这些类名前缀会变**,导致插件里靠类名压样式的规则/JS **静默失效**(不报错,只是观感/配色不对)。
81
+
82
+ 升级流程会**同步插件代码(逻辑保住)**,但**不会自动重抓新的类名**,所以升级后必须**人工复核**玻璃观感。
83
+
84
+ ### 升级后重点复核 + 处理
85
+ 1. 浏览器强刷(Ctrl/Cmd+Shift+R)。
86
+ 2. 逐项看 RA-Span 是否仍正常:
87
+ - 左栏/顶栏文字与图标(白字+深影、品牌区不被白化)
88
+ - 会话区过程内容配色(Bash 绿 / Read 蓝 / Edit 橙 / Write 紫 / Think 琥珀、错误红、工具输出水绿)
89
+ - 状态/统计行余额(走 `data-slot-conversation="composer.dock"` 的相对稳)
90
+ - 输入框(无重影、背景/文字随主题、可调色)
91
+ - 「新会话」按钮去白底
92
+ 3. 若某项观感/配色不对 → **新类名变了**。让用户在对应元素「右键 → 检查」,把高亮元素及父级的 class 发我,把 `client/client.js` 里对应的 `[class*="..."]` 前缀更新成新类名(只改选择器,不动逻辑)。
93
+ 4. 稳定项无需动:用 `data-slot-*` 的(状态栏)、注入 `<head>` 的 CSS、`data-dsh-theme` 属性。
94
+
95
+ ### 各依赖类名的功能清单(升级后最可能失效)
96
+ | 功能 | 依赖类名 | 失效表现 |
97
+ |---|---|---|
98
+ | 工具名配色(Bash/Read/Edit/Write) | `o3BgMG_title` / `CY-8Ka_title` | 标签不变色 |
99
+ | Think 配色 | `QWLzLg_*` | Think 不琥珀 |
100
+ | 提问/弹窗不透明 | `M8wy4a_card` | 弹窗透明重叠 |
101
+ | 输入框 | `uV2eYG_*` | 重影/背景不随主题 |
102
+ | 耗时统计 | `osXY9a_*` / `p-xYUq_*` | 统计字淡/重影 |
103
+ | 左栏「新会话」 | `newSession` | 白底回来 |
76
104
 
77
105
  ## 硬性安全边界(必须遵守)
78
106
  - **不主动 push / 打 tag / 发 Release**;需要发布版本时停下用 ask_user_question 等确认。
@@ -86,4 +114,5 @@ cd "$DSH_HOME/profiles/web" && pnpm install
86
114
  | 升级后设置页"未暴露命名空间" | 补丁2 被覆盖 | 重跑 apply-dsh-patches.sh |
87
115
  | 升级后提问窗口自己消失 | 心跳补丁(补丁3)被覆盖 | 重跑 apply-dsh-patches.sh 或装插件补丁脚本 |
88
116
  | 升级后插件不显示 | 插件安装副本未同步 | 步骤 5 `pnpm install` |
117
+ | 升级后 RA-Span 观感/配色不对(输入框重影、标签不变色、弹窗透明、统计字淡等) | DSH 混淆类名变了 | 见 RA-Span 校准节:强刷 + 逐项检查,让用户右键给出新类名,更新 `client/client.js` 对应选择器 |
89
118
  | 按钮全没了/界面异常 | 浏览器缓存了旧前端 | 强刷 Ctrl/Cmd+Shift+R |