@wenbin_wb/dsh-bridge 2.7.1 → 2.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/README.en.md +31 -8
- package/README.md +31 -8
- package/client/client.js +1024 -6
- package/client/index.js +1121 -8
- package/docs/feishu-usage.md +1 -0
- package/docs/qq-usage.md +1 -0
- package/docs/screenshots/mobile-chat.jpg +0 -0
- package/docs/screenshots/mobile-drawer.jpg +0 -0
- package/docs/screenshots/mobile-remote-settings.jpg +0 -0
- package/docs/screenshots/mobile-settings-im.jpg +0 -0
- package/docs/screenshots/mobile-settings-lan.jpg +0 -0
- package/docs/screenshots/mobile-settings-security.jpg +0 -0
- package/docs/screenshots/mobile-settings-tunnel.jpg +0 -0
- package/docs/screenshots/mobile-workspace-picker.jpg +0 -0
- package/docs/screenshots/remote-web-mobile.jpg +0 -0
- package/docs/telegram-usage.md +1 -0
- package/docs/wechat-usage.md +2 -0
- package/lib/bridge-rpc-constants.js +4 -0
- package/lib/bridge-rpc.js +41 -0
- package/lib/feishu/index.js +4 -1
- package/lib/index.js +252 -4
- package/lib/platform/conversation-bridge.js +42 -2
- package/lib/qq/index.js +4 -1
- package/lib/security/path-validator.js +195 -0
- package/lib/security/rate-limiter.js +93 -0
- package/lib/telegram/index.js +4 -1
- package/lib/wechat/index.js +4 -1
- package/package.json +2 -2
package/client/index.js
CHANGED
|
@@ -23,6 +23,48 @@ if (typeof window !== 'undefined') {
|
|
|
23
23
|
|
|
24
24
|
import { BRIDGE_RPC_CHANNEL, BRIDGE_ENDPOINTS } from '../lib/bridge-rpc-constants.js';
|
|
25
25
|
|
|
26
|
+
let _globalAdminToken = '';
|
|
27
|
+
function setGlobalAdminToken(t) {
|
|
28
|
+
_globalAdminToken = t || '';
|
|
29
|
+
if (typeof window !== 'undefined') {
|
|
30
|
+
try {
|
|
31
|
+
if (t) sessionStorage.setItem('dsh_admin_token', t);
|
|
32
|
+
else sessionStorage.removeItem('dsh_admin_token');
|
|
33
|
+
} catch {}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function getGlobalAdminToken() {
|
|
38
|
+
if (_globalAdminToken) return _globalAdminToken;
|
|
39
|
+
if (typeof window !== 'undefined') {
|
|
40
|
+
try {
|
|
41
|
+
const saved = sessionStorage.getItem('dsh_admin_token');
|
|
42
|
+
if (saved) {
|
|
43
|
+
_globalAdminToken = saved;
|
|
44
|
+
return saved;
|
|
45
|
+
}
|
|
46
|
+
} catch {}
|
|
47
|
+
}
|
|
48
|
+
return '';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function isLocalEnvironment() {
|
|
52
|
+
if (typeof window === 'undefined') return true;
|
|
53
|
+
const host = window.location.hostname || '';
|
|
54
|
+
const proto = window.location.protocol || '';
|
|
55
|
+
return (
|
|
56
|
+
host === '127.0.0.1' ||
|
|
57
|
+
host === 'localhost' ||
|
|
58
|
+
host === '::1' ||
|
|
59
|
+
host === '' ||
|
|
60
|
+
proto === 'file:' ||
|
|
61
|
+
proto === 'vscode-webview:' ||
|
|
62
|
+
proto === 'app:' ||
|
|
63
|
+
typeof window.__DSH_ELECTRON__ !== 'undefined' ||
|
|
64
|
+
(typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.includes('Electron'))
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
26
68
|
const GITHUB_URL = 'https://github.com/wenbin-wb/dsh-bridge';
|
|
27
69
|
const RELEASES_URL = 'https://github.com/wenbin-wb/dsh-bridge/releases';
|
|
28
70
|
const ISSUES_URL = 'https://github.com/wenbin-wb/dsh-bridge/issues/new';
|
|
@@ -38,7 +80,7 @@ function upgradeCommands(latest) {
|
|
|
38
80
|
}
|
|
39
81
|
|
|
40
82
|
const name = 'dsh-bridge';
|
|
41
|
-
const inject = ['slots', 'connection'];
|
|
83
|
+
const inject = ['slots', 'connection', 'workspaces', 'sessions'];
|
|
42
84
|
|
|
43
85
|
// semver 比较:a > b
|
|
44
86
|
function semverGt(a, b) {
|
|
@@ -959,7 +1001,7 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
|
|
|
959
1001
|
setCfgDraft({
|
|
960
1002
|
digestIntervalSec: String(platform.config.digestIntervalSec ?? 300),
|
|
961
1003
|
approvalTimeoutSec: String(platform.config.approvalTimeoutSec ?? 600),
|
|
962
|
-
maxMessageChars: String(platform.config.maxMessageChars
|
|
1004
|
+
maxMessageChars: String((platform.config.maxMessageChars >= 500 ? platform.config.maxMessageChars : null) ?? (platformId === 'telegram' ? 4096 : 2000)),
|
|
963
1005
|
sendChunkDelayMs: String(platform.config.sendChunkDelayMs ?? 1500),
|
|
964
1006
|
appId: platform.config.appId ?? '',
|
|
965
1007
|
// Secret 不由后端回传;空值表示沿用已保存密钥
|
|
@@ -1801,6 +1843,78 @@ function RestartDshCard({ rpcCall }) {
|
|
|
1801
1843
|
);
|
|
1802
1844
|
}
|
|
1803
1845
|
|
|
1846
|
+
// 运维 Tab 内的远程工作区管理卡片
|
|
1847
|
+
function RemoteWorkspaceCard({ rpcCall }) {
|
|
1848
|
+
const [workspaces, setWorkspaces] = React.useState([]);
|
|
1849
|
+
const [loading, setLoading] = React.useState(false);
|
|
1850
|
+
|
|
1851
|
+
const load = React.useCallback(async () => {
|
|
1852
|
+
if (!rpcCall) return;
|
|
1853
|
+
setLoading(true);
|
|
1854
|
+
try {
|
|
1855
|
+
const res = await rpcCall(BRIDGE_ENDPOINTS.listWorkspaces, {});
|
|
1856
|
+
const list = res?.value || res;
|
|
1857
|
+
if (Array.isArray(list)) setWorkspaces(list);
|
|
1858
|
+
else if (list?.workspaces && Array.isArray(list.workspaces)) setWorkspaces(list.workspaces);
|
|
1859
|
+
} catch {}
|
|
1860
|
+
finally { setLoading(false); }
|
|
1861
|
+
}, [rpcCall]);
|
|
1862
|
+
|
|
1863
|
+
React.useEffect(() => {
|
|
1864
|
+
load();
|
|
1865
|
+
}, [load]);
|
|
1866
|
+
|
|
1867
|
+
return React.createElement('div', { style: { ...s.card, marginBottom: 16 } },
|
|
1868
|
+
React.createElement('div', {
|
|
1869
|
+
style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 10, flexWrap: 'wrap', gap: 8 },
|
|
1870
|
+
},
|
|
1871
|
+
React.createElement('div', null,
|
|
1872
|
+
React.createElement('div', { style: { ...s.label, fontSize: 13, display: 'flex', alignItems: 'center', gap: 6 } },
|
|
1873
|
+
'🗂️ 远程工作区管理 (目录浏览器)'
|
|
1874
|
+
),
|
|
1875
|
+
React.createElement('div', { style: { ...s.muted, marginTop: 3 } },
|
|
1876
|
+
'在移动端或远程设备上可视点选电脑上的文件夹或直接输入路径添加至 DSH。'
|
|
1877
|
+
),
|
|
1878
|
+
),
|
|
1879
|
+
React.createElement('button', {
|
|
1880
|
+
type: 'button',
|
|
1881
|
+
style: { ...s.btnPri, height: 32, fontSize: 12, padding: '0 14px' },
|
|
1882
|
+
onClick: () => {
|
|
1883
|
+
if (typeof window.__dshOpenRemoteWorkspaceModal === 'function') {
|
|
1884
|
+
window.__dshOpenRemoteWorkspaceModal();
|
|
1885
|
+
} else if (typeof showRemoteWorkspaceDialog === 'function') {
|
|
1886
|
+
showRemoteWorkspaceDialog(rpcCall, () => load());
|
|
1887
|
+
}
|
|
1888
|
+
},
|
|
1889
|
+
}, '+ 远程添加工作区'),
|
|
1890
|
+
),
|
|
1891
|
+
workspaces.length > 0 ? React.createElement('div', {
|
|
1892
|
+
style: { display: 'flex', flexDirection: 'column', gap: 6, marginTop: 8 },
|
|
1893
|
+
},
|
|
1894
|
+
workspaces.map((ws, i) => React.createElement('div', {
|
|
1895
|
+
key: ws.path || i,
|
|
1896
|
+
style: {
|
|
1897
|
+
display: 'flex',
|
|
1898
|
+
alignItems: 'center',
|
|
1899
|
+
justifyContent: 'space-between',
|
|
1900
|
+
padding: '7px 12px',
|
|
1901
|
+
background: 'var(--dsw-alias-bg-layer-1,#ffffff)',
|
|
1902
|
+
border: '1px solid var(--dsw-alias-border-l2,#e5e7eb)',
|
|
1903
|
+
borderRadius: 8,
|
|
1904
|
+
fontSize: 12,
|
|
1905
|
+
},
|
|
1906
|
+
},
|
|
1907
|
+
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8, overflow: 'hidden' } },
|
|
1908
|
+
React.createElement('span', { style: { fontWeight: 600, color: 'var(--dsw-alias-brand-primary,#4f6ef7)', flexShrink: 0 } }, `@${i + 1} ${ws.title || ''}`),
|
|
1909
|
+
React.createElement('span', { style: { ...s.code, color: 'var(--dsw-alias-label-tertiary,#6b7280)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, ws.path),
|
|
1910
|
+
),
|
|
1911
|
+
)),
|
|
1912
|
+
) : React.createElement('div', {
|
|
1913
|
+
style: { ...s.muted, marginTop: 6, padding: '10px 14px', background: 'var(--dsw-alias-bg-layer-1,#ffffff)', border: '1px solid var(--dsw-alias-border-l2,#e5e7eb)', borderRadius: 8, textAlign: 'center' },
|
|
1914
|
+
}, loading ? '正在读取工作区列表…' : '暂无已注册工作区,点击右上角「+ 远程添加工作区」即可浏览添加。'),
|
|
1915
|
+
);
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1804
1918
|
// 版本检查 + 一键升级 + GitHub/反馈入口
|
|
1805
1919
|
function VersionBanner({ rpcCall }) {
|
|
1806
1920
|
const [info, setInfo] = React.useState(null);
|
|
@@ -2239,6 +2353,7 @@ function BridgePanel({ rpcCall }) {
|
|
|
2239
2353
|
const data = await res.json();
|
|
2240
2354
|
if (data?.ok && data.adminToken) {
|
|
2241
2355
|
setAdminToken(data.adminToken);
|
|
2356
|
+
setGlobalAdminToken(data.adminToken);
|
|
2242
2357
|
setAdminUnlocked(true);
|
|
2243
2358
|
return data.adminToken;
|
|
2244
2359
|
}
|
|
@@ -2255,7 +2370,7 @@ function BridgePanel({ rpcCall }) {
|
|
|
2255
2370
|
}, [isLocalhost, adminUnlocked, fetchLoopbackToken]);
|
|
2256
2371
|
|
|
2257
2372
|
const authRpcCall = React.useCallback(async (endpoint, payload = {}, signal) => {
|
|
2258
|
-
let token = adminToken;
|
|
2373
|
+
let token = adminToken || getGlobalAdminToken();
|
|
2259
2374
|
if (isLocalhost && !token) {
|
|
2260
2375
|
token = await fetchLoopbackToken();
|
|
2261
2376
|
}
|
|
@@ -2282,7 +2397,9 @@ function BridgePanel({ rpcCall }) {
|
|
|
2282
2397
|
try {
|
|
2283
2398
|
const res = await rpcCall(BRIDGE_ENDPOINTS.authAdminUnlock, { password: unlockPassword });
|
|
2284
2399
|
if (res?.ok) {
|
|
2285
|
-
|
|
2400
|
+
const token = res.value?.adminToken || '';
|
|
2401
|
+
setAdminToken(token);
|
|
2402
|
+
setGlobalAdminToken(token);
|
|
2286
2403
|
setAdminUnlocked(true);
|
|
2287
2404
|
setUnlockPassword('');
|
|
2288
2405
|
setShowUnlockModal(false);
|
|
@@ -2304,6 +2421,7 @@ function BridgePanel({ rpcCall }) {
|
|
|
2304
2421
|
}
|
|
2305
2422
|
} catch {}
|
|
2306
2423
|
setAdminToken('');
|
|
2424
|
+
setGlobalAdminToken('');
|
|
2307
2425
|
setAdminUnlocked(false);
|
|
2308
2426
|
}, [rpcCall, adminToken]);
|
|
2309
2427
|
|
|
@@ -2480,6 +2598,7 @@ function BridgePanel({ rpcCall }) {
|
|
|
2480
2598
|
} else if (activeTab === 'ops') {
|
|
2481
2599
|
tabContent = React.createElement(React.Fragment, null,
|
|
2482
2600
|
React.createElement(SystemMetricsWidget, { metrics: status?.system }),
|
|
2601
|
+
React.createElement(RemoteWorkspaceCard, { rpcCall: authRpcCall }),
|
|
2483
2602
|
React.createElement(NetworkDiagnosticWidget, { rpcCall: authRpcCall }),
|
|
2484
2603
|
React.createElement(BackupRestoreWidget, {
|
|
2485
2604
|
rpcCall: authRpcCall,
|
|
@@ -2833,6 +2952,7 @@ function injectMobileStyles() {
|
|
|
2833
2952
|
z-index: 9998 !important;
|
|
2834
2953
|
box-sizing: border-box !important;
|
|
2835
2954
|
user-select: none !important;
|
|
2955
|
+
pointer-events: none !important;
|
|
2836
2956
|
}
|
|
2837
2957
|
|
|
2838
2958
|
/* 左侧双横线按钮 (DeepSeek App 原生图标) */
|
|
@@ -2849,6 +2969,7 @@ function injectMobileStyles() {
|
|
|
2849
2969
|
cursor: pointer;
|
|
2850
2970
|
padding: 0;
|
|
2851
2971
|
transition: opacity 0.15s;
|
|
2972
|
+
pointer-events: auto !important;
|
|
2852
2973
|
}
|
|
2853
2974
|
.dsh-header-menu-btn:active {
|
|
2854
2975
|
opacity: 0.6;
|
|
@@ -2868,11 +2989,29 @@ function injectMobileStyles() {
|
|
|
2868
2989
|
cursor: pointer;
|
|
2869
2990
|
padding: 0;
|
|
2870
2991
|
transition: opacity 0.15s;
|
|
2992
|
+
pointer-events: auto !important;
|
|
2871
2993
|
}
|
|
2872
2994
|
.dsh-header-new-btn:active {
|
|
2873
2995
|
opacity: 0.6;
|
|
2874
2996
|
}
|
|
2875
2997
|
|
|
2998
|
+
/* 中间动态会话标题 (单行居中打点截断,100% 还原原生 App 导航体验) */
|
|
2999
|
+
.dsh-mobile-header-title {
|
|
3000
|
+
flex: 1 1 auto !important;
|
|
3001
|
+
min-width: 0 !important;
|
|
3002
|
+
text-align: center !important;
|
|
3003
|
+
font-size: 15px !important;
|
|
3004
|
+
font-weight: 600 !important;
|
|
3005
|
+
color: var(--dsw-alias-label-primary, #111827) !important;
|
|
3006
|
+
overflow: hidden !important;
|
|
3007
|
+
text-overflow: ellipsis !important;
|
|
3008
|
+
white-space: nowrap !important;
|
|
3009
|
+
padding: 0 10px !important;
|
|
3010
|
+
user-select: none !important;
|
|
3011
|
+
pointer-events: none !important;
|
|
3012
|
+
letter-spacing: -0.2px !important;
|
|
3013
|
+
}
|
|
3014
|
+
|
|
2876
3015
|
/* 3. 中间主内容区与输入框 */
|
|
2877
3016
|
div[class*="_centerCol"] {
|
|
2878
3017
|
flex: 1 1 100% !important;
|
|
@@ -2889,6 +3028,110 @@ function injectMobileStyles() {
|
|
|
2889
3028
|
display: none !important;
|
|
2890
3029
|
}
|
|
2891
3030
|
|
|
3031
|
+
/* 3.1 会话对话头部顶栏:移动端防挤压与空间释放优化(严格排除 .dsh-mobile-app-header) */
|
|
3032
|
+
div[class*="_centerCol"] header,
|
|
3033
|
+
header[class*="wSkVaW_header"] {
|
|
3034
|
+
padding: 4px 16px 2px 16px !important;
|
|
3035
|
+
position: relative !important;
|
|
3036
|
+
overflow: visible !important;
|
|
3037
|
+
}
|
|
3038
|
+
|
|
3039
|
+
div[class*="wSkVaW_titleRow"],
|
|
3040
|
+
div[class*="titleRow"] {
|
|
3041
|
+
display: flex !important;
|
|
3042
|
+
flex-direction: row !important;
|
|
3043
|
+
align-items: center !important;
|
|
3044
|
+
justify-content: space-between !important;
|
|
3045
|
+
gap: 8px !important;
|
|
3046
|
+
min-height: 32px !important;
|
|
3047
|
+
width: 100% !important;
|
|
3048
|
+
box-sizing: border-box !important;
|
|
3049
|
+
}
|
|
3050
|
+
|
|
3051
|
+
/* 移动端将原有嵌入在内容区的长面包屑标题隐藏(已统一提升至顶部导航栏正中),彻底释放第二行空间 */
|
|
3052
|
+
nav[class*="wSkVaW_crumbs"],
|
|
3053
|
+
nav[class*="crumbs"],
|
|
3054
|
+
div[class*="wSkVaW_crumbs"],
|
|
3055
|
+
div[class*="crumbs"],
|
|
3056
|
+
[class*="wSkVaW_crumbs"] {
|
|
3057
|
+
display: none !important;
|
|
3058
|
+
}
|
|
3059
|
+
|
|
3060
|
+
/* 子代理/智能体模式胶囊 (Actions):紧凑圆角胶囊 */
|
|
3061
|
+
div[class*="wSkVaW_headerActions"],
|
|
3062
|
+
div[class*="headerActions"] {
|
|
3063
|
+
flex: 0 0 auto !important;
|
|
3064
|
+
display: inline-flex !important;
|
|
3065
|
+
align-items: center !important;
|
|
3066
|
+
gap: 4px !important;
|
|
3067
|
+
margin-left: 0 !important;
|
|
3068
|
+
}
|
|
3069
|
+
|
|
3070
|
+
button[class*="h8S2Va_trigger"],
|
|
3071
|
+
button[class*="subagent"] {
|
|
3072
|
+
min-height: 26px !important;
|
|
3073
|
+
height: 26px !important;
|
|
3074
|
+
padding: 2px 8px !important;
|
|
3075
|
+
font-size: 11.5px !important;
|
|
3076
|
+
line-height: 16px !important;
|
|
3077
|
+
border-radius: 13px !important;
|
|
3078
|
+
background: var(--dsw-alias-bg-layer-2, rgba(0, 0, 0, 0.04)) !important;
|
|
3079
|
+
white-space: nowrap !important;
|
|
3080
|
+
flex-shrink: 0 !important;
|
|
3081
|
+
}
|
|
3082
|
+
|
|
3083
|
+
/* Session Log 导出下载按钮 (Utilities):在移动端极简为 28px 圆形纯图标按钮,隐藏长文本,极大释放顶部空间 */
|
|
3084
|
+
div[class*="wSkVaW_headerUtilities"],
|
|
3085
|
+
div[class*="headerUtilities"] {
|
|
3086
|
+
flex: 0 0 auto !important;
|
|
3087
|
+
margin-left: 4px !important;
|
|
3088
|
+
display: inline-flex !important;
|
|
3089
|
+
align-items: center !important;
|
|
3090
|
+
}
|
|
3091
|
+
|
|
3092
|
+
button[class*="nL4_yW_sessionLogButton"],
|
|
3093
|
+
button[class*="sessionLogButton"] {
|
|
3094
|
+
min-width: 28px !important;
|
|
3095
|
+
width: 28px !important;
|
|
3096
|
+
height: 28px !important;
|
|
3097
|
+
padding: 0 !important;
|
|
3098
|
+
border-radius: 50% !important;
|
|
3099
|
+
display: inline-flex !important;
|
|
3100
|
+
align-items: center !important;
|
|
3101
|
+
justify-content: center !important;
|
|
3102
|
+
flex-shrink: 0 !important;
|
|
3103
|
+
border: 1px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.1)) !important;
|
|
3104
|
+
background: var(--dsw-alias-bg-layer-2, rgba(0, 0, 0, 0.03)) !important;
|
|
3105
|
+
color: var(--dsw-alias-label-secondary, #6b7280) !important;
|
|
3106
|
+
margin-left: 0 !important;
|
|
3107
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03) !important;
|
|
3108
|
+
}
|
|
3109
|
+
|
|
3110
|
+
button[class*="nL4_yW_sessionLogButton"]:hover:not(:disabled),
|
|
3111
|
+
button[class*="sessionLogButton"]:hover:not(:disabled) {
|
|
3112
|
+
background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.06)) !important;
|
|
3113
|
+
color: var(--dsw-alias-label-primary, #111827) !important;
|
|
3114
|
+
}
|
|
3115
|
+
|
|
3116
|
+
button[class*="nL4_yW_sessionLogButton"] span,
|
|
3117
|
+
button[class*="sessionLogButton"] span {
|
|
3118
|
+
display: none !important;
|
|
3119
|
+
}
|
|
3120
|
+
|
|
3121
|
+
button[class*="nL4_yW_sessionLogButton"] svg,
|
|
3122
|
+
button[class*="sessionLogButton"] svg {
|
|
3123
|
+
width: 13px !important;
|
|
3124
|
+
height: 13px !important;
|
|
3125
|
+
margin: 0 !important;
|
|
3126
|
+
}
|
|
3127
|
+
|
|
3128
|
+
/* 子代理展开菜单在移动端右对齐与宽度自适应 */
|
|
3129
|
+
div[class*="h8S2Va_menu"] {
|
|
3130
|
+
max-width: calc(100vw - 32px) !important;
|
|
3131
|
+
left: auto !important;
|
|
3132
|
+
right: 0 !important;
|
|
3133
|
+
}
|
|
3134
|
+
|
|
2892
3135
|
/* 输入框底座:DeepSeek App 居中及底部固定 */
|
|
2893
3136
|
div[class*="wSkVaW_scrollBody"] {
|
|
2894
3137
|
padding-bottom: max(16px, env(safe-area-inset-bottom)) !important;
|
|
@@ -2903,6 +3146,67 @@ function injectMobileStyles() {
|
|
|
2903
3146
|
background: var(--dsw-alias-bg-layer-2, #f4f4f7) !important;
|
|
2904
3147
|
}
|
|
2905
3148
|
|
|
3149
|
+
/* 输入框底部工具栏:弹性自适应,彻底杜绝权限选择器(Full access)与模型选择器重叠碰撞 */
|
|
3150
|
+
div[class*="uV2eYG_row"] {
|
|
3151
|
+
display: flex !important;
|
|
3152
|
+
align-items: center !important;
|
|
3153
|
+
justify-content: space-between !important;
|
|
3154
|
+
gap: 6px !important;
|
|
3155
|
+
width: 100% !important;
|
|
3156
|
+
padding: 2px 2px 4px !important;
|
|
3157
|
+
box-sizing: border-box !important;
|
|
3158
|
+
}
|
|
3159
|
+
|
|
3160
|
+
div[class*="uV2eYG_tools"] {
|
|
3161
|
+
display: flex !important;
|
|
3162
|
+
align-items: center !important;
|
|
3163
|
+
gap: 6px !important;
|
|
3164
|
+
flex: 0 0 auto !important;
|
|
3165
|
+
min-width: 0 !important;
|
|
3166
|
+
}
|
|
3167
|
+
|
|
3168
|
+
div[class*="uV2eYG_modes"] {
|
|
3169
|
+
display: flex !important;
|
|
3170
|
+
align-items: center !important;
|
|
3171
|
+
gap: 4px !important;
|
|
3172
|
+
flex: 0 0 auto !important;
|
|
3173
|
+
min-width: 0 !important;
|
|
3174
|
+
}
|
|
3175
|
+
|
|
3176
|
+
button[class*="Sh0Q9G_trigger"] {
|
|
3177
|
+
flex: 0 0 auto !important;
|
|
3178
|
+
min-width: 0 !important;
|
|
3179
|
+
}
|
|
3180
|
+
|
|
3181
|
+
div[class*="uV2eYG_trailing"] {
|
|
3182
|
+
display: flex !important;
|
|
3183
|
+
align-items: center !important;
|
|
3184
|
+
justify-content: flex-end !important;
|
|
3185
|
+
gap: 6px !important;
|
|
3186
|
+
flex: 1 1 auto !important;
|
|
3187
|
+
min-width: 0 !important;
|
|
3188
|
+
}
|
|
3189
|
+
|
|
3190
|
+
div[class*="_7KE1Ra_root"] {
|
|
3191
|
+
flex: 0 1 auto !important;
|
|
3192
|
+
min-width: 0 !important;
|
|
3193
|
+
max-width: 180px !important;
|
|
3194
|
+
}
|
|
3195
|
+
|
|
3196
|
+
button[class*="_7KE1Ra_trigger"] {
|
|
3197
|
+
max-width: 100% !important;
|
|
3198
|
+
min-width: 0 !important;
|
|
3199
|
+
flex: 1 1 auto !important;
|
|
3200
|
+
padding: 0 4px 0 6px !important;
|
|
3201
|
+
}
|
|
3202
|
+
|
|
3203
|
+
span[class*="_7KE1Ra_triggerLabel"] {
|
|
3204
|
+
overflow: hidden !important;
|
|
3205
|
+
text-overflow: ellipsis !important;
|
|
3206
|
+
white-space: nowrap !important;
|
|
3207
|
+
min-width: 0 !important;
|
|
3208
|
+
}
|
|
3209
|
+
|
|
2906
3210
|
/* 4. 原生侧边栏抽屉化 (Drawer) */
|
|
2907
3211
|
div[class*="_sidebarCol"] {
|
|
2908
3212
|
position: fixed !important;
|
|
@@ -3119,6 +3423,86 @@ function injectMobileStyles() {
|
|
|
3119
3423
|
}
|
|
3120
3424
|
}
|
|
3121
3425
|
|
|
3426
|
+
/* 远程工作区选择弹窗移动端/桌面端自适应样式 */
|
|
3427
|
+
#dsh-remote-workspace-modal {
|
|
3428
|
+
position: fixed !important;
|
|
3429
|
+
inset: 0 !important;
|
|
3430
|
+
z-index: 100000 !important;
|
|
3431
|
+
background: rgba(0, 0, 0, 0.65) !important;
|
|
3432
|
+
backdrop-filter: blur(5px) !important;
|
|
3433
|
+
display: flex !important;
|
|
3434
|
+
align-items: center !important;
|
|
3435
|
+
justify-content: center !important;
|
|
3436
|
+
padding: 16px !important;
|
|
3437
|
+
box-sizing: border-box !important;
|
|
3438
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important;
|
|
3439
|
+
color: var(--dsw-alias-label-primary, #111827) !important;
|
|
3440
|
+
}
|
|
3441
|
+
|
|
3442
|
+
.dsh-ws-dialog-card {
|
|
3443
|
+
background: var(--dsw-alias-bg-layer-1, #ffffff) !important;
|
|
3444
|
+
border: 1px solid var(--dsw-alias-border-l1, #e5e7eb) !important;
|
|
3445
|
+
border-radius: 16px !important;
|
|
3446
|
+
width: 100% !important;
|
|
3447
|
+
max-width: 620px !important;
|
|
3448
|
+
max-height: 88vh !important;
|
|
3449
|
+
display: flex !important;
|
|
3450
|
+
flex-direction: column !important;
|
|
3451
|
+
box-shadow: 0 25px 35px -5px rgba(0,0,0,0.3), 0 12px 16px -5px rgba(0,0,0,0.2) !important;
|
|
3452
|
+
overflow: hidden !important;
|
|
3453
|
+
animation: dshModalFadeIn 0.2s ease-out !important;
|
|
3454
|
+
}
|
|
3455
|
+
|
|
3456
|
+
.dsh-ws-chips-scroll {
|
|
3457
|
+
display: flex !important;
|
|
3458
|
+
align-items: center !important;
|
|
3459
|
+
gap: 6px !important;
|
|
3460
|
+
overflow-x: auto !important;
|
|
3461
|
+
white-space: nowrap !important;
|
|
3462
|
+
scrollbar-width: none !important;
|
|
3463
|
+
-ms-overflow-style: none !important;
|
|
3464
|
+
-webkit-overflow-scrolling: touch !important;
|
|
3465
|
+
padding: 2px 0 !important;
|
|
3466
|
+
}
|
|
3467
|
+
.dsh-ws-chips-scroll::-webkit-scrollbar {
|
|
3468
|
+
display: none !important;
|
|
3469
|
+
}
|
|
3470
|
+
|
|
3471
|
+
@media (max-width: 640px) {
|
|
3472
|
+
#dsh-remote-workspace-modal {
|
|
3473
|
+
align-items: flex-end !important;
|
|
3474
|
+
padding: 0 !important;
|
|
3475
|
+
}
|
|
3476
|
+
|
|
3477
|
+
.dsh-ws-dialog-card {
|
|
3478
|
+
max-height: 92dvh !important;
|
|
3479
|
+
height: 92dvh !important;
|
|
3480
|
+
border-bottom-left-radius: 0 !important;
|
|
3481
|
+
border-bottom-right-radius: 0 !important;
|
|
3482
|
+
border-left: none !important;
|
|
3483
|
+
border-right: none !important;
|
|
3484
|
+
border-bottom: none !important;
|
|
3485
|
+
max-width: 100vw !important;
|
|
3486
|
+
width: 100vw !important;
|
|
3487
|
+
margin: 0 !important;
|
|
3488
|
+
animation: dshBottomSheetUp 0.25s cubic-bezier(0.16, 1, 0.3, 1) !important;
|
|
3489
|
+
}
|
|
3490
|
+
|
|
3491
|
+
.dsh-ws-drag-handle {
|
|
3492
|
+
display: block !important;
|
|
3493
|
+
}
|
|
3494
|
+
}
|
|
3495
|
+
|
|
3496
|
+
@keyframes dshModalFadeIn {
|
|
3497
|
+
from { opacity: 0; transform: scale(0.96); }
|
|
3498
|
+
to { opacity: 1; transform: scale(1); }
|
|
3499
|
+
}
|
|
3500
|
+
|
|
3501
|
+
@keyframes dshBottomSheetUp {
|
|
3502
|
+
from { transform: translateY(100%); }
|
|
3503
|
+
to { transform: translateY(0); }
|
|
3504
|
+
}
|
|
3505
|
+
|
|
3122
3506
|
@media (min-width: 769px) {
|
|
3123
3507
|
.dsh-mobile-app-header,
|
|
3124
3508
|
.dsh-mobile-backdrop {
|
|
@@ -3129,12 +3513,13 @@ function injectMobileStyles() {
|
|
|
3129
3513
|
document.head.appendChild(style);
|
|
3130
3514
|
}
|
|
3131
3515
|
|
|
3132
|
-
function setupMobileExperience() {
|
|
3516
|
+
function setupMobileExperience(rpcCall, ctx) {
|
|
3133
3517
|
if (typeof document === 'undefined' || typeof window === 'undefined') return;
|
|
3134
3518
|
injectMobileStyles();
|
|
3135
3519
|
|
|
3136
|
-
// 1. 创建顶部 DeepSeek App 风格导航条 (Header:
|
|
3520
|
+
// 1. 创建顶部 DeepSeek App 风格导航条 (Header: 左侧双横线,中间当前会话标题,右侧(+))
|
|
3137
3521
|
let header = document.querySelector('.dsh-mobile-app-header');
|
|
3522
|
+
let titleEl = document.querySelector('.dsh-mobile-header-title');
|
|
3138
3523
|
if (!header) {
|
|
3139
3524
|
header = document.createElement('header');
|
|
3140
3525
|
header.className = 'dsh-mobile-app-header';
|
|
@@ -3157,6 +3542,11 @@ function setupMobileExperience() {
|
|
|
3157
3542
|
}
|
|
3158
3543
|
};
|
|
3159
3544
|
|
|
3545
|
+
// 中间动态会话标题 (居中展示当前会话名称,避免顶部留白)
|
|
3546
|
+
titleEl = document.createElement('div');
|
|
3547
|
+
titleEl.className = 'dsh-mobile-header-title';
|
|
3548
|
+
titleEl.innerText = '新会话';
|
|
3549
|
+
|
|
3160
3550
|
// 右侧 (+) 新建会话按钮 (DeepSeek App 圆形加号风格)
|
|
3161
3551
|
const rightBtn = document.createElement('button');
|
|
3162
3552
|
rightBtn.className = 'dsh-header-new-btn';
|
|
@@ -3174,10 +3564,30 @@ function setupMobileExperience() {
|
|
|
3174
3564
|
};
|
|
3175
3565
|
|
|
3176
3566
|
header.appendChild(leftBtn);
|
|
3567
|
+
header.appendChild(titleEl);
|
|
3177
3568
|
header.appendChild(rightBtn);
|
|
3178
3569
|
document.body.appendChild(header);
|
|
3179
3570
|
}
|
|
3180
3571
|
|
|
3572
|
+
// 绑定会话标题实时同步 (切换会话或收到首条回复自动更新)
|
|
3573
|
+
const syncMobileTitle = () => {
|
|
3574
|
+
if (!titleEl) titleEl = document.querySelector('.dsh-mobile-header-title');
|
|
3575
|
+
if (!titleEl) return;
|
|
3576
|
+
const snap = ctx?.sessions?.list?.getSnapshot?.();
|
|
3577
|
+
if (!snap) return;
|
|
3578
|
+
const cur = snap.current ? snap.byId?.[snap.current] : null;
|
|
3579
|
+
if (!cur || cur.blank) {
|
|
3580
|
+
titleEl.innerText = '新会话';
|
|
3581
|
+
} else {
|
|
3582
|
+
titleEl.innerText = cur.displayTitle || cur.title || '会话';
|
|
3583
|
+
}
|
|
3584
|
+
};
|
|
3585
|
+
|
|
3586
|
+
syncMobileTitle();
|
|
3587
|
+
if (typeof ctx?.sessions?.list?.subscribe === 'function') {
|
|
3588
|
+
ctx.sessions.list.subscribe(syncMobileTitle);
|
|
3589
|
+
}
|
|
3590
|
+
|
|
3181
3591
|
// 2. 创建背景遮罩(用于抽屉侧边栏)
|
|
3182
3592
|
let backdrop = document.querySelector('.dsh-mobile-backdrop');
|
|
3183
3593
|
if (!backdrop) {
|
|
@@ -3309,16 +3719,719 @@ function setupMobileExperience() {
|
|
|
3309
3719
|
}
|
|
3310
3720
|
}
|
|
3311
3721
|
}, { passive: true });
|
|
3722
|
+
|
|
3723
|
+
// 5. 拦截原生的「添加工作区 / 打开文件夹」操作,在远程与移动端无缝弹出网页版目录选择器(本机电脑保持原生对话框)
|
|
3724
|
+
document.addEventListener('click', (e) => {
|
|
3725
|
+
if (isLocalEnvironment()) return; // 本机电脑环境不拦截,使用系统原生文件夹对话框
|
|
3726
|
+
const btn = e.target.closest('button, [role="button"], a');
|
|
3727
|
+
if (!btn) return;
|
|
3728
|
+
if (btn.closest('#dsh-remote-workspace-modal')) return;
|
|
3729
|
+
|
|
3730
|
+
const label = (
|
|
3731
|
+
btn.getAttribute('aria-label') ||
|
|
3732
|
+
btn.innerText ||
|
|
3733
|
+
btn.title ||
|
|
3734
|
+
''
|
|
3735
|
+
).trim();
|
|
3736
|
+
|
|
3737
|
+
const isAddWorkspace = (
|
|
3738
|
+
label === '添加工作区' ||
|
|
3739
|
+
label === '新建工作区' ||
|
|
3740
|
+
label === '打开工作区' ||
|
|
3741
|
+
label === '打开文件夹' ||
|
|
3742
|
+
label === 'Add Workspace' ||
|
|
3743
|
+
label === 'Open Folder' ||
|
|
3744
|
+
label.includes('添加工作区') ||
|
|
3745
|
+
label.includes('打开工作区') ||
|
|
3746
|
+
label.includes('打开文件夹') ||
|
|
3747
|
+
btn.matches('button[aria-label*="工作区"][aria-label*="添加"], button[aria-label*="工作区"][aria-label*="打开"]')
|
|
3748
|
+
);
|
|
3749
|
+
|
|
3750
|
+
if (isAddWorkspace) {
|
|
3751
|
+
e.preventDefault();
|
|
3752
|
+
e.stopPropagation();
|
|
3753
|
+
e.stopImmediatePropagation();
|
|
3754
|
+
|
|
3755
|
+
if (typeof window.__dshOpenRemoteWorkspaceModal === 'function') {
|
|
3756
|
+
window.__dshOpenRemoteWorkspaceModal();
|
|
3757
|
+
}
|
|
3758
|
+
}
|
|
3759
|
+
}, true);
|
|
3760
|
+
}
|
|
3761
|
+
|
|
3762
|
+
// 辅助函数:HTML 转义
|
|
3763
|
+
function escapeHtml(str) {
|
|
3764
|
+
if (!str) return '';
|
|
3765
|
+
return String(str)
|
|
3766
|
+
.replace(/&/g, '&')
|
|
3767
|
+
.replace(/</g, '<')
|
|
3768
|
+
.replace(/>/g, '>')
|
|
3769
|
+
.replace(/"/g, '"')
|
|
3770
|
+
.replace(/'/g, ''');
|
|
3771
|
+
}
|
|
3772
|
+
|
|
3773
|
+
// ---- 全局网页端远程工作区目录选择弹窗 (树形层级浏览 + 面包屑导航 + 一键添加与切换) ----
|
|
3774
|
+
|
|
3775
|
+
function showRemoteWorkspaceDialog(rpcCall, onWorkspaceAdded, clientCtx, onPicked, onCancel) {
|
|
3776
|
+
if (typeof document === 'undefined' || typeof window === 'undefined') return;
|
|
3777
|
+
|
|
3778
|
+
const existing = document.getElementById('dsh-remote-workspace-modal');
|
|
3779
|
+
if (existing) existing.remove();
|
|
3780
|
+
|
|
3781
|
+
const overlay = document.createElement('div');
|
|
3782
|
+
overlay.id = 'dsh-remote-workspace-modal';
|
|
3783
|
+
overlay.style.cssText = `
|
|
3784
|
+
position: fixed;
|
|
3785
|
+
inset: 0;
|
|
3786
|
+
z-index: 100000;
|
|
3787
|
+
background: rgba(0, 0, 0, 0.65);
|
|
3788
|
+
backdrop-filter: blur(5px);
|
|
3789
|
+
display: flex;
|
|
3790
|
+
align-items: center;
|
|
3791
|
+
justify-content: center;
|
|
3792
|
+
padding: 16px;
|
|
3793
|
+
box-sizing: border-box;
|
|
3794
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
3795
|
+
color: var(--dsw-alias-label-primary, #111827);
|
|
3796
|
+
`;
|
|
3797
|
+
|
|
3798
|
+
const modal = document.createElement('div');
|
|
3799
|
+
modal.className = 'dsh-ws-dialog-card';
|
|
3800
|
+
|
|
3801
|
+
let currentPath = '';
|
|
3802
|
+
let parentPath = null;
|
|
3803
|
+
let breadcrumbs = [];
|
|
3804
|
+
let entries = [];
|
|
3805
|
+
let roots = [];
|
|
3806
|
+
let drives = [];
|
|
3807
|
+
let workspaces = [];
|
|
3808
|
+
let filterQuery = '';
|
|
3809
|
+
let showManualInput = false;
|
|
3810
|
+
let isLoading = false;
|
|
3811
|
+
let isSubmitting = false;
|
|
3812
|
+
let statusMessage = null;
|
|
3813
|
+
let isErrorMessage = false;
|
|
3814
|
+
|
|
3815
|
+
function closeModal() {
|
|
3816
|
+
document.removeEventListener('keydown', handleKeydown);
|
|
3817
|
+
overlay.style.opacity = '0';
|
|
3818
|
+
overlay.style.transition = 'opacity 0.15s ease';
|
|
3819
|
+
setTimeout(() => overlay.remove(), 150);
|
|
3820
|
+
if (typeof onCancel === 'function') {
|
|
3821
|
+
try {
|
|
3822
|
+
onCancel();
|
|
3823
|
+
} catch (e) {}
|
|
3824
|
+
}
|
|
3825
|
+
}
|
|
3826
|
+
|
|
3827
|
+
overlay.addEventListener('click', (e) => {
|
|
3828
|
+
if (e.target === overlay) closeModal();
|
|
3829
|
+
});
|
|
3830
|
+
|
|
3831
|
+
const handleKeydown = (e) => {
|
|
3832
|
+
if (e.key === 'Escape') {
|
|
3833
|
+
closeModal();
|
|
3834
|
+
}
|
|
3835
|
+
};
|
|
3836
|
+
document.addEventListener('keydown', handleKeydown);
|
|
3837
|
+
|
|
3838
|
+
function render() {
|
|
3839
|
+
const filteredEntries = (entries || []).filter(e => {
|
|
3840
|
+
if (!filterQuery.trim()) return true;
|
|
3841
|
+
return e.name.toLowerCase().includes(filterQuery.trim().toLowerCase());
|
|
3842
|
+
});
|
|
3843
|
+
|
|
3844
|
+
modal.innerHTML = `
|
|
3845
|
+
<!-- 移动端顶部下拉指示条 -->
|
|
3846
|
+
<div class="dsh-ws-drag-handle" style="width: 36px; height: 4px; background: var(--dsw-alias-border-l2, #d1d5db); border-radius: 2px; margin: 8px auto 0 auto; display: none;"></div>
|
|
3847
|
+
|
|
3848
|
+
<!-- 弹窗顶部标题栏 -->
|
|
3849
|
+
<div style="padding: 12px 16px; border-bottom: 1px solid var(--dsw-alias-border-l2, #e5e7eb); display: flex; align-items: center; justify-content: space-between; flex-shrink: 0; background: var(--dsw-alias-bg-layer-2, #f9fafb);">
|
|
3850
|
+
<div style="display: flex; align-items: center; gap: 8px; overflow: hidden;">
|
|
3851
|
+
<span style="font-size: 20px; flex-shrink: 0;">🗂️</span>
|
|
3852
|
+
<div style="overflow: hidden;">
|
|
3853
|
+
<div style="font-size: 15px; font-weight: 600; color: var(--dsw-alias-label-primary, #111827); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">选择电脑工作区</div>
|
|
3854
|
+
<div style="font-size: 11px; color: var(--dsw-alias-label-tertiary, #6b7280); margin-top: 1px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">点击进入文件夹,或点击「+ 选为工作区」直接添加并切换</div>
|
|
3855
|
+
</div>
|
|
3856
|
+
</div>
|
|
3857
|
+
<button id="dsh-ws-close-btn" style="border: none; background: none; font-size: 18px; cursor: pointer; color: var(--dsw-alias-label-tertiary, #9ca3af); padding: 4px 8px; border-radius: 6px; line-height: 1; flex-shrink: 0;">✕</button>
|
|
3858
|
+
</div>
|
|
3859
|
+
|
|
3860
|
+
<div style="padding: 12px 16px; overflow-y: auto; flex: 1; display: flex; flex-direction: column; gap: 10px;">
|
|
3861
|
+
<!-- 提示信息横幅 -->
|
|
3862
|
+
${statusMessage ? `
|
|
3863
|
+
<div style="padding: 8px 12px; border-radius: 8px; font-size: 12px; line-height: 1.5; font-weight: 500; display: flex; align-items: center; gap: 8px; ${isErrorMessage ? 'background: var(--dsw-alias-state-error-bg, #fef2f2); border: 1px solid var(--dsw-alias-state-error-border, #fecaca); color: var(--dsw-alias-state-error-primary, #dc2626);' : 'background: var(--dsw-alias-state-success-bg, #ecfdf5); border: 1px solid var(--dsw-alias-state-success-border, #a7f3d0); color: var(--dsw-alias-state-success-primary, #059669);'}">
|
|
3864
|
+
<span>${isErrorMessage ? '⚠️' : '🎉'}</span>
|
|
3865
|
+
<span>${escapeHtml(statusMessage)}</span>
|
|
3866
|
+
</div>
|
|
3867
|
+
` : ''}
|
|
3868
|
+
|
|
3869
|
+
<!-- 快速直达与磁盘横向滑动栏 (极简省空间) -->
|
|
3870
|
+
<div class="dsh-ws-chips-scroll">
|
|
3871
|
+
${(drives || []).map(d => {
|
|
3872
|
+
const isActive = currentPath.startsWith(d.path) || currentPath === d.path;
|
|
3873
|
+
return `
|
|
3874
|
+
<button class="dsh-ws-quick-btn" data-path="${escapeHtml(d.path)}" style="border: 1px solid ${isActive ? 'var(--dsw-alias-brand-primary, #4f6ef7)' : 'var(--dsw-alias-border-l2, #d1d5db)'}; background: ${isActive ? 'var(--dsw-alias-brand-primary, #4f6ef7)' : 'var(--dsw-alias-bg-layer-2, #f9fafb)'}; color: ${isActive ? '#fff' : 'var(--dsw-alias-label-primary, #111827)'}; border-radius: 14px; padding: 4px 10px; font-size: 11px; cursor: pointer; font-weight: 500; flex-shrink: 0; transition: all 0.1s;">
|
|
3875
|
+
💾 ${escapeHtml(d.name)}
|
|
3876
|
+
</button>
|
|
3877
|
+
`;
|
|
3878
|
+
}).join('')}
|
|
3879
|
+
<span style="color: var(--dsw-alias-border-l2, #d1d5db); margin: 0 1px; flex-shrink: 0;">|</span>
|
|
3880
|
+
${(roots || []).map(r => {
|
|
3881
|
+
const isActive = currentPath === r.path;
|
|
3882
|
+
return `
|
|
3883
|
+
<button class="dsh-ws-quick-btn" data-path="${escapeHtml(r.path)}" style="border: 1px solid ${isActive ? 'var(--dsw-alias-brand-primary, #4f6ef7)' : 'var(--dsw-alias-border-l2, #d1d5db)'}; background: ${isActive ? 'var(--dsw-alias-state-info-bg, #eff6ff)' : 'var(--dsw-alias-bg-layer-2, #f9fafb)'}; color: ${isActive ? 'var(--dsw-alias-brand-primary, #4f6ef7)' : 'var(--dsw-alias-label-secondary, #374151)'}; border-radius: 14px; padding: 4px 10px; font-size: 11px; cursor: pointer; font-weight: 500; flex-shrink: 0;">
|
|
3884
|
+
${escapeHtml(r.name)}
|
|
3885
|
+
</button>
|
|
3886
|
+
`;
|
|
3887
|
+
}).join('')}
|
|
3888
|
+
</div>
|
|
3889
|
+
|
|
3890
|
+
<!-- 交互式面包屑路径导航条 (Breadcrumbs Bar) -->
|
|
3891
|
+
<div style="background: var(--dsw-alias-bg-layer-3, #f3f4f6); border: 1px solid var(--dsw-alias-border-l2, #e5e7eb); border-radius: 10px; padding: 6px 10px; display: flex; align-items: center; justify-content: space-between; gap: 6px;">
|
|
3892
|
+
<div class="dsh-ws-chips-scroll" style="flex: 1;">
|
|
3893
|
+
<span style="font-size: 12px; margin-right: 2px; flex-shrink: 0;">📂</span>
|
|
3894
|
+
${(breadcrumbs || []).map((crumb, idx) => {
|
|
3895
|
+
const isLast = idx === breadcrumbs.length - 1;
|
|
3896
|
+
return `
|
|
3897
|
+
<button class="dsh-ws-crumb-btn" data-path="${escapeHtml(crumb.path)}" style="border: none; background: ${isLast ? 'var(--dsw-alias-bg-layer-1, #fff)' : 'transparent'}; color: ${isLast ? 'var(--dsw-alias-brand-primary, #4f6ef7)' : 'var(--dsw-alias-label-secondary, #4b5563)'}; font-family: ui-monospace, Menlo, monospace; font-size: 11px; font-weight: ${isLast ? '700' : '500'}; padding: 3px 6px; border-radius: 4px; cursor: pointer; text-decoration: ${isLast ? 'none' : 'underline'}; text-underline-offset: 2px; flex-shrink: 0; box-shadow: ${isLast ? '0 1px 2px rgba(0,0,0,0.06)' : 'none'};">
|
|
3898
|
+
${escapeHtml(crumb.name)}
|
|
3899
|
+
</button>
|
|
3900
|
+
${!isLast ? `<span style="color: var(--dsw-alias-label-tertiary, #9ca3af); font-size: 11px; font-weight: 600; flex-shrink: 0;">/</span>` : ''}
|
|
3901
|
+
`;
|
|
3902
|
+
}).join('')}
|
|
3903
|
+
</div>
|
|
3904
|
+
|
|
3905
|
+
<div style="display: flex; align-items: center; gap: 4px; flex-shrink: 0;">
|
|
3906
|
+
${parentPath ? `
|
|
3907
|
+
<button id="dsh-ws-up-btn" data-path="${escapeHtml(parentPath)}" title="返回上一级" style="border: 1px solid var(--dsw-alias-border-l2, #d1d5db); background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #111827); padding: 3px 8px; border-radius: 6px; font-size: 11px; font-weight: 600; cursor: pointer; display: inline-flex; align-items: center; gap: 2px;">
|
|
3908
|
+
⬆️ 上级
|
|
3909
|
+
</button>
|
|
3910
|
+
` : ''}
|
|
3911
|
+
<button id="dsh-ws-refresh-btn" title="刷新目录" style="border: 1px solid var(--dsw-alias-border-l2, #d1d5db); background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #111827); padding: 3px 6px; border-radius: 6px; font-size: 11px; cursor: pointer;">
|
|
3912
|
+
🔄
|
|
3913
|
+
</button>
|
|
3914
|
+
</div>
|
|
3915
|
+
</div>
|
|
3916
|
+
|
|
3917
|
+
<!-- 当前所在目录确认卡片 (Primary Action Card) -->
|
|
3918
|
+
<div style="background: var(--dsw-alias-state-info-bg, #eff6ff); border: 1px solid var(--dsw-alias-state-info-border, #bfdbfe); border-radius: 10px; padding: 10px 12px; display: flex; flex-direction: column; gap: 6px;">
|
|
3919
|
+
<div style="display: flex; align-items: center; justify-content: space-between; gap: 6px;">
|
|
3920
|
+
<span style="font-size: 11px; font-weight: 600; color: var(--dsw-alias-brand-primary, #2563eb); flex-shrink: 0;">当前目录:</span>
|
|
3921
|
+
<span style="font-family: ui-monospace, Menlo, monospace; font-size: 11px; color: var(--dsw-alias-label-primary, #1e3a8a); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; text-align: right; font-weight: 600;">${escapeHtml(currentPath)}</span>
|
|
3922
|
+
</div>
|
|
3923
|
+
<button id="dsh-ws-add-current-btn" style="border: none; background: var(--dsw-alias-brand-primary, #2563eb); color: #fff; height: 36px; border-radius: 8px; font-size: 13px; font-weight: 600; cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 6px; width: 100%; box-shadow: 0 2px 4px rgba(37,99,235,0.25); transition: opacity 0.1s;" ${isSubmitting ? 'disabled' : ''}>
|
|
3924
|
+
${isSubmitting ? '正在添加并切换…' : '👉 设为当前工作区并进入'}
|
|
3925
|
+
</button>
|
|
3926
|
+
</div>
|
|
3927
|
+
|
|
3928
|
+
<!-- 子目录列表与过滤栏 -->
|
|
3929
|
+
<div style="border: 1px solid var(--dsw-alias-border-l2, #e5e7eb); border-radius: 10px; overflow: hidden; display: flex; flex-direction: column; background: var(--dsw-alias-bg-layer-1, #fff);">
|
|
3930
|
+
<!-- 实时过滤搜索框 -->
|
|
3931
|
+
<div style="padding: 7px 10px; background: var(--dsw-alias-bg-layer-2, #f9fafb); border-bottom: 1px solid var(--dsw-alias-border-l2, #e5e7eb); display: flex; align-items: center; justify-content: space-between; gap: 6px;">
|
|
3932
|
+
<div style="display: flex; align-items: center; gap: 6px; flex: 1;">
|
|
3933
|
+
<span style="font-size: 11px; color: var(--dsw-alias-label-tertiary, #9ca3af);">🔍</span>
|
|
3934
|
+
<input id="dsh-ws-filter-input" type="text" value="${escapeHtml(filterQuery)}" placeholder="过滤子文件夹…" style="border: none; background: transparent; font-size: 12px; width: 100%; color: var(--dsw-alias-label-primary, #111827); outline: none;" />
|
|
3935
|
+
</div>
|
|
3936
|
+
<span style="font-size: 10px; color: var(--dsw-alias-label-tertiary, #6b7280); flex-shrink: 0;">
|
|
3937
|
+
${filteredEntries.length} 个文件夹
|
|
3938
|
+
</span>
|
|
3939
|
+
</div>
|
|
3940
|
+
|
|
3941
|
+
<!-- 子文件夹滚动列表 (移动端舒适大点按区域) -->
|
|
3942
|
+
<div style="max-height: 240px; min-height: 120px; overflow-y: auto; padding: 2px 0;">
|
|
3943
|
+
${isLoading ? `
|
|
3944
|
+
<div style="padding: 32px; text-align: center; font-size: 12px; color: var(--dsw-alias-label-tertiary, #6b7280); display: flex; flex-direction: column; align-items: center; gap: 6px;">
|
|
3945
|
+
<span style="font-size: 20px;">⏳</span>
|
|
3946
|
+
<span>正在读取目录内容…</span>
|
|
3947
|
+
</div>
|
|
3948
|
+
` : filteredEntries.length === 0 ? `
|
|
3949
|
+
<div style="padding: 26px 16px; text-align: center; font-size: 12px; color: var(--dsw-alias-label-tertiary, #6b7280); display: flex; flex-direction: column; align-items: center; gap: 4px;">
|
|
3950
|
+
<span style="font-size: 22px;">📁</span>
|
|
3951
|
+
<span>${filterQuery ? '未找到匹配的子文件夹' : '当前文件夹下没有更多子文件夹'}</span>
|
|
3952
|
+
<span style="font-size: 11px; color: var(--dsw-alias-label-tertiary, #9ca3af);">(直接点击上方蓝色按钮即可进入当前目录)</span>
|
|
3953
|
+
</div>
|
|
3954
|
+
` : filteredEntries.map(e => `
|
|
3955
|
+
<div class="dsh-ws-entry-row" data-path="${escapeHtml(e.path)}" style="display: flex; align-items: center; justify-content: space-between; padding: 9px 12px; border-bottom: 1px solid var(--dsw-alias-border-l2, #f3f4f6); cursor: pointer; font-size: 12px; transition: background 0.1s; min-height: 40px;">
|
|
3956
|
+
<div class="dsh-ws-drill-btn" data-path="${escapeHtml(e.path)}" style="display: flex; align-items: center; gap: 8px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; padding: 2px 0;">
|
|
3957
|
+
<span style="font-size: 16px; flex-shrink: 0;">📁</span>
|
|
3958
|
+
<span style="font-family: ui-monospace, Menlo, monospace; font-weight: 500; color: var(--dsw-alias-label-primary, #111827); overflow: hidden; text-overflow: ellipsis;">${escapeHtml(e.name)}</span>
|
|
3959
|
+
<span style="color: var(--dsw-alias-label-tertiary, #9ca3af); font-size: 12px; margin-left: 2px; flex-shrink: 0;">›</span>
|
|
3960
|
+
</div>
|
|
3961
|
+
<button class="dsh-ws-pick-entry-btn" data-path="${escapeHtml(e.path)}" title="直接添加此子文件夹为工作区并进入" style="border: 1px solid var(--dsw-alias-state-success-border, #a7f3d0); background: var(--dsw-alias-state-success-bg, #ecfdf5); color: var(--dsw-alias-state-success-primary, #059669); padding: 4px 10px; border-radius: 12px; font-size: 11px; font-weight: 600; cursor: pointer; display: inline-flex; align-items: center; gap: 2px; flex-shrink: 0; margin-left: 8px; white-space: nowrap; transition: all 0.1s;">
|
|
3962
|
+
+ 选为工作区
|
|
3963
|
+
</button>
|
|
3964
|
+
</div>
|
|
3965
|
+
`).join('')}
|
|
3966
|
+
</div>
|
|
3967
|
+
</div>
|
|
3968
|
+
|
|
3969
|
+
<!-- 手动输入路径折叠区 -->
|
|
3970
|
+
<div>
|
|
3971
|
+
<div style="display: flex; align-items: center; justify-content: space-between;">
|
|
3972
|
+
<button id="dsh-ws-toggle-manual" style="border: none; background: none; color: var(--dsw-alias-label-tertiary, #6b7280); font-size: 11px; cursor: pointer; padding: 2px 0; text-decoration: underline;">
|
|
3973
|
+
${showManualInput ? '▼ 收起绝对路径手动输入' : '▶ 手动粘贴/输入绝对路径'}
|
|
3974
|
+
</button>
|
|
3975
|
+
</div>
|
|
3976
|
+
${showManualInput ? `
|
|
3977
|
+
<div style="margin-top: 6px; display: flex; gap: 6px;">
|
|
3978
|
+
<input id="dsh-ws-manual-input" type="text" value="${escapeHtml(currentPath)}" placeholder="输入电脑绝对路径,例如 C:\\Projects\\my-app" style="flex: 1; font-family: ui-monospace, Menlo, monospace; font-size: 11px; padding: 6px 8px; border-radius: 8px; border: 1px solid var(--dsw-alias-border-l2, #d1d5db); background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #111827); outline: none;" />
|
|
3979
|
+
<button id="dsh-ws-manual-jump-btn" style="border: 1px solid var(--dsw-alias-border-l2, #d1d5db); background: var(--dsw-alias-bg-layer-2, #f9fafb); color: var(--dsw-alias-label-primary, #111827); padding: 0 10px; border-radius: 8px; font-size: 11px; cursor: pointer; white-space: nowrap;">
|
|
3980
|
+
前往
|
|
3981
|
+
</button>
|
|
3982
|
+
<button id="dsh-ws-manual-add-btn" style="border: none; background: var(--dsw-alias-brand-primary, #4f6ef7); color: #fff; padding: 0 12px; border-radius: 8px; font-size: 11px; font-weight: 500; cursor: pointer; white-space: nowrap;">
|
|
3983
|
+
添加并进入
|
|
3984
|
+
</button>
|
|
3985
|
+
</div>
|
|
3986
|
+
` : ''}
|
|
3987
|
+
</div>
|
|
3988
|
+
|
|
3989
|
+
<!-- 已在 DSH 注册的工作区展示 (支持一键切换) -->
|
|
3990
|
+
${(workspaces && workspaces.length > 0) ? `
|
|
3991
|
+
<div style="padding-top: 2px;">
|
|
3992
|
+
<div style="font-size: 11px; font-weight: 600; color: var(--dsw-alias-label-tertiary, #6b7280); margin-bottom: 4px;">已注册工作区 (${workspaces.length} 个,点击直接切换):</div>
|
|
3993
|
+
<div style="display: flex; flex-direction: column; gap: 4px; max-height: 80px; overflow-y: auto;">
|
|
3994
|
+
${workspaces.map((w, i) => `
|
|
3995
|
+
<div class="dsh-ws-registered-row" data-ws-id="${escapeHtml(w.id || '')}" data-ws-path="${escapeHtml(w.path)}" style="display: flex; align-items: center; justify-content: space-between; background: var(--dsw-alias-bg-layer-2, #f9fafb); border: 1px solid var(--dsw-alias-border-l2, #e5e7eb); border-radius: 6px; padding: 4px 8px; font-size: 11px; cursor: pointer; transition: background 0.1s;">
|
|
3996
|
+
<div style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1;">
|
|
3997
|
+
<span style="font-weight: 600; color: var(--dsw-alias-brand-primary, #4f6ef7);">@${i + 1} ${escapeHtml(w.title || '')}</span>
|
|
3998
|
+
<span style="color: var(--dsw-alias-label-tertiary, #6b7280); margin-left: 6px; font-family: ui-monospace, Menlo, monospace; font-size: 10px;">${escapeHtml(w.path)}</span>
|
|
3999
|
+
</div>
|
|
4000
|
+
<button class="dsh-ws-switch-btn" data-ws-id="${escapeHtml(w.id || '')}" data-ws-path="${escapeHtml(w.path)}" style="border: 1px solid var(--dsw-alias-brand-primary, #4f6ef7); background: var(--dsw-alias-state-info-bg, #eff6ff); color: var(--dsw-alias-brand-primary, #4f6ef7); padding: 2px 8px; border-radius: 6px; font-size: 10px; font-weight: 600; cursor: pointer; flex-shrink: 0; margin-left: 6px; white-space: nowrap;">
|
|
4001
|
+
进入 ➔
|
|
4002
|
+
</button>
|
|
4003
|
+
</div>
|
|
4004
|
+
`).join('')}
|
|
4005
|
+
</div>
|
|
4006
|
+
</div>
|
|
4007
|
+
` : ''}
|
|
4008
|
+
</div>
|
|
4009
|
+
|
|
4010
|
+
<!-- 弹窗底部操作条 -->
|
|
4011
|
+
<div style="padding: 8px 16px; border-top: 1px solid var(--dsw-alias-border-l2, #e5e7eb); display: flex; align-items: center; justify-content: space-between; background: var(--dsw-alias-bg-layer-2, #f9fafb); flex-shrink: 0;">
|
|
4012
|
+
<span style="font-size: 10px; color: var(--dsw-alias-label-tertiary, #6b7280);">
|
|
4013
|
+
💡 点击文件夹可逐级进入
|
|
4014
|
+
</span>
|
|
4015
|
+
<button id="dsh-ws-cancel-btn" style="border: 1px solid var(--dsw-alias-border-l2, #d1d5db); background: var(--dsw-alias-bg-layer-1, #fff); color: var(--dsw-alias-label-primary, #111827); padding: 5px 14px; border-radius: 8px; font-size: 12px; cursor: pointer; font-weight: 500;">关闭</button>
|
|
4016
|
+
</div>
|
|
4017
|
+
`;
|
|
4018
|
+
|
|
4019
|
+
// 绑定事件处理器
|
|
4020
|
+
modal.querySelector('#dsh-ws-close-btn')?.addEventListener('click', closeModal);
|
|
4021
|
+
modal.querySelector('#dsh-ws-cancel-btn')?.addEventListener('click', closeModal);
|
|
4022
|
+
|
|
4023
|
+
// 面包屑点击
|
|
4024
|
+
modal.querySelectorAll('.dsh-ws-crumb-btn').forEach(btn => {
|
|
4025
|
+
btn.addEventListener('click', () => {
|
|
4026
|
+
const p = btn.getAttribute('data-path');
|
|
4027
|
+
if (p) loadDirectory(p);
|
|
4028
|
+
});
|
|
4029
|
+
});
|
|
4030
|
+
|
|
4031
|
+
// 快捷盘符与常用目录点击
|
|
4032
|
+
modal.querySelectorAll('.dsh-ws-quick-btn').forEach(btn => {
|
|
4033
|
+
btn.addEventListener('click', () => {
|
|
4034
|
+
const p = btn.getAttribute('data-path');
|
|
4035
|
+
if (p) loadDirectory(p);
|
|
4036
|
+
});
|
|
4037
|
+
});
|
|
4038
|
+
|
|
4039
|
+
// 返回上一级与刷新
|
|
4040
|
+
modal.querySelector('#dsh-ws-up-btn')?.addEventListener('click', (e) => {
|
|
4041
|
+
const p = e.currentTarget.getAttribute('data-path');
|
|
4042
|
+
if (p) loadDirectory(p);
|
|
4043
|
+
});
|
|
4044
|
+
modal.querySelector('#dsh-ws-refresh-btn')?.addEventListener('click', () => {
|
|
4045
|
+
loadDirectory(currentPath);
|
|
4046
|
+
});
|
|
4047
|
+
|
|
4048
|
+
// 添加当前目录为工作区
|
|
4049
|
+
modal.querySelector('#dsh-ws-add-current-btn')?.addEventListener('click', () => {
|
|
4050
|
+
doSubmit(currentPath);
|
|
4051
|
+
});
|
|
4052
|
+
|
|
4053
|
+
// 过滤输入框
|
|
4054
|
+
const filterInput = modal.querySelector('#dsh-ws-filter-input');
|
|
4055
|
+
if (filterInput) {
|
|
4056
|
+
filterInput.addEventListener('input', (e) => {
|
|
4057
|
+
filterQuery = e.target.value;
|
|
4058
|
+
render();
|
|
4059
|
+
const nextInput = modal.querySelector('#dsh-ws-filter-input');
|
|
4060
|
+
if (nextInput) {
|
|
4061
|
+
nextInput.focus();
|
|
4062
|
+
nextInput.selectionStart = nextInput.selectionEnd = nextInput.value.length;
|
|
4063
|
+
}
|
|
4064
|
+
});
|
|
4065
|
+
}
|
|
4066
|
+
|
|
4067
|
+
// 深入文件夹
|
|
4068
|
+
modal.querySelectorAll('.dsh-ws-drill-btn').forEach(btn => {
|
|
4069
|
+
btn.addEventListener('click', (e) => {
|
|
4070
|
+
e.stopPropagation();
|
|
4071
|
+
const p = btn.getAttribute('data-path');
|
|
4072
|
+
if (p) loadDirectory(p);
|
|
4073
|
+
});
|
|
4074
|
+
});
|
|
4075
|
+
|
|
4076
|
+
// 列表项点击(整行进入文件夹)
|
|
4077
|
+
modal.querySelectorAll('.dsh-ws-entry-row').forEach(row => {
|
|
4078
|
+
row.addEventListener('click', (e) => {
|
|
4079
|
+
if (e.target.closest('.dsh-ws-pick-entry-btn')) return;
|
|
4080
|
+
const p = row.getAttribute('data-path');
|
|
4081
|
+
if (p) loadDirectory(p);
|
|
4082
|
+
});
|
|
4083
|
+
});
|
|
4084
|
+
|
|
4085
|
+
// 快捷选为工作区按钮
|
|
4086
|
+
modal.querySelectorAll('.dsh-ws-pick-entry-btn').forEach(btn => {
|
|
4087
|
+
btn.addEventListener('click', (e) => {
|
|
4088
|
+
e.stopPropagation();
|
|
4089
|
+
const p = btn.getAttribute('data-path');
|
|
4090
|
+
if (p) doSubmit(p);
|
|
4091
|
+
});
|
|
4092
|
+
});
|
|
4093
|
+
|
|
4094
|
+
// 已注册工作区切换按钮与整行点击
|
|
4095
|
+
modal.querySelectorAll('.dsh-ws-registered-row, .dsh-ws-switch-btn').forEach(el => {
|
|
4096
|
+
el.addEventListener('click', (e) => {
|
|
4097
|
+
e.stopPropagation();
|
|
4098
|
+
const wsId = el.getAttribute('data-ws-id');
|
|
4099
|
+
const wsPath = el.getAttribute('data-ws-path');
|
|
4100
|
+
if (wsId || wsPath) {
|
|
4101
|
+
switchToWorkspace(wsId, wsPath);
|
|
4102
|
+
}
|
|
4103
|
+
});
|
|
4104
|
+
});
|
|
4105
|
+
|
|
4106
|
+
// 手动输入折叠切换
|
|
4107
|
+
modal.querySelector('#dsh-ws-toggle-manual')?.addEventListener('click', () => {
|
|
4108
|
+
showManualInput = !showManualInput;
|
|
4109
|
+
render();
|
|
4110
|
+
});
|
|
4111
|
+
|
|
4112
|
+
// 手动前往与添加
|
|
4113
|
+
const manualInput = modal.querySelector('#dsh-ws-manual-input');
|
|
4114
|
+
modal.querySelector('#dsh-ws-manual-jump-btn')?.addEventListener('click', () => {
|
|
4115
|
+
if (manualInput && manualInput.value.trim()) {
|
|
4116
|
+
loadDirectory(manualInput.value.trim());
|
|
4117
|
+
}
|
|
4118
|
+
});
|
|
4119
|
+
modal.querySelector('#dsh-ws-manual-add-btn')?.addEventListener('click', () => {
|
|
4120
|
+
if (manualInput && manualInput.value.trim()) {
|
|
4121
|
+
doSubmit(manualInput.value.trim());
|
|
4122
|
+
}
|
|
4123
|
+
});
|
|
4124
|
+
if (manualInput) {
|
|
4125
|
+
manualInput.addEventListener('keydown', (e) => {
|
|
4126
|
+
if (e.key === 'Enter') {
|
|
4127
|
+
doSubmit(manualInput.value.trim());
|
|
4128
|
+
}
|
|
4129
|
+
});
|
|
4130
|
+
}
|
|
4131
|
+
}
|
|
4132
|
+
|
|
4133
|
+
async function authRpc(endpoint, payload = {}) {
|
|
4134
|
+
let token = getGlobalAdminToken();
|
|
4135
|
+
if (!token && isLocalEnvironment()) {
|
|
4136
|
+
try {
|
|
4137
|
+
const res = await fetch('/__dsh_bridge__/loopback-token', { method: 'POST' });
|
|
4138
|
+
if (res.ok) {
|
|
4139
|
+
const data = await res.json();
|
|
4140
|
+
if (data?.adminToken) {
|
|
4141
|
+
token = data.adminToken;
|
|
4142
|
+
setGlobalAdminToken(token);
|
|
4143
|
+
}
|
|
4144
|
+
}
|
|
4145
|
+
} catch {}
|
|
4146
|
+
}
|
|
4147
|
+
return rpcCall(endpoint, {
|
|
4148
|
+
...payload,
|
|
4149
|
+
...(token ? { adminToken: token } : {}),
|
|
4150
|
+
...(isLocalEnvironment() ? { isLocalhost: true } : {}),
|
|
4151
|
+
});
|
|
4152
|
+
}
|
|
4153
|
+
|
|
4154
|
+
async function switchToWorkspace(wsId, wsPath) {
|
|
4155
|
+
if (isSubmitting) return;
|
|
4156
|
+
isSubmitting = true;
|
|
4157
|
+
statusMessage = `正在切换工作区…`;
|
|
4158
|
+
isErrorMessage = false;
|
|
4159
|
+
render();
|
|
4160
|
+
|
|
4161
|
+
if (typeof onPicked === 'function' && wsPath) {
|
|
4162
|
+
try {
|
|
4163
|
+
onPicked(wsPath);
|
|
4164
|
+
} catch (e) {}
|
|
4165
|
+
}
|
|
4166
|
+
|
|
4167
|
+
let switched = false;
|
|
4168
|
+
if (clientCtx?.workspaces?.startSession && wsId) {
|
|
4169
|
+
try {
|
|
4170
|
+
clientCtx.workspaces.startSession(wsId);
|
|
4171
|
+
switched = true;
|
|
4172
|
+
} catch (e) {
|
|
4173
|
+
console.warn('[dsh-bridge] startSession failed:', e);
|
|
4174
|
+
}
|
|
4175
|
+
}
|
|
4176
|
+
|
|
4177
|
+
if (!switched && wsPath) {
|
|
4178
|
+
try {
|
|
4179
|
+
if (clientCtx?.workspaces?.create) {
|
|
4180
|
+
const ws = await clientCtx.workspaces.create({ path: wsPath });
|
|
4181
|
+
if (ws?.workspaceId && clientCtx?.workspaces?.startSession) {
|
|
4182
|
+
clientCtx.workspaces.startSession(ws.workspaceId);
|
|
4183
|
+
switched = true;
|
|
4184
|
+
}
|
|
4185
|
+
}
|
|
4186
|
+
if (!switched) {
|
|
4187
|
+
const raw = await authRpc(BRIDGE_ENDPOINTS.addRemoteWorkspace, { path: wsPath });
|
|
4188
|
+
const res = raw?.value || raw;
|
|
4189
|
+
if (res?.workspaceId && clientCtx?.workspaces?.startSession) {
|
|
4190
|
+
try {
|
|
4191
|
+
clientCtx.workspaces.startSession(res.workspaceId);
|
|
4192
|
+
switched = true;
|
|
4193
|
+
} catch (e) {}
|
|
4194
|
+
}
|
|
4195
|
+
if (!switched && res?.sessionId && clientCtx?.sessions?.open) {
|
|
4196
|
+
try {
|
|
4197
|
+
clientCtx.sessions.open(res.sessionId);
|
|
4198
|
+
switched = true;
|
|
4199
|
+
} catch (e) {}
|
|
4200
|
+
}
|
|
4201
|
+
}
|
|
4202
|
+
} catch (e) {}
|
|
4203
|
+
}
|
|
4204
|
+
|
|
4205
|
+
statusMessage = `✓ 已切换至工作区!`;
|
|
4206
|
+
render();
|
|
4207
|
+
setTimeout(() => {
|
|
4208
|
+
closeModal();
|
|
4209
|
+
document.body.classList.remove('dsh-drawer-open');
|
|
4210
|
+
}, 400);
|
|
4211
|
+
}
|
|
4212
|
+
|
|
4213
|
+
async function loadDirectory(targetPath) {
|
|
4214
|
+
if (isSubmitting) return;
|
|
4215
|
+
if (!rpcCall) return;
|
|
4216
|
+
isLoading = true;
|
|
4217
|
+
filterQuery = '';
|
|
4218
|
+
statusMessage = null;
|
|
4219
|
+
isErrorMessage = false;
|
|
4220
|
+
render();
|
|
4221
|
+
|
|
4222
|
+
try {
|
|
4223
|
+
const raw = await authRpc(BRIDGE_ENDPOINTS.listRemoteDirectories, { path: targetPath });
|
|
4224
|
+
const res = raw?.value || raw;
|
|
4225
|
+
if (res) {
|
|
4226
|
+
currentPath = res.currentPath || targetPath || '';
|
|
4227
|
+
parentPath = res.parentPath || null;
|
|
4228
|
+
breadcrumbs = res.breadcrumbs || [];
|
|
4229
|
+
entries = res.entries || [];
|
|
4230
|
+
roots = res.roots || [];
|
|
4231
|
+
drives = res.drives || [];
|
|
4232
|
+
workspaces = res.workspaces || [];
|
|
4233
|
+
if (res.error) {
|
|
4234
|
+
statusMessage = res.error;
|
|
4235
|
+
isErrorMessage = true;
|
|
4236
|
+
}
|
|
4237
|
+
}
|
|
4238
|
+
} catch (err) {
|
|
4239
|
+
statusMessage = err.message || '读取目录失败';
|
|
4240
|
+
isErrorMessage = true;
|
|
4241
|
+
} finally {
|
|
4242
|
+
isLoading = false;
|
|
4243
|
+
render();
|
|
4244
|
+
}
|
|
4245
|
+
}
|
|
4246
|
+
|
|
4247
|
+
async function doSubmit(pathToRegister) {
|
|
4248
|
+
if (isSubmitting) return;
|
|
4249
|
+
const p = (pathToRegister || currentPath || '').trim();
|
|
4250
|
+
if (!p) {
|
|
4251
|
+
statusMessage = '请输入或选择工作区路径';
|
|
4252
|
+
isErrorMessage = true;
|
|
4253
|
+
render();
|
|
4254
|
+
return;
|
|
4255
|
+
}
|
|
4256
|
+
if (!rpcCall) return;
|
|
4257
|
+
|
|
4258
|
+
isSubmitting = true;
|
|
4259
|
+
statusMessage = null;
|
|
4260
|
+
isErrorMessage = false;
|
|
4261
|
+
render();
|
|
4262
|
+
|
|
4263
|
+
try {
|
|
4264
|
+
// 1. 先通过 clientCtx.workspaces.create 注册本地客户端快照
|
|
4265
|
+
let clientWs = null;
|
|
4266
|
+
if (clientCtx?.workspaces?.create) {
|
|
4267
|
+
try {
|
|
4268
|
+
clientWs = await clientCtx.workspaces.create({ path: p });
|
|
4269
|
+
} catch (e) {
|
|
4270
|
+
console.warn('[dsh-bridge] clientCtx.workspaces.create failed:', e);
|
|
4271
|
+
}
|
|
4272
|
+
}
|
|
4273
|
+
|
|
4274
|
+
// 2. 调用服务端 RPC 进行持久化与 session 绑定
|
|
4275
|
+
const raw = await authRpc(BRIDGE_ENDPOINTS.addRemoteWorkspace, { path: p });
|
|
4276
|
+
const res = raw?.value || raw;
|
|
4277
|
+
if (res && res.ok) {
|
|
4278
|
+
statusMessage = `✓ 工作区「${res.title || p}」已选定,正在切换…`;
|
|
4279
|
+
isErrorMessage = false;
|
|
4280
|
+
workspaces = res.workspaces || [];
|
|
4281
|
+
render();
|
|
4282
|
+
|
|
4283
|
+
const targetWorkspaceId = clientWs?.workspaceId || res.workspaceId;
|
|
4284
|
+
|
|
4285
|
+
// 如果是通过 Hero / DirectoryFlow 流程打开的,通知 Flow
|
|
4286
|
+
if (typeof onPicked === 'function') {
|
|
4287
|
+
try {
|
|
4288
|
+
onPicked(p);
|
|
4289
|
+
} catch (e) {}
|
|
4290
|
+
}
|
|
4291
|
+
|
|
4292
|
+
// 切换至新工作区并创建会话
|
|
4293
|
+
let switched = false;
|
|
4294
|
+
if (clientCtx?.workspaces?.startSession && targetWorkspaceId) {
|
|
4295
|
+
try {
|
|
4296
|
+
clientCtx.workspaces.startSession(targetWorkspaceId);
|
|
4297
|
+
switched = true;
|
|
4298
|
+
} catch (e) {
|
|
4299
|
+
console.warn('[dsh-bridge] startSession failed:', e);
|
|
4300
|
+
}
|
|
4301
|
+
}
|
|
4302
|
+
|
|
4303
|
+
if (!switched && clientCtx?.sessions?.open && res.sessionId) {
|
|
4304
|
+
try {
|
|
4305
|
+
clientCtx.sessions.open(res.sessionId);
|
|
4306
|
+
switched = true;
|
|
4307
|
+
} catch (e) {
|
|
4308
|
+
console.warn('[dsh-bridge] sessions.open failed:', e);
|
|
4309
|
+
}
|
|
4310
|
+
}
|
|
4311
|
+
|
|
4312
|
+
if (typeof onWorkspaceAdded === 'function') {
|
|
4313
|
+
onWorkspaceAdded(res);
|
|
4314
|
+
}
|
|
4315
|
+
|
|
4316
|
+
setTimeout(() => {
|
|
4317
|
+
closeModal();
|
|
4318
|
+
document.body.classList.remove('dsh-drawer-open');
|
|
4319
|
+
}, 500);
|
|
4320
|
+
} else {
|
|
4321
|
+
statusMessage = res?.error || raw?.error || '添加工作区失败';
|
|
4322
|
+
isErrorMessage = true;
|
|
4323
|
+
render();
|
|
4324
|
+
}
|
|
4325
|
+
} catch (err) {
|
|
4326
|
+
statusMessage = err.message || '添加工作区异常';
|
|
4327
|
+
isErrorMessage = true;
|
|
4328
|
+
render();
|
|
4329
|
+
} finally {
|
|
4330
|
+
isSubmitting = false;
|
|
4331
|
+
}
|
|
4332
|
+
}
|
|
4333
|
+
|
|
4334
|
+
overlay.appendChild(modal);
|
|
4335
|
+
document.body.appendChild(overlay);
|
|
4336
|
+
|
|
4337
|
+
// 初次加载目录
|
|
4338
|
+
loadDirectory('');
|
|
4339
|
+
}
|
|
4340
|
+
|
|
4341
|
+
// ---- DSH 原生 Slot 目录选择器适配组件 ----
|
|
4342
|
+
|
|
4343
|
+
function RemoteDirectoryFlow(props) {
|
|
4344
|
+
const { open, pick } = props;
|
|
4345
|
+
const outcome = React.useRef(props);
|
|
4346
|
+
outcome.current = props;
|
|
4347
|
+
const armed = React.useRef(false);
|
|
4348
|
+
|
|
4349
|
+
React.useEffect(() => {
|
|
4350
|
+
if (!open) {
|
|
4351
|
+
armed.current = false;
|
|
4352
|
+
return;
|
|
4353
|
+
}
|
|
4354
|
+
if (armed.current) return;
|
|
4355
|
+
armed.current = true;
|
|
4356
|
+
|
|
4357
|
+
// 1. 本机电脑访问(Localhost / 127.0.0.1 / Electron 客户端):直接唤起系统原生文件夹选择对话框!
|
|
4358
|
+
if (isLocalEnvironment()) {
|
|
4359
|
+
const pickFn = typeof pick === 'function' ? pick : (typeof window.__dshClientCtx?.workspaces?.pickDirectory === 'function' ? () => window.__dshClientCtx.workspaces.pickDirectory() : null);
|
|
4360
|
+
if (pickFn) {
|
|
4361
|
+
pickFn().then((chosenPath) => {
|
|
4362
|
+
if (chosenPath === null) {
|
|
4363
|
+
if (outcome.current?.onCancel) outcome.current.onCancel();
|
|
4364
|
+
} else if (chosenPath) {
|
|
4365
|
+
if (outcome.current?.onPicked) outcome.current.onPicked(chosenPath);
|
|
4366
|
+
}
|
|
4367
|
+
}, (err) => {
|
|
4368
|
+
if (outcome.current?.onError) outcome.current.onError(err instanceof Error ? err.message : String(err));
|
|
4369
|
+
});
|
|
4370
|
+
return;
|
|
4371
|
+
}
|
|
4372
|
+
}
|
|
4373
|
+
|
|
4374
|
+
// 2. 远程或移动端访问(手机或局域网跨设备/公网):呼出网页版远程目录树形选择器!
|
|
4375
|
+
if (typeof window.__dshOpenRemoteWorkspaceModal === 'function') {
|
|
4376
|
+
window.__dshOpenRemoteWorkspaceModal(
|
|
4377
|
+
(res) => {
|
|
4378
|
+
if (res?.path && outcome.current?.onPicked) {
|
|
4379
|
+
outcome.current.onPicked(res.path);
|
|
4380
|
+
}
|
|
4381
|
+
},
|
|
4382
|
+
(chosenPath) => {
|
|
4383
|
+
if (chosenPath && outcome.current?.onPicked) {
|
|
4384
|
+
outcome.current.onPicked(chosenPath);
|
|
4385
|
+
}
|
|
4386
|
+
},
|
|
4387
|
+
() => {
|
|
4388
|
+
if (outcome.current?.onCancel) {
|
|
4389
|
+
outcome.current.onCancel();
|
|
4390
|
+
}
|
|
4391
|
+
}
|
|
4392
|
+
);
|
|
4393
|
+
}
|
|
4394
|
+
}, [open, pick]);
|
|
4395
|
+
|
|
4396
|
+
return null;
|
|
3312
4397
|
}
|
|
3313
4398
|
|
|
3314
4399
|
// ---- 插件入口 ----
|
|
3315
4400
|
|
|
3316
4401
|
function apply(ctx) {
|
|
3317
|
-
|
|
3318
|
-
|
|
4402
|
+
window.__dshClientCtx = ctx;
|
|
3319
4403
|
const rpcCall = (endpoint, payload, signal) =>
|
|
3320
4404
|
ctx.connection.rpc.call(BRIDGE_RPC_CHANNEL, endpoint, payload, signal);
|
|
3321
4405
|
|
|
4406
|
+
window.__dshOpenRemoteWorkspaceModal = (onAdded, onPickDirect, onCancel) =>
|
|
4407
|
+
showRemoteWorkspaceDialog(rpcCall, onAdded, ctx, onPickDirect, onCancel);
|
|
4408
|
+
|
|
4409
|
+
setupMobileExperience(rpcCall, ctx);
|
|
4410
|
+
|
|
4411
|
+
const injected = () => ({ pick: () => ctx.workspaces?.pickDirectory?.() });
|
|
4412
|
+
|
|
4413
|
+
// 注册至 DSH 原生目录选择 Slot(设置 priority: -10 覆盖原生 Electron 选择器,在远程/移动网页端生效)
|
|
4414
|
+
ctx.slots.inject('conversation.hero.workspace.directoryFlow', () =>
|
|
4415
|
+
ctx.slots.inject('sidebar.workspaces.directoryFlow', function* () {
|
|
4416
|
+
yield ctx.slots.register(
|
|
4417
|
+
{
|
|
4418
|
+
name: 'conversation.hero.workspace.directoryFlow',
|
|
4419
|
+
priority: -10,
|
|
4420
|
+
inject: injected,
|
|
4421
|
+
},
|
|
4422
|
+
RemoteDirectoryFlow,
|
|
4423
|
+
);
|
|
4424
|
+
yield ctx.slots.register(
|
|
4425
|
+
{
|
|
4426
|
+
name: 'sidebar.workspaces.directoryFlow',
|
|
4427
|
+
priority: -10,
|
|
4428
|
+
inject: injected,
|
|
4429
|
+
},
|
|
4430
|
+
RemoteDirectoryFlow,
|
|
4431
|
+
);
|
|
4432
|
+
}),
|
|
4433
|
+
);
|
|
4434
|
+
|
|
3322
4435
|
ctx.slots.inject('settings.section', () =>
|
|
3323
4436
|
ctx.slots.register(
|
|
3324
4437
|
{
|