dsh-long-plugins 2.2.3 → 2.4.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 +280 -114
- package/dsh.plugin.json +1 -1
- package/lib/index.js +124 -13
- package/package.json +1 -1
- package/patches/dsh-client-connection-heartbeat.sh +8 -3
- package/skill/dsh-long-plugins-install/SKILL.md +11 -0
- package/skill/dsh-upgrade/SKILL.md +13 -29
package/client/client.js
CHANGED
|
@@ -529,17 +529,27 @@ window.__ModuleLoader__.load({
|
|
|
529
529
|
}
|
|
530
530
|
}
|
|
531
531
|
}
|
|
532
|
-
window.
|
|
533
|
-
window.
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
532
|
+
const dragOn = !!(window.__dshLongMod && window.__dshLongMod('uploadDragDrop'))
|
|
533
|
+
const pasteOn = !!(window.__dshLongMod && window.__dshLongMod('uploadPaste'))
|
|
534
|
+
if (dragOn) {
|
|
535
|
+
window.addEventListener('dragenter', onDragEnter, true)
|
|
536
|
+
window.addEventListener('dragover', onDragOver, true)
|
|
537
|
+
window.addEventListener('dragleave', onDragLeave, true)
|
|
538
|
+
window.addEventListener('drop', onDrop, true)
|
|
539
|
+
}
|
|
540
|
+
if (pasteOn) {
|
|
541
|
+
window.addEventListener('paste', onPaste, true)
|
|
542
|
+
}
|
|
537
543
|
return () => {
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
544
|
+
if (dragOn) {
|
|
545
|
+
window.removeEventListener('dragenter', onDragEnter, true)
|
|
546
|
+
window.removeEventListener('dragover', onDragOver, true)
|
|
547
|
+
window.removeEventListener('dragleave', onDragLeave, true)
|
|
548
|
+
window.removeEventListener('drop', onDrop, true)
|
|
549
|
+
}
|
|
550
|
+
if (pasteOn) {
|
|
551
|
+
window.removeEventListener('paste', onPaste, true)
|
|
552
|
+
}
|
|
543
553
|
}
|
|
544
554
|
}, [controller, sessionId])
|
|
545
555
|
|
|
@@ -1367,25 +1377,52 @@ window.__ModuleLoader__.load({
|
|
|
1367
1377
|
}
|
|
1368
1378
|
|
|
1369
1379
|
function apply(ctx) {
|
|
1370
|
-
const
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1380
|
+
const mod = window.__dshLongMod || (() => true)
|
|
1381
|
+
const anyUpload = mod('uploadAttach') || mod('uploadDragDrop') || mod('uploadPaste')
|
|
1382
|
+
if (anyUpload) {
|
|
1383
|
+
const controller = new FileDraftController(ctx)
|
|
1384
|
+
ctx.effect(() => () => controller.dispose(), 'dsh-upload-manager: draft-file state')
|
|
1385
|
+
ctx.effect(() => ctx.inputTriggers.registerSource({
|
|
1386
|
+
trigger: '@',
|
|
1387
|
+
name: SOURCE,
|
|
1388
|
+
order: 10_000,
|
|
1389
|
+
candidates: async () => [],
|
|
1390
|
+
onPick: () => undefined,
|
|
1391
|
+
codec: {
|
|
1392
|
+
clipboardText: () => '',
|
|
1393
|
+
serialize: async (ref) => {
|
|
1394
|
+
const file = controller.fileForRef(ref)
|
|
1395
|
+
if (!file) throw new Error('待发送文件已失效,请重新上传')
|
|
1396
|
+
controller.markSerializing(ref)
|
|
1397
|
+
return serializedFile(file)
|
|
1398
|
+
},
|
|
1385
1399
|
},
|
|
1386
|
-
},
|
|
1387
|
-
|
|
1388
|
-
|
|
1400
|
+
}), 'dsh-upload-manager: hidden file reference codec')
|
|
1401
|
+
// 输入框附件上传(回形针) + 待发送文件栏:由 uploadAttach 控制
|
|
1402
|
+
if (mod('uploadAttach')) {
|
|
1403
|
+
ctx.slots.inject('conversation.input.left', () => ctx.slots.register({
|
|
1404
|
+
name: 'conversation.input.left',
|
|
1405
|
+
id: 'local-file-upload',
|
|
1406
|
+
order: -20,
|
|
1407
|
+
label: '上传文件',
|
|
1408
|
+
}, (props) => React.createElement(UploadControl, { ...props, controller })))
|
|
1409
|
+
ctx.slots.inject('conversation.input.dock', () => ctx.slots.register({
|
|
1410
|
+
name: 'conversation.input.dock',
|
|
1411
|
+
id: 'local-file-upload-rail',
|
|
1412
|
+
order: 80,
|
|
1413
|
+
label: '待发送文件',
|
|
1414
|
+
}, (props) => React.createElement(PendingFileRail, { ...props, controller })))
|
|
1415
|
+
}
|
|
1416
|
+
// 拖放上传(遮罩+window 监听) / 粘贴上传:由各自开关控制
|
|
1417
|
+
if (mod('uploadDragDrop') || mod('uploadPaste')) {
|
|
1418
|
+
ctx.slots.inject('conversation.input.dock', () => ctx.slots.register({
|
|
1419
|
+
name: 'conversation.input.dock',
|
|
1420
|
+
id: 'local-file-upload-dropzone',
|
|
1421
|
+
order: 90,
|
|
1422
|
+
label: '拖放/粘贴上传',
|
|
1423
|
+
}, (props) => React.createElement(DragDropOverlay, { ...props, controller })))
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1389
1426
|
ctx.effect(() => {
|
|
1390
1427
|
const style = document.createElement('style')
|
|
1391
1428
|
style.dataset.plugin = 'dsh-file-uploads'
|
|
@@ -1393,41 +1430,24 @@ window.__ModuleLoader__.load({
|
|
|
1393
1430
|
document.head.appendChild(style)
|
|
1394
1431
|
return () => style.remove()
|
|
1395
1432
|
}, 'dsh-upload-manager: styles')
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
order: 90,
|
|
1415
|
-
label: '拖放上传',
|
|
1416
|
-
}, (props) => React.createElement(DragDropOverlay, { ...props, controller })))
|
|
1417
|
-
|
|
1418
|
-
ctx.slots.inject('settings.section', () => ctx.slots.register({
|
|
1419
|
-
name: 'settings.section',
|
|
1420
|
-
id: 'uploaded-files',
|
|
1421
|
-
order: 30,
|
|
1422
|
-
label: '上传文件',
|
|
1423
|
-
}, UploadSettingsSection))
|
|
1424
|
-
|
|
1425
|
-
ctx.slots.inject('settings.section', () => ctx.slots.register({
|
|
1426
|
-
name: 'settings.section',
|
|
1427
|
-
id: 'output-files',
|
|
1428
|
-
order: 35,
|
|
1429
|
-
label: '输出文件',
|
|
1430
|
-
}, WorkspaceFilesSection))
|
|
1433
|
+
// 上传文件预览/管理设置区:由 uploadPreview 控制
|
|
1434
|
+
if (mod('uploadPreview')) {
|
|
1435
|
+
ctx.slots.inject('settings.section', () => ctx.slots.register({
|
|
1436
|
+
name: 'settings.section',
|
|
1437
|
+
id: 'uploaded-files',
|
|
1438
|
+
order: 30,
|
|
1439
|
+
label: '上传文件',
|
|
1440
|
+
}, UploadSettingsSection))
|
|
1441
|
+
}
|
|
1442
|
+
// 输出文件(工作区)设置区:由 workspace 控制
|
|
1443
|
+
if (mod('workspace')) {
|
|
1444
|
+
ctx.slots.inject('settings.section', () => ctx.slots.register({
|
|
1445
|
+
name: 'settings.section',
|
|
1446
|
+
id: 'output-files',
|
|
1447
|
+
order: 35,
|
|
1448
|
+
label: '输出文件',
|
|
1449
|
+
}, WorkspaceFilesSection))
|
|
1450
|
+
}
|
|
1431
1451
|
}
|
|
1432
1452
|
return { apply, inject }
|
|
1433
1453
|
})()
|
|
@@ -1961,7 +1981,9 @@ window.__ModuleLoader__.load({
|
|
|
1961
1981
|
if (!cancelled) setBalance(null);
|
|
1962
1982
|
}
|
|
1963
1983
|
};
|
|
1964
|
-
|
|
1984
|
+
// 首次拉取延迟到浏览器空闲(减轻首屏网络风暴), 60s 轮询不变
|
|
1985
|
+
const schedule = () => { try { if (window.requestIdleCallback) window.requestIdleCallback(() => load(), { timeout: 300 }); else setTimeout(load, 300) } catch (_) { load() } };
|
|
1986
|
+
schedule();
|
|
1965
1987
|
const interval = setInterval(load, 60000);
|
|
1966
1988
|
return () => {
|
|
1967
1989
|
cancelled = true;
|
|
@@ -2003,7 +2025,9 @@ window.__ModuleLoader__.load({
|
|
|
2003
2025
|
if (!cancelled) setCost(null);
|
|
2004
2026
|
}
|
|
2005
2027
|
};
|
|
2006
|
-
|
|
2028
|
+
// 首次拉取延迟到空闲(减轻首屏网络风暴), 60s 轮询不变
|
|
2029
|
+
const schedule = () => { try { if (window.requestIdleCallback) window.requestIdleCallback(() => load(), { timeout: 300 }); else setTimeout(load, 300) } catch (_) { load() } };
|
|
2030
|
+
schedule();
|
|
2007
2031
|
const interval = setInterval(load, 60000);
|
|
2008
2032
|
return () => {
|
|
2009
2033
|
cancelled = true;
|
|
@@ -2058,18 +2082,21 @@ window.__ModuleLoader__.load({
|
|
|
2058
2082
|
const inject = ["slots", "locale"];
|
|
2059
2083
|
/** Register the token-usage card into the sidebar footer. */
|
|
2060
2084
|
function apply(ctx) {
|
|
2061
|
-
injectMobileCss();
|
|
2085
|
+
injectMobileCss(); // 移动端布局 CSS 始终注入(与余额开关解耦)
|
|
2062
2086
|
ctx.effect(() => ctx.locale.register(NS, {
|
|
2063
2087
|
zh,
|
|
2064
2088
|
en
|
|
2065
2089
|
}), "dsh-token-usage: dictionaries");
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2090
|
+
// 余额 chip:仅当"余额"模块开启时注册(禁用余额时保留移动布局)
|
|
2091
|
+
if (!(window.__dshLongModules && window.__dshLongModules.balance === false)) {
|
|
2092
|
+
ctx.slots.inject("conversation.composer.dock", () => ctx.slots.register({
|
|
2093
|
+
name: "conversation.composer.dock",
|
|
2094
|
+
id: "token-balance",
|
|
2095
|
+
order: -10,
|
|
2096
|
+
locale: NS,
|
|
2097
|
+
inject: () => ({})
|
|
2098
|
+
}, BalanceChip));
|
|
2099
|
+
}
|
|
2073
2100
|
}
|
|
2074
2101
|
return { apply, inject }
|
|
2075
2102
|
})()
|
|
@@ -2433,6 +2460,11 @@ window.__ModuleLoader__.load({
|
|
|
2433
2460
|
.dsh-turn-preview{width:min(360px,88vw);height:min(70vh,520px);top:50%!important;left:50%!important;transform:translate(-50%,-50%)!important}
|
|
2434
2461
|
|
|
2435
2462
|
}
|
|
2463
|
+
/* 会话导航开关(off 时隐藏轮次刻尺/手机把手):与 DSH 原生回合导航避免冲突 */
|
|
2464
|
+
html[data-dsh-turnruler="off"] .dsh-turn-ruler,
|
|
2465
|
+
html[data-dsh-turnruler="off"] .dsh-turn-phone-tab {
|
|
2466
|
+
display: none !important;
|
|
2467
|
+
}
|
|
2436
2468
|
`
|
|
2437
2469
|
const turnRulerPlugin = {
|
|
2438
2470
|
inject: [],
|
|
@@ -2463,6 +2495,11 @@ window.__ModuleLoader__.load({
|
|
|
2463
2495
|
let scrollAnim = 0
|
|
2464
2496
|
let scrollTarget = -1
|
|
2465
2497
|
let scrollFrom = 0
|
|
2498
|
+
// 会话导航开关:从 glass 配置读取 turnRuler;false=off 隐藏刻尺(避免与 DSH 原生回合导航冲突)
|
|
2499
|
+
;(() => {
|
|
2500
|
+
const setVis = (en) => { try { document.documentElement.setAttribute('data-dsh-turnruler', en ? 'on' : 'off') } catch (_) {} }
|
|
2501
|
+
fetch('/api/dsh-uploads/glass-config', { cache: 'no-store' }).then((r) => r.json()).then((b) => { if (b && b.cfg) setVis((b.cfg.modules && b.cfg.modules.turnRuler) !== false) }).catch(() => {})
|
|
2502
|
+
})()
|
|
2466
2503
|
|
|
2467
2504
|
const ensureRuler = () => {
|
|
2468
2505
|
if (ruler && ruler.isConnected) return ruler
|
|
@@ -3133,6 +3170,7 @@ window.__ModuleLoader__.load({
|
|
|
3133
3170
|
color: #ffffff !important;
|
|
3134
3171
|
text-shadow: 0 1px 1px rgba(0,0,0,.5);
|
|
3135
3172
|
}
|
|
3173
|
+
/* (移除 color/text-shadow 过渡: 会在收尾重渲染时短暂贴默认色导致"闪黑") */
|
|
3136
3174
|
/* DSH 弹窗/浮层(提问/确认/重命名等):给不透明底+主题文字, 杜绝透明与会话内容重叠 */
|
|
3137
3175
|
html[data-dsh-glass="on"] [class*="Radix"] [class*="Content"],
|
|
3138
3176
|
html[data-dsh-glass="on"] [class*="DialogContent"],
|
|
@@ -3152,10 +3190,10 @@ window.__ModuleLoader__.load({
|
|
|
3152
3190
|
backdrop-filter: blur(var(--dsh-glass-blur, 16px)) !important;
|
|
3153
3191
|
-webkit-backdrop-filter: blur(var(--dsh-glass-blur, 16px)) !important;
|
|
3154
3192
|
}
|
|
3155
|
-
/* 任务/todo 面板(lXshSW_root, 在 conversation.input.dock 内)
|
|
3193
|
+
/* 任务/todo 面板(lXshSW_root, 在 conversation.input.dock 内):半透明磨砂底+可读文字 */
|
|
3156
3194
|
html[data-dsh-glass="on"] [class*="lXshSW_root"],
|
|
3157
3195
|
html[data-dsh-glass="on"] [class*="lXshSW_body"] {
|
|
3158
|
-
background-color: color-mix(in srgb, var(--dsw-specific-input-major, #e8edf3)
|
|
3196
|
+
background-color: color-mix(in srgb, var(--dsw-specific-input-major, #e8edf3) 62%, transparent) !important;
|
|
3159
3197
|
color: var(--dsw-alias-label-primary) !important;
|
|
3160
3198
|
backdrop-filter: blur(var(--dsh-glass-blur, 16px)) !important;
|
|
3161
3199
|
-webkit-backdrop-filter: blur(var(--dsh-glass-blur, 16px)) !important;
|
|
@@ -3165,6 +3203,19 @@ window.__ModuleLoader__.load({
|
|
|
3165
3203
|
html[data-dsh-glass="on"] [class*="lXshSW_item"] {
|
|
3166
3204
|
color: var(--dsw-alias-label-primary) !important;
|
|
3167
3205
|
}
|
|
3206
|
+
/* 排队消息/等待任务面板(_7yHdaG_dock/panel, 在 conversation.input.dock 内):不透明/磨砂底+可读文字 */
|
|
3207
|
+
html[data-dsh-glass="on"] [class*="_7yHdaG_panel"],
|
|
3208
|
+
html[data-dsh-glass="on"] [class*="_7yHdaG_dock"] {
|
|
3209
|
+
background-color: color-mix(in srgb, var(--dsw-specific-input-major, #e8edf3) 62%, transparent) !important;
|
|
3210
|
+
color: var(--dsw-alias-label-primary) !important;
|
|
3211
|
+
backdrop-filter: blur(var(--dsh-glass-blur, 16px)) !important;
|
|
3212
|
+
-webkit-backdrop-filter: blur(var(--dsh-glass-blur, 16px)) !important;
|
|
3213
|
+
}
|
|
3214
|
+
html[data-dsh-glass="on"] [class*="_7yHdaG_header"],
|
|
3215
|
+
html[data-dsh-glass="on"] [class*="_7yHdaG_preview"],
|
|
3216
|
+
html[data-dsh-glass="on"] [class*="_7yHdaG_row"] {
|
|
3217
|
+
color: var(--dsw-alias-label-primary) !important;
|
|
3218
|
+
}
|
|
3168
3219
|
/* 轮次/历史提问预览:默认隐藏(点开才显示),并去掉头部底色,避免底部露出白条 */
|
|
3169
3220
|
.dsh-turn-preview:not(.open){display:none !important;}
|
|
3170
3221
|
.dsh-turn-preview-head{background:transparent !important;}
|
|
@@ -3224,18 +3275,27 @@ window.__ModuleLoader__.load({
|
|
|
3224
3275
|
backdrop-filter: none !important;
|
|
3225
3276
|
-webkit-backdrop-filter: none !important;
|
|
3226
3277
|
}
|
|
3227
|
-
/* 输入框旁权限/功能选择下拉(盾牌 Full access 等
|
|
3228
|
-
html[data-dsh-glass="on"] #root [class*="uV2eYG_card"] [class*="_root_"]
|
|
3229
|
-
|
|
3230
|
-
|
|
3278
|
+
/* 输入框旁权限/功能选择下拉(盾牌 Full access 等):给不透明底+可读文字(高优先级, 压过 DSH 透明覆盖) */
|
|
3279
|
+
html[data-dsh-glass="on"] #root [class*="uV2eYG_card"] [class*="_root_"] [class*="_list_"],
|
|
3280
|
+
html[data-dsh-glass="on"] #root [class*="uV2eYG_card"] [class*="_root_"] [class*="_list_"] [class*="_viewport_"],
|
|
3281
|
+
html[data-dsh-glass="on"] #root [class*="uV2eYG_card"] [class*="_list_"] [class*="_viewport_"] [class*="_itemWrap_"] {
|
|
3282
|
+
background-color: var(--dsw-specific-input-major, #e8edf3) !important;
|
|
3231
3283
|
backdrop-filter: none !important;
|
|
3232
3284
|
-webkit-backdrop-filter: none !important;
|
|
3233
3285
|
}
|
|
3234
|
-
html[data-dsh-glass="on"] #root [class*="uV2eYG_card"] [class*="
|
|
3235
|
-
|
|
3236
|
-
|
|
3286
|
+
html[data-dsh-glass="on"] #root [class*="uV2eYG_card"] [class*="_item_"],
|
|
3287
|
+
html[data-dsh-glass="on"] #root [class*="uV2eYG_card"] [class*="_itemLabel_"] {
|
|
3288
|
+
color: var(--dsw-alias-label-primary) !important;
|
|
3289
|
+
}
|
|
3290
|
+
/* 兜底: Radix 风格菜单/下拉(可能被渲染到 body 顶层) */
|
|
3291
|
+
html[data-dsh-glass="on"] [class*="Radix"] [class*="Menu"],
|
|
3292
|
+
html[data-dsh-glass="on"] [class*="radix-menu"],
|
|
3293
|
+
html[data-dsh-glass="on"] [class*="menuitem"],
|
|
3294
|
+
html[data-dsh-glass="on"] [role="menu"] {
|
|
3295
|
+
background-color: var(--dsw-specific-input-major, #e8edf3) !important;
|
|
3237
3296
|
backdrop-filter: none !important;
|
|
3238
3297
|
-webkit-backdrop-filter: none !important;
|
|
3298
|
+
color: var(--dsw-alias-label-primary) !important;
|
|
3239
3299
|
}
|
|
3240
3300
|
/* 玻璃开启 + 浅色系统主题:加深弱化标签/元信息文字,避免浅灰字叠在浅磨砂卡上看不清 */
|
|
3241
3301
|
@media (prefers-color-scheme: light) {
|
|
@@ -3245,6 +3305,8 @@ window.__ModuleLoader__.load({
|
|
|
3245
3305
|
--dsw-alias-label-tertiary: #50545b !important;
|
|
3246
3306
|
}
|
|
3247
3307
|
}
|
|
3308
|
+
`
|
|
3309
|
+
const GLASS_LAYOUT_CSS = `
|
|
3248
3310
|
.dsh-glass-section{display:flex;flex-direction:column;gap:16px;min-width:0;padding:4px 2px 24px;color:var(--dsw-alias-label-primary)}
|
|
3249
3311
|
.dsh-glass-head{display:flex;align-items:center;justify-content:space-between;gap:16px}
|
|
3250
3312
|
.dsh-glass-head h2{margin:0;font-size:20px;line-height:28px}
|
|
@@ -3319,6 +3381,80 @@ window.__ModuleLoader__.load({
|
|
|
3319
3381
|
),
|
|
3320
3382
|
)
|
|
3321
3383
|
}
|
|
3384
|
+
// 「dsh-long」设置区:各模块开关(独立、移动端干净),避免把开关塞进 RA-Span
|
|
3385
|
+
const DSH_LONG_MODULES = [
|
|
3386
|
+
['glass', 'RA-Span'],
|
|
3387
|
+
['turnRuler', '会话导航(轮次刻尺)'],
|
|
3388
|
+
['uploadAttach', '附件上传(回形针)'],
|
|
3389
|
+
['uploadDragDrop', '附件拖放上传'],
|
|
3390
|
+
['uploadPaste', '附件粘贴上传'],
|
|
3391
|
+
['uploadPreview', '上传文件预览/管理'],
|
|
3392
|
+
['skillDocs', '技能文档'],
|
|
3393
|
+
['balance', '账户余额'],
|
|
3394
|
+
['workspace', '输出文件预览/管理'],
|
|
3395
|
+
['mobile', '移动端布局'],
|
|
3396
|
+
]
|
|
3397
|
+
const DshLongSettingsSection = () => {
|
|
3398
|
+
const [state, setState] = React.useState({ loading: true, saving: false, error: '', modules: null, patches: null })
|
|
3399
|
+
const load = React.useCallback(async () => {
|
|
3400
|
+
setState((s) => ({ ...s, loading: true, error: '', patches: null }))
|
|
3401
|
+
try {
|
|
3402
|
+
const [r, pr] = await Promise.all([
|
|
3403
|
+
fetch('/api/dsh-uploads/glass-config', { cache: 'no-store' }),
|
|
3404
|
+
fetch('/api/dsh-uploads/patch-status', { cache: 'no-store' }),
|
|
3405
|
+
])
|
|
3406
|
+
const b = await r.json()
|
|
3407
|
+
if (!r.ok) throw new Error(b.error || ('HTTP ' + r.status))
|
|
3408
|
+
let patches = null
|
|
3409
|
+
try { const pb = await pr.json(); patches = pb.patches } catch (_) {}
|
|
3410
|
+
setState((s) => ({ ...s, loading: false, modules: b.cfg.modules || {}, patches }))
|
|
3411
|
+
} catch (e) { setState((s) => ({ ...s, loading: false, error: String((e && e.message) || e) })) }
|
|
3412
|
+
}, [])
|
|
3413
|
+
React.useEffect(() => { load(); }, [load])
|
|
3414
|
+
const setMod = React.useCallback((k, v) => setState((s) => ({ ...s, modules: { ...(s.modules || {}), [k]: v } })), [])
|
|
3415
|
+
const save = React.useCallback(async () => {
|
|
3416
|
+
setState((s) => ({ ...s, saving: true, error: '' }))
|
|
3417
|
+
try {
|
|
3418
|
+
const r = await fetch('/api/dsh-uploads/glass-config', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ modules: state.modules }) })
|
|
3419
|
+
const b = await r.json()
|
|
3420
|
+
if (!r.ok) throw new Error(b.error || ('HTTP ' + r.status))
|
|
3421
|
+
window.__dshLongModules = state.modules
|
|
3422
|
+
try { localStorage.setItem('dsh-long:modules', JSON.stringify(state.modules)) } catch (e) {}
|
|
3423
|
+
setState((s) => ({ ...s, saving: false, error: '' }))
|
|
3424
|
+
} catch (e) { setState((s) => ({ ...s, saving: false, error: String((e && e.message) || e) })) }
|
|
3425
|
+
}, [state.modules])
|
|
3426
|
+
const PATCH_LABELS = { trustedHosts: 'privileged 信任 trustedHosts', loopback: '反代域名视为 loopback', heartbeat: 'WebSocket 心跳' }
|
|
3427
|
+
const pstat = (st) => st === '已打' ? { text: '已打', cls: 'ok' } : (st === '原生无需' ? { text: '原生无需', cls: 'na' } : { text: '未打', cls: 'warn' })
|
|
3428
|
+
if (state.loading) return React.createElement('div', { className: 'dsh-glass-section' }, '加载中…')
|
|
3429
|
+
return React.createElement('div', { className: 'dsh-glass-section' },
|
|
3430
|
+
React.createElement('div', { className: 'dsh-glass-head' },
|
|
3431
|
+
React.createElement('div', null, React.createElement('h2', null, 'dsh-long'), React.createElement('p', null, '各模块开关:关掉某模块即禁用其功能(若新版 DSH 自带某项,可关掉插件对应功能避免冲突)。')),
|
|
3432
|
+
),
|
|
3433
|
+
state.error ? React.createElement('div', { className: 'dsh-glass-error' }, state.error) : null,
|
|
3434
|
+
React.createElement('div', { className: 'dsh-glass-card' },
|
|
3435
|
+
DSH_LONG_MODULES.map(([k, label]) => React.createElement('div', { key: k, className: 'dsh-glass-row' },
|
|
3436
|
+
React.createElement('label', null, label),
|
|
3437
|
+
React.createElement('label', { className: 'dsh-glass-switch' }, React.createElement('input', { type: 'checkbox', checked: (state.modules ? state.modules[k] !== false : true), onChange: (e) => setMod(k, e.target.checked) }), (state.modules ? state.modules[k] !== false : true) ? '已开启' : '已关闭'),
|
|
3438
|
+
)),
|
|
3439
|
+
React.createElement('div', { className: 'dsh-glass-actions' },
|
|
3440
|
+
React.createElement('button', { type: 'button', onClick: load }, '重置'),
|
|
3441
|
+
React.createElement('button', { type: 'button', className: 'primary', disabled: state.saving, onClick: save }, state.saving ? '保存中…' : '保存生效'),
|
|
3442
|
+
),
|
|
3443
|
+
),
|
|
3444
|
+
state.patches ? React.createElement('div', { className: 'dsh-glass-card' },
|
|
3445
|
+
React.createElement('div', { className: 'dsh-glass-zone-title' }, '补丁状态(只读)'),
|
|
3446
|
+
typeof state.patches === 'string' ? React.createElement('p', { className: 'dsh-glass-note' }, state.patches)
|
|
3447
|
+
: Object.keys(PATCH_LABELS).map((k) => {
|
|
3448
|
+
const st = state.patches[k]
|
|
3449
|
+
const info = pstat(st)
|
|
3450
|
+
return React.createElement('div', { key: k, className: 'dsh-glass-row' },
|
|
3451
|
+
React.createElement('label', null, PATCH_LABELS[k]),
|
|
3452
|
+
React.createElement('span', { className: 'dsh-glass-val' + (info.cls === 'ok' ? '' : (info.cls === 'na' ? ' dsh-glass-na' : ' dsh-glass-warn')) }, info.text),
|
|
3453
|
+
)
|
|
3454
|
+
})
|
|
3455
|
+
) : null,
|
|
3456
|
+
)
|
|
3457
|
+
}
|
|
3322
3458
|
const GlassSettingsSection = () => {
|
|
3323
3459
|
const [state, setState] = React.useState({ loading: true, saving: false, error: '', cfg: null, bgImage: '', bgList: [] })
|
|
3324
3460
|
const load = React.useCallback(async () => {
|
|
@@ -3329,7 +3465,7 @@ window.__ModuleLoader__.load({
|
|
|
3329
3465
|
if (!r.ok) throw new Error(b.error || ('HTTP ' + r.status))
|
|
3330
3466
|
let list = []
|
|
3331
3467
|
try { const rb = await fetch('/api/dsh-uploads/glass-backgrounds', { cache: 'no-store' }); const jb = await rb.json(); list = jb.list || [] } catch (_) {}
|
|
3332
|
-
setState((s) => ({ ...s, loading: false, cfg: b.cfg, bgImage: b.
|
|
3468
|
+
setState((s) => ({ ...s, loading: false, cfg: b.cfg, bgImage: b.bgImageUrl || '', bgList: list }))
|
|
3333
3469
|
} catch (e) { setState((s) => ({ ...s, loading: false, error: glassText(e) })) }
|
|
3334
3470
|
}, [])
|
|
3335
3471
|
React.useEffect(() => { load(); }, [load])
|
|
@@ -3352,9 +3488,16 @@ window.__ModuleLoader__.load({
|
|
|
3352
3488
|
try {
|
|
3353
3489
|
const cfg = state.cfg || {}
|
|
3354
3490
|
const num = (v, fb) => ((typeof v === 'number' && Number.isFinite(v) && v >= 0) ? v : fb)
|
|
3491
|
+
// 背景图: 仅当用户上传了新图(data-URI)才传; 未改(URL)省略以保持引用; 清空传 '' 清除
|
|
3492
|
+
const sBg = state.bgImage
|
|
3493
|
+
let bgImageOut
|
|
3494
|
+
if (sBg === '') bgImageOut = ''
|
|
3495
|
+
else if (typeof sBg === 'string' && sBg.startsWith('data:image/')) bgImageOut = sBg
|
|
3496
|
+
else bgImageOut = undefined
|
|
3355
3497
|
const payload = {
|
|
3356
3498
|
enabled: !!cfg.enabled, blur: num(cfg.blur, 0),
|
|
3357
|
-
|
|
3499
|
+
bgMask: num(cfg.bgMask, 0.28), bgColor: cfg.bgColor || '#1a2332', bgBlur: num(cfg.bgBlur, 0), bgOpacity: num(cfg.bgOpacity, 1),
|
|
3500
|
+
...(bgImageOut !== undefined ? { bgImage: bgImageOut } : {}),
|
|
3358
3501
|
bgTint: {
|
|
3359
3502
|
light: { color: (cfg.bgTint?.light?.color) || '#1a2332', mask: num(cfg.bgTint?.light?.mask, 0.28) },
|
|
3360
3503
|
dark: { color: (cfg.bgTint?.dark?.color) || '#1a2332', mask: num(cfg.bgTint?.dark?.mask, 0.28) },
|
|
@@ -3371,8 +3514,9 @@ window.__ModuleLoader__.load({
|
|
|
3371
3514
|
const r = await fetch('/api/dsh-uploads/glass-config', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(payload) })
|
|
3372
3515
|
const b = await r.json()
|
|
3373
3516
|
if (!r.ok) throw new Error(b.error || ('HTTP ' + r.status))
|
|
3374
|
-
|
|
3375
|
-
|
|
3517
|
+
const applyBg = (payload.bgImage && payload.bgImage.startsWith('data:')) ? payload.bgImage : ((state.bgImage && state.bgImage.startsWith('/')) ? state.bgImage : '')
|
|
3518
|
+
window.__dshGlassApply && window.__dshGlassApply({ ...payload, bgImage: applyBg })
|
|
3519
|
+
setState((s) => ({ ...s, saving: false, cfg: { ...(s.cfg || {}), ...payload }, bgImage: applyBg, error: '' }))
|
|
3376
3520
|
} catch (e) { setState((s) => ({ ...s, saving: false, error: glassText(e) })) }
|
|
3377
3521
|
}, [state])
|
|
3378
3522
|
const cfg = state.cfg || {}
|
|
@@ -3466,7 +3610,7 @@ window.__ModuleLoader__.load({
|
|
|
3466
3610
|
const style = document.createElement('style')
|
|
3467
3611
|
style.dataset.plugin = 'dsh-long-plugins'
|
|
3468
3612
|
style.dataset.pluginCss = 'dsh-long-plugins/glass'
|
|
3469
|
-
style.textContent = GLASS_CSS
|
|
3613
|
+
style.textContent = GLASS_CSS // 玻璃视觉规则(仅 RA-Span 开启时注入)
|
|
3470
3614
|
document.head.appendChild(style)
|
|
3471
3615
|
return () => style.remove()
|
|
3472
3616
|
}, 'dsh-long-plugins: glass styles')
|
|
@@ -3586,18 +3730,15 @@ window.__ModuleLoader__.load({
|
|
|
3586
3730
|
if (isInSkip(root)) return
|
|
3587
3731
|
root.querySelectorAll('div,span,button,a,label,p,li,h1,h2,h3,h4').forEach((el) => {
|
|
3588
3732
|
if (isInSkip(el)) return
|
|
3589
|
-
|
|
3590
|
-
const hasText = el.textContent && el.textContent.trim()
|
|
3591
|
-
if (!hasText) return
|
|
3733
|
+
if (el.dataset && el.dataset.dshgl) return // 已处理过, 跳过
|
|
3592
3734
|
const ownText = Array.from(el.childNodes).some((n) => n.nodeType === 3 && n.textContent.trim())
|
|
3593
|
-
const textLike = /^(BUTTON|A|SPAN|LABEL|LI|P|H1|H2|H3|H4)$/.test(
|
|
3735
|
+
const textLike = /^(BUTTON|A|SPAN|LABEL|LI|P|H1|H2|H3|H4)$/.test(el.tagName)
|
|
3594
3736
|
if (!textLike && !ownText) return
|
|
3595
|
-
// 跳过有盒子背景的元素(
|
|
3596
|
-
|
|
3597
|
-
const bgbg = getComputedStyle(el).backgroundColor
|
|
3598
|
-
if (bgbg && bgbg !== 'rgba(0, 0, 0, 0)' && bgbg !== 'transparent') return
|
|
3737
|
+
// 跳过有盒子背景的元素(用类名判断, 不再 getComputedStyle 以免每元素强制样式重算导致卡顿)
|
|
3738
|
+
if (/bubble|badge|chip|tag|pill|code-block|md-code|n_block|n_banner|n_plain|button/i.test(el.className)) return
|
|
3599
3739
|
el.style.setProperty('color', '#ffffff', 'important')
|
|
3600
3740
|
el.style.setProperty('text-shadow', '0 1px 1px rgba(0,0,0,.5)')
|
|
3741
|
+
if (el.dataset) el.dataset.dshgl = '1'
|
|
3601
3742
|
})
|
|
3602
3743
|
// 图标也变白(随 currentColor),用于工作区文件夹等单色图标;保留显式品牌色
|
|
3603
3744
|
root.querySelectorAll('svg, [class*="icon"], [class*="Icon"]').forEach((el) => {
|
|
@@ -3622,15 +3763,14 @@ window.__ModuleLoader__.load({
|
|
|
3622
3763
|
if (!scroll) return
|
|
3623
3764
|
scroll.querySelectorAll('div,span,p,li,pre,code,button,a,label,h1,h2,h3,h4').forEach((el) => {
|
|
3624
3765
|
if (isInSkip(el)) return
|
|
3625
|
-
|
|
3626
|
-
if (!hasText) return
|
|
3766
|
+
if (el.dataset && el.dataset.dshgl) return // 已处理过, 跳过(只处理新内容)
|
|
3627
3767
|
const ownText = Array.from(el.childNodes).some((n) => n.nodeType === 3 && n.textContent.trim())
|
|
3628
3768
|
const textLike = /^(BUTTON|A|SPAN|LABEL|LI|P|H1|H2|H3|H4|PRE|CODE)$/.test(el.tagName)
|
|
3629
3769
|
if (!textLike && !ownText) return
|
|
3630
|
-
|
|
3631
|
-
if (bgbg && bgbg !== 'rgba(0, 0, 0, 0)' && bgbg !== 'transparent') return
|
|
3770
|
+
if (/bubble|badge|chip|tag|pill|code-block|md-code|n_block|n_banner|n_plain/i.test(el.className)) return
|
|
3632
3771
|
el.style.setProperty('color', '#ffffff', 'important')
|
|
3633
3772
|
el.style.setProperty('text-shadow', '0 1px 1px rgba(0,0,0,.5)')
|
|
3773
|
+
if (el.dataset) el.dataset.dshgl = '1'
|
|
3634
3774
|
})
|
|
3635
3775
|
// 在白字底上, 给过程内容配色区分, Error 用红色。
|
|
3636
3776
|
// 文字常在标签的子元素里(白字已把它设白), 故连同文本子元素一起上色。
|
|
@@ -3726,19 +3866,22 @@ window.__ModuleLoader__.load({
|
|
|
3726
3866
|
}
|
|
3727
3867
|
// 主题相关的东西统一重算:背景罩 + 输入框。切主题时由 observer/mq 触发。
|
|
3728
3868
|
// 写入前先断开观察器,写完再重连,避免自触发造成死循环。
|
|
3869
|
+
// 把非关键的"内容配色/白字"延迟到浏览器空闲时执行, 让页面先渲染出来(优化刷新加载时间)
|
|
3870
|
+
const defer = (fn) => { try { if (window.requestIdleCallback) window.requestIdleCallback(fn, { timeout: 120 }); else setTimeout(fn, 0) } catch (_) { fn() } }
|
|
3729
3871
|
const applyTheme = () => {
|
|
3730
3872
|
const obs = window.__dshGlassStyleObs
|
|
3731
3873
|
if (obs) obs.disconnect()
|
|
3732
3874
|
try {
|
|
3733
3875
|
const tk = dshDark() ? 'dark' : 'light'
|
|
3734
3876
|
root.setAttribute('data-dsh-theme', tk)
|
|
3877
|
+
root.setAttribute('data-dsh-turnruler', ((c.modules && c.modules.turnRuler) !== false) ? 'on' : 'off')
|
|
3878
|
+
window.__dshLongModules = c.modules || {}
|
|
3879
|
+
try { localStorage.setItem('dsh-long:modules', JSON.stringify(window.__dshLongModules)) } catch (e) {}
|
|
3735
3880
|
const bt = bgTint(tk)
|
|
3736
3881
|
const v = `rgba(${hexToRgb(bt.color)},${bt.mask})`
|
|
3737
3882
|
root.style.setProperty('--dsh-glass-bg-zone', v)
|
|
3738
3883
|
applyInputCard()
|
|
3739
|
-
applyChromeText()
|
|
3740
|
-
applySessionText()
|
|
3741
|
-
fixDialogs()
|
|
3884
|
+
defer(() => { applyChromeText(); applySessionText(); fixDialogs() })
|
|
3742
3885
|
} finally {
|
|
3743
3886
|
if (obs) obs.observe(document.documentElement, { attributes: true, attributeFilter: ['style', 'class'] })
|
|
3744
3887
|
}
|
|
@@ -3778,7 +3921,7 @@ window.__ModuleLoader__.load({
|
|
|
3778
3921
|
// 侧边栏/顶栏 DOM 变化(会话列表更新等)时重算左栏/顶栏文字,防抖 120ms
|
|
3779
3922
|
let chromeTimer = null
|
|
3780
3923
|
const chromeRoots = [...document.querySelectorAll('#root [class*="sidebarCol"],#root [class*="wSkVaW_header"]')]
|
|
3781
|
-
const chromeObs = new MutationObserver(() => { if (chromeTimer) clearTimeout(chromeTimer); chromeTimer = setTimeout(applyChromeText,
|
|
3924
|
+
const chromeObs = new MutationObserver(() => { if (chromeTimer) clearTimeout(chromeTimer); chromeTimer = setTimeout(applyChromeText, 30) })
|
|
3782
3925
|
chromeRoots.forEach((r) => chromeObs.observe(r, { childList: true, subtree: true, characterData: true }))
|
|
3783
3926
|
// 输入框卡片(uV2eYG_card/root)可能在启动后才挂载(会话/输入区加载晚),
|
|
3784
3927
|
// 启动那一刻 applyInputCard 常找不到框 → 需刷新才生效。监听新增卡片节点时重算,防抖 100ms。
|
|
@@ -3796,7 +3939,7 @@ window.__ModuleLoader__.load({
|
|
|
3796
3939
|
const sessionObs = new MutationObserver((muts) => {
|
|
3797
3940
|
let hit = false
|
|
3798
3941
|
for (const m of muts) { for (const node of m.addedNodes) { if (node.nodeType === 1 && inScroll(node)) { hit = true; break } } if (hit) break }
|
|
3799
|
-
if (hit) { if (sessionTimer) clearTimeout(sessionTimer); sessionTimer = setTimeout(() => { applySessionText(); fixDialogs() },
|
|
3942
|
+
if (hit) { if (sessionTimer) clearTimeout(sessionTimer); sessionTimer = setTimeout(() => { applySessionText(); fixDialogs() }, 20) }
|
|
3800
3943
|
})
|
|
3801
3944
|
sessionObs.observe(document.body, { childList: true, subtree: true })
|
|
3802
3945
|
}
|
|
@@ -3817,9 +3960,11 @@ window.__ModuleLoader__.load({
|
|
|
3817
3960
|
}, 'dsh-long-plugins: glass applier')
|
|
3818
3961
|
ctx.slots.inject('settings.section', () => ctx.slots.register({ name: 'settings.section', id: 'glass-ui', order: 40, label: 'RA-Span' }, GlassSettingsSection))
|
|
3819
3962
|
ctx.effect(() => {
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3963
|
+
// 玻璃配置/背景图历史拉取延迟到空闲, 减轻首屏网络风暴(背景没用玻璃时几乎零成本)
|
|
3964
|
+
const loadGlass = () => { fetch('/api/dsh-uploads/glass-config', { cache: 'no-store' }).then((r) => r.json()).then((b) => {
|
|
3965
|
+
if (b && b.cfg) { window.__dshGlassApply && window.__dshGlassApply({ ...b.cfg, bgImage: b.bgImageUrl || '' }) }
|
|
3966
|
+
}).catch(() => {}) }
|
|
3967
|
+
try { if (window.requestIdleCallback) window.requestIdleCallback(loadGlass, { timeout: 500 }); else setTimeout(loadGlass, 500) } catch (_) { loadGlass() }
|
|
3823
3968
|
}, 'dsh-long-plugins: glass boot')
|
|
3824
3969
|
},
|
|
3825
3970
|
}
|
|
@@ -3834,7 +3979,17 @@ window.__ModuleLoader__.load({
|
|
|
3834
3979
|
...glassPlugin.inject,
|
|
3835
3980
|
]))
|
|
3836
3981
|
|
|
3982
|
+
// 模块开关缓存:页面加载时同步读取(让 main apply 的 modEnabled 门控生效);
|
|
3983
|
+
// 配置加载(applyTheme)/保存(dsh-long 保存)后更新。开关在下次刷新生效。
|
|
3984
|
+
try {
|
|
3985
|
+
const _m = localStorage.getItem('dsh-long:modules')
|
|
3986
|
+
if (_m) window.__dshLongModules = JSON.parse(_m)
|
|
3987
|
+
} catch (_) {}
|
|
3988
|
+
// 全局模块开关查询(供各组件/apply 内部判断)
|
|
3989
|
+
window.__dshLongMod = (n) => (window.__dshLongModules ? window.__dshLongModules[n] !== false : true)
|
|
3990
|
+
|
|
3837
3991
|
function apply(ctx) {
|
|
3992
|
+
const modEnabled = (n) => (window.__dshLongModules ? window.__dshLongModules[n] !== false : true)
|
|
3838
3993
|
const safeApply = (label, fn) => {
|
|
3839
3994
|
try {
|
|
3840
3995
|
fn(ctx)
|
|
@@ -3842,13 +3997,24 @@ window.__ModuleLoader__.load({
|
|
|
3842
3997
|
console.error('[dsh-long-plugins] ' + label + ' apply failed:', error)
|
|
3843
3998
|
}
|
|
3844
3999
|
}
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
4000
|
+
// 「dsh-long」设置区(模块开关+补丁状态)始终注册——独立于任何模块开关, 否则禁用 RA-Span 会连带它一起消失
|
|
4001
|
+
ctx.slots.inject('settings.section', () => ctx.slots.register({ name: 'settings.section', id: 'dsh-long', order: 30, label: 'dsh-long' }, DshLongSettingsSection))
|
|
4002
|
+
// .dsh-glass-* 布局类(设置区排版)的 CSS 始终注入(不随 glass 开关丢失; 体积小)
|
|
4003
|
+
ctx.effect(() => {
|
|
4004
|
+
const style = document.createElement('style')
|
|
4005
|
+
style.dataset.plugin = 'dsh-long-plugins'
|
|
4006
|
+
style.dataset.pluginCss = 'dsh-long-plugins/glass-layout'
|
|
4007
|
+
style.textContent = GLASS_LAYOUT_CSS
|
|
4008
|
+
document.head.appendChild(style)
|
|
4009
|
+
return () => style.remove()
|
|
4010
|
+
}, 'dsh-long-plugins: glass layout css (always)')
|
|
4011
|
+
safeApply('upload', (c) => uploadPlugin.apply(c)) // 上传子功能(预览/附件/拖放/粘贴)在插件内部按模块开关
|
|
4012
|
+
if (modEnabled('skillDocs')) safeApply('skill-docs', (c) => skillDocsPlugin.apply(c))
|
|
4013
|
+
safeApply('token-usage', (c) => tokenUsagePlugin.apply(c)) // 余额 chip 开关在其内部; MOBILE_CSS 始终注入
|
|
4014
|
+
if (modEnabled('mobile')) safeApply('mobile-hamburger', (c) => mobilePlugin.apply(c))
|
|
4015
|
+
if (modEnabled('workspace')) safeApply('workspace-files', (c) => workspaceFilesPlugin.apply(c))
|
|
4016
|
+
if (modEnabled('turnRuler')) safeApply('turn-ruler', (c) => turnRulerPlugin.apply(c))
|
|
4017
|
+
if (modEnabled('glass')) safeApply('glass', (c) => glassPlugin.apply(c))
|
|
3852
4018
|
}
|
|
3853
4019
|
|
|
3854
4020
|
exports.apply = apply
|
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.
|
|
4
|
+
"version": "2.4.0",
|
|
5
5
|
"entry": {
|
|
6
6
|
"name": "dsh-long-plugins",
|
|
7
7
|
"inject": [
|
package/lib/index.js
CHANGED
|
@@ -1628,19 +1628,54 @@ window.addEventListener('resize',function(){ try{ if(fitMode) zoomFit(); else ap
|
|
|
1628
1628
|
bgTint: { light: { color: "#1a2332", mask: 0.28 }, dark: { color: "#1a2332", mask: 0.28 } },
|
|
1629
1629
|
inputBox: { light: { color: "", opacity: 1 }, dark: { color: "", opacity: 1 } },
|
|
1630
1630
|
zone: { session: { color: "#1a2332", mask: 0.45, opacity: 0.5 }, input: { color: "#1a2332", mask: 0.6, opacity: 0.55 } },
|
|
1631
|
+
// 各模块开关(「dsh-long」设置区):false=禁用该模块功能(避免与 DSH 新版自带功能冲突/按需关闭)
|
|
1632
|
+
modules: { glass: true, uploadAttach: true, uploadDragDrop: true, uploadPaste: true, uploadPreview: true, skillDocs: true, balance: true, mobile: true, workspace: true, turnRuler: true },
|
|
1631
1633
|
};
|
|
1632
1634
|
const glassDir = resolve(homedir(), ".dsh-long-plugins");
|
|
1633
1635
|
const glassFile = join(glassDir, "glass.json");
|
|
1634
1636
|
const bgDir = join(glassDir, "backgrounds");
|
|
1637
|
+
const currentBgFile = join(bgDir, "current.uri");
|
|
1638
|
+
// 背景图文件引用: 配置里 bgImage 为 "current"(存 bgDir/current.uri) 或 ""(无) ;不再内嵌大图 data-URI
|
|
1639
|
+
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; };
|
|
1640
|
+
const bgImageUrl = (cfg) => (cfg.bgImage === "current") ? "/api/dsh-uploads/glass-background" : "";
|
|
1641
|
+
// 只保留当前背景图(current.uri), 删除其它历史图片文件(旧图清理)
|
|
1642
|
+
async function pruneBackgrounds() {
|
|
1643
|
+
try {
|
|
1644
|
+
const names = await readdir(bgDir);
|
|
1645
|
+
for (const n of names) {
|
|
1646
|
+
if (n === "current.uri" || !n.endsWith(".uri")) continue;
|
|
1647
|
+
try { await unlink(join(bgDir, n)); } catch (_) {}
|
|
1648
|
+
}
|
|
1649
|
+
} catch (_) {}
|
|
1650
|
+
}
|
|
1635
1651
|
async function readGlassJSON() {
|
|
1636
|
-
try {
|
|
1637
|
-
|
|
1652
|
+
try {
|
|
1653
|
+
const raw = JSON.parse(await readFile(glassFile, "utf8"));
|
|
1654
|
+
const cfg = { ...GLASS_DEFAULTS, ...(raw && typeof raw === "object" ? raw : {}) };
|
|
1655
|
+
// 迁移旧版: bgImage 是内嵌 data-URI(大图) → 存成文件 + 改引用
|
|
1656
|
+
if (typeof cfg.bgImage === "string" && cfg.bgImage.startsWith("data:image/")) {
|
|
1657
|
+
try {
|
|
1658
|
+
await mkdir(bgDir, { recursive: true, mode: 0o700 });
|
|
1659
|
+
await writeFile(currentBgFile, cfg.bgImage, "utf8");
|
|
1660
|
+
cfg.bgImage = "current";
|
|
1661
|
+
await writeFile(glassFile, JSON.stringify(cfg, null, 2), "utf8");
|
|
1662
|
+
} catch (_) {}
|
|
1663
|
+
}
|
|
1664
|
+
return cfg;
|
|
1665
|
+
} catch { return { ...GLASS_DEFAULTS }; }
|
|
1638
1666
|
}
|
|
1639
1667
|
const glassConfig = async (req, res) => {
|
|
1640
1668
|
try {
|
|
1641
1669
|
requireTrusted(req);
|
|
1642
1670
|
if (req.method === "GET" || req.method === "HEAD") {
|
|
1643
1671
|
const cfg = await readGlassJSON();
|
|
1672
|
+
// 背景图只返回"是否存在 + 图片 URL", 不再返回内嵌大图 data-URI(减小响应)
|
|
1673
|
+
let imgMeta = { present: false, bytes: 0 };
|
|
1674
|
+
let imgUrl = "";
|
|
1675
|
+
if (cfg.bgImage === "current") {
|
|
1676
|
+
imgUrl = "/api/dsh-uploads/glass-background";
|
|
1677
|
+
try { imgMeta = { present: true, bytes: (await stat(currentBgFile)).size || 0 }; } catch (_) { imgMeta = { present: true, bytes: 0 }; }
|
|
1678
|
+
}
|
|
1644
1679
|
sendJson(res, 200, {
|
|
1645
1680
|
found: true,
|
|
1646
1681
|
cfg: {
|
|
@@ -1651,9 +1686,12 @@ window.addEventListener('resize',function(){ try{ if(fitMode) zoomFit(); else ap
|
|
|
1651
1686
|
bgTint: cfg.bgTint || { light: { color: "#1a2332", mask: 0.28 }, dark: { color: "#1a2332", mask: 0.28 } },
|
|
1652
1687
|
bgColor: cfg.bgColor || "#1a2332", bgMask: cfg.bgMask ?? 0.28,
|
|
1653
1688
|
bgBlur: cfg.bgBlur ?? 0, bgOpacity: cfg.bgOpacity ?? 1,
|
|
1654
|
-
|
|
1689
|
+
turnRuler: (cfg.modules && cfg.modules.turnRuler) !== false,
|
|
1690
|
+
modules: { glass: true, uploadAttach: true, uploadDragDrop: true, uploadPaste: true, uploadPreview: true, skillDocs: true, balance: true, mobile: true, workspace: true, turnRuler: true, ...(cfg.modules || {}) },
|
|
1691
|
+
bgImage: imgMeta,
|
|
1655
1692
|
},
|
|
1656
|
-
bgImage:
|
|
1693
|
+
bgImage: imgMeta,
|
|
1694
|
+
bgImageUrl: imgUrl,
|
|
1657
1695
|
});
|
|
1658
1696
|
return;
|
|
1659
1697
|
}
|
|
@@ -1661,16 +1699,27 @@ window.addEventListener('resize',function(){ try{ if(fitMode) zoomFit(); else ap
|
|
|
1661
1699
|
const body = await readJsonBody(req, 4 * 1024 * 1024);
|
|
1662
1700
|
const current = await readGlassJSON();
|
|
1663
1701
|
const next = { ...current, ...sanitizeGlass(typeof body === "object" && body !== null ? body : {}) };
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1702
|
+
// 各模块开关表:body.modules 里有的键按布尔合并
|
|
1703
|
+
if (body && typeof body.modules === "object" && body.modules !== null) {
|
|
1704
|
+
const m = { glass: true, uploadAttach: true, uploadDragDrop: true, uploadPaste: true, uploadPreview: true, skillDocs: true, balance: true, mobile: true, workspace: true, turnRuler: true, ...(current.modules || {}) };
|
|
1705
|
+
for (const k of Object.keys(body.modules)) m[k] = body.modules[k] !== false;
|
|
1706
|
+
next.modules = m;
|
|
1707
|
+
}
|
|
1708
|
+
// 背景图改为"文件 + 引用": 用户上传的 data-URI 存入 current.uri, 配置只存 "current" 引用
|
|
1709
|
+
if (typeof next.bgImage === "string" && next.bgImage.startsWith("data:image/")) {
|
|
1668
1710
|
try {
|
|
1669
1711
|
await mkdir(bgDir, { recursive: true, mode: 0o700 });
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
} catch (_) {}
|
|
1712
|
+
await writeFile(currentBgFile, next.bgImage, "utf8");
|
|
1713
|
+
next.bgImage = "current";
|
|
1714
|
+
} catch (_) { next.bgImage = ""; }
|
|
1715
|
+
} else if ("bgImage" in body && body.bgImage === "") {
|
|
1716
|
+
next.bgImage = ""; // 显式清除
|
|
1717
|
+
try { await unlink(currentBgFile); } catch (_) {}
|
|
1673
1718
|
}
|
|
1719
|
+
// 只保留当前背景图, 删除历史旧图
|
|
1720
|
+
await pruneBackgrounds();
|
|
1721
|
+
await mkdir(glassDir, { recursive: true, mode: 0o700 });
|
|
1722
|
+
await writeFile(glassFile, JSON.stringify(next, null, 2), "utf8");
|
|
1674
1723
|
sendJson(res, 200, { ok: true, saved: true });
|
|
1675
1724
|
return;
|
|
1676
1725
|
}
|
|
@@ -1691,14 +1740,64 @@ window.addEventListener('resize',function(){ try{ if(fitMode) zoomFit(); else ap
|
|
|
1691
1740
|
catch { sendJson(res, 404, { error: "not found" }); return; }
|
|
1692
1741
|
}
|
|
1693
1742
|
let list = [];
|
|
1694
|
-
|
|
1743
|
+
// 只保留当前背景图, 删除历史旧图; 列表排除当前图
|
|
1744
|
+
try { await pruneBackgrounds(); } catch (_) {}
|
|
1745
|
+
try { list = (await readdir(bgDir)).filter((n) => n.endsWith(".uri") && n !== "current.uri").sort().reverse().slice(0, 30); } catch (_) {}
|
|
1695
1746
|
sendJson(res, 200, { list });
|
|
1696
1747
|
} catch (error) {
|
|
1697
1748
|
sendError(res, error, onError);
|
|
1698
1749
|
}
|
|
1699
1750
|
};
|
|
1751
|
+
// 返回当前背景图(文件存的 data-URI 解码为真实图片)
|
|
1752
|
+
const glassBackground = async (req, res) => {
|
|
1753
|
+
try {
|
|
1754
|
+
requireTrusted(req);
|
|
1755
|
+
const cfg = await readGlassJSON();
|
|
1756
|
+
if (cfg.bgImage !== "current") { sendJson(res, 404, { error: "no background" }); return; }
|
|
1757
|
+
const buf = await readFile(currentBgFile, "utf8");
|
|
1758
|
+
const d = decodeDataUri(buf);
|
|
1759
|
+
if (!d) { sendJson(res, 404, { error: "bad image" }); return; }
|
|
1760
|
+
const bytes = Buffer.from(d.base64, "base64");
|
|
1761
|
+
res.writeHead(200, { "content-type": d.mime, "cache-control": "no-store", "x-content-type-options": "nosniff" });
|
|
1762
|
+
res.end(bytes);
|
|
1763
|
+
} catch (error) {
|
|
1764
|
+
sendError(res, error, onError);
|
|
1765
|
+
}
|
|
1766
|
+
};
|
|
1767
|
+
// 补丁状态:只读检测 DSH 核心各补丁的「已打 / 未打 / 原生无需」,供 dsh-long 设置区显示
|
|
1768
|
+
const patchStatus = async (req, res) => {
|
|
1769
|
+
try {
|
|
1770
|
+
requireTrusted(req);
|
|
1771
|
+
// 定位 DSH 核心 dsh-client-connection 目录(探测常见路径)
|
|
1772
|
+
let DIR = "";
|
|
1773
|
+
const candidates = [
|
|
1774
|
+
"/volume1/npm/global/lib/node_modules/@deepseek-ai/dsh/node_modules/@deepseek-ai/dsh-client-connection/lib",
|
|
1775
|
+
"/usr/local/lib/node_modules/@deepseek-ai/dsh/node_modules/@deepseek-ai/dsh-client-connection/lib",
|
|
1776
|
+
"/volume1/dsh/.dsh/node_modules/@deepseek-ai/dsh/node_modules/@deepseek-ai/dsh-client-connection/lib",
|
|
1777
|
+
"/usr/lib/node_modules/@deepseek-ai/dsh/node_modules/@deepseek-ai/dsh-client-connection/lib",
|
|
1778
|
+
];
|
|
1779
|
+
for (const c of candidates) { try { await stat(join(c, "index.js")); DIR = c; break; } catch (_) {} }
|
|
1780
|
+
if (!DIR) { sendJson(res, 200, { ok: true, patches: null, error: "未找到 DSH 核心 dsh-client-connection 目录" }); return; }
|
|
1781
|
+
const IDX = join(DIR, "index.js");
|
|
1782
|
+
const CLI = join(DIR, "client.js");
|
|
1783
|
+
const idx = await readFile(IDX, "utf8");
|
|
1784
|
+
let cli = "";
|
|
1785
|
+
try { cli = await readFile(CLI, "utf8"); } catch (_) {}
|
|
1786
|
+
const mark1 = "PRIVILEGED_METHODS.has(method) && !isTrustedApiRequest(request, trustedHosts)";
|
|
1787
|
+
const mark2 = "isLoopback: pageLocation === void 0 || isLoopbackHostname(pageLocation.hostname) || [";
|
|
1788
|
+
const mark3 = "WEBSOCKET_HEARTBEAT_MS";
|
|
1789
|
+
const patches = {
|
|
1790
|
+
trustedHosts: idx.includes(mark1) ? "已打" : "未打",
|
|
1791
|
+
loopback: cli.includes(mark2) ? "已打" : "未打",
|
|
1792
|
+
heartbeat: idx.includes(mark3) ? "已打" : (idx.includes("socket.ping()") ? "原生无需" : "未打"),
|
|
1793
|
+
};
|
|
1794
|
+
sendJson(res, 200, { ok: true, patches, dir: DIR });
|
|
1795
|
+
} catch (error) {
|
|
1796
|
+
sendError(res, error, onError);
|
|
1797
|
+
}
|
|
1798
|
+
};
|
|
1700
1799
|
|
|
1701
|
-
return { root, maxFileBytes, totalMaxBytes, api, download, preview, workspaceList, workspaceFile, workspacePreview, workspaceBrowse, workspaceDelete, workspaceRename, workspaceSave, docxPreviewPage, docxPreviewAsset, pptxPreviewPage, xlsxPreviewPage, xlsxPreviewAsset, glassConfig, glassBackgrounds };
|
|
1800
|
+
return { root, maxFileBytes, totalMaxBytes, api, download, preview, workspaceList, workspaceFile, workspacePreview, workspaceBrowse, workspaceDelete, workspaceRename, workspaceSave, docxPreviewPage, docxPreviewAsset, pptxPreviewPage, xlsxPreviewPage, xlsxPreviewAsset, glassConfig, glassBackgrounds, glassBackground, patchStatus };
|
|
1702
1801
|
}
|
|
1703
1802
|
|
|
1704
1803
|
/** 人类可读文件大小。 */
|
|
@@ -2609,6 +2708,18 @@ export async function apply(ctx, config = {}) {
|
|
|
2609
2708
|
handler: handlers.glassBackgrounds,
|
|
2610
2709
|
}), "dsh-long-plugins: glass-ui backgrounds route");
|
|
2611
2710
|
|
|
2711
|
+
ctx.effect(() => ctx.webServer.register({
|
|
2712
|
+
kind: "exact",
|
|
2713
|
+
path: "/api/dsh-uploads/glass-background",
|
|
2714
|
+
handler: handlers.glassBackground,
|
|
2715
|
+
}), "dsh-long-plugins: glass-ui background image route");
|
|
2716
|
+
|
|
2717
|
+
ctx.effect(() => ctx.webServer.register({
|
|
2718
|
+
kind: "exact",
|
|
2719
|
+
path: "/api/dsh-uploads/patch-status",
|
|
2720
|
+
handler: handlers.patchStatus,
|
|
2721
|
+
}), "dsh-long-plugins: patch status route");
|
|
2722
|
+
|
|
2612
2723
|
ctx.effect(() => ctx.webServer.register({
|
|
2613
2724
|
kind: "exact",
|
|
2614
2725
|
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.
|
|
3
|
+
"version": "2.4.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",
|
|
@@ -42,11 +42,16 @@ if grep -q 'WEBSOCKET_HEARTBEAT_MS' "$IDX"; then
|
|
|
42
42
|
echo " 已打过,跳过 ✓"
|
|
43
43
|
exit 0
|
|
44
44
|
fi
|
|
45
|
+
# DSH 已原生心跳(如 v0.1.2-alpha 网关定期 ping):检测到已是 ping 心跳即跳过,避免重复/冲突
|
|
46
|
+
if grep -q 'socket\.ping()' "$IDX"; then
|
|
47
|
+
echo " 检测到 DSH 已有 WebSocket ping 心跳(原生或已打过),跳过 ✓"
|
|
48
|
+
exit 0
|
|
49
|
+
fi
|
|
45
50
|
|
|
46
|
-
# 待替换的原始代码未在 ->
|
|
51
|
+
# 待替换的原始代码未在 -> 可能是 DSH 已改逻辑(如 v0.1.2-alpha 会话视图/网关重构):温和跳过而不是硬失败
|
|
47
52
|
if ! grep -q 'var WebSocketDownlinks = class {' "$IDX"; then
|
|
48
|
-
echo "
|
|
49
|
-
exit
|
|
53
|
+
echo " 提示:未找到 WebSocketDownlinks 类,DSH 可能已改逻辑(如新版原生心跳),跳过本补丁(不再硬失败)" >&2
|
|
54
|
+
exit 0
|
|
50
55
|
fi
|
|
51
56
|
|
|
52
57
|
cp "$IDX" "$IDX.bak-heartbeat-$(date +%Y%m%d-%H%M)"
|
|
@@ -119,6 +119,16 @@ pnpm install # 或用探测到的 pnpm 绝对路径
|
|
|
119
119
|
# md2docxScript: /your/path/md2docx.py # 可选,默认用包内 lib/md2docx.py
|
|
120
120
|
```
|
|
121
121
|
|
|
122
|
+
### 4.5 设置插件默认:回合导航 + RA-Span 关闭(避免与 DSH 新版自带功能冲突 / 默认更简洁)
|
|
123
|
+
> DSH ≥ v0.1.2-alpha 已自带「紧凑回合导航」。新机默认把插件 `modules.turnRuler` 置 false(避免轮次刻尺冲突),`modules.glass`(RA-Span)也默认 false(要玻璃桌面可在「dsh-long」设置区手动开)。如目标 DSH 无原生回合导航,可在「dsh-long」手动开回。
|
|
124
|
+
```sh
|
|
125
|
+
GJ="${HOME:-/root}/.dsh-long-plugins/glass.json"
|
|
126
|
+
mkdir -p "$(dirname "$GJ")"
|
|
127
|
+
[ -f "$GJ" ] || echo '{"enabled":false,"blur":20,"bgImage":"","bgColor":"#1a2332","bgMask":0.28,"bgTint":{"light":{"color":"#1a2332","mask":0.28},"dark":{"color":"#1a2332","mask":0.28}},"inputBox":{"light":{"color":"","opacity":1},"dark":{"color":"","opacity":1}},"zone":{"session":{"color":"#1a2332","mask":0.45,"opacity":0.5},"input":{"color":"#1a2332","mask":0.6,"opacity":0.55}},"modules":{"glass":false,"uploadAttach":true,"uploadDragDrop":true,"uploadPaste":true,"uploadPreview":true,"skillDocs":true,"balance":true,"mobile":true,"workspace":true,"turnRuler":false}}' > "$GJ"
|
|
128
|
+
node -e "const fs=require('fs');const p=JSON.parse(fs.readFileSync(process.argv[1],'utf8'));p.modules=p.modules||{};p.modules.glass=false;p.modules.turnRuler=false;fs.writeFileSync(process.argv[1],JSON.stringify(p,null,2)+'\n')" "$GJ" \
|
|
129
|
+
&& echo " ✅ 已默认关闭插件 RA-Span(modules.glass=false) 与回合导航(modules.turnRuler=false)"
|
|
130
|
+
```
|
|
131
|
+
|
|
122
132
|
## 工作目录 / 上传目录匹配(重要决策点)
|
|
123
133
|
|
|
124
134
|
上传/工作区根**不是写死的**,靠环境变量推导:
|
|
@@ -176,6 +186,7 @@ setsid nohup <node绝对路径> --expose-internals --max-old-space-size=8192 \
|
|
|
176
186
|
> **md2docx 工具需新建会话后 agent 才加载**(会话工具集创建时固定),验证时开新会话。
|
|
177
187
|
6. 窄屏(手机)下预览窗按钮自动换行不截断。
|
|
178
188
|
7. 用 `dsh --profile <p> --dump-config` 核对组合树正确。
|
|
189
|
+
8. **「dsh-long」设置区**:设置面板出现「dsh-long」节,含**各模块开关**(RA-Span/会话导航/附件上传/拖放/粘贴/上传预览/技能文档/余额/移动端/输出文件)与**「补丁状态(只读)」**——显示 3 个 DSH 核心补丁的已打/未打/原生无需。模块开关存于 `~/.dsh-long-plugins/glass.json` 的 `modules` 字段,保存后下次刷新生效。
|
|
179
190
|
|
|
180
191
|
## 硬性安全边界(必须遵守)
|
|
181
192
|
- **不主动 `git push` / 打 tag / 发 Release**;需要发版本时停下用 `ask_user_question` 等用户确认。
|
|
@@ -60,6 +60,19 @@ sh <插件目录>/patches/dsh-client-connection-heartbeat.sh
|
|
|
60
60
|
cd "$DSH_HOME/profiles/web" && pnpm install
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
+
### 5.5 升级后自动禁用插件「回合导航」(避免与 DSH 新版自带回合导航冲突)
|
|
64
|
+
> DSH ≥ v0.1.2-alpha 已自带「紧凑回合导航」。若插件 turn-ruler 仍开启,两者同时操纵会话 DOM 可能冲突→前端崩。升级流程里**自动把插件 `modules.turnRuler` 置为 false**;如目标版无原生回合导航,可在「dsh-long」设置区手动开回来。
|
|
65
|
+
```sh
|
|
66
|
+
GJ="${HOME:-/root}/.dsh-long-plugins/glass.json"
|
|
67
|
+
if [ -f "$GJ" ]; then
|
|
68
|
+
node -e "const fs=require('fs');const p=JSON.parse(fs.readFileSync(process.argv[1],'utf8'));p.modules=p.modules||{};p.modules.turnRuler=false;fs.writeFileSync(process.argv[1],JSON.stringify(p,null,2)+'\n')" "$GJ" \
|
|
69
|
+
&& echo " ✅ 已自动关闭插件回合导航 (modules.turnRuler=false,可在 dsh-long 手动开回)" \
|
|
70
|
+
|| echo " ⚠️ 未能更新插件配置(可手动在 dsh-long 关掉「会话导航」)"
|
|
71
|
+
else
|
|
72
|
+
echo " (未找到插件配置, 跳过; 可手动在 dsh-long 关掉「会话导航」)"
|
|
73
|
+
fi
|
|
74
|
+
```
|
|
75
|
+
|
|
63
76
|
### 6. 重启 dsh + 强刷浏览器
|
|
64
77
|
```sh
|
|
65
78
|
/volume1/dsh/start.sh # 或按当前环境的重启方式
|
|
@@ -73,34 +86,6 @@ cd "$DSH_HOME/profiles/web" && pnpm install
|
|
|
73
86
|
4. 提问/审批窗口:弹出一个问题,等 60s+ 不再自己消失(心跳补丁生效)。
|
|
74
87
|
5. 插件设置面板「上传文件」「输出文件」「技能文档」都在。
|
|
75
88
|
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` | 白底回来 |
|
|
104
89
|
|
|
105
90
|
## 硬性安全边界(必须遵守)
|
|
106
91
|
- **不主动 push / 打 tag / 发 Release**;需要发布版本时停下用 ask_user_question 等确认。
|
|
@@ -114,5 +99,4 @@ dsh-long-plugins 的「RA-Span(统一桌面/玻璃)」**大量依赖 DSH 内
|
|
|
114
99
|
| 升级后设置页"未暴露命名空间" | 补丁2 被覆盖 | 重跑 apply-dsh-patches.sh |
|
|
115
100
|
| 升级后提问窗口自己消失 | 心跳补丁(补丁3)被覆盖 | 重跑 apply-dsh-patches.sh 或装插件补丁脚本 |
|
|
116
101
|
| 升级后插件不显示 | 插件安装副本未同步 | 步骤 5 `pnpm install` |
|
|
117
|
-
| 升级后 RA-Span 观感/配色不对(输入框重影、标签不变色、弹窗透明、统计字淡等) | DSH 混淆类名变了 | 见 RA-Span 校准节:强刷 + 逐项检查,让用户右键给出新类名,更新 `client/client.js` 对应选择器 |
|
|
118
102
|
| 按钮全没了/界面异常 | 浏览器缓存了旧前端 | 强刷 Ctrl/Cmd+Shift+R |
|